From 0d0827d4fcdb98b8703c3d4a29c8b1a67abab567 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sun, 5 Nov 2017 18:28:58 +0100 Subject: [PATCH 0001/1127] Updating why helmsman doc --- docs/why_helmsman.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/why_helmsman.md b/docs/why_helmsman.md index 5fb6c761..5a88e3af 100644 --- a/docs/why_helmsman.md +++ b/docs/why_helmsman.md @@ -1,4 +1,4 @@ -# Why Helmsman? +# Why Helmsman? This document describes the reasoning and need behind the inception of Helmsman. @@ -7,13 +7,13 @@ This document describes the reasoning and need behind the inception of Helmsman. Helmsman was created with continous deployment automation in mind. When we started using k8s, we deployed applications on our cluster directly from k8s manifest files. Initially, we had a custom shell script added to our CI to deploy the k8s resources on the cluster. That script could only create the k8s resources from the manifest files. Soon we needed to have a more flexible way to dynamically create/delete those resources. We structured our git repo and used custom file names (adding enabled or disabled into file names) and updated the shell script accordingly. It did not take long before we realized that this does not scale and is difficult to maintain. -![CI-pipeline-before-helm](https://github.com/praqma/helmsman/docs/images/CI-pipeline-before-helm.jpg ) +![CI-pipeline-before-helm](images/CI-pipeline-before-helm.jpg) ## Helm to the rescue? While looking for solutions for managing the growing number of k8s manifest files from a CI pipeline, we came to know about Helm and quickly releaized its potential. By creating Helm charts, we packaged related k8s manifests together into a single entity "a chart". This reduced the amount of files the CI script has to deal with. However, all the CI shell script could do is package a chart and install/upgrade it in our k8s cluster whenever a new commit is done into the chart's files in git. -![CI-pipeline-after-helm](https://github.com/praqma/helmsman/docs/images/CI-pipeline-after-helm.jpg) +![CI-pipeline-after-helm](images/CI-pipeline-after-helm.jpg) But there were a couple of issues here: 1. Helm has more to it than package and install. Operations such as rollback, running chart tests etc. are only doable from the Helm's CLI client. @@ -32,7 +32,7 @@ In English, [Helmsman](https://www.merriam-webster.com/dictionary/helmsman) is t As the diagram below shows, we recommend having a_ desired state file_ for each k8s cluster you are managing. Along with that file, you would need to have any custom [values yaml files](https://docs.helm.sh/chart_template_guide/#values-files) for the Helm chart's you deploy on your k8s. Then you could configure your CI pipeline to use Helmsman docker container to process your desired state file whenever a commit is made to it. -![CI-pipeline-helmsman](https://github.com/praqma/helmsman/docs/images/CI-pipeline-helmsman.jpg) +![CI-pipeline-helmsman](images/CI-pipeline-helmsman.jpg) -> Helmsman can also be used manually as a binary tool on a machine which has Helm and Kubectl installed. \ No newline at end of file +> Helmsman can also be used manually as a binary tool on a machine which has Helm and Kubectl installed. From 2685d3b1997f220cfb4456ed508ab531e0b4320a Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sun, 5 Nov 2017 19:37:47 +0100 Subject: [PATCH 0002/1127] adding the desired state specification doc --- docs/desired_state_specification.md | 146 ++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index e69de29b..d2254786 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -0,0 +1,146 @@ +# Helmsman desired state specification + +This document describes the specification for how to write your Helm charts desired state file. The desired state file consists of: + +- [Metadata](#Metadata) [Optional] -- metadata for any human reader of the desired state file. +- [Certifications](#Certifications) [Optional] -- only needed when you want Helmsman to connect kubectl to your cluster for you. +- [Settings](#Settings) -- data about your k8s cluster. +- [Namespaces](#Namespaces) -- defines the namespaces where you want your Helm charts to be deployed. +- [Helm Repos](#Helm-Repos) -- defines the repos where you want to get Helm charts from. +- [Apps](#Apps) -- defines the applications/charts you want to manage in your cluster. + +## Metadata + +Optional : Yes. + +Synopsis: Metadata is used for the human reader of the desired state file. While it is optional, we recommend having a maintainer and scope/cluster metadata. + +Options: +- you can define any key/value pairs. + +Example: + +``` +[metadata] +scope = "cluster foo" +maintainer = "k8s-admin" +``` + +## Certifications + +Optional : Yes, if you don't want Helmsman to connect kubectl to your cluster for you. + +Synopsis: defines where to find the certifactions needed for connecting kubectl to a k8s cluster. + +Options: +- caCrt : a valid path to a CRT file. +- caKey : a valid path to a key file. + +Example: + +``` +[certifications] +caCrt = "ca.crt" +caKey = "ca.key" +``` + +## Settings + +Optional : No. + +Synopsis: provides data about your k8s cluster. + +Options: +- kubeContext : this is always required and defines what context to use in kubectl. Helmsman will try connect to this context first, if it does not exist, it will try to create it (i.e. connect to a k8s cluster) using the options below. + +The following options can be skipped if your kubectl context is already created and you don't want Helmsman to connect kubectl to your cluster for you. When using Helmsman in CI pipeline, these details are required to connect to your cluster everytime the pipeline is executed. + +- username : the username to be used for kubectl credentials. +- password : a path to a ".passwd" file containing the password to be used for kubectl credentials. Get the password from your k8s admin or consult k8s docs on how to get it. The .passwd file should be added to your .gitignore file in your git repo. +- clusterURI : the URI for your cluster API. + +Example: + +``` +[settings] +kubeContext = "minikube" +# username = "admin" +# password = "passwd.passwd" +# clusterURI = "https://192.168.99.100:8443" +``` + +## Namespaces + +Optional : No. + +Synopsis: defines the namespaces to be used/created in your k8s cluster. You can add as many namespaces as you like. +If a namespaces does not already exist, Helmsman will be created. + +Options: +- you can define any key/value pairs. + +Example: + +``` +[namespaces] +staging = "staging" +production = "default" +``` + +## Helm Repos + +Optional : No. + +Purpose: defines the Helm repos where your charts can be found. You can add as many repos as you like. Public repos do not require authentication. Private repos require authentication. + +> Currently only AWS S3 buckets can be used for private repos (using the [Helm S3 plugin](https://github.com/hypnoglow/helm-s3)). For that you need to have valid AWS access keys in your environment variables. See [here](https://github.com/hypnoglow/helm-s3#note-on-aws-authentication) for more details. + +Options: +None. + +Example: + +``` +[helmRepos] +stable = "https://kubernetes-charts.storage.googleapis.com" +incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" +``` + +## Apps + +Optional : Yes. + +Synopsis: defines the releases (instances of Helm charts) you would like to manage in your k8s cluster. + +Releases must have unique names which are defined under `apps`. Example: in `[apps.jenkins]`, the release name will be `jenkins` and it should be unique in your cluster. + +Options: +- name : the Helm release name. Releases must have unique names within a cluster. +- description : a release metadata for human readers. +- env : the namespace where the release should be deployed. The namespace should map to one of the ones defined in [namespaces](#Namespaces). +- enabled : describes the required state of the release (true for enabled, false for disabled). Change to false if you want to delete this app release [empty = flase]. +- chart : the chart name. It should contain the repo name as well. Example: repoName/chartName. Changing the chart name means delete and reinstall this release using the new Chart. +- version : the chart version. +- valuesFile : a valid path to custom Helm values.yaml file. Leaving it empty uses the default chart values. +- purge : defines whether to use the Helm purge flag wgen deleting the release. (true/false) +- test : defines whether to run the chart tests whenever the release is installed/upgraded/rolledback. + +Example: + +> Whitespace does not matter in TOML files. You could use whatever indentation style you prefer for readability. + +``` +[apps] + + [apps.jenkins] + name = "jenkins" + description = "jenkins" + env = "staging" + enabled = true + chart = "stable/jenkins" + version = "0.9.0" + valuesFile = "" + purge = false + test = true +``` + From cd8b7e48dc38a49473fc200137b9af8da6624bb0 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sun, 5 Nov 2017 19:40:44 +0100 Subject: [PATCH 0003/1127] fixing links in the desired state specification doc --- docs/desired_state_specification.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index d2254786..c8bfbbc7 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -2,12 +2,12 @@ This document describes the specification for how to write your Helm charts desired state file. The desired state file consists of: -- [Metadata](#Metadata) [Optional] -- metadata for any human reader of the desired state file. -- [Certifications](#Certifications) [Optional] -- only needed when you want Helmsman to connect kubectl to your cluster for you. -- [Settings](#Settings) -- data about your k8s cluster. -- [Namespaces](#Namespaces) -- defines the namespaces where you want your Helm charts to be deployed. -- [Helm Repos](#Helm-Repos) -- defines the repos where you want to get Helm charts from. -- [Apps](#Apps) -- defines the applications/charts you want to manage in your cluster. +- [Metadata](#metadata) [Optional] -- metadata for any human reader of the desired state file. +- [Certifications](#certifications) [Optional] -- only needed when you want Helmsman to connect kubectl to your cluster for you. +- [Settings](#settings) -- data about your k8s cluster. +- [Namespaces](#namespaces) -- defines the namespaces where you want your Helm charts to be deployed. +- [Helm Repos](#helm-repos) -- defines the repos where you want to get Helm charts from. +- [Apps](#apps) -- defines the applications/charts you want to manage in your cluster. ## Metadata @@ -96,7 +96,7 @@ Purpose: defines the Helm repos where your charts can be found. You can add as m > Currently only AWS S3 buckets can be used for private repos (using the [Helm S3 plugin](https://github.com/hypnoglow/helm-s3)). For that you need to have valid AWS access keys in your environment variables. See [here](https://github.com/hypnoglow/helm-s3#note-on-aws-authentication) for more details. Options: -None. +- you can define any key/value pairs. Example: @@ -117,7 +117,7 @@ Releases must have unique names which are defined under `apps`. Example: in `[ap Options: - name : the Helm release name. Releases must have unique names within a cluster. - description : a release metadata for human readers. -- env : the namespace where the release should be deployed. The namespace should map to one of the ones defined in [namespaces](#Namespaces). +- env : the namespace where the release should be deployed. The namespace should map to one of the ones defined in [namespaces](#namespaces). - enabled : describes the required state of the release (true for enabled, false for disabled). Change to false if you want to delete this app release [empty = flase]. - chart : the chart name. It should contain the repo name as well. Example: repoName/chartName. Changing the chart name means delete and reinstall this release using the new Chart. - version : the chart version. From 337b8c699f2505b589a2639498d07f9192865bbc Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Thu, 9 Nov 2017 14:20:58 +0100 Subject: [PATCH 0004/1127] added a dockerfile fro helmsman. --- dockerfile/dockerfile | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 dockerfile/dockerfile diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile new file mode 100644 index 00000000..bfe679f6 --- /dev/null +++ b/dockerfile/dockerfile @@ -0,0 +1,35 @@ +FROM golang:1.9 as builder +WORKDIR /go/src/ +RUN go get github.com/BurntSushi/toml +RUN git clone https://github.com/Praqma/helmsman.git +RUN go install helmsman/. + +FROM alpine:3.6 as kube +ENV KUBE_LATEST_VERSION="v1.8.2" + +RUN apk add --update ca-certificates +RUN apk add --update -t deps curl +RUN curl -L https://storage.googleapis.com/kubernetes-release/release/${KUBE_LATEST_VERSION}/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl +RUN chmod +x /usr/local/bin/kubectl + +# The image to keep +FROM alpine:3.6 +RUN apk add --update --no-cache ca-certificates git + +ENV VERSION v2.7.0 + +WORKDIR / + +RUN apk add --update -t deps curl tar gzip make bash \ + && curl -L http://storage.googleapis.com/kubernetes-helm/helm-${VERSION}-linux-amd64.tar.gz | tar zxv -C /tmp \ + && mv /tmp/linux-amd64/helm /usr/local/bin/helm \ + && chmod +x /usr/local/bin/helm \ + && mkdir -p ~/.helm/plugins \ + && helm plugin install https://github.com/hypnoglow/helm-s3.git --version 0.4.0 \ + && rm -rf /tmp/linux-amd64 + +COPY --from=kube /usr/local/bin/kubectl /bin/kubectl +COPY --from=builder /go/bin/helmsman /bin/helmsman + +ENTRYPOINT ["/bin/helmsman"] + From 8d0bdb50583a9096fda6ebae4da7f215490f51bf Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Thu, 9 Nov 2017 14:21:24 +0100 Subject: [PATCH 0005/1127] fixed #6 --- init.go | 11 ++++++++--- utils.go | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/init.go b/init.go index 28d0ae97..53005cb2 100644 --- a/init.go +++ b/init.go @@ -167,11 +167,16 @@ func setKubeContext(context string) bool { // It returns true if successful, false otherwise func createContext() bool { + if s.Settings["password"] == "" || s.Settings["username"] == "" || s.Settings["clusterURI"] == "" { + log.Fatal("ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ] " + + "as you did not specify enough information in the Settings section of your desired state file.") + return false + } cmd := command{ Cmd: "bash", Args: []string{"-c", "kubectl config set-credentials " + s.Settings["username"] + " --username=" + s.Settings["username"] + " --password=" + readFile(s.Settings["password"]) + " --client-key=" + s.Certifications["caKey"]}, - Description: "creating kubectl context - part 1", + Description: "creating kubectl context - setting credentials.", } exitCode, _ := cmd.exec(debug) @@ -185,7 +190,7 @@ func createContext() bool { Cmd: "bash", Args: []string{"-c", "kubectl config set-cluster " + s.Settings["kubeContext"] + " --server=" + s.Settings["clusterURI"] + " --certificate-authority=" + s.Certifications["caCrt"]}, - Description: "creating kubectl context - part 2", + Description: "creating kubectl context - setting cluster.", } exitCode, _ = cmd.exec(debug) @@ -199,7 +204,7 @@ func createContext() bool { Cmd: "bash", Args: []string{"-c", "kubectl config set-context " + s.Settings["kubeContext"] + " --cluster=" + s.Settings["kubeContext"] + " --user=" + s.Settings["username"] + " --password=" + readFile(s.Settings["password"])}, - Description: "creating kubectl context - part 3", + Description: "creating kubectl context - setting context.", } exitCode, _ = cmd.exec(debug) diff --git a/utils.go b/utils.go index 3e06e22c..43b23857 100644 --- a/utils.go +++ b/utils.go @@ -68,7 +68,7 @@ func isOfType(filename string, filetype string) bool { func readFile(filepath string) string { data, err := ioutil.ReadFile(filepath) if err != nil { - log.Fatal("ERROR: failed to read password file content: " + err.Error()) + log.Fatal("ERROR: failed to read [ " + filepath + " ] file content: " + err.Error()) } return string(data) } From 1bd14c8f279b6fdd18e34859b848d25c90774425 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Thu, 9 Nov 2017 15:09:49 +0100 Subject: [PATCH 0006/1127] updated README to include the docker image usage. Closes #4 --- README.md | 11 ++++++----- dockerfile/README.md | 7 +++++++ 2 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 dockerfile/README.md diff --git a/README.md b/README.md index 5f71278a..475e810b 100644 --- a/README.md +++ b/README.md @@ -77,17 +77,18 @@ Similarly, if you change `enabled` back to `true`, it will figure out that you w Helmsman can be used in two ways: -1. In a continuous deployment pipeline. In this case Helmsman can be used in a docker container run by your CI system to maintain your desired state (which you can store in a version control repository). The docker image will be available soon. +1. In a continuous deployment pipeline. Helmsman can be used in a docker container run by your CI system to maintain your desired state (which you can store in a version control repository). The docker image is available on [dockerhub](https://hub.docker.com/r/praqma/helmsman/). -[//]: # (docker run -it praqma/helmsman ```) - -[//]: # (The docker image is built from a [dockerfile](dockerfile/dockerfile).) +``` +docker run -it --rm -v /local/path/to/your/desired_state_file:/tmp praqma/helmsman -f tmp/example.toml +``` +> The latest docker image will contain the latest build of Helmsman. 2. As a binary application. Helmsman dependes on [Helm](https://helm.sh) and [Kubectl](https://kubernetes.io/docs/user-guide/kubectl/) being installed. See below for installation. # Installation -Install Helmsman for your OS from the [releases page](https://github.com/Praqma/Helmsman/releases). Currently, available for both Linux and MacOS only. +Install Helmsman for your OS from the [releases page](https://github.com/Praqma/Helmsman/releases). Available for Linux and MacOS. # Documentaion diff --git a/dockerfile/README.md b/dockerfile/README.md new file mode 100644 index 00000000..b801a956 --- /dev/null +++ b/dockerfile/README.md @@ -0,0 +1,7 @@ +# Usage + +``` +docker run -it --rm -v /local/path/to/your/desired_state_file:/tmp praqma/helmsman -f tmp/example.toml +``` + +Check the different versions on [Dockerhub](https://hub.docker.com/r/praqma/helmsman/) \ No newline at end of file From 89ecbdce3536fb3fb664098a746e1ec63326f21e Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Thu, 9 Nov 2017 15:16:21 +0100 Subject: [PATCH 0007/1127] added absoulte links in README to fix broken links in dockerhub. --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 475e810b..b81840a5 100644 --- a/README.md +++ b/README.md @@ -4,16 +4,16 @@ Helmsman is a Helm Charts as Code tool which adds another layer of abstraction o # Why Helmsman? -Helmsman was created to ease continous deployment of Helm charts. When you want to configure a continous deployment pipeline to manage multiple charts deployed on your k8s cluster(s), a CI script will quickly become complex and difficult to maintain. That's where Helmsman comes to rescue. Read more about [how Helmsman can save you time and effort in the docs](docs/why_helmsman.md). +Helmsman was created to ease continous deployment of Helm charts. When you want to configure a continous deployment pipeline to manage multiple charts deployed on your k8s cluster(s), a CI script will quickly become complex and difficult to maintain. That's where Helmsman comes to rescue. Read more about [how Helmsman can save you time and effort in the docs](https://github.com/Praqma/helmsman/blob/master/docs/why_helmsman.md). # How does it work? -Helmsman uses a simple declarative [TOML](https://github.com/toml-lang/toml) file to allow you to describe a desired state for your k8s applications as in the [example file](example.toml). +Helmsman uses a simple declarative [TOML](https://github.com/toml-lang/toml) file to allow you to describe a desired state for your k8s applications as in the [example file](https://github.com/Praqma/helmsman/blob/master/example.toml). -The desired state file follows the [desired state specification](docs/desired_state_specification.md). +The desired state file follows the [desired state specification](https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md). -Helmsman sees what you desire, validates that your desire makes sense (e.g. that the charts you desire are available in the repos you defined), compares it with the current state of Helm and figures out what to do to make your desire come true. Below is the result of executing the [example.toml](example.toml) +Helmsman sees what you desire, validates that your desire makes sense (e.g. that the charts you desire are available in the repos you defined), compares it with the current state of Helm and figures out what to do to make your desire come true. Below is the result of executing the [example.toml](https://github.com/Praqma/helmsman/blob/master/example.toml) ``` $ helmsman -f example.toml -apply @@ -92,7 +92,7 @@ Install Helmsman for your OS from the [releases page](https://github.com/Praqma/ # Documentaion -Documentation can be found under the [docs](/docs/) directory. +Documentation can be found under the [docs](https://github.com/Praqma/helmsman/blob/master/docs/) directory. # Contributing -Contribution and feeback/feature requests are welcome. Please check the [Contribution Guide](CONTRIBUTING.md). \ No newline at end of file +Contribution and feeback/feature requests are welcome. Please check the [Contribution Guide](https://github.com/Praqma/helmsman/blob/master/CONTRIBUTING.md). \ No newline at end of file From 9988b4f7c6a8e9f6735728d3628ac46edf2e12a2 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Thu, 9 Nov 2017 16:12:26 +0100 Subject: [PATCH 0008/1127] closing #8 --- init.go | 49 ++++++++++++++++++++++++++++++++++++++++++++++--- state.go | 14 +++----------- 2 files changed, 49 insertions(+), 14 deletions(-) diff --git a/init.go b/init.go index 53005cb2..7a1a2e1b 100644 --- a/init.go +++ b/init.go @@ -32,6 +32,11 @@ func init() { os.Exit(1) } + // if !toolExists("aws") { + // log.Fatal("ERROR: aws cli is not installed/configured correctly. Aborting!") + // os.Exit(1) + // } + // after the init() func is run, read the TOML desired state file fromTOML(file, &s) @@ -171,15 +176,53 @@ func createContext() bool { log.Fatal("ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ] " + "as you did not specify enough information in the Settings section of your desired state file.") return false + } else if s.Certifications == nil || s.Certifications["caCrt"] == "" || s.Certifications["caKey"] == "" { + log.Fatal("ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ] " + + "as you did not provide Certifications to use in your desired state file.") + return false + } + + // download certs using AWS cli + if !toolExists("aws help") { + log.Fatal("ERROR: aws is not installed/configured correctly. It is needed for downloading certs. Aborting!") + return false } + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "aws s3 cp " + s.Certifications["caCrt"] + " ca.crt"}, + Description: "downloading ca.crt from S3.", + } + + exitCode, _ := cmd.exec(debug) + + if exitCode != 0 { + log.Fatal("ERROR: failed to download caCrt.") + return false + } + + cmd = command{ + Cmd: "bash", + Args: []string{"-c", "aws s3 cp " + s.Certifications["caKey"] + " ca.key"}, + Description: "downloading ca.key from S3.", + } + + exitCode, _ = cmd.exec(debug) + + if exitCode != 0 { + log.Fatal("ERROR: failed to download caKey.") + return false + } + + // connecting to the cluster + cmd = command{ Cmd: "bash", Args: []string{"-c", "kubectl config set-credentials " + s.Settings["username"] + " --username=" + s.Settings["username"] + - " --password=" + readFile(s.Settings["password"]) + " --client-key=" + s.Certifications["caKey"]}, + " --password=" + readFile(s.Settings["password"]) + " --client-key=ca.key"}, Description: "creating kubectl context - setting credentials.", } - exitCode, _ := cmd.exec(debug) + exitCode, _ = cmd.exec(debug) if exitCode != 0 { log.Fatal("ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ].") @@ -189,7 +232,7 @@ func createContext() bool { cmd = command{ Cmd: "bash", Args: []string{"-c", "kubectl config set-cluster " + s.Settings["kubeContext"] + " --server=" + s.Settings["clusterURI"] + - " --certificate-authority=" + s.Certifications["caCrt"]}, + " --certificate-authority=ca.crt"}, Description: "creating kubectl context - setting cluster.", } diff --git a/state.go b/state.go index fe643e15..723c6ffd 100644 --- a/state.go +++ b/state.go @@ -50,18 +50,10 @@ func (s state) validate() bool { return false } for key, value := range s.Certifications { - _, err1 := url.ParseRequestURI(value) - _, err2 := os.Stat(value) - if err1 != nil && err2 != nil { - log.Fatal("ERROR: certifications validation failed -- [ " + key + " ] must be either s3 bucket URLs or valid file path.") + _, err := url.ParseRequestURI(value) + if err != nil || !strings.HasPrefix(value, "s3://") { + log.Fatal("ERROR: certifications validation failed -- [ " + key + " ] must be a valid s3 bucket URL.") return false - } else if err1 == nil { - // check it is valid s3 link - if !strings.HasPrefix(value, "s3://") { - log.Fatal("ERROR: certifications validation failed -- [ "+key+" ] URL can must be valid s3 bucket URL. ", - "A valid file path is also OK!.") - return false - } } } From 0da0e922bd79d21c1eaa113e4fef1fb87802bfec Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Thu, 9 Nov 2017 16:40:27 +0100 Subject: [PATCH 0009/1127] closing #7 --- init.go | 28 ++++++++++++++++++++-------- state.go | 6 ++---- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/init.go b/init.go index 7a1a2e1b..734ab6fb 100644 --- a/init.go +++ b/init.go @@ -4,12 +4,13 @@ import ( "flag" "log" "os" + "strings" ) // init is executed after all package vars are initialized [before the main() func in this case]. // It checks if Helm and Kubectl exist and configures: the connection to the k8s cluster, helm repos, namespaces, etc. func init() { - // parsing command line flags + //parsing command line flags flag.StringVar(&file, "f", "", "desired state file name") flag.BoolVar(&apply, "apply", false, "apply the plan directly") flag.BoolVar(&debug, "debug", false, "show the execution logs") @@ -32,11 +33,6 @@ func init() { os.Exit(1) } - // if !toolExists("aws") { - // log.Fatal("ERROR: aws cli is not installed/configured correctly. Aborting!") - // os.Exit(1) - // } - // after the init() func is run, read the TOML desired state file fromTOML(file, &s) @@ -172,6 +168,7 @@ func setKubeContext(context string) bool { // It returns true if successful, false otherwise func createContext() bool { + var password string if s.Settings["password"] == "" || s.Settings["username"] == "" || s.Settings["clusterURI"] == "" { log.Fatal("ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ] " + "as you did not specify enough information in the Settings section of your desired state file.") @@ -180,6 +177,21 @@ func createContext() bool { log.Fatal("ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ] " + "as you did not provide Certifications to use in your desired state file.") return false + } else { + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "echo " + s.Settings["password"]}, + Description: "reading the password from env variable.", + } + + var exitCode int + exitCode, password = cmd.exec(debug) + + password = strings.TrimSpace(password) + if exitCode != 0 || password == "" { + log.Fatal("ERROR: failed to read password from env variable.") + return false + } } // download certs using AWS cli @@ -218,7 +230,7 @@ func createContext() bool { cmd = command{ Cmd: "bash", Args: []string{"-c", "kubectl config set-credentials " + s.Settings["username"] + " --username=" + s.Settings["username"] + - " --password=" + readFile(s.Settings["password"]) + " --client-key=ca.key"}, + " --password=" + password + " --client-key=ca.key"}, Description: "creating kubectl context - setting credentials.", } @@ -246,7 +258,7 @@ func createContext() bool { cmd = command{ Cmd: "bash", Args: []string{"-c", "kubectl config set-context " + s.Settings["kubeContext"] + " --cluster=" + s.Settings["kubeContext"] + - " --user=" + s.Settings["username"] + " --password=" + readFile(s.Settings["password"])}, + " --user=" + s.Settings["username"] + " --password=" + password}, Description: "creating kubectl context - setting context.", } diff --git a/state.go b/state.go index 723c6ffd..a13edd6d 100644 --- a/state.go +++ b/state.go @@ -31,10 +31,8 @@ func (s state) validate() bool { "kubeContext to use. Can't work without it. Sorry!") return false } else if len(s.Settings) > 1 { - _, err := os.Stat(s.Settings["password"]) - if s.Settings["password"] == "" || !isOfType(s.Settings["password"], ".passwd") || err != nil { - log.Fatal("ERROR: settings validation failed -- the specified password file is not valid. ", - "It should be a valid path and of type \".passwd\".") + if s.Settings["password"] == "" || !strings.HasPrefix(s.Settings["password"], "$") { + log.Fatal("ERROR: settings validation failed -- password should be an env variable starting with $ ") return false } else if _, err := url.ParseRequestURI(s.Settings["clusterURI"]); err != nil { log.Fatal("ERROR: settings validation failed -- clusterURI must have a valid URL.") From 722290e0c7189bce02d10af12e56cc494e285ba0 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Thu, 9 Nov 2017 17:09:35 +0100 Subject: [PATCH 0010/1127] added aws to dockerfile --- dockerfile/dockerfile | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile index bfe679f6..592545ea 100644 --- a/dockerfile/dockerfile +++ b/dockerfile/dockerfile @@ -16,12 +16,16 @@ RUN chmod +x /usr/local/bin/kubectl FROM alpine:3.6 RUN apk add --update --no-cache ca-certificates git -ENV VERSION v2.7.0 - -WORKDIR / - -RUN apk add --update -t deps curl tar gzip make bash \ - && curl -L http://storage.googleapis.com/kubernetes-helm/helm-${VERSION}-linux-amd64.tar.gz | tar zxv -C /tmp \ +ENV HELM_VERSION v2.7.0 +# Versions: https://pypi.python.org/pypi/awscli#downloads +ENV AWS_CLI_VERSION 1.11.131 + +RUN apk --no-cache update \ + && apk --no-cache add python py-pip py-setuptools ca-certificates groff less \ + && pip --no-cache-dir install awscli==${AWS_CLI_VERSION} \ + && rm -rf /var/cache/apk/* \ + && apk add --update -t deps curl tar gzip make bash \ + && curl -L http://storage.googleapis.com/kubernetes-helm/helm-${HELM_VERSION}-linux-amd64.tar.gz | tar zxv -C /tmp \ && mv /tmp/linux-amd64/helm /usr/local/bin/helm \ && chmod +x /usr/local/bin/helm \ && mkdir -p ~/.helm/plugins \ @@ -31,5 +35,6 @@ RUN apk add --update -t deps curl tar gzip make bash \ COPY --from=kube /usr/local/bin/kubectl /bin/kubectl COPY --from=builder /go/bin/helmsman /bin/helmsman +WORKDIR /tmp ENTRYPOINT ["/bin/helmsman"] From bd14da5f5719c0fc0ea3569a9105552b7a87d91c Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Wed, 15 Nov 2017 10:09:13 +0100 Subject: [PATCH 0011/1127] added feature for apllying changes in values.yaml when the release is deployed. #9 --- decision_maker.go | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index 5d5d5ecb..0fd03c6e 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -155,16 +155,8 @@ func inspectUpgradeScenario(namespace string, r release) { if getReleaseNamespace(releaseName) == namespace { if extractChartName(r.Chart) == getReleaseChartName(releaseName) && r.Version != getReleaseChartVersion(releaseName) { // upgrade - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm upgrade " + releaseName + " " + r.Chart + " -f " + r.ValuesFile}, - Description: "upgrading release [ " + releaseName + " ]", - } - outcome.addCommand(cmd) + upgradeRelease(r) logDecision("DECISION: release [ " + releaseName + " ] is desired to be upgraded. Planing this for you!") - if r.Test { - testRelease(releaseName) - } } else if extractChartName(r.Chart) != getReleaseChartName(releaseName) { reInstallRelease(namespace, r) @@ -173,8 +165,9 @@ func inspectUpgradeScenario(namespace string, r release) { namespace + " ]]") } else { + upgradeRelease(r) logDecision("DECISION: release [ " + releaseName + " ] is desired to be enabled and is currently enabled." + - "Nothing for me to do. Horraayyy!") + "I will upgrade it in case you changed your values.yaml!") } } else { reInstallRelease(namespace, r) @@ -184,6 +177,21 @@ func inspectUpgradeScenario(namespace string, r release) { } } +// upgradeRelease upgrades an existing release with the specified values.yaml +func upgradeRelease(r release) { + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm upgrade " + r.Name + " " + r.Chart + " -f " + r.ValuesFile}, + Description: "upgrading release [ " + r.Name + " ]", + } + + outcome.addCommand(cmd) + + if r.Test { + testRelease(r.Name) + } +} + // reInstallRelease purge deletes a release and reinstall it. // This is used when moving a release to another namespace or when changing the chart used for it. func reInstallRelease(namespace string, r release) { From c390ddeff456eb8de1c9189e2564ad33c8baadac Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Wed, 15 Nov 2017 13:42:50 +0100 Subject: [PATCH 0012/1127] added circleci config and some tests. --- .circleci/config.yml | 27 ++++++ command_test.go | 110 +++++++++++++++++++++++ example.toml | 24 ++--- init.go | 2 +- plan.go | 4 +- plan_test.go | 210 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 363 insertions(+), 14 deletions(-) create mode 100644 .circleci/config.yml create mode 100644 command_test.go create mode 100644 plan_test.go diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..0d59530b --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,27 @@ +# Golang CircleCI 2.0 configuration file +# +# Check https://circleci.com/docs/2.0/language-go/ for more details +version: 2 +jobs: + build: + docker: + # specify the version + - image: circleci/golang:1.8 + + # Specify service dependencies here if necessary + # CircleCI maintains a library of pre-built images + # documented at https://circleci.com/docs/2.0/circleci-images/ + # - image: circleci/postgres:9.4 + + #### TEMPLATE_NOTE: go expects specific checkout path representing url + #### expecting it in the form of + #### /go/src/github.com/circleci/go-tool + #### /go/src/bitbucket.org/circleci/go-tool + working_directory: /go/src/github.com/praqma/helmsman + steps: + - checkout + + # specify any bash command here prefixed with `run: ` + - run: go get github.com/BurntSushi/toml + - run: go get github.com/goreleaser/goreleaser + - run: go test \ No newline at end of file diff --git a/command_test.go b/command_test.go new file mode 100644 index 00000000..88e62f82 --- /dev/null +++ b/command_test.go @@ -0,0 +1,110 @@ +package main + +import ( + "strings" + "testing" +) + +// func Test_command_printDescription(t *testing.T) { +// type fields struct { +// Cmd string +// Args []string +// Description string +// } +// tests := []struct { +// name string +// fields fields +// }{ +// // TODO: Add test cases. +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// c := command{ +// Cmd: tt.fields.Cmd, +// Args: tt.fields.Args, +// Description: tt.fields.Description, +// } +// c.printDescription() +// }) +// } +// } + +// func Test_command_printFullCommand(t *testing.T) { +// type fields struct { +// Cmd string +// Args []string +// Description string +// } +// tests := []struct { +// name string +// fields fields +// }{ +// // TODO: Add test cases. +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// c := command{ +// Cmd: tt.fields.Cmd, +// Args: tt.fields.Args, +// Description: tt.fields.Description, +// } +// c.printFullCommand() +// }) +// } +// } + +func Test_command_exec(t *testing.T) { + type fields struct { + Cmd string + Args []string + Description string + } + type args struct { + debug bool + } + tests := []struct { + name string + fields fields + args args + want int + want1 string + }{ + { + name: "echo", + fields: fields{ + Cmd: "bash", + Args: []string{"-c", "echo this is fun"}, + Description: "A bash command execution test with echo.", + }, + args: args{debug: false}, + want: 0, + want1: "this is fun", + }, { + name: "exitCode", + fields: fields{ + Cmd: "bash", + Args: []string{"-c", "echo $?"}, + Description: "A bash command execution test with exitCode.", + }, + args: args{debug: false}, + want: 0, + want1: "0", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := command{ + Cmd: tt.fields.Cmd, + Args: tt.fields.Args, + Description: tt.fields.Description, + } + got, got1 := c.exec(tt.args.debug) + if got != tt.want { + t.Errorf("command.exec() got = %v, want %v", got, tt.want) + } + if strings.TrimSpace(got1) != tt.want1 { + t.Errorf("command.exec() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} diff --git a/example.toml b/example.toml index 0bd50553..8107737c 100644 --- a/example.toml +++ b/example.toml @@ -6,8 +6,8 @@ maintainer = "k8s-admin" # paths to the certificate for connecting to the cluster # You can skip this if you use Helmsman on a machine with kubectl already connected to your k8s cluster. [certifications] -caCrt = "ca.crt" # s3 bucket path -caKey = "ca.key" # Or, a path to the file location +# caCrt = "ca.crt" # s3 bucket path +# caKey = "ca.key" # Or, a path to the file location [settings] kubeContext = "minikube" # will try connect to this context first, if it does not exist, it will be created using the details below @@ -46,13 +46,13 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" test = true # run the tests whenever this release is installed/upgraded/rolledback - [apps.vault] - name = "vault" # should be unique across all apps - description = "vault" - env = "staging" # maps to the namespace as defined in environmetns above - enabled = true # change to false if you want to delete this app release [empty = flase] - chart = "incubator/vault" # don't change the chart name, create a new release instead - version = "0.1.0" - valuesFile = "" # leaving it empty uses the default chart values - purge = false # will only be considered when there is a delete operation - test = true # run the tests whenever this release is installed/upgraded/rolledback + # [apps.vault] + # name = "vault" # should be unique across all apps + # description = "vault" + # env = "staging" # maps to the namespace as defined in environmetns above + # enabled = true # change to false if you want to delete this app release [empty = flase] + # chart = "incubator/vault" # don't change the chart name, create a new release instead + # version = "0.1.0" + # valuesFile = "" # leaving it empty uses the default chart values + # purge = false # will only be considered when there is a delete operation + # test = true # run the tests whenever this release is installed/upgraded/rolledback diff --git a/init.go b/init.go index 734ab6fb..0aba55a5 100644 --- a/init.go +++ b/init.go @@ -11,7 +11,7 @@ import ( // It checks if Helm and Kubectl exist and configures: the connection to the k8s cluster, helm repos, namespaces, etc. func init() { //parsing command line flags - flag.StringVar(&file, "f", "", "desired state file name") + flag.StringVar(&file, "f", "example.toml", "desired state file name") flag.BoolVar(&apply, "apply", false, "apply the plan directly") flag.BoolVar(&debug, "debug", false, "show the execution logs") flag.BoolVar(&help, "help", false, "show Helmsman help") diff --git a/plan.go b/plan.go index a91524e0..b96cc1fb 100644 --- a/plan.go +++ b/plan.go @@ -42,7 +42,9 @@ func (p plan) execPlan() { p.printPlan() for _, cmd := range p.Commands { log.Println("INFO: attempting: -- ", cmd.Description) - cmd.exec(debug) + if exitCode, _ := cmd.exec(debug); exitCode != 0 { + log.Fatal("Command returned the following non zero exit code while executing the plan: " + string(exitCode)) + } } } diff --git a/plan_test.go b/plan_test.go new file mode 100644 index 00000000..2f5183b6 --- /dev/null +++ b/plan_test.go @@ -0,0 +1,210 @@ +package main + +import ( + "reflect" + "strings" + "testing" + "time" +) + +func Test_createPlan(t *testing.T) { + tests := []struct { + name string + want plan + }{ + { + name: "test creating a plan", + want: createPlan(), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := createPlan(); reflect.DeepEqual(got, tt.want) { + t.Errorf("createPlan() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_plan_addCommand(t *testing.T) { + type fields struct { + Commands []command + Decisions []string + Created time.Time + } + type args struct { + c command + } + tests := []struct { + name string + fields fields + args args + }{ + { + name: "testing command 1", + fields: fields{ + Commands: []command{}, + Decisions: []string{}, + Created: time.Now(), + }, + args: args{ + c: command{ + Cmd: "bash", + Args: []string{"-c", "echo this is fun"}, + Description: "A bash command execution test with echo.", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := &plan{ + Commands: tt.fields.Commands, + Decisions: tt.fields.Decisions, + Created: tt.fields.Created, + } + p.addCommand(tt.args.c) + if got := len(p.Commands); got != 1 { + t.Errorf("addCommand(): got %v, want 1", got) + } + }) + } +} + +func Test_plan_addDecision(t *testing.T) { + type fields struct { + Commands []command + Decisions []string + Created time.Time + } + type args struct { + decision string + } + tests := []struct { + name string + fields fields + args args + }{ + { + name: "testing decision adding", + fields: fields{ + Commands: []command{}, + Decisions: []string{}, + Created: time.Now(), + }, + args: args{ + decision: "This is a test decision.", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := &plan{ + Commands: tt.fields.Commands, + Decisions: tt.fields.Decisions, + Created: tt.fields.Created, + } + p.addDecision(tt.args.decision) + if got := len(p.Decisions); got != 1 { + t.Errorf("addDecision(): got %v, want 1", got) + } + }) + } +} + +func Test_plan_execPlan(t *testing.T) { + type fields struct { + Commands []command + Decisions []string + Created time.Time + } + tests := []struct { + name string + fields fields + }{ + { + name: "testing executing a plan", + fields: fields{ + Commands: []command{ + { + Cmd: "bash", + Args: []string{"-c", "export TEST=hello world"}, + Description: "Setting TEST env var.", + }, { + Cmd: "bash", + Args: []string{"-c", "export TEST=hello world, again!"}, + Description: "Setting TEST env var, again.", + }, + }, + Decisions: []string{"Set TEST env var to: hello world.", "Set TEST env var to: hello world, again!."}, + Created: time.Now(), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := plan{ + Commands: tt.fields.Commands, + Decisions: tt.fields.Decisions, + Created: tt.fields.Created, + } + p.execPlan() + c := command{ + Cmd: "bash", + Args: []string{"-c", "echo {$TEST}"}, + Description: "", + } + if _, got := c.exec(false); strings.TrimSpace(got) != "hello world, again!" { + t.Errorf("execPlan(): got %v, want hello world, again!", got) + } + }) + } +} + +// func Test_plan_printPlanCmds(t *testing.T) { +// type fields struct { +// Commands []command +// Decisions []string +// Created time.Time +// } +// tests := []struct { +// name string +// fields fields +// }{ +// // TODO: Add test cases. +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// p := plan{ +// Commands: tt.fields.Commands, +// Decisions: tt.fields.Decisions, +// Created: tt.fields.Created, +// } +// p.printPlanCmds() +// }) +// } +// } + +// func Test_plan_printPlan(t *testing.T) { +// type fields struct { +// Commands []command +// Decisions []string +// Created time.Time +// } +// tests := []struct { +// name string +// fields fields +// }{ +// // TODO: Add test cases. +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// p := plan{ +// Commands: tt.fields.Commands, +// Decisions: tt.fields.Decisions, +// Created: tt.fields.Created, +// } +// p.printPlan() +// }) +// } +// } From 0344a23376561c7f97076742a2ff6690a656ee06 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Wed, 15 Nov 2017 13:46:14 +0100 Subject: [PATCH 0013/1127] enabled goreleaser in circleci build --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0d59530b..32b929ae 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -24,4 +24,4 @@ jobs: # specify any bash command here prefixed with `run: ` - run: go get github.com/BurntSushi/toml - run: go get github.com/goreleaser/goreleaser - - run: go test \ No newline at end of file + - run: goreleaser \ No newline at end of file From f87d46978f311e2f3e572800f58c3893d8a62728 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Wed, 15 Nov 2017 14:52:00 +0100 Subject: [PATCH 0014/1127] #10 updated the circleci build script and config --- .circleci/build.sh | 22 ++++++++++++++++++++++ .circleci/config.yml | 3 ++- 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 .circleci/build.sh diff --git a/.circleci/build.sh b/.circleci/build.sh new file mode 100644 index 00000000..ed0f6d6a --- /dev/null +++ b/.circleci/build.sh @@ -0,0 +1,22 @@ +#!/bin/bash -x +echo "running tests ..." +go test + +if [ $? -eq 0 ]; then + echo "releasing ..." + goreleaser | grep -o "error" + if [ $? -eq 0 ]; then + goreleaser | grep -0 "already_exists" + if [ $? -gt 0 ]; then + echo "ERROR: failed to release. Terminating." + Exit 9 + else + echo "No new releases made. That is Ok!" + fi + else + echo "New release was successfully made." + fi +else + echo "tests failed ... Aborting!" + Exit 9 +fi \ No newline at end of file diff --git a/.circleci/config.yml b/.circleci/config.yml index 32b929ae..40d7812c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -24,4 +24,5 @@ jobs: # specify any bash command here prefixed with `run: ` - run: go get github.com/BurntSushi/toml - run: go get github.com/goreleaser/goreleaser - - run: goreleaser \ No newline at end of file + - run: chmod +x .circleci/build.sh + - run: .circleci/build.sh \ No newline at end of file From 26a6d013febe983dde78ac0f7906f01f0aa4c7c7 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Wed, 15 Nov 2017 14:53:38 +0100 Subject: [PATCH 0015/1127] #10 fixed a typo in the build script. --- .circleci/build.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/build.sh b/.circleci/build.sh index ed0f6d6a..b2d4ba5c 100644 --- a/.circleci/build.sh +++ b/.circleci/build.sh @@ -9,7 +9,7 @@ if [ $? -eq 0 ]; then goreleaser | grep -0 "already_exists" if [ $? -gt 0 ]; then echo "ERROR: failed to release. Terminating." - Exit 9 + exit 9 else echo "No new releases made. That is Ok!" fi @@ -18,5 +18,5 @@ if [ $? -eq 0 ]; then fi else echo "tests failed ... Aborting!" - Exit 9 + exit 9 fi \ No newline at end of file From 2a4a60682d116b4cfddc8ca7bc19e43ee3739b37 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Thu, 16 Nov 2017 13:01:30 +0100 Subject: [PATCH 0016/1127] #2 adding unit tests. --- example.toml | 2 +- init.go | 35 +-- init_test.go | 134 +++++++++++ main.go | 22 ++ plan_test.go | 6 +- release.go | 4 +- release_test.go | 238 +++++++++++++++++++ state.go | 81 +++---- state_test.go | 406 ++++++++++++++++++++++++++++++++ test_files/dockerfile | 26 ++ test_files/invalid_example.toml | 19 ++ test_files/values.xml | 0 test_files/values.yaml | 0 utils.go | 8 +- utils_test.go | 154 ++++++++++++ 15 files changed, 1057 insertions(+), 78 deletions(-) create mode 100644 init_test.go create mode 100644 release_test.go create mode 100644 state_test.go create mode 100644 test_files/dockerfile create mode 100644 test_files/invalid_example.toml create mode 100644 test_files/values.xml create mode 100644 test_files/values.yaml create mode 100644 utils_test.go diff --git a/example.toml b/example.toml index 8107737c..56871015 100644 --- a/example.toml +++ b/example.toml @@ -5,7 +5,7 @@ maintainer = "k8s-admin" # paths to the certificate for connecting to the cluster # You can skip this if you use Helmsman on a machine with kubectl already connected to your k8s cluster. -[certifications] +[certificates] # caCrt = "ca.crt" # s3 bucket path # caKey = "ca.key" # Or, a path to the file location diff --git a/init.go b/init.go index 0aba55a5..17008692 100644 --- a/init.go +++ b/init.go @@ -34,31 +34,18 @@ func init() { } // after the init() func is run, read the TOML desired state file - fromTOML(file, &s) - - // validate the desired state content - s.validate() // syntax validation - - // set the kubecontext to be used Or create it if it does not exist - if !setKubeContext(s.Settings["kubeContext"]) { - if !createContext() { - os.Exit(1) - } - } - - // add repos -- fails if they are not valid - if !addHelmRepos(s.HelmRepos) { - os.Exit(1) + result, msg := fromTOML(file, &s) + if result { + log.Printf(msg) + } else { + log.Fatal(msg) } - // validate charts-versions exist in supllied repos - if !validateReleaseCharts(s.Apps) { - os.Exit(1) + // validate the desired state content + if result, msg := s.validate(); !result { // syntax validation + log.Fatal(msg) } - // add/validate namespaces - addNamespaces(s.Namespaces) - } // toolExists returns true if the tool is present in the environment and false otherwise. @@ -173,7 +160,7 @@ func createContext() bool { log.Fatal("ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ] " + "as you did not specify enough information in the Settings section of your desired state file.") return false - } else if s.Certifications == nil || s.Certifications["caCrt"] == "" || s.Certifications["caKey"] == "" { + } else if s.Certificates == nil || s.Certificates["caCrt"] == "" || s.Certificates["caKey"] == "" { log.Fatal("ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ] " + "as you did not provide Certifications to use in your desired state file.") return false @@ -202,7 +189,7 @@ func createContext() bool { cmd := command{ Cmd: "bash", - Args: []string{"-c", "aws s3 cp " + s.Certifications["caCrt"] + " ca.crt"}, + Args: []string{"-c", "aws s3 cp " + s.Certificates["caCrt"] + " ca.crt"}, Description: "downloading ca.crt from S3.", } @@ -215,7 +202,7 @@ func createContext() bool { cmd = command{ Cmd: "bash", - Args: []string{"-c", "aws s3 cp " + s.Certifications["caKey"] + " ca.key"}, + Args: []string{"-c", "aws s3 cp " + s.Certificates["caKey"] + " ca.key"}, Description: "downloading ca.key from S3.", } diff --git a/init_test.go b/init_test.go new file mode 100644 index 00000000..c27815ce --- /dev/null +++ b/init_test.go @@ -0,0 +1,134 @@ +package main + +import "testing" + +func Test_toolExists(t *testing.T) { + type args struct { + tool string + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "test case 1 -- checking helm exists.", + args: args{ + tool: "helm", + }, + want: true, + }, { + name: "test case 2 -- checking kubectl exists.", + args: args{ + tool: "kubectl", + }, + want: true, + }, { + name: "test case 3 -- checking ipconfig exists.", + args: args{ + tool: "ipconfig", + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := toolExists(tt.args.tool); got != tt.want { + t.Errorf("toolExists() = %v, want %v", got, tt.want) + } + }) + } +} + +// func Test_addNamespaces(t *testing.T) { +// type args struct { +// namespaces map[string]string +// } +// tests := []struct { +// name string +// args args +// }{ +// // TODO: Add test cases. +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// addNamespaces(tt.args.namespaces) +// }) +// } +// } + +// func Test_validateReleaseCharts(t *testing.T) { +// type args struct { +// apps map[string]release +// } +// tests := []struct { +// name string +// args args +// want bool +// }{ +// // TODO: Add test cases. +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// if got := validateReleaseCharts(tt.args.apps); got != tt.want { +// t.Errorf("validateReleaseCharts() = %v, want %v", got, tt.want) +// } +// }) +// } +// } + +// func Test_addHelmRepos(t *testing.T) { +// type args struct { +// repos map[string]string +// } +// tests := []struct { +// name string +// args args +// want bool +// }{ +// // TODO: Add test cases. +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// if got := addHelmRepos(tt.args.repos); got != tt.want { +// t.Errorf("addHelmRepos() = %v, want %v", got, tt.want) +// } +// }) +// } +// } + +// func Test_setKubeContext(t *testing.T) { +// type args struct { +// context string +// } +// tests := []struct { +// name string +// args args +// want bool +// }{ +// // TODO: Add test cases. +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// if got := setKubeContext(tt.args.context); got != tt.want { +// t.Errorf("setKubeContext() = %v, want %v", got, tt.want) +// } +// }) +// } +// } + +// func Test_createContext(t *testing.T) { +// tests := []struct { +// name string +// want bool +// }{ +// // TODO: Add test cases. +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// if got := createContext(); got != tt.want { +// t.Errorf("createContext() = %v, want %v", got, tt.want) +// } +// }) +// } +// } diff --git a/main.go b/main.go index 34a1d5a9..f911722c 100644 --- a/main.go +++ b/main.go @@ -1,5 +1,7 @@ package main +import "os" + var s state var debug bool var file string @@ -8,6 +10,26 @@ var help bool func main() { + // set the kubecontext to be used Or create it if it does not exist + if !setKubeContext(s.Settings["kubeContext"]) { + if !createContext() { + os.Exit(1) + } + } + + // add repos -- fails if they are not valid + if !addHelmRepos(s.HelmRepos) { + os.Exit(1) + } + + // validate charts-versions exist in supllied repos + if !validateReleaseCharts(s.Apps) { + os.Exit(1) + } + + // add/validate namespaces + addNamespaces(s.Namespaces) + p := makePlan(&s) if !apply { diff --git a/plan_test.go b/plan_test.go index 2f5183b6..c12cbd49 100644 --- a/plan_test.go +++ b/plan_test.go @@ -128,11 +128,11 @@ func Test_plan_execPlan(t *testing.T) { Commands: []command{ { Cmd: "bash", - Args: []string{"-c", "export TEST=hello world"}, + Args: []string{"-c", "export TEST='hello world'"}, Description: "Setting TEST env var.", }, { Cmd: "bash", - Args: []string{"-c", "export TEST=hello world, again!"}, + Args: []string{"-c", "export TEST='hello world, again!'"}, Description: "Setting TEST env var, again.", }, }, @@ -151,7 +151,7 @@ func Test_plan_execPlan(t *testing.T) { p.execPlan() c := command{ Cmd: "bash", - Args: []string{"-c", "echo {$TEST}"}, + Args: []string{"-c", "echo $TEST"}, Description: "", } if _, got := c.exec(false); strings.TrimSpace(got) != "hello world, again!" { diff --git a/release.go b/release.go index 605c9aea..06dfdeb3 100644 --- a/release.go +++ b/release.go @@ -20,13 +20,13 @@ type release struct { } // validateRelease validates if a release inside a desired state meets the specifications or not. -// check the full specification @ https://github.com/Praqma/Helmsman/docs/desired_state_spec.md +// check the full specification @ https://github.com/Praqma/helmsman/docs/desired_state_spec.md func validateRelease(r release, names map[string]bool) (bool, string) { _, err := os.Stat(r.ValuesFile) if r.Name == "" || names[r.Name] { return false, "release name can't be empty and must be unique." } else if r.Env == "" { - return false, "env can't be empty." + return false, "release targeted env (namespace) can't be empty." } else if r.Chart == "" || !strings.ContainsAny(r.Chart, "/") { return false, "chart can't be empty and must be of the format: repo/chart." } else if r.Version == "" { diff --git a/release_test.go b/release_test.go new file mode 100644 index 00000000..2bdb83e8 --- /dev/null +++ b/release_test.go @@ -0,0 +1,238 @@ +package main + +import ( + "strings" + "testing" +) + +func Test_validateRelease(t *testing.T) { + type args struct { + r release + } + tests := []struct { + name string + args args + want bool + want1 string + }{ + { + name: "test case 1", + args: args{ + r: release{ + Name: "release1", + Description: "", + Env: "namespace", + Enabled: true, + Chart: "repo/chartX", + Version: "1.0", + ValuesFile: "test_files/values.yaml", + Purge: true, + Test: true, + }, + }, + want: true, + want1: "", + }, { + name: "test case 2", + args: args{ + r: release{ + Name: "release2", + Description: "", + Env: "namespace", + Enabled: true, + Chart: "repo/chartX", + Version: "1.0", + ValuesFile: "values.yaml", + Purge: true, + Test: true, + }, + }, + want: false, + want1: "valuesFile must be a valid file path for a yaml file, Or can be left empty.", + }, { + name: "test case 3", + args: args{ + r: release{ + Name: "release3", + Description: "", + Env: "namespace", + Enabled: true, + Chart: "repo/chartX", + Version: "1.0", + ValuesFile: "test_files/values.xml", + Purge: true, + Test: true, + }, + }, + want: false, + want1: "valuesFile must be a valid file path for a yaml file, Or can be left empty.", + }, { + name: "test case 4", + args: args{ + r: release{ + Name: "release1", + Description: "", + Env: "namespace", + Enabled: true, + Chart: "repo/chartX", + Version: "1.0", + ValuesFile: "test_files/values.yaml", + Purge: true, + Test: true, + }, + }, + want: false, + want1: "release name can't be empty and must be unique.", + }, { + name: "test case 5", + args: args{ + r: release{ + Name: "", + Description: "", + Env: "namespace", + Enabled: true, + Chart: "repo/chartX", + Version: "1.0", + ValuesFile: "test_files/values.yaml", + Purge: true, + Test: true, + }, + }, + want: false, + want1: "release name can't be empty and must be unique.", + }, { + name: "test case 6", + args: args{ + r: release{ + Name: "release6", + Description: "", + Env: "", + Enabled: true, + Chart: "repo/chartX", + Version: "1.0", + ValuesFile: "test_files/values.yaml", + Purge: true, + Test: true, + }, + }, + want: false, + want1: "release targeted env (namespace) can't be empty.", + }, { + name: "test case 7", + args: args{ + r: release{ + Name: "release7", + Description: "", + Env: "namespace", + Enabled: true, + Chart: "chartX", + Version: "1.0", + ValuesFile: "test_files/values.yaml", + Purge: true, + Test: true, + }, + }, + want: false, + want1: "chart can't be empty and must be of the format: repo/chart.", + }, { + name: "test case 8", + args: args{ + r: release{ + Name: "release8", + Description: "", + Env: "namespace", + Enabled: true, + Chart: "", + Version: "1.0", + ValuesFile: "test_files/values.yaml", + Purge: true, + Test: true, + }, + }, + want: false, + want1: "chart can't be empty and must be of the format: repo/chart.", + }, { + name: "test case 9", + args: args{ + r: release{ + Name: "release9", + Description: "", + Env: "namespace", + Enabled: true, + Chart: "repo/chartX", + Version: "", + ValuesFile: "test_files/values.yaml", + Purge: true, + Test: true, + }, + }, + want: false, + want1: "version can't be empty.", + }, { + name: "test case 10", + args: args{ + r: release{ + Name: "release10", + Description: "", + Env: "namespace", + Enabled: true, + Chart: "repo/chartX", + Version: "1.0", + ValuesFile: "test_files/values.yaml", + Purge: true, + Test: true, + }, + }, + want: true, + want1: "", + }, + } + names := make(map[string]bool) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, got1 := validateRelease(tt.args.r, names) + if got != tt.want { + t.Errorf("validateRelease() got = %v, want %v", got, tt.want) + } + if strings.TrimSpace(got1) != tt.want1 { + t.Errorf("validateRelease() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} + +// func Test_release_print(t *testing.T) { +// type fields struct { +// Name string +// Description string +// Env string +// Enabled bool +// Chart string +// Version string +// ValuesFile string +// Purge bool +// Test bool +// } +// tests := []struct { +// name string +// fields fields +// }{ +// // TODO: Add test cases. +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// r := release{ +// Name: tt.fields.Name, +// Description: tt.fields.Description, +// Env: tt.fields.Env, +// Enabled: tt.fields.Enabled, +// Chart: tt.fields.Chart, +// Version: tt.fields.Version, +// ValuesFile: tt.fields.ValuesFile, +// Purge: tt.fields.Purge, +// Test: tt.fields.Test, +// } +// r.print() +// }) +// } +// } diff --git a/state.go b/state.go index a13edd6d..473f1736 100644 --- a/state.go +++ b/state.go @@ -10,79 +10,74 @@ import ( // state type represents the desired state of applications on a k8s cluster. type state struct { - Metadata map[string]string - Certifications map[string]string - Settings map[string]string - Namespaces map[string]string - HelmRepos map[string]string - Apps map[string]release + Metadata map[string]string + Certificates map[string]string + Settings map[string]string + Namespaces map[string]string + HelmRepos map[string]string + Apps map[string]release } // validate validates that the values specified in the desired state are valid according to the desired state spec. // check https://github.com/Praqma/Helmsman/docs/desired_state_spec.md for the detailed specification -func (s state) validate() bool { +func (s state) validate() (bool, string) { // settings if s.Settings == nil { - log.Fatal("ERROR: settings validation failed -- no settings table provided in TOML.") - return false + return false, "ERROR: settings validation failed -- no settings table provided in TOML." } else if s.Settings["kubeContext"] == "" { - log.Fatal("ERROR: settings validation failed -- you have not provided a ", - "kubeContext to use. Can't work without it. Sorry!") - return false + return false, "ERROR: settings validation failed -- you have not provided a " + + "kubeContext to use. Can't work without it. Sorry!" } else if len(s.Settings) > 1 { if s.Settings["password"] == "" || !strings.HasPrefix(s.Settings["password"], "$") { - log.Fatal("ERROR: settings validation failed -- password should be an env variable starting with $ ") - return false + return false, "ERROR: settings validation failed -- password should be an env variable starting with $ " } else if _, err := url.ParseRequestURI(s.Settings["clusterURI"]); err != nil { - log.Fatal("ERROR: settings validation failed -- clusterURI must have a valid URL.") - return false + return false, "ERROR: settings validation failed -- clusterURI must have a valid URL." } } - // certifications - if s.Certifications != nil { - if len(s.Settings) > 1 && len(s.Certifications) < 2 { - log.Fatal("ERROR: certifications validation failed -- You want me to connect to your cluster for you ", - "but have not given me the keys to do so. Please add [caCrt] and [caKey] under Certifications.") - return false + // certificates + if s.Certificates != nil { + if len(s.Settings) > 1 && len(s.Certificates) < 2 { + return false, "ERROR: certifications validation failed -- You want me to connect to your cluster for you " + + "but have not given me the keys to do so. Please add [caCrt] and [caKey] under Certifications." } - for key, value := range s.Certifications { + for key, value := range s.Certificates { _, err := url.ParseRequestURI(value) if err != nil || !strings.HasPrefix(value, "s3://") { - log.Fatal("ERROR: certifications validation failed -- [ " + key + " ] must be a valid s3 bucket URL.") - return false + return false, "ERROR: certifications validation failed -- [ " + key + " ] must be a valid S3 bucket URL." } } + } else { + if len(s.Settings) > 1 { + return false, "ERROR: certifications validation failed -- You want me to connect to your cluster for you " + + "but have not given me the keys to do so. Please add [caCrt] and [caKey] under Certifications." + } } // namespaces if s.Namespaces == nil || len(s.Namespaces) < 1 { - log.Fatal("ERROR: namespaces validation failed -- I need at least one namespace ", - "to work with!") - return false + return false, "ERROR: namespaces validation failed -- I need at least one namespace " + + "to work with!" } for k, v := range s.Namespaces { if v == "" { - log.Fatal("ERROR: namespaces validation failed -- namespace ["+k+" ] ", - "must have a value or be removed/commented.") - return false + return false, "ERROR: namespaces validation failed -- namespace [" + k + " ] " + + "must have a value or be removed/commented." } } // repos if s.HelmRepos == nil || len(s.HelmRepos) < 1 { - log.Fatal("ERROR: repos validation failed -- I need at least one helm repo ", - "to work with!") - return false + return false, "ERROR: repos validation failed -- I need at least one helm repo " + + "to work with!" } for k, v := range s.HelmRepos { _, err := url.ParseRequestURI(v) if err != nil { - log.Fatal("ERROR: repos validation failed -- repo ["+k+" ] ", - "must have a valid URL.") - return false + return false, "ERROR: repos validation failed -- repo [" + k + " ] " + + "must have a valid URL." } continue @@ -100,13 +95,11 @@ func (s state) validate() bool { for appLabel, r := range s.Apps { result, errMsg := validateRelease(r, names) if !result { - log.Fatal("ERROR: apps validation failed -- for app ["+appLabel+" ]. ", - errMsg) - return false + return false, "ERROR: apps validation failed -- for app [" + appLabel + " ]. " + errMsg } } - return true + return true, "" } // print prints the desired state @@ -115,10 +108,10 @@ func (s state) print() { fmt.Println("\nMetadata: ") fmt.Println("--------- ") printMap(s.Metadata) - fmt.Println("\nCertifications: ") + fmt.Println("\nCertificates: ") fmt.Println("--------- ") - printMap(s.Certifications) - fmt.Println("Settings: ") + printMap(s.Certificates) + fmt.Println("\nSettings: ") fmt.Println("--------- ") printMap(s.Settings) fmt.Println("\nNamespaces: ") diff --git a/state_test.go b/state_test.go new file mode 100644 index 00000000..473a4e50 --- /dev/null +++ b/state_test.go @@ -0,0 +1,406 @@ +package main + +import "testing" + +func Test_state_validate(t *testing.T) { + type fields struct { + Metadata map[string]string + Certificates map[string]string + Settings map[string]string + Namespaces map[string]string + HelmRepos map[string]string + Apps map[string]release + } + tests := []struct { + name string + fields fields + want bool + }{ + { + name: "test case 1", + fields: fields{ + Metadata: make(map[string]string), + Certificates: map[string]string{ + "caCrt": "s3://some-bucket/12345.crt", + "caKey": "s3://some-bucket/12345.key", + }, + Settings: map[string]string{ + "kubeContext": "minikube", + "username": "admin", + "password": "$K8S_PASSWORD", + "clusterURI": "https://192.168.99.100:8443", + }, + Namespaces: map[string]string{ + "staging": "staging", + }, + HelmRepos: map[string]string{ + "stable": "https://kubernetes-charts.storage.googleapis.com", + "myrepo": "s3://my-repo/charts", + }, + Apps: make(map[string]release), + }, + want: true, + }, { + name: "test case 2 -- settings/nil_value", + fields: fields{ + Metadata: make(map[string]string), + Certificates: map[string]string{ + "caCrt": "s3://some-bucket/12345.crt", + "caKey": "s3://some-bucket/12345.key", + }, + Settings: nil, + Namespaces: map[string]string{ + "staging": "staging", + }, + HelmRepos: map[string]string{ + "stable": "https://kubernetes-charts.storage.googleapis.com", + "myrepo": "s3://my-repo/charts", + }, + Apps: make(map[string]release), + }, + want: false, + }, { + name: "test case 3 -- settings/empty_context", + fields: fields{ + Metadata: make(map[string]string), + Certificates: map[string]string{ + "caCrt": "s3://some-bucket/12345.crt", + "caKey": "s3://some-bucket/12345.key", + }, + Settings: map[string]string{ + "kubeContext": "", + "username": "admin", + "password": "$K8S_PASSWORD", + "clusterURI": "https://192.168.99.100:8443", + }, + Namespaces: map[string]string{ + "staging": "staging", + }, + HelmRepos: map[string]string{ + "stable": "https://kubernetes-charts.storage.googleapis.com", + "myrepo": "s3://my-repo/charts", + }, + Apps: make(map[string]release), + }, + want: false, + }, { + name: "test case 4 -- settings/optional_params", + fields: fields{ + Metadata: make(map[string]string), + Certificates: map[string]string{ + "caCrt": "s3://some-bucket/12345.crt", + "caKey": "s3://some-bucket/12345.key", + }, + Settings: map[string]string{ + "kubeContext": "minikube", + }, + Namespaces: map[string]string{ + "staging": "staging", + }, + HelmRepos: map[string]string{ + "stable": "https://kubernetes-charts.storage.googleapis.com", + "myrepo": "s3://my-repo/charts", + }, + Apps: make(map[string]release), + }, + want: true, + }, { + name: "test case 5 -- settings/password", + fields: fields{ + Metadata: make(map[string]string), + Certificates: map[string]string{ + "caCrt": "s3://some-bucket/12345.crt", + "caKey": "s3://some-bucket/12345.key", + }, + Settings: map[string]string{ + "kubeContext": "minikube", + "username": "admin", + "password": "K8S_PASSWORD", + "clusterURI": "https://192.168.99.100:8443", + }, + Namespaces: map[string]string{ + "staging": "staging", + }, + HelmRepos: map[string]string{ + "stable": "https://kubernetes-charts.storage.googleapis.com", + "myrepo": "s3://my-repo/charts", + }, + Apps: make(map[string]release), + }, + want: false, + }, { + name: "test case 6 -- settings/clusterURI", + fields: fields{ + Metadata: make(map[string]string), + Certificates: map[string]string{ + "caCrt": "s3://some-bucket/12345.crt", + "caKey": "s3://some-bucket/12345.key", + }, + Settings: map[string]string{ + "kubeContext": "minikube", + "username": "admin", + "password": "K8S_PASSWORD", + "clusterURI": "https//192.168.99.100:8443", + }, + Namespaces: map[string]string{ + "staging": "staging", + }, + HelmRepos: map[string]string{ + "stable": "https://kubernetes-charts.storage.googleapis.com", + "myrepo": "s3://my-repo/charts", + }, + Apps: make(map[string]release), + }, + want: false, + }, { + name: "test case 7 -- certifications/missing key", + fields: fields{ + Metadata: make(map[string]string), + Certificates: map[string]string{ + "caCrt": "s3://some-bucket/12345.crt", + }, + Settings: map[string]string{ + "kubeContext": "minikube", + "username": "admin", + "password": "$K8S_PASSWORD", + "clusterURI": "https://192.168.99.100:8443", + }, + Namespaces: map[string]string{ + "staging": "staging", + }, + HelmRepos: map[string]string{ + "stable": "https://kubernetes-charts.storage.googleapis.com", + "myrepo": "s3://my-repo/charts", + }, + Apps: make(map[string]release), + }, + want: false, + }, { + name: "test case 8 -- certifications/nil_value", + fields: fields{ + Metadata: make(map[string]string), + Certificates: nil, + Settings: map[string]string{ + "kubeContext": "minikube", + "username": "admin", + "password": "$K8S_PASSWORD", + "clusterURI": "https://192.168.99.100:8443", + }, + Namespaces: map[string]string{ + "staging": "staging", + }, + HelmRepos: map[string]string{ + "stable": "https://kubernetes-charts.storage.googleapis.com", + "myrepo": "s3://my-repo/charts", + }, + Apps: make(map[string]release), + }, + want: false, + }, { + name: "test case 9 -- certifications/invalid_s3", + fields: fields{ + Metadata: make(map[string]string), + Certificates: map[string]string{ + "caCrt": "s3://some-bucket/12345.crt", + "caKey": "http://someurl.com/", + }, + Settings: map[string]string{ + "kubeContext": "minikube", + "username": "admin", + "password": "$K8S_PASSWORD", + "clusterURI": "https://192.168.99.100:8443", + }, + Namespaces: map[string]string{ + "staging": "staging", + }, + HelmRepos: map[string]string{ + "stable": "https://kubernetes-charts.storage.googleapis.com", + "myrepo": "s3://my-repo/charts", + }, + Apps: make(map[string]release), + }, + want: false, + }, { + name: "test case 10 -- certifications/nil_value_pass", + fields: fields{ + Metadata: make(map[string]string), + Certificates: nil, + Settings: map[string]string{ + "kubeContext": "minikube", + }, + Namespaces: map[string]string{ + "staging": "staging", + }, + HelmRepos: map[string]string{ + "stable": "https://kubernetes-charts.storage.googleapis.com", + "myrepo": "s3://my-repo/charts", + }, + Apps: make(map[string]release), + }, + want: true, + }, { + name: "test case 11 -- namespaces/nil_value", + fields: fields{ + Metadata: make(map[string]string), + Certificates: nil, + Settings: map[string]string{ + "kubeContext": "minikube", + }, + Namespaces: nil, + HelmRepos: map[string]string{ + "stable": "https://kubernetes-charts.storage.googleapis.com", + "myrepo": "s3://my-repo/charts", + }, + Apps: make(map[string]release), + }, + want: false, + }, { + name: "test case 12 -- namespaces/empty", + fields: fields{ + Metadata: make(map[string]string), + Certificates: nil, + Settings: map[string]string{ + "kubeContext": "minikube", + }, + Namespaces: map[string]string{}, + HelmRepos: map[string]string{ + "stable": "https://kubernetes-charts.storage.googleapis.com", + "myrepo": "s3://my-repo/charts", + }, + Apps: make(map[string]release), + }, + want: false, + }, { + name: "test case 13 -- namespaces/empty_namespace_value", + fields: fields{ + Metadata: make(map[string]string), + Certificates: nil, + Settings: map[string]string{ + "kubeContext": "minikube", + }, + Namespaces: map[string]string{ + "staging": "staging", + "x": "y", + "z": "", + }, + HelmRepos: map[string]string{ + "stable": "https://kubernetes-charts.storage.googleapis.com", + "myrepo": "s3://my-repo/charts", + }, + Apps: make(map[string]release), + }, + want: false, + }, { + name: "test case 14 -- helmRepos/nil_value", + fields: fields{ + Metadata: make(map[string]string), + Certificates: nil, + Settings: map[string]string{ + "kubeContext": "minikube", + }, + Namespaces: map[string]string{ + "staging": "staging", + }, + HelmRepos: nil, + Apps: make(map[string]release), + }, + want: false, + }, { + name: "test case 15 -- helmRepos/empty", + fields: fields{ + Metadata: make(map[string]string), + Certificates: nil, + Settings: map[string]string{ + "kubeContext": "minikube", + }, + Namespaces: map[string]string{ + "staging": "staging", + }, + HelmRepos: map[string]string{}, + Apps: make(map[string]release), + }, + want: false, + }, { + name: "test case 16 -- helmRepos/empty_repo_value", + fields: fields{ + Metadata: make(map[string]string), + Certificates: nil, + Settings: map[string]string{ + "kubeContext": "minikube", + }, + Namespaces: map[string]string{ + "staging": "staging", + }, + HelmRepos: map[string]string{ + "stable": "https://kubernetes-charts.storage.googleapis.com", + "myrepo": "", + }, + Apps: make(map[string]release), + }, + want: false, + }, { + name: "test case 17 -- helmRepos/invalid_repo_value", + fields: fields{ + Metadata: make(map[string]string), + Certificates: nil, + Settings: map[string]string{ + "kubeContext": "minikube", + }, + Namespaces: map[string]string{ + "staging": "staging", + }, + HelmRepos: map[string]string{ + "stable": "https://kubernetes-charts.storage.googleapis.com", + "myrepo": "s3//my-repo/charts", + }, + Apps: make(map[string]release), + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := state{ + Metadata: tt.fields.Metadata, + Certificates: tt.fields.Certificates, + Settings: tt.fields.Settings, + Namespaces: tt.fields.Namespaces, + HelmRepos: tt.fields.HelmRepos, + Apps: tt.fields.Apps, + } + if got, _ := s.validate(); got != tt.want { + t.Errorf("state.validate() = %v, want %v", got, tt.want) + } + }) + } +} + +// func Test_state_print(t *testing.T) { +// type fields struct { +// Metadata map[string]string +// Certifications map[string]string +// Settings map[string]string +// Namespaces map[string]string +// HelmRepos map[string]string +// Apps map[string]release +// } +// tests := []struct { +// name string +// fields fields +// }{ +// // TODO: Add test cases. +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// s := state{ +// Metadata: tt.fields.Metadata, +// Certifications: tt.fields.Certifications, +// Settings: tt.fields.Settings, +// Namespaces: tt.fields.Namespaces, +// HelmRepos: tt.fields.HelmRepos, +// Apps: tt.fields.Apps, +// } +// s.print() +// }) +// } +// } diff --git a/test_files/dockerfile b/test_files/dockerfile new file mode 100644 index 00000000..6cde0e48 --- /dev/null +++ b/test_files/dockerfile @@ -0,0 +1,26 @@ +# This is a docker image for the helmsman test container +# It can be pulled from praqma/helmsman-test +FROM golang:1.8-alpine3.6 as builder + +ENV KUBE_LATEST_VERSION v1.8.2 +ENV HELM_VERSION v2.7.0 +WORKDIR /go/src/ + +RUN apk add --update ca-certificates \ + && apk add --update -t deps curl \ + && curl -L https://storage.googleapis.com/kubernetes-release/release/${KUBE_LATEST_VERSION}/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl \ + && chmod +x /usr/local/bin/kubectl + +RUN apk add --update --no-cache ca-certificates git \ + && apk add --update -t deps curl tar gzip make bash \ + && curl -L http://storage.googleapis.com/kubernetes-helm/helm-${HELM_VERSION}-linux-amd64.tar.gz | tar zxv -C /tmp \ + && mv /tmp/linux-amd64/helm /usr/local/bin/helm \ + && chmod +x /usr/local/bin/helm \ + && rm -rf /tmp/linux-amd64 + # && mkdir -p ~/.helm/plugins \ + # && helm plugin install https://github.com/hypnoglow/helm-s3.git --version 0.4.0 \ + +RUN go get github.com/BurntSushi/toml \ + && go get github.com/goreleaser/goreleaser + # && git clone https://github.com/Praqma/helmsman.git \ + # && go install helmsman/. \ No newline at end of file diff --git a/test_files/invalid_example.toml b/test_files/invalid_example.toml new file mode 100644 index 00000000..f2b71d1b --- /dev/null +++ b/test_files/invalid_example.toml @@ -0,0 +1,19 @@ +# THIS IS AN INVALID TOML FILE USED FOR TESTING PURPOSES ONLY. +[metadata] +org = orgX +maintainer = "k8s-admin" + +[certificates] + +[settings] +kubeContext = + +[namespaces] +staging = "staging" +production = "default" + +[helmRepos] +stable = "https://kubernetes-charts.storage.googleapis.com" +incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" + +[apps] diff --git a/test_files/values.xml b/test_files/values.xml new file mode 100644 index 00000000..e69de29b diff --git a/test_files/values.yaml b/test_files/values.yaml new file mode 100644 index 00000000..e69de29b diff --git a/utils.go b/utils.go index 43b23857..2255a66d 100644 --- a/utils.go +++ b/utils.go @@ -7,6 +7,7 @@ import ( "log" "os" "path/filepath" + "strconv" "strings" "github.com/BurntSushi/toml" @@ -21,13 +22,12 @@ func printMap(m map[string]string) { // fromTOML reads a toml file and decodes it to a state type. // It uses the BurntSuchi TOML parser which throws an error if the TOML file is not valid. -func fromTOML(file string, s *state) { +func fromTOML(file string, s *state) (bool, string) { if _, err := toml.DecodeFile(file, s); err != nil { - log.Fatal(err) - os.Exit(1) + return false, err.Error() } - log.Printf("Parsed [[ %s ]] successfully and found [%v] apps", file, len(s.Apps)) + return true, "Parsed [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps." } diff --git a/utils_test.go b/utils_test.go new file mode 100644 index 00000000..ccebaab7 --- /dev/null +++ b/utils_test.go @@ -0,0 +1,154 @@ +package main + +import "testing" + +// func Test_printMap(t *testing.T) { +// type args struct { +// m map[string]string +// } +// tests := []struct { +// name string +// args args +// }{ +// // TODO: Add test cases. +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// printMap(tt.args.m) +// }) +// } +// } + +func Test_fromTOML(t *testing.T) { + type args struct { + file string + s *state + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "test case 1 -- invalid TOML", + args: args{ + file: "test_files/invalid_example.toml", + s: new(state), + }, + want: false, + }, { + name: "test case 2 -- valid TOML", + args: args{ + file: "example.toml", + s: new(state), + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got, _ := fromTOML(tt.args.file, tt.args.s); got != tt.want { + t.Errorf("fromToml() = %v, want %v", got, tt.want) + } + }) + } +} + +// func Test_toTOML(t *testing.T) { +// type args struct { +// file string +// s *state +// } +// tests := []struct { +// name string +// args args +// }{ +// // TODO: Add test cases. +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// toTOML(tt.args.file, tt.args.s) +// }) +// } +// } + +func Test_isOfType(t *testing.T) { + type args struct { + filename string + filetype string + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "test case 1 -- valid xml check", + args: args{ + filename: "name.xml", + filetype: ".xml", + }, + want: true, + }, { + name: "test case 2 -- valid yaml check", + args: args{ + filename: "another_name.yaml", + filetype: ".yaml", + }, + want: true, + }, { + name: "test case 3 -- invalid yaml check", + args: args{ + filename: "name.xml", + filetype: ".yaml", + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := isOfType(tt.args.filename, tt.args.filetype); got != tt.want { + t.Errorf("isOfType() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_readFile(t *testing.T) { + type args struct { + filepath string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "test case 1 -- successful reading.", + args: args{ + filepath: "test_files/values.yaml", + }, + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := readFile(tt.args.filepath); got != tt.want { + t.Errorf("readFile() = %v, want %v", got, tt.want) + } + }) + } +} + +// func Test_printHelp(t *testing.T) { +// tests := []struct { +// name string +// }{ +// // TODO: Add test cases. +// } +// for range tests { +// t.Run(tt.name, func(t *testing.T) { +// printHelp() +// }) +// } +// } From 9d13fa9328effd8908ce4c932da8f7aaca97b7ea Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Thu, 16 Nov 2017 13:15:24 +0100 Subject: [PATCH 0017/1127] removing the context setup from init to allow tests to run in a non-configured env. --- init.go | 194 ------------------------------------------------------ main.go | 199 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 198 insertions(+), 195 deletions(-) diff --git a/init.go b/init.go index 17008692..6323d97a 100644 --- a/init.go +++ b/init.go @@ -4,7 +4,6 @@ import ( "flag" "log" "os" - "strings" ) // init is executed after all package vars are initialized [before the main() func in this case]. @@ -65,196 +64,3 @@ func toolExists(tool string) bool { return true } - -// addNamespaces creates a set of namespaces in your k8s cluster. -// If a namespace with the same name exsts, it will skip it. -func addNamespaces(namespaces map[string]string) { - for _, namespace := range namespaces { - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "kubectl create namespace " + namespace}, - Description: "creating namespace " + namespace, - } - - exitCode, _ := cmd.exec(debug) - - if exitCode != 0 { - log.Println("WARN: I could not create namespace [" + - namespace + " ]. It already exists. I am skipping this.") - } - } -} - -// validateReleaseCharts validates if the charts defined in a release are valid. -// Valid charts are the ones that can be found in the defined repos. -// This function uses Helm search to verify if the chart can be found or not. -func validateReleaseCharts(apps map[string]release) bool { - - for app, r := range apps { - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm search " + r.Chart + " --version " + r.Version}, - Description: "validating chart " + r.Chart + "-" + r.Version + " is available in the used repos.", - } - - exitCode, _ := cmd.exec(debug) - - if exitCode != 0 { - log.Fatal("ERROR: chart "+r.Chart+"-"+r.Version+" is specified for ", - "app ["+app+"] but is not found in the provided repos.") - return false - } - } - return true -} - -// addHelmRepos adds repositories to Helm if they don't exist already. -// Helm does not mind if a repo with the same name exists. It treats it as an update. -func addHelmRepos(repos map[string]string) bool { - - for repoName, url := range repos { - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm repo add " + repoName + " " + url}, - Description: "adding repo " + repoName, - } - - exitCode, _ := cmd.exec(debug) - - if exitCode != 0 { - log.Fatal("ERROR: there has been a problem while adding repo [" + - repoName + "].") - return false - } - - } - - return true -} - -// setKubeContext sets your kubectl context to the one specified in the desired state file. -// It returns false if it fails to set the context. This means the context deos not exist. -func setKubeContext(context string) bool { - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "kubectl config use-context " + context}, - Description: "setting kubectl context to [ " + context + " ]", - } - - exitCode, _ := cmd.exec(debug) - - if exitCode != 0 { - log.Println("INFO: KubeContext: " + context + " does not exist. I will try to create it.") - return false - } - - return true -} - -// createContext creates a context -connecting to a k8s cluster- in kubectl config. -// It returns true if successful, false otherwise -func createContext() bool { - - var password string - if s.Settings["password"] == "" || s.Settings["username"] == "" || s.Settings["clusterURI"] == "" { - log.Fatal("ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ] " + - "as you did not specify enough information in the Settings section of your desired state file.") - return false - } else if s.Certificates == nil || s.Certificates["caCrt"] == "" || s.Certificates["caKey"] == "" { - log.Fatal("ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ] " + - "as you did not provide Certifications to use in your desired state file.") - return false - } else { - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "echo " + s.Settings["password"]}, - Description: "reading the password from env variable.", - } - - var exitCode int - exitCode, password = cmd.exec(debug) - - password = strings.TrimSpace(password) - if exitCode != 0 || password == "" { - log.Fatal("ERROR: failed to read password from env variable.") - return false - } - } - - // download certs using AWS cli - if !toolExists("aws help") { - log.Fatal("ERROR: aws is not installed/configured correctly. It is needed for downloading certs. Aborting!") - return false - } - - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "aws s3 cp " + s.Certificates["caCrt"] + " ca.crt"}, - Description: "downloading ca.crt from S3.", - } - - exitCode, _ := cmd.exec(debug) - - if exitCode != 0 { - log.Fatal("ERROR: failed to download caCrt.") - return false - } - - cmd = command{ - Cmd: "bash", - Args: []string{"-c", "aws s3 cp " + s.Certificates["caKey"] + " ca.key"}, - Description: "downloading ca.key from S3.", - } - - exitCode, _ = cmd.exec(debug) - - if exitCode != 0 { - log.Fatal("ERROR: failed to download caKey.") - return false - } - - // connecting to the cluster - cmd = command{ - Cmd: "bash", - Args: []string{"-c", "kubectl config set-credentials " + s.Settings["username"] + " --username=" + s.Settings["username"] + - " --password=" + password + " --client-key=ca.key"}, - Description: "creating kubectl context - setting credentials.", - } - - exitCode, _ = cmd.exec(debug) - - if exitCode != 0 { - log.Fatal("ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ].") - return false - } - - cmd = command{ - Cmd: "bash", - Args: []string{"-c", "kubectl config set-cluster " + s.Settings["kubeContext"] + " --server=" + s.Settings["clusterURI"] + - " --certificate-authority=ca.crt"}, - Description: "creating kubectl context - setting cluster.", - } - - exitCode, _ = cmd.exec(debug) - - if exitCode != 0 { - log.Fatal("ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ].") - return false - } - - cmd = command{ - Cmd: "bash", - Args: []string{"-c", "kubectl config set-context " + s.Settings["kubeContext"] + " --cluster=" + s.Settings["kubeContext"] + - " --user=" + s.Settings["username"] + " --password=" + password}, - Description: "creating kubectl context - setting context.", - } - - exitCode, _ = cmd.exec(debug) - - if exitCode != 0 { - log.Fatal("ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ].") - return false - } - - return setKubeContext(s.Settings["kubeContext"]) -} diff --git a/main.go b/main.go index f911722c..998633d0 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,10 @@ package main -import "os" +import ( + "log" + "os" + "strings" +) var s state var debug bool @@ -39,3 +43,196 @@ func main() { } } + +// setKubeContext sets your kubectl context to the one specified in the desired state file. +// It returns false if it fails to set the context. This means the context deos not exist. +func setKubeContext(context string) bool { + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "kubectl config use-context " + context}, + Description: "setting kubectl context to [ " + context + " ]", + } + + exitCode, _ := cmd.exec(debug) + + if exitCode != 0 { + log.Println("INFO: KubeContext: " + context + " does not exist. I will try to create it.") + return false + } + + return true +} + +// addHelmRepos adds repositories to Helm if they don't exist already. +// Helm does not mind if a repo with the same name exists. It treats it as an update. +func addHelmRepos(repos map[string]string) bool { + + for repoName, url := range repos { + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm repo add " + repoName + " " + url}, + Description: "adding repo " + repoName, + } + + exitCode, _ := cmd.exec(debug) + + if exitCode != 0 { + log.Fatal("ERROR: there has been a problem while adding repo [" + + repoName + "].") + return false + } + + } + + return true +} + +// validateReleaseCharts validates if the charts defined in a release are valid. +// Valid charts are the ones that can be found in the defined repos. +// This function uses Helm search to verify if the chart can be found or not. +func validateReleaseCharts(apps map[string]release) bool { + + for app, r := range apps { + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm search " + r.Chart + " --version " + r.Version}, + Description: "validating chart " + r.Chart + "-" + r.Version + " is available in the used repos.", + } + + exitCode, _ := cmd.exec(debug) + + if exitCode != 0 { + log.Fatal("ERROR: chart "+r.Chart+"-"+r.Version+" is specified for ", + "app ["+app+"] but is not found in the provided repos.") + return false + } + } + return true +} + +// addNamespaces creates a set of namespaces in your k8s cluster. +// If a namespace with the same name exsts, it will skip it. +func addNamespaces(namespaces map[string]string) { + for _, namespace := range namespaces { + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "kubectl create namespace " + namespace}, + Description: "creating namespace " + namespace, + } + + exitCode, _ := cmd.exec(debug) + + if exitCode != 0 { + log.Println("WARN: I could not create namespace [" + + namespace + " ]. It already exists. I am skipping this.") + } + } +} + +// createContext creates a context -connecting to a k8s cluster- in kubectl config. +// It returns true if successful, false otherwise +func createContext() bool { + + var password string + if s.Settings["password"] == "" || s.Settings["username"] == "" || s.Settings["clusterURI"] == "" { + log.Fatal("ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ] " + + "as you did not specify enough information in the Settings section of your desired state file.") + return false + } else if s.Certificates == nil || s.Certificates["caCrt"] == "" || s.Certificates["caKey"] == "" { + log.Fatal("ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ] " + + "as you did not provide Certifications to use in your desired state file.") + return false + } else { + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "echo " + s.Settings["password"]}, + Description: "reading the password from env variable.", + } + + var exitCode int + exitCode, password = cmd.exec(debug) + + password = strings.TrimSpace(password) + if exitCode != 0 || password == "" { + log.Fatal("ERROR: failed to read password from env variable.") + return false + } + } + + // download certs using AWS cli + if !toolExists("aws help") { + log.Fatal("ERROR: aws is not installed/configured correctly. It is needed for downloading certs. Aborting!") + return false + } + + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "aws s3 cp " + s.Certificates["caCrt"] + " ca.crt"}, + Description: "downloading ca.crt from S3.", + } + + exitCode, _ := cmd.exec(debug) + + if exitCode != 0 { + log.Fatal("ERROR: failed to download caCrt.") + return false + } + + cmd = command{ + Cmd: "bash", + Args: []string{"-c", "aws s3 cp " + s.Certificates["caKey"] + " ca.key"}, + Description: "downloading ca.key from S3.", + } + + exitCode, _ = cmd.exec(debug) + + if exitCode != 0 { + log.Fatal("ERROR: failed to download caKey.") + return false + } + + // connecting to the cluster + cmd = command{ + Cmd: "bash", + Args: []string{"-c", "kubectl config set-credentials " + s.Settings["username"] + " --username=" + s.Settings["username"] + + " --password=" + password + " --client-key=ca.key"}, + Description: "creating kubectl context - setting credentials.", + } + + exitCode, _ = cmd.exec(debug) + + if exitCode != 0 { + log.Fatal("ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ].") + return false + } + + cmd = command{ + Cmd: "bash", + Args: []string{"-c", "kubectl config set-cluster " + s.Settings["kubeContext"] + " --server=" + s.Settings["clusterURI"] + + " --certificate-authority=ca.crt"}, + Description: "creating kubectl context - setting cluster.", + } + + exitCode, _ = cmd.exec(debug) + + if exitCode != 0 { + log.Fatal("ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ].") + return false + } + + cmd = command{ + Cmd: "bash", + Args: []string{"-c", "kubectl config set-context " + s.Settings["kubeContext"] + " --cluster=" + s.Settings["kubeContext"] + + " --user=" + s.Settings["username"] + " --password=" + password}, + Description: "creating kubectl context - setting context.", + } + + exitCode, _ = cmd.exec(debug) + + if exitCode != 0 { + log.Fatal("ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ].") + return false + } + + return setKubeContext(s.Settings["kubeContext"]) +} From 43fe84a7453a819e267a669a3d5e72f9611e6bc5 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Thu, 16 Nov 2017 13:16:05 +0100 Subject: [PATCH 0018/1127] cleaned up dockerfile for the helmsman test --- test_files/dockerfile | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/test_files/dockerfile b/test_files/dockerfile index 6cde0e48..9c0ee80f 100644 --- a/test_files/dockerfile +++ b/test_files/dockerfile @@ -17,10 +17,6 @@ RUN apk add --update --no-cache ca-certificates git \ && mv /tmp/linux-amd64/helm /usr/local/bin/helm \ && chmod +x /usr/local/bin/helm \ && rm -rf /tmp/linux-amd64 - # && mkdir -p ~/.helm/plugins \ - # && helm plugin install https://github.com/hypnoglow/helm-s3.git --version 0.4.0 \ RUN go get github.com/BurntSushi/toml \ - && go get github.com/goreleaser/goreleaser - # && git clone https://github.com/Praqma/helmsman.git \ - # && go install helmsman/. \ No newline at end of file + && go get github.com/goreleaser/goreleaser \ No newline at end of file From ee8400f7cc6977907ec08ab28928ec4d2959795f Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Thu, 16 Nov 2017 13:17:06 +0100 Subject: [PATCH 0019/1127] updated the circleci script and config to use the hemlsman-test image --- .circleci/build.sh | 2 +- .circleci/config.yml | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.circleci/build.sh b/.circleci/build.sh index b2d4ba5c..eca885e3 100644 --- a/.circleci/build.sh +++ b/.circleci/build.sh @@ -1,6 +1,6 @@ #!/bin/bash -x echo "running tests ..." -go test +go test -v if [ $? -eq 0 ]; then echo "releasing ..." diff --git a/.circleci/config.yml b/.circleci/config.yml index 40d7812c..79de1af0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -6,7 +6,7 @@ jobs: build: docker: # specify the version - - image: circleci/golang:1.8 + - image: praqma/helmsman-test # Specify service dependencies here if necessary # CircleCI maintains a library of pre-built images @@ -17,12 +17,11 @@ jobs: #### expecting it in the form of #### /go/src/github.com/circleci/go-tool #### /go/src/bitbucket.org/circleci/go-tool - working_directory: /go/src/github.com/praqma/helmsman + working_directory: /go/src/ steps: - checkout # specify any bash command here prefixed with `run: ` - - run: go get github.com/BurntSushi/toml - - run: go get github.com/goreleaser/goreleaser + - run: git clone https://github.com/Praqma/helmsman.git - run: chmod +x .circleci/build.sh - run: .circleci/build.sh \ No newline at end of file From 03a13765eb6b383defe5b05398ac432ebfb3034d Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Thu, 16 Nov 2017 13:21:59 +0100 Subject: [PATCH 0020/1127] #10 removed the checkout step from circleci config since it fails --- .circleci/config.yml | 1 - test_files/dockerfile | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 79de1af0..0c76a658 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -19,7 +19,6 @@ jobs: #### /go/src/bitbucket.org/circleci/go-tool working_directory: /go/src/ steps: - - checkout # specify any bash command here prefixed with `run: ` - run: git clone https://github.com/Praqma/helmsman.git diff --git a/test_files/dockerfile b/test_files/dockerfile index 9c0ee80f..22a8a4e9 100644 --- a/test_files/dockerfile +++ b/test_files/dockerfile @@ -4,7 +4,6 @@ FROM golang:1.8-alpine3.6 as builder ENV KUBE_LATEST_VERSION v1.8.2 ENV HELM_VERSION v2.7.0 -WORKDIR /go/src/ RUN apk add --update ca-certificates \ && apk add --update -t deps curl \ @@ -19,4 +18,6 @@ RUN apk add --update --no-cache ca-certificates git \ && rm -rf /tmp/linux-amd64 RUN go get github.com/BurntSushi/toml \ - && go get github.com/goreleaser/goreleaser \ No newline at end of file + && go get github.com/goreleaser/goreleaser + +WORKDIR /go/src/ \ No newline at end of file From c566252915a527436713eccc06aed03012bb1e42 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Thu, 16 Nov 2017 13:38:59 +0100 Subject: [PATCH 0021/1127] #11 updated plan_test --- plan_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plan_test.go b/plan_test.go index c12cbd49..31454e09 100644 --- a/plan_test.go +++ b/plan_test.go @@ -128,15 +128,15 @@ func Test_plan_execPlan(t *testing.T) { Commands: []command{ { Cmd: "bash", - Args: []string{"-c", "export TEST='hello world'"}, - Description: "Setting TEST env var.", + Args: []string{"-c", "TEST='hello world'"}, + Description: "Setting TEST var.", }, { Cmd: "bash", - Args: []string{"-c", "export TEST='hello world, again!'"}, - Description: "Setting TEST env var, again.", + Args: []string{"-c", "TEST='hello world, again!'"}, + Description: "Setting TEST var, again.", }, }, - Decisions: []string{"Set TEST env var to: hello world.", "Set TEST env var to: hello world, again!."}, + Decisions: []string{"Set TEST var to: hello world.", "Set TEST var to: hello world, again!."}, Created: time.Now(), }, }, From a1b61e97536aa6250562574653947ceed1059555 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Thu, 16 Nov 2017 17:36:11 +0100 Subject: [PATCH 0022/1127] #10 fixed the working directory in the testing docker image --- .circleci/config.yml | 4 ++-- test_files/dockerfile | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0c76a658..dbbea78a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -17,10 +17,10 @@ jobs: #### expecting it in the form of #### /go/src/github.com/circleci/go-tool #### /go/src/bitbucket.org/circleci/go-tool - working_directory: /go/src/ + # working_directory: /go/src/ steps: + - checkout # specify any bash command here prefixed with `run: ` - - run: git clone https://github.com/Praqma/helmsman.git - run: chmod +x .circleci/build.sh - run: .circleci/build.sh \ No newline at end of file diff --git a/test_files/dockerfile b/test_files/dockerfile index 22a8a4e9..0c4e5bab 100644 --- a/test_files/dockerfile +++ b/test_files/dockerfile @@ -20,4 +20,4 @@ RUN apk add --update --no-cache ca-certificates git \ RUN go get github.com/BurntSushi/toml \ && go get github.com/goreleaser/goreleaser -WORKDIR /go/src/ \ No newline at end of file +WORKDIR src/helmsman \ No newline at end of file From 0dc06cbcf9a32ef9cd29a32ddeac9c2d86952d51 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Thu, 16 Nov 2017 17:37:15 +0100 Subject: [PATCH 0023/1127] #11 changed the test to create empty files and inspect them. --- plan_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/plan_test.go b/plan_test.go index 31454e09..664ca4a7 100644 --- a/plan_test.go +++ b/plan_test.go @@ -128,15 +128,15 @@ func Test_plan_execPlan(t *testing.T) { Commands: []command{ { Cmd: "bash", - Args: []string{"-c", "TEST='hello world'"}, - Description: "Setting TEST var.", + Args: []string{"-c", "touch hello.world"}, + Description: "Creating hello.world file.", }, { Cmd: "bash", - Args: []string{"-c", "TEST='hello world, again!'"}, - Description: "Setting TEST var, again.", + Args: []string{"-c", "touch hello.world1"}, + Description: "Creating hello.world1 file.", }, }, - Decisions: []string{"Set TEST var to: hello world.", "Set TEST var to: hello world, again!."}, + Decisions: []string{"Create hello.world.", "Create hello.world1."}, Created: time.Now(), }, }, @@ -151,10 +151,10 @@ func Test_plan_execPlan(t *testing.T) { p.execPlan() c := command{ Cmd: "bash", - Args: []string{"-c", "echo $TEST"}, + Args: []string{"-c", "ls | grep hello.world | wc -l"}, Description: "", } - if _, got := c.exec(false); strings.TrimSpace(got) != "hello world, again!" { + if _, got := c.exec(false); strings.TrimSpace(got) != "2" { t.Errorf("execPlan(): got %v, want hello world, again!", got) } }) From 20fb1f7c456adec381ccfe680b754429c57e4bf8 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Thu, 16 Nov 2017 17:44:38 +0100 Subject: [PATCH 0024/1127] #10 fixed a typo in the circleci script. --- .circleci/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/build.sh b/.circleci/build.sh index eca885e3..c8027236 100644 --- a/.circleci/build.sh +++ b/.circleci/build.sh @@ -6,7 +6,7 @@ if [ $? -eq 0 ]; then echo "releasing ..." goreleaser | grep -o "error" if [ $? -eq 0 ]; then - goreleaser | grep -0 "already_exists" + goreleaser | grep -o "already_exists" if [ $? -gt 0 ]; then echo "ERROR: failed to release. Terminating." exit 9 From 74dca4b650edb28bf76e3a44237b893f8855f12b Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Thu, 16 Nov 2017 18:00:26 +0100 Subject: [PATCH 0025/1127] #10 updated the circleci script --- .circleci/build.sh | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) mode change 100644 => 100755 .circleci/build.sh diff --git a/.circleci/build.sh b/.circleci/build.sh old mode 100644 new mode 100755 index c8027236..26d4acaa --- a/.circleci/build.sh +++ b/.circleci/build.sh @@ -3,19 +3,23 @@ echo "running tests ..." go test -v if [ $? -eq 0 ]; then - echo "releasing ..." - goreleaser | grep -o "error" + echo "building ..." + go build + if [ $? -eq 0 ]; then - goreleaser | grep -o "already_exists" - if [ $? -gt 0 ]; then - echo "ERROR: failed to release. Terminating." - exit 9 + echo "cleaning after tests ..." + rm hello.world hello.world1 helmsman + echo "releasing ..." + goreleaser | tee /dev/tty | grep -o "error" + if [ $? -eq 0 ]; then + echo "goreleaser experienced an error and no new releases made. That is Ok!" else - echo "No new releases made. That is Ok!" - fi + echo "New release was successfully made." + fi else - echo "New release was successfully made." - fi + echo "Build failed!!" + exit 9 + fi else echo "tests failed ... Aborting!" exit 9 From 3a9d0e28ce3c101298e5ec1c2a2e5849ba3177b1 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 18 Nov 2017 11:56:32 +0100 Subject: [PATCH 0026/1127] removed the entrypoint instruction from helmsman docker image. --- dockerfile/dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile index 592545ea..8c1408f3 100644 --- a/dockerfile/dockerfile +++ b/dockerfile/dockerfile @@ -36,5 +36,5 @@ COPY --from=kube /usr/local/bin/kubectl /bin/kubectl COPY --from=builder /go/bin/helmsman /bin/helmsman WORKDIR /tmp -ENTRYPOINT ["/bin/helmsman"] +# ENTRYPOINT ["/bin/helmsman"] From 7ae21092090feca6c90f709ad1f720e6cf8775b3 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 18 Nov 2017 13:30:02 +0100 Subject: [PATCH 0027/1127] added helm init and updated error messages processing. --- main.go | 99 +++++++++++++++++++++++++++++++------------------------- state.go | 2 +- 2 files changed, 56 insertions(+), 45 deletions(-) diff --git a/main.go b/main.go index 998633d0..35729200 100644 --- a/main.go +++ b/main.go @@ -2,7 +2,6 @@ package main import ( "log" - "os" "strings" ) @@ -16,19 +15,23 @@ func main() { // set the kubecontext to be used Or create it if it does not exist if !setKubeContext(s.Settings["kubeContext"]) { - if !createContext() { - os.Exit(1) + if r, msg := createContext(); !r { + log.Fatal(msg) } } + if r, msg := initHelm(); !r { + log.Fatal(msg) + } + // add repos -- fails if they are not valid - if !addHelmRepos(s.HelmRepos) { - os.Exit(1) + if r, msg := addHelmRepos(s.HelmRepos); !r { + log.Fatal(msg) } // validate charts-versions exist in supllied repos - if !validateReleaseCharts(s.Apps) { - os.Exit(1) + if r, msg := validateReleaseCharts(s.Apps); !r { + log.Fatal(msg) } // add/validate namespaces @@ -63,9 +66,25 @@ func setKubeContext(context string) bool { return true } +// initHelm initialize helm on a k8s cluster +func initHelm() (bool, string) { + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm init"}, + Description: "initializing helm on the current context.", + } + + exitCode, msg := cmd.exec(debug) + + if exitCode != 0 { + return false, "ERROR: there has been a problem while initializing helm: " + msg + } + return true, "" +} + // addHelmRepos adds repositories to Helm if they don't exist already. // Helm does not mind if a repo with the same name exists. It treats it as an update. -func addHelmRepos(repos map[string]string) bool { +func addHelmRepos(repos map[string]string) (bool, string) { for repoName, url := range repos { cmd := command{ @@ -77,20 +96,18 @@ func addHelmRepos(repos map[string]string) bool { exitCode, _ := cmd.exec(debug) if exitCode != 0 { - log.Fatal("ERROR: there has been a problem while adding repo [" + - repoName + "].") - return false + return false, "ERROR: there has been a problem while adding repo [" + repoName + "]." } } - return true + return true, "" } // validateReleaseCharts validates if the charts defined in a release are valid. // Valid charts are the ones that can be found in the defined repos. // This function uses Helm search to verify if the chart can be found or not. -func validateReleaseCharts(apps map[string]release) bool { +func validateReleaseCharts(apps map[string]release) (bool, string) { for app, r := range apps { cmd := command{ @@ -102,12 +119,11 @@ func validateReleaseCharts(apps map[string]release) bool { exitCode, _ := cmd.exec(debug) if exitCode != 0 { - log.Fatal("ERROR: chart "+r.Chart+"-"+r.Version+" is specified for ", - "app ["+app+"] but is not found in the provided repos.") - return false + return false, "ERROR: chart " + r.Chart + "-" + r.Version + " is specified for " + + "app [" + app + "] but is not found in the provided repos." } } - return true + return true, "" } // addNamespaces creates a set of namespaces in your k8s cluster. @@ -131,17 +147,15 @@ func addNamespaces(namespaces map[string]string) { // createContext creates a context -connecting to a k8s cluster- in kubectl config. // It returns true if successful, false otherwise -func createContext() bool { +func createContext() (bool, string) { var password string if s.Settings["password"] == "" || s.Settings["username"] == "" || s.Settings["clusterURI"] == "" { - log.Fatal("ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ] " + - "as you did not specify enough information in the Settings section of your desired state file.") - return false + return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ] " + + "as you did not specify enough information in the Settings section of your desired state file." } else if s.Certificates == nil || s.Certificates["caCrt"] == "" || s.Certificates["caKey"] == "" { - log.Fatal("ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ] " + - "as you did not provide Certifications to use in your desired state file.") - return false + return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ] " + + "as you did not provide Certifications to use in your desired state file." } else { cmd := command{ Cmd: "bash", @@ -154,15 +168,13 @@ func createContext() bool { password = strings.TrimSpace(password) if exitCode != 0 || password == "" { - log.Fatal("ERROR: failed to read password from env variable.") - return false + return false, "ERROR: failed to read password from env variable." } } // download certs using AWS cli if !toolExists("aws help") { - log.Fatal("ERROR: aws is not installed/configured correctly. It is needed for downloading certs. Aborting!") - return false + return false, "ERROR: aws is not installed/configured correctly. It is needed for downloading certs. Aborting!" } cmd := command{ @@ -171,11 +183,10 @@ func createContext() bool { Description: "downloading ca.crt from S3.", } - exitCode, _ := cmd.exec(debug) + exitCode, msg := cmd.exec(debug) if exitCode != 0 { - log.Fatal("ERROR: failed to download caCrt.") - return false + return false, "ERROR: failed to download caCrt." + msg } cmd = command{ @@ -184,11 +195,10 @@ func createContext() bool { Description: "downloading ca.key from S3.", } - exitCode, _ = cmd.exec(debug) + exitCode, msg = cmd.exec(debug) if exitCode != 0 { - log.Fatal("ERROR: failed to download caKey.") - return false + return false, "ERROR: failed to download caKey." + msg } // connecting to the cluster @@ -199,11 +209,10 @@ func createContext() bool { Description: "creating kubectl context - setting credentials.", } - exitCode, _ = cmd.exec(debug) + exitCode, msg = cmd.exec(debug) if exitCode != 0 { - log.Fatal("ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ].") - return false + return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ]: " + msg } cmd = command{ @@ -213,11 +222,10 @@ func createContext() bool { Description: "creating kubectl context - setting cluster.", } - exitCode, _ = cmd.exec(debug) + exitCode, msg = cmd.exec(debug) if exitCode != 0 { - log.Fatal("ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ].") - return false + return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ]: " + msg } cmd = command{ @@ -227,12 +235,15 @@ func createContext() bool { Description: "creating kubectl context - setting context.", } - exitCode, _ = cmd.exec(debug) + exitCode, msg = cmd.exec(debug) if exitCode != 0 { - log.Fatal("ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ].") - return false + return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ]: " + msg + } + + if setKubeContext(s.Settings["kubeContext"]) { + return true, "" } - return setKubeContext(s.Settings["kubeContext"]) + return false, "ERROR: something went wrong while setting the kube context to the newly created one." } diff --git a/state.go b/state.go index 473f1736..0e8770a9 100644 --- a/state.go +++ b/state.go @@ -38,7 +38,7 @@ func (s state) validate() (bool, string) { // certificates if s.Certificates != nil { - if len(s.Settings) > 1 && len(s.Certificates) < 2 { + if len(s.Settings) > 1 && len(s.Certificates) != 2 { return false, "ERROR: certifications validation failed -- You want me to connect to your cluster for you " + "but have not given me the keys to do so. Please add [caCrt] and [caKey] under Certifications." } From e4c6003eea9473cb1d2c340589a12a5c336bc9e2 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 18 Nov 2017 13:41:48 +0100 Subject: [PATCH 0028/1127] updated logs for running commands with debug option. --- command.go | 2 +- plan.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/command.go b/command.go index 001c49ab..d5052178 100644 --- a/command.go +++ b/command.go @@ -32,7 +32,7 @@ func (c command) printFullCommand() { func (c command) exec(debug bool) (int, string) { if debug { - log.Println("INFO: executing command: " + c.Description) + log.Println("INFO: executing command: " + c.Args[1]) } cmd := exec.Command(c.Cmd, c.Args...) var out bytes.Buffer diff --git a/plan.go b/plan.go index b96cc1fb..b928ad49 100644 --- a/plan.go +++ b/plan.go @@ -42,8 +42,8 @@ func (p plan) execPlan() { p.printPlan() for _, cmd := range p.Commands { log.Println("INFO: attempting: -- ", cmd.Description) - if exitCode, _ := cmd.exec(debug); exitCode != 0 { - log.Fatal("Command returned the following non zero exit code while executing the plan: " + string(exitCode)) + if exitCode, msg := cmd.exec(debug); exitCode != 0 { + log.Fatal("Command returned with exit code: " + string(exitCode) + ". And error message: " + msg) } } } From e0b23de2752cf113da02fe0e8e883d85b6c09411 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 18 Nov 2017 14:09:28 +0100 Subject: [PATCH 0029/1127] updated the helm init step to upgrade Tiller to match the client version --- main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index 35729200..e48cc132 100644 --- a/main.go +++ b/main.go @@ -70,8 +70,8 @@ func setKubeContext(context string) bool { func initHelm() (bool, string) { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm init"}, - Description: "initializing helm on the current context.", + Args: []string{"-c", "helm init --upgrade"}, + Description: "initializing helm on the current context and upgrade Tiller.", } exitCode, msg := cmd.exec(debug) From 0c01a883aaa38c3ee8d6fea23afea2e7b1d975ef Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 18 Nov 2017 14:39:49 +0100 Subject: [PATCH 0030/1127] fixed a bug in getReleaseChart() --- helm_helpers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helm_helpers.go b/helm_helpers.go index c25e2e6f..e29f1af6 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -70,7 +70,7 @@ func getReleaseChart(releaseName string) string { if exitCode == 0 { line := strings.Split(result, "\n")[1] - return strings.Fields(line)[4] // 4 is the position of chart details in helm ls output + return strings.Fields(line)[8] // 8 is the position of chart details in helm ls output } log.Fatal("ERROR: seems release [ " + releaseName + " ] does not exist.") os.Exit(1) From 95bade0c89f893a90021a1ee6cec51b03b0215ff Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 18 Nov 2017 23:22:27 +0100 Subject: [PATCH 0031/1127] enabled purge deleting a failed release. --- decision_maker.go | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index 0fd03c6e..64fa6f7f 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -2,7 +2,6 @@ package main import ( "log" - "os" "strings" ) @@ -36,17 +35,14 @@ func decide(r release, s *state) { inspectRollbackScenario(s.Namespaces[r.Env], r) - } else { - if !helmReleaseExists(s.Namespaces[r.Env], r.Name, "all") { + } else if helmReleaseExists(s.Namespaces[r.Env], r.Name, "failed") { - installRelease(s.Namespaces[r.Env], r) + deleteRelease(r.Name, true) - } else { + } else { - log.Fatal("ERROR: it seems that release [ " + r.Name + " ] exists in the current k8s context. Please double check!") - os.Exit(1) + installRelease(s.Namespaces[r.Env], r) - } } } @@ -122,26 +118,32 @@ func inspectDeleteScenario(namespace string, r release) { releaseName := r.Name //if it exists in helm list , add command to delete it, else log that it is skipped if helmReleaseExists(namespace, releaseName, "deployed") { - purge := "" - purgeDesc := "" - if r.Purge { - purge = "--purge" - purgeDesc = "and purged!" - } // delete it - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm delete " + purge + " " + releaseName}, - Description: "deleting release [ " + releaseName + " ]", - } - outcome.addCommand(cmd) - logDecision("DECISION: release [ " + releaseName + " ] is desired to be deleted " + purgeDesc + ". Planing this for you!") + deleteRelease(releaseName, r.Purge) } else { logDecision("DECISION: release [ " + releaseName + " ] is set to be disabled but is not yet deployed. Skipping.") } } +// deleteRelease deletes a release from a k8s cluster +func deleteRelease(releaseName string, purge bool) { + p := "" + purgeDesc := "" + if purge { + p = "--purge" + purgeDesc = "and purged!" + } + + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm delete " + p + " " + releaseName}, + Description: "deleting release [ " + releaseName + " ]", + } + outcome.addCommand(cmd) + logDecision("DECISION: release [ " + releaseName + " ] is desired to be deleted " + purgeDesc + ". Planing this for you!") +} + // inspectUpgradeScenario evaluates if a release should be upgraded. // - If the relase is already in the same namespace specified in the input, // it will be upgraded using the values file specified in the release info. From 02851ed3313e90c8b85ac2f90b5d0d90c0253900 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sun, 19 Nov 2017 15:50:42 +0100 Subject: [PATCH 0032/1127] fixed the rollback revision number param. --- decision_maker.go | 2 +- helm_helpers.go | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index 64fa6f7f..689e4a55 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -90,7 +90,7 @@ func inspectRollbackScenario(namespace string, r release) { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm rollback " + releaseName}, + Args: []string{"-c", "helm rollback " + releaseName + " " + getReleaseRevision(releaseName, "deleted")}, Description: "rolling back release [ " + releaseName + " ]", } outcome.addCommand(cmd) diff --git a/helm_helpers.go b/helm_helpers.go index e29f1af6..9fc5b26c 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -73,7 +73,24 @@ func getReleaseChart(releaseName string) string { return strings.Fields(line)[8] // 8 is the position of chart details in helm ls output } log.Fatal("ERROR: seems release [ " + releaseName + " ] does not exist.") - os.Exit(1) + + return "" +} + +// getReleaseRevision returns the revision number for a release (if it exists) +func getReleaseRevision(releaseName string, state string) string { + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm list " + releaseName + " --" + state}, + Description: "inspecting the release revision for : " + releaseName, + } + exitCode, result := cmd.exec(debug) + + if exitCode == 0 { + line := strings.Split(result, "\n")[1] + return strings.Fields(line)[1] // 1 is the position of revision number in helm ls output + } + log.Fatal("ERROR: seems release [ " + releaseName + " ] does not exist.") return "" } From ed392faa0b8ea198d698328c34cb5963d85b4a10 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sun, 19 Nov 2017 16:00:10 +0100 Subject: [PATCH 0033/1127] Create LICENSE --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..03e7e5dc --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Praqma + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From e5ce3f1bcef5349c7b7d347dc2e8e11011623be4 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sun, 19 Nov 2017 19:18:14 +0100 Subject: [PATCH 0034/1127] fixed install/upgrade bugs --- decision_maker.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index 689e4a55..a1b6ad5d 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -68,7 +68,7 @@ func installRelease(namespace string, r release) { releaseName := r.Name cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm install " + r.Chart + " -n " + releaseName + " --namespace " + namespace + " -f " + r.ValuesFile}, + Args: []string{"-c", "helm install " + r.Chart + " -n " + releaseName + " --namespace " + namespace + getValuesFile(r) + " --version " + r.Version}, Description: "installing release [ " + releaseName + " ] in namespace [[ " + namespace + " ]]", } outcome.addCommand(cmd) @@ -183,7 +183,7 @@ func inspectUpgradeScenario(namespace string, r release) { func upgradeRelease(r release) { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm upgrade " + r.Name + " " + r.Chart + " -f " + r.ValuesFile}, + Args: []string{"-c", "helm upgrade " + r.Name + " " + r.Chart + getValuesFile(r) + " --version " + r.Version + " --force"}, Description: "upgrading release [ " + r.Name + " ]", } @@ -208,7 +208,7 @@ func reInstallRelease(namespace string, r release) { installCmd := command{ Cmd: "bash", - Args: []string{"-c", "helm install " + r.Chart + " -n " + releaseName + " --namespace " + namespace + " -f " + r.ValuesFile}, + Args: []string{"-c", "helm install " + r.Chart + " --version " + r.Version + " -n " + releaseName + " --namespace " + namespace + getValuesFile(r)}, Description: "installing release [ " + releaseName + " ] in namespace [[ " + namespace + " ]]", } outcome.addCommand(installCmd) @@ -236,3 +236,11 @@ func extractChartName(releaseChart string) string { return strings.TrimSpace(strings.Split(releaseChart, "/")[1]) } + +// getValuesFile return partial install/upgrade release command to substitute the -f flag in Helm. +func getValuesFile(r release) string { + if r.ValuesFile != "" { + return " -f " + r.ValuesFile + } + return "" +} From 719453dde048bd8119bb24e824413fe0a3308c88 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sun, 19 Nov 2017 19:19:52 +0100 Subject: [PATCH 0035/1127] close #5 updated docs. --- README.md | 106 ++++++------------ dockerfile/README.md | 14 ++- docs/desired_state_specification.md | 29 +++-- docs/how_to/manipulate_apps.md | 72 ++++++++++++ docs/how_to/run_helmsman_in_ci.md | 31 +++++ .../run_helmsman_with_hosted_cluster.md | 63 +++++++++++ docs/how_to/run_helmsman_with_minikube.md | 47 ++++++++ docs/why_helmsman.md | 14 ++- example.toml | 37 +++--- 9 files changed, 304 insertions(+), 109 deletions(-) create mode 100644 docs/how_to/manipulate_apps.md create mode 100644 docs/how_to/run_helmsman_in_ci.md create mode 100644 docs/how_to/run_helmsman_with_hosted_cluster.md create mode 100644 docs/how_to/run_helmsman_with_minikube.md diff --git a/README.md b/README.md index b81840a5..949add83 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,31 @@ +--- +version: v0.1.2 +--- + # What is Helmsman? -Helmsman is a Helm Charts as Code tool which adds another layer of abstraction on top of [Helm](https://helm.sh) (the [Kubernetes](https://kubernetes.io/) package manager). It allows you to automate the deployment/management of your Helm charts (k8s packaged applications). +Helmsman is a Helm Charts (k8s applications) as Code tool which adds a layer of abstraction on top of [Helm](https://helm.sh) (the [Kubernetes](https://kubernetes.io/) package manager). It allows you to automate the deployment/management of your Helm charts. # Why Helmsman? -Helmsman was created to ease continous deployment of Helm charts. When you want to configure a continous deployment pipeline to manage multiple charts deployed on your k8s cluster(s), a CI script will quickly become complex and difficult to maintain. That's where Helmsman comes to rescue. Read more about [how Helmsman can save you time and effort in the docs](https://github.com/Praqma/helmsman/blob/master/docs/why_helmsman.md). +Helmsman was created to ease continous deployment of Helm charts. When you want to configure a continous deployment pipeline to manage multiple charts deployed on your k8s cluster(s), a CI script will quickly become complex and difficult to maintain. That's where Helmsman comes to rescue. Read more about [how Helmsman can save you time and effort](https://github.com/Praqma/helmsman/blob/master/docs/why_helmsman.md). + + +# Features + +- **Idempotency**: As long your desired state file does not change, you can execute Helmsman several times and get the same result. +- **Continue from failures**: In the case of partial executions due to a specific chart deployment failure, fix your helm chart and execute Helmsman again without needing to rollback the partial successes first. +- **Built for CD**: Helmsman can be used as a docker image or a binary. +- **Applications as code**: describe your desired applications and manage them from a single version-controlled declarative file. +- **Easy to use**: knowledge of Helm CLI and Kubectl is NOT manadatory to use Helmsman. +- **Plan, View, apply**: you can run Helmsman to generate and view a plan with/without executing it. + +# Usage +Helmsman can be used in three different settings: + +- As a binary on local machine [with either a Minikube or a hosted cluster ]. See the docs [here](https://github.com/Praqma/helmsman/blob/master/docs/how_to/run_helmsman_with_minikube.md) for instructions on using Minikube and [here](https://github.com/Praqma/helmsman/blob/master/docs/how_to/run_helmsman_with_hosted_cluster.md) for using hosted cluster. +- As a docker image [in a CI system or local machine]. See the docs [here](https://github.com/Praqma/helmsman/blob/master/docs/how_to/run_helmsman_in_ci.md) for instructions. # How does it work? @@ -15,76 +35,16 @@ The desired state file follows the [desired state specification](https://github. Helmsman sees what you desire, validates that your desire makes sense (e.g. that the charts you desire are available in the repos you defined), compares it with the current state of Helm and figures out what to do to make your desire come true. Below is the result of executing the [example.toml](https://github.com/Praqma/helmsman/blob/master/example.toml) -``` -$ helmsman -f example.toml -apply -2017/11/04 17:23:34 Parsed [[ example.toml ]] successfully and found [2] apps -2017/11/04 17:23:49 WARN: I could not create namespace [staging ]. It already exists. I am skipping this. -2017/11/04 17:23:49 WARN: I could not create namespace [default ]. It already exists. I am skipping this. ---------------- -Ok, I have generated a plan for you at: 2017-11-04 17:23:49.649943386 +0100 CET m=+14.976742294 -DECISION: release [ jenkins ] is currently deleted and is desired to be rolledback to namespace [[ staging ]] . No problem! -DECISION: release [ jenkins ] is required to be tested when installed/upgraded/rolledback. Got it! -DECISION: release [ vault ] is not present in the current k8s context. Will install it in namespace [[ staging ]] -DECISION: release [ vault ] is required to be tested when installed/upgraded/rolledback. Got it! -``` - -``` -$ helm list -NAME REVISION UPDATED STATUS CHART NAMESPACE -jenkins 1 Thu Nov 4 17:24:05 2017 DEPLOYED jenkins-0.9.0 staging -vault 1 Thu Nov 4 17:24:55 2017 DEPLOYED vault-0.1.0 staging -``` - -You can then change your desire, for example to disable the Jenkins release that was created above by setting `enabled = false` : - -``` -... -[apps.jenkins] - name = "jenkins" # should be unique across all apps - description = "jenkins" - env = "staging" # maps to the namespace as defined in environmetns above - enabled = false # change to false if you want to delete this app release [empty = flase] - chart = "stable/jenkins" # changing the chart name means delete and recreate this chart - version = "0.9.0" - valuesFile = "" # leaving it empty uses the default chart values - purge = false # will only be considered when there is a delete operation - test = true # run the tests whenever this release is installed/upgraded/rolledback -... - -``` - -Then run Helmsman again and it will detect that you want to delete Jenkins: - -``` -$ helmsman -f example.toml -apply -2017/11/04 17:25:29 Parsed [[ example.toml ]] successfully and found [2] apps -2017/11/04 17:25:44 WARN: I could not create namespace [staging ]. It already exists. I am skipping this. -2017/11/04 17:25:44 WARN: I could not create namespace [default ]. It already exists. I am skipping this. ---------------- -Ok, I have generated a plan for you at: 2017-11-04 17:23:44.649947467 +0100 CET m=+14.976746752 -DECISION: release [ jenkins ] is desired to be deleted and purged!. Planing this for you! -``` - -``` -$ helm list -NAME REVISION UPDATED STATUS CHART NAMESPACE -vault 1 Thu Nov 4 17:24:55 2017 DEPLOYED vault-0.1.0 staging -``` - -Similarly, if you change `enabled` back to `true`, it will figure out that you would like to roll it back. You can also change the chart or chart version and specify a values.yaml file to override the default chart values. - -# Usage - -Helmsman can be used in two ways: - -1. In a continuous deployment pipeline. Helmsman can be used in a docker container run by your CI system to maintain your desired state (which you can store in a version control repository). The docker image is available on [dockerhub](https://hub.docker.com/r/praqma/helmsman/). - -``` -docker run -it --rm -v /local/path/to/your/desired_state_file:/tmp praqma/helmsman -f tmp/example.toml -``` -> The latest docker image will contain the latest build of Helmsman. - -2. As a binary application. Helmsman dependes on [Helm](https://helm.sh) and [Kubectl](https://kubernetes.io/docs/user-guide/kubectl/) being installed. See below for installation. +To plan without executing: +``` $ helmsman -f example.toml ``` + +To plan and execute the plan: +``` $ helmsman -apply -f example.toml ``` + +To debug the planning: +``` $ helmsman -debug -apply -f example.toml ``` + +Check the documentation for [how to manage an app from the desired state file](https://github.com/Praqma/helmsman/blob/master/docs/how_to/manipulate_apps.md). # Installation @@ -92,7 +52,7 @@ Install Helmsman for your OS from the [releases page](https://github.com/Praqma/ # Documentaion -Documentation can be found under the [docs](https://github.com/Praqma/helmsman/blob/master/docs/) directory. +Documentation and How-Tos can be found [here](https://github.com/Praqma/helmsman/blob/master/docs/). # Contributing Contribution and feeback/feature requests are welcome. Please check the [Contribution Guide](https://github.com/Praqma/helmsman/blob/master/CONTRIBUTING.md). \ No newline at end of file diff --git a/dockerfile/README.md b/dockerfile/README.md index b801a956..ae4fe3ca 100644 --- a/dockerfile/README.md +++ b/dockerfile/README.md @@ -1,7 +1,17 @@ +--- +version: v0.1.2 +--- + # Usage ``` -docker run -it --rm -v /local/path/to/your/desired_state_file:/tmp praqma/helmsman -f tmp/example.toml +docker run -v $(pwd):/tmp --rm -it \ +-e KUBECTL_PASSWORD= \ +-e AWS_ACCESS_KEY_ID= \ +-e AWS_DEFAULT_REGION= \ +-e AWS_SECRET_ACCESS_KEY= \ +praqma/helmsman:v0.1.2 \ +helmsman -debug -apply -f ``` -Check the different versions on [Dockerhub](https://hub.docker.com/r/praqma/helmsman/) \ No newline at end of file +Check the different image tags on [Dockerhub](https://hub.docker.com/r/praqma/helmsman/) \ No newline at end of file diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index c8bfbbc7..7432b5a5 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -1,9 +1,13 @@ +--- +version: v0.1.2 +--- + # Helmsman desired state specification This document describes the specification for how to write your Helm charts desired state file. The desired state file consists of: - [Metadata](#metadata) [Optional] -- metadata for any human reader of the desired state file. -- [Certifications](#certifications) [Optional] -- only needed when you want Helmsman to connect kubectl to your cluster for you. +- [Certificates](#certificates) [Optional] -- only needed when you want Helmsman to connect kubectl to your cluster for you. - [Settings](#settings) -- data about your k8s cluster. - [Namespaces](#namespaces) -- defines the namespaces where you want your Helm charts to be deployed. - [Helm Repos](#helm-repos) -- defines the repos where you want to get Helm charts from. @@ -26,22 +30,22 @@ scope = "cluster foo" maintainer = "k8s-admin" ``` -## Certifications +## Certificates Optional : Yes, if you don't want Helmsman to connect kubectl to your cluster for you. Synopsis: defines where to find the certifactions needed for connecting kubectl to a k8s cluster. Options: -- caCrt : a valid path to a CRT file. -- caKey : a valid path to a key file. +- caCrt : a valid s3 bucket to a CRT file. +- caKey : a valid s3 bucket to a key file. Example: ``` -[certifications] -caCrt = "ca.crt" -caKey = "ca.key" +[certificates] +caCrt = "s3://mybucket/ca.crt" +caKey = "s3://mybucket/ca.key" ``` ## Settings @@ -56,7 +60,7 @@ Options: The following options can be skipped if your kubectl context is already created and you don't want Helmsman to connect kubectl to your cluster for you. When using Helmsman in CI pipeline, these details are required to connect to your cluster everytime the pipeline is executed. - username : the username to be used for kubectl credentials. -- password : a path to a ".passwd" file containing the password to be used for kubectl credentials. Get the password from your k8s admin or consult k8s docs on how to get it. The .passwd file should be added to your .gitignore file in your git repo. +- password : an environment variable name (starting with `$`) where your password is stored. Get the password from your k8s admin or consult k8s docs on how to get it. - clusterURI : the URI for your cluster API. Example: @@ -65,7 +69,7 @@ Example: [settings] kubeContext = "minikube" # username = "admin" -# password = "passwd.passwd" +# password = "$PASSWORD" # clusterURI = "https://192.168.99.100:8443" ``` @@ -93,7 +97,7 @@ Optional : No. Purpose: defines the Helm repos where your charts can be found. You can add as many repos as you like. Public repos do not require authentication. Private repos require authentication. -> Currently only AWS S3 buckets can be used for private repos (using the [Helm S3 plugin](https://github.com/hypnoglow/helm-s3)). For that you need to have valid AWS access keys in your environment variables. See [here](https://github.com/hypnoglow/helm-s3#note-on-aws-authentication) for more details. +> AS of version v0.1.2, only AWS S3 buckets can be used for private repos (using the [Helm S3 plugin](https://github.com/hypnoglow/helm-s3)). For that you need to have valid AWS access keys in your environment variables. See [here](https://github.com/hypnoglow/helm-s3#note-on-aws-authentication) for more details. Options: - you can define any key/value pairs. @@ -104,6 +108,7 @@ Example: [helmRepos] stable = "https://kubernetes-charts.storage.googleapis.com" incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" +myrepo = "s3://my-private-repo/charts" ``` ## Apps @@ -118,10 +123,10 @@ Options: - name : the Helm release name. Releases must have unique names within a cluster. - description : a release metadata for human readers. - env : the namespace where the release should be deployed. The namespace should map to one of the ones defined in [namespaces](#namespaces). -- enabled : describes the required state of the release (true for enabled, false for disabled). Change to false if you want to delete this app release [empty = flase]. +- enabled : describes the required state of the release (true for enabled, false for disabled). Once a release is deployed, you can change it to false if you want to delete this app release [empty = flase]. - chart : the chart name. It should contain the repo name as well. Example: repoName/chartName. Changing the chart name means delete and reinstall this release using the new Chart. - version : the chart version. -- valuesFile : a valid path to custom Helm values.yaml file. Leaving it empty uses the default chart values. +- valuesFile : a valid path to custom Helm values.yaml file. File extension must be `yaml`. Leaving it empty uses the default chart values. - purge : defines whether to use the Helm purge flag wgen deleting the release. (true/false) - test : defines whether to run the chart tests whenever the release is installed/upgraded/rolledback. diff --git a/docs/how_to/manipulate_apps.md b/docs/how_to/manipulate_apps.md new file mode 100644 index 00000000..f17c1ee0 --- /dev/null +++ b/docs/how_to/manipulate_apps.md @@ -0,0 +1,72 @@ +--- +version: v0.1.2 +--- + + +You can run helmsman with the [example.toml](https://github.com/Praqma/helmsman/blob/master/example.toml) file. + +``` + +$ helmsman -apply -f example.toml +2017/11/19 18:17:57 Parsed [[ example.toml ]] successfully and found [ 2 ] apps. +2017/11/19 18:17:59 WARN: I could not create namespace [staging ]. It already exists. I am skipping this. +2017/11/19 18:17:59 WARN: I could not create namespace [default ]. It already exists. I am skipping this. +2017/11/19 18:18:02 INFO: Executing the following plan ... +--------------- +Ok, I have generated a plan for you at: 2017-11-19 18:17:59.347859706 +0100 CET m=+2.255430021 +DECISION: release [ jenkins ] is not present in the current k8s context. Will install it in namespace [[ staging ]] +DECISION: release [ artifactory ] is not present in the current k8s context. Will install it in namespace [[ staging ]] +2017/11/19 18:18:02 INFO: attempting: -- installing release [ jenkins ] in namespace [[ staging ]] +2017/11/19 18:18:05 INFO: attempting: -- installing release [ artifactory ] in namespace [[ staging ]] + +``` + +``` +$ helm list --namespace staging +NAME REVISION UPDATED STATUS CHART NAMESPACE +artifactory 1 Sun Nov 19 18:18:06 2017 DEPLOYED artifactory-6.2.0 staging +jenkins 1 Sun Nov 19 18:18:03 2017 DEPLOYED jenkins-0.9.1 staging +``` + +You can then change your desire, for example to disable the Jenkins release that was created above by setting `enabled = false` : + +Then run Helmsman again and it will detect that you want to delete Jenkins: + +``` +$ helmsman -apply -f example.toml +2017/11/19 18:28:27 Parsed [[ example.toml ]] successfully and found [ 2 ] apps. +2017/11/19 18:28:29 WARN: I could not create namespace [staging ]. It already exists. I am skipping this. +2017/11/19 18:28:29 WARN: I could not create namespace [default ]. It already exists. I am skipping this. +2017/11/19 18:29:01 INFO: Executing the following plan ... +--------------- +Ok, I have generated a plan for you at: 2017-11-19 18:28:29.437061909 +0100 CET m=+1.987623555 +DECISION: release [ jenkins ] is desired to be deleted . Planing this for you! +DECISION: release [ artifactory ] is desired to be upgraded. Planing this for you! +2017/11/19 18:29:01 INFO: attempting: -- deleting release [ jenkins ] +2017/11/19 18:29:11 INFO: attempting: -- upgrading release [ artifactory ] +``` + +``` +$ helm list --namespace staging +NAME REVISION UPDATED STATUS CHART NAMESPACE +artifactory 2 Sun Nov 19 18:29:11 2017 DEPLOYED artifactory-6.2.0 staging +``` + +Similarly, if you change `enabled` back to `true`, it will figure out that you would like to roll it back. + +``` +$ helmsman -apply -f example.toml +2017/11/19 18:30:41 Parsed [[ example.toml ]] successfully and found [ 2 ] apps. +2017/11/19 18:30:42 WARN: I could not create namespace [staging ]. It already exists. I am skipping this. +2017/11/19 18:30:43 WARN: I could not create namespace [default ]. It already exists. I am skipping this. +2017/11/19 18:30:49 INFO: Executing the following plan ... +--------------- +Ok, I have generated a plan for you at: 2017-11-19 18:30:43.108693039 +0100 CET m=+1.978435517 +DECISION: release [ jenkins ] is currently deleted and is desired to be rolledback to namespace [[ staging ]] . No problem! +DECISION: release [ artifactory ] is desired to be upgraded. Planing this for you! +2017/11/19 18:30:49 INFO: attempting: -- rolling back release [ jenkins ] +2017/11/19 18:30:50 INFO: attempting: -- upgrading release [ artifactory ] +``` + +Similarly, You can also change the chart or chart version and specify a values.yaml file to override the default chart values. + diff --git a/docs/how_to/run_helmsman_in_ci.md b/docs/how_to/run_helmsman_in_ci.md new file mode 100644 index 00000000..64b65655 --- /dev/null +++ b/docs/how_to/run_helmsman_in_ci.md @@ -0,0 +1,31 @@ +--- +version: v0.1.2 +--- + +# Run Helmsman in CI + +You can run Helmsman as a job in your CI system using the [helmsman docker image](https://hub.docker.com/r/praqma/helmsman/). +The following example is a `config.yml` file for CircleCI but can be replicated for other CI systems. + +``` +version: 2 +jobs: + + deploy-apps: + docker: + - image: praqma/helmsman:v0.1.2 + steps: + - checkout + - run: + name: Deploy Helm Packages using helmsman + command: helmsman -debug -apply -f helmsman-deployments.toml + + +workflows: + version: 2 + build: + jobs: + - deploy-apps +``` + +The `helmsman-deployments.toml` is your desired state file which will version controlled in your git repo. \ No newline at end of file diff --git a/docs/how_to/run_helmsman_with_hosted_cluster.md b/docs/how_to/run_helmsman_with_hosted_cluster.md new file mode 100644 index 00000000..6fba3216 --- /dev/null +++ b/docs/how_to/run_helmsman_with_hosted_cluster.md @@ -0,0 +1,63 @@ +--- +version: v0.1.2 +--- + +You can manage Helm charts deployment on a hosted K8S cluster in the cloud or on-prem. You need to include the required information to connect to the cluster in your state file. Below is an example: + +**IMPORTANT**: Only Certificates and private helm repos in S3 buckets are currently supported. Helmsman needs valid AWS access keys to be able to retrieve private charts or certificates from your s3 buckets. It expects the keys to be in the following environemnt variables: + +- AWS_ACCESS_KEY_ID +- AWS_SECRET_ACCESS_KEY +- AWS_DEFAULT_REGION + +Also, the K8S user password is expected in an environment variable which you can give any name you want and define it in your desired state file. + +``` +[metadata] +org = "orgX" +maintainer = "k8s-admin" + +# Certificates are used to connect to the cluster. Currently, they can only be retrieved from s3 buckets. +[certificates] +caCrt = "s3://your-bucket/ca.crt" +caKey = "s3://your-bucket/ca.key" + +[settings] +kubeContext = "mycontext" +username = "<>" +password = "$PASSWORD" # the name of an environment variable containing the k8s password +clusterURI = "<>" # cluster API + +[namespaces] +staging = "staging" + +[helmRepos] +stable = "https://kubernetes-charts.storage.googleapis.com" +incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" +myrepo = "s3://my-private-repo/charts" + +[apps] + + [apps.jenkins] + name = "jenkins" + description = "jenkins" + env = "staging" + enabled = true + chart = "stable/jenkins" + version = "0.9.1" + valuesFile = "" + purge = false + test = false + + + [apps.artifactory] + name = "artifactory" + description = "artifactory" + env = "staging" + enabled = true + chart = "stable/artifactory" + version = "6.2.0" + valuesFile = "" + purge = false + test = false +``` \ No newline at end of file diff --git a/docs/how_to/run_helmsman_with_minikube.md b/docs/how_to/run_helmsman_with_minikube.md new file mode 100644 index 00000000..ef9e969a --- /dev/null +++ b/docs/how_to/run_helmsman_with_minikube.md @@ -0,0 +1,47 @@ +--- +version: v0.1.2 +--- + +You can run Helmsman local as a binary application with Minikube, you just need to skip all the cluster connection settings in your desired state file. Below is the example.toml desired state file adapted to work with Minikube. + + +``` +[metadata] +org = "orgX" +maintainer = "k8s-admin" + +[settings] +kubeContext = "minikube" + +[namespaces] +staging = "staging" + +[helmRepos] +stable = "https://kubernetes-charts.storage.googleapis.com" +incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" + +[apps] + + [apps.jenkins] + name = "jenkins" + description = "jenkins" + env = "staging" + enabled = true + chart = "stable/jenkins" + version = "0.9.1" + valuesFile = "" + purge = false + test = false + + + [apps.artifactory] + name = "artifactory" + description = "artifactory" + env = "staging" + enabled = true + chart = "stable/artifactory" + version = "6.2.0" + valuesFile = "" + purge = false + test = false +``` \ No newline at end of file diff --git a/docs/why_helmsman.md b/docs/why_helmsman.md index 5a88e3af..0f21d4df 100644 --- a/docs/why_helmsman.md +++ b/docs/why_helmsman.md @@ -1,11 +1,15 @@ +--- +version: v0.1.2 +--- + # Why Helmsman? This document describes the reasoning and need behind the inception of Helmsman. ## Before Helm -Helmsman was created with continous deployment automation in mind. -When we started using k8s, we deployed applications on our cluster directly from k8s manifest files. Initially, we had a custom shell script added to our CI to deploy the k8s resources on the cluster. That script could only create the k8s resources from the manifest files. Soon we needed to have a more flexible way to dynamically create/delete those resources. We structured our git repo and used custom file names (adding enabled or disabled into file names) and updated the shell script accordingly. It did not take long before we realized that this does not scale and is difficult to maintain. +Helmsman was created with continous deployment in mind. +When we started using k8s, we deployed applications on our cluster directly from k8s manifest files. Initially, we had a custom shell script added to our CI system to deploy the k8s resources on the cluster. That script could only create the k8s resources from the manifest files. Soon we needed to have a more flexible way to dynamically create/delete those resources. We structured our git repo and used custom file names (adding enabled or disabled into file names) and updated the shell script accordingly. It did not take long before we realized that this does not scale and is difficult to maintain. ![CI-pipeline-before-helm](images/CI-pipeline-before-helm.jpg) @@ -26,11 +30,11 @@ With all this in mind, we needed a flexible and dynamic solution that can let us ## The Helmsman way -In English, [Helmsman](https://www.merriam-webster.com/dictionary/helmsman) is the person at the helm (in a ship). In k8s and Helm context, Helmsman sets at the Helm and maintains your Helm charts' lifecycle in your k8s cluster(s). Helmsman gets its directions to navigate from a [declarative file](desired_state_specification.md) maintained by the user (k8s admin). +In English, [Helmsman](https://www.merriam-webster.com/dictionary/helmsman) is the person at the helm (in a ship). In k8s and Helm context, Helmsman holds the Helm and maintains your Helm charts' lifecycle in your k8s cluster(s). Helmsman gets its directions to navigate from a [declarative file](desired_state_specification.md) maintained by the user (k8s admin). -> The Helmsman user does not need to know much about Helm and possibly even about k8s. +> Although knowledge about Helm and K8S is highly beneficial, such knowledge is NOT required to use Helmsman. -As the diagram below shows, we recommend having a_ desired state file_ for each k8s cluster you are managing. Along with that file, you would need to have any custom [values yaml files](https://docs.helm.sh/chart_template_guide/#values-files) for the Helm chart's you deploy on your k8s. Then you could configure your CI pipeline to use Helmsman docker container to process your desired state file whenever a commit is made to it. +As the diagram below shows, we recommend having a _desired state file_ for each k8s cluster you are managing. Along with that file, you would need to have any custom [values yaml files](https://docs.helm.sh/chart_template_guide/#values-files) for the Helm chart's you deploy on your k8s. Then you could configure your CI pipeline to use Helmsman docker image to process your desired state file whenever a commit is made to it. ![CI-pipeline-helmsman](images/CI-pipeline-helmsman.jpg) diff --git a/example.toml b/example.toml index 56871015..b94fb0ff 100644 --- a/example.toml +++ b/example.toml @@ -1,3 +1,4 @@ +# version: v0.1.2 # metadata -- add as many key/value pairs as you want [metadata] org = "orgX" @@ -6,13 +7,13 @@ maintainer = "k8s-admin" # paths to the certificate for connecting to the cluster # You can skip this if you use Helmsman on a machine with kubectl already connected to your k8s cluster. [certificates] -# caCrt = "ca.crt" # s3 bucket path -# caKey = "ca.key" # Or, a path to the file location +# caCrt = "s3://mybucket/ca.crt" # s3 bucket path +# caKey = "s3://mybucket/ca.key" [settings] kubeContext = "minikube" # will try connect to this context first, if it does not exist, it will be created using the details below # username = "admin" -# password = "passwd.passwd" # read it from a .passwd file which you should make it ignored by git. +# password = "$PASSWORD" # the name of an environment variable containing the k8s password # clusterURI = "https://192.168.99.100:8443" # cluster API @@ -25,9 +26,11 @@ production = "default" # define any private/public helm charts repos you would like to get charts from # syntax: repo_name = "repo_url" +# only private repos hosted in s3 buckets are now supported [helmRepos] stable = "https://kubernetes-charts.storage.googleapis.com" incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" +#myrepo = "s3://my-private-repo/charts" # define the desired state of your applications helm charts # each contains the following: @@ -40,19 +43,19 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" env = "staging" # maps to the namespace as defined in environmetns above enabled = true # change to false if you want to delete this app release [empty = flase] chart = "stable/jenkins" # changing the chart name means delete and recreate this chart - version = "0.9.0" + version = "0.9.1" # chart version valuesFile = "" # leaving it empty uses the default chart values purge = false # will only be considered when there is a delete operation - test = true # run the tests whenever this release is installed/upgraded/rolledback - - - # [apps.vault] - # name = "vault" # should be unique across all apps - # description = "vault" - # env = "staging" # maps to the namespace as defined in environmetns above - # enabled = true # change to false if you want to delete this app release [empty = flase] - # chart = "incubator/vault" # don't change the chart name, create a new release instead - # version = "0.1.0" - # valuesFile = "" # leaving it empty uses the default chart values - # purge = false # will only be considered when there is a delete operation - # test = true # run the tests whenever this release is installed/upgraded/rolledback + test = false # run the tests whenever this release is installed/upgraded/rolledback + + + [apps.vault] + name = "artifactory" # should be unique across all apps + description = "artifactory" + env = "staging" # maps to the namespace as defined in environmetns above + enabled = true # change to false if you want to delete this app release [empty = flase] + chart = "stable/artifactory" # changing the chart name means delete and recreate this chart + version = "6.2.0" # chart version + valuesFile = "" # leaving it empty uses the default chart values + purge = false # will only be considered when there is a delete operation + test = false # run the tests whenever this release is installed/upgraded/rolledback From f5de30e609a73e753fe4c47672c8a71468619b9c Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Wed, 22 Nov 2017 11:07:45 +0100 Subject: [PATCH 0036/1127] removed a broken link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 949add83..254a0f8b 100644 --- a/README.md +++ b/README.md @@ -55,4 +55,4 @@ Install Helmsman for your OS from the [releases page](https://github.com/Praqma/ Documentation and How-Tos can be found [here](https://github.com/Praqma/helmsman/blob/master/docs/). # Contributing -Contribution and feeback/feature requests are welcome. Please check the [Contribution Guide](https://github.com/Praqma/helmsman/blob/master/CONTRIBUTING.md). \ No newline at end of file +Contribution and feeback/feature requests are welcome. \ No newline at end of file From dadb5f9c907140181dc2f42e569efd3065c1a229 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 25 Nov 2017 11:56:43 +0100 Subject: [PATCH 0037/1127] #15 added support for reading user input from env vars. --- decision_maker.go | 34 ++++++++++++++++++++++++---------- release.go | 29 ++++++++++++++++++++++++++--- 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index a1b6ad5d..0d2c30e3 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -2,6 +2,7 @@ package main import ( "log" + "os" "strings" ) @@ -24,24 +25,28 @@ func decide(r release, s *state) { // check for deletion if !r.Enabled { - inspectDeleteScenario(s.Namespaces[r.Env], r) + inspectDeleteScenario(s.Namespaces[r.Namespace], r) } else { // check for install/upgrade/rollback - if helmReleaseExists(s.Namespaces[r.Env], r.Name, "deployed") { + if helmReleaseExists(s.Namespaces[r.Namespace], r.Name, "deployed") { - inspectUpgradeScenario(s.Namespaces[r.Env], r) + inspectUpgradeScenario(s.Namespaces[r.Namespace], r) - } else if helmReleaseExists(s.Namespaces[r.Env], r.Name, "deleted") { + } else if helmReleaseExists(s.Namespaces[r.Namespace], r.Name, "deleted") { - inspectRollbackScenario(s.Namespaces[r.Env], r) + inspectRollbackScenario(s.Namespaces[r.Namespace], r) - } else if helmReleaseExists(s.Namespaces[r.Env], r.Name, "failed") { + } else if helmReleaseExists(s.Namespaces[r.Namespace], r.Name, "failed") { deleteRelease(r.Name, true) + } else if helmReleaseExists(s.Namespaces[r.Namespace], r.Name, "all") { // it is deployed but in another namespace + + reInstallRelease(s.Namespaces[r.Namespace], r) + } else { - installRelease(s.Namespaces[r.Env], r) + installRelease(s.Namespaces[r.Namespace], r) } @@ -68,7 +73,7 @@ func installRelease(namespace string, r release) { releaseName := r.Name cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm install " + r.Chart + " -n " + releaseName + " --namespace " + namespace + getValuesFile(r) + " --version " + r.Version}, + Args: []string{"-c", "helm install " + r.Chart + " -n " + releaseName + " --namespace " + namespace + getValuesFile(r) + " --version " + r.Version + getSetValues(r)}, Description: "installing release [ " + releaseName + " ] in namespace [[ " + namespace + " ]]", } outcome.addCommand(cmd) @@ -183,7 +188,7 @@ func inspectUpgradeScenario(namespace string, r release) { func upgradeRelease(r release) { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm upgrade " + r.Name + " " + r.Chart + getValuesFile(r) + " --version " + r.Version + " --force"}, + Args: []string{"-c", "helm upgrade " + r.Name + " " + r.Chart + getValuesFile(r) + " --version " + r.Version + " --force " + getSetValues(r)}, Description: "upgrading release [ " + r.Name + " ]", } @@ -208,7 +213,7 @@ func reInstallRelease(namespace string, r release) { installCmd := command{ Cmd: "bash", - Args: []string{"-c", "helm install " + r.Chart + " --version " + r.Version + " -n " + releaseName + " --namespace " + namespace + getValuesFile(r)}, + Args: []string{"-c", "helm install " + r.Chart + " --version " + r.Version + " -n " + releaseName + " --namespace " + namespace + getValuesFile(r) + getSetValues(r)}, Description: "installing release [ " + releaseName + " ] in namespace [[ " + namespace + " ]]", } outcome.addCommand(installCmd) @@ -244,3 +249,12 @@ func getValuesFile(r release) string { } return "" } + +// getSetValues returns --set params to be used with helm install/upgrade commands +func getSetValues(r release) string { + result := "" + for k, v := range r.Set { + result = result + " --set " + k + "=" + os.Getenv(strings.SplitAfter(v, "$")[1]) + } + return result +} diff --git a/release.go b/release.go index 06dfdeb3..069a85d1 100644 --- a/release.go +++ b/release.go @@ -10,13 +10,14 @@ import ( type release struct { Name string Description string - Env string + Namespace string Enabled bool Chart string Version string ValuesFile string Purge bool Test bool + Set map[string]string } // validateRelease validates if a release inside a desired state meets the specifications or not. @@ -25,7 +26,7 @@ func validateRelease(r release, names map[string]bool) (bool, string) { _, err := os.Stat(r.ValuesFile) if r.Name == "" || names[r.Name] { return false, "release name can't be empty and must be unique." - } else if r.Env == "" { + } else if r.Namespace == "" { return false, "release targeted env (namespace) can't be empty." } else if r.Chart == "" || !strings.ContainsAny(r.Chart, "/") { return false, "chart can't be empty and must be of the format: repo/chart." @@ -33,23 +34,45 @@ func validateRelease(r release, names map[string]bool) (bool, string) { return false, "version can't be empty." } else if r.ValuesFile != "" && (!isOfType(r.ValuesFile, ".yaml") || err != nil) { return false, "valuesFile must be a valid file path for a yaml file, Or can be left empty." + } else if len(r.Set) > 0 { + for k, v := range r.Set { + if !strings.HasPrefix(v, "$") { + return false, "the value for variable [ " + k + " ] must be an environment variable name and start with '$'." + } else if !envVarExists(v) { + return false, "env variable [ " + v + " ] is not found in the environment." + } + } } names[r.Name] = true return true, "" } +// envVarExists checks if an environment variable is set or not. +// it accepts env var with/without '$' at the beginning +func envVarExists(v string) bool { + + if strings.HasPrefix(v, "$") { + v = strings.SplitAfter(v, "$")[1] + } + + _, ok := os.LookupEnv(v) + return ok +} + // print prints the details of the release func (r release) print() { fmt.Println("") fmt.Println("\tname : ", r.Name) fmt.Println("\tdescription : ", r.Description) - fmt.Println("\tenv : ", r.Env) + fmt.Println("\tnamespace : ", r.Namespace) fmt.Println("\tenabled : ", r.Enabled) fmt.Println("\tchart : ", r.Chart) fmt.Println("\tversion : ", r.Version) fmt.Println("\tvaluesFile : ", r.ValuesFile) fmt.Println("\tpurge : ", r.Purge) fmt.Println("\ttest : ", r.Test) + fmt.Println("\tvalues to override from env:") + printMap(r.Set) fmt.Println("------------------- ") } From ef1497e2fee9138ada1aa9d9f332de11d0f3ab28 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 25 Nov 2017 11:57:20 +0100 Subject: [PATCH 0038/1127] added a step to cleanup repo before releasing. --- .circleci/build.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/build.sh b/.circleci/build.sh index 26d4acaa..02a37dcb 100755 --- a/.circleci/build.sh +++ b/.circleci/build.sh @@ -9,6 +9,7 @@ if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then echo "cleaning after tests ..." rm hello.world hello.world1 helmsman + git clean -fd echo "releasing ..." goreleaser | tee /dev/tty | grep -o "error" if [ $? -eq 0 ]; then From bde9047045f140b08ef1fe8e1ab9b034a3316cf3 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 25 Nov 2017 12:34:12 +0100 Subject: [PATCH 0039/1127] improved error reporting. --- helm_helpers.go | 8 +++++--- main.go | 50 ++++++++++++++++--------------------------------- 2 files changed, 21 insertions(+), 37 deletions(-) diff --git a/helm_helpers.go b/helm_helpers.go index 9fc5b26c..b26eb57d 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -2,7 +2,6 @@ package main import ( "log" - "os" "strings" ) @@ -35,10 +34,11 @@ func helmReleaseExists(namespace string, releaseName string, scope string) bool } if exitCode, result := cmd.exec(debug); exitCode == 0 { - // match, _ := regexp.MatchString(releaseName, result) return strings.Contains(result, releaseName+"\n") } + log.Fatal("ERROR: something went wrong while checking helm release.") + return false } @@ -53,7 +53,6 @@ func getReleaseNamespace(releaseName string) string { } } else { log.Fatal("ERROR: seems release [ " + releaseName + " ] does not exist.") - os.Exit(1) } return "" } @@ -119,5 +118,8 @@ func getReleaseStatus(releaseName string) string { if exitCode, result := cmd.exec(debug); exitCode == 0 { return result } + + log.Fatal("ERROR: something went wrong while checking release status.") + return "" } diff --git a/main.go b/main.go index e48cc132..649eff2d 100644 --- a/main.go +++ b/main.go @@ -74,10 +74,8 @@ func initHelm() (bool, string) { Description: "initializing helm on the current context and upgrade Tiller.", } - exitCode, msg := cmd.exec(debug) - - if exitCode != 0 { - return false, "ERROR: there has been a problem while initializing helm: " + msg + if exitCode, _ := cmd.exec(debug); exitCode != 0 { + return false, "ERROR: there was a problem while initializing helm. " } return true, "" } @@ -93,10 +91,8 @@ func addHelmRepos(repos map[string]string) (bool, string) { Description: "adding repo " + repoName, } - exitCode, _ := cmd.exec(debug) - - if exitCode != 0 { - return false, "ERROR: there has been a problem while adding repo [" + repoName + "]." + if exitCode, _ := cmd.exec(debug); exitCode != 0 { + return false, "ERROR: there was a problem while adding repo [" + repoName + "]." } } @@ -116,9 +112,7 @@ func validateReleaseCharts(apps map[string]release) (bool, string) { Description: "validating chart " + r.Chart + "-" + r.Version + " is available in the used repos.", } - exitCode, _ := cmd.exec(debug) - - if exitCode != 0 { + if exitCode, _ := cmd.exec(debug); exitCode != 0 { return false, "ERROR: chart " + r.Chart + "-" + r.Version + " is specified for " + "app [" + app + "] but is not found in the provided repos." } @@ -136,9 +130,7 @@ func addNamespaces(namespaces map[string]string) { Description: "creating namespace " + namespace, } - exitCode, _ := cmd.exec(debug) - - if exitCode != 0 { + if exitCode, _ := cmd.exec(debug); exitCode != 0 { log.Println("WARN: I could not create namespace [" + namespace + " ]. It already exists. I am skipping this.") } @@ -183,10 +175,8 @@ func createContext() (bool, string) { Description: "downloading ca.crt from S3.", } - exitCode, msg := cmd.exec(debug) - - if exitCode != 0 { - return false, "ERROR: failed to download caCrt." + msg + if exitCode, _ := cmd.exec(debug); exitCode != 0 { + return false, "ERROR: failed to download caCrt." } cmd = command{ @@ -195,10 +185,8 @@ func createContext() (bool, string) { Description: "downloading ca.key from S3.", } - exitCode, msg = cmd.exec(debug) - - if exitCode != 0 { - return false, "ERROR: failed to download caKey." + msg + if exitCode, _ := cmd.exec(debug); exitCode != 0 { + return false, "ERROR: failed to download caKey." } // connecting to the cluster @@ -209,10 +197,8 @@ func createContext() (bool, string) { Description: "creating kubectl context - setting credentials.", } - exitCode, msg = cmd.exec(debug) - - if exitCode != 0 { - return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ]: " + msg + if exitCode, _ := cmd.exec(debug); exitCode != 0 { + return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ]. " } cmd = command{ @@ -222,10 +208,8 @@ func createContext() (bool, string) { Description: "creating kubectl context - setting cluster.", } - exitCode, msg = cmd.exec(debug) - - if exitCode != 0 { - return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ]: " + msg + if exitCode, _ := cmd.exec(debug); exitCode != 0 { + return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ]." } cmd = command{ @@ -235,10 +219,8 @@ func createContext() (bool, string) { Description: "creating kubectl context - setting context.", } - exitCode, msg = cmd.exec(debug) - - if exitCode != 0 { - return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ]: " + msg + if exitCode, _ := cmd.exec(debug); exitCode != 0 { + return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ]." } if setKubeContext(s.Settings["kubeContext"]) { From dd25678e69c7e05440fbb7546a093a0063765dbd Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 25 Nov 2017 12:40:20 +0100 Subject: [PATCH 0040/1127] updated the k8s password env var validation --- main.go | 17 ++--------------- release.go | 12 ------------ utils.go | 12 ++++++++++++ 3 files changed, 14 insertions(+), 27 deletions(-) diff --git a/main.go b/main.go index 649eff2d..1425b637 100644 --- a/main.go +++ b/main.go @@ -2,7 +2,6 @@ package main import ( "log" - "strings" ) var s state @@ -148,20 +147,8 @@ func createContext() (bool, string) { } else if s.Certificates == nil || s.Certificates["caCrt"] == "" || s.Certificates["caKey"] == "" { return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ] " + "as you did not provide Certifications to use in your desired state file." - } else { - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "echo " + s.Settings["password"]}, - Description: "reading the password from env variable.", - } - - var exitCode int - exitCode, password = cmd.exec(debug) - - password = strings.TrimSpace(password) - if exitCode != 0 || password == "" { - return false, "ERROR: failed to read password from env variable." - } + } else if !envVarExists(s.Settings["password"]) { + return false, "ERROR: env variable [ " + s.Settings["password"] + " ] does not exist in the environment." } // download certs using AWS cli diff --git a/release.go b/release.go index 069a85d1..c4ef2c1c 100644 --- a/release.go +++ b/release.go @@ -48,18 +48,6 @@ func validateRelease(r release, names map[string]bool) (bool, string) { return true, "" } -// envVarExists checks if an environment variable is set or not. -// it accepts env var with/without '$' at the beginning -func envVarExists(v string) bool { - - if strings.HasPrefix(v, "$") { - v = strings.SplitAfter(v, "$")[1] - } - - _, ok := os.LookupEnv(v) - return ok -} - // print prints the details of the release func (r release) print() { fmt.Println("") diff --git a/utils.go b/utils.go index 2255a66d..d7f6de1f 100644 --- a/utils.go +++ b/utils.go @@ -85,3 +85,15 @@ func printHelp() { fmt.Println("-help prints Helmsman help.") } + +// envVarExists checks if an environment variable is set or not. +// it accepts env var with/without '$' at the beginning +func envVarExists(v string) bool { + + if strings.HasPrefix(v, "$") { + v = strings.SplitAfter(v, "$")[1] + } + + _, ok := os.LookupEnv(v) + return ok +} From abfd629b96642237ea2edd4411c028b153343b51 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 25 Nov 2017 12:43:49 +0100 Subject: [PATCH 0041/1127] updated example.toml --- example.toml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/example.toml b/example.toml index b94fb0ff..4b8e3fea 100644 --- a/example.toml +++ b/example.toml @@ -35,27 +35,34 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" # define the desired state of your applications helm charts # each contains the following: + [apps] [apps.jenkins] name = "jenkins" # should be unique across all apps description = "jenkins" - env = "staging" # maps to the namespace as defined in environmetns above + namespace = "staging" # maps to the namespace as defined in environmetns above enabled = true # change to false if you want to delete this app release [empty = flase] chart = "stable/jenkins" # changing the chart name means delete and recreate this chart version = "0.9.1" # chart version valuesFile = "" # leaving it empty uses the default chart values purge = false # will only be considered when there is a delete operation test = false # run the tests whenever this release is installed/upgraded/rolledback + # [apps.jenkins.set] # values to override values from values.yaml with values from env vars-- useful for passing secrets to charts + # db_username="$DB_USERNAME" + # db_password="$DB_PASSWORD" - [apps.vault] + [apps.artifactory] name = "artifactory" # should be unique across all apps description = "artifactory" - env = "staging" # maps to the namespace as defined in environmetns above + namespace = "staging" # maps to the namespace as defined in environmetns above enabled = true # change to false if you want to delete this app release [empty = flase] chart = "stable/artifactory" # changing the chart name means delete and recreate this chart version = "6.2.0" # chart version valuesFile = "" # leaving it empty uses the default chart values purge = false # will only be considered when there is a delete operation test = false # run the tests whenever this release is installed/upgraded/rolledback + # [apps.artifactory.set] # values to override values from values.yaml with values from env vars-- useful for passing secrets to charts + # db_username="$DB_USERNAME" + # db_password="$DB_PASSWORD" From ad47d125aeed207e58e2878c7b64e620bcadc584 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 25 Nov 2017 12:56:08 +0100 Subject: [PATCH 0042/1127] close #16 allowed certs to be used from local file system. --- example.toml | 5 +++-- state.go | 7 ++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/example.toml b/example.toml index 4b8e3fea..3ec1a5ac 100644 --- a/example.toml +++ b/example.toml @@ -6,9 +6,10 @@ maintainer = "k8s-admin" # paths to the certificate for connecting to the cluster # You can skip this if you use Helmsman on a machine with kubectl already connected to your k8s cluster. +# you have to use exact key names here : 'caCrt' for certificate and 'caKey' for the key [certificates] -# caCrt = "s3://mybucket/ca.crt" # s3 bucket path -# caKey = "s3://mybucket/ca.key" +# caCrt = "s3://mybucket/ca.crt" # s3 bucket path, or valid local file relative path +# caKey = "../ca.key" [settings] kubeContext = "minikube" # will try connect to this context first, if it does not exist, it will be created using the details below diff --git a/state.go b/state.go index 0e8770a9..2dfa5b52 100644 --- a/state.go +++ b/state.go @@ -43,9 +43,10 @@ func (s state) validate() (bool, string) { "but have not given me the keys to do so. Please add [caCrt] and [caKey] under Certifications." } for key, value := range s.Certificates { - _, err := url.ParseRequestURI(value) - if err != nil || !strings.HasPrefix(value, "s3://") { - return false, "ERROR: certifications validation failed -- [ " + key + " ] must be a valid S3 bucket URL." + _, err1 := url.ParseRequestURI(value) + _, err2 := os.Stat(value) + if (err1 != nil || !strings.HasPrefix(value, "s3://")) && err2 != nil { + return false, "ERROR: certifications validation failed -- [ " + key + " ] must be a valid S3 bucket URL or a valid relative file path." } } From c69676c8e7be0c04c8f7b77896349a2c682b0eab Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 25 Nov 2017 13:11:21 +0100 Subject: [PATCH 0043/1127] updated tests. --- .gitignore | 4 +++- release.go | 2 +- release_test.go | 26 +++++++++++++------------- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index bffc9251..05eb9627 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ *.passwd *.key *.crt -/dist \ No newline at end of file +/dist +*.world +*.world1 \ No newline at end of file diff --git a/release.go b/release.go index c4ef2c1c..715046e9 100644 --- a/release.go +++ b/release.go @@ -27,7 +27,7 @@ func validateRelease(r release, names map[string]bool) (bool, string) { if r.Name == "" || names[r.Name] { return false, "release name can't be empty and must be unique." } else if r.Namespace == "" { - return false, "release targeted env (namespace) can't be empty." + return false, "release targeted namespace can't be empty." } else if r.Chart == "" || !strings.ContainsAny(r.Chart, "/") { return false, "chart can't be empty and must be of the format: repo/chart." } else if r.Version == "" { diff --git a/release_test.go b/release_test.go index 2bdb83e8..17c5ec65 100644 --- a/release_test.go +++ b/release_test.go @@ -21,7 +21,7 @@ func Test_validateRelease(t *testing.T) { r: release{ Name: "release1", Description: "", - Env: "namespace", + Namespace: "namespace", Enabled: true, Chart: "repo/chartX", Version: "1.0", @@ -38,7 +38,7 @@ func Test_validateRelease(t *testing.T) { r: release{ Name: "release2", Description: "", - Env: "namespace", + Namespace: "namespace", Enabled: true, Chart: "repo/chartX", Version: "1.0", @@ -55,7 +55,7 @@ func Test_validateRelease(t *testing.T) { r: release{ Name: "release3", Description: "", - Env: "namespace", + Namespace: "namespace", Enabled: true, Chart: "repo/chartX", Version: "1.0", @@ -72,7 +72,7 @@ func Test_validateRelease(t *testing.T) { r: release{ Name: "release1", Description: "", - Env: "namespace", + Namespace: "namespace", Enabled: true, Chart: "repo/chartX", Version: "1.0", @@ -89,7 +89,7 @@ func Test_validateRelease(t *testing.T) { r: release{ Name: "", Description: "", - Env: "namespace", + Namespace: "namespace", Enabled: true, Chart: "repo/chartX", Version: "1.0", @@ -106,7 +106,7 @@ func Test_validateRelease(t *testing.T) { r: release{ Name: "release6", Description: "", - Env: "", + Namespace: "", Enabled: true, Chart: "repo/chartX", Version: "1.0", @@ -116,14 +116,14 @@ func Test_validateRelease(t *testing.T) { }, }, want: false, - want1: "release targeted env (namespace) can't be empty.", + want1: "release targeted namespace can't be empty.", }, { name: "test case 7", args: args{ r: release{ Name: "release7", Description: "", - Env: "namespace", + Namespace: "namespace", Enabled: true, Chart: "chartX", Version: "1.0", @@ -140,7 +140,7 @@ func Test_validateRelease(t *testing.T) { r: release{ Name: "release8", Description: "", - Env: "namespace", + Namespace: "namespace", Enabled: true, Chart: "", Version: "1.0", @@ -157,7 +157,7 @@ func Test_validateRelease(t *testing.T) { r: release{ Name: "release9", Description: "", - Env: "namespace", + Namespace: "namespace", Enabled: true, Chart: "repo/chartX", Version: "", @@ -174,7 +174,7 @@ func Test_validateRelease(t *testing.T) { r: release{ Name: "release10", Description: "", - Env: "namespace", + Namespace: "namespace", Enabled: true, Chart: "repo/chartX", Version: "1.0", @@ -205,7 +205,7 @@ func Test_validateRelease(t *testing.T) { // type fields struct { // Name string // Description string -// Env string +// Namespace string // Enabled bool // Chart string // Version string @@ -224,7 +224,7 @@ func Test_validateRelease(t *testing.T) { // r := release{ // Name: tt.fields.Name, // Description: tt.fields.Description, -// Env: tt.fields.Env, +// Namespace: tt.fields.Namespace, // Enabled: tt.fields.Enabled, // Chart: tt.fields.Chart, // Version: tt.fields.Version, From 0dbe08ccd45194f2d316a4359308196cf200114a Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 25 Nov 2017 15:59:37 +0100 Subject: [PATCH 0044/1127] fixed cluster password reading bug. --- decision_maker.go | 4 ++-- main.go | 9 +++++++-- release.go | 2 +- utils.go | 9 +++++---- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index 0d2c30e3..b065099f 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -2,7 +2,6 @@ package main import ( "log" - "os" "strings" ) @@ -254,7 +253,8 @@ func getValuesFile(r release) string { func getSetValues(r release) string { result := "" for k, v := range r.Set { - result = result + " --set " + k + "=" + os.Getenv(strings.SplitAfter(v, "$")[1]) + _, value := envVarExists(v) + result = result + " --set " + k + "=" + value } return result } diff --git a/main.go b/main.go index 1425b637..5f5e58eb 100644 --- a/main.go +++ b/main.go @@ -141,14 +141,19 @@ func addNamespaces(namespaces map[string]string) { func createContext() (bool, string) { var password string + var ok bool + if s.Settings["password"] == "" || s.Settings["username"] == "" || s.Settings["clusterURI"] == "" { return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ] " + "as you did not specify enough information in the Settings section of your desired state file." } else if s.Certificates == nil || s.Certificates["caCrt"] == "" || s.Certificates["caKey"] == "" { return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ] " + "as you did not provide Certifications to use in your desired state file." - } else if !envVarExists(s.Settings["password"]) { - return false, "ERROR: env variable [ " + s.Settings["password"] + " ] does not exist in the environment." + } else { + ok, password = envVarExists(s.Settings["password"]) + if !ok { + return false, "ERROR: env variable [ " + s.Settings["password"] + " ] does not exist in the environment." + } } // download certs using AWS cli diff --git a/release.go b/release.go index 715046e9..b34066f8 100644 --- a/release.go +++ b/release.go @@ -38,7 +38,7 @@ func validateRelease(r release, names map[string]bool) (bool, string) { for k, v := range r.Set { if !strings.HasPrefix(v, "$") { return false, "the value for variable [ " + k + " ] must be an environment variable name and start with '$'." - } else if !envVarExists(v) { + } else if ok, _ := envVarExists(v); ok { return false, "env variable [ " + v + " ] is not found in the environment." } } diff --git a/utils.go b/utils.go index d7f6de1f..1ac31747 100644 --- a/utils.go +++ b/utils.go @@ -86,14 +86,15 @@ func printHelp() { } -// envVarExists checks if an environment variable is set or not. +// envVarExists checks if an environment variable is set or not and returns it. +// empty string is returned for unset env vars // it accepts env var with/without '$' at the beginning -func envVarExists(v string) bool { +func envVarExists(v string) (bool, string) { if strings.HasPrefix(v, "$") { v = strings.SplitAfter(v, "$")[1] } - _, ok := os.LookupEnv(v) - return ok + value, ok := os.LookupEnv(v) + return ok, value } From 2081f04a1176fdb97d69f5c5bdbe51b0f804ebb8 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 25 Nov 2017 16:07:08 +0100 Subject: [PATCH 0045/1127] fixed a code typo. --- release.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release.go b/release.go index b34066f8..4d683a92 100644 --- a/release.go +++ b/release.go @@ -38,7 +38,7 @@ func validateRelease(r release, names map[string]bool) (bool, string) { for k, v := range r.Set { if !strings.HasPrefix(v, "$") { return false, "the value for variable [ " + k + " ] must be an environment variable name and start with '$'." - } else if ok, _ := envVarExists(v); ok { + } else if ok, _ := envVarExists(v); !ok { return false, "env variable [ " + v + " ] is not found in the environment." } } From 46840f3d8a4570ea069c7f272908a9d45debe0f1 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 25 Nov 2017 16:35:00 +0100 Subject: [PATCH 0046/1127] disabled running tests on upgrade and rollback. --- decision_maker.go | 18 +++++++++--------- example.toml | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index b065099f..9954cd09 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -101,9 +101,9 @@ func inspectRollbackScenario(namespace string, r release) { logDecision("DECISION: release [ " + releaseName + " ] is currently deleted and is desired to be rolledback to " + "namespace [[ " + namespace + " ]] . No problem!") - if r.Test { - testRelease(releaseName) - } + // if r.Test { + // testRelease(releaseName) + // } } else { @@ -193,9 +193,9 @@ func upgradeRelease(r release) { outcome.addCommand(cmd) - if r.Test { - testRelease(r.Name) - } + // if r.Test { + // testRelease(r.Name) + // } } // reInstallRelease purge deletes a release and reinstall it. @@ -217,9 +217,9 @@ func reInstallRelease(namespace string, r release) { } outcome.addCommand(installCmd) - if r.Test { - testRelease(releaseName) - } + // if r.Test { + // testRelease(releaseName) + // } } // logDecision adds the decisions made to the plan. diff --git a/example.toml b/example.toml index 3ec1a5ac..6b325f6e 100644 --- a/example.toml +++ b/example.toml @@ -48,7 +48,7 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" version = "0.9.1" # chart version valuesFile = "" # leaving it empty uses the default chart values purge = false # will only be considered when there is a delete operation - test = false # run the tests whenever this release is installed/upgraded/rolledback + test = false # run the tests when this release is installed for the first time only # [apps.jenkins.set] # values to override values from values.yaml with values from env vars-- useful for passing secrets to charts # db_username="$DB_USERNAME" # db_password="$DB_PASSWORD" @@ -63,7 +63,7 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" version = "6.2.0" # chart version valuesFile = "" # leaving it empty uses the default chart values purge = false # will only be considered when there is a delete operation - test = false # run the tests whenever this release is installed/upgraded/rolledback + test = false # run the tests when this release is installed for the first time only # [apps.artifactory.set] # values to override values from values.yaml with values from env vars-- useful for passing secrets to charts # db_username="$DB_USERNAME" # db_password="$DB_PASSWORD" From e971ba28da3cbb448dccf3efa1f16face51c27cf Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 25 Nov 2017 17:07:19 +0100 Subject: [PATCH 0047/1127] adding version. --- init.go | 4 ++-- main.go | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/init.go b/init.go index 6323d97a..e512880e 100644 --- a/init.go +++ b/init.go @@ -17,6 +17,8 @@ func init() { flag.Parse() + println("Helmsman version: " + version) + if help { printHelp() os.Exit(0) @@ -24,12 +26,10 @@ func init() { if !toolExists("helm") { log.Fatal("ERROR: helm is not installed/configured correctly. Aborting!") - os.Exit(1) } if !toolExists("kubectl") { log.Fatal("ERROR: kubectl is not installed/configured correctly. Aborting!") - os.Exit(1) } // after the init() func is run, read the TOML desired state file diff --git a/main.go b/main.go index 5f5e58eb..9c66a4a7 100644 --- a/main.go +++ b/main.go @@ -9,6 +9,7 @@ var debug bool var file string var apply bool var help bool +var version = "master" func main() { From 894bd3db7dece55bc5ded834e228385eaee61e6e Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 25 Nov 2017 17:18:26 +0100 Subject: [PATCH 0048/1127] added version print flag --- init.go | 9 ++++++++- main.go | 1 + utils.go | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/init.go b/init.go index e512880e..5c99a849 100644 --- a/init.go +++ b/init.go @@ -2,6 +2,7 @@ package main import ( "flag" + "fmt" "log" "os" ) @@ -14,16 +15,22 @@ func init() { flag.BoolVar(&apply, "apply", false, "apply the plan directly") flag.BoolVar(&debug, "debug", false, "show the execution logs") flag.BoolVar(&help, "help", false, "show Helmsman help") + flag.BoolVar(&v, "v", false, "show the version") flag.Parse() - println("Helmsman version: " + version) + if v { + fmt.Println("Helmsman version: " + version) + os.Exit(0) + } if help { printHelp() os.Exit(0) } + fmt.Println("Helmsman version: " + version) + if !toolExists("helm") { log.Fatal("ERROR: helm is not installed/configured correctly. Aborting!") } diff --git a/main.go b/main.go index 9c66a4a7..17ae750c 100644 --- a/main.go +++ b/main.go @@ -9,6 +9,7 @@ var debug bool var file string var apply bool var help bool +var v bool var version = "master" func main() { diff --git a/utils.go b/utils.go index 1ac31747..4be4171c 100644 --- a/utils.go +++ b/utils.go @@ -75,6 +75,7 @@ func readFile(filepath string) string { // printHelp prints Helmsman commands func printHelp() { + fmt.Println("Helmsman version: " + version) fmt.Println("Helmsman is a Helm Charts as Code tool which allows you to automate the deployment/management of your Helm charts.") fmt.Println(" Usage: helmsman [options]") fmt.Println() From 7d06fa022312d1aa781ac884522701aa3c3e899f Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 25 Nov 2017 17:31:42 +0100 Subject: [PATCH 0049/1127] added release-notes.md --- .circleci/build.sh | 2 +- release-notes.md | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 release-notes.md diff --git a/.circleci/build.sh b/.circleci/build.sh index 02a37dcb..dc5acd25 100755 --- a/.circleci/build.sh +++ b/.circleci/build.sh @@ -11,7 +11,7 @@ if [ $? -eq 0 ]; then rm hello.world hello.world1 helmsman git clean -fd echo "releasing ..." - goreleaser | tee /dev/tty | grep -o "error" + goreleaser --release-notes release_notes.md | tee /dev/tty | grep -o "error" if [ $? -eq 0 ]; then echo "goreleaser experienced an error and no new releases made. That is Ok!" else diff --git a/release-notes.md b/release-notes.md new file mode 100644 index 00000000..84dea200 --- /dev/null +++ b/release-notes.md @@ -0,0 +1,7 @@ +# v0.1.3 + +- Bug fixes. +- Support for passing user input values to charts from environment variables. Such values either override values from values.yaml or add to them. This is particularily useful for passing secrets from environment variables to helm charts. +- Tests no longer run on upgrade and rollback operations. This is because some charts does not use unique generated names for the tests and Helm does not manage the tests. In some cases, tests would fail due to having k8s resources with the same names existing from a previous run. +- Support for use of certificates and keys for cluster connection from the local file system. YOU SHOULD NEVER commit your certificates to git! This update is useful while testing on a local machine. +- The key 'env' for release namespaces in the desired state file is now changed to the more sensible 'namespace'. \ No newline at end of file From 3e2348101f6749e0c23848d19f02f0abf0cda686 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sun, 26 Nov 2017 11:00:21 +0100 Subject: [PATCH 0050/1127] fixed a bug in reading certs from local file system --- main.go | 46 +++++++++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/main.go b/main.go index 17ae750c..812997a3 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,7 @@ package main import ( "log" + "strings" ) var s state @@ -163,31 +164,42 @@ func createContext() (bool, string) { return false, "ERROR: aws is not installed/configured correctly. It is needed for downloading certs. Aborting!" } - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "aws s3 cp " + s.Certificates["caCrt"] + " ca.crt"}, - Description: "downloading ca.crt from S3.", - } + caCrt := s.Certificates["caCrt"] + caKey := s.Certificates["caKey"] - if exitCode, _ := cmd.exec(debug); exitCode != 0 { - return false, "ERROR: failed to download caCrt." - } + if strings.HasPrefix(s.Certificates["caCrt"], "s3") { + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "aws s3 cp " + s.Certificates["caCrt"] + " ca.crt"}, + Description: "downloading ca.crt from S3.", + } - cmd = command{ - Cmd: "bash", - Args: []string{"-c", "aws s3 cp " + s.Certificates["caKey"] + " ca.key"}, - Description: "downloading ca.key from S3.", + if exitCode, _ := cmd.exec(debug); exitCode != 0 { + return false, "ERROR: failed to download caCrt." + } + + caCrt = "ca.crt" } - if exitCode, _ := cmd.exec(debug); exitCode != 0 { - return false, "ERROR: failed to download caKey." + if strings.HasPrefix(s.Certificates["caKey"], "s3") { + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "aws s3 cp " + s.Certificates["caKey"] + " ca.key"}, + Description: "downloading ca.key from S3.", + } + + if exitCode, _ := cmd.exec(debug); exitCode != 0 { + return false, "ERROR: failed to download caKey." + } + + caKey = "ca.key" } // connecting to the cluster - cmd = command{ + cmd := command{ Cmd: "bash", Args: []string{"-c", "kubectl config set-credentials " + s.Settings["username"] + " --username=" + s.Settings["username"] + - " --password=" + password + " --client-key=ca.key"}, + " --password=" + password + " --client-key=" + caKey}, Description: "creating kubectl context - setting credentials.", } @@ -198,7 +210,7 @@ func createContext() (bool, string) { cmd = command{ Cmd: "bash", Args: []string{"-c", "kubectl config set-cluster " + s.Settings["kubeContext"] + " --server=" + s.Settings["clusterURI"] + - " --certificate-authority=ca.crt"}, + " --certificate-authority=" + caCrt}, Description: "creating kubectl context - setting cluster.", } From c53320bd6ea7b88ad829650a48e8bd997f9ebf52 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sun, 26 Nov 2017 11:58:39 +0100 Subject: [PATCH 0051/1127] updated docs for v0.1.3 --- README.md | 36 +++++++---- docs/how_to/define_namespaces.md | 38 ++++++++++++ docs/how_to/manipulate_apps.md | 38 +++++++++++- docs/how_to/move_charts_across_namespaces.md | 59 +++++++++++++++++++ .../how_to/pass_secrets_from env_variables.md | 29 +++++++++ docs/how_to/run_helmsman_in_ci.md | 2 +- .../run_helmsman_with_hosted_cluster.md | 12 ++-- docs/how_to/run_helmsman_with_minikube.md | 6 +- docs/how_to/test_charts.md | 25 ++++++++ docs/how_to/use_local_charts.md | 18 ++++++ docs/how_to/use_private_helm_charts.md | 29 +++++++++ 11 files changed, 270 insertions(+), 22 deletions(-) create mode 100644 docs/how_to/define_namespaces.md create mode 100644 docs/how_to/move_charts_across_namespaces.md create mode 100644 docs/how_to/pass_secrets_from env_variables.md create mode 100644 docs/how_to/test_charts.md create mode 100644 docs/how_to/use_local_charts.md create mode 100644 docs/how_to/use_private_helm_charts.md diff --git a/README.md b/README.md index 254a0f8b..5be67c8c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ --- -version: v0.1.2 +version: v0.1.3 --- # What is Helmsman? @@ -13,19 +13,33 @@ Helmsman was created to ease continous deployment of Helm charts. When you want # Features -- **Idempotency**: As long your desired state file does not change, you can execute Helmsman several times and get the same result. -- **Continue from failures**: In the case of partial executions due to a specific chart deployment failure, fix your helm chart and execute Helmsman again without needing to rollback the partial successes first. - **Built for CD**: Helmsman can be used as a docker image or a binary. - **Applications as code**: describe your desired applications and manage them from a single version-controlled declarative file. -- **Easy to use**: knowledge of Helm CLI and Kubectl is NOT manadatory to use Helmsman. -- **Plan, View, apply**: you can run Helmsman to generate and view a plan with/without executing it. +- **Easy to use**: deep knowledge of Helm CLI and Kubectl is NOT manadatory to use Helmsman. +- **Plan, View, apply**: you can run Helmsman to generate and view a plan with/without executing it. +- **Portable**: Helmsman can be used to manage charts deployments on any k8s cluster. +- **Idempotency**: As long your desired state file does not change, you can execute Helmsman several times and get the same result. +- **Continue from failures**: In the case of partial deployment due to a specific chart deployment failure, fix your helm chart and execute Helmsman again without needing to rollback the partial successes first. + +# Helmsman lets you: + +- [install/delete/upgrade/rollback your helm charts from code](https://github.com/Praqma/helmsman/blob/master/docs/how_to/manipulate_apps.md). +- [pass secrets/user input to helm charts from environment variables](https://github.com/Praqma/helmsman/blob/master/docs/how_to/pass_secrets_from env_variables.md). +- [test releases when they are first installed](https://github.com/Praqma/helmsman/blob/master/docs/how_to/test_charts.md). +- [use public and private helm charts](https://github.com/Praqma/helmsman/blob/master/docs/how_to/use_private_helm_charts.md). +- [use locally developed helm charts (the tar archives)](https://github.com/Praqma/helmsman/blob/master/docs/how_to/use_local_charts.md). +- [define namespaces to be used in your cluster](https://github.com/Praqma/helmsman/blob/master/docs/how_to/define_namespaces.md). +- [move charts across namespaces](https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md). + # Usage Helmsman can be used in three different settings: -- As a binary on local machine [with either a Minikube or a hosted cluster ]. See the docs [here](https://github.com/Praqma/helmsman/blob/master/docs/how_to/run_helmsman_with_minikube.md) for instructions on using Minikube and [here](https://github.com/Praqma/helmsman/blob/master/docs/how_to/run_helmsman_with_hosted_cluster.md) for using hosted cluster. -- As a docker image [in a CI system or local machine]. See the docs [here](https://github.com/Praqma/helmsman/blob/master/docs/how_to/run_helmsman_in_ci.md) for instructions. +- [As a binary with Minikube](https://github.com/Praqma/helmsman/blob/master/docs/how_to/run_helmsman_with_minikube.md). +- [As a binary with a hosted cluster](https://github.com/Praqma/helmsman/blob/master/docs/how_to/run_helmsman_with_hosted_cluster.md). +- [As a docker image in a CI system or local machine](https://github.com/Praqma/helmsman/blob/master/docs/how_to/run_helmsman_in_ci.md). + # How does it work? @@ -33,7 +47,7 @@ Helmsman uses a simple declarative [TOML](https://github.com/toml-lang/toml) fil The desired state file follows the [desired state specification](https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md). -Helmsman sees what you desire, validates that your desire makes sense (e.g. that the charts you desire are available in the repos you defined), compares it with the current state of Helm and figures out what to do to make your desire come true. Below is the result of executing the [example.toml](https://github.com/Praqma/helmsman/blob/master/example.toml) +Helmsman sees what you desire, validates that your desire makes sense (e.g. that the charts you desire are available in the repos you defined), compares it with the current state of Helm and figures out what to do to make your desire come true. To plan without executing: ``` $ helmsman -f example.toml ``` @@ -41,18 +55,20 @@ To plan without executing: To plan and execute the plan: ``` $ helmsman -apply -f example.toml ``` -To debug the planning: +To show debugging details: ``` $ helmsman -debug -apply -f example.toml ``` -Check the documentation for [how to manage an app from the desired state file](https://github.com/Praqma/helmsman/blob/master/docs/how_to/manipulate_apps.md). # Installation Install Helmsman for your OS from the [releases page](https://github.com/Praqma/Helmsman/releases). Available for Linux and MacOS. +Also available as a [docker image](https://hub.docker.com/r/praqma/helmsman/). + # Documentaion Documentation and How-Tos can be found [here](https://github.com/Praqma/helmsman/blob/master/docs/). # Contributing + Contribution and feeback/feature requests are welcome. \ No newline at end of file diff --git a/docs/how_to/define_namespaces.md b/docs/how_to/define_namespaces.md new file mode 100644 index 00000000..ea80937d --- /dev/null +++ b/docs/how_to/define_namespaces.md @@ -0,0 +1,38 @@ +--- +version: v0.1.3 +--- + +# define namespaces + +You can define namespaces to be used in your cluster. If they don't exist, Helmsman will create them for you. + +``` +... + +[namespaces] +staging = "staging" +production = "default" +myOtherNamespace = "namespaceX" + +... +``` + +You can then tell Helmsman to put specific releases in a specific namespace: + +``` +... +[apps] + + [apps.jenkins] + name = "jenkins" + description = "jenkins" + namespace = "myOtherNamespace" # this is the pointer to the namespace defined above -- i.e. it deploys to namespace 'namespaceX' + enabled = true + chart = "stable/jenkins" + version = "0.9.1" + valuesFile = "" + purge = false + test = true + +... +``` \ No newline at end of file diff --git a/docs/how_to/manipulate_apps.md b/docs/how_to/manipulate_apps.md index f17c1ee0..980e67cb 100644 --- a/docs/how_to/manipulate_apps.md +++ b/docs/how_to/manipulate_apps.md @@ -1,7 +1,8 @@ --- -version: v0.1.2 +version: v0.1.3 --- +# install releases You can run helmsman with the [example.toml](https://github.com/Praqma/helmsman/blob/master/example.toml) file. @@ -28,10 +29,14 @@ artifactory 1 Sun Nov 19 18:18:06 2017 DEPLOYED artifactory-6.2.0 staging jenkins 1 Sun Nov 19 18:18:03 2017 DEPLOYED jenkins-0.9.1 staging ``` +# delete releases + You can then change your desire, for example to disable the Jenkins release that was created above by setting `enabled = false` : Then run Helmsman again and it will detect that you want to delete Jenkins: +> Note: deleting the jenkins app entry in example.toml WILL NOT resultin deleting the jenkins release. It simply means that Helmsman is no longer responsible for managing it. + ``` $ helmsman -apply -f example.toml 2017/11/19 18:28:27 Parsed [[ example.toml ]] successfully and found [ 2 ] apps. @@ -52,6 +57,30 @@ NAME REVISION UPDATED STATUS CHART NAMESPA artifactory 2 Sun Nov 19 18:29:11 2017 DEPLOYED artifactory-6.2.0 staging ``` +If you would like the release to be deleted along with its history, you can use the `purge` key in your desired state file as follows: + +> NOTE: purge deleting a release means you can't roll it back. + +``` +... +[apps] + + [apps.jenkins] + name = "jenkins" + description = "jenkins" + namespace = "staging" + enabled = false # this tells helmsman to delete it + chart = "stable/jenkins" + version = "0.9.1" + valuesFile = "" + purge = true # this means purge delete this release whenever it is required to be deleted + test = flase + +... +``` + +# rollback releases + Similarly, if you change `enabled` back to `true`, it will figure out that you would like to roll it back. ``` @@ -68,5 +97,10 @@ DECISION: release [ artifactory ] is desired to be upgraded. Planing this for yo 2017/11/19 18:30:50 INFO: attempting: -- upgrading release [ artifactory ] ``` -Similarly, You can also change the chart or chart version and specify a values.yaml file to override the default chart values. +# upgrade releases + +Everytime you run Helmsman, it will upgrade existing deployed releases to the version you specified in the desired state file. It also applies the `values.yaml` file you specify with each install/upgrade. This means that when you don't change anything for a specific release, Helmsman would upgrade with the `values.yaml` file you provide (just in case it is a new file or you changed something there.) + +If you change the chart, the existing release will be deleted and a new one with the same name will be created using the new chart. + diff --git a/docs/how_to/move_charts_across_namespaces.md b/docs/how_to/move_charts_across_namespaces.md new file mode 100644 index 00000000..3b242693 --- /dev/null +++ b/docs/how_to/move_charts_across_namespaces.md @@ -0,0 +1,59 @@ +--- +version: v0.1.3 +--- + +# move charts across namespaces + +If you have a workflow for testing a release first in the `staging` namespace then move to the `production` namespace, Helmsman can help you. + +``` +... + +[namespaces] +staging = "staging" +production = "default" +myOtherNamespace = "namespaceX" + +[apps] + + [apps.jenkins] + name = "jenkins" + description = "jenkins" + namespace = "staging" # this is where it is deployed + enabled = true + chart = "stable/jenkins" + version = "0.9.1" + valuesFile = "" + purge = false + test = true + +... +``` + +Then if you change the namespace key for jenkins: + +``` +... + +[namespaces] +staging = "staging" +production = "default" +myOtherNamespace = "namespaceX" + +[apps] + + [apps.jenkins] + name = "jenkins" + description = "jenkins" + namespace = "production" # we want to move it to production + enabled = true + chart = "stable/jenkins" + version = "0.9.1" + valuesFile = "" + purge = false + test = true + +... +``` + +Helmsman will delete the jenkins release from the `staging` namespace and install it in the `production` namespace (default in the above setup). \ No newline at end of file diff --git a/docs/how_to/pass_secrets_from env_variables.md b/docs/how_to/pass_secrets_from env_variables.md new file mode 100644 index 00000000..1413ec76 --- /dev/null +++ b/docs/how_to/pass_secrets_from env_variables.md @@ -0,0 +1,29 @@ +--- +version: v0.1.3 +--- + +# pass secrets from env. variables: + +Starting from v0.1.3, Helmsman allows you to pass secrets and other user input to helm charts from environment variables as follows: + +``` +... +[apps] + + [apps.jira] + name = "jira" + description = "jira" + namespace = "staging" + enabled = true + chart = "myrepo/jira" + version = "0.1.5" + valuesFile = "applications/jira-values.yaml" + purge = false + test = true + [apps.jira.set] # the format is [apps.<>.set] + db_username= "$JIRA_DB_USERNAME" # pass any number of key/value pairs where the key is the input expected by the helm charts and the value is an env variable name starting with $ + db_password= "$JIRA_DB_PASSWORD" +... +``` + +These input variables will be passed to the chart when it is deployed/upgraded using helm's `--set <>=<>` \ No newline at end of file diff --git a/docs/how_to/run_helmsman_in_ci.md b/docs/how_to/run_helmsman_in_ci.md index 64b65655..dd2b52bf 100644 --- a/docs/how_to/run_helmsman_in_ci.md +++ b/docs/how_to/run_helmsman_in_ci.md @@ -1,5 +1,5 @@ --- -version: v0.1.2 +version: v0.1.3 --- # Run Helmsman in CI diff --git a/docs/how_to/run_helmsman_with_hosted_cluster.md b/docs/how_to/run_helmsman_with_hosted_cluster.md index 6fba3216..00c55980 100644 --- a/docs/how_to/run_helmsman_with_hosted_cluster.md +++ b/docs/how_to/run_helmsman_with_hosted_cluster.md @@ -1,10 +1,10 @@ --- -version: v0.1.2 +version: v0.1.3 --- You can manage Helm charts deployment on a hosted K8S cluster in the cloud or on-prem. You need to include the required information to connect to the cluster in your state file. Below is an example: -**IMPORTANT**: Only Certificates and private helm repos in S3 buckets are currently supported. Helmsman needs valid AWS access keys to be able to retrieve private charts or certificates from your s3 buckets. It expects the keys to be in the following environemnt variables: +**IMPORTANT**: Certificates can be used from S3 buckets or local file system. If you use s3 buckets, Helmsman needs valid AWS access keys to be able to retrieve private charts or certificates from your s3 buckets. It expects the keys to be in the following environemnt variables: - AWS_ACCESS_KEY_ID - AWS_SECRET_ACCESS_KEY @@ -19,8 +19,8 @@ maintainer = "k8s-admin" # Certificates are used to connect to the cluster. Currently, they can only be retrieved from s3 buckets. [certificates] -caCrt = "s3://your-bucket/ca.crt" -caKey = "s3://your-bucket/ca.key" +caCrt = "s3://your-bucket/ca.crt" # s3 bucket +caKey = "../../ca.key" # relative file path [settings] kubeContext = "mycontext" @@ -41,7 +41,7 @@ myrepo = "s3://my-private-repo/charts" [apps.jenkins] name = "jenkins" description = "jenkins" - env = "staging" + namespace = "staging" enabled = true chart = "stable/jenkins" version = "0.9.1" @@ -53,7 +53,7 @@ myrepo = "s3://my-private-repo/charts" [apps.artifactory] name = "artifactory" description = "artifactory" - env = "staging" + namespace = "staging" enabled = true chart = "stable/artifactory" version = "6.2.0" diff --git a/docs/how_to/run_helmsman_with_minikube.md b/docs/how_to/run_helmsman_with_minikube.md index ef9e969a..7ecd256b 100644 --- a/docs/how_to/run_helmsman_with_minikube.md +++ b/docs/how_to/run_helmsman_with_minikube.md @@ -1,5 +1,5 @@ --- -version: v0.1.2 +version: v0.1.3 --- You can run Helmsman local as a binary application with Minikube, you just need to skip all the cluster connection settings in your desired state file. Below is the example.toml desired state file adapted to work with Minikube. @@ -25,7 +25,7 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" [apps.jenkins] name = "jenkins" description = "jenkins" - env = "staging" + namespace = "staging" enabled = true chart = "stable/jenkins" version = "0.9.1" @@ -37,7 +37,7 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" [apps.artifactory] name = "artifactory" description = "artifactory" - env = "staging" + namespace = "staging" enabled = true chart = "stable/artifactory" version = "6.2.0" diff --git a/docs/how_to/test_charts.md b/docs/how_to/test_charts.md new file mode 100644 index 00000000..c38d6cc0 --- /dev/null +++ b/docs/how_to/test_charts.md @@ -0,0 +1,25 @@ +--- +version: v0.1.3 +--- + +# test charts + +You can specifiy that you would like a chart to be tested whenever it is installed for the first time using the `test` key as follows: + +``` +... +[apps] + + [apps.jenkins] + name = "jenkins" + description = "jenkins" + namespace = "staging" + enabled = true + chart = "stable/jenkins" + version = "0.9.1" + valuesFile = "" + purge = false + test = true # setting this to true, means you want the charts tests to be run on this release when it is intalled. + +... +``` \ No newline at end of file diff --git a/docs/how_to/use_local_charts.md b/docs/how_to/use_local_charts.md new file mode 100644 index 00000000..fc50ac72 --- /dev/null +++ b/docs/how_to/use_local_charts.md @@ -0,0 +1,18 @@ +--- +version: v0.1.3 +--- + +# use local helm charts + +You can use your locally developed charts. But first, you have to serve them on localhost using helm's `serve` option. + +``` +... + +[helmRepos] +stable = "https://kubernetes-charts.storage.googleapis.com" +incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" +local = http://127.0.0.1:8879 + +... +``` \ No newline at end of file diff --git a/docs/how_to/use_private_helm_charts.md b/docs/how_to/use_private_helm_charts.md new file mode 100644 index 00000000..582ee4c1 --- /dev/null +++ b/docs/how_to/use_private_helm_charts.md @@ -0,0 +1,29 @@ +--- +version: v0.1.3 +--- + +# use private helm charts + +Helmsman allows you to use private charts from private repos. Currently only repos hosted in S3 buckets are supported for private repos. +Other hosting options will be supported in the future. + +define your private repo: + +``` +... + +[helmRepos] +stable = "https://kubernetes-charts.storage.googleapis.com" +incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" +myPrivateRepo = s3://this-is-a-private-repo/charts + +... +``` + +If you are using S3 private repos, you need to provide the following AWS env variables: + +- AWS_ACCESS_KEY_ID +- AWS_SECRET_ACCESS_KEY +- AWS_DEFAULT_REGION + +Helmsman uses the [helm s3](https://github.com/hypnoglow/helm-s3) plugin to work with S3 helm repos. \ No newline at end of file From 227ac81fbf93d9058d7f2fc8a079487cc544640a Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sun, 26 Nov 2017 12:01:26 +0100 Subject: [PATCH 0052/1127] fixed broken link in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5be67c8c..db98c1db 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Helmsman was created to ease continous deployment of Helm charts. When you want # Helmsman lets you: - [install/delete/upgrade/rollback your helm charts from code](https://github.com/Praqma/helmsman/blob/master/docs/how_to/manipulate_apps.md). -- [pass secrets/user input to helm charts from environment variables](https://github.com/Praqma/helmsman/blob/master/docs/how_to/pass_secrets_from env_variables.md). +- [pass secrets/user input to helm charts from environment variables](https://github.com/Praqma/helmsman/blob/master/docs/how_to/pass_secrets_from_env_variables.md). - [test releases when they are first installed](https://github.com/Praqma/helmsman/blob/master/docs/how_to/test_charts.md). - [use public and private helm charts](https://github.com/Praqma/helmsman/blob/master/docs/how_to/use_private_helm_charts.md). - [use locally developed helm charts (the tar archives)](https://github.com/Praqma/helmsman/blob/master/docs/how_to/use_local_charts.md). From 9e5fc7cc634922104def0dd00d956e8267fa2951 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sun, 26 Nov 2017 12:05:17 +0100 Subject: [PATCH 0053/1127] fixed a whitespace in docs file name --- ...s_from env_variables.md => pass_secrets_from_env_variables.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/how_to/{pass_secrets_from env_variables.md => pass_secrets_from_env_variables.md} (100%) diff --git a/docs/how_to/pass_secrets_from env_variables.md b/docs/how_to/pass_secrets_from_env_variables.md similarity index 100% rename from docs/how_to/pass_secrets_from env_variables.md rename to docs/how_to/pass_secrets_from_env_variables.md From bf962b90fca41e475c5b5052ad73ed44ef2710c4 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Thu, 1 Feb 2018 13:29:11 +0100 Subject: [PATCH 0054/1127] adding gcs package [ci skip] --- gcs/gcs.go | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 gcs/gcs.go diff --git a/gcs/gcs.go b/gcs/gcs.go new file mode 100644 index 00000000..6693e767 --- /dev/null +++ b/gcs/gcs.go @@ -0,0 +1,56 @@ +package gcs + +import ( + "io" + "log" + "os" + + // Imports the Google Cloud Storage client package. + "cloud.google.com/go/storage" + "golang.org/x/net/context" +) + +func checkCredentialsEnvVar() bool { + if os.Getenv("GOOGLE_APPLICATION_CREDENTIALS") != "" { + return true + } + return false +} + +// ReadFile reads a file from storage bucket and saves it in a desired location. +func ReadFile(bucketName string, filename string, outFile string) { + if !checkCredentialsEnvVar() { + log.Fatal("Failed to find the Google_APPLICATION_CREDENTIALS env var. Please make sure it is set in the environment.") + } + + ctx := context.Background() + client, err := storage.NewClient(ctx) + if err != nil { + log.Fatalf("Failed to configure Storage bucket: %v", err) + } + storageBucket := client.Bucket(bucketName) + + // Creates an Object handler for our file + obj := storageBucket.Object(filename) + + // Read the object. + r, err := obj.NewReader(ctx) + if err != nil { + log.Fatalf("Failed to create object reader: %v", err) + } + defer r.Close() + + // create output file and write to it + var writers []io.Writer + file, err := os.Create(outFile) + if err != nil { + log.Fatalf("Failed to create an output file: %v", err) + } + writers = append(writers, file) + defer file.Close() + + dest := io.MultiWriter(writers...) + if _, err := io.Copy(dest, r); err != nil { + log.Fatalf("Failed to read object content: %v", err) + } +} From d7f173fa2d9d690ba0a4056f3be4b47e3cf5363a Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 3 Feb 2018 16:11:05 +0100 Subject: [PATCH 0055/1127] adding support for reading secrets from GCS buckets. --- gcs/gcs.go | 2 +- init.go | 2 +- main.go | 30 +++++++++++++++++++++++++----- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/gcs/gcs.go b/gcs/gcs.go index 6693e767..f6040e82 100644 --- a/gcs/gcs.go +++ b/gcs/gcs.go @@ -20,7 +20,7 @@ func checkCredentialsEnvVar() bool { // ReadFile reads a file from storage bucket and saves it in a desired location. func ReadFile(bucketName string, filename string, outFile string) { if !checkCredentialsEnvVar() { - log.Fatal("Failed to find the Google_APPLICATION_CREDENTIALS env var. Please make sure it is set in the environment.") + log.Fatal("Failed to find the GOOGLE_APPLICATION_CREDENTIALS env var. Please make sure it is set in the environment.") } ctx := context.Background() diff --git a/init.go b/init.go index 5c99a849..e57095b1 100644 --- a/init.go +++ b/init.go @@ -39,7 +39,7 @@ func init() { log.Fatal("ERROR: kubectl is not installed/configured correctly. Aborting!") } - // after the init() func is run, read the TOML desired state file + // read the TOML desired state file result, msg := fromTOML(file, &s) if result { log.Printf(msg) diff --git a/main.go b/main.go index 812997a3..efb91d2d 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,8 @@ package main import ( "log" "strings" + + "Helmsman/gcs" ) var s state @@ -50,7 +52,7 @@ func main() { } // setKubeContext sets your kubectl context to the one specified in the desired state file. -// It returns false if it fails to set the context. This means the context deos not exist. +// It returns false if it fails to set the context. This means the context does not exist. func setKubeContext(context string) bool { cmd := command{ Cmd: "bash", @@ -164,13 +166,15 @@ func createContext() (bool, string) { return false, "ERROR: aws is not installed/configured correctly. It is needed for downloading certs. Aborting!" } + // set certs locations (relative filepath, GCS bucket, AWS bucket) caCrt := s.Certificates["caCrt"] caKey := s.Certificates["caKey"] - if strings.HasPrefix(s.Certificates["caCrt"], "s3") { + // download certs from AWS (if applicable) + if strings.HasPrefix(caCrt, "s3") { cmd := command{ Cmd: "bash", - Args: []string{"-c", "aws s3 cp " + s.Certificates["caCrt"] + " ca.crt"}, + Args: []string{"-c", "aws s3 cp " + caCrt + " ca.crt"}, Description: "downloading ca.crt from S3.", } @@ -181,10 +185,10 @@ func createContext() (bool, string) { caCrt = "ca.crt" } - if strings.HasPrefix(s.Certificates["caKey"], "s3") { + if strings.HasPrefix(caKey, "s3") { cmd := command{ Cmd: "bash", - Args: []string{"-c", "aws s3 cp " + s.Certificates["caKey"] + " ca.key"}, + Args: []string{"-c", "aws s3 cp " + caKey + " ca.key"}, Description: "downloading ca.key from S3.", } @@ -195,6 +199,22 @@ func createContext() (bool, string) { caKey = "ca.key" } + // download certs from GCS (if applicable) + // GCS bucket+file format should be: gs://bucket-name/file-name.extension + if strings.HasPrefix(caCrt, "gs") { + tmp := strings.SplitAfterN(caCrt, "//", 2)[1] + gcs.ReadFile(strings.SplitN(tmp, "/", 2)[0], strings.SplitN(tmp, "/", 2)[1], "ca.crt") + + caCrt = "ca.crt" + } + + if strings.HasPrefix(caKey, "gs") { + tmp := strings.SplitAfterN(caKey, "//", 2)[1] + gcs.ReadFile(strings.SplitN(tmp, "/", 2)[0], strings.SplitN(tmp, "/", 2)[1], "ca.key") + + caCrt = "ca.key" + } + // connecting to the cluster cmd := command{ Cmd: "bash", From fa10e98c76f925d89f1d39bcb1d6acd371b11e3c Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 3 Feb 2018 16:14:13 +0100 Subject: [PATCH 0056/1127] enabling certs and settings to be passed from env vars. --- state.go | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/state.go b/state.go index 2dfa5b52..1a0ee24d 100644 --- a/state.go +++ b/state.go @@ -29,10 +29,13 @@ func (s state) validate() (bool, string) { return false, "ERROR: settings validation failed -- you have not provided a " + "kubeContext to use. Can't work without it. Sorry!" } else if len(s.Settings) > 1 { + s.Settings["password"] = subsituteEnv(s.Settings["password"]) + s.Settings["clusterURI"] = subsituteEnv(s.Settings["clusterURI"]) + if s.Settings["password"] == "" || !strings.HasPrefix(s.Settings["password"], "$") { - return false, "ERROR: settings validation failed -- password should be an env variable starting with $ " + return false, "ERROR: settings validation failed -- password should be set as an env variable. It is currently missing or empty. " } else if _, err := url.ParseRequestURI(s.Settings["clusterURI"]); err != nil { - return false, "ERROR: settings validation failed -- clusterURI must have a valid URL." + return false, "ERROR: settings validation failed -- clusterURI must have a valid URL set in an env varibale or passed directly. Either the env var is missing/empty or the URL is invalid." } } @@ -40,20 +43,21 @@ func (s state) validate() (bool, string) { if s.Certificates != nil { if len(s.Settings) > 1 && len(s.Certificates) != 2 { return false, "ERROR: certifications validation failed -- You want me to connect to your cluster for you " + - "but have not given me the keys to do so. Please add [caCrt] and [caKey] under Certifications." + "but have not given me the keys to do so. Please add [caCrt] and [caKey] under Certifications. You might also need to provide [clientCrt]." } for key, value := range s.Certificates { + value = subsituteEnv(value) _, err1 := url.ParseRequestURI(value) _, err2 := os.Stat(value) - if (err1 != nil || !strings.HasPrefix(value, "s3://")) && err2 != nil { - return false, "ERROR: certifications validation failed -- [ " + key + " ] must be a valid S3 bucket URL or a valid relative file path." + if (err1 != nil || (!strings.HasPrefix(value, "s3://") && !strings.HasPrefix(value, "gs://"))) && err2 != nil { + return false, "ERROR: certifications validation failed -- [ " + key + " ] must be a valid S3 or GCS bucket URL or a valid relative file path." } } } else { if len(s.Settings) > 1 { return false, "ERROR: certifications validation failed -- You want me to connect to your cluster for you " + - "but have not given me the keys to do so. Please add [caCrt] and [caKey] under Certifications." + "but have not given me the keys to do so. Please add [caCrt] and [caKey] under Certifications. You might also need to provide [clientCrt]." } } @@ -103,6 +107,16 @@ func (s state) validate() (bool, string) { return true, "" } +// substitueEnv checks if a string is an env variable (starts with '$'), then it returns its value +// if the env variable is empty or unset, an empty string is returned +// if the string does not start with '$', it is returned as is. +func subsituteEnv(name string) string { + if strings.HasPrefix(name, "$") { + return os.Getenv(strings.SplitAfterN(name, "$", 2)[1]) + } + return name +} + // print prints the desired state func (s state) print() { From a2d28b0b65050d2e9b21257b637149973a6bbf73 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 3 Feb 2018 16:14:53 +0100 Subject: [PATCH 0057/1127] adding a warning about volumes when moving apps across namespaces. --- decision_maker.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index 9954cd09..f87b7dd4 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -31,15 +31,16 @@ func decide(r release, s *state) { inspectUpgradeScenario(s.Namespaces[r.Namespace], r) - } else if helmReleaseExists(s.Namespaces[r.Namespace], r.Name, "deleted") { + } else if helmReleaseExists(nil, r.Name, "deleted") { inspectRollbackScenario(s.Namespaces[r.Namespace], r) - } else if helmReleaseExists(s.Namespaces[r.Namespace], r.Name, "failed") { + } else if helmReleaseExists(nil, r.Name, "failed") { deleteRelease(r.Name, true) + installRelease(s.Namespaces[r.Namespace], r) - } else if helmReleaseExists(s.Namespaces[r.Namespace], r.Name, "all") { // it is deployed but in another namespace + } else if helmReleaseExists(nil, r.Name, "all") { // it is deployed but in another namespace reInstallRelease(s.Namespaces[r.Namespace], r) @@ -110,6 +111,8 @@ func inspectRollbackScenario(namespace string, r release) { reInstallRelease(namespace, r) logDecision("DECISION: release [ " + releaseName + " ] is deleted BUT from namespace [[ " + getReleaseNamespace(releaseName) + " ]]. Will purge delete it from there and install it in namespace [[ " + namespace + " ]]") + logDecision("WARNING: rolling back release [ " + releaseName + " ] from [[ " + getReleaseNamespace(releaseName) + " ]] to [[ " + namespace + + " ]] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md for details if this release uses PV and PVC.") } } @@ -180,6 +183,8 @@ func inspectUpgradeScenario(namespace string, r release) { logDecision("DECISION: release [ " + releaseName + " ] is desired to be enabled in a new namespace [[ " + namespace + " ]]. I am planning a purge delete of the current release from namespace [[ " + getReleaseNamespace(releaseName) + " ]] " + "and will install it for you in namespace [[ " + namespace + " ]]") + logDecision("WARNING: Moving release [ " + releaseName + " ] from [[ " + getReleaseNamespace(releaseName) + " ]] to [[ " + namespace + + " ]] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md for details if this release uses PV and PVC.") } } @@ -198,7 +203,7 @@ func upgradeRelease(r release) { // } } -// reInstallRelease purge deletes a release and reinstall it. +// reInstallRelease purge deletes a release and reinstalls it. // This is used when moving a release to another namespace or when changing the chart used for it. func reInstallRelease(namespace string, r release) { From a3e2ecc450f7b0643acc7ba5a961587fde3727d6 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Fri, 9 Feb 2018 10:54:38 +0100 Subject: [PATCH 0058/1127] enabling the GCS package to work with credentials from env var. --- gcs/gcs.go | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/gcs/gcs.go b/gcs/gcs.go index f6040e82..e7420f2b 100644 --- a/gcs/gcs.go +++ b/gcs/gcs.go @@ -1,7 +1,10 @@ package gcs import ( + "bytes" + "encoding/json" "io" + "io/ioutil" "log" "os" @@ -11,7 +14,27 @@ import ( ) func checkCredentialsEnvVar() bool { - if os.Getenv("GOOGLE_APPLICATION_CREDENTIALS") != "" { + if os.Getenv("GCLOUD_CREDENTIALS") != "" { + // write the credentials content into a json file + file, err := os.Create("/tmp/credentials.json") + if err != nil { + log.Fatal("ERROR: Cannot create credentials file: ", err) + } else { + // making sure special characters are not escaped + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + enc.SetEscapeHTML(false) + enc.Encode(os.Getenv("GCLOUD_CREDENTIALS")) + + // writing the credentials content to a file + err := ioutil.WriteFile(file.Name(), buf.Bytes(), 0644) + if err != nil { + log.Fatal("ERROR: Cannot write the credentials file: ", err) + } + } + defer file.Close() + + os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "/tmp/credentials.json") return true } return false @@ -20,7 +43,7 @@ func checkCredentialsEnvVar() bool { // ReadFile reads a file from storage bucket and saves it in a desired location. func ReadFile(bucketName string, filename string, outFile string) { if !checkCredentialsEnvVar() { - log.Fatal("Failed to find the GOOGLE_APPLICATION_CREDENTIALS env var. Please make sure it is set in the environment.") + log.Fatal("Failed to find the GCLOUD_CREDENTIALS env var. Please make sure it is set in the environment.") } ctx := context.Background() From ca48963bdbcf099eee330250c2f832d621f5aff4 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Fri, 9 Feb 2018 10:56:23 +0100 Subject: [PATCH 0059/1127] small modifications for code readability. --- decision_maker.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index f87b7dd4..7e83a9a3 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -31,16 +31,16 @@ func decide(r release, s *state) { inspectUpgradeScenario(s.Namespaces[r.Namespace], r) - } else if helmReleaseExists(nil, r.Name, "deleted") { + } else if helmReleaseExists("", r.Name, "deleted") { inspectRollbackScenario(s.Namespaces[r.Namespace], r) - } else if helmReleaseExists(nil, r.Name, "failed") { + } else if helmReleaseExists("", r.Name, "failed") { deleteRelease(r.Name, true) installRelease(s.Namespaces[r.Namespace], r) - } else if helmReleaseExists(nil, r.Name, "all") { // it is deployed but in another namespace + } else if helmReleaseExists("", r.Name, "all") { // it is deployed but in another namespace reInstallRelease(s.Namespaces[r.Namespace], r) From 3f77094a4851b1370e5df1fa89a44690b7594c25 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Fri, 9 Feb 2018 10:57:11 +0100 Subject: [PATCH 0060/1127] enabling connection to clusters using client certificates. --- main.go | 54 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/main.go b/main.go index efb91d2d..5d87c825 100644 --- a/main.go +++ b/main.go @@ -161,17 +161,17 @@ func createContext() (bool, string) { } } - // download certs using AWS cli - if !toolExists("aws help") { - return false, "ERROR: aws is not installed/configured correctly. It is needed for downloading certs. Aborting!" - } - // set certs locations (relative filepath, GCS bucket, AWS bucket) caCrt := s.Certificates["caCrt"] caKey := s.Certificates["caKey"] + caClient := s.Certificates["caClient"] // download certs from AWS (if applicable) if strings.HasPrefix(caCrt, "s3") { + // check AWS exists + if !toolExists("aws help") { + return false, "ERROR: aws is not installed/configured correctly. It is needed for downloading certs. Aborting!" + } cmd := command{ Cmd: "bash", Args: []string{"-c", "aws s3 cp " + caCrt + " ca.crt"}, @@ -186,6 +186,10 @@ func createContext() (bool, string) { } if strings.HasPrefix(caKey, "s3") { + // check AWS exists + if !toolExists("aws help") { + return false, "ERROR: aws is not installed/configured correctly. It is needed for downloading certs. Aborting!" + } cmd := command{ Cmd: "bash", Args: []string{"-c", "aws s3 cp " + caKey + " ca.key"}, @@ -215,11 +219,43 @@ func createContext() (bool, string) { caCrt = "ca.key" } + // client certificate + if caClient != "" { + if strings.HasPrefix(caClient, "s3") { + // check AWS exists + if !toolExists("aws help") { + return false, "ERROR: aws is not installed/configured correctly. It is needed for downloading certs. Aborting!" + } + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "aws s3 cp " + caClient + " client.crt"}, + Description: "downloading caClient.crt from S3.", + } + + if exitCode, _ := cmd.exec(debug); exitCode != 0 { + return false, "ERROR: failed to download caClient." + } + + caClient = "client.crt" + } + + if strings.HasPrefix(caClient, "gs") { + tmp := strings.SplitAfterN(caClient, "//", 2)[1] + gcs.ReadFile(strings.SplitN(tmp, "/", 2)[0], strings.SplitN(tmp, "/", 2)[1], "client.crt") + + caClient = "client.crt" + } + } + // connecting to the cluster + setCredentialsCmd := "kubectl config set-credentials " + s.Settings["username"] + " --username=" + s.Settings["username"] + + " --password=" + password + " --client-key=" + caKey + if caClient != "" { + setCredentialsCmd = setCredentialsCmd + " --client-certificate=" + caClient + } cmd := command{ - Cmd: "bash", - Args: []string{"-c", "kubectl config set-credentials " + s.Settings["username"] + " --username=" + s.Settings["username"] + - " --password=" + password + " --client-key=" + caKey}, + Cmd: "bash", + Args: []string{"-c", setCredentialsCmd}, Description: "creating kubectl context - setting credentials.", } @@ -241,7 +277,7 @@ func createContext() (bool, string) { cmd = command{ Cmd: "bash", Args: []string{"-c", "kubectl config set-context " + s.Settings["kubeContext"] + " --cluster=" + s.Settings["kubeContext"] + - " --user=" + s.Settings["username"] + " --password=" + password}, + " --user=" + s.Settings["username"]}, Description: "creating kubectl context - setting context.", } From ac702c7aa9c3a458b56d7346c0f356c33676c968 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Fri, 9 Feb 2018 11:05:40 +0100 Subject: [PATCH 0061/1127] changing the import of the gcs package to be from github. --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 5d87c825..8760d76b 100644 --- a/main.go +++ b/main.go @@ -4,7 +4,7 @@ import ( "log" "strings" - "Helmsman/gcs" + "github.com/Praqma/helmsman/gcs" ) var s state From 51b5d4d014757e1bf7f3315aca56db09610119f5 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Fri, 9 Feb 2018 11:09:51 +0100 Subject: [PATCH 0062/1127] adding commands to get the gcs package from github in docker images. --- dockerfile/dockerfile | 3 ++- test_files/dockerfile | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile index 8c1408f3..36750ba8 100644 --- a/dockerfile/dockerfile +++ b/dockerfile/dockerfile @@ -1,6 +1,7 @@ FROM golang:1.9 as builder WORKDIR /go/src/ -RUN go get github.com/BurntSushi/toml +RUN go get github.com/BurntSushi/toml +RUN go get github.com/Praqma/helmsman/gcs RUN git clone https://github.com/Praqma/helmsman.git RUN go install helmsman/. diff --git a/test_files/dockerfile b/test_files/dockerfile index 0c4e5bab..fcc82dae 100644 --- a/test_files/dockerfile +++ b/test_files/dockerfile @@ -18,6 +18,7 @@ RUN apk add --update --no-cache ca-certificates git \ && rm -rf /tmp/linux-amd64 RUN go get github.com/BurntSushi/toml \ - && go get github.com/goreleaser/goreleaser + && go get github.com/goreleaser/goreleaser \ + && go get github.com/Praqma/helmsman/gcs WORKDIR src/helmsman \ No newline at end of file From 1ccfcc7feb76d29bfeae0ed1effb9d240a3b56b2 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Fri, 9 Feb 2018 11:46:00 +0100 Subject: [PATCH 0063/1127] updating tests --- state.go | 2 +- state_test.go | 113 ++++++++++++++++++++++++++++++-------------------- 2 files changed, 69 insertions(+), 46 deletions(-) diff --git a/state.go b/state.go index 1a0ee24d..da1b04a1 100644 --- a/state.go +++ b/state.go @@ -32,7 +32,7 @@ func (s state) validate() (bool, string) { s.Settings["password"] = subsituteEnv(s.Settings["password"]) s.Settings["clusterURI"] = subsituteEnv(s.Settings["clusterURI"]) - if s.Settings["password"] == "" || !strings.HasPrefix(s.Settings["password"], "$") { + if s.Settings["password"] == "" { return false, "ERROR: settings validation failed -- password should be set as an env variable. It is currently missing or empty. " } else if _, err := url.ParseRequestURI(s.Settings["clusterURI"]); err != nil { return false, "ERROR: settings validation failed -- clusterURI must have a valid URL set in an env varibale or passed directly. Either the env var is missing/empty or the URL is invalid." diff --git a/state_test.go b/state_test.go index 473a4e50..d4c968f3 100644 --- a/state_test.go +++ b/state_test.go @@ -1,6 +1,9 @@ package main -import "testing" +import ( + "os" + "testing" +) func Test_state_validate(t *testing.T) { type fields struct { @@ -105,7 +108,7 @@ func Test_state_validate(t *testing.T) { }, want: true, }, { - name: "test case 5 -- settings/password", + name: "test case 5 -- settings/password-passed-directly", fields: fields{ Metadata: make(map[string]string), Certificates: map[string]string{ @@ -127,9 +130,33 @@ func Test_state_validate(t *testing.T) { }, Apps: make(map[string]release), }, + want: true, + }, { + name: "test case 6 -- settings/clusterURI-empty-env-var", + fields: fields{ + Metadata: make(map[string]string), + Certificates: map[string]string{ + "caCrt": "s3://some-bucket/12345.crt", + "caKey": "s3://some-bucket/12345.key", + }, + Settings: map[string]string{ + "kubeContext": "minikube", + "username": "admin", + "password": "K8S_PASSWORD", + "clusterURI": "$URI", // unset env + }, + Namespaces: map[string]string{ + "staging": "staging", + }, + HelmRepos: map[string]string{ + "stable": "https://kubernetes-charts.storage.googleapis.com", + "myrepo": "s3://my-repo/charts", + }, + Apps: make(map[string]release), + }, want: false, }, { - name: "test case 6 -- settings/clusterURI", + name: "test case 7 -- settings/clusterURI-env-var", fields: fields{ Metadata: make(map[string]string), Certificates: map[string]string{ @@ -140,7 +167,31 @@ func Test_state_validate(t *testing.T) { "kubeContext": "minikube", "username": "admin", "password": "K8S_PASSWORD", - "clusterURI": "https//192.168.99.100:8443", + "clusterURI": "$SET_URI", // set env var + }, + Namespaces: map[string]string{ + "staging": "staging", + }, + HelmRepos: map[string]string{ + "stable": "https://kubernetes-charts.storage.googleapis.com", + "myrepo": "s3://my-repo/charts", + }, + Apps: make(map[string]release), + }, + want: true, + }, { + name: "test case 8 -- settings/clusterURI-invalid", + fields: fields{ + Metadata: make(map[string]string), + Certificates: map[string]string{ + "caCrt": "s3://some-bucket/12345.crt", + "caKey": "s3://some-bucket/12345.key", + }, + Settings: map[string]string{ + "kubeContext": "minikube", + "username": "admin", + "password": "K8S_PASSWORD", + "clusterURI": "https//192.168.99.100:8443", // invalid url }, Namespaces: map[string]string{ "staging": "staging", @@ -153,7 +204,7 @@ func Test_state_validate(t *testing.T) { }, want: false, }, { - name: "test case 7 -- certifications/missing key", + name: "test case 9 -- certifications/missing key", fields: fields{ Metadata: make(map[string]string), Certificates: map[string]string{ @@ -176,7 +227,7 @@ func Test_state_validate(t *testing.T) { }, want: false, }, { - name: "test case 8 -- certifications/nil_value", + name: "test case 10 -- certifications/nil_value", fields: fields{ Metadata: make(map[string]string), Certificates: nil, @@ -197,7 +248,7 @@ func Test_state_validate(t *testing.T) { }, want: false, }, { - name: "test case 9 -- certifications/invalid_s3", + name: "test case 11 -- certifications/invalid_s3", fields: fields{ Metadata: make(map[string]string), Certificates: map[string]string{ @@ -221,7 +272,7 @@ func Test_state_validate(t *testing.T) { }, want: false, }, { - name: "test case 10 -- certifications/nil_value_pass", + name: "test case 12 -- certifications/nil_value_pass", fields: fields{ Metadata: make(map[string]string), Certificates: nil, @@ -239,7 +290,7 @@ func Test_state_validate(t *testing.T) { }, want: true, }, { - name: "test case 11 -- namespaces/nil_value", + name: "test case 13 -- namespaces/nil_value", fields: fields{ Metadata: make(map[string]string), Certificates: nil, @@ -255,7 +306,7 @@ func Test_state_validate(t *testing.T) { }, want: false, }, { - name: "test case 12 -- namespaces/empty", + name: "test case 14 -- namespaces/empty", fields: fields{ Metadata: make(map[string]string), Certificates: nil, @@ -271,7 +322,7 @@ func Test_state_validate(t *testing.T) { }, want: false, }, { - name: "test case 13 -- namespaces/empty_namespace_value", + name: "test case 15 -- namespaces/empty_namespace_value", fields: fields{ Metadata: make(map[string]string), Certificates: nil, @@ -291,7 +342,7 @@ func Test_state_validate(t *testing.T) { }, want: false, }, { - name: "test case 14 -- helmRepos/nil_value", + name: "test case 16 -- helmRepos/nil_value", fields: fields{ Metadata: make(map[string]string), Certificates: nil, @@ -306,7 +357,7 @@ func Test_state_validate(t *testing.T) { }, want: false, }, { - name: "test case 15 -- helmRepos/empty", + name: "test case 17 -- helmRepos/empty", fields: fields{ Metadata: make(map[string]string), Certificates: nil, @@ -321,7 +372,7 @@ func Test_state_validate(t *testing.T) { }, want: false, }, { - name: "test case 16 -- helmRepos/empty_repo_value", + name: "test case 18 -- helmRepos/empty_repo_value", fields: fields{ Metadata: make(map[string]string), Certificates: nil, @@ -339,7 +390,7 @@ func Test_state_validate(t *testing.T) { }, want: false, }, { - name: "test case 17 -- helmRepos/invalid_repo_value", + name: "test case 19 -- helmRepos/invalid_repo_value", fields: fields{ Metadata: make(map[string]string), Certificates: nil, @@ -358,6 +409,8 @@ func Test_state_validate(t *testing.T) { want: false, }, } + os.Setenv("K8S_PASSWORD", "my-fake-password") + os.Setenv("SET_URI", "https://192.168.99.100:8443") for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { s := state{ @@ -374,33 +427,3 @@ func Test_state_validate(t *testing.T) { }) } } - -// func Test_state_print(t *testing.T) { -// type fields struct { -// Metadata map[string]string -// Certifications map[string]string -// Settings map[string]string -// Namespaces map[string]string -// HelmRepos map[string]string -// Apps map[string]release -// } -// tests := []struct { -// name string -// fields fields -// }{ -// // TODO: Add test cases. -// } -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// s := state{ -// Metadata: tt.fields.Metadata, -// Certifications: tt.fields.Certifications, -// Settings: tt.fields.Settings, -// Namespaces: tt.fields.Namespaces, -// HelmRepos: tt.fields.HelmRepos, -// Apps: tt.fields.Apps, -// } -// s.print() -// }) -// } -// } From 03242e233d9f701568ff684266463b81ecc40b0e Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Fri, 9 Feb 2018 13:18:31 +0100 Subject: [PATCH 0064/1127] adding a check verifying that app desired namespaces are defined in the desired state file. --- decision_maker.go | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index 7e83a9a3..6a590784 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -27,26 +27,26 @@ func decide(r release, s *state) { inspectDeleteScenario(s.Namespaces[r.Namespace], r) } else { // check for install/upgrade/rollback - if helmReleaseExists(s.Namespaces[r.Namespace], r.Name, "deployed") { + if helmReleaseExists(getDesiredNamespaces(r), r.Name, "deployed") { inspectUpgradeScenario(s.Namespaces[r.Namespace], r) } else if helmReleaseExists("", r.Name, "deleted") { - inspectRollbackScenario(s.Namespaces[r.Namespace], r) + inspectRollbackScenario(getDesiredNamespaces(r), r) } else if helmReleaseExists("", r.Name, "failed") { deleteRelease(r.Name, true) - installRelease(s.Namespaces[r.Namespace], r) + installRelease(getDesiredNamespaces(r), r) } else if helmReleaseExists("", r.Name, "all") { // it is deployed but in another namespace - reInstallRelease(s.Namespaces[r.Namespace], r) + reInstallRelease(getDesiredNamespaces(r), r) } else { - installRelease(s.Namespaces[r.Namespace], r) + installRelease(getDesiredNamespaces(r), r) } @@ -263,3 +263,15 @@ func getSetValues(r release) string { } return result } + +// getDesiredNamespaces validates that namespace where the release is desired to be installed is defined in the Namespaces definition +// it returns the namespace if it is already defined +// otherwise, it throws an error +func getDesiredNamespaces(r release) string { + value, ok := s.Namespaces[r.Namespace] + if !ok { + log.Fatal("ERROR: " + r.Namespace + " is not defined in the Namespaces section of your desired state file. Release [ " + r.Name + + " ] can't be installed in that Namespace until its defined in your desired state file.") + } + return value +} From cc6a53ef33901ded0c7594588b734e377cd86333 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Fri, 9 Feb 2018 13:19:58 +0100 Subject: [PATCH 0065/1127] checking that a namespace is not empty when searching for deployed helm releases. --- helm_helpers.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/helm_helpers.go b/helm_helpers.go index b26eb57d..8533856f 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -18,8 +18,10 @@ func helmReleaseExists(namespace string, releaseName string, scope string) bool options = "--all -q" } else if scope == "deleted" { options = "--deleted -q" - } else if scope == "deployed" { + } else if scope == "deployed" && namespace != "" { options = "--deployed -q --namespace " + namespace + } else if scope == "deployed" && namespace == "" { + options = "--deployed -q " } else if scope == "failed" { options = "--failed -q" } else { From c1ce54ea82b4c0fe1204fc157f5c94e63020e6f6 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Fri, 9 Feb 2018 13:21:27 +0100 Subject: [PATCH 0066/1127] fixed a certification validation condition. --- state.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/state.go b/state.go index da1b04a1..91bbdec9 100644 --- a/state.go +++ b/state.go @@ -41,7 +41,7 @@ func (s state) validate() (bool, string) { // certificates if s.Certificates != nil { - if len(s.Settings) > 1 && len(s.Certificates) != 2 { + if len(s.Settings) > 1 && len(s.Certificates) < 2 { return false, "ERROR: certifications validation failed -- You want me to connect to your cluster for you " + "but have not given me the keys to do so. Please add [caCrt] and [caKey] under Certifications. You might also need to provide [clientCrt]." } From e1e03ae70bf4c1a9be0684bdfe1ce679d07a33dc Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Fri, 9 Feb 2018 13:23:14 +0100 Subject: [PATCH 0067/1127] adding log messages when fetching certificates from GCS or S3 --- main.go | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/main.go b/main.go index 8760d76b..9209451f 100644 --- a/main.go +++ b/main.go @@ -145,20 +145,12 @@ func addNamespaces(namespaces map[string]string) { // It returns true if successful, false otherwise func createContext() (bool, string) { - var password string - var ok bool - if s.Settings["password"] == "" || s.Settings["username"] == "" || s.Settings["clusterURI"] == "" { return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ] " + "as you did not specify enough information in the Settings section of your desired state file." } else if s.Certificates == nil || s.Certificates["caCrt"] == "" || s.Certificates["caKey"] == "" { return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ] " + "as you did not provide Certifications to use in your desired state file." - } else { - ok, password = envVarExists(s.Settings["password"]) - if !ok { - return false, "ERROR: env variable [ " + s.Settings["password"] + " ] does not exist in the environment." - } } // set certs locations (relative filepath, GCS bucket, AWS bucket) @@ -182,6 +174,7 @@ func createContext() (bool, string) { return false, "ERROR: failed to download caCrt." } + log.Println("INFO: downloaded certificate authority ca.crt from S3.") caCrt = "ca.crt" } @@ -200,6 +193,7 @@ func createContext() (bool, string) { return false, "ERROR: failed to download caKey." } + log.Println("INFO: downloaded ca.key from S3.") caKey = "ca.key" } @@ -209,6 +203,7 @@ func createContext() (bool, string) { tmp := strings.SplitAfterN(caCrt, "//", 2)[1] gcs.ReadFile(strings.SplitN(tmp, "/", 2)[0], strings.SplitN(tmp, "/", 2)[1], "ca.crt") + log.Println("INFO: downloaded certificate authority ca.crt from GCS.") caCrt = "ca.crt" } @@ -216,7 +211,8 @@ func createContext() (bool, string) { tmp := strings.SplitAfterN(caKey, "//", 2)[1] gcs.ReadFile(strings.SplitN(tmp, "/", 2)[0], strings.SplitN(tmp, "/", 2)[1], "ca.key") - caCrt = "ca.key" + log.Println("INFO: downloaded ca.key from GCS.") + caKey = "ca.key" } // client certificate @@ -236,20 +232,23 @@ func createContext() (bool, string) { return false, "ERROR: failed to download caClient." } + log.Println("INFO: Client certificate downloaded from S3.") caClient = "client.crt" - } - if strings.HasPrefix(caClient, "gs") { + } else if strings.HasPrefix(caClient, "gs") { tmp := strings.SplitAfterN(caClient, "//", 2)[1] gcs.ReadFile(strings.SplitN(tmp, "/", 2)[0], strings.SplitN(tmp, "/", 2)[1], "client.crt") - + log.Println("INFO: Client certificate downloaded from GCS.") caClient = "client.crt" + + } else { + log.Println("INFO: Client certificate will be used from local file system.") } } // connecting to the cluster setCredentialsCmd := "kubectl config set-credentials " + s.Settings["username"] + " --username=" + s.Settings["username"] + - " --password=" + password + " --client-key=" + caKey + " --password=" + s.Settings["password"] + " --client-key=" + caKey if caClient != "" { setCredentialsCmd = setCredentialsCmd + " --client-certificate=" + caClient } From 5284e4eb4741359d628878a9e41a7915f98bbfc9 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 10 Feb 2018 06:02:07 +0100 Subject: [PATCH 0068/1127] adding log messages on successful downloads. --- gcs/gcs.go | 1 + 1 file changed, 1 insertion(+) diff --git a/gcs/gcs.go b/gcs/gcs.go index e7420f2b..87fde9ef 100644 --- a/gcs/gcs.go +++ b/gcs/gcs.go @@ -76,4 +76,5 @@ func ReadFile(bucketName string, filename string, outFile string) { if _, err := io.Copy(dest, r); err != nil { log.Fatalf("Failed to read object content: %v", err) } + log.Println("INFO: Successfully downloaded " + filename + " from GCS as " + outFile) } From f6240b784c1a10fea2fb4f203f6c73ccd2df37fd Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 10 Feb 2018 06:03:22 +0100 Subject: [PATCH 0069/1127] fixing go dependencies needed for build and upgrading the alpine image to 3.7. --- dockerfile/dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile index 36750ba8..580e6b02 100644 --- a/dockerfile/dockerfile +++ b/dockerfile/dockerfile @@ -1,8 +1,8 @@ FROM golang:1.9 as builder WORKDIR /go/src/ RUN go get github.com/BurntSushi/toml -RUN go get github.com/Praqma/helmsman/gcs RUN git clone https://github.com/Praqma/helmsman.git +RUN go get github.com/Praqma/helmsman/gcs RUN go install helmsman/. FROM alpine:3.6 as kube @@ -14,7 +14,7 @@ RUN curl -L https://storage.googleapis.com/kubernetes-release/release/${KUBE_LAT RUN chmod +x /usr/local/bin/kubectl # The image to keep -FROM alpine:3.6 +FROM alpine:3.7 RUN apk add --update --no-cache ca-certificates git ENV HELM_VERSION v2.7.0 From c69c69e8136514c2d95a7d888fe1f400003daf22 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 10 Feb 2018 06:03:51 +0100 Subject: [PATCH 0070/1127] adding aws package for S3 operations. --- aws/aws.go | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 aws/aws.go diff --git a/aws/aws.go b/aws/aws.go new file mode 100644 index 00000000..7838ea8e --- /dev/null +++ b/aws/aws.go @@ -0,0 +1,55 @@ +package aws + +import ( + "log" + "os" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/s3" + "github.com/aws/aws-sdk-go/service/s3/s3manager" +) + +func checkCredentialsEnvVar() bool { + + if os.Getenv("AWS_ACCESS_KEY_ID") == "" || os.Getenv("AWS_SECRET_ACCESS_KEY") == "" || os.Getenv("AWS_REGION") == "" { + return false + } + return true +} + +// ReadFile reads a file from S3 bucket and saves it in a desired location. +func ReadFile(bucketName string, filename string, outFile string) { + // Checking env vars are set to configure AWS + if !checkCredentialsEnvVar() { + log.Fatal("Failed to find the AWS env vars needed to configure AWS. Please make sure they are set in the environment.") + } + + // Create Session -- use config (credentials + region) from env vars or aws profile + sess, err := session.NewSession() + + if err != nil { + log.Fatal("ERROR: Can't create AWS session: " + err.Error()) + } + // create S3 download manger + downloader := s3manager.NewDownloader(sess) + + file, err := os.Create(outFile) + if err != nil { + log.Fatal("ERROR: Failed to open file " + outFile + ": " + err.Error()) + } + + defer file.Close() + + _, err = downloader.Download(file, + &s3.GetObjectInput{ + Bucket: aws.String(bucketName), + Key: aws.String(filename), + }) + if err != nil { + log.Fatal("ERROR: Failed to download file " + filename + " from S3: " + err.Error()) + } + + log.Println("INFO: Successfully downloaded " + filename + " from S3 as " + outFile) + +} From 395068f6d364c4643e69756bf14add287db651ac Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 10 Feb 2018 07:25:42 +0100 Subject: [PATCH 0071/1127] removing json encoding as it is not needed and causes formatting probelms. --- gcs/gcs.go | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/gcs/gcs.go b/gcs/gcs.go index 87fde9ef..f396d7a8 100644 --- a/gcs/gcs.go +++ b/gcs/gcs.go @@ -1,8 +1,6 @@ package gcs import ( - "bytes" - "encoding/json" "io" "io/ioutil" "log" @@ -16,23 +14,12 @@ import ( func checkCredentialsEnvVar() bool { if os.Getenv("GCLOUD_CREDENTIALS") != "" { // write the credentials content into a json file - file, err := os.Create("/tmp/credentials.json") + d := []byte(os.Getenv("GCLOUD_CREDENTIALS")) + err := ioutil.WriteFile("/tmp/credentials.json", d, 0644) + if err != nil { log.Fatal("ERROR: Cannot create credentials file: ", err) - } else { - // making sure special characters are not escaped - var buf bytes.Buffer - enc := json.NewEncoder(&buf) - enc.SetEscapeHTML(false) - enc.Encode(os.Getenv("GCLOUD_CREDENTIALS")) - - // writing the credentials content to a file - err := ioutil.WriteFile(file.Name(), buf.Bytes(), 0644) - if err != nil { - log.Fatal("ERROR: Cannot write the credentials file: ", err) - } } - defer file.Close() os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "/tmp/credentials.json") return true From 02be7d116780acd61acdead0d656f2ecf00051aa Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 10 Feb 2018 07:27:07 +0100 Subject: [PATCH 0072/1127] updating certs downloading to use the aws package instead of AWS CLI --- main.go | 114 +++++++++++++++++++++++++------------------------------- 1 file changed, 51 insertions(+), 63 deletions(-) diff --git a/main.go b/main.go index 9209451f..6044f1c0 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "log" "strings" + "github.com/Praqma/helmsman/aws" "github.com/Praqma/helmsman/gcs" ) @@ -158,92 +159,67 @@ func createContext() (bool, string) { caKey := s.Certificates["caKey"] caClient := s.Certificates["caClient"] - // download certs from AWS (if applicable) - if strings.HasPrefix(caCrt, "s3") { - // check AWS exists - if !toolExists("aws help") { - return false, "ERROR: aws is not installed/configured correctly. It is needed for downloading certs. Aborting!" - } - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "aws s3 cp " + caCrt + " ca.crt"}, - Description: "downloading ca.crt from S3.", - } + // download certs and keys + // GCS bucket+file format should be: gs://bucket-name/dir.../filename.ext + // S3 bucket+file format should be: s3://bucket-name/dir.../filename.ext - if exitCode, _ := cmd.exec(debug); exitCode != 0 { - return false, "ERROR: failed to download caCrt." - } + // CA cert + if caCrt != "" { - log.Println("INFO: downloaded certificate authority ca.crt from S3.") - caCrt = "ca.crt" - } + tmp := getBucketElements(caCrt) + if strings.HasPrefix(caCrt, "s3") { - if strings.HasPrefix(caKey, "s3") { - // check AWS exists - if !toolExists("aws help") { - return false, "ERROR: aws is not installed/configured correctly. It is needed for downloading certs. Aborting!" - } - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "aws s3 cp " + caKey + " ca.key"}, - Description: "downloading ca.key from S3.", - } + aws.ReadFile(tmp["bucketName"], tmp["filePath"], "ca.crt") + caCrt = "ca.crt" - if exitCode, _ := cmd.exec(debug); exitCode != 0 { - return false, "ERROR: failed to download caKey." + } else if strings.HasPrefix(caCrt, "gs") { + + gcs.ReadFile(tmp["bucketName"], tmp["filePath"], "ca.crt") + caCrt = "ca.crt" + + } else { + log.Println("INFO: CA certificate will be used from local file system.") } - log.Println("INFO: downloaded ca.key from S3.") - caKey = "ca.key" } - // download certs from GCS (if applicable) - // GCS bucket+file format should be: gs://bucket-name/file-name.extension - if strings.HasPrefix(caCrt, "gs") { - tmp := strings.SplitAfterN(caCrt, "//", 2)[1] - gcs.ReadFile(strings.SplitN(tmp, "/", 2)[0], strings.SplitN(tmp, "/", 2)[1], "ca.crt") + // CA key + if caKey != "" { - log.Println("INFO: downloaded certificate authority ca.crt from GCS.") - caCrt = "ca.crt" - } + tmp := getBucketElements(caKey) + if strings.HasPrefix(caKey, "s3") { + + aws.ReadFile(tmp["bucketName"], tmp["filePath"], "ca.key") + caKey = "ca.key" + + } else if strings.HasPrefix(caKey, "gs") { - if strings.HasPrefix(caKey, "gs") { - tmp := strings.SplitAfterN(caKey, "//", 2)[1] - gcs.ReadFile(strings.SplitN(tmp, "/", 2)[0], strings.SplitN(tmp, "/", 2)[1], "ca.key") + gcs.ReadFile(tmp["bucketName"], tmp["filePath"], "ca.key") + caKey = "ca.key" - log.Println("INFO: downloaded ca.key from GCS.") - caKey = "ca.key" + } else { + log.Println("INFO: CA key will be used from local file system.") + } } // client certificate if caClient != "" { + + tmp := getBucketElements(caClient) if strings.HasPrefix(caClient, "s3") { - // check AWS exists - if !toolExists("aws help") { - return false, "ERROR: aws is not installed/configured correctly. It is needed for downloading certs. Aborting!" - } - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "aws s3 cp " + caClient + " client.crt"}, - Description: "downloading caClient.crt from S3.", - } - - if exitCode, _ := cmd.exec(debug); exitCode != 0 { - return false, "ERROR: failed to download caClient." - } - - log.Println("INFO: Client certificate downloaded from S3.") + + aws.ReadFile(tmp["bucketName"], tmp["filePath"], "client.crt") caClient = "client.crt" } else if strings.HasPrefix(caClient, "gs") { - tmp := strings.SplitAfterN(caClient, "//", 2)[1] - gcs.ReadFile(strings.SplitN(tmp, "/", 2)[0], strings.SplitN(tmp, "/", 2)[1], "client.crt") - log.Println("INFO: Client certificate downloaded from GCS.") + + gcs.ReadFile(tmp["bucketName"], tmp["filePath"], "client.crt") caClient = "client.crt" } else { - log.Println("INFO: Client certificate will be used from local file system.") + log.Println("INFO: CA client key will be used from local file system.") } + } // connecting to the cluster @@ -290,3 +266,15 @@ func createContext() (bool, string) { return false, "ERROR: something went wrong while setting the kube context to the newly created one." } + +// getBucketElements returns a map containing the bucket name and the file path inside the bucket +// this func works for S3 and GCS bucket links of the format: +// s3 or gs://bucketname/dir.../file.ext +func getBucketElements(link string) map[string]string { + + tmp := strings.SplitAfterN(link, "//", 2)[1] + m := make(map[string]string) + m["bucketName"] = strings.SplitN(tmp, "/", 2)[0] + m["filePath"] = strings.SplitN(tmp, "/", 2)[1] + return m +} From 2e85113208caca98f23b6e49fcf18f67cf04b288 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 10 Feb 2018 08:10:43 +0100 Subject: [PATCH 0073/1127] changing auth function to an exported function. --- gcs/gcs.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/gcs/gcs.go b/gcs/gcs.go index f396d7a8..6e30e643 100644 --- a/gcs/gcs.go +++ b/gcs/gcs.go @@ -11,7 +11,15 @@ import ( "golang.org/x/net/context" ) -func checkCredentialsEnvVar() bool { +// Auth checks for GCLOUD_CREDENTIALS in the environment +// returns true if they exist and creates a json credentials file and sets the GOOGLE_APPLICATION_CREDENTIALS env var +// returns false if credentials are not found +func Auth() bool { + if os.Getenv("GOOGLE_APPLICATION_CREDENTIALS") != "" { + log.Println("INFO: GOOGLE_APPLICATION_CREDENTIALS is already set in the environment.") + return true + } + if os.Getenv("GCLOUD_CREDENTIALS") != "" { // write the credentials content into a json file d := []byte(os.Getenv("GCLOUD_CREDENTIALS")) @@ -29,7 +37,7 @@ func checkCredentialsEnvVar() bool { // ReadFile reads a file from storage bucket and saves it in a desired location. func ReadFile(bucketName string, filename string, outFile string) { - if !checkCredentialsEnvVar() { + if !Auth() { log.Fatal("Failed to find the GCLOUD_CREDENTIALS env var. Please make sure it is set in the environment.") } From 879e379a757a7539c359a1152d9a79b41abb1028 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 10 Feb 2018 08:17:47 +0100 Subject: [PATCH 0074/1127] adding the aws package. --- test_files/dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test_files/dockerfile b/test_files/dockerfile index fcc82dae..80bb2da0 100644 --- a/test_files/dockerfile +++ b/test_files/dockerfile @@ -19,6 +19,7 @@ RUN apk add --update --no-cache ca-certificates git \ RUN go get github.com/BurntSushi/toml \ && go get github.com/goreleaser/goreleaser \ - && go get github.com/Praqma/helmsman/gcs + && go get github.com/Praqma/helmsman/gcs \ + && go get github.com/Praqma/helmsman/aws WORKDIR src/helmsman \ No newline at end of file From ad685bd42f90c7d2a0714cfee2b67f9b5d6a73cb Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 10 Feb 2018 08:19:00 +0100 Subject: [PATCH 0075/1127] adding the aws package, rmoving aws cli and adding helm-gcs plugin. --- dockerfile/dockerfile | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile index 580e6b02..61c7b228 100644 --- a/dockerfile/dockerfile +++ b/dockerfile/dockerfile @@ -2,7 +2,8 @@ FROM golang:1.9 as builder WORKDIR /go/src/ RUN go get github.com/BurntSushi/toml RUN git clone https://github.com/Praqma/helmsman.git -RUN go get github.com/Praqma/helmsman/gcs +RUN go get github.com/Praqma/helmsman/gcs +RUN go get github.com/Praqma/helmsman/aws RUN go install helmsman/. FROM alpine:3.6 as kube @@ -18,12 +19,8 @@ FROM alpine:3.7 RUN apk add --update --no-cache ca-certificates git ENV HELM_VERSION v2.7.0 -# Versions: https://pypi.python.org/pypi/awscli#downloads -ENV AWS_CLI_VERSION 1.11.131 RUN apk --no-cache update \ - && apk --no-cache add python py-pip py-setuptools ca-certificates groff less \ - && pip --no-cache-dir install awscli==${AWS_CLI_VERSION} \ && rm -rf /var/cache/apk/* \ && apk add --update -t deps curl tar gzip make bash \ && curl -L http://storage.googleapis.com/kubernetes-helm/helm-${HELM_VERSION}-linux-amd64.tar.gz | tar zxv -C /tmp \ @@ -31,6 +28,7 @@ RUN apk --no-cache update \ && chmod +x /usr/local/bin/helm \ && mkdir -p ~/.helm/plugins \ && helm plugin install https://github.com/hypnoglow/helm-s3.git --version 0.4.0 \ + && helm plugin install https://github.com/nouney/helm-gcs \ && rm -rf /tmp/linux-amd64 COPY --from=kube /usr/local/bin/kubectl /bin/kubectl From 8886ac2c6a3513db742f4851f4fea60f29f980e1 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 10 Feb 2018 08:48:35 +0100 Subject: [PATCH 0076/1127] enabling the use of private GCS helm repos. --- main.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/main.go b/main.go index 6044f1c0..ce458d0b 100644 --- a/main.go +++ b/main.go @@ -90,6 +90,11 @@ func initHelm() (bool, string) { func addHelmRepos(repos map[string]string) (bool, string) { for repoName, url := range repos { + // check if repo is in GCS, then perform GCS auth -- needed for private GCS helm repos + // failed auth would not throw an error here, as it is possible that the repo is public and does not need authentication + if strings.HasPrefix(url, "gs://") { + gcs.Auth() + } cmd := command{ Cmd: "bash", Args: []string{"-c", "helm repo add " + repoName + " " + url}, From 39ddc3083f63bca72c6133a1f3ed49ee821dd764 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 10 Feb 2018 08:52:17 +0100 Subject: [PATCH 0077/1127] moving dependency fetching to the circleci build instead of the docker image. --- .circleci/build.sh | 4 ++++ test_files/dockerfile | 4 +--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.circleci/build.sh b/.circleci/build.sh index dc5acd25..714415ae 100755 --- a/.circleci/build.sh +++ b/.circleci/build.sh @@ -1,4 +1,8 @@ #!/bin/bash -x +echo "fetching dependencies ..." +go get github.com/Praqma/helmsman/gcs +go get github.com/Praqma/helmsman/aws + echo "running tests ..." go test -v diff --git a/test_files/dockerfile b/test_files/dockerfile index 80bb2da0..0c4e5bab 100644 --- a/test_files/dockerfile +++ b/test_files/dockerfile @@ -18,8 +18,6 @@ RUN apk add --update --no-cache ca-certificates git \ && rm -rf /tmp/linux-amd64 RUN go get github.com/BurntSushi/toml \ - && go get github.com/goreleaser/goreleaser \ - && go get github.com/Praqma/helmsman/gcs \ - && go get github.com/Praqma/helmsman/aws + && go get github.com/goreleaser/goreleaser WORKDIR src/helmsman \ No newline at end of file From fab31f8d5e53e37aa3e4ee8d6f72e8294bf5aef2 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Mon, 12 Feb 2018 12:53:14 +0000 Subject: [PATCH 0078/1127] making helmsman's binary statically linked to work with stripped down linux distributions. --- dockerfile/dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile index 61c7b228..ca17ee0c 100644 --- a/dockerfile/dockerfile +++ b/dockerfile/dockerfile @@ -4,7 +4,8 @@ RUN go get github.com/BurntSushi/toml RUN git clone https://github.com/Praqma/helmsman.git RUN go get github.com/Praqma/helmsman/gcs RUN go get github.com/Praqma/helmsman/aws -RUN go install helmsman/. +# build a statically linked binary so that it works on stripped linux images such as alpine/busybox. +RUN CGO_ENABLED=0 GOOS=linux go install -a -ldflags '-extldflags "-static"' helmsman/. FROM alpine:3.6 as kube ENV KUBE_LATEST_VERSION="v1.8.2" From d50e2ff181e6cb1a89b6817423ce8c73c86f1d6d Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Mon, 12 Feb 2018 13:39:56 +0000 Subject: [PATCH 0079/1127] fixing a bug in env vars substitution while validating certs. --- state.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/state.go b/state.go index 91bbdec9..a74cb707 100644 --- a/state.go +++ b/state.go @@ -46,12 +46,13 @@ func (s state) validate() (bool, string) { "but have not given me the keys to do so. Please add [caCrt] and [caKey] under Certifications. You might also need to provide [clientCrt]." } for key, value := range s.Certificates { - value = subsituteEnv(value) - _, err1 := url.ParseRequestURI(value) - _, err2 := os.Stat(value) - if (err1 != nil || (!strings.HasPrefix(value, "s3://") && !strings.HasPrefix(value, "gs://"))) && err2 != nil { + tmp := subsituteEnv(value) + _, err1 := url.ParseRequestURI(tmp) + _, err2 := os.Stat(tmp) + if (err1 != nil || (!strings.HasPrefix(tmp, "s3://") && !strings.HasPrefix(tmp, "gs://"))) && err2 != nil { return false, "ERROR: certifications validation failed -- [ " + key + " ] must be a valid S3 or GCS bucket URL or a valid relative file path." } + s.Certificates[key] = tmp } } else { From 292a16254000def2a7be53a48f5568d28850d000 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Fri, 23 Feb 2018 08:39:35 +0000 Subject: [PATCH 0080/1127] updating release notes. --- release-notes.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/release-notes.md b/release-notes.md index 84dea200..9654409a 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,7 +1,10 @@ -# v0.1.3 +# v0.2.0 -- Bug fixes. -- Support for passing user input values to charts from environment variables. Such values either override values from values.yaml or add to them. This is particularily useful for passing secrets from environment variables to helm charts. -- Tests no longer run on upgrade and rollback operations. This is because some charts does not use unique generated names for the tests and Helm does not manage the tests. In some cases, tests would fail due to having k8s resources with the same names existing from a previous run. -- Support for use of certificates and keys for cluster connection from the local file system. YOU SHOULD NEVER commit your certificates to git! This update is useful while testing on a local machine. -- The key 'env' for release namespaces in the desired state file is now changed to the more sensible 'namespace'. \ No newline at end of file +- Support reading cluster certificates from Google Cloud Storage (GCS). +- Supporting private helm repos in GCS. +- Support using client-certificates in the cluster authentication. +- Adding a warning about PV and PVCs possible issues when moving apps across namespaces. +- Allowing certs file paths or bucket URLs and clusterURI to be passed from environment variables. +- Fixing a bug with undefined namespaces. +- Removing the aws cli dependency. +- Smaller docker image. \ No newline at end of file From 8279468ff1e6f0fc0bd109eb614dae9d38951adb Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Fri, 23 Feb 2018 08:40:04 +0000 Subject: [PATCH 0081/1127] updating example.toml --- example.toml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/example.toml b/example.toml index 6b325f6e..18d77051 100644 --- a/example.toml +++ b/example.toml @@ -6,17 +6,18 @@ maintainer = "k8s-admin" # paths to the certificate for connecting to the cluster # You can skip this if you use Helmsman on a machine with kubectl already connected to your k8s cluster. -# you have to use exact key names here : 'caCrt' for certificate and 'caKey' for the key +# you have to use exact key names here : 'caCrt' for certificate and 'caKey' for the key and caClient for the client certififcate [certificates] -# caCrt = "s3://mybucket/ca.crt" # s3 bucket path, or valid local file relative path -# caKey = "../ca.key" +#caClient = "gs://mybucket/client.crt" # GCS bucket path +#caCrt = "s3://mybucket/ca.crt" # S3 bucket path +#caKey = "../ca.key" # valid local file relative path [settings] kubeContext = "minikube" # will try connect to this context first, if it does not exist, it will be created using the details below -# username = "admin" -# password = "$PASSWORD" # the name of an environment variable containing the k8s password -# clusterURI = "https://192.168.99.100:8443" # cluster API - +#username = "admin" +#password = "$K8S_PASSWORD" # the name of an environment variable containing the k8s password +#clusterURI = "$K8S_URI" # the name of an environment variable containing the cluster API +##clusterURI = "https://192.168.99.100:8443" # equivalent to the above # define your environments and thier k8s namespaces # syntax: environment_name = "k8s_namespace" @@ -31,7 +32,8 @@ production = "default" [helmRepos] stable = "https://kubernetes-charts.storage.googleapis.com" incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" -#myrepo = "s3://my-private-repo/charts" +#myS3repo = "s3://my-S3-private-repo/charts" +#myGCSrepo = "gs://my-GCS-private-repo/charts" # define the desired state of your applications helm charts # each contains the following: From b8153b49210be50706301ea1ddf8022cda2b13f8 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Fri, 23 Feb 2018 09:00:24 +0000 Subject: [PATCH 0082/1127] updating circleci config to build all tags. --- .circleci/config.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index dbbea78a..70666425 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,6 +4,9 @@ version: 2 jobs: build: + filters: + tags: + only: /.*/ docker: # specify the version - image: praqma/helmsman-test From 29f37f3e5045f52927e1c701a2d740fe26493c04 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Fri, 23 Feb 2018 11:29:26 +0000 Subject: [PATCH 0083/1127] updating documentation for version v0.2.0 --- README.md | 4 +- docs/desired_state_specification.md | 41 +++++++---- docs/how_to/define_namespaces.md | 2 +- docs/how_to/manipulate_apps.md | 4 +- docs/how_to/move_charts_across_namespaces.md | 65 +++++++++++++++++- .../how_to/pass_secrets_from_env_variables.md | 2 +- docs/how_to/run_helmsman_in_ci.md | 2 +- .../run_helmsman_with_hosted_cluster.md | 44 +++++++++--- docs/how_to/run_helmsman_with_minikube.md | 2 +- docs/how_to/test_charts.md | 2 +- docs/how_to/use_local_charts.md | 2 +- docs/how_to/use_private_helm_charts.md | 20 ++++-- docs/images/helmsman.png | Bin 0 -> 218217 bytes 13 files changed, 153 insertions(+), 37 deletions(-) create mode 100644 docs/images/helmsman.png diff --git a/README.md b/README.md index db98c1db..15edf369 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ --- -version: v0.1.3 +version: v0.2.0 --- +![helmsman-logo](docs/images/helmsman.png) + # What is Helmsman? Helmsman is a Helm Charts (k8s applications) as Code tool which adds a layer of abstraction on top of [Helm](https://helm.sh) (the [Kubernetes](https://kubernetes.io/) package manager). It allows you to automate the deployment/management of your Helm charts. diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 7432b5a5..f2a79bf0 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -1,5 +1,5 @@ --- -version: v0.1.2 +version: v0.2.0 --- # Helmsman desired state specification @@ -32,20 +32,27 @@ maintainer = "k8s-admin" ## Certificates -Optional : Yes, if you don't want Helmsman to connect kubectl to your cluster for you. +Optional : Yes, only needed if you want Helmsman to connect kubectl to your cluster for you. -Synopsis: defines where to find the certifactions needed for connecting kubectl to a k8s cluster. +Synopsis: defines where to find the certifactions needed for connecting kubectl to a k8s cluster. If connection settings (username/password/clusterAPI) are provided in the Settings section below, then you need AT LEAST to provide caCrt and caKey. You can optionally provide a client certificate (caClient) depending on your cluster connection setup. Options: -- caCrt : a valid s3 bucket to a CRT file. -- caKey : a valid s3 bucket to a key file. +- caCrt : a valid S3/GCS bucket or local relative file path to a certificate file. +- caKey : a valid S3/GCS bucket or local relative file path to a client key file. +- caClient: a valid S3/GCS bucket or local relative file path to a client certificate file. + +> You can use environment variables to pass the values of the options above. The environment variable name should start with $ + +> bucket format is: ://bucket-name/dir1/dir2/.../file.extension Example: ``` [certificates] -caCrt = "s3://mybucket/ca.crt" -caKey = "s3://mybucket/ca.key" +caCrt = "s3://myS3bucket/mydir/ca.crt" +caKey = "gs://myGCSbucket/ca.key" +caClient ="../path/to/my/local/client-certificate.crt" +#caClient = "$CA_CLIENT" ``` ## Settings @@ -60,8 +67,8 @@ Options: The following options can be skipped if your kubectl context is already created and you don't want Helmsman to connect kubectl to your cluster for you. When using Helmsman in CI pipeline, these details are required to connect to your cluster everytime the pipeline is executed. - username : the username to be used for kubectl credentials. -- password : an environment variable name (starting with `$`) where your password is stored. Get the password from your k8s admin or consult k8s docs on how to get it. -- clusterURI : the URI for your cluster API. +- password : an environment variable name (starting with `$`) where your password is stored. Get the password from your k8s admin or consult k8s docs on how to get/set it. +- clusterURI : the URI for your cluster API or an environment variable containing the URI. Example: @@ -69,8 +76,9 @@ Example: [settings] kubeContext = "minikube" # username = "admin" -# password = "$PASSWORD" +# password = "$K8S_PASSWORD" # clusterURI = "https://192.168.99.100:8443" +## clusterURI= "$K8S_URI" ``` ## Namespaces @@ -95,9 +103,15 @@ production = "default" Optional : No. -Purpose: defines the Helm repos where your charts can be found. You can add as many repos as you like. Public repos do not require authentication. Private repos require authentication. +Purpose: defines the Helm repos where your charts can be found. You can add as many repos as you like. Public repos can be added without any additional setup. Private repos require authentication. + +> AS of version v0.2.0, both AWS S3 and Google GCS buckets can be used for private repos (using the [Helm S3](https://github.com/hypnoglow/helm-s3) and [Helm GCS](https://github.com/nouney/helm-gcs) plugins). -> AS of version v0.1.2, only AWS S3 buckets can be used for private repos (using the [Helm S3 plugin](https://github.com/hypnoglow/helm-s3)). For that you need to have valid AWS access keys in your environment variables. See [here](https://github.com/hypnoglow/helm-s3#note-on-aws-authentication) for more details. +Authenticating to private helm repos: +- **For S3 repos**: you need to have valid AWS access keys in your environment variables. See [here](https://github.com/hypnoglow/helm-s3#note-on-aws-authentication) for more details. +- **For GCS repos**: check [here](https://www.terraform.io/docs/providers/google/index.html#authentication-json-file) for getting the required authentication file. Once you have the file, you have two options, either: + - set `GOOGLE_APPLICATION_CREDENTIALS` environment variable to contain the absolute path to your Google cloud credentials.json file. + - Or, set `GCLOUD_CREDENTIALS` environment variable to contain the content of the credentials.json file. Options: - you can define any key/value pairs. @@ -108,7 +122,8 @@ Example: [helmRepos] stable = "https://kubernetes-charts.storage.googleapis.com" incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" -myrepo = "s3://my-private-repo/charts" +myS3repo = "s3://my-S3-private-repo/charts" +myGCSrepo = "gs://my-GCS-private-repo/charts" ``` ## Apps diff --git a/docs/how_to/define_namespaces.md b/docs/how_to/define_namespaces.md index ea80937d..2217458f 100644 --- a/docs/how_to/define_namespaces.md +++ b/docs/how_to/define_namespaces.md @@ -1,5 +1,5 @@ --- -version: v0.1.3 +version: v0.2.0 --- # define namespaces diff --git a/docs/how_to/manipulate_apps.md b/docs/how_to/manipulate_apps.md index 980e67cb..2b0b5c60 100644 --- a/docs/how_to/manipulate_apps.md +++ b/docs/how_to/manipulate_apps.md @@ -1,5 +1,5 @@ --- -version: v0.1.3 +version: v0.2.0 --- # install releases @@ -57,7 +57,7 @@ NAME REVISION UPDATED STATUS CHART NAMESPA artifactory 2 Sun Nov 19 18:29:11 2017 DEPLOYED artifactory-6.2.0 staging ``` -If you would like the release to be deleted along with its history, you can use the `purge` key in your desired state file as follows: +If you would like the release to be deleted along with its history, you can use the `purge` flag in your desired state file as follows: > NOTE: purge deleting a release means you can't roll it back. diff --git a/docs/how_to/move_charts_across_namespaces.md b/docs/how_to/move_charts_across_namespaces.md index 3b242693..d2e53016 100644 --- a/docs/how_to/move_charts_across_namespaces.md +++ b/docs/how_to/move_charts_across_namespaces.md @@ -1,10 +1,12 @@ --- -version: v0.1.3 +version: v0.2.0 --- # move charts across namespaces -If you have a workflow for testing a release first in the `staging` namespace then move to the `production` namespace, Helmsman can help you. +If you have a workflow for testing a release first in the `staging` namespace then move it to the `production` namespace, Helmsman can help you. + +> NOTE: If your chart uses a persistent volume, then you have to read the note on PVs below first. ``` ... @@ -56,4 +58,61 @@ myOtherNamespace = "namespaceX" ... ``` -Helmsman will delete the jenkins release from the `staging` namespace and install it in the `production` namespace (default in the above setup). \ No newline at end of file +Helmsman will delete the jenkins release from the `staging` namespace and install it in the `production` namespace (default in the above setup). + +## Note on Persistent Volumes + +Helmsman does not automatically move PVCs across namespaces. You have to follow the steps below to retain your data when moving an app to a different namesapce. + +Persistent Volumes (PV) are accessed through Persistent Volume Claims (PVC). But **PVCs are namespaced object** which means moving an application from one namespace to another will result in a new PVC created in the new namespace. The old PV -which possibly contains your application data- will still be mounted to the old PVC (the one in the old namespace) even if you have deleted your application helm release. + +Now, the newly created PVC (in the new namespace) will not be able to mount to the old PV and instead it will mount to any other available one or (in the case of dynamic provisioning) will provision a new PV. This means the application in the new namespace does not have the old data. Don't panic, the old PV is still there and contains your old data. + +### Mounting the old PV to the new PVC (in the new namespace) + +1. You have to make sure the _Reclaim Policy_ of the old PV is set to **Retain**. In dynamic provisioned PVs, the default is Delete. +To change it: + +``` +kubectl patch pv -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}' +``` + +2. Once your old helm release is deleted, the old PVC and PV are still there. Go ahead and delete the PVC + +``` +kubectl delete pvc --namespace +``` +Since, we changed the Reclaim Policy to Retain, the PV will stay around (with all your data). + +3. The PV is now the **Released** state but not yet available for mounting. + +``` +kubectl get pv +NAME CAPACITY ACCESSMODES RECLAIMPOLICY STATUS CLAIM STORAGECLASS REASON AGE + ... +pvc-f791ef92-01ab-11e8-8a7e-02412acf5adc 20Gi RWO Retain Released staging/myapp-persistent-storage-test-old-0 gp2 5m + +``` +Now, you need to make it Available, for that we need to remove the `PV.Spec.ClaimRef` from the PV spec: + +``` +kubectl edit pv +# edit the file and save it +``` + +Now, the PV should become in the **Available** state: + +``` +kubectl get pv +NAME CAPACITY ACCESSMODES RECLAIMPOLICY STATUS CLAIM STORAGECLASS REASON AGE +... +pvc-f791ef92-01ab-11e8-8a7e-02412acf5adc 20Gi RWO Retain Available gp2 7m + +``` +4. Delete the new PVC (and its mounted PV if necessary), then delete your application pod(s) in the new namespace. Assuming you have a deployment/replication controller in place, the pod will be recreated in the new namespace and this time will mount to the old volume and your data will be once again available to your application. + +> NOTE: if there are multiple PVs in the Available state and they match capacity and read access for your application, then your application (in the new namespace) might mount to any of them. In this case, either ensure only the right PV is in the available state or make the PV available to a specific PVC - pre-fill `PV.Spec.ClaimRef` with a pointer to a PVC. Leave the `PV.Spec.ClaimRef,UID` empty, as the PVC does not need to exist at this point and you don't know PVC's UID. This PV can be bound only to the specified PVC + +Further details: +https://github.com/kubernetes/kubernetes/issues/48609 +https://kubernetes.io/docs/tasks/administer-cluster/change-pv-reclaim-policy/ \ No newline at end of file diff --git a/docs/how_to/pass_secrets_from_env_variables.md b/docs/how_to/pass_secrets_from_env_variables.md index 1413ec76..abdcf66f 100644 --- a/docs/how_to/pass_secrets_from_env_variables.md +++ b/docs/how_to/pass_secrets_from_env_variables.md @@ -1,5 +1,5 @@ --- -version: v0.1.3 +version: v0.2.0 --- # pass secrets from env. variables: diff --git a/docs/how_to/run_helmsman_in_ci.md b/docs/how_to/run_helmsman_in_ci.md index dd2b52bf..8ff1b79f 100644 --- a/docs/how_to/run_helmsman_in_ci.md +++ b/docs/how_to/run_helmsman_in_ci.md @@ -1,5 +1,5 @@ --- -version: v0.1.3 +version: v0.2.0 --- # Run Helmsman in CI diff --git a/docs/how_to/run_helmsman_with_hosted_cluster.md b/docs/how_to/run_helmsman_with_hosted_cluster.md index 00c55980..70e5710b 100644 --- a/docs/how_to/run_helmsman_with_hosted_cluster.md +++ b/docs/how_to/run_helmsman_with_hosted_cluster.md @@ -1,16 +1,32 @@ --- -version: v0.1.3 +version: v0.2.0 --- -You can manage Helm charts deployment on a hosted K8S cluster in the cloud or on-prem. You need to include the required information to connect to the cluster in your state file. Below is an example: +You can manage Helm charts deployment on a hosted K8S cluster in the cloud or on-prem. You need to include the required information to connect to the cluster in your state file. -**IMPORTANT**: Certificates can be used from S3 buckets or local file system. If you use s3 buckets, Helmsman needs valid AWS access keys to be able to retrieve private charts or certificates from your s3 buckets. It expects the keys to be in the following environemnt variables: +**IMPORTANT**: Helmsman expects certain environment variables to be available depending on where your cluster and connection certificates are hosted. Certificates can be used from S3/GCS buckets or local file system. + +## AWS +If you use s3 buckets for storing certificates or for hosting private helm repos, Helmsman needs valid AWS access keys to be able to retrieve private charts or certificates from your s3 buckets. It expects the keys to be in the following environemnt variables: - AWS_ACCESS_KEY_ID - AWS_SECRET_ACCESS_KEY - AWS_DEFAULT_REGION -Also, the K8S user password is expected in an environment variable which you can give any name you want and define it in your desired state file. +## GCS +If you use GCS buckets for storing certificates or for hosting private helm repos, Helmsman needs valid Google Cloud credentials to authenticate reading requests from private buckets. This can be provided in one of two ways: + +- set `GOOGLE_APPLICATION_CREDENTIALS` environment variable to contain the absolute path to your Google cloud credentials.json file. +- Or, set `GCLOUD_CREDENTIALS` environment variable to contain the content of the credentials.json file. + +check [here](https://www.terraform.io/docs/providers/google/index.html#authentication-json-file) for getting the required authentication file. + +## Additional environemnt variables + +The K8S user password is expected in an environment variable which you can give any name you want and define it in your desired state file. Additionally, you can optionally use environment variables to provide certificate paths and clusterURI. + + +Below is an example state file: ``` [metadata] @@ -20,13 +36,14 @@ maintainer = "k8s-admin" # Certificates are used to connect to the cluster. Currently, they can only be retrieved from s3 buckets. [certificates] caCrt = "s3://your-bucket/ca.crt" # s3 bucket -caKey = "../../ca.key" # relative file path +caKey = "$K8S_CLIENT_KEY" # relative file path +caClient = "gs://your-GCS-bucket/caClient.crt" # GCS bucket [settings] kubeContext = "mycontext" username = "<>" -password = "$PASSWORD" # the name of an environment variable containing the k8s password -clusterURI = "<>" # cluster API +password = "$K8S_PASSWORD" # the name of an environment variable containing the k8s password +clusterURI = "$K8S_URI" # cluster API [namespaces] staging = "staging" @@ -35,6 +52,7 @@ staging = "staging" stable = "https://kubernetes-charts.storage.googleapis.com" incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" myrepo = "s3://my-private-repo/charts" +myGCSrepo = "gs://my-GCS-repo/charts" [apps] @@ -60,4 +78,14 @@ myrepo = "s3://my-private-repo/charts" valuesFile = "" purge = false test = false -``` \ No newline at end of file +``` + +The above example requires the following environment variables to be set: + +- AWS_ACCESS_KEY_ID (since S3 is used for helm repo and certificates) +- AWS_SECRET_ACCESS_KEY +- AWS_DEFAULT_REGION +- GOOGLE_APPLICATION_CREDENTIALS (since GCS is used for helm repo and certificates) +- K8S_CLIENT_KEY (used in the file) +- K8S_PASSWORD (used in the file) +- K8S_URI (used in the file) \ No newline at end of file diff --git a/docs/how_to/run_helmsman_with_minikube.md b/docs/how_to/run_helmsman_with_minikube.md index 7ecd256b..f4631918 100644 --- a/docs/how_to/run_helmsman_with_minikube.md +++ b/docs/how_to/run_helmsman_with_minikube.md @@ -1,5 +1,5 @@ --- -version: v0.1.3 +version: v0.2.0 --- You can run Helmsman local as a binary application with Minikube, you just need to skip all the cluster connection settings in your desired state file. Below is the example.toml desired state file adapted to work with Minikube. diff --git a/docs/how_to/test_charts.md b/docs/how_to/test_charts.md index c38d6cc0..de1ef3e0 100644 --- a/docs/how_to/test_charts.md +++ b/docs/how_to/test_charts.md @@ -1,5 +1,5 @@ --- -version: v0.1.3 +version: v0.2.0 --- # test charts diff --git a/docs/how_to/use_local_charts.md b/docs/how_to/use_local_charts.md index fc50ac72..e2994b8e 100644 --- a/docs/how_to/use_local_charts.md +++ b/docs/how_to/use_local_charts.md @@ -1,5 +1,5 @@ --- -version: v0.1.3 +version: v0.2.0 --- # use local helm charts diff --git a/docs/how_to/use_private_helm_charts.md b/docs/how_to/use_private_helm_charts.md index 582ee4c1..53227695 100644 --- a/docs/how_to/use_private_helm_charts.md +++ b/docs/how_to/use_private_helm_charts.md @@ -1,11 +1,12 @@ --- -version: v0.1.3 +version: v0.2.0 --- # use private helm charts -Helmsman allows you to use private charts from private repos. Currently only repos hosted in S3 buckets are supported for private repos. -Other hosting options will be supported in the future. +Helmsman allows you to use private charts from private repos. Currently only repos hosted in S3 or GCS buckets are supported for private repos. + +Other hosting options might be supported in the future. Please open an issue if you require supporting other options. define your private repo: @@ -20,10 +21,21 @@ myPrivateRepo = s3://this-is-a-private-repo/charts ... ``` +## S3 + If you are using S3 private repos, you need to provide the following AWS env variables: - AWS_ACCESS_KEY_ID - AWS_SECRET_ACCESS_KEY - AWS_DEFAULT_REGION -Helmsman uses the [helm s3](https://github.com/hypnoglow/helm-s3) plugin to work with S3 helm repos. \ No newline at end of file +Helmsman uses the [helm s3](https://github.com/hypnoglow/helm-s3) plugin to work with S3 helm repos. + +## GCS + +If you are using GCS private repos, you need to provide one of the following env variables: + +- `GOOGLE_APPLICATION_CREDENTIALS` environment variable to contain the absolute path to your Google cloud credentials.json file. +- Or, `GCLOUD_CREDENTIALS` environment variable to contain the content of the credentials.json file. + +Helmsman uses the [helm GCS](https://github.com/nouney/helm-gcs) plugin to work with GCS helm repos. \ No newline at end of file diff --git a/docs/images/helmsman.png b/docs/images/helmsman.png new file mode 100644 index 0000000000000000000000000000000000000000..0c7a2ce547f1b5170b7cbbbe18365074570fcb30 GIT binary patch literal 218217 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/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 F{|`s~bj|<( literal 0 HcmV?d00001 From 723dd466b0a14258039987e0369a116130810491 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Fri, 23 Feb 2018 11:29:53 +0000 Subject: [PATCH 0084/1127] updating the goreleaser command. --- .circleci/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/build.sh b/.circleci/build.sh index 714415ae..c4d383b4 100755 --- a/.circleci/build.sh +++ b/.circleci/build.sh @@ -15,7 +15,7 @@ if [ $? -eq 0 ]; then rm hello.world hello.world1 helmsman git clean -fd echo "releasing ..." - goreleaser --release-notes release_notes.md | tee /dev/tty | grep -o "error" + goreleaser --release-notes release-notes.md | tee /dev/tty | grep -o "error" if [ $? -eq 0 ]; then echo "goreleaser experienced an error and no new releases made. That is Ok!" else From 7c673ff8b89b66021eca6801d7637cdda4a07f15 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Fri, 23 Feb 2018 11:30:56 +0000 Subject: [PATCH 0085/1127] allowing either of AWS_REGION or AWS_DEFAULT_REGION to be used fro aws. --- aws/aws.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/aws/aws.go b/aws/aws.go index 7838ea8e..d9ba165d 100644 --- a/aws/aws.go +++ b/aws/aws.go @@ -12,8 +12,17 @@ import ( func checkCredentialsEnvVar() bool { - if os.Getenv("AWS_ACCESS_KEY_ID") == "" || os.Getenv("AWS_SECRET_ACCESS_KEY") == "" || os.Getenv("AWS_REGION") == "" { + if os.Getenv("AWS_ACCESS_KEY_ID") == "" || os.Getenv("AWS_SECRET_ACCESS_KEY") == "" { + return false + + } else if os.Getenv("AWS_REGION") == "" { + + if os.Getenv("AWS_DEFAULT_REGION") == "" { + return false + } + os.Setenv("AWS_REGION", os.Getenv("AWS_DEFAULT_REGION")) + } return true } From 6a029ae8d44daa87039f1b45b6d48706d324d450 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Fri, 23 Feb 2018 15:21:24 +0000 Subject: [PATCH 0086/1127] adding a version timestamp. --- dockerfile/dockerfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile index ca17ee0c..2bc8b66a 100644 --- a/dockerfile/dockerfile +++ b/dockerfile/dockerfile @@ -5,7 +5,9 @@ RUN git clone https://github.com/Praqma/helmsman.git RUN go get github.com/Praqma/helmsman/gcs RUN go get github.com/Praqma/helmsman/aws # build a statically linked binary so that it works on stripped linux images such as alpine/busybox. -RUN CGO_ENABLED=0 GOOS=linux go install -a -ldflags '-extldflags "-static"' helmsman/. +RUN cd helmsman \ + && TAG=$(git describe --abbrev=0 --tags)-$(date +"%s") \ + && CGO_ENABLED=0 GOOS=linux go install -a -ldflags '-X main.version='$TAG' -extldflags "-static"' . FROM alpine:3.6 as kube ENV KUBE_LATEST_VERSION="v1.8.2" From 16600dd2ce1eb76e7c25771c84a79ab1fcaa3b9d Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Fri, 23 Feb 2018 15:22:33 +0000 Subject: [PATCH 0087/1127] adding a version timestamp in the circleci build script. --- .circleci/build.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.circleci/build.sh b/.circleci/build.sh index c4d383b4..21832bf2 100755 --- a/.circleci/build.sh +++ b/.circleci/build.sh @@ -8,7 +8,8 @@ go test -v if [ $? -eq 0 ]; then echo "building ..." - go build + TAG=$(git describe --abbrev=0 --tags)-$(date +"%s") + go build -ldflags "-X main.version="$TAG if [ $? -eq 0 ]; then echo "cleaning after tests ..." From aeb607b6314a73374ceaf7f6f3a8c1cb8f2f2cda Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sun, 25 Feb 2018 16:24:18 +0100 Subject: [PATCH 0088/1127] updating circleci config to use a workflow. --- .circleci/build.sh | 32 -------------------- .circleci/config.yml | 72 +++++++++++++++++++++++++++++++++----------- 2 files changed, 54 insertions(+), 50 deletions(-) delete mode 100755 .circleci/build.sh diff --git a/.circleci/build.sh b/.circleci/build.sh deleted file mode 100755 index 21832bf2..00000000 --- a/.circleci/build.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash -x -echo "fetching dependencies ..." -go get github.com/Praqma/helmsman/gcs -go get github.com/Praqma/helmsman/aws - -echo "running tests ..." -go test -v - -if [ $? -eq 0 ]; then - echo "building ..." - TAG=$(git describe --abbrev=0 --tags)-$(date +"%s") - go build -ldflags "-X main.version="$TAG - - if [ $? -eq 0 ]; then - echo "cleaning after tests ..." - rm hello.world hello.world1 helmsman - git clean -fd - echo "releasing ..." - goreleaser --release-notes release-notes.md | tee /dev/tty | grep -o "error" - if [ $? -eq 0 ]; then - echo "goreleaser experienced an error and no new releases made. That is Ok!" - else - echo "New release was successfully made." - fi - else - echo "Build failed!!" - exit 9 - fi -else - echo "tests failed ... Aborting!" - exit 9 -fi \ No newline at end of file diff --git a/.circleci/config.yml b/.circleci/config.yml index 70666425..519ccd45 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,26 +4,62 @@ version: 2 jobs: build: - filters: - tags: - only: /.*/ docker: - # specify the version - image: praqma/helmsman-test - - # Specify service dependencies here if necessary - # CircleCI maintains a library of pre-built images - # documented at https://circleci.com/docs/2.0/circleci-images/ - # - image: circleci/postgres:9.4 - - #### TEMPLATE_NOTE: go expects specific checkout path representing url - #### expecting it in the form of - #### /go/src/github.com/circleci/go-tool - #### /go/src/bitbucket.org/circleci/go-tool - # working_directory: /go/src/ steps: - checkout + - run: + name: Build helmsman + command: | + echo "fetching dependencies ..." + go get github.com/Praqma/helmsman/gcs + go get github.com/Praqma/helmsman/aws + echo "building ..." + TAG=$(git describe --abbrev=0 --tags)-$(date +"%s") + go build -ldflags "-X main.version="$TAG + + test: + docker: + - image: praqma/helmsman-test + steps: + - checkout + - run: + name: Unit test helmsman + command: | + echo "fetching dependencies ..." + go get github.com/Praqma/helmsman/gcs + go get github.com/Praqma/helmsman/aws + echo "running tests ..." + go test -v + + release: + docker: + - image: praqma/helmsman-test + steps: + - checkout + - run: + name: Release helmsman + command: | + echo "fetching dependencies ..." + go get github.com/Praqma/helmsman/gcs + go get github.com/Praqma/helmsman/aws + echo "releasing ..." + goreleaser --release-notes release-notes.md + - # specify any bash command here prefixed with `run: ` - - run: chmod +x .circleci/build.sh - - run: .circleci/build.sh \ No newline at end of file +workflows: + version: 2 + build-test-push-release: + jobs: + - build + - test: + requires: + - build + - release: + requires: + - test + filters: + branches: + only: master + tags: + only: /^v.*/ \ No newline at end of file From 293716ca7e79dd2b67c58796808d60a6e782c2dd Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sun, 25 Feb 2018 16:30:12 +0100 Subject: [PATCH 0089/1127] adding namespace/release protection support. --- decision_maker.go | 98 +++++++++++++++++++++++++++++++++++++++-------- main.go | 10 ++--- release.go | 2 + state.go | 15 ++++---- utils.go | 7 ++++ 5 files changed, 102 insertions(+), 30 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index 6a590784..2705ca7e 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -23,30 +23,59 @@ func decide(r release, s *state) { // check for deletion if !r.Enabled { - - inspectDeleteScenario(s.Namespaces[r.Namespace], r) + if !isProtected(r) { + inspectDeleteScenario(getDesiredNamespace(r), r) + } else { + logDecision("DECISION: release " + r.Name + " is PROTECTED. Operations are not allowed on this release until " + + "you remove its protection.") + } } else { // check for install/upgrade/rollback - if helmReleaseExists(getDesiredNamespaces(r), r.Name, "deployed") { - - inspectUpgradeScenario(s.Namespaces[r.Namespace], r) + if helmReleaseExists(getDesiredNamespace(r), r.Name, "deployed") { + if !isProtected(r) { + inspectUpgradeScenario(getDesiredNamespace(r), r) // upgrade + } else { + logDecision("DECISION: release " + r.Name + " is PROTECTED. Operations are not allowed on this release until " + + "you remove its protection.") + } } else if helmReleaseExists("", r.Name, "deleted") { + if !isProtected(r) { - inspectRollbackScenario(getDesiredNamespaces(r), r) + inspectRollbackScenario(getDesiredNamespace(r), r) // rollback + + } else { + logDecision("DECISION: release " + r.Name + " is PROTECTED. Operations are not allowed on this release until " + + "you remove its protection.") + } } else if helmReleaseExists("", r.Name, "failed") { - deleteRelease(r.Name, true) - installRelease(getDesiredNamespaces(r), r) + if !isProtected(r) { + + reInstallRelease(getDesiredNamespace(r), r) // re-install failed release + + } else { + logDecision("DECISION: release " + r.Name + " is PROTECTED. Operations are not allowed on this release until " + + "you remove its protection.") + } - } else if helmReleaseExists("", r.Name, "all") { // it is deployed but in another namespace + } else if helmReleaseExists("", r.Name, "all") { // not deployed in the desired namespace but deployed elsewhere - reInstallRelease(getDesiredNamespaces(r), r) + if !isProtected(r) { + + reInstallRelease(getDesiredNamespace(r), r) // move the release to a new (the desired) namespace + logDecision("WARNING: moving release [ " + r.Name + " ] from [[ " + getReleaseNamespace(r.Name) + " ]] to [[ " + getDesiredNamespace(r) + + " ]] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md for details if this release uses PV and PVC.") + + } else { + logDecision("DECISION: release " + r.Name + " is PROTECTED. Operations are not allowed on this release until " + + "you remove its protection.") + } } else { - installRelease(getDesiredNamespaces(r), r) + installRelease(getDesiredNamespace(r), r) // install a new release } @@ -183,7 +212,7 @@ func inspectUpgradeScenario(namespace string, r release) { logDecision("DECISION: release [ " + releaseName + " ] is desired to be enabled in a new namespace [[ " + namespace + " ]]. I am planning a purge delete of the current release from namespace [[ " + getReleaseNamespace(releaseName) + " ]] " + "and will install it for you in namespace [[ " + namespace + " ]]") - logDecision("WARNING: Moving release [ " + releaseName + " ] from [[ " + getReleaseNamespace(releaseName) + " ]] to [[ " + namespace + + logDecision("WARNING: moving release [ " + releaseName + " ] from [[ " + getReleaseNamespace(releaseName) + " ]] to [[ " + namespace + " ]] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md for details if this release uses PV and PVC.") } } @@ -221,6 +250,7 @@ func reInstallRelease(namespace string, r release) { Description: "installing release [ " + releaseName + " ] in namespace [[ " + namespace + " ]]", } outcome.addCommand(installCmd) + logDecision("DECISION: release [ " + releaseName + " ] will be deleted from namespace [[ " + getReleaseNamespace(releaseName) + " ]] and reinstalled in [[ " + namespace + "]].") // if r.Test { // testRelease(releaseName) @@ -264,14 +294,48 @@ func getSetValues(r release) string { return result } -// getDesiredNamespaces validates that namespace where the release is desired to be installed is defined in the Namespaces definition +// getDesiredNamespace validates that namespace where the release is desired to be installed is defined in the Namespaces definition // it returns the namespace if it is already defined // otherwise, it throws an error -func getDesiredNamespaces(r release) string { - value, ok := s.Namespaces[r.Namespace] - if !ok { +func getDesiredNamespace(r release) string { + if !checkNamespaceDefined(r.Namespace) { log.Fatal("ERROR: " + r.Namespace + " is not defined in the Namespaces section of your desired state file. Release [ " + r.Name + " ] can't be installed in that Namespace until its defined in your desired state file.") } - return value + + return r.Namespace +} + +// getCurrentNamespaceProtection returns the protection state for the namespace where a release is currently installed. +// It returns true if a namespace is defined as protected in the desired state file, false otherwise. +func getCurrentNamespaceProtection(r release) bool { + + return s.Namespaces[getReleaseNamespace(r.Name)].Protected +} + +// checkNamespaceDefined checks if a given namespace is defined in the namespaces section of the desired state file +func checkNamespaceDefined(ns string) bool { + _, ok := s.Namespaces[ns] + if !ok { + return false + } + return true +} + +// isProtected checks if a release is protected or not. +// A protected is release is either: a) deployed in a protected namespace b) flagged as protected in the desired state file +// Any release in a protected namespace is protected by default regardless of its flag +// returns true if a release is protected, false otherwise +func isProtected(r release) bool { + + if getCurrentNamespaceProtection(r) { + return true + } + + if r.Protected { + return true + } + + return false + } diff --git a/main.go b/main.go index ce458d0b..e6fc8d9c 100644 --- a/main.go +++ b/main.go @@ -132,17 +132,17 @@ func validateReleaseCharts(apps map[string]release) (bool, string) { // addNamespaces creates a set of namespaces in your k8s cluster. // If a namespace with the same name exsts, it will skip it. -func addNamespaces(namespaces map[string]string) { - for _, namespace := range namespaces { +func addNamespaces(namespaces map[string]namespace) { + for ns := range namespaces { cmd := command{ Cmd: "bash", - Args: []string{"-c", "kubectl create namespace " + namespace}, - Description: "creating namespace " + namespace, + Args: []string{"-c", "kubectl create namespace " + ns}, + Description: "creating namespace " + ns, } if exitCode, _ := cmd.exec(debug); exitCode != 0 { log.Println("WARN: I could not create namespace [" + - namespace + " ]. It already exists. I am skipping this.") + ns + " ]. It already exists. I am skipping this.") } } } diff --git a/release.go b/release.go index 4d683a92..0d77df59 100644 --- a/release.go +++ b/release.go @@ -17,6 +17,7 @@ type release struct { ValuesFile string Purge bool Test bool + Protected bool Set map[string]string } @@ -60,6 +61,7 @@ func (r release) print() { fmt.Println("\tvaluesFile : ", r.ValuesFile) fmt.Println("\tpurge : ", r.Purge) fmt.Println("\ttest : ", r.Test) + fmt.Println("\tprotected : ", r.Protected) fmt.Println("\tvalues to override from env:") printMap(r.Set) fmt.Println("------------------- ") diff --git a/state.go b/state.go index a74cb707..26d18b22 100644 --- a/state.go +++ b/state.go @@ -8,12 +8,17 @@ import ( "strings" ) +// namespace type represents the fields of a namespace +type namespace struct { + Protected bool +} + // state type represents the desired state of applications on a k8s cluster. type state struct { Metadata map[string]string Certificates map[string]string Settings map[string]string - Namespaces map[string]string + Namespaces map[string]namespace HelmRepos map[string]string Apps map[string]release } @@ -67,12 +72,6 @@ func (s state) validate() (bool, string) { return false, "ERROR: namespaces validation failed -- I need at least one namespace " + "to work with!" } - for k, v := range s.Namespaces { - if v == "" { - return false, "ERROR: namespaces validation failed -- namespace [" + k + " ] " + - "must have a value or be removed/commented." - } - } // repos if s.HelmRepos == nil || len(s.HelmRepos) < 1 { @@ -132,7 +131,7 @@ func (s state) print() { printMap(s.Settings) fmt.Println("\nNamespaces: ") fmt.Println("------------- ") - printMap(s.Namespaces) + printNamespacesMap(s.Namespaces) fmt.Println("\nRepositories: ") fmt.Println("------------- ") printMap(s.HelmRepos) diff --git a/utils.go b/utils.go index 4be4171c..fb33c8eb 100644 --- a/utils.go +++ b/utils.go @@ -20,6 +20,13 @@ func printMap(m map[string]string) { } } +// printObjectMap prints to the console any map of string keys and object values. +func printNamespacesMap(m map[string]namespace) { + for key, value := range m { + fmt.Println(key, " : protected = ", value) + } +} + // fromTOML reads a toml file and decodes it to a state type. // It uses the BurntSuchi TOML parser which throws an error if the TOML file is not valid. func fromTOML(file string, s *state) (bool, string) { From ec39983190d7a237c35ac465c00f192ee6563ff6 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sun, 25 Feb 2018 16:31:01 +0100 Subject: [PATCH 0090/1127] updating docs for v1.0.0 --- README.md | 9 +- docs/best_practice.md | 20 ++++ docs/deplyment_strategies.md | 94 ++++++++++++++++++ docs/desired_state_specification.md | 19 ++-- docs/how_to/move_charts_across_namespaces.md | 13 ++- .../how_to/pass_secrets_from_env_variables.md | 2 +- .../how_to/protect_namespaces_and_releases.md | 64 ++++++++++++ docs/how_to/run_helmsman_in_ci.md | 4 +- .../run_helmsman_with_hosted_cluster.md | 4 +- docs/how_to/run_helmsman_with_minikube.md | 4 +- docs/how_to/test_charts.md | 2 +- docs/how_to/use_local_charts.md | 2 +- docs/how_to/use_private_helm_charts.md | 2 +- docs/images/helmsman.png | Bin 218217 -> 93536 bytes docs/images/multi-DSF.png | Bin 0 -> 57660 bytes example.toml | 18 ++-- 16 files changed, 224 insertions(+), 33 deletions(-) create mode 100644 docs/best_practice.md create mode 100644 docs/deplyment_strategies.md create mode 100644 docs/how_to/protect_namespaces_and_releases.md create mode 100644 docs/images/multi-DSF.png diff --git a/README.md b/README.md index 15edf369..fa733a8b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ --- -version: v0.2.0 +version: v1.0.0 --- ![helmsman-logo](docs/images/helmsman.png) @@ -20,6 +20,7 @@ Helmsman was created to ease continous deployment of Helm charts. When you want - **Easy to use**: deep knowledge of Helm CLI and Kubectl is NOT manadatory to use Helmsman. - **Plan, View, apply**: you can run Helmsman to generate and view a plan with/without executing it. - **Portable**: Helmsman can be used to manage charts deployments on any k8s cluster. +- **Protect Namespaces/Releases**: you can define certain namespaces/releases to be protected against accidental human mistakes. - **Idempotency**: As long your desired state file does not change, you can execute Helmsman several times and get the same result. - **Continue from failures**: In the case of partial deployment due to a specific chart deployment failure, fix your helm chart and execute Helmsman again without needing to rollback the partial successes first. @@ -40,14 +41,14 @@ Helmsman can be used in three different settings: - [As a binary with Minikube](https://github.com/Praqma/helmsman/blob/master/docs/how_to/run_helmsman_with_minikube.md). - [As a binary with a hosted cluster](https://github.com/Praqma/helmsman/blob/master/docs/how_to/run_helmsman_with_hosted_cluster.md). -- [As a docker image in a CI system or local machine](https://github.com/Praqma/helmsman/blob/master/docs/how_to/run_helmsman_in_ci.md). +- [As a docker image in a CI system or local machine](https://github.com/Praqma/helmsman/blob/master/docs/how_to/run_helmsman_in_ci.md) Always use a tagged docker image from [dockerhub](https://hub.docker.com/r/praqma/helmsman/) as the `latest` image can (at times) be unstable. # How does it work? Helmsman uses a simple declarative [TOML](https://github.com/toml-lang/toml) file to allow you to describe a desired state for your k8s applications as in the [example file](https://github.com/Praqma/helmsman/blob/master/example.toml). -The desired state file follows the [desired state specification](https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md). +The desired state file (DSF) follows the [desired state specification](https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md). Helmsman sees what you desire, validates that your desire makes sense (e.g. that the charts you desire are available in the repos you defined), compares it with the current state of Helm and figures out what to do to make your desire come true. @@ -73,4 +74,4 @@ Documentation and How-Tos can be found [here](https://github.com/Praqma/helmsman # Contributing -Contribution and feeback/feature requests are welcome. \ No newline at end of file +Pull requests, feeback/feature requests are welcome. \ No newline at end of file diff --git a/docs/best_practice.md b/docs/best_practice.md new file mode 100644 index 00000000..f9c122a5 --- /dev/null +++ b/docs/best_practice.md @@ -0,0 +1,20 @@ +--- +version: v1.0.0 +--- + +# Best Practice + +When using Helmsman, we recommend the following best practices: + +- Add useful metadata in your desired state files (DSFs) so that others (who have access to them) can make understand what your DSF is for. We recommend the following metadata: organization, maintainer (name and email), and description/purpose. + +- Use environment variabels to pass K8S connection secrets (password, certificates paths on the local system or AWS/GCS bucket urls and the API URI). This keeps all sensitive information out of your version controlled source code. + +- Define certain namespaces (e.g, production) as protected namespaces (supported in v1.0.0+) and deploy your production-ready releases there. + +- If you use multiple desired state files (DSF) with the same cluster, make sure your namespace protection definitions are identical across all DSFs. + +- Don't maintain the same release in multiple DSFs. + +- While the decison on how many DSFs to use and what each can containt is up to you and dependes on your case, we recommend coming up with your own rule for that and following it. + diff --git a/docs/deplyment_strategies.md b/docs/deplyment_strategies.md new file mode 100644 index 00000000..d8a86565 --- /dev/null +++ b/docs/deplyment_strategies.md @@ -0,0 +1,94 @@ +--- +version: v1.0.0 +--- + +# Deployment Strategies + +This document describes the different startegies to use Helmsman for maintaining your helm charts deployment to k8s clusters. + +## Deploying 3rd party charts (apps) in a production cluster + +Suppose you are deploying 3rd party charts (e.g. Jenkins, Jira ... etc.) in your cluster. These applications can be deployed with Helmsman using a single desired state file. The desired state tells helmsman to deploy these apps into certain namespaces in a production cluster. + +You can test 3rd party charts in designated namespaces (e.g, staging) within the same production cluster. This also can be defined in the same desired state file. Below is an example of a desired state file for deploying 3rd party apps in production and staging namespaces: + +``` +[metadata] +org = "example" + +# using a minikube cluster +[settings] +kubeContext = "minikube" + +[namespaces] + [namespaces.staging] + protected = false + [namespaces.production] + prtoected = true + +[helmRepos] +stable = "https://kubernetes-charts.storage.googleapis.com" +incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" + +[apps] + + [apps.jenkins] + name = "jenkins-prod" # should be unique across all apps + description = "production jenkins" + namespace = "production" + enabled = true + chart = "stable/jenkins" + version = "0.9.1" # chart version + valuesFile = "../my-jenkins-production-values.yaml" + + + [apps.artifactory] + name = "artifactory-prod" # should be unique across all apps + description = "production artifactory" + namespace = "production" + enabled = true + chart = "stable/artifactory" + version = "6.2.0" # chart version + valuesFile = "../my-artificatory-production-values.yaml" + + + # the jenkins release below is being tested in the staging namespace + [apps.jenkins-test] + name = "jenkins-test" # should be unique across all apps + description = "test release of jenkins, testing xyz feature" + namespace = "staging" + enabled = true + chart = "stable/jenkins" + version = "0.9.1" # chart version + valuesFile = "../my-jenkins-testing-values.yaml" + +``` + +You can split the desired state file into multiple files if your deployment pipelines requires that, but it is important to read the notes below on using multiple desired state files with one cluster. + +## Working with multiple clusters + +If you use multiple clusters for multiple purposes, you need at least one Helmsman desired state file for each cluster. + + +## Deploying your dev charts + +If you are developing your own applications/services and packaging them in helm charts. It makes sense to automtically deploy these charts to a staging namespace or a dev cluster on every source code commit. + +Often, you would have multiple apps developed in separate source code repositories but you would like to test their deployment in the same cluster/namespace. In that case, Helmsman can be used [as part of your CI pipeline](how_to/run_helmsman_in_ci.md) as described in the diagram below: + +![multi-DSF](images/multi-DSF.png) + +Each repository will have a Helmsman desired state file (DSF). But it is important to consider the notes below on using multiple desired state files with one cluster. + +If you need supporting applications (charts) for your application (e.g, reverse proxies, DB, k8s dashborad etc.), you can describe the desired state for these in a separate file which can live in another repository. Adding such file in the pipeline where you create your cluster from code makes total "DevOps" sense. + +## Notes on using multiple Helmsman desired state files with the same cluster + +Helmsman works with a single desired state file at a time and does not maintain a state anywhere. i.e. it does not have any context awareness about other desired state files used with the same cluster. For this reason, it is the user's responsibility to make sure that: + +- no releases have the same name in different desired state files pointing to the same cluster. If such conflict exists, Helmsman will not raise any errors but that release would be subject to unexpected behaviour. + +- protected namespaces are defined protected in all the desired state files. Otherwise, namespace protection can be accidentally compromised if the same release name is used across multiple desired state files. + +Also please refere to the [best parctice](best_practice.md) document. diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index f2a79bf0..44d5a0d8 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -1,5 +1,5 @@ --- -version: v0.2.0 +version: v1.0.0 --- # Helmsman desired state specification @@ -85,18 +85,23 @@ kubeContext = "minikube" Optional : No. -Synopsis: defines the namespaces to be used/created in your k8s cluster. You can add as many namespaces as you like. -If a namespaces does not already exist, Helmsman will be created. +Synopsis: defines the namespaces to be used/created in your k8s cluster and wether they are protected or not. You can add as many namespaces as you like. +If a namespaces does not already exist, Helmsman will create it. Options: -- you can define any key/value pairs. +- protected : defines if a namespace is protected (true or false). Default false. + +> For the defintion of what a protected namespace means, check the [protection guide](how_to/protect_namespaces_and_releases.md) Example: ``` [namespaces] -staging = "staging" -production = "default" +[namespaces.staging] +[namespaces.dev] +protected = false +[namespaces.production] +protected = true ``` ## Helm Repos @@ -144,6 +149,7 @@ Options: - valuesFile : a valid path to custom Helm values.yaml file. File extension must be `yaml`. Leaving it empty uses the default chart values. - purge : defines whether to use the Helm purge flag wgen deleting the release. (true/false) - test : defines whether to run the chart tests whenever the release is installed/upgraded/rolledback. +- protected : defines if the release should be protected against changes. Namespace-level protection has higher priority than this flag. Check the [protection guide](how_to/protect_namespaces_and_releases.md) for more details. Example: @@ -162,5 +168,6 @@ Example: valuesFile = "" purge = false test = true + protected = false ``` diff --git a/docs/how_to/move_charts_across_namespaces.md b/docs/how_to/move_charts_across_namespaces.md index d2e53016..9efc550a 100644 --- a/docs/how_to/move_charts_across_namespaces.md +++ b/docs/how_to/move_charts_across_namespaces.md @@ -1,5 +1,5 @@ --- -version: v0.2.0 +version: v1.0.0 --- # move charts across namespaces @@ -12,9 +12,9 @@ If you have a workflow for testing a release first in the `staging` namespace th ... [namespaces] -staging = "staging" -production = "default" -myOtherNamespace = "namespaceX" +[namespaces.staging] +[namespaces.production] + [apps] @@ -38,9 +38,8 @@ Then if you change the namespace key for jenkins: ... [namespaces] -staging = "staging" -production = "default" -myOtherNamespace = "namespaceX" +[namespaces.staging] +[namespaces.production] [apps] diff --git a/docs/how_to/pass_secrets_from_env_variables.md b/docs/how_to/pass_secrets_from_env_variables.md index abdcf66f..636e1dc6 100644 --- a/docs/how_to/pass_secrets_from_env_variables.md +++ b/docs/how_to/pass_secrets_from_env_variables.md @@ -1,5 +1,5 @@ --- -version: v0.2.0 +version: v1.0.0 --- # pass secrets from env. variables: diff --git a/docs/how_to/protect_namespaces_and_releases.md b/docs/how_to/protect_namespaces_and_releases.md new file mode 100644 index 00000000..4765200a --- /dev/null +++ b/docs/how_to/protect_namespaces_and_releases.md @@ -0,0 +1,64 @@ +--- +version: v1.0.0 +--- + +# Namespace and Release Protection + +Since helmsman is used with version controlled code and is often configured to be triggered as part of a CI pipeline, accidental mistakes could happen by the user (e.g, disabling a production application and taking out of service as a result of a mistaken change in the desired state file). + +Helmsman provides the `plan` flag which helps you see the actions that it will take based on your desired state file before actually doing them. We recommend to use a `plan-hold-approve` pipeline when using helmsman with production clusters. + +As of version v1.0.0, helmsman provides a fine-grained mechansim to protect releases/namespaces from accidental desired state file changes. + +## Protection definition + +- When a release (application) is protected, it CANNOT: + - deleted + - upgraded + - moved to another namespace + +- A release CAN be moved into protection from a non-protected state. +- If a protected release need to be updated/changed or even deleted, this is possible, but the protection has to be removed first (i.e. remove the namespace/release from the protected state). This explained further below. + +> A release is an instance (installation) of an application which has been packaged as a helm chart. + +## Protection mechanism +Protection is supported in two forms: + +- **Namespace-level Protection**: is defined at the namespace level. A namespace can be declaratively defined to be protected in the desired state file as in the example below: + +``` +[namespaces] + [namespaces.staging] + protected = false + [namespaces.production] + prtoected = true + +``` + +- **Release-level Protection** is defined at the release level as in the example below: + +``` +[apps] + + [apps.jenkins] + name = "jenkins" + description = "jenkins" + namespace = "staging" + enabled = true + chart = "stable/jenkins" + version = "0.9.1" + valuesFile = "" + purge = false + test = false + protected = true # defining this release to be protected. +``` + +> All releases in a protected namespace are automatically protected. Namespace protection has higher priority than the relase-level protection. + +## Important Notes + +- You can combine both types of protection in your desired state file. The namespace-level protection always has a higher priority. +- Removing the protection from a namespace means all releases deployed in that namespace are no longer protected. +- We recommend using namespace-level protection for production namespace(s) and release-level protection for releases deployed in other namespaces. +- Release/namespace protection is only applied on single desired state files. It is your responsibility to make sure that multiple desired state files (if used) do not conflict with each other (e.g, one defines a particular namespace as defined and another defines it unprotected.) If you use multiple desired state files with the same cluster, please refer to [deploymemt strategies](../deplyment_strategies.md) and [best practice](../best_practice.md) documentation. \ No newline at end of file diff --git a/docs/how_to/run_helmsman_in_ci.md b/docs/how_to/run_helmsman_in_ci.md index 8ff1b79f..8a98636c 100644 --- a/docs/how_to/run_helmsman_in_ci.md +++ b/docs/how_to/run_helmsman_in_ci.md @@ -1,5 +1,5 @@ --- -version: v0.2.0 +version: v1.0.0 --- # Run Helmsman in CI @@ -13,7 +13,7 @@ jobs: deploy-apps: docker: - - image: praqma/helmsman:v0.1.2 + - image: praqma/helmsman:v1.0.0 steps: - checkout - run: diff --git a/docs/how_to/run_helmsman_with_hosted_cluster.md b/docs/how_to/run_helmsman_with_hosted_cluster.md index 70e5710b..118bf998 100644 --- a/docs/how_to/run_helmsman_with_hosted_cluster.md +++ b/docs/how_to/run_helmsman_with_hosted_cluster.md @@ -1,5 +1,5 @@ --- -version: v0.2.0 +version: v1.0.0 --- You can manage Helm charts deployment on a hosted K8S cluster in the cloud or on-prem. You need to include the required information to connect to the cluster in your state file. @@ -46,7 +46,7 @@ password = "$K8S_PASSWORD" # the name of an environment variable containing the clusterURI = "$K8S_URI" # cluster API [namespaces] -staging = "staging" +[namespaces.staging] [helmRepos] stable = "https://kubernetes-charts.storage.googleapis.com" diff --git a/docs/how_to/run_helmsman_with_minikube.md b/docs/how_to/run_helmsman_with_minikube.md index f4631918..4fdef160 100644 --- a/docs/how_to/run_helmsman_with_minikube.md +++ b/docs/how_to/run_helmsman_with_minikube.md @@ -1,5 +1,5 @@ --- -version: v0.2.0 +version: v1.0.0 --- You can run Helmsman local as a binary application with Minikube, you just need to skip all the cluster connection settings in your desired state file. Below is the example.toml desired state file adapted to work with Minikube. @@ -14,7 +14,7 @@ maintainer = "k8s-admin" kubeContext = "minikube" [namespaces] -staging = "staging" +[namespaces.staging] [helmRepos] stable = "https://kubernetes-charts.storage.googleapis.com" diff --git a/docs/how_to/test_charts.md b/docs/how_to/test_charts.md index de1ef3e0..7e6bdaa3 100644 --- a/docs/how_to/test_charts.md +++ b/docs/how_to/test_charts.md @@ -1,5 +1,5 @@ --- -version: v0.2.0 +version: v1.0.0 --- # test charts diff --git a/docs/how_to/use_local_charts.md b/docs/how_to/use_local_charts.md index e2994b8e..2669c313 100644 --- a/docs/how_to/use_local_charts.md +++ b/docs/how_to/use_local_charts.md @@ -1,5 +1,5 @@ --- -version: v0.2.0 +version: v1.0.0 --- # use local helm charts diff --git a/docs/how_to/use_private_helm_charts.md b/docs/how_to/use_private_helm_charts.md index 53227695..96714a56 100644 --- a/docs/how_to/use_private_helm_charts.md +++ b/docs/how_to/use_private_helm_charts.md @@ -1,5 +1,5 @@ --- -version: v0.2.0 +version: v1.0.0 --- # use private helm charts diff --git a/docs/images/helmsman.png b/docs/images/helmsman.png index 0c7a2ce547f1b5170b7cbbbe18365074570fcb30..e7c1f3f44d7453ed2083d655f7816838c08315ed 100644 GIT binary patch literal 93536 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�vH4wV@l-)> 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 %AXDPHF#TTu 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 Pfalf?6Mdqt)4l%!!ACrX literal 218217 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/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 F{|`s~bj|<( diff --git a/docs/images/multi-DSF.png b/docs/images/multi-DSF.png new file mode 100644 index 0000000000000000000000000000000000000000..4ae7d79cc134e0bf933fee4be6549dd853c8c62c GIT binary patch literal 57660 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%0DrVV6cKxLPv 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 literal 0 HcmV?d00001 diff --git a/example.toml b/example.toml index 18d77051..1d907ccd 100644 --- a/example.toml +++ b/example.toml @@ -1,8 +1,9 @@ -# version: v0.1.2 +# version: v1.0.0 # metadata -- add as many key/value pairs as you want [metadata] -org = "orgX" -maintainer = "k8s-admin" +org = "example.com" +maintainer = "k8s-admin (me@example.com)" +description = "example Desired State File for demo purposes." # paths to the certificate for connecting to the cluster # You can skip this if you use Helmsman on a machine with kubectl already connected to your k8s cluster. @@ -20,10 +21,14 @@ kubeContext = "minikube" # will try connect to this context first, if it does no ##clusterURI = "https://192.168.99.100:8443" # equivalent to the above # define your environments and thier k8s namespaces -# syntax: environment_name = "k8s_namespace" +# syntax: +# [namespaces.] -- whitespace before this entry does not matter, use whatever indentation style you like + # protected = -- default to false [namespaces] -staging = "staging" -production = "default" + [namespaces.staging] + protected = false + [namespaces.production] + prtoected = true # define any private/public helm charts repos you would like to get charts from @@ -51,6 +56,7 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" valuesFile = "" # leaving it empty uses the default chart values purge = false # will only be considered when there is a delete operation test = false # run the tests when this release is installed for the first time only + prtoected = true # [apps.jenkins.set] # values to override values from values.yaml with values from env vars-- useful for passing secrets to charts # db_username="$DB_USERNAME" # db_password="$DB_PASSWORD" From 418b5b759778ba22b8b9bb7ae53ec5ca16e17e16 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sun, 25 Feb 2018 16:31:44 +0100 Subject: [PATCH 0091/1127] updating dockerfile to create appropriate verions. --- dockerfile/dockerfile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile index 2bc8b66a..8ab9f40e 100644 --- a/dockerfile/dockerfile +++ b/dockerfile/dockerfile @@ -6,7 +6,11 @@ RUN go get github.com/Praqma/helmsman/gcs RUN go get github.com/Praqma/helmsman/aws # build a statically linked binary so that it works on stripped linux images such as alpine/busybox. RUN cd helmsman \ - && TAG=$(git describe --abbrev=0 --tags)-$(date +"%s") \ + && LastTag=$(git describe --abbrev=0 --tags) \ + && TAG=$LastTag-$(date +"%s") \ + && LT_SHA=$(git rev-parse ${LastTag}^{}) \ + && LC_SHA=$(git rev-parse HEAD) \ + && if [ ${LT_SHA} != ${LC_SHA} ]; then TAG=latest-$(date +"%s"); fi \ && CGO_ENABLED=0 GOOS=linux go install -a -ldflags '-X main.version='$TAG' -extldflags "-static"' . FROM alpine:3.6 as kube From 5a5b844eb6340383da9b656f85cd1f03ca160f79 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sun, 25 Feb 2018 16:33:05 +0100 Subject: [PATCH 0092/1127] updating state validation unit tests. --- state_test.go | 96 ++++++++++++++++++++------------------------------- 1 file changed, 38 insertions(+), 58 deletions(-) diff --git a/state_test.go b/state_test.go index d4c968f3..df39b445 100644 --- a/state_test.go +++ b/state_test.go @@ -10,7 +10,7 @@ func Test_state_validate(t *testing.T) { Metadata map[string]string Certificates map[string]string Settings map[string]string - Namespaces map[string]string + Namespaces map[string]namespace HelmRepos map[string]string Apps map[string]release } @@ -33,8 +33,8 @@ func Test_state_validate(t *testing.T) { "password": "$K8S_PASSWORD", "clusterURI": "https://192.168.99.100:8443", }, - Namespaces: map[string]string{ - "staging": "staging", + Namespaces: map[string]namespace{ + "staging": namespace{false}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -52,8 +52,8 @@ func Test_state_validate(t *testing.T) { "caKey": "s3://some-bucket/12345.key", }, Settings: nil, - Namespaces: map[string]string{ - "staging": "staging", + Namespaces: map[string]namespace{ + "staging": namespace{false}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -76,8 +76,8 @@ func Test_state_validate(t *testing.T) { "password": "$K8S_PASSWORD", "clusterURI": "https://192.168.99.100:8443", }, - Namespaces: map[string]string{ - "staging": "staging", + Namespaces: map[string]namespace{ + "staging": namespace{false}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -97,8 +97,8 @@ func Test_state_validate(t *testing.T) { Settings: map[string]string{ "kubeContext": "minikube", }, - Namespaces: map[string]string{ - "staging": "staging", + Namespaces: map[string]namespace{ + "staging": namespace{false}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -121,8 +121,8 @@ func Test_state_validate(t *testing.T) { "password": "K8S_PASSWORD", "clusterURI": "https://192.168.99.100:8443", }, - Namespaces: map[string]string{ - "staging": "staging", + Namespaces: map[string]namespace{ + "staging": namespace{false}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -145,8 +145,8 @@ func Test_state_validate(t *testing.T) { "password": "K8S_PASSWORD", "clusterURI": "$URI", // unset env }, - Namespaces: map[string]string{ - "staging": "staging", + Namespaces: map[string]namespace{ + "staging": namespace{false}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -169,8 +169,8 @@ func Test_state_validate(t *testing.T) { "password": "K8S_PASSWORD", "clusterURI": "$SET_URI", // set env var }, - Namespaces: map[string]string{ - "staging": "staging", + Namespaces: map[string]namespace{ + "staging": namespace{false}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -193,8 +193,8 @@ func Test_state_validate(t *testing.T) { "password": "K8S_PASSWORD", "clusterURI": "https//192.168.99.100:8443", // invalid url }, - Namespaces: map[string]string{ - "staging": "staging", + Namespaces: map[string]namespace{ + "staging": namespace{false}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -216,8 +216,8 @@ func Test_state_validate(t *testing.T) { "password": "$K8S_PASSWORD", "clusterURI": "https://192.168.99.100:8443", }, - Namespaces: map[string]string{ - "staging": "staging", + Namespaces: map[string]namespace{ + "staging": namespace{false}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -237,8 +237,8 @@ func Test_state_validate(t *testing.T) { "password": "$K8S_PASSWORD", "clusterURI": "https://192.168.99.100:8443", }, - Namespaces: map[string]string{ - "staging": "staging", + Namespaces: map[string]namespace{ + "staging": namespace{false}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -261,8 +261,8 @@ func Test_state_validate(t *testing.T) { "password": "$K8S_PASSWORD", "clusterURI": "https://192.168.99.100:8443", }, - Namespaces: map[string]string{ - "staging": "staging", + Namespaces: map[string]namespace{ + "staging": namespace{false}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -279,8 +279,8 @@ func Test_state_validate(t *testing.T) { Settings: map[string]string{ "kubeContext": "minikube", }, - Namespaces: map[string]string{ - "staging": "staging", + Namespaces: map[string]namespace{ + "staging": namespace{false}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -313,7 +313,7 @@ func Test_state_validate(t *testing.T) { Settings: map[string]string{ "kubeContext": "minikube", }, - Namespaces: map[string]string{}, + Namespaces: map[string]namespace{}, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", "myrepo": "s3://my-repo/charts", @@ -322,65 +322,45 @@ func Test_state_validate(t *testing.T) { }, want: false, }, { - name: "test case 15 -- namespaces/empty_namespace_value", + name: "test case 15 -- helmRepos/nil_value", fields: fields{ Metadata: make(map[string]string), Certificates: nil, Settings: map[string]string{ "kubeContext": "minikube", }, - Namespaces: map[string]string{ - "staging": "staging", - "x": "y", - "z": "", - }, - HelmRepos: map[string]string{ - "stable": "https://kubernetes-charts.storage.googleapis.com", - "myrepo": "s3://my-repo/charts", - }, - Apps: make(map[string]release), - }, - want: false, - }, { - name: "test case 16 -- helmRepos/nil_value", - fields: fields{ - Metadata: make(map[string]string), - Certificates: nil, - Settings: map[string]string{ - "kubeContext": "minikube", - }, - Namespaces: map[string]string{ - "staging": "staging", + Namespaces: map[string]namespace{ + "staging": namespace{false}, }, HelmRepos: nil, Apps: make(map[string]release), }, want: false, }, { - name: "test case 17 -- helmRepos/empty", + name: "test case 16 -- helmRepos/empty", fields: fields{ Metadata: make(map[string]string), Certificates: nil, Settings: map[string]string{ "kubeContext": "minikube", }, - Namespaces: map[string]string{ - "staging": "staging", + Namespaces: map[string]namespace{ + "staging": namespace{false}, }, HelmRepos: map[string]string{}, Apps: make(map[string]release), }, want: false, }, { - name: "test case 18 -- helmRepos/empty_repo_value", + name: "test case 17 -- helmRepos/empty_repo_value", fields: fields{ Metadata: make(map[string]string), Certificates: nil, Settings: map[string]string{ "kubeContext": "minikube", }, - Namespaces: map[string]string{ - "staging": "staging", + Namespaces: map[string]namespace{ + "staging": namespace{false}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -390,15 +370,15 @@ func Test_state_validate(t *testing.T) { }, want: false, }, { - name: "test case 19 -- helmRepos/invalid_repo_value", + name: "test case 18 -- helmRepos/invalid_repo_value", fields: fields{ Metadata: make(map[string]string), Certificates: nil, Settings: map[string]string{ "kubeContext": "minikube", }, - Namespaces: map[string]string{ - "staging": "staging", + Namespaces: map[string]namespace{ + "staging": namespace{false}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", From 06c8ce78d5e45fa4ee7aba4ab10eb78184baab38 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sun, 25 Feb 2018 16:42:12 +0100 Subject: [PATCH 0093/1127] fixing yaml formatting issue in circleci config. --- .circleci/config.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 519ccd45..aebcbeea 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -46,7 +46,6 @@ jobs: echo "releasing ..." goreleaser --release-notes release-notes.md - workflows: version: 2 build-test-push-release: From ee4710d928146ea64608d271e8f897f7b54f1e9d Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sun, 25 Feb 2018 16:44:40 +0100 Subject: [PATCH 0094/1127] fixing yaml formatting issue in circleci config. --- .circleci/config.yml | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index aebcbeea..927ec596 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -31,21 +31,19 @@ jobs: go get github.com/Praqma/helmsman/aws echo "running tests ..." go test -v - release: - docker: - - image: praqma/helmsman-test - steps: - - checkout - - run: - name: Release helmsman - command: | - echo "fetching dependencies ..." - go get github.com/Praqma/helmsman/gcs - go get github.com/Praqma/helmsman/aws - echo "releasing ..." - goreleaser --release-notes release-notes.md - + docker: + - image: praqma/helmsman-test + steps: + - checkout + - run: + name: Release helmsman + command: | + echo "fetching dependencies ..." + go get github.com/Praqma/helmsman/gcs + go get github.com/Praqma/helmsman/aws + echo "releasing ..." + goreleaser --release-notes release-notes.md workflows: version: 2 build-test-push-release: @@ -61,4 +59,4 @@ workflows: branches: only: master tags: - only: /^v.*/ \ No newline at end of file + only: /^v.*/ \ No newline at end of file From 511d728279bdbb369cc9b197f646451cd3ea76bf Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Thu, 1 Mar 2018 13:53:20 +0100 Subject: [PATCH 0095/1127] updated docs. [ci skip] --- README.md | 1 + docs/deplyment_strategies.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fa733a8b..8ec12823 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ Helmsman was created to ease continous deployment of Helm charts. When you want - [use locally developed helm charts (the tar archives)](https://github.com/Praqma/helmsman/blob/master/docs/how_to/use_local_charts.md). - [define namespaces to be used in your cluster](https://github.com/Praqma/helmsman/blob/master/docs/how_to/define_namespaces.md). - [move charts across namespaces](https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md). +- [protect namespaces/releases against accidental changes](https://github.com/Praqma/helmsman/blob/master/docs/how_to/protect_namespaces_and_releases.md) # Usage diff --git a/docs/deplyment_strategies.md b/docs/deplyment_strategies.md index d8a86565..564e0baf 100644 --- a/docs/deplyment_strategies.md +++ b/docs/deplyment_strategies.md @@ -24,7 +24,7 @@ kubeContext = "minikube" [namespaces.staging] protected = false [namespaces.production] - prtoected = true + protected = true [helmRepos] stable = "https://kubernetes-charts.storage.googleapis.com" From a4f44cd0fe5112544c403b4071a8037f285b2298 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Thu, 1 Mar 2018 14:09:59 +0100 Subject: [PATCH 0096/1127] updating release notes for v1.0.0 --- release-notes.md | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/release-notes.md b/release-notes.md index 9654409a..babddf7d 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,10 +1,5 @@ -# v0.2.0 +# v1.0.0 -- Support reading cluster certificates from Google Cloud Storage (GCS). -- Supporting private helm repos in GCS. -- Support using client-certificates in the cluster authentication. -- Adding a warning about PV and PVCs possible issues when moving apps across namespaces. -- Allowing certs file paths or bucket URLs and clusterURI to be passed from environment variables. -- Fixing a bug with undefined namespaces. -- Removing the aws cli dependency. -- Smaller docker image. \ No newline at end of file +- Introducing protected namespaces and releases. +- Minor enhancments. +- Updated documentations. \ No newline at end of file From 1fc1230224683a37fcf6314db2d9edc3559f1e50 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 3 Mar 2018 12:35:45 +0100 Subject: [PATCH 0097/1127] fixing #19 --- decision_maker.go | 2 +- helm_helpers.go | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index 2705ca7e..6ee915d5 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -268,7 +268,7 @@ func logDecision(decision string) { } -// extractChartName extracts the Helm chart name from full chart name. +// extractChartName extracts the Helm chart name from full chart name in the desired state. // example: it extracts "chartY" from "repoX/chartY" func extractChartName(releaseChart string) string { diff --git a/helm_helpers.go b/helm_helpers.go index 8533856f..65a70b1f 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -97,13 +97,15 @@ func getReleaseRevision(releaseName string, state string) string { } // getReleaseChartName extracts and returns the Helm chart name from the chart info retrieved by getReleaseChart(). -// example: getReleaseChart() returns "stable/jenkins-0.9.0" and this functions will extract "stable/jenkins" from it. +// example: getReleaseChart() returns "jenkins-0.9.0" and this functions will extract "jenkins" from it. func getReleaseChartName(releaseName string) string { - return strings.TrimSpace(strings.Split(getReleaseChart(releaseName), "-")[0]) + chart := getReleaseChart(releaseName) + runes := []rune(chart) + return string(runes[0:strings.LastIndexByte(chart, '-')]) } // getReleaseChartVersion extracts and returns the Helm chart version from the chart info retrieved by getReleaseChart(). -// example: getReleaseChart() returns "stable/jenkins-0.9.0" and this functions will extract "0.9.0" from it. +// example: getReleaseChart() returns "jenkins-0.9.0" and this functions will extract "0.9.0" from it. func getReleaseChartVersion(releaseName string) string { return strings.TrimSpace(strings.Split(getReleaseChart(releaseName), "-")[1]) } From c69cf51015ed23f0c7d4523f90ffeb299f9e8fbf Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 3 Mar 2018 12:39:21 +0100 Subject: [PATCH 0098/1127] fixing #20 --- decision_maker.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/decision_maker.go b/decision_maker.go index 6ee915d5..5bdcd4c1 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -328,6 +328,11 @@ func checkNamespaceDefined(ns string) bool { // returns true if a release is protected, false otherwise func isProtected(r release) bool { + // if the release does not exist in the cluster, it is not protected + if !helmReleaseExists("", r.Name, "all") { + return false + } + if getCurrentNamespaceProtection(r) { return true } From 35f6cd432ad19bb14b3501525083b0352bb67e04 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 3 Mar 2018 13:08:45 +0100 Subject: [PATCH 0099/1127] fixing version checks for charts with '-' in their names. --- helm_helpers.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/helm_helpers.go b/helm_helpers.go index 65a70b1f..b293bf3b 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -107,7 +107,9 @@ func getReleaseChartName(releaseName string) string { // getReleaseChartVersion extracts and returns the Helm chart version from the chart info retrieved by getReleaseChart(). // example: getReleaseChart() returns "jenkins-0.9.0" and this functions will extract "0.9.0" from it. func getReleaseChartVersion(releaseName string) string { - return strings.TrimSpace(strings.Split(getReleaseChart(releaseName), "-")[1]) + chart := getReleaseChart(releaseName) + runes := []rune(chart) + return string(runes[strings.LastIndexByte(chart, '-')+1 : len(chart)]) } // getReleaseStatus returns the output of Helm status command for a release. From 26e11afc25f6041884e329ff7d8d3340e481a059 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 3 Mar 2018 13:13:48 +0100 Subject: [PATCH 0100/1127] updating release notes for v1.0.1 --- release-notes.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/release-notes.md b/release-notes.md index babddf7d..e6b6f488 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,5 +1,3 @@ -# v1.0.0 +# v1.0.1 -- Introducing protected namespaces and releases. -- Minor enhancments. -- Updated documentations. \ No newline at end of file +- Bug fixes for #19 and #20. \ No newline at end of file From 32f92194f62b42229c08f2ec5263ee98d828cb60 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Wed, 7 Mar 2018 15:05:37 +0100 Subject: [PATCH 0101/1127] adding a more friendly format for the plan time. --- plan.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plan.go b/plan.go index b928ad49..d16a8d42 100644 --- a/plan.go +++ b/plan.go @@ -59,7 +59,7 @@ func (p plan) printPlanCmds() { // printPlan prints the decisions made in a plan. func (p plan) printPlan() { fmt.Println("---------------") - fmt.Printf("Ok, I have generated a plan for you at: %s \n", p.Created) + fmt.Printf("Ok, I have generated a plan for you at: %s \n", p.Created.Format("Mon Jan _2 2006 15:04:05")) for _, decision := range p.Decisions { fmt.Println(decision) } From 86cd25682ebf418a6a3377e7109eeb3d65355373 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Wed, 7 Mar 2018 15:06:53 +0100 Subject: [PATCH 0102/1127] making gcloud credentials file name more self-explanatory. --- gcs/gcs.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gcs/gcs.go b/gcs/gcs.go index 6e30e643..2cb607e6 100644 --- a/gcs/gcs.go +++ b/gcs/gcs.go @@ -21,15 +21,16 @@ func Auth() bool { } if os.Getenv("GCLOUD_CREDENTIALS") != "" { + credFile := "/tmp/gcloud_credentials.json" // write the credentials content into a json file d := []byte(os.Getenv("GCLOUD_CREDENTIALS")) - err := ioutil.WriteFile("/tmp/credentials.json", d, 0644) + err := ioutil.WriteFile(credFile, d, 0644) if err != nil { log.Fatal("ERROR: Cannot create credentials file: ", err) } - os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "/tmp/credentials.json") + os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", credFile) return true } return false From 299ce1fca3630c2dfc6a7644844e66694a531046 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Wed, 7 Mar 2018 15:08:08 +0100 Subject: [PATCH 0103/1127] fixing #21 --- decision_maker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/decision_maker.go b/decision_maker.go index 5bdcd4c1..ea36091c 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -289,7 +289,7 @@ func getSetValues(r release) string { result := "" for k, v := range r.Set { _, value := envVarExists(v) - result = result + " --set " + k + "=" + value + result = result + " --set " + k + "=\"" + strings.Replace(value, ",", "\\,", -1) + "\"" } return result } From 703e49509e211745f215cf952f3790c71f192313 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Wed, 7 Mar 2018 15:34:12 +0100 Subject: [PATCH 0104/1127] adding windows build target in goreleaser. --- .goreleaser.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.goreleaser.yml b/.goreleaser.yml index d83fe9bf..58eb2f65 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -5,5 +5,6 @@ builds: goos: - darwin - linux + - windows goarch: - amd64 \ No newline at end of file From 0a57c33e4240129a2edb954c03cec3c9e05ee91a Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Wed, 7 Mar 2018 15:34:50 +0100 Subject: [PATCH 0105/1127] updating release notes for v1.0.2 --- release-notes.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/release-notes.md b/release-notes.md index e6b6f488..96ceffd8 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,3 +1,4 @@ -# v1.0.1 +# v1.0.2 -- Bug fixes for #19 and #20. \ No newline at end of file +- Bug fix for #21. +- Minor enhancements. \ No newline at end of file From bcdadc3a3d4d96bffe33976a392c4034e65fef3b Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Wed, 7 Mar 2018 15:41:36 +0100 Subject: [PATCH 0106/1127] updating README to add Windows support. [ci skip] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8ec12823..9b1e1387 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ To show debugging details: # Installation -Install Helmsman for your OS from the [releases page](https://github.com/Praqma/Helmsman/releases). Available for Linux and MacOS. +Install Helmsman for your OS from the [releases page](https://github.com/Praqma/Helmsman/releases). Available for Linux, MacOS & Windows. Also available as a [docker image](https://hub.docker.com/r/praqma/helmsman/). From 8026dffb6fca8c73e22562ec455c7155384a69bb Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Mon, 19 Mar 2018 11:47:24 +0100 Subject: [PATCH 0107/1127] adding static binding flag for the application build. --- .goreleaser.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index 58eb2f65..83cce6e1 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -2,9 +2,11 @@ # Build customization builds: - binary: helmsman + ldflags: -s -w -X main.build={{.Version}} -extldflags "-static" + env: + - CGO_ENABLED=0 goos: - darwin - linux - - windows goarch: - amd64 \ No newline at end of file From c1d1c5a3d045775e353727d3a2aaf87aea6cc7f5 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Mon, 19 Mar 2018 11:49:50 +0100 Subject: [PATCH 0108/1127] changing the debug logging to command description. --- command.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/command.go b/command.go index d5052178..db1bbdfd 100644 --- a/command.go +++ b/command.go @@ -32,7 +32,7 @@ func (c command) printFullCommand() { func (c command) exec(debug bool) (int, string) { if debug { - log.Println("INFO: executing command: " + c.Args[1]) + log.Println("INFO: " + c.Description) } cmd := exec.Command(c.Cmd, c.Args...) var out bytes.Buffer From f26aee8870dd975b7c482f9d0bde6cdbb4dffb2a Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Mon, 19 Mar 2018 11:51:47 +0100 Subject: [PATCH 0109/1127] adding support for the wait flag. --- decision_maker.go | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index ea36091c..9513277b 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -102,7 +102,7 @@ func installRelease(namespace string, r release) { releaseName := r.Name cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm install " + r.Chart + " -n " + releaseName + " --namespace " + namespace + getValuesFile(r) + " --version " + r.Version + getSetValues(r)}, + Args: []string{"-c", "helm install " + r.Chart + " -n " + releaseName + " --namespace " + namespace + getValuesFile(r) + " --version " + r.Version + getSetValues(r) + getWait(r)}, Description: "installing release [ " + releaseName + " ] in namespace [[ " + namespace + " ]]", } outcome.addCommand(cmd) @@ -124,7 +124,7 @@ func inspectRollbackScenario(namespace string, r release) { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm rollback " + releaseName + " " + getReleaseRevision(releaseName, "deleted")}, + Args: []string{"-c", "helm rollback " + releaseName + " " + getReleaseRevision(releaseName, "deleted") + getWait(r)}, Description: "rolling back release [ " + releaseName + " ]", } outcome.addCommand(cmd) @@ -221,7 +221,7 @@ func inspectUpgradeScenario(namespace string, r release) { func upgradeRelease(r release) { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm upgrade " + r.Name + " " + r.Chart + getValuesFile(r) + " --version " + r.Version + " --force " + getSetValues(r)}, + Args: []string{"-c", "helm upgrade " + r.Name + " " + r.Chart + getValuesFile(r) + " --version " + r.Version + " --force " + getSetValues(r) + getWait(r)}, Description: "upgrading release [ " + r.Name + " ]", } @@ -246,7 +246,7 @@ func reInstallRelease(namespace string, r release) { installCmd := command{ Cmd: "bash", - Args: []string{"-c", "helm install " + r.Chart + " --version " + r.Version + " -n " + releaseName + " --namespace " + namespace + getValuesFile(r) + getSetValues(r)}, + Args: []string{"-c", "helm install " + r.Chart + " --version " + r.Version + " -n " + releaseName + " --namespace " + namespace + getValuesFile(r) + getSetValues(r) + getWait(r)}, Description: "installing release [ " + releaseName + " ] in namespace [[ " + namespace + " ]]", } outcome.addCommand(installCmd) @@ -294,6 +294,16 @@ func getSetValues(r release) string { return result } +// getWait returns a partial helm command containing the helm wait flag (--wait) if the wait flag for the release was set to true +// Otherwise, retruns an empty string +func getWait(r release) string { + result := "" + if r.Wait { + result = " --wait" + } + return result +} + // getDesiredNamespace validates that namespace where the release is desired to be installed is defined in the Namespaces definition // it returns the namespace if it is already defined // otherwise, it throws an error From 3ddfc529bd21f93f361c5aad0366afbfcddeee3a Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Mon, 19 Mar 2018 11:52:22 +0100 Subject: [PATCH 0110/1127] adding a wait flag to releases. --- release.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/release.go b/release.go index 0d77df59..1cf78628 100644 --- a/release.go +++ b/release.go @@ -18,6 +18,7 @@ type release struct { Purge bool Test bool Protected bool + Wait bool Set map[string]string } @@ -62,6 +63,7 @@ func (r release) print() { fmt.Println("\tpurge : ", r.Purge) fmt.Println("\ttest : ", r.Test) fmt.Println("\tprotected : ", r.Protected) + fmt.Println("\twait : ", r.Wait) fmt.Println("\tvalues to override from env:") printMap(r.Set) fmt.Println("------------------- ") From 494ac27d395c4ab6550697b808f97288ff774fea Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Mon, 19 Mar 2018 11:53:07 +0100 Subject: [PATCH 0111/1127] adding a helmsman logo and slogan --- init.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/init.go b/init.go index e57095b1..cc117e87 100644 --- a/init.go +++ b/init.go @@ -7,6 +7,16 @@ import ( "os" ) +const ( + banner = " _ _ \n" + + "| | | | \n" + + "| |__ ___| |_ __ ___ ___ _ __ ___ __ _ _ __\n" + + "| '_ \\ / _ \\ | '_ ` _ \\/ __| '_ ` _ \\ / _` | '_ \\ \n" + + "| | | | __/ | | | | | \\__ \\ | | | | | (_| | | | | \n" + + "|_| |_|\\___|_|_| |_| |_|___/_| |_| |_|\\__,_|_| |_|" + slogan = "A Helm-Chart-as-Code tool.\n\n" +) + // init is executed after all package vars are initialized [before the main() func in this case]. // It checks if Helm and Kubectl exist and configures: the connection to the k8s cluster, helm repos, namespaces, etc. func init() { @@ -19,6 +29,8 @@ func init() { flag.Parse() + fmt.Println(banner + "version: " + version + "\n" + slogan) + if v { fmt.Println("Helmsman version: " + version) os.Exit(0) @@ -29,7 +41,7 @@ func init() { os.Exit(0) } - fmt.Println("Helmsman version: " + version) + //fmt.Println("Helmsman version: " + version) if !toolExists("helm") { log.Fatal("ERROR: helm is not installed/configured correctly. Aborting!") From 36bc9c27bac33b7222591cbebc68f40760c30a53 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Mon, 19 Mar 2018 11:53:51 +0100 Subject: [PATCH 0112/1127] adding log enhancment. --- utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils.go b/utils.go index fb33c8eb..0a3a84ba 100644 --- a/utils.go +++ b/utils.go @@ -34,7 +34,7 @@ func fromTOML(file string, s *state) (bool, string) { if _, err := toml.DecodeFile(file, s); err != nil { return false, err.Error() } - return true, "Parsed [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps." + return true, "INFO: Parsed [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps." } From c9114eda3e938c2646f2656ee1a53384aab406de Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Mon, 19 Mar 2018 12:09:07 +0100 Subject: [PATCH 0113/1127] bumping version --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index e6fc8d9c..1a5147cd 100644 --- a/main.go +++ b/main.go @@ -14,7 +14,7 @@ var file string var apply bool var help bool var v bool -var version = "master" +var version = "v1.0.2" func main() { From 80ba021f7e8f3746f6112b0948444a95cb6e2868 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Fri, 30 Mar 2018 20:25:30 +0200 Subject: [PATCH 0114/1127] improving logs with detailed error messages. --- command.go | 10 +++++----- plan.go | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/command.go b/command.go index db1bbdfd..c0b4e193 100644 --- a/command.go +++ b/command.go @@ -35,8 +35,9 @@ func (c command) exec(debug bool) (int, string) { log.Println("INFO: " + c.Description) } cmd := exec.Command(c.Cmd, c.Args...) - var out bytes.Buffer - cmd.Stdout = &out + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr if err := cmd.Start(); err != nil { log.Fatalf("ERROR: cmd.Start: %v", err) @@ -45,13 +46,12 @@ func (c command) exec(debug bool) (int, string) { if err := cmd.Wait(); err != nil { if exiterr, ok := err.(*exec.ExitError); ok { if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { - //log.Printf("Exit Status: %d", status.ExitStatus()) - return status.ExitStatus(), out.String() + return status.ExitStatus(), stderr.String() } } else { log.Fatalf("ERROR: cmd.Wait: %v", err) } } - return 0, out.String() + return 0, stdout.String() } diff --git a/plan.go b/plan.go index d16a8d42..47cf2155 100644 --- a/plan.go +++ b/plan.go @@ -58,8 +58,8 @@ func (p plan) printPlanCmds() { // printPlan prints the decisions made in a plan. func (p plan) printPlan() { - fmt.Println("---------------") - fmt.Printf("Ok, I have generated a plan for you at: %s \n", p.Created.Format("Mon Jan _2 2006 15:04:05")) + fmt.Println("----------------------") + fmt.Printf("Plan generated at: %s \n", p.Created.Format("Mon Jan _2 2006 15:04:05")) for _, decision := range p.Decisions { fmt.Println(decision) } From 2731d67dc3927681d000a2e6f20b929eb49bbd51 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Fri, 30 Mar 2018 20:27:40 +0200 Subject: [PATCH 0115/1127] fixing a bug in release search and improving logs --- helm_helpers.go | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/helm_helpers.go b/helm_helpers.go index b293bf3b..a7b14701 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -35,11 +35,13 @@ func helmReleaseExists(namespace string, releaseName string, scope string) bool Description: "listing the existing releases in namespace [ " + namespace + " ] with status [ " + scope + " ]", } - if exitCode, result := cmd.exec(debug); exitCode == 0 { - return strings.Contains(result, releaseName+"\n") + exitCode, result := cmd.exec(debug) + if exitCode == 0 { + + return sliceContains(strings.Split(result, "\n"), releaseName) } - log.Fatal("ERROR: something went wrong while checking helm release.") + log.Fatal("ERROR: while checking helm release: " + result) return false } @@ -73,7 +75,7 @@ func getReleaseChart(releaseName string) string { line := strings.Split(result, "\n")[1] return strings.Fields(line)[8] // 8 is the position of chart details in helm ls output } - log.Fatal("ERROR: seems release [ " + releaseName + " ] does not exist.") + log.Fatal("ERROR: release [ " + releaseName + " ] does not exist.") return "" } @@ -91,7 +93,7 @@ func getReleaseRevision(releaseName string, state string) string { line := strings.Split(result, "\n")[1] return strings.Fields(line)[1] // 1 is the position of revision number in helm ls output } - log.Fatal("ERROR: seems release [ " + releaseName + " ] does not exist.") + log.Fatal("ERROR: release [ " + releaseName + " ] does not exist.") return "" } @@ -121,11 +123,12 @@ func getReleaseStatus(releaseName string) string { Description: "inspecting the status of release: " + releaseName, } - if exitCode, result := cmd.exec(debug); exitCode == 0 { + exitCode, result := cmd.exec(debug) + if exitCode == 0 { return result } - log.Fatal("ERROR: something went wrong while checking release status.") + log.Fatal("ERROR: while checking release [ " + releaseName + " ] status: " + result) return "" } From e1af2b71e64c769814d404dc72dbfb0eb492d4ea Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Fri, 30 Mar 2018 20:29:05 +0200 Subject: [PATCH 0116/1127] adding util functions. --- utils.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/utils.go b/utils.go index 0a3a84ba..cfdb2a4b 100644 --- a/utils.go +++ b/utils.go @@ -106,3 +106,13 @@ func envVarExists(v string) (bool, string) { value, ok := os.LookupEnv(v) return ok, value } + +// sliceContains checks if a string slice contains a given string +func sliceContains(slice []string, s string) bool { + for _, a := range slice { + if strings.TrimSpace(a) == s { + return true + } + } + return false +} From 31b4fe0b9bb25d8f765e2ddebd775cf3af87afe6 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Fri, 30 Mar 2018 20:32:05 +0200 Subject: [PATCH 0117/1127] bumping charts versions --- example.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example.toml b/example.toml index 1d907ccd..1963e0c8 100644 --- a/example.toml +++ b/example.toml @@ -52,7 +52,7 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" namespace = "staging" # maps to the namespace as defined in environmetns above enabled = true # change to false if you want to delete this app release [empty = flase] chart = "stable/jenkins" # changing the chart name means delete and recreate this chart - version = "0.9.1" # chart version + version = "0.14.3" # chart version valuesFile = "" # leaving it empty uses the default chart values purge = false # will only be considered when there is a delete operation test = false # run the tests when this release is installed for the first time only @@ -68,7 +68,7 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" namespace = "staging" # maps to the namespace as defined in environmetns above enabled = true # change to false if you want to delete this app release [empty = flase] chart = "stable/artifactory" # changing the chart name means delete and recreate this chart - version = "6.2.0" # chart version + version = "7.0.6" # chart version valuesFile = "" # leaving it empty uses the default chart values purge = false # will only be considered when there is a delete operation test = false # run the tests when this release is installed for the first time only From 614d02836470a9ec6d67b4af38cb6204abe571a2 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Fri, 30 Mar 2018 20:36:50 +0200 Subject: [PATCH 0118/1127] fixing not waiting for helm Tiller to be ready and improving logs. --- main.go | 73 +++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 58 insertions(+), 15 deletions(-) diff --git a/main.go b/main.go index 1a5147cd..63635454 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,7 @@ package main import ( "log" "strings" + "time" "github.com/Praqma/helmsman/aws" "github.com/Praqma/helmsman/gcs" @@ -34,7 +35,7 @@ func main() { log.Fatal(msg) } - // validate charts-versions exist in supllied repos + // validate charts-versions exist in defined repos if r, msg := validateReleaseCharts(s.Apps); !r { log.Fatal(msg) } @@ -42,6 +43,9 @@ func main() { // add/validate namespaces addNamespaces(s.Namespaces) + // check if helm Tiller is ready + waitForTiller() + p := makePlan(&s) if !apply { @@ -76,11 +80,11 @@ func initHelm() (bool, string) { cmd := command{ Cmd: "bash", Args: []string{"-c", "helm init --upgrade"}, - Description: "initializing helm on the current context and upgrade Tiller.", + Description: "initializing helm on the current context and upgrading Tiller.", } - if exitCode, _ := cmd.exec(debug); exitCode != 0 { - return false, "ERROR: there was a problem while initializing helm. " + if exitCode, err := cmd.exec(debug); exitCode != 0 { + return false, "ERROR: while initializing helm: " + err } return true, "" } @@ -101,12 +105,22 @@ func addHelmRepos(repos map[string]string) (bool, string) { Description: "adding repo " + repoName, } - if exitCode, _ := cmd.exec(debug); exitCode != 0 { - return false, "ERROR: there was a problem while adding repo [" + repoName + "]." + if exitCode, err := cmd.exec(debug); exitCode != 0 { + return false, "ERROR: while adding repo [" + repoName + "]: " + err } } + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm repo update "}, + Description: "updating helm repos", + } + + if exitCode, err := cmd.exec(debug); exitCode != 0 { + return false, "ERROR: while updating helm repos : " + err + } + return true, "" } @@ -119,12 +133,12 @@ func validateReleaseCharts(apps map[string]release) (bool, string) { cmd := command{ Cmd: "bash", Args: []string{"-c", "helm search " + r.Chart + " --version " + r.Version}, - Description: "validating chart " + r.Chart + "-" + r.Version + " is available in the used repos.", + Description: "validating if chart " + r.Chart + "-" + r.Version + " is available in the defined repos.", } - if exitCode, _ := cmd.exec(debug); exitCode != 0 { + if exitCode, result := cmd.exec(debug); exitCode != 0 || strings.Contains(result, "No results found") { return false, "ERROR: chart " + r.Chart + "-" + r.Version + " is specified for " + - "app [" + app + "] but is not found in the provided repos." + "app [" + app + "] but is not found in the defined repos." } } return true, "" @@ -239,8 +253,8 @@ func createContext() (bool, string) { Description: "creating kubectl context - setting credentials.", } - if exitCode, _ := cmd.exec(debug); exitCode != 0 { - return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ]. " + if exitCode, err := cmd.exec(debug); exitCode != 0 { + return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ]: " + err } cmd = command{ @@ -250,8 +264,8 @@ func createContext() (bool, string) { Description: "creating kubectl context - setting cluster.", } - if exitCode, _ := cmd.exec(debug); exitCode != 0 { - return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ]." + if exitCode, err := cmd.exec(debug); exitCode != 0 { + return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ]: " + err } cmd = command{ @@ -261,8 +275,8 @@ func createContext() (bool, string) { Description: "creating kubectl context - setting context.", } - if exitCode, _ := cmd.exec(debug); exitCode != 0 { - return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ]." + if exitCode, err := cmd.exec(debug); exitCode != 0 { + return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ]: " + err } if setKubeContext(s.Settings["kubeContext"]) { @@ -283,3 +297,32 @@ func getBucketElements(link string) map[string]string { m["filePath"] = strings.SplitN(tmp, "/", 2)[1] return m } + +// waitForTiller keeps checking if the helm Tiller is ready or not by executing helm list and checking its error (if any) +// waits for 5 seconds before each new attempt and eventually terminates after 10 failed attempts. +func waitForTiller() { + + attempt := 0 + + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm list"}, + Description: "checking Tiller is ready.", + } + + exitCode, err := cmd.exec(debug) + + for attempt < 10 { + if exitCode == 0 { + return + } else if strings.Contains(err, "could not find a ready tiller pod") { + log.Println("INFO: waiting for helm Tiller to be ready ...") + time.Sleep(5 * time.Second) + exitCode, err = cmd.exec(debug) + } else { + log.Fatal("ERROR: while waiting for helm Tiller to be ready : " + err) + } + attempt = attempt + 1 + } + log.Fatal("ERROR: timeout reached while waiting for helm Tiller to be ready. Aborting!") +} From 38650b1ac5f530e584b34f2290f690ba2bd168f7 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 31 Mar 2018 10:37:38 +0200 Subject: [PATCH 0119/1127] adding --verbose flag. --- command.go | 8 ++++++-- helm_helpers.go | 8 ++++---- init.go | 5 +++-- main.go | 29 +++++++++++++++++------------ plan.go | 2 +- utils.go | 38 +++++++++++++++++++++++++++++++++----- 6 files changed, 64 insertions(+), 26 deletions(-) diff --git a/command.go b/command.go index c0b4e193..1873bbc0 100644 --- a/command.go +++ b/command.go @@ -29,11 +29,15 @@ func (c command) printFullCommand() { } // exec executes the executable command and returns the exit code and execution result -func (c command) exec(debug bool) (int, string) { +func (c command) exec(debug bool, verbose bool) (int, string) { - if debug { + if debug || verbose { log.Println("INFO: " + c.Description) } + if verbose { + log.Println("VERBOSE: " + c.Args[1]) + } + cmd := exec.Command(c.Cmd, c.Args...) var stdout, stderr bytes.Buffer cmd.Stdout = &stdout diff --git a/helm_helpers.go b/helm_helpers.go index a7b14701..4ecef629 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -35,7 +35,7 @@ func helmReleaseExists(namespace string, releaseName string, scope string) bool Description: "listing the existing releases in namespace [ " + namespace + " ] with status [ " + scope + " ]", } - exitCode, result := cmd.exec(debug) + exitCode, result := cmd.exec(debug, verbose) if exitCode == 0 { return sliceContains(strings.Split(result, "\n"), releaseName) @@ -69,7 +69,7 @@ func getReleaseChart(releaseName string) string { Args: []string{"-c", "helm list " + releaseName}, Description: "inspecting the chart used for release: " + releaseName, } - exitCode, result := cmd.exec(debug) + exitCode, result := cmd.exec(debug, verbose) if exitCode == 0 { line := strings.Split(result, "\n")[1] @@ -87,7 +87,7 @@ func getReleaseRevision(releaseName string, state string) string { Args: []string{"-c", "helm list " + releaseName + " --" + state}, Description: "inspecting the release revision for : " + releaseName, } - exitCode, result := cmd.exec(debug) + exitCode, result := cmd.exec(debug, verbose) if exitCode == 0 { line := strings.Split(result, "\n")[1] @@ -123,7 +123,7 @@ func getReleaseStatus(releaseName string) string { Description: "inspecting the status of release: " + releaseName, } - exitCode, result := cmd.exec(debug) + exitCode, result := cmd.exec(debug, verbose) if exitCode == 0 { return result } diff --git a/init.go b/init.go index cc117e87..1a42cf14 100644 --- a/init.go +++ b/init.go @@ -14,7 +14,7 @@ const ( "| '_ \\ / _ \\ | '_ ` _ \\/ __| '_ ` _ \\ / _` | '_ \\ \n" + "| | | | __/ | | | | | \\__ \\ | | | | | (_| | | | | \n" + "|_| |_|\\___|_|_| |_| |_|___/_| |_| |_|\\__,_|_| |_|" - slogan = "A Helm-Chart-as-Code tool.\n\n" + slogan = "A Helm-Charts-as-Code tool.\n\n" ) // init is executed after all package vars are initialized [before the main() func in this case]. @@ -26,6 +26,7 @@ func init() { flag.BoolVar(&debug, "debug", false, "show the execution logs") flag.BoolVar(&help, "help", false, "show Helmsman help") flag.BoolVar(&v, "v", false, "show the version") + flag.BoolVar(&verbose, "verbose", false, "show verbose execution logs") flag.Parse() @@ -75,7 +76,7 @@ func toolExists(tool string) bool { Description: "validating that " + tool + " is installed.", } - exitCode, _ := cmd.exec(debug) + exitCode, _ := cmd.exec(debug, false) if exitCode != 0 { return false diff --git a/main.go b/main.go index 63635454..c04601d2 100644 --- a/main.go +++ b/main.go @@ -15,6 +15,7 @@ var file string var apply bool var help bool var v bool +var verbose bool var version = "v1.0.2" func main() { @@ -30,6 +31,10 @@ func main() { log.Fatal(msg) } + if verbose { + logVersions() + } + // add repos -- fails if they are not valid if r, msg := addHelmRepos(s.HelmRepos); !r { log.Fatal(msg) @@ -65,7 +70,7 @@ func setKubeContext(context string) bool { Description: "setting kubectl context to [ " + context + " ]", } - exitCode, _ := cmd.exec(debug) + exitCode, _ := cmd.exec(debug, verbose) if exitCode != 0 { log.Println("INFO: KubeContext: " + context + " does not exist. I will try to create it.") @@ -83,7 +88,7 @@ func initHelm() (bool, string) { Description: "initializing helm on the current context and upgrading Tiller.", } - if exitCode, err := cmd.exec(debug); exitCode != 0 { + if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { return false, "ERROR: while initializing helm: " + err } return true, "" @@ -105,7 +110,7 @@ func addHelmRepos(repos map[string]string) (bool, string) { Description: "adding repo " + repoName, } - if exitCode, err := cmd.exec(debug); exitCode != 0 { + if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { return false, "ERROR: while adding repo [" + repoName + "]: " + err } @@ -117,7 +122,7 @@ func addHelmRepos(repos map[string]string) (bool, string) { Description: "updating helm repos", } - if exitCode, err := cmd.exec(debug); exitCode != 0 { + if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { return false, "ERROR: while updating helm repos : " + err } @@ -136,7 +141,7 @@ func validateReleaseCharts(apps map[string]release) (bool, string) { Description: "validating if chart " + r.Chart + "-" + r.Version + " is available in the defined repos.", } - if exitCode, result := cmd.exec(debug); exitCode != 0 || strings.Contains(result, "No results found") { + if exitCode, result := cmd.exec(debug, verbose); exitCode != 0 || strings.Contains(result, "No results found") { return false, "ERROR: chart " + r.Chart + "-" + r.Version + " is specified for " + "app [" + app + "] but is not found in the defined repos." } @@ -154,7 +159,7 @@ func addNamespaces(namespaces map[string]namespace) { Description: "creating namespace " + ns, } - if exitCode, _ := cmd.exec(debug); exitCode != 0 { + if exitCode, _ := cmd.exec(debug, verbose); exitCode != 0 { log.Println("WARN: I could not create namespace [" + ns + " ]. It already exists. I am skipping this.") } @@ -253,7 +258,7 @@ func createContext() (bool, string) { Description: "creating kubectl context - setting credentials.", } - if exitCode, err := cmd.exec(debug); exitCode != 0 { + if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ]: " + err } @@ -264,7 +269,7 @@ func createContext() (bool, string) { Description: "creating kubectl context - setting cluster.", } - if exitCode, err := cmd.exec(debug); exitCode != 0 { + if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ]: " + err } @@ -275,7 +280,7 @@ func createContext() (bool, string) { Description: "creating kubectl context - setting context.", } - if exitCode, err := cmd.exec(debug); exitCode != 0 { + if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ]: " + err } @@ -307,10 +312,10 @@ func waitForTiller() { cmd := command{ Cmd: "bash", Args: []string{"-c", "helm list"}, - Description: "checking Tiller is ready.", + Description: "checking if helm Tiller is ready.", } - exitCode, err := cmd.exec(debug) + exitCode, err := cmd.exec(debug, verbose) for attempt < 10 { if exitCode == 0 { @@ -318,7 +323,7 @@ func waitForTiller() { } else if strings.Contains(err, "could not find a ready tiller pod") { log.Println("INFO: waiting for helm Tiller to be ready ...") time.Sleep(5 * time.Second) - exitCode, err = cmd.exec(debug) + exitCode, err = cmd.exec(debug, verbose) } else { log.Fatal("ERROR: while waiting for helm Tiller to be ready : " + err) } diff --git a/plan.go b/plan.go index 47cf2155..ae0684c2 100644 --- a/plan.go +++ b/plan.go @@ -42,7 +42,7 @@ func (p plan) execPlan() { p.printPlan() for _, cmd := range p.Commands { log.Println("INFO: attempting: -- ", cmd.Description) - if exitCode, msg := cmd.exec(debug); exitCode != 0 { + if exitCode, msg := cmd.exec(debug, verbose); exitCode != 0 { log.Fatal("Command returned with exit code: " + string(exitCode) + ". And error message: " + msg) } } diff --git a/utils.go b/utils.go index cfdb2a4b..0e6719f6 100644 --- a/utils.go +++ b/utils.go @@ -84,14 +84,42 @@ func readFile(filepath string) string { func printHelp() { fmt.Println("Helmsman version: " + version) fmt.Println("Helmsman is a Helm Charts as Code tool which allows you to automate the deployment/management of your Helm charts.") - fmt.Println(" Usage: helmsman [options]") + fmt.Println("Usage: helmsman [options]") fmt.Println() fmt.Println("Options:") - fmt.Println("-f specifies the desired state TOML file.") - fmt.Println("-debug prints all the logs during execution.") - fmt.Println("-apply generates and applies an action plan.") - fmt.Println("-help prints Helmsman help.") + fmt.Println("-f specifies the desired state TOML file.") + fmt.Println("-debug prints basic logs during execution.") + fmt.Println("-verbose prints more verbose logs during execution.") + fmt.Println("-apply generates and applies an action plan.") + fmt.Println("-help prints Helmsman help.") + fmt.Println("-v prints Helmsman version.") +} + +func logVersions() { + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "kubectl version"}, + Description: "Kubectl version: ", + } + + exitCode, result := cmd.exec(debug, false) + if exitCode != 0 { + log.Fatal("ERROR: while checking kubectl version: " + result) + } + log.Println("VERBOSE: kubectl version: \n " + result + "\n") + + cmd = command{ + Cmd: "bash", + Args: []string{"-c", "helm version"}, + Description: "Helm version: ", + } + + exitCode, result = cmd.exec(debug, false) + if exitCode != 0 { + log.Fatal("ERROR: while checking helm version: " + result) + } + log.Println("VERBOSE: helm version: \n" + result + "\n") } // envVarExists checks if an environment variable is set or not and returns it. From 15a9f1ce9caab0abf30151efeef885f8382d4c37 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 31 Mar 2018 10:38:22 +0200 Subject: [PATCH 0120/1127] bumping tools versions. --- dockerfile/dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile index 8ab9f40e..d8d97238 100644 --- a/dockerfile/dockerfile +++ b/dockerfile/dockerfile @@ -14,7 +14,7 @@ RUN cd helmsman \ && CGO_ENABLED=0 GOOS=linux go install -a -ldflags '-X main.version='$TAG' -extldflags "-static"' . FROM alpine:3.6 as kube -ENV KUBE_LATEST_VERSION="v1.8.2" +ENV KUBE_LATEST_VERSION="v1.10.0" RUN apk add --update ca-certificates RUN apk add --update -t deps curl @@ -25,7 +25,7 @@ RUN chmod +x /usr/local/bin/kubectl FROM alpine:3.7 RUN apk add --update --no-cache ca-certificates git -ENV HELM_VERSION v2.7.0 +ARG HELM_VERSION=v2.8.1 RUN apk --no-cache update \ && rm -rf /var/cache/apk/* \ @@ -34,7 +34,7 @@ RUN apk --no-cache update \ && mv /tmp/linux-amd64/helm /usr/local/bin/helm \ && chmod +x /usr/local/bin/helm \ && mkdir -p ~/.helm/plugins \ - && helm plugin install https://github.com/hypnoglow/helm-s3.git --version 0.4.0 \ + && helm plugin install https://github.com/hypnoglow/helm-s3.git \ && helm plugin install https://github.com/nouney/helm-gcs \ && rm -rf /tmp/linux-amd64 From 792f452474cacc44cfed9b5bdb9ff5632a28a8a6 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 31 Mar 2018 10:38:51 +0200 Subject: [PATCH 0121/1127] adding docker build and push steps --- .circleci/config.yml | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 927ec596..e6ec5e50 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -16,7 +16,7 @@ jobs: go get github.com/Praqma/helmsman/aws echo "building ..." TAG=$(git describe --abbrev=0 --tags)-$(date +"%s") - go build -ldflags "-X main.version="$TAG + go build -ldflags '-X main.version='$TAG' -extldflags "-static"' test: docker: @@ -43,7 +43,22 @@ jobs: go get github.com/Praqma/helmsman/gcs go get github.com/Praqma/helmsman/aws echo "releasing ..." - goreleaser --release-notes release-notes.md + goreleaser --release-notes release-notes.md + + - setup_remote_docker + - run: + name: build docker images and push them to dockerhub + command: | + TAG=$(git describe --abbrev=0 --tags) + docker login -u samincl -p $DOCKERHUB + docker build -t praqma/helmsman:$TAG-helm-v2.8.1 --build-arg HELM_VERSION=v2.8.1 dockerfile/. + docker push praqma/helmsman:$TAG-helm-v2.8.1 + docker build -t praqma/helmsman:$TAG-helm-v2.8.0 --build-arg HELM_VERSION=v2.8.0 dockerfile/. + docker push praqma/helmsman:$TAG-helm-v2.8.0 + docker build -t praqma/helmsman:$TAG-helm-v2.7.2 --build-arg HELM_VERSION=v2.7.2 dockerfile/. + docker push praqma/helmsman:$TAG-helm-v2.7.2 + + workflows: version: 2 build-test-push-release: From 2f987d33d579ac612eda70166e16013ef436789b Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 31 Mar 2018 10:51:49 +0200 Subject: [PATCH 0122/1127] updating date format in version numbers. --- .circleci/config.yml | 2 +- dockerfile/dockerfile | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e6ec5e50..1a73239e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -15,7 +15,7 @@ jobs: go get github.com/Praqma/helmsman/gcs go get github.com/Praqma/helmsman/aws echo "building ..." - TAG=$(git describe --abbrev=0 --tags)-$(date +"%s") + TAG=$(git describe --abbrev=0 --tags)-$(date +"%d%m%y") go build -ldflags '-X main.version='$TAG' -extldflags "-static"' test: diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile index d8d97238..dba8a34b 100644 --- a/dockerfile/dockerfile +++ b/dockerfile/dockerfile @@ -7,10 +7,10 @@ RUN go get github.com/Praqma/helmsman/aws # build a statically linked binary so that it works on stripped linux images such as alpine/busybox. RUN cd helmsman \ && LastTag=$(git describe --abbrev=0 --tags) \ - && TAG=$LastTag-$(date +"%s") \ + && TAG=$LastTag-$(date +"%d%m%y") \ && LT_SHA=$(git rev-parse ${LastTag}^{}) \ && LC_SHA=$(git rev-parse HEAD) \ - && if [ ${LT_SHA} != ${LC_SHA} ]; then TAG=latest-$(date +"%s"); fi \ + && if [ ${LT_SHA} != ${LC_SHA} ]; then TAG=latest-$(date +"%d%m%y"); fi \ && CGO_ENABLED=0 GOOS=linux go install -a -ldflags '-X main.version='$TAG' -extldflags "-static"' . FROM alpine:3.6 as kube From 01ab053458673b77a48e893ab8cccdeed3dbd966 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 31 Mar 2018 11:41:08 +0200 Subject: [PATCH 0123/1127] adding serviceaccount support and improving state validation. --- example.toml | 1 + main.go | 11 ++++++++++- state.go | 52 ++++++++++++++++++++++++++++++++++------------------ utils.go | 14 ++++++++++++++ 4 files changed, 59 insertions(+), 19 deletions(-) diff --git a/example.toml b/example.toml index 1963e0c8..fe1bd184 100644 --- a/example.toml +++ b/example.toml @@ -19,6 +19,7 @@ kubeContext = "minikube" # will try connect to this context first, if it does no #password = "$K8S_PASSWORD" # the name of an environment variable containing the k8s password #clusterURI = "$K8S_URI" # the name of an environment variable containing the cluster API ##clusterURI = "https://192.168.99.100:8443" # equivalent to the above +#serviceAccount = "foo" # k8s serviceaccount must be already defined, validation error will be thrown otherwise # define your environments and thier k8s namespaces # syntax: diff --git a/main.go b/main.go index c04601d2..4b7ee0c1 100644 --- a/main.go +++ b/main.go @@ -82,9 +82,18 @@ func setKubeContext(context string) bool { // initHelm initialize helm on a k8s cluster func initHelm() (bool, string) { + serviceAccount := "" + if value, ok := s.Settings["serviceAccount"]; ok { + if ok, err := validateSerrviceAccount(value); ok { + serviceAccount = "--service-account " + value + } else { + return false, "ERROR: while initializing helm: " + err + } + + } cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm init --upgrade"}, + Args: []string{"-c", "helm init --upgrade " + serviceAccount}, Description: "initializing helm on the current context and upgrading Tiller.", } diff --git a/state.go b/state.go index 26d18b22..ff58ba25 100644 --- a/state.go +++ b/state.go @@ -30,40 +30,56 @@ func (s state) validate() (bool, string) { // settings if s.Settings == nil { return false, "ERROR: settings validation failed -- no settings table provided in TOML." - } else if s.Settings["kubeContext"] == "" { + } else if value, ok := s.Settings["kubeContext"]; !ok || value == "" { return false, "ERROR: settings validation failed -- you have not provided a " + "kubeContext to use. Can't work without it. Sorry!" - } else if len(s.Settings) > 1 { - s.Settings["password"] = subsituteEnv(s.Settings["password"]) - s.Settings["clusterURI"] = subsituteEnv(s.Settings["clusterURI"]) + } else if value, ok = s.Settings["clusterURI"]; ok { + + s.Settings["clusterURI"] = subsituteEnv(value) + if _, err := url.ParseRequestURI(s.Settings["clusterURI"]); err != nil { + return false, "ERROR: settings validation failed -- clusterURI must have a valid URL set in an env varibale or passed directly. Either the env var is missing/empty or the URL is invalid." + } + + if _, ok = s.Settings["username"]; !ok { + return false, "ERROR: settings validation failed -- username must be provided if clusterURI is defined." + } + if value, ok = s.Settings["password"]; ok { + s.Settings["password"] = subsituteEnv(value) + } else { + return false, "ERROR: settings validation failed -- password must be provided if clusterURI is defined." + } if s.Settings["password"] == "" { return false, "ERROR: settings validation failed -- password should be set as an env variable. It is currently missing or empty. " - } else if _, err := url.ParseRequestURI(s.Settings["clusterURI"]); err != nil { - return false, "ERROR: settings validation failed -- clusterURI must have a valid URL set in an env varibale or passed directly. Either the env var is missing/empty or the URL is invalid." } } // certificates if s.Certificates != nil { - if len(s.Settings) > 1 && len(s.Certificates) < 2 { + _, ok1 := s.Settings["clusterURI"] + _, ok2 := s.Certificates["caCrt"] + _, ok3 := s.Certificates["caKey"] + if ok1 && (!ok2 || !ok3) { return false, "ERROR: certifications validation failed -- You want me to connect to your cluster for you " + - "but have not given me the keys to do so. Please add [caCrt] and [caKey] under Certifications. You might also need to provide [clientCrt]." - } - for key, value := range s.Certificates { - tmp := subsituteEnv(value) - _, err1 := url.ParseRequestURI(tmp) - _, err2 := os.Stat(tmp) - if (err1 != nil || (!strings.HasPrefix(tmp, "s3://") && !strings.HasPrefix(tmp, "gs://"))) && err2 != nil { - return false, "ERROR: certifications validation failed -- [ " + key + " ] must be a valid S3 or GCS bucket URL or a valid relative file path." + "but have not given me the cert/key to do so. Please add [caCrt] and [caKey] under Certifications. You might also need to provide [clientCrt]." + } else if ok1 { + for key, value := range s.Certificates { + tmp := subsituteEnv(value) + _, err1 := url.ParseRequestURI(tmp) + _, err2 := os.Stat(tmp) + if (err1 != nil || (!strings.HasPrefix(tmp, "s3://") && !strings.HasPrefix(tmp, "gs://"))) && err2 != nil { + return false, "ERROR: certifications validation failed -- [ " + key + " ] must be a valid S3 or GCS bucket URL or a valid relative file path." + } + s.Certificates[key] = tmp } - s.Certificates[key] = tmp + } else { + log.Println("INFO: certificates provided but not needed. Skipping certificates validation.") } } else { - if len(s.Settings) > 1 { + if _, ok := s.Settings["clusterURI"]; ok { return false, "ERROR: certifications validation failed -- You want me to connect to your cluster for you " + - "but have not given me the keys to do so. Please add [caCrt] and [caKey] under Certifications. You might also need to provide [clientCrt]." + "but have not given me the cert/key to do so. Please add [caCrt] and [caKey] under Certifications. You might also need to provide [clientCrt]." } } diff --git a/utils.go b/utils.go index 0e6719f6..a6a8b4fd 100644 --- a/utils.go +++ b/utils.go @@ -144,3 +144,17 @@ func sliceContains(slice []string, s string) bool { } return false } + +// validateSerrviceAccount checks if k8s service account exists +func validateSerrviceAccount(sa string) (bool, string) { + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "kubectl get serviceaccount " + sa}, + Description: "validating that serviceaccount [ " + sa + " ] exists.", + } + + if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { + return false, err + } + return true, "" +} From 0d093dbbb7f87c98fbd911659da44129d79568cb Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 31 Mar 2018 11:54:09 +0200 Subject: [PATCH 0124/1127] updating tests. --- command_test.go | 7 ++++--- plan_test.go | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/command_test.go b/command_test.go index 88e62f82..45b5c397 100644 --- a/command_test.go +++ b/command_test.go @@ -60,7 +60,8 @@ func Test_command_exec(t *testing.T) { Description string } type args struct { - debug bool + debug bool + verbose bool } tests := []struct { name string @@ -76,7 +77,7 @@ func Test_command_exec(t *testing.T) { Args: []string{"-c", "echo this is fun"}, Description: "A bash command execution test with echo.", }, - args: args{debug: false}, + args: args{debug: false, verbose: false}, want: 0, want1: "this is fun", }, { @@ -98,7 +99,7 @@ func Test_command_exec(t *testing.T) { Args: tt.fields.Args, Description: tt.fields.Description, } - got, got1 := c.exec(tt.args.debug) + got, got1 := c.exec(tt.args.debug, tt.args.verbose) if got != tt.want { t.Errorf("command.exec() got = %v, want %v", got, tt.want) } diff --git a/plan_test.go b/plan_test.go index 664ca4a7..f564dd8b 100644 --- a/plan_test.go +++ b/plan_test.go @@ -154,7 +154,7 @@ func Test_plan_execPlan(t *testing.T) { Args: []string{"-c", "ls | grep hello.world | wc -l"}, Description: "", } - if _, got := c.exec(false); strings.TrimSpace(got) != "2" { + if _, got := c.exec(false, false); strings.TrimSpace(got) != "2" { t.Errorf("execPlan(): got %v, want hello world, again!", got) } }) From 345d350bc0016993f192d9b068fc5102a35fdf45 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 31 Mar 2018 11:54:38 +0200 Subject: [PATCH 0125/1127] improving validation checks. --- state.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/state.go b/state.go index ff58ba25..bf7e16af 100644 --- a/state.go +++ b/state.go @@ -28,7 +28,7 @@ type state struct { func (s state) validate() (bool, string) { // settings - if s.Settings == nil { + if s.Settings == nil || len(s.Settings) == 0 { return false, "ERROR: settings validation failed -- no settings table provided in TOML." } else if value, ok := s.Settings["kubeContext"]; !ok || value == "" { return false, "ERROR: settings validation failed -- you have not provided a " + @@ -55,7 +55,7 @@ func (s state) validate() (bool, string) { } // certificates - if s.Certificates != nil { + if s.Certificates != nil && len(s.Certificates) != 0 { _, ok1 := s.Settings["clusterURI"] _, ok2 := s.Certificates["caCrt"] _, ok3 := s.Certificates["caKey"] @@ -84,13 +84,13 @@ func (s state) validate() (bool, string) { } // namespaces - if s.Namespaces == nil || len(s.Namespaces) < 1 { + if s.Namespaces == nil || len(s.Namespaces) == 0 { return false, "ERROR: namespaces validation failed -- I need at least one namespace " + "to work with!" } // repos - if s.HelmRepos == nil || len(s.HelmRepos) < 1 { + if s.HelmRepos == nil || len(s.HelmRepos) == 0 { return false, "ERROR: repos validation failed -- I need at least one helm repo " + "to work with!" } From 8de545880e676d61fe047e173551103dec6bfef2 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 31 Mar 2018 15:54:48 +0200 Subject: [PATCH 0126/1127] adding priorities support for apps operations. --- decision_maker.go | 113 ++++++++++++++++++++++++---------------------- plan.go | 66 +++++++++++++++++++++------ release.go | 4 ++ 3 files changed, 113 insertions(+), 70 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index 9513277b..aa7c68df 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -26,8 +26,8 @@ func decide(r release, s *state) { if !isProtected(r) { inspectDeleteScenario(getDesiredNamespace(r), r) } else { - logDecision("DECISION: release " + r.Name + " is PROTECTED. Operations are not allowed on this release until " + - "you remove its protection.") + logDecision("DECISION: release "+r.Name+" is PROTECTED. Operations are not allowed on this release until "+ + "you remove its protection.", r.Priority) } } else { // check for install/upgrade/rollback @@ -35,8 +35,8 @@ func decide(r release, s *state) { if !isProtected(r) { inspectUpgradeScenario(getDesiredNamespace(r), r) // upgrade } else { - logDecision("DECISION: release " + r.Name + " is PROTECTED. Operations are not allowed on this release until " + - "you remove its protection.") + logDecision("DECISION: release "+r.Name+" is PROTECTED. Operations are not allowed on this release until "+ + "you remove its protection.", r.Priority) } } else if helmReleaseExists("", r.Name, "deleted") { @@ -45,8 +45,8 @@ func decide(r release, s *state) { inspectRollbackScenario(getDesiredNamespace(r), r) // rollback } else { - logDecision("DECISION: release " + r.Name + " is PROTECTED. Operations are not allowed on this release until " + - "you remove its protection.") + logDecision("DECISION: release "+r.Name+" is PROTECTED. Operations are not allowed on this release until "+ + "you remove its protection.", r.Priority) } } else if helmReleaseExists("", r.Name, "failed") { @@ -56,8 +56,8 @@ func decide(r release, s *state) { reInstallRelease(getDesiredNamespace(r), r) // re-install failed release } else { - logDecision("DECISION: release " + r.Name + " is PROTECTED. Operations are not allowed on this release until " + - "you remove its protection.") + logDecision("DECISION: release "+r.Name+" is PROTECTED. Operations are not allowed on this release until "+ + "you remove its protection.", r.Priority) } } else if helmReleaseExists("", r.Name, "all") { // not deployed in the desired namespace but deployed elsewhere @@ -65,12 +65,13 @@ func decide(r release, s *state) { if !isProtected(r) { reInstallRelease(getDesiredNamespace(r), r) // move the release to a new (the desired) namespace - logDecision("WARNING: moving release [ " + r.Name + " ] from [[ " + getReleaseNamespace(r.Name) + " ]] to [[ " + getDesiredNamespace(r) + - " ]] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md for details if this release uses PV and PVC.") + logDecision("WARNING: moving release [ "+r.Name+" ] from [[ "+getReleaseNamespace(r.Name)+" ]] to [[ "+getDesiredNamespace(r)+ + " ]] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md"+ + " for details if this release uses PV and PVC.", r.Priority) } else { - logDecision("DECISION: release " + r.Name + " is PROTECTED. Operations are not allowed on this release until " + - "you remove its protection.") + logDecision("DECISION: release "+r.Name+" is PROTECTED. Operations are not allowed on this release until "+ + "you remove its protection.", r.Priority) } } else { @@ -84,15 +85,15 @@ func decide(r release, s *state) { } // testRelease creates a Helm command to test a particular release. -func testRelease(releaseName string) { +func testRelease(r release) { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm test " + releaseName}, - Description: "running tests for release [ " + releaseName + " ]", + Args: []string{"-c", "helm test " + r.Name}, + Description: "running tests for release [ " + r.Name + " ]", } - outcome.addCommand(cmd) - logDecision("DECISION: release [ " + releaseName + " ] is required to be tested when installed/upgraded/rolledback. Got it!") + outcome.addCommand(cmd, r.Priority) + logDecision("DECISION: release [ "+r.Name+" ] is required to be tested when installed/upgraded/rolledback. Got it!", r.Priority) } @@ -105,12 +106,12 @@ func installRelease(namespace string, r release) { Args: []string{"-c", "helm install " + r.Chart + " -n " + releaseName + " --namespace " + namespace + getValuesFile(r) + " --version " + r.Version + getSetValues(r) + getWait(r)}, Description: "installing release [ " + releaseName + " ] in namespace [[ " + namespace + " ]]", } - outcome.addCommand(cmd) - logDecision("DECISION: release [ " + releaseName + " ] is not present in the current k8s context. Will install it in namespace [[ " + - namespace + " ]]") + outcome.addCommand(cmd, r.Priority) + logDecision("DECISION: release [ "+releaseName+" ] is not present in the current k8s context. Will install it in namespace [[ "+ + namespace+" ]]", r.Priority) if r.Test { - testRelease(releaseName) + testRelease(r) } } @@ -127,21 +128,22 @@ func inspectRollbackScenario(namespace string, r release) { Args: []string{"-c", "helm rollback " + releaseName + " " + getReleaseRevision(releaseName, "deleted") + getWait(r)}, Description: "rolling back release [ " + releaseName + " ]", } - outcome.addCommand(cmd) - logDecision("DECISION: release [ " + releaseName + " ] is currently deleted and is desired to be rolledback to " + - "namespace [[ " + namespace + " ]] . No problem!") + outcome.addCommand(cmd, r.Priority) + logDecision("DECISION: release [ "+releaseName+" ] is currently deleted and is desired to be rolledback to "+ + "namespace [[ "+namespace+" ]] . No problem!", r.Priority) // if r.Test { - // testRelease(releaseName) + // testRelease(r) // } } else { reInstallRelease(namespace, r) - logDecision("DECISION: release [ " + releaseName + " ] is deleted BUT from namespace [[ " + getReleaseNamespace(releaseName) + - " ]]. Will purge delete it from there and install it in namespace [[ " + namespace + " ]]") - logDecision("WARNING: rolling back release [ " + releaseName + " ] from [[ " + getReleaseNamespace(releaseName) + " ]] to [[ " + namespace + - " ]] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md for details if this release uses PV and PVC.") + logDecision("DECISION: release [ "+releaseName+" ] is deleted BUT from namespace [[ "+getReleaseNamespace(releaseName)+ + " ]]. Will purge delete it from there and install it in namespace [[ "+namespace+" ]]", r.Priority) + logDecision("WARNING: rolling back release [ "+releaseName+" ] from [[ "+getReleaseNamespace(releaseName)+" ]] to [[ "+namespace+ + " ]] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md"+ + " for details if this release uses PV and PVC.", r.Priority) } } @@ -155,29 +157,29 @@ func inspectDeleteScenario(namespace string, r release) { //if it exists in helm list , add command to delete it, else log that it is skipped if helmReleaseExists(namespace, releaseName, "deployed") { // delete it - deleteRelease(releaseName, r.Purge) + deleteRelease(r) } else { - logDecision("DECISION: release [ " + releaseName + " ] is set to be disabled but is not yet deployed. Skipping.") + logDecision("DECISION: release [ "+releaseName+" ] is set to be disabled but is not yet deployed. Skipping.", r.Priority) } } // deleteRelease deletes a release from a k8s cluster -func deleteRelease(releaseName string, purge bool) { +func deleteRelease(r release) { p := "" purgeDesc := "" - if purge { + if r.Purge { p = "--purge" purgeDesc = "and purged!" } cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm delete " + p + " " + releaseName}, - Description: "deleting release [ " + releaseName + " ]", + Args: []string{"-c", "helm delete " + p + " " + r.Name}, + Description: "deleting release [ " + r.Name + " ]", } - outcome.addCommand(cmd) - logDecision("DECISION: release [ " + releaseName + " ] is desired to be deleted " + purgeDesc + ". Planing this for you!") + outcome.addCommand(cmd, r.Priority) + logDecision("DECISION: release [ "+r.Name+" ] is desired to be deleted "+purgeDesc+". Planing this for you!", r.Priority) } // inspectUpgradeScenario evaluates if a release should be upgraded. @@ -194,26 +196,27 @@ func inspectUpgradeScenario(namespace string, r release) { if extractChartName(r.Chart) == getReleaseChartName(releaseName) && r.Version != getReleaseChartVersion(releaseName) { // upgrade upgradeRelease(r) - logDecision("DECISION: release [ " + releaseName + " ] is desired to be upgraded. Planing this for you!") + logDecision("DECISION: release [ "+releaseName+" ] is desired to be upgraded. Planing this for you!", r.Priority) } else if extractChartName(r.Chart) != getReleaseChartName(releaseName) { reInstallRelease(namespace, r) - logDecision("DECISION: release [ " + releaseName + " ] is desired to use a new Chart [ " + r.Chart + - " ]. I am planning a purge delete of the current release and will install it with the new chart in namespace [[ " + - namespace + " ]]") + logDecision("DECISION: release [ "+releaseName+" ] is desired to use a new Chart [ "+r.Chart+ + " ]. I am planning a purge delete of the current release and will install it with the new chart in namespace [[ "+ + namespace+" ]]", r.Priority) } else { upgradeRelease(r) - logDecision("DECISION: release [ " + releaseName + " ] is desired to be enabled and is currently enabled." + - "I will upgrade it in case you changed your values.yaml!") + logDecision("DECISION: release [ "+releaseName+" ] is desired to be enabled and is currently enabled."+ + "I will upgrade it in case you changed your values.yaml!", r.Priority) } } else { reInstallRelease(namespace, r) - logDecision("DECISION: release [ " + releaseName + " ] is desired to be enabled in a new namespace [[ " + namespace + - " ]]. I am planning a purge delete of the current release from namespace [[ " + getReleaseNamespace(releaseName) + " ]] " + - "and will install it for you in namespace [[ " + namespace + " ]]") - logDecision("WARNING: moving release [ " + releaseName + " ] from [[ " + getReleaseNamespace(releaseName) + " ]] to [[ " + namespace + - " ]] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md for details if this release uses PV and PVC.") + logDecision("DECISION: release [ "+releaseName+" ] is desired to be enabled in a new namespace [[ "+namespace+ + " ]]. I am planning a purge delete of the current release from namespace [[ "+getReleaseNamespace(releaseName)+" ]] "+ + "and will install it for you in namespace [[ "+namespace+" ]]", r.Priority) + logDecision("WARNING: moving release [ "+releaseName+" ] from [[ "+getReleaseNamespace(releaseName)+" ]] to [[ "+namespace+ + " ]] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md"+ + " for details if this release uses PV and PVC.", r.Priority) } } @@ -225,10 +228,10 @@ func upgradeRelease(r release) { Description: "upgrading release [ " + r.Name + " ]", } - outcome.addCommand(cmd) + outcome.addCommand(cmd, r.Priority) // if r.Test { - // testRelease(r.Name) + // testRelease(r) // } } @@ -242,15 +245,15 @@ func reInstallRelease(namespace string, r release) { Args: []string{"-c", "helm delete --purge " + releaseName}, Description: "deleting release [ " + releaseName + " ]", } - outcome.addCommand(delCmd) + outcome.addCommand(delCmd, r.Priority) installCmd := command{ Cmd: "bash", Args: []string{"-c", "helm install " + r.Chart + " --version " + r.Version + " -n " + releaseName + " --namespace " + namespace + getValuesFile(r) + getSetValues(r) + getWait(r)}, Description: "installing release [ " + releaseName + " ] in namespace [[ " + namespace + " ]]", } - outcome.addCommand(installCmd) - logDecision("DECISION: release [ " + releaseName + " ] will be deleted from namespace [[ " + getReleaseNamespace(releaseName) + " ]] and reinstalled in [[ " + namespace + "]].") + outcome.addCommand(installCmd, r.Priority) + logDecision("DECISION: release [ "+releaseName+" ] will be deleted from namespace [[ "+getReleaseNamespace(releaseName)+" ]] and reinstalled in [[ "+namespace+"]].", r.Priority) // if r.Test { // testRelease(releaseName) @@ -259,12 +262,12 @@ func reInstallRelease(namespace string, r release) { // logDecision adds the decisions made to the plan. // Depending on the debug flag being set or not, it will either log the the decision to output or not. -func logDecision(decision string) { +func logDecision(decision string, priority int) { if debug { log.Println(decision) } - outcome.addDecision(decision) + outcome.addDecision(decision, priority) } diff --git a/plan.go b/plan.go index ae0684c2..1baa7c10 100644 --- a/plan.go +++ b/plan.go @@ -3,13 +3,27 @@ package main import ( "fmt" "log" + "sort" + "strconv" "time" ) +// orderedDecision type representing a Descsion and it's priority weight +type orderedDecision struct { + Description string + Priority int +} + +// orderedCommand type representing a Command and it's priority weight +type orderedCommand struct { + Command command + Priority int +} + // plan type representing the plan of actions to make the desired state come true. type plan struct { - Commands []command - Decisions []string + Commands []orderedCommand + Decisions []orderedDecision Created time.Time } @@ -17,32 +31,40 @@ type plan struct { func createPlan() plan { p := plan{ - Commands: []command{}, - Decisions: []string{}, + Commands: []orderedCommand{}, + Decisions: []orderedDecision{}, Created: time.Now(), } return p } // addCommand adds a command type to the plan -func (p *plan) addCommand(c command) { +func (p *plan) addCommand(cmd command, priority int) { + oc := orderedCommand{ + Command: cmd, + Priority: priority, + } - p.Commands = append(p.Commands, c) + p.Commands = append(p.Commands, oc) } // addDecision adds a decision type to the plan -func (p *plan) addDecision(decision string) { - - p.Decisions = append(p.Decisions, decision) +func (p *plan) addDecision(decision string, priority int) { + od := orderedDecision{ + Description: decision, + Priority: priority, + } + p.Decisions = append(p.Decisions, od) } // execPlan executes the commands (actions) which were added to the plan. func (p plan) execPlan() { + p.sortPlan() log.Println("INFO: Executing the following plan ... ") p.printPlan() for _, cmd := range p.Commands { - log.Println("INFO: attempting: -- ", cmd.Description) - if exitCode, msg := cmd.exec(debug, verbose); exitCode != 0 { + log.Println("INFO: attempting: -- ", cmd.Command.Description) + if exitCode, msg := cmd.Command.exec(debug, verbose); exitCode != 0 { log.Fatal("Command returned with exit code: " + string(exitCode) + ". And error message: " + msg) } } @@ -51,16 +73,30 @@ func (p plan) execPlan() { // printPlanCmds prints the actual commands that will be executed as part of a plan. func (p plan) printPlanCmds() { fmt.Println("Printing the commands of the current plan ...") - for _, Cmd := range p.Commands { - fmt.Println(Cmd.Description) + for _, cmd := range p.Commands { + fmt.Println(cmd.Command.Description) } } // printPlan prints the decisions made in a plan. func (p plan) printPlan() { fmt.Println("----------------------") - fmt.Printf("Plan generated at: %s \n", p.Created.Format("Mon Jan _2 2006 15:04:05")) + log.Printf("INFO: Plan generated at: %s \n", p.Created.Format("Mon Jan _2 2006 15:04:05")) for _, decision := range p.Decisions { - fmt.Println(decision) + fmt.Println(decision.Description + " -- priority: " + strconv.Itoa(decision.Priority)) } } + +// sortPlan sorts the slices of commands and decisions based on priorities +// the lower the priority value the earlier a command should be attempted +func (p plan) sortPlan() { + log.Println("INFO: sorting the commands in the plan based on priorities (order flags) ... ") + + sort.SliceStable(p.Commands, func(i, j int) bool { + return p.Commands[i].Priority < p.Commands[j].Priority + }) + + sort.SliceStable(p.Decisions, func(i, j int) bool { + return p.Decisions[i].Priority < p.Decisions[j].Priority + }) +} diff --git a/release.go b/release.go index 1cf78628..4cde1410 100644 --- a/release.go +++ b/release.go @@ -19,6 +19,7 @@ type release struct { Test bool Protected bool Wait bool + Priority int Set map[string]string } @@ -44,6 +45,8 @@ func validateRelease(r release, names map[string]bool) (bool, string) { return false, "env variable [ " + v + " ] is not found in the environment." } } + } else if r.Priority != 0 && r.Priority > 0 { + return false, "priority can only be 0 or negative value, positive values are not allowed." } names[r.Name] = true @@ -64,6 +67,7 @@ func (r release) print() { fmt.Println("\ttest : ", r.Test) fmt.Println("\tprotected : ", r.Protected) fmt.Println("\twait : ", r.Wait) + fmt.Println("\tpriority : ", r.Priority) fmt.Println("\tvalues to override from env:") printMap(r.Set) fmt.Println("------------------- ") From 95a0119ccf9c02783e792122533bdeaea1a8f0d5 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 31 Mar 2018 17:41:21 +0200 Subject: [PATCH 0127/1127] adding --ns-override flag --- decision_maker.go | 47 ++++++++++++++++------------------------------- init.go | 1 + main.go | 42 +++++++++++++++++++++++++++++++----------- release.go | 25 +++++++++++++++++++++++-- state.go | 12 ++++++++---- utils.go | 13 +++++++------ 6 files changed, 86 insertions(+), 54 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index aa7c68df..9867e164 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -19,7 +19,7 @@ func makePlan(s *state) *plan { // decide makes a decision about what commands (actions) need to be executed // to make a release section of the desired state come true. -func decide(r release, s *state) { +func decide(r *release, s *state) { // check for deletion if !r.Enabled { @@ -85,7 +85,7 @@ func decide(r release, s *state) { } // testRelease creates a Helm command to test a particular release. -func testRelease(r release) { +func testRelease(r *release) { cmd := command{ Cmd: "bash", @@ -98,7 +98,7 @@ func testRelease(r release) { } // installRelease creates a Helm command to install a particular release in a particular namespace. -func installRelease(namespace string, r release) { +func installRelease(namespace string, r *release) { releaseName := r.Name cmd := command{ @@ -118,7 +118,7 @@ func installRelease(namespace string, r release) { // inspectRollbackScenario evaluates if a rollback action needs to be taken for a given release. // if the release is already deleted but from a different namespace than the one specified in input, // it purge deletes it and create it in the spcified namespace. -func inspectRollbackScenario(namespace string, r release) { +func inspectRollbackScenario(namespace string, r *release) { releaseName := r.Name if getReleaseNamespace(r.Name) == namespace { @@ -151,7 +151,7 @@ func inspectRollbackScenario(namespace string, r release) { // inspectDeleteScenario evaluates if a delete action needs to be taken for a given release. // If the purge flage is set to true for the release in question, then it will be permenantly removed. // If the release is not deployed, it will be skipped. -func inspectDeleteScenario(namespace string, r release) { +func inspectDeleteScenario(namespace string, r *release) { releaseName := r.Name //if it exists in helm list , add command to delete it, else log that it is skipped @@ -165,7 +165,7 @@ func inspectDeleteScenario(namespace string, r release) { } // deleteRelease deletes a release from a k8s cluster -func deleteRelease(r release) { +func deleteRelease(r *release) { p := "" purgeDesc := "" if r.Purge { @@ -189,7 +189,7 @@ func deleteRelease(r release) { // it will be purge deleted and installed in the same namespace using the new chart. // - If the release is NOT in the same namespace specified in the input, // it will be purge deleted and installed in the new namespace. -func inspectUpgradeScenario(namespace string, r release) { +func inspectUpgradeScenario(namespace string, r *release) { releaseName := r.Name if getReleaseNamespace(releaseName) == namespace { @@ -221,7 +221,7 @@ func inspectUpgradeScenario(namespace string, r release) { } // upgradeRelease upgrades an existing release with the specified values.yaml -func upgradeRelease(r release) { +func upgradeRelease(r *release) { cmd := command{ Cmd: "bash", Args: []string{"-c", "helm upgrade " + r.Name + " " + r.Chart + getValuesFile(r) + " --version " + r.Version + " --force " + getSetValues(r) + getWait(r)}, @@ -237,7 +237,7 @@ func upgradeRelease(r release) { // reInstallRelease purge deletes a release and reinstalls it. // This is used when moving a release to another namespace or when changing the chart used for it. -func reInstallRelease(namespace string, r release) { +func reInstallRelease(namespace string, r *release) { releaseName := r.Name delCmd := command{ @@ -280,7 +280,7 @@ func extractChartName(releaseChart string) string { } // getValuesFile return partial install/upgrade release command to substitute the -f flag in Helm. -func getValuesFile(r release) string { +func getValuesFile(r *release) string { if r.ValuesFile != "" { return " -f " + r.ValuesFile } @@ -288,7 +288,7 @@ func getValuesFile(r release) string { } // getSetValues returns --set params to be used with helm install/upgrade commands -func getSetValues(r release) string { +func getSetValues(r *release) string { result := "" for k, v := range r.Set { _, value := envVarExists(v) @@ -299,7 +299,7 @@ func getSetValues(r release) string { // getWait returns a partial helm command containing the helm wait flag (--wait) if the wait flag for the release was set to true // Otherwise, retruns an empty string -func getWait(r release) string { +func getWait(r *release) string { result := "" if r.Wait { result = " --wait" @@ -307,39 +307,24 @@ func getWait(r release) string { return result } -// getDesiredNamespace validates that namespace where the release is desired to be installed is defined in the Namespaces definition -// it returns the namespace if it is already defined -// otherwise, it throws an error -func getDesiredNamespace(r release) string { - if !checkNamespaceDefined(r.Namespace) { - log.Fatal("ERROR: " + r.Namespace + " is not defined in the Namespaces section of your desired state file. Release [ " + r.Name + - " ] can't be installed in that Namespace until its defined in your desired state file.") - } +// getDesiredNamespace returns the namespace of a release +func getDesiredNamespace(r *release) string { return r.Namespace } // getCurrentNamespaceProtection returns the protection state for the namespace where a release is currently installed. // It returns true if a namespace is defined as protected in the desired state file, false otherwise. -func getCurrentNamespaceProtection(r release) bool { +func getCurrentNamespaceProtection(r *release) bool { return s.Namespaces[getReleaseNamespace(r.Name)].Protected } -// checkNamespaceDefined checks if a given namespace is defined in the namespaces section of the desired state file -func checkNamespaceDefined(ns string) bool { - _, ok := s.Namespaces[ns] - if !ok { - return false - } - return true -} - // isProtected checks if a release is protected or not. // A protected is release is either: a) deployed in a protected namespace b) flagged as protected in the desired state file // Any release in a protected namespace is protected by default regardless of its flag // returns true if a release is protected, false otherwise -func isProtected(r release) bool { +func isProtected(r *release) bool { // if the release does not exist in the cluster, it is not protected if !helmReleaseExists("", r.Name, "all") { diff --git a/init.go b/init.go index 1a42cf14..a564b817 100644 --- a/init.go +++ b/init.go @@ -27,6 +27,7 @@ func init() { flag.BoolVar(&help, "help", false, "show Helmsman help") flag.BoolVar(&v, "v", false, "show the version") flag.BoolVar(&verbose, "verbose", false, "show verbose execution logs") + flag.StringVar(&nsOverride, "ns-override", "", "override defined namespaces with this one") flag.Parse() diff --git a/main.go b/main.go index 4b7ee0c1..79dac753 100644 --- a/main.go +++ b/main.go @@ -16,7 +16,8 @@ var apply bool var help bool var v bool var verbose bool -var version = "v1.0.2" +var nsOverride string +var version = "master" func main() { @@ -54,6 +55,7 @@ func main() { p := makePlan(&s) if !apply { + p.sortPlan() p.printPlan() } else { p.execPlan() @@ -141,7 +143,7 @@ func addHelmRepos(repos map[string]string) (bool, string) { // validateReleaseCharts validates if the charts defined in a release are valid. // Valid charts are the ones that can be found in the defined repos. // This function uses Helm search to verify if the chart can be found or not. -func validateReleaseCharts(apps map[string]release) (bool, string) { +func validateReleaseCharts(apps map[string]*release) (bool, string) { for app, r := range apps { cmd := command{ @@ -160,18 +162,36 @@ func validateReleaseCharts(apps map[string]release) (bool, string) { // addNamespaces creates a set of namespaces in your k8s cluster. // If a namespace with the same name exsts, it will skip it. +// If --ns-override flag is used, it only creates the provided namespace in that flag func addNamespaces(namespaces map[string]namespace) { - for ns := range namespaces { - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "kubectl create namespace " + ns}, - Description: "creating namespace " + ns, + if nsOverride == "" { + for ns := range namespaces { + createNamespace(ns) } + } else { + createNamespace(nsOverride) + overrideAppsNamespace(nsOverride) + } +} - if exitCode, _ := cmd.exec(debug, verbose); exitCode != 0 { - log.Println("WARN: I could not create namespace [" + - ns + " ]. It already exists. I am skipping this.") - } +func overrideAppsNamespace(newNs string) { + log.Println("INFO: overriding apps namespaces with [ " + newNs + " ] ...") + for _, r := range s.Apps { + overrideNamespace(r, newNs) + } +} + +// createNamespace creates a namespace in the k8s cluster +func createNamespace(ns string) { + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "kubectl create namespace " + ns}, + Description: "creating namespace " + ns, + } + + if exitCode, _ := cmd.exec(debug, verbose); exitCode != 0 { + log.Println("WARN: I could not create namespace [" + + ns + " ]. It already exists. I am skipping this.") } } diff --git a/release.go b/release.go index 4cde1410..0373d491 100644 --- a/release.go +++ b/release.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "log" "os" "strings" ) @@ -25,12 +26,17 @@ type release struct { // validateRelease validates if a release inside a desired state meets the specifications or not. // check the full specification @ https://github.com/Praqma/helmsman/docs/desired_state_spec.md -func validateRelease(r release, names map[string]bool) (bool, string) { +func validateRelease(r *release, names map[string]bool) (bool, string) { _, err := os.Stat(r.ValuesFile) if r.Name == "" || names[r.Name] { return false, "release name can't be empty and must be unique." - } else if r.Namespace == "" { + } else if nsOverride == "" && r.Namespace == "" { return false, "release targeted namespace can't be empty." + } else if nsOverride == "" && r.Namespace != "" { + if !checkNamespaceDefined(r.Namespace) { + return false, "release " + r.Name + " is using namespace [ " + r.Namespace + " ] which is not defined in the Namespaces section of your desired state file." + + " Release [ " + r.Name + " ] can't be installed in that Namespace until its defined." + } } else if r.Chart == "" || !strings.ContainsAny(r.Chart, "/") { return false, "chart can't be empty and must be of the format: repo/chart." } else if r.Version == "" { @@ -53,6 +59,21 @@ func validateRelease(r release, names map[string]bool) (bool, string) { return true, "" } +// checkNamespaceDefined checks if a given namespace is defined in the namespaces section of the desired state file +func checkNamespaceDefined(ns string) bool { + _, ok := s.Namespaces[ns] + if !ok { + return false + } + return true +} + +// overrideNamespace overrides a release defined namespace with a new given one +func overrideNamespace(r *release, newNs string) { + log.Println("INFO: overriding namespace for app: " + r.Name) + r.Namespace = newNs +} + // print prints the details of the release func (r release) print() { fmt.Println("") diff --git a/state.go b/state.go index bf7e16af..38c38c4b 100644 --- a/state.go +++ b/state.go @@ -20,7 +20,7 @@ type state struct { Settings map[string]string Namespaces map[string]namespace HelmRepos map[string]string - Apps map[string]release + Apps map[string]*release } // validate validates that the values specified in the desired state are valid according to the desired state spec. @@ -84,9 +84,13 @@ func (s state) validate() (bool, string) { } // namespaces - if s.Namespaces == nil || len(s.Namespaces) == 0 { - return false, "ERROR: namespaces validation failed -- I need at least one namespace " + - "to work with!" + if nsOverride == "" { + if s.Namespaces == nil || len(s.Namespaces) == 0 { + return false, "ERROR: namespaces validation failed -- I need at least one namespace " + + "to work with!" + } + } else { + log.Println("INFO: ns-override is used. Overriding all namespaces with [ " + nsOverride + " ] Skipping defined namespaces validation.") } // repos diff --git a/utils.go b/utils.go index a6a8b4fd..7355cec7 100644 --- a/utils.go +++ b/utils.go @@ -87,12 +87,13 @@ func printHelp() { fmt.Println("Usage: helmsman [options]") fmt.Println() fmt.Println("Options:") - fmt.Println("-f specifies the desired state TOML file.") - fmt.Println("-debug prints basic logs during execution.") - fmt.Println("-verbose prints more verbose logs during execution.") - fmt.Println("-apply generates and applies an action plan.") - fmt.Println("-help prints Helmsman help.") - fmt.Println("-v prints Helmsman version.") + fmt.Println("--f specifies the desired state TOML file.") + fmt.Println("--debug prints basic logs during execution.") + fmt.Println("-verbose prints more verbose logs during execution.") + fmt.Println("--ns-override override defined namespaces with a provided one.") + fmt.Println("--apply generates and applies an action plan.") + fmt.Println("--help prints Helmsman help.") + fmt.Println("--v prints Helmsman version.") } func logVersions() { From d37ed40f6e62b542c6e289f5d193f44cf890768f Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sun, 1 Apr 2018 12:07:41 +0200 Subject: [PATCH 0128/1127] adding cleanup for any downloaded secrets. --- main.go | 22 ++++++++++++++++++++++ utils.go | 8 ++++++++ 2 files changed, 30 insertions(+) diff --git a/main.go b/main.go index 79dac753..d7074205 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,7 @@ package main import ( "log" + "os" "strings" "time" @@ -17,6 +18,7 @@ var help bool var v bool var verbose bool var nsOverride string +var checkCleanup bool var version = "master" func main() { @@ -26,6 +28,7 @@ func main() { if r, msg := createContext(); !r { log.Fatal(msg) } + checkCleanup = true } if r, msg := initHelm(); !r { @@ -61,6 +64,11 @@ func main() { p.execPlan() } + if checkCleanup { + cleanup() + } + + log.Println("INFO: execution completed successfully!") } // setKubeContext sets your kubectl context to the one specified in the desired state file. @@ -360,3 +368,17 @@ func waitForTiller() { } log.Fatal("ERROR: timeout reached while waiting for helm Tiller to be ready. Aborting!") } + +func cleanup() { + if _, err := os.Stat("ca.crt"); err == nil { + deleteFile("ca.crt") + } + + if _, err := os.Stat("ca.key"); err == nil { + deleteFile("ca.key") + } + + if _, err := os.Stat("client.crt"); err == nil { + deleteFile("client.crt") + } +} diff --git a/utils.go b/utils.go index 7355cec7..3a67cd23 100644 --- a/utils.go +++ b/utils.go @@ -159,3 +159,11 @@ func validateSerrviceAccount(sa string) (bool, string) { } return true, "" } + +// deleteFile deletes a file +func deleteFile(path string) { + log.Println("INFO: cleaning up ... deleting " + path) + if err := os.Remove(path); err != nil { + log.Fatal("ERROR: could not delete file: " + path) + } +} From ea48c27c92b115c2a2f29fd69c127c0372e4d187 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sun, 1 Apr 2018 13:40:17 +0200 Subject: [PATCH 0129/1127] updating docs for v1.1.0 --- CONTRIBUTION.md | 29 +++++++ README.md | 77 +++++++++-------- docs/deplyment_strategies.md | 4 +- docs/desired_state_specification.md | 8 +- docs/how_to/define_namespaces.md | 9 +- docs/how_to/helmsman_on_windows10.md | 17 ++++ docs/how_to/manipulate_apps.md | 4 +- docs/how_to/move_charts_across_namespaces.md | 2 +- docs/how_to/override_defined_namespaces.md | 67 +++++++++++++++ .../how_to/pass_secrets_from_env_variables.md | 2 +- .../how_to/protect_namespaces_and_releases.md | 2 +- docs/how_to/run_helmsman_in_ci.md | 4 +- .../run_helmsman_with_hosted_cluster.md | 2 +- docs/how_to/run_helmsman_with_minikube.md | 4 +- docs/how_to/test_charts.md | 2 +- docs/how_to/use_local_charts.md | 2 +- docs/how_to/use_private_helm_charts.md | 2 +- docs/how_to/use_the_priority_key.md | 84 +++++++++++++++++++ example.toml | 6 +- main.go | 2 +- release-notes.md | 18 +++- 21 files changed, 285 insertions(+), 62 deletions(-) create mode 100644 CONTRIBUTION.md create mode 100644 docs/how_to/helmsman_on_windows10.md create mode 100644 docs/how_to/override_defined_namespaces.md create mode 100644 docs/how_to/use_the_priority_key.md diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md new file mode 100644 index 00000000..adca7c2b --- /dev/null +++ b/CONTRIBUTION.md @@ -0,0 +1,29 @@ +# Contribution Guide + +Pull requests, feeback/feature requests are all welcome. This guide will be updated overtime. + +## Build helmsman from source + +To build helmsman from source, you need go:1.8+. Follow the steps below: + +``` +git clone https://github.com/Praqma/helmsman.git +go get github.com/BurntSushi/toml +go get github.com/Praqma/helmsman/gcs +go get github.com/Praqma/helmsman/aws +TAG=$(git describe --abbrev=0 --tags)-$(date +"%s") +go build -ldflags '-X main.version='$TAG' -extldflags "-static"' +``` + +## Submitting pull requests + +Please make sure you state the purpose of the pull request and that the code you submit is documented. If in doubt, [this guide](https://blog.github.com/2015-01-21-how-to-write-the-perfect-pull-request/) offers some good tips on writing a PR. + +## Contribution to documentation + +Contribution to the documentation can be done via pull requests or by openeing an issue. + +## Reporting issues/featuer requests + +Please provide details of the issue, versions of helmsman, helm and kubernetes and all possible logs. + diff --git a/README.md b/README.md index 9b1e1387..205a01c4 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,29 @@ --- -version: v1.0.0 +version: v1.1.0 --- ![helmsman-logo](docs/images/helmsman.png) # What is Helmsman? -Helmsman is a Helm Charts (k8s applications) as Code tool which adds a layer of abstraction on top of [Helm](https://helm.sh) (the [Kubernetes](https://kubernetes.io/) package manager). It allows you to automate the deployment/management of your Helm charts. +Helmsman is a Helm Charts (k8s applications) as Code tool which allows you to automate the deployment/management of your Helm charts from version controlled code. -# Why Helmsman? +# How does it work? + +Helmsman uses a simple declarative [TOML](https://github.com/toml-lang/toml) file to allow you to describe a desired state for your k8s applications as in the [example file](https://github.com/Praqma/helmsman/blob/master/example.toml). + +The desired state file (DSF) follows the [desired state specification](https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md). + +Helmsman sees what you desire, validates that your desire makes sense (e.g. that the charts you desire are available in the repos you defined), compares it with the current state of Helm and figures out what to do to make your desire come true. + +To plan without executing: +``` $ helmsman -f example.toml ``` -Helmsman was created to ease continous deployment of Helm charts. When you want to configure a continous deployment pipeline to manage multiple charts deployed on your k8s cluster(s), a CI script will quickly become complex and difficult to maintain. That's where Helmsman comes to rescue. Read more about [how Helmsman can save you time and effort](https://github.com/Praqma/helmsman/blob/master/docs/why_helmsman.md). +To plan and execute the plan: +``` $ helmsman -apply -f example.toml ``` +To show debugging details: +``` $ helmsman -debug -apply -f example.toml ``` # Features @@ -21,10 +33,31 @@ Helmsman was created to ease continous deployment of Helm charts. When you want - **Plan, View, apply**: you can run Helmsman to generate and view a plan with/without executing it. - **Portable**: Helmsman can be used to manage charts deployments on any k8s cluster. - **Protect Namespaces/Releases**: you can define certain namespaces/releases to be protected against accidental human mistakes. +- **Define the order of managing releases**: you can define the priorities at which releases are managed by helmsman (useful for dependencies). - **Idempotency**: As long your desired state file does not change, you can execute Helmsman several times and get the same result. - **Continue from failures**: In the case of partial deployment due to a specific chart deployment failure, fix your helm chart and execute Helmsman again without needing to rollback the partial successes first. -# Helmsman lets you: +# Install + +## From binary + +Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. +``` +# on Linux +curl -L https://github.com/Praqma/helmsman/releases/download/v1.1.0/helmsman_1.1.0_linux_amd64.tar.gz | tar zx +# on MacOS +curl -L https://github.com/Praqma/helmsman/releases/download/v1.1.0/helmsman_1.1.0_darwin_amd64.tar.gz | tar zx + +mv helmsman /bin/helmsman +``` + +## As a docker image +Check the images on [dockerhub](https://hub.docker.com/r/praqma/helmsman/tags/) + +# Documentaion + +Documentation and How-Tos can be found [here](https://github.com/Praqma/helmsman/blob/master/docs/). +Helmsman lets you: - [install/delete/upgrade/rollback your helm charts from code](https://github.com/Praqma/helmsman/blob/master/docs/how_to/manipulate_apps.md). - [pass secrets/user input to helm charts from environment variables](https://github.com/Praqma/helmsman/blob/master/docs/how_to/pass_secrets_from_env_variables.md). @@ -34,9 +67,11 @@ Helmsman was created to ease continous deployment of Helm charts. When you want - [define namespaces to be used in your cluster](https://github.com/Praqma/helmsman/blob/master/docs/how_to/define_namespaces.md). - [move charts across namespaces](https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md). - [protect namespaces/releases against accidental changes](https://github.com/Praqma/helmsman/blob/master/docs/how_to/protect_namespaces_and_releases.md) +- [Define priorities at which releases are deployed/managed](https://github.com/Praqma/helmsman/blob/master/docs/how_to/use_the_priority_key.md) +- [Override the defined namespaces to deploy all releases in a specific namespace](https://github.com/Praqma/helmsman/blob/master/docs/how_to/override_defined_namespaces.md) -# Usage +## Usage Helmsman can be used in three different settings: @@ -45,34 +80,6 @@ Helmsman can be used in three different settings: - [As a docker image in a CI system or local machine](https://github.com/Praqma/helmsman/blob/master/docs/how_to/run_helmsman_in_ci.md) Always use a tagged docker image from [dockerhub](https://hub.docker.com/r/praqma/helmsman/) as the `latest` image can (at times) be unstable. -# How does it work? - -Helmsman uses a simple declarative [TOML](https://github.com/toml-lang/toml) file to allow you to describe a desired state for your k8s applications as in the [example file](https://github.com/Praqma/helmsman/blob/master/example.toml). - -The desired state file (DSF) follows the [desired state specification](https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md). - -Helmsman sees what you desire, validates that your desire makes sense (e.g. that the charts you desire are available in the repos you defined), compares it with the current state of Helm and figures out what to do to make your desire come true. - -To plan without executing: -``` $ helmsman -f example.toml ``` - -To plan and execute the plan: -``` $ helmsman -apply -f example.toml ``` - -To show debugging details: -``` $ helmsman -debug -apply -f example.toml ``` - - -# Installation - -Install Helmsman for your OS from the [releases page](https://github.com/Praqma/Helmsman/releases). Available for Linux, MacOS & Windows. - -Also available as a [docker image](https://hub.docker.com/r/praqma/helmsman/). - -# Documentaion - -Documentation and How-Tos can be found [here](https://github.com/Praqma/helmsman/blob/master/docs/). - # Contributing -Pull requests, feeback/feature requests are welcome. \ No newline at end of file +Pull requests, feeback/feature requests are welcome. Please check our [contribution guide](CONTRIBUTION.md). \ No newline at end of file diff --git a/docs/deplyment_strategies.md b/docs/deplyment_strategies.md index 564e0baf..855be5ed 100644 --- a/docs/deplyment_strategies.md +++ b/docs/deplyment_strategies.md @@ -1,5 +1,5 @@ --- -version: v1.0.0 +version: v1.1.0 --- # Deployment Strategies @@ -77,6 +77,8 @@ If you are developing your own applications/services and packaging them in helm Often, you would have multiple apps developed in separate source code repositories but you would like to test their deployment in the same cluster/namespace. In that case, Helmsman can be used [as part of your CI pipeline](how_to/run_helmsman_in_ci.md) as described in the diagram below: +> as of v1.1.0 , you can use the `ns-override`flag to force helmsman to deploy/move all apps into a given namespace. For example, you could use this flag in a CI job that gets triggered on commits to the dev branch to deploy all apps into the `staging` namespace. + ![multi-DSF](images/multi-DSF.png) Each repository will have a Helmsman desired state file (DSF). But it is important to consider the notes below on using multiple desired state files with one cluster. diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 44d5a0d8..3dc2394c 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -1,5 +1,5 @@ --- -version: v1.0.0 +version: v1.1.0 --- # Helmsman desired state specification @@ -69,6 +69,7 @@ The following options can be skipped if your kubectl context is already created - username : the username to be used for kubectl credentials. - password : an environment variable name (starting with `$`) where your password is stored. Get the password from your k8s admin or consult k8s docs on how to get/set it. - clusterURI : the URI for your cluster API or an environment variable containing the URI. +- serviceAccount: the name of the service account to use to initiate helm. This should have enough permissions to allow Helm to work and should exist already in the cluster. More details can be found in [helm's RBAC guide](https://github.com/kubernetes/helm/blob/master/docs/rbac.md) Example: @@ -79,6 +80,7 @@ kubeContext = "minikube" # password = "$K8S_PASSWORD" # clusterURI = "https://192.168.99.100:8443" ## clusterURI= "$K8S_URI" +# serviceAccount = "my-service-account" ``` ## Namespaces @@ -150,6 +152,8 @@ Options: - purge : defines whether to use the Helm purge flag wgen deleting the release. (true/false) - test : defines whether to run the chart tests whenever the release is installed/upgraded/rolledback. - protected : defines if the release should be protected against changes. Namespace-level protection has higher priority than this flag. Check the [protection guide](how_to/protect_namespaces_and_releases.md) for more details. +- wait : defines whether helmsman should block execution until all k8s resources are in a ready state. Default is false. +- priority : defines the priority of applying operations on this release. Only negative values allowed and the lower the value, the higher the priority. Default priority is 0. Apps with equal priorities will be applied in the order they were added in your state file (DSF). Example: @@ -169,5 +173,7 @@ Example: purge = false test = true protected = false + wait = true + priority = -3 ``` diff --git a/docs/how_to/define_namespaces.md b/docs/how_to/define_namespaces.md index 2217458f..9594c18b 100644 --- a/docs/how_to/define_namespaces.md +++ b/docs/how_to/define_namespaces.md @@ -1,5 +1,5 @@ --- -version: v0.2.0 +version: v1.1.0 --- # define namespaces @@ -10,10 +10,9 @@ You can define namespaces to be used in your cluster. If they don't exist, Helms ... [namespaces] -staging = "staging" -production = "default" -myOtherNamespace = "namespaceX" - +[namespaces.staging] +[namespaces.production] + protected = true # default is false ... ``` diff --git a/docs/how_to/helmsman_on_windows10.md b/docs/how_to/helmsman_on_windows10.md new file mode 100644 index 00000000..086dd70a --- /dev/null +++ b/docs/how_to/helmsman_on_windows10.md @@ -0,0 +1,17 @@ +--- +version: v1.1.0 +--- + +# Using Helmsman from a docker image on Windows 10 + +If you have Windows 10 with Docker installed, you might be able to run Helmsman in a linux container on Windows. + +1. Switch to the Linux containers from the docker tray icon. +2. Configure your local kubectl on Windows to connect to your cluster. +3. Configure your desired state file to use the kubeContext only. i.e. no cluster connection settings. +2. Run the following command: + +``` +docker run --rm -it -v :/root/.kube -v :/tmp praqma/helmsman:v1.0.2 helmsman -f dsf.toml --debug --apply +``` + diff --git a/docs/how_to/manipulate_apps.md b/docs/how_to/manipulate_apps.md index 2b0b5c60..86240207 100644 --- a/docs/how_to/manipulate_apps.md +++ b/docs/how_to/manipulate_apps.md @@ -1,5 +1,5 @@ --- -version: v0.2.0 +version: v1.1.0 --- # install releases @@ -99,7 +99,7 @@ DECISION: release [ artifactory ] is desired to be upgraded. Planing this for yo # upgrade releases -Everytime you run Helmsman, it will upgrade existing deployed releases to the version you specified in the desired state file. It also applies the `values.yaml` file you specify with each install/upgrade. This means that when you don't change anything for a specific release, Helmsman would upgrade with the `values.yaml` file you provide (just in case it is a new file or you changed something there.) +Everytime you run Helmsman, (unless the release is [protected or deployed in a protected namespace](protect_namespaces_and_releases.md)) it will upgrade existing deployed releases to the version you specified in the desired state file. It also applies the `values.yaml` file you specify with each install/upgrade. This means that when you don't change anything for a specific release, Helmsman would upgrade with the `values.yaml` file you provide (just in case it is a new file or you changed something there.) If you change the chart, the existing release will be deleted and a new one with the same name will be created using the new chart. diff --git a/docs/how_to/move_charts_across_namespaces.md b/docs/how_to/move_charts_across_namespaces.md index 9efc550a..5c939ab2 100644 --- a/docs/how_to/move_charts_across_namespaces.md +++ b/docs/how_to/move_charts_across_namespaces.md @@ -1,5 +1,5 @@ --- -version: v1.0.0 +version: v1.1.0 --- # move charts across namespaces diff --git a/docs/how_to/override_defined_namespaces.md b/docs/how_to/override_defined_namespaces.md new file mode 100644 index 00000000..b12b3681 --- /dev/null +++ b/docs/how_to/override_defined_namespaces.md @@ -0,0 +1,67 @@ +--- +version: v1.1.0 +--- + +# Override defined namespaces from command line + +If you use different release branches for your releasing/managing your applications in your k8s clusters, then you might want to use the same desired state but with different namespaces on each branch. Instead of duplicating the DSF in multiple branches and adjusting it, you can use the `--ns-override` command line flag when running helmsman. + +This flag overrides all namespaces defined in your DSF with the single one you pass from command line. + +# Example + +dsf.toml +``` +[metadata] +org = "example.com" +description = "example Desired State File for demo purposes." + + +[settings] +kubeContext = "minikube" + +[namespaces] + [namespaces.staging] + protected = false + [namespaces.production] + prtoected = true + +[helmRepos] +stable = "https://kubernetes-charts.storage.googleapis.com" +incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" + + +[apps] + + [apps.jenkins] + name = "jenkins" # should be unique across all apps + description = "jenkins" + namespace = "production" # maps to the namespace as defined in environmetns above + enabled = true # change to false if you want to delete this app release [empty = flase] + chart = "stable/jenkins" # changing the chart name means delete and recreate this chart + version = "0.14.3" # chart version + valuesFile = "" # leaving it empty uses the default chart values + + [apps.artifactory] + name = "artifactory" # should be unique across all apps + description = "artifactory" + namespace = "staging" # maps to the namespace as defined in environmetns above + enabled = true # change to false if you want to delete this app release [empty = flase] + chart = "stable/artifactory" # changing the chart name means delete and recreate this chart + version = "7.0.6" # chart version + valuesFile = "" # leaving it empty uses the default chart values +``` + +In command line, we run : + +``` +helmsman -f dsf.toml --debug --ns-override testing +``` + +This will override the `staging` and `production` namespaces defined in `dsf.toml` : + +``` +2018/03/31 17:38:12 INFO: Plan generated at: Sat Mar 31 2018 17:37:57 +DECISION: release [ jenkins ] is not present in the current k8s context. Will install it in namespace [[ testing ]] -- priority: 0 +DECISION: release [ artifactory ] is not present in the current k8s context. Will install it in namespace [[ testing ]] -- priority: 0 +``` \ No newline at end of file diff --git a/docs/how_to/pass_secrets_from_env_variables.md b/docs/how_to/pass_secrets_from_env_variables.md index 636e1dc6..64152e3b 100644 --- a/docs/how_to/pass_secrets_from_env_variables.md +++ b/docs/how_to/pass_secrets_from_env_variables.md @@ -1,5 +1,5 @@ --- -version: v1.0.0 +version: v1.1.0 --- # pass secrets from env. variables: diff --git a/docs/how_to/protect_namespaces_and_releases.md b/docs/how_to/protect_namespaces_and_releases.md index 4765200a..fb21b6e2 100644 --- a/docs/how_to/protect_namespaces_and_releases.md +++ b/docs/how_to/protect_namespaces_and_releases.md @@ -1,5 +1,5 @@ --- -version: v1.0.0 +version: v1.1.0 --- # Namespace and Release Protection diff --git a/docs/how_to/run_helmsman_in_ci.md b/docs/how_to/run_helmsman_in_ci.md index 8a98636c..b144f206 100644 --- a/docs/how_to/run_helmsman_in_ci.md +++ b/docs/how_to/run_helmsman_in_ci.md @@ -1,5 +1,5 @@ --- -version: v1.0.0 +version: v1.1.0 --- # Run Helmsman in CI @@ -13,7 +13,7 @@ jobs: deploy-apps: docker: - - image: praqma/helmsman:v1.0.0 + - image: praqma/helmsman:v1.1.0 steps: - checkout - run: diff --git a/docs/how_to/run_helmsman_with_hosted_cluster.md b/docs/how_to/run_helmsman_with_hosted_cluster.md index 118bf998..aa7e657f 100644 --- a/docs/how_to/run_helmsman_with_hosted_cluster.md +++ b/docs/how_to/run_helmsman_with_hosted_cluster.md @@ -1,5 +1,5 @@ --- -version: v1.0.0 +version: v1.1.0 --- You can manage Helm charts deployment on a hosted K8S cluster in the cloud or on-prem. You need to include the required information to connect to the cluster in your state file. diff --git a/docs/how_to/run_helmsman_with_minikube.md b/docs/how_to/run_helmsman_with_minikube.md index 4fdef160..381a140d 100644 --- a/docs/how_to/run_helmsman_with_minikube.md +++ b/docs/how_to/run_helmsman_with_minikube.md @@ -1,8 +1,8 @@ --- -version: v1.0.0 +version: v1.1.0 --- -You can run Helmsman local as a binary application with Minikube, you just need to skip all the cluster connection settings in your desired state file. Below is the example.toml desired state file adapted to work with Minikube. +You can run Helmsman locally as a binary application with Minikube, you just need to skip all the cluster connection settings in your desired state file. Below is the example.toml desired state file adapted to work with Minikube. ``` diff --git a/docs/how_to/test_charts.md b/docs/how_to/test_charts.md index 7e6bdaa3..3210f643 100644 --- a/docs/how_to/test_charts.md +++ b/docs/how_to/test_charts.md @@ -1,5 +1,5 @@ --- -version: v1.0.0 +version: v1.1.0 --- # test charts diff --git a/docs/how_to/use_local_charts.md b/docs/how_to/use_local_charts.md index 2669c313..ee2dc93d 100644 --- a/docs/how_to/use_local_charts.md +++ b/docs/how_to/use_local_charts.md @@ -1,5 +1,5 @@ --- -version: v1.0.0 +version: v1.1.0 --- # use local helm charts diff --git a/docs/how_to/use_private_helm_charts.md b/docs/how_to/use_private_helm_charts.md index 96714a56..a055c532 100644 --- a/docs/how_to/use_private_helm_charts.md +++ b/docs/how_to/use_private_helm_charts.md @@ -1,5 +1,5 @@ --- -version: v1.0.0 +version: v1.1.0 --- # use private helm charts diff --git a/docs/how_to/use_the_priority_key.md b/docs/how_to/use_the_priority_key.md new file mode 100644 index 00000000..a36e5eb6 --- /dev/null +++ b/docs/how_to/use_the_priority_key.md @@ -0,0 +1,84 @@ +--- +version: v1.1.0 +--- + +# Using the priority key for Apps + +The `priority` flag in Apps definition allows you to define the order at which apps operations will be applied. This is useful if you have dependecies between your apps/services. + +Priority is an optinal flag and has a default value of 0 (zero). If set, it can only use a negative value. The lower the value, the higher the priority. + +## Example + +``` +[metadata] +org = "example.com" +description = "example Desired State File for demo purposes." + + +[settings] +kubeContext = "minikube" + +[namespaces] + [namespaces.staging] + protected = false + [namespaces.production] + prtoected = true + +[helmRepos] +stable = "https://kubernetes-charts.storage.googleapis.com" +incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" + + +[apps] + + [apps.jenkins] + name = "jenkins" # should be unique across all apps + description = "jenkins" + namespace = "staging" # maps to the namespace as defined in environmetns above + enabled = true # change to false if you want to delete this app release [empty = flase] + chart = "stable/jenkins" # changing the chart name means delete and recreate this chart + version = "0.14.3" # chart version + valuesFile = "" # leaving it empty uses the default chart values + priority= -2 + + [apps.jenkins1] + name = "jenkins1" # should be unique across all apps + description = "jenkins" + namespace = "staging" # maps to the namespace as defined in environmetns above + enabled = true # change to false if you want to delete this app release [empty = flase] + chart = "stable/jenkins" # changing the chart name means delete and recreate this chart + version = "0.14.3" # chart version + valuesFile = "" # leaving it empty uses the default chart values + + + [apps.jenkins2] + name = "jenkins2" # should be unique across all apps + description = "jenkins" + namespace = "production" # maps to the namespace as defined in environmetns above + enabled = true # change to false if you want to delete this app release [empty = flase] + chart = "stable/jenkins" # changing the chart name means delete and recreate this chart + version = "0.14.3" # chart version + valuesFile = "" # leaving it empty uses the default chart values + priority= -3 + + [apps.artifactory] + name = "artifactory" # should be unique across all apps + description = "artifactory" + namespace = "staging" # maps to the namespace as defined in environmetns above + enabled = true # change to false if you want to delete this app release [empty = flase] + chart = "stable/artifactory" # changing the chart name means delete and recreate this chart + version = "7.0.6" # chart version + valuesFile = "" # leaving it empty uses the default chart values + priority= -2 +``` + +The above example will generate the following plan: + +``` +DECISION: release [ jenkins2 ] is not present in the current k8s context. Will install it in namespace [[ production ]] -- priority: -3 +DECISION: release [ jenkins ] is not present in the current k8s context. Will install it in namespace [[ staging ]] -- priority: -2 +DECISION: release [ artifactory ] is not present in the current k8s context. Will install it in namespace [[ staging ]] -- priority: -2 +DECISION: release [ jenkins1 ] is not present in the current k8s context. Will install it in namespace [[ staging ]] -- priority: 0 + +``` \ No newline at end of file diff --git a/example.toml b/example.toml index fe1bd184..8a49d88f 100644 --- a/example.toml +++ b/example.toml @@ -61,6 +61,8 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" # [apps.jenkins.set] # values to override values from values.yaml with values from env vars-- useful for passing secrets to charts # db_username="$DB_USERNAME" # db_password="$DB_PASSWORD" + priority= -3 + wait = true [apps.artifactory] @@ -73,6 +75,4 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" valuesFile = "" # leaving it empty uses the default chart values purge = false # will only be considered when there is a delete operation test = false # run the tests when this release is installed for the first time only - # [apps.artifactory.set] # values to override values from values.yaml with values from env vars-- useful for passing secrets to charts - # db_username="$DB_USERNAME" - # db_password="$DB_PASSWORD" + priority= -2 diff --git a/main.go b/main.go index d7074205..6d0ec970 100644 --- a/main.go +++ b/main.go @@ -68,7 +68,7 @@ func main() { cleanup() } - log.Println("INFO: execution completed successfully!") + log.Println("INFO: completed successfully!") } // setKubeContext sets your kubectl context to the one specified in the desired state file. diff --git a/release-notes.md b/release-notes.md index 96ceffd8..d555f6ff 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,4 +1,16 @@ -# v1.0.2 +# v1.1.0 -- Bug fix for #21. -- Minor enhancements. \ No newline at end of file +This release introduces some new features and comes with several enhancements in logging and validation. + +- Introducing `priority` key for apps in the desired state to define the priority (order) of processing apps (useful for dependencies). +- Introducing `wait` key for apps to block helmsman execution until a release operation is complete. +- Intorducing the `--ns-override` flag for overriding namespaces defined in the desired state (useful for deploying from git branches to namespaces). +- Support initializing helm with a k8s service account. +- Introducing the `--verbose` flag for more detailed logs. +- Cleaning up any downloaded certs/keys after execution. +- Improved logging with full helm error messages. +- Improved validations for desired states. +- Bumping Helm and Kubectl versions in docker images. +- Providing multiple docker image tags for different helm versions. +- Fixing not waiting for helm Tiller to be ready. +- Fixing a bug in helm release search. \ No newline at end of file From a8f3778cc1516c1298e7a643ae0c3a494d1834a4 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sun, 1 Apr 2018 15:25:14 +0200 Subject: [PATCH 0130/1127] fixing tests --- plan_test.go | 117 ++++++++++++++++++++++++------------------------ release.go | 12 +++-- release_test.go | 46 +++++++++++++------ state.go | 2 +- state_test.go | 38 ++++++++-------- 5 files changed, 116 insertions(+), 99 deletions(-) diff --git a/plan_test.go b/plan_test.go index f564dd8b..9c8dc727 100644 --- a/plan_test.go +++ b/plan_test.go @@ -2,7 +2,6 @@ package main import ( "reflect" - "strings" "testing" "time" ) @@ -28,8 +27,8 @@ func Test_createPlan(t *testing.T) { func Test_plan_addCommand(t *testing.T) { type fields struct { - Commands []command - Decisions []string + Commands []orderedCommand + Decisions []orderedDecision Created time.Time } type args struct { @@ -43,8 +42,8 @@ func Test_plan_addCommand(t *testing.T) { { name: "testing command 1", fields: fields{ - Commands: []command{}, - Decisions: []string{}, + Commands: []orderedCommand{}, + Decisions: []orderedDecision{}, Created: time.Now(), }, args: args{ @@ -63,7 +62,7 @@ func Test_plan_addCommand(t *testing.T) { Decisions: tt.fields.Decisions, Created: tt.fields.Created, } - p.addCommand(tt.args.c) + p.addCommand(tt.args.c, 0) if got := len(p.Commands); got != 1 { t.Errorf("addCommand(): got %v, want 1", got) } @@ -73,8 +72,8 @@ func Test_plan_addCommand(t *testing.T) { func Test_plan_addDecision(t *testing.T) { type fields struct { - Commands []command - Decisions []string + Commands []orderedCommand + Decisions []orderedDecision Created time.Time } type args struct { @@ -88,8 +87,8 @@ func Test_plan_addDecision(t *testing.T) { { name: "testing decision adding", fields: fields{ - Commands: []command{}, - Decisions: []string{}, + Commands: []orderedCommand{}, + Decisions: []orderedDecision{}, Created: time.Now(), }, args: args{ @@ -104,7 +103,7 @@ func Test_plan_addDecision(t *testing.T) { Decisions: tt.fields.Decisions, Created: tt.fields.Created, } - p.addDecision(tt.args.decision) + p.addDecision(tt.args.decision, 0) if got := len(p.Decisions); got != 1 { t.Errorf("addDecision(): got %v, want 1", got) } @@ -112,54 +111,54 @@ func Test_plan_addDecision(t *testing.T) { } } -func Test_plan_execPlan(t *testing.T) { - type fields struct { - Commands []command - Decisions []string - Created time.Time - } - tests := []struct { - name string - fields fields - }{ - { - name: "testing executing a plan", - fields: fields{ - Commands: []command{ - { - Cmd: "bash", - Args: []string{"-c", "touch hello.world"}, - Description: "Creating hello.world file.", - }, { - Cmd: "bash", - Args: []string{"-c", "touch hello.world1"}, - Description: "Creating hello.world1 file.", - }, - }, - Decisions: []string{"Create hello.world.", "Create hello.world1."}, - Created: time.Now(), - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - p := plan{ - Commands: tt.fields.Commands, - Decisions: tt.fields.Decisions, - Created: tt.fields.Created, - } - p.execPlan() - c := command{ - Cmd: "bash", - Args: []string{"-c", "ls | grep hello.world | wc -l"}, - Description: "", - } - if _, got := c.exec(false, false); strings.TrimSpace(got) != "2" { - t.Errorf("execPlan(): got %v, want hello world, again!", got) - } - }) - } -} +// func Test_plan_execPlan(t *testing.T) { +// type fields struct { +// Commands []command +// Decisions []string +// Created time.Time +// } +// tests := []struct { +// name string +// fields fields +// }{ +// { +// name: "testing executing a plan", +// fields: fields{ +// Commands: []command{ +// { +// Cmd: "bash", +// Args: []string{"-c", "touch hello.world"}, +// Description: "Creating hello.world file.", +// }, { +// Cmd: "bash", +// Args: []string{"-c", "touch hello.world1"}, +// Description: "Creating hello.world1 file.", +// }, +// }, +// Decisions: []string{"Create hello.world.", "Create hello.world1."}, +// Created: time.Now(), +// }, +// }, +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// p := plan{ +// Commands: tt.fields.Commands, +// Decisions: tt.fields.Decisions, +// Created: tt.fields.Created, +// } +// p.execPlan() +// c := command{ +// Cmd: "bash", +// Args: []string{"-c", "ls | grep hello.world | wc -l"}, +// Description: "", +// } +// if _, got := c.exec(false, false); strings.TrimSpace(got) != "2" { +// t.Errorf("execPlan(): got %v, want hello world, again!", got) +// } +// }) +// } +// } // func Test_plan_printPlanCmds(t *testing.T) { // type fields struct { diff --git a/release.go b/release.go index 0373d491..5052f6a6 100644 --- a/release.go +++ b/release.go @@ -26,17 +26,15 @@ type release struct { // validateRelease validates if a release inside a desired state meets the specifications or not. // check the full specification @ https://github.com/Praqma/helmsman/docs/desired_state_spec.md -func validateRelease(r *release, names map[string]bool) (bool, string) { +func validateRelease(r *release, names map[string]bool, s state) (bool, string) { _, err := os.Stat(r.ValuesFile) if r.Name == "" || names[r.Name] { return false, "release name can't be empty and must be unique." } else if nsOverride == "" && r.Namespace == "" { return false, "release targeted namespace can't be empty." - } else if nsOverride == "" && r.Namespace != "" { - if !checkNamespaceDefined(r.Namespace) { - return false, "release " + r.Name + " is using namespace [ " + r.Namespace + " ] which is not defined in the Namespaces section of your desired state file." + - " Release [ " + r.Name + " ] can't be installed in that Namespace until its defined." - } + } else if nsOverride == "" && r.Namespace != "" && !checkNamespaceDefined(r.Namespace, s) { + return false, "release " + r.Name + " is using namespace [ " + r.Namespace + " ] which is not defined in the Namespaces section of your desired state file." + + " Release [ " + r.Name + " ] can't be installed in that Namespace until its defined." } else if r.Chart == "" || !strings.ContainsAny(r.Chart, "/") { return false, "chart can't be empty and must be of the format: repo/chart." } else if r.Version == "" { @@ -60,7 +58,7 @@ func validateRelease(r *release, names map[string]bool) (bool, string) { } // checkNamespaceDefined checks if a given namespace is defined in the namespaces section of the desired state file -func checkNamespaceDefined(ns string) bool { +func checkNamespaceDefined(ns string, s state) bool { _, ok := s.Namespaces[ns] if !ok { return false diff --git a/release_test.go b/release_test.go index 17c5ec65..0874bc05 100644 --- a/release_test.go +++ b/release_test.go @@ -6,8 +6,18 @@ import ( ) func Test_validateRelease(t *testing.T) { + st := state{ + Metadata: make(map[string]string), + Certificates: make(map[string]string), + Settings: make(map[string]string), + Namespaces: map[string]namespace{"namespace": namespace{false}}, + HelmRepos: make(map[string]string), + Apps: make(map[string]*release), + } + type args struct { - r release + s state + r *release } tests := []struct { name string @@ -18,7 +28,7 @@ func Test_validateRelease(t *testing.T) { { name: "test case 1", args: args{ - r: release{ + r: &release{ Name: "release1", Description: "", Namespace: "namespace", @@ -29,30 +39,32 @@ func Test_validateRelease(t *testing.T) { Purge: true, Test: true, }, + s: st, }, want: true, want1: "", }, { name: "test case 2", args: args{ - r: release{ + r: &release{ Name: "release2", Description: "", Namespace: "namespace", Enabled: true, Chart: "repo/chartX", Version: "1.0", - ValuesFile: "values.yaml", + ValuesFile: "xyz.yaml", Purge: true, Test: true, }, + s: st, }, want: false, want1: "valuesFile must be a valid file path for a yaml file, Or can be left empty.", }, { name: "test case 3", args: args{ - r: release{ + r: &release{ Name: "release3", Description: "", Namespace: "namespace", @@ -63,13 +75,14 @@ func Test_validateRelease(t *testing.T) { Purge: true, Test: true, }, + s: st, }, want: false, want1: "valuesFile must be a valid file path for a yaml file, Or can be left empty.", }, { name: "test case 4", args: args{ - r: release{ + r: &release{ Name: "release1", Description: "", Namespace: "namespace", @@ -80,13 +93,14 @@ func Test_validateRelease(t *testing.T) { Purge: true, Test: true, }, + s: st, }, want: false, want1: "release name can't be empty and must be unique.", }, { name: "test case 5", args: args{ - r: release{ + r: &release{ Name: "", Description: "", Namespace: "namespace", @@ -97,13 +111,14 @@ func Test_validateRelease(t *testing.T) { Purge: true, Test: true, }, + s: st, }, want: false, want1: "release name can't be empty and must be unique.", }, { name: "test case 6", args: args{ - r: release{ + r: &release{ Name: "release6", Description: "", Namespace: "", @@ -114,13 +129,14 @@ func Test_validateRelease(t *testing.T) { Purge: true, Test: true, }, + s: st, }, want: false, want1: "release targeted namespace can't be empty.", }, { name: "test case 7", args: args{ - r: release{ + r: &release{ Name: "release7", Description: "", Namespace: "namespace", @@ -131,13 +147,14 @@ func Test_validateRelease(t *testing.T) { Purge: true, Test: true, }, + s: st, }, want: false, want1: "chart can't be empty and must be of the format: repo/chart.", }, { name: "test case 8", args: args{ - r: release{ + r: &release{ Name: "release8", Description: "", Namespace: "namespace", @@ -148,13 +165,14 @@ func Test_validateRelease(t *testing.T) { Purge: true, Test: true, }, + s: st, }, want: false, want1: "chart can't be empty and must be of the format: repo/chart.", }, { name: "test case 9", args: args{ - r: release{ + r: &release{ Name: "release9", Description: "", Namespace: "namespace", @@ -165,13 +183,14 @@ func Test_validateRelease(t *testing.T) { Purge: true, Test: true, }, + s: st, }, want: false, want1: "version can't be empty.", }, { name: "test case 10", args: args{ - r: release{ + r: &release{ Name: "release10", Description: "", Namespace: "namespace", @@ -182,6 +201,7 @@ func Test_validateRelease(t *testing.T) { Purge: true, Test: true, }, + s: st, }, want: true, want1: "", @@ -190,7 +210,7 @@ func Test_validateRelease(t *testing.T) { names := make(map[string]bool) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, got1 := validateRelease(tt.args.r, names) + got, got1 := validateRelease(tt.args.r, names, tt.args.s) if got != tt.want { t.Errorf("validateRelease() got = %v, want %v", got, tt.want) } diff --git a/state.go b/state.go index 38c38c4b..a7b60b07 100644 --- a/state.go +++ b/state.go @@ -118,7 +118,7 @@ func (s state) validate() (bool, string) { names := make(map[string]bool) for appLabel, r := range s.Apps { - result, errMsg := validateRelease(r, names) + result, errMsg := validateRelease(r, names, s) if !result { return false, "ERROR: apps validation failed -- for app [" + appLabel + " ]. " + errMsg } diff --git a/state_test.go b/state_test.go index df39b445..e3185fb0 100644 --- a/state_test.go +++ b/state_test.go @@ -12,7 +12,7 @@ func Test_state_validate(t *testing.T) { Settings map[string]string Namespaces map[string]namespace HelmRepos map[string]string - Apps map[string]release + Apps map[string]*release } tests := []struct { name string @@ -40,7 +40,7 @@ func Test_state_validate(t *testing.T) { "stable": "https://kubernetes-charts.storage.googleapis.com", "myrepo": "s3://my-repo/charts", }, - Apps: make(map[string]release), + Apps: make(map[string]*release), }, want: true, }, { @@ -59,7 +59,7 @@ func Test_state_validate(t *testing.T) { "stable": "https://kubernetes-charts.storage.googleapis.com", "myrepo": "s3://my-repo/charts", }, - Apps: make(map[string]release), + Apps: make(map[string]*release), }, want: false, }, { @@ -83,7 +83,7 @@ func Test_state_validate(t *testing.T) { "stable": "https://kubernetes-charts.storage.googleapis.com", "myrepo": "s3://my-repo/charts", }, - Apps: make(map[string]release), + Apps: make(map[string]*release), }, want: false, }, { @@ -104,7 +104,7 @@ func Test_state_validate(t *testing.T) { "stable": "https://kubernetes-charts.storage.googleapis.com", "myrepo": "s3://my-repo/charts", }, - Apps: make(map[string]release), + Apps: make(map[string]*release), }, want: true, }, { @@ -128,7 +128,7 @@ func Test_state_validate(t *testing.T) { "stable": "https://kubernetes-charts.storage.googleapis.com", "myrepo": "s3://my-repo/charts", }, - Apps: make(map[string]release), + Apps: make(map[string]*release), }, want: true, }, { @@ -152,7 +152,7 @@ func Test_state_validate(t *testing.T) { "stable": "https://kubernetes-charts.storage.googleapis.com", "myrepo": "s3://my-repo/charts", }, - Apps: make(map[string]release), + Apps: make(map[string]*release), }, want: false, }, { @@ -176,7 +176,7 @@ func Test_state_validate(t *testing.T) { "stable": "https://kubernetes-charts.storage.googleapis.com", "myrepo": "s3://my-repo/charts", }, - Apps: make(map[string]release), + Apps: make(map[string]*release), }, want: true, }, { @@ -200,7 +200,7 @@ func Test_state_validate(t *testing.T) { "stable": "https://kubernetes-charts.storage.googleapis.com", "myrepo": "s3://my-repo/charts", }, - Apps: make(map[string]release), + Apps: make(map[string]*release), }, want: false, }, { @@ -223,7 +223,7 @@ func Test_state_validate(t *testing.T) { "stable": "https://kubernetes-charts.storage.googleapis.com", "myrepo": "s3://my-repo/charts", }, - Apps: make(map[string]release), + Apps: make(map[string]*release), }, want: false, }, { @@ -244,7 +244,7 @@ func Test_state_validate(t *testing.T) { "stable": "https://kubernetes-charts.storage.googleapis.com", "myrepo": "s3://my-repo/charts", }, - Apps: make(map[string]release), + Apps: make(map[string]*release), }, want: false, }, { @@ -268,7 +268,7 @@ func Test_state_validate(t *testing.T) { "stable": "https://kubernetes-charts.storage.googleapis.com", "myrepo": "s3://my-repo/charts", }, - Apps: make(map[string]release), + Apps: make(map[string]*release), }, want: false, }, { @@ -286,7 +286,7 @@ func Test_state_validate(t *testing.T) { "stable": "https://kubernetes-charts.storage.googleapis.com", "myrepo": "s3://my-repo/charts", }, - Apps: make(map[string]release), + Apps: make(map[string]*release), }, want: true, }, { @@ -302,7 +302,7 @@ func Test_state_validate(t *testing.T) { "stable": "https://kubernetes-charts.storage.googleapis.com", "myrepo": "s3://my-repo/charts", }, - Apps: make(map[string]release), + Apps: make(map[string]*release), }, want: false, }, { @@ -318,7 +318,7 @@ func Test_state_validate(t *testing.T) { "stable": "https://kubernetes-charts.storage.googleapis.com", "myrepo": "s3://my-repo/charts", }, - Apps: make(map[string]release), + Apps: make(map[string]*release), }, want: false, }, { @@ -333,7 +333,7 @@ func Test_state_validate(t *testing.T) { "staging": namespace{false}, }, HelmRepos: nil, - Apps: make(map[string]release), + Apps: make(map[string]*release), }, want: false, }, { @@ -348,7 +348,7 @@ func Test_state_validate(t *testing.T) { "staging": namespace{false}, }, HelmRepos: map[string]string{}, - Apps: make(map[string]release), + Apps: make(map[string]*release), }, want: false, }, { @@ -366,7 +366,7 @@ func Test_state_validate(t *testing.T) { "stable": "https://kubernetes-charts.storage.googleapis.com", "myrepo": "", }, - Apps: make(map[string]release), + Apps: make(map[string]*release), }, want: false, }, { @@ -384,7 +384,7 @@ func Test_state_validate(t *testing.T) { "stable": "https://kubernetes-charts.storage.googleapis.com", "myrepo": "s3//my-repo/charts", }, - Apps: make(map[string]release), + Apps: make(map[string]*release), }, want: false, }, From 5740a52c2781ef0315dc6ea252722f6bd627ec84 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sun, 1 Apr 2018 15:26:10 +0200 Subject: [PATCH 0131/1127] bumping go version in the build/test docker image. --- test_files/dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_files/dockerfile b/test_files/dockerfile index 0c4e5bab..eea05ba7 100644 --- a/test_files/dockerfile +++ b/test_files/dockerfile @@ -1,6 +1,6 @@ # This is a docker image for the helmsman test container # It can be pulled from praqma/helmsman-test -FROM golang:1.8-alpine3.6 as builder +FROM golang:1.10-alpine3.7 as builder ENV KUBE_LATEST_VERSION v1.8.2 ENV HELM_VERSION v2.7.0 From 4f9cd93f7b10ea4dc32aed7f9e064d0c5eb91454 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sun, 1 Apr 2018 15:26:28 +0200 Subject: [PATCH 0132/1127] updating ci pipeline --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1a73239e..4cb2304a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -50,7 +50,7 @@ jobs: name: build docker images and push them to dockerhub command: | TAG=$(git describe --abbrev=0 --tags) - docker login -u samincl -p $DOCKERHUB + docker login -u $DOCKER_USER -p $DOCKERHUB docker build -t praqma/helmsman:$TAG-helm-v2.8.1 --build-arg HELM_VERSION=v2.8.1 dockerfile/. docker push praqma/helmsman:$TAG-helm-v2.8.1 docker build -t praqma/helmsman:$TAG-helm-v2.8.0 --build-arg HELM_VERSION=v2.8.0 dockerfile/. From 8d761f12cb8d866e4809b0b8d39041808240752c Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sun, 1 Apr 2018 19:51:46 +0200 Subject: [PATCH 0133/1127] updating versions --- CONTRIBUTION.md | 2 +- main.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md index adca7c2b..3c99751b 100644 --- a/CONTRIBUTION.md +++ b/CONTRIBUTION.md @@ -4,7 +4,7 @@ Pull requests, feeback/feature requests are all welcome. This guide will be upda ## Build helmsman from source -To build helmsman from source, you need go:1.8+. Follow the steps below: +To build helmsman from source, you need go:1.9+. Follow the steps below: ``` git clone https://github.com/Praqma/helmsman.git diff --git a/main.go b/main.go index 6d0ec970..7826a34f 100644 --- a/main.go +++ b/main.go @@ -19,7 +19,7 @@ var v bool var verbose bool var nsOverride string var checkCleanup bool -var version = "master" +var version = "v1.1.0" func main() { From 831c1f51c12fef2c0972d81950f2035a9ec8391e Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sun, 1 Apr 2018 20:01:44 +0200 Subject: [PATCH 0134/1127] updating README [skip ci] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 205a01c4..5842a7ea 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ curl -L https://github.com/Praqma/helmsman/releases/download/v1.1.0/helmsman_1.1 # on MacOS curl -L https://github.com/Praqma/helmsman/releases/download/v1.1.0/helmsman_1.1.0_darwin_amd64.tar.gz | tar zx -mv helmsman /bin/helmsman +mv helmsman /usr/local/bin/helmsman ``` ## As a docker image From 66660680add26b9a22c226ae0cd9c592d81d76eb Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Wed, 4 Apr 2018 11:41:08 +0200 Subject: [PATCH 0135/1127] fixing charts version validations when using older versions. --- main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index 7826a34f..41602dd2 100644 --- a/main.go +++ b/main.go @@ -19,7 +19,7 @@ var v bool var verbose bool var nsOverride string var checkCleanup bool -var version = "v1.1.0" +var version = "v1.1.1" func main() { @@ -156,7 +156,7 @@ func validateReleaseCharts(apps map[string]*release) (bool, string) { for app, r := range apps { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm search " + r.Chart + " --version " + r.Version}, + Args: []string{"-c", "helm search " + r.Chart + " --version " + " -l"}, Description: "validating if chart " + r.Chart + "-" + r.Version + " is available in the defined repos.", } From 98f4da31c5d6fcc86c5832afc1f166a82ec737e4 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Wed, 4 Apr 2018 11:43:15 +0200 Subject: [PATCH 0136/1127] disabling docker images builds in circleci. --- .circleci/config.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4cb2304a..7d64dc01 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -45,18 +45,18 @@ jobs: echo "releasing ..." goreleaser --release-notes release-notes.md - - setup_remote_docker - - run: - name: build docker images and push them to dockerhub - command: | - TAG=$(git describe --abbrev=0 --tags) - docker login -u $DOCKER_USER -p $DOCKERHUB - docker build -t praqma/helmsman:$TAG-helm-v2.8.1 --build-arg HELM_VERSION=v2.8.1 dockerfile/. - docker push praqma/helmsman:$TAG-helm-v2.8.1 - docker build -t praqma/helmsman:$TAG-helm-v2.8.0 --build-arg HELM_VERSION=v2.8.0 dockerfile/. - docker push praqma/helmsman:$TAG-helm-v2.8.0 - docker build -t praqma/helmsman:$TAG-helm-v2.7.2 --build-arg HELM_VERSION=v2.7.2 dockerfile/. - docker push praqma/helmsman:$TAG-helm-v2.7.2 + # - setup_remote_docker + # - run: + # name: build docker images and push them to dockerhub + # command: | + # TAG=$(git describe --abbrev=0 --tags) + # docker login -u $DOCKER_USER -p $DOCKERHUB + # docker build -t praqma/helmsman:$TAG-helm-v2.8.1 --build-arg HELM_VERSION=v2.8.1 dockerfile/. + # docker push praqma/helmsman:$TAG-helm-v2.8.1 + # docker build -t praqma/helmsman:$TAG-helm-v2.8.0 --build-arg HELM_VERSION=v2.8.0 dockerfile/. + # docker push praqma/helmsman:$TAG-helm-v2.8.0 + # docker build -t praqma/helmsman:$TAG-helm-v2.7.2 --build-arg HELM_VERSION=v2.7.2 dockerfile/. + # docker push praqma/helmsman:$TAG-helm-v2.7.2 workflows: From eafba32232bf4c6f25d98d7145179bfc3ec064d7 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Wed, 4 Apr 2018 11:43:42 +0200 Subject: [PATCH 0137/1127] updating release notes for v1.1.1 --- release-notes.md | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/release-notes.md b/release-notes.md index d555f6ff..6e6120fa 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,16 +1,3 @@ -# v1.1.0 +# v1.1.1 -This release introduces some new features and comes with several enhancements in logging and validation. - -- Introducing `priority` key for apps in the desired state to define the priority (order) of processing apps (useful for dependencies). -- Introducing `wait` key for apps to block helmsman execution until a release operation is complete. -- Intorducing the `--ns-override` flag for overriding namespaces defined in the desired state (useful for deploying from git branches to namespaces). -- Support initializing helm with a k8s service account. -- Introducing the `--verbose` flag for more detailed logs. -- Cleaning up any downloaded certs/keys after execution. -- Improved logging with full helm error messages. -- Improved validations for desired states. -- Bumping Helm and Kubectl versions in docker images. -- Providing multiple docker image tags for different helm versions. -- Fixing not waiting for helm Tiller to be ready. -- Fixing a bug in helm release search. \ No newline at end of file +- Fixing charts validation not allowing the use of older chart versions. \ No newline at end of file From 0b94c20ffa80661e2bb918a5c9347304abe8c4d0 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Wed, 4 Apr 2018 13:44:51 +0200 Subject: [PATCH 0138/1127] fixing charts validation not allowing the use of older chart versions. --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 41602dd2..af497fb1 100644 --- a/main.go +++ b/main.go @@ -156,7 +156,7 @@ func validateReleaseCharts(apps map[string]*release) (bool, string) { for app, r := range apps { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm search " + r.Chart + " --version " + " -l"}, + Args: []string{"-c", "helm search " + r.Chart + " --version " + r.Version + " -l"}, Description: "validating if chart " + r.Chart + "-" + r.Version + " is available in the defined repos.", } From 825b6d46997411118e2b6b5611a194779940442c Mon Sep 17 00:00:00 2001 From: David Archer Date: Sun, 22 Apr 2018 10:08:30 -0400 Subject: [PATCH 0139/1127] Fix typos in example.toml --- example.toml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/example.toml b/example.toml index 8a49d88f..a7c8a5fe 100644 --- a/example.toml +++ b/example.toml @@ -7,7 +7,7 @@ description = "example Desired State File for demo purposes." # paths to the certificate for connecting to the cluster # You can skip this if you use Helmsman on a machine with kubectl already connected to your k8s cluster. -# you have to use exact key names here : 'caCrt' for certificate and 'caKey' for the key and caClient for the client certififcate +# you have to use exact key names here : 'caCrt' for certificate and 'caKey' for the key and caClient for the client certificate [certificates] #caClient = "gs://mybucket/client.crt" # GCS bucket path #caCrt = "s3://mybucket/ca.crt" # S3 bucket path @@ -21,7 +21,7 @@ kubeContext = "minikube" # will try connect to this context first, if it does no ##clusterURI = "https://192.168.99.100:8443" # equivalent to the above #serviceAccount = "foo" # k8s serviceaccount must be already defined, validation error will be thrown otherwise -# define your environments and thier k8s namespaces +# define your environments and their k8s namespaces # syntax: # [namespaces.] -- whitespace before this entry does not matter, use whatever indentation style you like # protected = -- default to false @@ -29,7 +29,7 @@ kubeContext = "minikube" # will try connect to this context first, if it does no [namespaces.staging] protected = false [namespaces.production] - prtoected = true + protected = true # define any private/public helm charts repos you would like to get charts from @@ -50,14 +50,14 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" [apps.jenkins] name = "jenkins" # should be unique across all apps description = "jenkins" - namespace = "staging" # maps to the namespace as defined in environmetns above + namespace = "staging" # maps to the namespace as defined in environments above enabled = true # change to false if you want to delete this app release [empty = flase] chart = "stable/jenkins" # changing the chart name means delete and recreate this chart version = "0.14.3" # chart version valuesFile = "" # leaving it empty uses the default chart values purge = false # will only be considered when there is a delete operation test = false # run the tests when this release is installed for the first time only - prtoected = true + protected = true # [apps.jenkins.set] # values to override values from values.yaml with values from env vars-- useful for passing secrets to charts # db_username="$DB_USERNAME" # db_password="$DB_PASSWORD" @@ -68,7 +68,7 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" [apps.artifactory] name = "artifactory" # should be unique across all apps description = "artifactory" - namespace = "staging" # maps to the namespace as defined in environmetns above + namespace = "staging" # maps to the namespace as defined in environments above enabled = true # change to false if you want to delete this app release [empty = flase] chart = "stable/artifactory" # changing the chart name means delete and recreate this chart version = "7.0.6" # chart version From 9e4f8ea57ad899821d3a917ac2c6ebf14550c949 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Thu, 26 Apr 2018 13:47:07 +0200 Subject: [PATCH 0140/1127] [ci skip] adding missing info. --- docs/desired_state_specification.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 3dc2394c..3650e418 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -1,5 +1,5 @@ --- -version: v1.1.0 +version: v1.1.1 --- # Helmsman desired state specification @@ -68,7 +68,7 @@ The following options can be skipped if your kubectl context is already created - username : the username to be used for kubectl credentials. - password : an environment variable name (starting with `$`) where your password is stored. Get the password from your k8s admin or consult k8s docs on how to get/set it. -- clusterURI : the URI for your cluster API or an environment variable containing the URI. +- clusterURI : the URI for your cluster API or the name of an environment variable (starting with `$`) containing the URI. - serviceAccount: the name of the service account to use to initiate helm. This should have enough permissions to allow Helm to work and should exist already in the cluster. More details can be found in [helm's RBAC guide](https://github.com/kubernetes/helm/blob/master/docs/rbac.md) Example: @@ -110,7 +110,7 @@ protected = true Optional : No. -Purpose: defines the Helm repos where your charts can be found. You can add as many repos as you like. Public repos can be added without any additional setup. Private repos require authentication. +Synopsis: defines the Helm repos where your charts can be found. You can add as many repos as you like. Public repos can be added without any additional setup. Private repos require authentication. > AS of version v0.2.0, both AWS S3 and Google GCS buckets can be used for private repos (using the [Helm S3](https://github.com/hypnoglow/helm-s3) and [Helm GCS](https://github.com/nouney/helm-gcs) plugins). @@ -121,7 +121,7 @@ Authenticating to private helm repos: - Or, set `GCLOUD_CREDENTIALS` environment variable to contain the content of the credentials.json file. Options: -- you can define any key/value pairs. +- you can define any key/value pairs where key is the repo name and value is a valid URI for the repo. Example: @@ -144,7 +144,7 @@ Releases must have unique names which are defined under `apps`. Example: in `[ap Options: - name : the Helm release name. Releases must have unique names within a cluster. - description : a release metadata for human readers. -- env : the namespace where the release should be deployed. The namespace should map to one of the ones defined in [namespaces](#namespaces). +- namespace : the namespace where the release should be deployed. The namespace should map to one of the ones defined in [namespaces](#namespaces). - enabled : describes the required state of the release (true for enabled, false for disabled). Once a release is deployed, you can change it to false if you want to delete this app release [empty = flase]. - chart : the chart name. It should contain the repo name as well. Example: repoName/chartName. Changing the chart name means delete and reinstall this release using the new Chart. - version : the chart version. @@ -154,6 +154,7 @@ Options: - protected : defines if the release should be protected against changes. Namespace-level protection has higher priority than this flag. Check the [protection guide](how_to/protect_namespaces_and_releases.md) for more details. - wait : defines whether helmsman should block execution until all k8s resources are in a ready state. Default is false. - priority : defines the priority of applying operations on this release. Only negative values allowed and the lower the value, the higher the priority. Default priority is 0. Apps with equal priorities will be applied in the order they were added in your state file (DSF). +- [apps..set] : is used to override certain values from values.yaml with values from environment variables. This is particularily useful for passing secrets to charts. Example: @@ -165,7 +166,7 @@ Example: [apps.jenkins] name = "jenkins" description = "jenkins" - env = "staging" + namespace = "staging" enabled = true chart = "stable/jenkins" version = "0.9.0" @@ -175,5 +176,9 @@ Example: protected = false wait = true priority = -3 + [apps.jenkins.set] + secret1="$SECRET_ENV_VAR1" + secret2="$SECRET_ENV_VAR2" + ``` From 9b148e10d18ace7015c6fb88b581278741b9bbec Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 28 Apr 2018 19:10:58 +0200 Subject: [PATCH 0141/1127] refactoring some functions and adding new utilties for files manipulation --- utils.go | 73 +++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 62 insertions(+), 11 deletions(-) diff --git a/utils.go b/utils.go index 3a67cd23..1ad40fb9 100644 --- a/utils.go +++ b/utils.go @@ -3,6 +3,7 @@ package main import ( "bytes" "fmt" + "io" "io/ioutil" "log" "os" @@ -11,6 +12,8 @@ import ( "strings" "github.com/BurntSushi/toml" + "github.com/Praqma/helmsman/aws" + "github.com/Praqma/helmsman/gcs" ) // printMap prints to the console any map of string keys and values. @@ -87,15 +90,17 @@ func printHelp() { fmt.Println("Usage: helmsman [options]") fmt.Println() fmt.Println("Options:") - fmt.Println("--f specifies the desired state TOML file.") - fmt.Println("--debug prints basic logs during execution.") - fmt.Println("-verbose prints more verbose logs during execution.") - fmt.Println("--ns-override override defined namespaces with a provided one.") - fmt.Println("--apply generates and applies an action plan.") - fmt.Println("--help prints Helmsman help.") - fmt.Println("--v prints Helmsman version.") + fmt.Println("--f specifies the desired state TOML file.") + fmt.Println("--debug prints basic logs during execution.") + fmt.Println("--apply generates and applies an action plan.") + fmt.Println("--verbose prints more verbose logs during execution.") + fmt.Println("--ns-override override defined namespaces with a provided one.") + fmt.Println("--skip-validation generates and applies an action plan.") + fmt.Println("--help prints Helmsman help.") + fmt.Println("--v prints Helmsman version.") } +// logVersions prints the versions of kubectl and helm to the logs func logVersions() { cmd := command{ Cmd: "bash", @@ -146,12 +151,17 @@ func sliceContains(slice []string, s string) bool { return false } -// validateSerrviceAccount checks if k8s service account exists -func validateSerrviceAccount(sa string) (bool, string) { +// validateServiceAccount checks if k8s service account exists in a given namespace +func validateServiceAccount(sa string, namespace string) (bool, string) { + if namespace == "" { + namespace = "default" + } + ns := " -n " + namespace + cmd := command{ Cmd: "bash", - Args: []string{"-c", "kubectl get serviceaccount " + sa}, - Description: "validating that serviceaccount [ " + sa + " ] exists.", + Args: []string{"-c", "kubectl get serviceaccount " + sa + ns}, + Description: "validating that serviceaccount [ " + sa + " ] exists in namespace [ " + namespace + " ].", } if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { @@ -160,6 +170,47 @@ func validateSerrviceAccount(sa string) (bool, string) { return true, "" } +// downloadFile downloads a file from GCS or AWS buckets and name it with a given outfile +// if downloaded, returns the outfile name. If the file path is local file system path, it is returned as is. +func downloadFile(path string, outfile string) string { + if strings.HasPrefix(path, "s3") { + + tmp := getBucketElements(path) + aws.ReadFile(tmp["bucketName"], tmp["filePath"], outfile) + + } else if strings.HasPrefix(path, "gs") { + + tmp := getBucketElements(path) + gcs.ReadFile(tmp["bucketName"], tmp["filePath"], outfile) + + } else { + + log.Println("INFO: " + outfile + " will be used from local file system.") + copyFile(path, outfile) + } + return outfile +} + +// copyFile copies a file from source to destination +func copyFile(source string, destination string) { + from, err := os.Open(source) + if err != nil { + log.Fatal("ERROR: while copying " + source + " to " + destination + " : " + err.Error()) + } + defer from.Close() + + to, err := os.OpenFile(destination, os.O_RDWR|os.O_CREATE, 0666) + if err != nil { + log.Fatal("ERROR: while copying " + source + " to " + destination + " : " + err.Error()) + } + defer to.Close() + + _, err = io.Copy(to, from) + if err != nil { + log.Fatal("ERROR: while copying " + source + " to " + destination + " : " + err.Error()) + } +} + // deleteFile deletes a file func deleteFile(path string) { log.Println("INFO: cleaning up ... deleting " + path) From 294bff84f29fbf566975e2f75a2a97953f16e1c6 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 28 Apr 2018 19:11:55 +0200 Subject: [PATCH 0142/1127] refactoring printPlanCmds --- plan.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plan.go b/plan.go index 1baa7c10..480ac533 100644 --- a/plan.go +++ b/plan.go @@ -63,7 +63,6 @@ func (p plan) execPlan() { log.Println("INFO: Executing the following plan ... ") p.printPlan() for _, cmd := range p.Commands { - log.Println("INFO: attempting: -- ", cmd.Command.Description) if exitCode, msg := cmd.Command.exec(debug, verbose); exitCode != 0 { log.Fatal("Command returned with exit code: " + string(exitCode) + ". And error message: " + msg) } @@ -74,7 +73,7 @@ func (p plan) execPlan() { func (p plan) printPlanCmds() { fmt.Println("Printing the commands of the current plan ...") for _, cmd := range p.Commands { - fmt.Println(cmd.Command.Description) + fmt.Println(cmd.Command.Args[1]) } } From 18786d98ebfb736ff86e96e6b3778695676ce1f8 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 28 Apr 2018 19:12:51 +0200 Subject: [PATCH 0143/1127] adding --skip-validation option --- init.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/init.go b/init.go index a564b817..e28f54eb 100644 --- a/init.go +++ b/init.go @@ -28,6 +28,7 @@ func init() { flag.BoolVar(&v, "v", false, "show the version") flag.BoolVar(&verbose, "verbose", false, "show verbose execution logs") flag.StringVar(&nsOverride, "ns-override", "", "override defined namespaces with this one") + flag.BoolVar(&skipValidation, "skip-validation", false, "skip desired state validation") flag.Parse() @@ -61,9 +62,13 @@ func init() { log.Fatal(msg) } - // validate the desired state content - if result, msg := s.validate(); !result { // syntax validation - log.Fatal(msg) + if !skipValidation { + // validate the desired state content + if result, msg := s.validate(); !result { // syntax validation + log.Fatal(msg) + } + } else { + log.Println("INFO: desried state validation is skipped.") } } From e37ff0d45dfa28a5e03f5952a885304f6825af16 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 28 Apr 2018 19:15:38 +0200 Subject: [PATCH 0144/1127] adding representation for tiller deployment in namespaces and TLS options and refactoring validations. #22 #23 --- state.go | 64 +++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 57 insertions(+), 7 deletions(-) diff --git a/state.go b/state.go index a7b60b07..75a079ef 100644 --- a/state.go +++ b/state.go @@ -10,7 +10,14 @@ import ( // namespace type represents the fields of a namespace type namespace struct { - Protected bool + Protected bool + InstallTiller bool + TillerServiceAccount string + CaCert string + TillerCert string + TillerKey string + ClientCert string + ClientKey string } // state type represents the desired state of applications on a k8s cluster. @@ -64,13 +71,11 @@ func (s state) validate() (bool, string) { "but have not given me the cert/key to do so. Please add [caCrt] and [caKey] under Certifications. You might also need to provide [clientCrt]." } else if ok1 { for key, value := range s.Certificates { - tmp := subsituteEnv(value) - _, err1 := url.ParseRequestURI(tmp) - _, err2 := os.Stat(tmp) - if (err1 != nil || (!strings.HasPrefix(tmp, "s3://") && !strings.HasPrefix(tmp, "gs://"))) && err2 != nil { + r, path := isValidCert(value) + if !r { return false, "ERROR: certifications validation failed -- [ " + key + " ] must be a valid S3 or GCS bucket URL or a valid relative file path." } - s.Certificates[key] = tmp + s.Certificates[key] = path } } else { log.Println("INFO: certificates provided but not needed. Skipping certificates validation.") @@ -89,8 +94,31 @@ func (s state) validate() (bool, string) { return false, "ERROR: namespaces validation failed -- I need at least one namespace " + "to work with!" } + + for k, v := range s.Namespaces { + if !v.InstallTiller && k != "kube-system" { + log.Println("INFO: naemspace validation -- Tiller is not desired to be deployed in namespace [ " + k + " ].") + } else { + if tillerTLSEnabled(k) { + // validating the TLS certs and keys for Tiller + // if they are valid, their values (if they are env vars) are substituted + var ok1, ok2, ok3, ok4, ok5 bool + ok1, v.CaCert = isValidCert(v.CaCert) + ok2, v.ClientCert = isValidCert(v.ClientCert) + ok3, v.ClientKey = isValidCert(v.ClientKey) + ok4, v.TillerCert = isValidCert(v.TillerCert) + ok5, v.TillerKey = isValidCert(v.TillerKey) + if !ok1 || !ok2 || !ok3 || !ok4 || !ok5 { + return false, "ERROR: namespaces validation failed -- some certs/keys are not valid for Tiller TLS in namespace [ " + k + " ]." + } + log.Println("INFO: namespace validation -- Tiller is desired to be deployed with TLS in namespace [ " + k + " ]. ") + } else { + log.Println("INFO: namespace validation -- Tiller is desired to be deployed WITHOUT TLS in namespace [ " + k + " ]. ") + } + } + } } else { - log.Println("INFO: ns-override is used. Overriding all namespaces with [ " + nsOverride + " ] Skipping defined namespaces validation.") + log.Println("INFO: ns-override is used to override all namespaces with [ " + nsOverride + " ] Skipping defined namespaces validation.") } // repos @@ -137,6 +165,28 @@ func subsituteEnv(name string) string { return name } +// isValidCert checks if a certificate/key path/URI is valid +func isValidCert(value string) (bool, string) { + tmp := subsituteEnv(value) + _, err1 := url.ParseRequestURI(tmp) + _, err2 := os.Stat(tmp) + if err2 != nil && (err1 != nil || (!strings.HasPrefix(tmp, "s3://") && !strings.HasPrefix(tmp, "gs://"))) { + return false, "" + } + return true, tmp +} + +// tillerTLSEnabled checks if Tiller is desired to be deployed with TLS enabled for a given namespace +// TLS is considered desired ONLY if all certs and keys for both Tiller and the Helm client are defined. +func tillerTLSEnabled(namespace string) bool { + + ns := s.Namespaces[namespace] + if ns.CaCert != "" && ns.TillerCert != "" && ns.TillerKey != "" && ns.ClientCert != "" && ns.ClientKey != "" { + return true + } + return false +} + // print prints the desired state func (s state) print() { From a674154bf6f6b42e504408d14fef42ef2b57e88a Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 28 Apr 2018 19:17:51 +0200 Subject: [PATCH 0145/1127] refactoring helm helpers to improve performance and supporting TLS and multiple namespaces. #22 #23 --- decision_maker.go | 85 +++++++++++++------- helm_helpers.go | 192 +++++++++++++++++++++++++++++++++----------- main.go | 197 +++++++++++++++++++++++++++++----------------- 3 files changed, 327 insertions(+), 147 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index 9867e164..a8cfe3e9 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -1,15 +1,17 @@ package main import ( - "log" "strings" ) var outcome plan +var releases string // makePlan creates a plan of the actions needed to make the desired state come true. func makePlan(s *state) *plan { outcome = createPlan() + buildState() + for _, r := range s.Apps { decide(r, s) } @@ -42,7 +44,7 @@ func decide(r *release, s *state) { } else if helmReleaseExists("", r.Name, "deleted") { if !isProtected(r) { - inspectRollbackScenario(getDesiredNamespace(r), r) // rollback + rollbackRelease(getDesiredNamespace(r), r) // rollback } else { logDecision("DECISION: release "+r.Name+" is PROTECTED. Operations are not allowed on this release until "+ @@ -60,7 +62,7 @@ func decide(r *release, s *state) { "you remove its protection.", r.Priority) } - } else if helmReleaseExists("", r.Name, "all") { // not deployed in the desired namespace but deployed elsewhere + } else if helmReleaseExists("", r.Name, "") { // not deployed in the desired namespace but deployed elsewhere if !isProtected(r) { @@ -89,11 +91,11 @@ func testRelease(r *release) { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm test " + r.Name}, + Args: []string{"-c", "helm test " + r.Name + getDesiredTillerNamespace(r) + getTLSFlags(r)}, Description: "running tests for release [ " + r.Name + " ]", } outcome.addCommand(cmd, r.Priority) - logDecision("DECISION: release [ "+r.Name+" ] is required to be tested when installed/upgraded/rolledback. Got it!", r.Priority) + logDecision("DECISION: release [ "+r.Name+" ] is required to be tested when installed. Got it!", r.Priority) } @@ -103,7 +105,7 @@ func installRelease(namespace string, r *release) { releaseName := r.Name cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm install " + r.Chart + " -n " + releaseName + " --namespace " + namespace + getValuesFile(r) + " --version " + r.Version + getSetValues(r) + getWait(r)}, + Args: []string{"-c", "helm install " + r.Chart + " -n " + releaseName + " --namespace " + namespace + getValuesFile(r) + " --version " + r.Version + getSetValues(r) + getWait(r) + getDesiredTillerNamespace(r) + getTLSFlags(r)}, Description: "installing release [ " + releaseName + " ] in namespace [[ " + namespace + " ]]", } outcome.addCommand(cmd, r.Priority) @@ -115,26 +117,23 @@ func installRelease(namespace string, r *release) { } } -// inspectRollbackScenario evaluates if a rollback action needs to be taken for a given release. +// rollbackRelease evaluates if a rollback action needs to be taken for a given release. // if the release is already deleted but from a different namespace than the one specified in input, // it purge deletes it and create it in the spcified namespace. -func inspectRollbackScenario(namespace string, r *release) { +func rollbackRelease(namespace string, r *release) { releaseName := r.Name if getReleaseNamespace(r.Name) == namespace { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm rollback " + releaseName + " " + getReleaseRevision(releaseName, "deleted") + getWait(r)}, + Args: []string{"-c", "helm rollback " + releaseName + " " + getReleaseRevision(releaseName, "deleted") + getWait(r) + getDesiredTillerNamespace(r) + getTLSFlags(r)}, Description: "rolling back release [ " + releaseName + " ]", } outcome.addCommand(cmd, r.Priority) + upgradeRelease(r) logDecision("DECISION: release [ "+releaseName+" ] is currently deleted and is desired to be rolledback to "+ - "namespace [[ "+namespace+" ]] . No problem!", r.Priority) - - // if r.Test { - // testRelease(r) - // } + "namespace [[ "+namespace+" ]] . It will also be upgraded in case values have changed.", r.Priority) } else { @@ -175,7 +174,7 @@ func deleteRelease(r *release) { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm delete " + p + " " + r.Name}, + Args: []string{"-c", "helm delete " + p + " " + r.Name + getCurrentTillerNamespace(r) + getTLSFlags(r)}, Description: "deleting release [ " + r.Name + " ]", } outcome.addCommand(cmd, r.Priority) @@ -224,15 +223,11 @@ func inspectUpgradeScenario(namespace string, r *release) { func upgradeRelease(r *release) { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm upgrade " + r.Name + " " + r.Chart + getValuesFile(r) + " --version " + r.Version + " --force " + getSetValues(r) + getWait(r)}, + Args: []string{"-c", "helm upgrade " + r.Name + " " + r.Chart + getValuesFile(r) + " --version " + r.Version + " --force " + getSetValues(r) + getWait(r) + getDesiredTillerNamespace(r) + getTLSFlags(r)}, Description: "upgrading release [ " + r.Name + " ]", } outcome.addCommand(cmd, r.Priority) - - // if r.Test { - // testRelease(r) - // } } // reInstallRelease purge deletes a release and reinstalls it. @@ -242,31 +237,25 @@ func reInstallRelease(namespace string, r *release) { releaseName := r.Name delCmd := command{ Cmd: "bash", - Args: []string{"-c", "helm delete --purge " + releaseName}, + Args: []string{"-c", "helm delete --purge " + releaseName + getCurrentTillerNamespace(r) + getTLSFlags(r)}, Description: "deleting release [ " + releaseName + " ]", } outcome.addCommand(delCmd, r.Priority) installCmd := command{ Cmd: "bash", - Args: []string{"-c", "helm install " + r.Chart + " --version " + r.Version + " -n " + releaseName + " --namespace " + namespace + getValuesFile(r) + getSetValues(r) + getWait(r)}, + Args: []string{"-c", "helm install " + r.Chart + " --version " + r.Version + " -n " + releaseName + " --namespace " + namespace + getValuesFile(r) + getSetValues(r) + getWait(r) + getDesiredTillerNamespace(r) + getTLSFlags(r)}, Description: "installing release [ " + releaseName + " ] in namespace [[ " + namespace + " ]]", } outcome.addCommand(installCmd, r.Priority) logDecision("DECISION: release [ "+releaseName+" ] will be deleted from namespace [[ "+getReleaseNamespace(releaseName)+" ]] and reinstalled in [[ "+namespace+"]].", r.Priority) - // if r.Test { - // testRelease(releaseName) - // } } // logDecision adds the decisions made to the plan. // Depending on the debug flag being set or not, it will either log the the decision to output or not. func logDecision(decision string, priority int) { - if debug { - log.Println(decision) - } outcome.addDecision(decision, priority) } @@ -327,7 +316,7 @@ func getCurrentNamespaceProtection(r *release) bool { func isProtected(r *release) bool { // if the release does not exist in the cluster, it is not protected - if !helmReleaseExists("", r.Name, "all") { + if !helmReleaseExists("", r.Name, "") { return false } @@ -342,3 +331,41 @@ func isProtected(r *release) bool { return false } + +// getDesiredTillerNamespace returns a tiller-namespace flag with which a release is desired to be maintained +func getDesiredTillerNamespace(r *release) string { + + if s.Namespaces[r.Namespace].InstallTiller { + return " --tiller-namespace " + r.Namespace + } + + return "" // same as return " --tiller-namespace kube-system" +} + +// getCurrentTillerNamespace returns the tiller-namespace with which a release is currently maintained +func getCurrentTillerNamespace(r *release) string { + if v, ok := currentState[r.Name]; ok { + return " --tiller-namespace " + v.TillerNamespace + } + return "" +} + +// getTLSFlags returns TLS flags with which a release is maintained +// If the release where the namespace is to be deployed has Tiller deployed, the TLS flages will use certs/keys for that namespace (if any) +// otherwise, it will be the certs/keys for the kube-system namespace. +func getTLSFlags(r *release) string { + tls := "" + if s.Namespaces[r.Namespace].InstallTiller { + if tillerTLSEnabled(r.Namespace) { + + tls = " --tls --tls-ca-cert " + r.Namespace + "-ca.cert --tls-cert " + r.Namespace + "-client.cert --tls-key " + r.Namespace + "-client.key " + } + } else { + if tillerTLSEnabled("kube-system") { + + tls = " --tls --tls-ca-cert kube-system-ca.cert --tls-cert kube-system-client.cert --tls-key kube-system-client.key " + } + } + + return tls +} diff --git a/helm_helpers.go b/helm_helpers.go index 4ecef629..c074ca2c 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -2,17 +2,90 @@ package main import ( "log" + "strconv" "strings" + "time" ) -// helmRealseExists checks if a Helm release is/was deployed in a k8s cluster. -// The search criteria is: -// -// -releaseName: the name of the release to look for. Helm releases have unique names within a k8s cluster. -// -scope: defines where to search for the release. Options are: [deleted, deployed, all, failed] -// -namespace: search in that namespace (only applicable if searching for currently deployed releases) -func helmReleaseExists(namespace string, releaseName string, scope string) bool { +var currentState map[string]releaseState + +// releaseState represents the current state of a release +type releaseState struct { + Revision int + Updated time.Time + Status string + Chart string + Namespace string + TillerNamespace string +} + +// getAllReleases fetches a list of all releases in a k8s cluster +func getAllReleases() string { + result := getTillerReleases("kube-system") + for ns, v := range s.Namespaces { + if v.InstallTiller && ns != "kube-system" { + result = result + getTillerReleases(ns) + } + } + + return result +} + +func getTillerReleases(tillerNS string) string { + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm list --all --tiller-namespace " + tillerNS + getNSTLSFlags(tillerNS)}, + Description: "listing all existing releases in namespace [ " + tillerNS + " ]...", + } + + exitCode, result := cmd.exec(debug, verbose) + if exitCode != 0 { + log.Fatal("ERROR: failed to list all releases in namespace [ " + tillerNS + " ]: " + result) + } + + // appending tiller-namespace to each release found + lines := strings.Split(result, "\n") + for i, l := range lines { + if l != "" && !strings.HasPrefix(l, "NAME") && !strings.HasSuffix(l, "NAMESPACE") { + lines[i] = strings.TrimSuffix(l, "\n") + tillerNS + } + } + return strings.Join(lines, "\n") +} + +// buildState builds the currentState map contianing information about all releases existing in a k8s cluster +func buildState() { + log.Println("INFO: mapping the current helm state ...") + currentState = make(map[string]releaseState) + lines := strings.Split(getAllReleases(), "\n") + + // length -2 because of the first header line and the last line has a '\n' + // resulting in an empty line added at the end of the slice + for i := 1; i <= len(lines)-2; i++ { + if strings.HasPrefix(lines[i], "NAME") && strings.HasSuffix(lines[i], "NAMESPACE") { + continue + } + r, _ := strconv.Atoi(strings.Fields(lines[i])[1]) + t := strings.Fields(lines[i])[2] + " " + strings.Fields(lines[i])[3] + " " + strings.Fields(lines[i])[4] + " " + + strings.Fields(lines[i])[5] + " " + strings.Fields(lines[i])[6] + time, err := time.Parse("Mon Jan _2 15:04:05 2006", t) + if err != nil { + log.Fatal("ERROR: while converting release time: " + err.Error()) + } + currentState[strings.Fields(lines[i])[0]] = releaseState{ + Revision: r, + Updated: time, + Status: strings.Fields(lines[i])[7], + Chart: strings.Fields(lines[i])[8], + Namespace: strings.Fields(lines[i])[9], + TillerNamespace: strings.Fields(lines[i])[10], + } + } +} + +// Deprecated: listReleases lists releases in a given namespace and with a given status +func listReleases(namespace string, scope string) string { var options string if scope == "all" { options = "--all -q" @@ -29,73 +102,90 @@ func helmReleaseExists(namespace string, releaseName string, scope string) bool log.Println("INFO: scope " + scope + " is not valid, using [ all ] instead!") } + ns := namespace + tls := "" + if namespace == "" { + ns = "all" + tls = getNSTLSFlags("kube-system") + } else { + tls = getNSTLSFlags(namespace) + } + cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm list " + options}, - Description: "listing the existing releases in namespace [ " + namespace + " ] with status [ " + scope + " ]", + Args: []string{"-c", "helm list " + options + tls}, + Description: "listing the existing releases in namespace [ " + ns + " ] with status [ " + scope + " ]", } exitCode, result := cmd.exec(debug, verbose) - if exitCode == 0 { - - return sliceContains(strings.Split(result, "\n"), releaseName) + if exitCode != 0 { + log.Fatal("ERROR: failed to list " + scope + " releases in " + ns + " namespace(s): " + result) } - log.Fatal("ERROR: while checking helm release: " + result) + return result +} + +// helmRealseExists checks if a Helm release is/was deployed in a k8s cluster. +// The search criteria is: +// +// -releaseName: the name of the release to look for. Helm releases have unique names within a k8s cluster. +// -scope: defines where to search for the release. Options are: [deleted, deployed, all, failed] +// -namespace: search in that namespace (only applicable if searching for currently deployed releases) +func helmReleaseExists(namespace string, releaseName string, status string) bool { + v, ok := currentState[releaseName] + if !ok { + return false + } - return false + if namespace != "" && status != "" { + if v.Namespace == namespace && v.Status == strings.ToUpper(status) { + return true + } + return false + } else if namespace != "" { + if v.Namespace == namespace { + return true + } + return false + } else if status != "" { + if v.Status == strings.ToUpper(status) { + return true + } + return false + } + return true } // getReleaseNamespace returns the namespace in which a release is deployed. // throws an error and exits the program if the release does not exist. func getReleaseNamespace(releaseName string) string { - if result := getReleaseStatus(releaseName); result != "" { - if strings.Contains(result, "NAMESPACE:") { - s := strings.Split(result, "\nNAMESPACE: ")[1] - return strings.Split(s, "\n")[0] - } - } else { + v, ok := currentState[releaseName] + if !ok { log.Fatal("ERROR: seems release [ " + releaseName + " ] does not exist.") } - return "" + return v.Namespace } // getReleaseChart returns the Helm chart which is used by a deployed release. // throws an error and exits the program if the release does not exist. func getReleaseChart(releaseName string) string { - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm list " + releaseName}, - Description: "inspecting the chart used for release: " + releaseName, - } - exitCode, result := cmd.exec(debug, verbose) - if exitCode == 0 { - line := strings.Split(result, "\n")[1] - return strings.Fields(line)[8] // 8 is the position of chart details in helm ls output + v, ok := currentState[releaseName] + if !ok { + log.Fatal("ERROR: seems release [ " + releaseName + " ] does not exist.") } - log.Fatal("ERROR: release [ " + releaseName + " ] does not exist.") - - return "" + return v.Chart } // getReleaseRevision returns the revision number for a release (if it exists) func getReleaseRevision(releaseName string, state string) string { - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm list " + releaseName + " --" + state}, - Description: "inspecting the release revision for : " + releaseName, - } - exitCode, result := cmd.exec(debug, verbose) - if exitCode == 0 { - line := strings.Split(result, "\n")[1] - return strings.Fields(line)[1] // 1 is the position of revision number in helm ls output + v, ok := currentState[releaseName] + if !ok { + log.Fatal("ERROR: seems release [ " + releaseName + " ] does not exist.") } - log.Fatal("ERROR: release [ " + releaseName + " ] does not exist.") - - return "" + return strconv.Itoa(v.Revision) } // getReleaseChartName extracts and returns the Helm chart name from the chart info retrieved by getReleaseChart(). @@ -119,7 +209,7 @@ func getReleaseChartVersion(releaseName string) string { func getReleaseStatus(releaseName string) string { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm status " + releaseName}, + Args: []string{"-c", "helm status " + releaseName + getNSTLSFlags("kube-system")}, Description: "inspecting the status of release: " + releaseName, } @@ -132,3 +222,13 @@ func getReleaseStatus(releaseName string) string { return "" } + +// getNSTLSFlags returns TLS flags for a given namespace if it's deployed with TLS +func getNSTLSFlags(ns string) string { + tls := "" + if tillerTLSEnabled(ns) { + + tls = " --tls --tls-ca-cert " + ns + "-ca.cert --tls-cert " + ns + "-client.cert --tls-key " + ns + "-client.key " + } + return tls +} diff --git a/main.go b/main.go index af497fb1..211c71ec 100644 --- a/main.go +++ b/main.go @@ -6,7 +6,6 @@ import ( "strings" "time" - "github.com/Praqma/helmsman/aws" "github.com/Praqma/helmsman/gcs" ) @@ -19,7 +18,8 @@ var v bool var verbose bool var nsOverride string var checkCleanup bool -var version = "v1.1.1" +var skipValidation bool +var version = "v1.2.0-rc" func main() { @@ -31,10 +31,22 @@ func main() { checkCleanup = true } + // add/validate namespaces + addNamespaces(s.Namespaces) + if r, msg := initHelm(); !r { log.Fatal(msg) } + // check if helm Tiller is ready + for k, v := range s.Namespaces { + if v.InstallTiller { + waitForTiller(k) + } + } + + waitForTiller("kube-system") + if verbose { logVersions() } @@ -44,23 +56,22 @@ func main() { log.Fatal(msg) } - // validate charts-versions exist in defined repos - if r, msg := validateReleaseCharts(s.Apps); !r { - log.Fatal(msg) + if !skipValidation { + // validate charts-versions exist in defined repos + if r, msg := validateReleaseCharts(s.Apps); !r { + log.Fatal(msg) + } + } else { + log.Println("INFO: charts validation is skipped.") } - // add/validate namespaces - addNamespaces(s.Namespaces) - - // check if helm Tiller is ready - waitForTiller() - + log.Println("INFO: checking what I need to do for your charts ... ") p := makePlan(&s) - if !apply { - p.sortPlan() - p.printPlan() - } else { + p.sortPlan() + p.printPlan() + + if apply { p.execPlan() } @@ -90,29 +101,89 @@ func setKubeContext(context string) bool { return true } -// initHelm initialize helm on a k8s cluster -func initHelm() (bool, string) { - serviceAccount := "" - if value, ok := s.Settings["serviceAccount"]; ok { - if ok, err := validateSerrviceAccount(value); ok { - serviceAccount = "--service-account " + value +// deployTiller deploys Helm's Tiller in a specific namespace with a serviceAccount +// If serviceAccount is not provided (empty string), the defaultServiceAccount is used. +// If no defaultServiceAccount is provided, Tiller is deployed with the namespace default service account +// If no namespace is provided, Tiller is deployed to kube-system +func deployTiller(namespace string, serviceAccount string, defaultServiceAccount string) (bool, string) { + sa := "" + if serviceAccount != "" { + if ok, err := validateServiceAccount(serviceAccount, namespace); ok { + sa = "--service-account " + serviceAccount } else { - return false, "ERROR: while initializing helm: " + err + return false, "ERROR: while deploying Helm Tiller in namespace [" + namespace + "]: " + err } + } else if defaultServiceAccount != "" { + sa = "--service-account " + defaultServiceAccount + } + if namespace == "" { + namespace = "kube-system" + } + tillerNameSpace := " --tiller-namespace " + namespace + + tls := "" + if tillerTLSEnabled(namespace) { + tillerCert := downloadFile(s.Namespaces[namespace].TillerCert, namespace+"-tiller.cert") + tillerKey := downloadFile(s.Namespaces[namespace].TillerKey, namespace+"-tiller.key") + caCert := downloadFile(s.Namespaces[namespace].CaCert, namespace+"-ca.cert") + // client cert and key + downloadFile(s.Namespaces[namespace].ClientCert, namespace+"-client.cert") + downloadFile(s.Namespaces[namespace].ClientKey, namespace+"-client.key") + tls = " --tiller-tls --tiller-tls-cert " + tillerCert + " --tiller-tls-key " + tillerKey + " --tiller-tls-verify --tls-ca-cert " + caCert + } + + storageBackend := "" + if v, ok := s.Settings["storageBackend"]; ok && v == "secret" { + storageBackend = " --override 'spec.template.spec.containers[0].command'='{/tiller,--storage=secret}'" } cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm init --upgrade " + serviceAccount}, - Description: "initializing helm on the current context and upgrading Tiller.", + Args: []string{"-c", "helm init --upgrade " + sa + tillerNameSpace + tls + storageBackend}, + Description: "initializing helm on the current context and upgrading Tiller on namespace [ " + namespace + " ].", } if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { - return false, "ERROR: while initializing helm: " + err + return false, "ERROR: while deploying Helm Tiller in namespace [" + namespace + "]: " + err } return true, "" } +// initHelm initializes helm on a k8s cluster and deploys Tiller in one or more namespaces +func initHelm() (bool, string) { + + defaultSA := "" + if value, ok := s.Settings["serviceAccount"]; ok { + if ok, err := validateServiceAccount(value, "kube-system"); ok { + defaultSA = value + } else { + return false, "ERROR: while validating service account: " + err + } + } + + log.Println("INFO: deploying shared Tiller on namespace [ kube-system ].") + if v, ok := s.Namespaces["kube-system"]; ok { + if ok, err := deployTiller("kube-system", v.TillerServiceAccount, defaultSA); !ok { + return false, err + } + } else { + if ok, err := deployTiller("kube-system", "", defaultSA); !ok { + return false, err + } + } + + for k, ns := range s.Namespaces { + if ns.InstallTiller && k != "kube-system" { + log.Println("INFO: deploying Tiller on namespace [ " + k + " ].") + if ok, err := deployTiller(k, ns.TillerServiceAccount, defaultSA); !ok { + return false, err + } + } + } + + return true, "" +} + // addHelmRepos adds repositories to Helm if they don't exist already. // Helm does not mind if a repo with the same name exists. It treats it as an update. func addHelmRepos(repos map[string]string) (bool, string) { @@ -182,6 +253,7 @@ func addNamespaces(namespaces map[string]namespace) { } } +// overrideAppsNamespace replaces all apps namespaces with one specific namespace func overrideAppsNamespace(newNs string) { log.Println("INFO: overriding apps namespaces with [ " + newNs + " ] ...") for _, r := range s.Apps { @@ -227,59 +299,20 @@ func createContext() (bool, string) { // CA cert if caCrt != "" { - tmp := getBucketElements(caCrt) - if strings.HasPrefix(caCrt, "s3") { - - aws.ReadFile(tmp["bucketName"], tmp["filePath"], "ca.crt") - caCrt = "ca.crt" - - } else if strings.HasPrefix(caCrt, "gs") { - - gcs.ReadFile(tmp["bucketName"], tmp["filePath"], "ca.crt") - caCrt = "ca.crt" - - } else { - log.Println("INFO: CA certificate will be used from local file system.") - } + caCrt = downloadFile(caCrt, "ca.crt") } // CA key if caKey != "" { + caKey = downloadFile(caKey, "ca.key") - tmp := getBucketElements(caKey) - if strings.HasPrefix(caKey, "s3") { - - aws.ReadFile(tmp["bucketName"], tmp["filePath"], "ca.key") - caKey = "ca.key" - - } else if strings.HasPrefix(caKey, "gs") { - - gcs.ReadFile(tmp["bucketName"], tmp["filePath"], "ca.key") - caKey = "ca.key" - - } else { - log.Println("INFO: CA key will be used from local file system.") - } } // client certificate if caClient != "" { - tmp := getBucketElements(caClient) - if strings.HasPrefix(caClient, "s3") { - - aws.ReadFile(tmp["bucketName"], tmp["filePath"], "client.crt") - caClient = "client.crt" - - } else if strings.HasPrefix(caClient, "gs") { - - gcs.ReadFile(tmp["bucketName"], tmp["filePath"], "client.crt") - caClient = "client.crt" - - } else { - log.Println("INFO: CA client key will be used from local file system.") - } + caClient = downloadFile(caClient, "client.crt") } @@ -342,14 +375,14 @@ func getBucketElements(link string) map[string]string { // waitForTiller keeps checking if the helm Tiller is ready or not by executing helm list and checking its error (if any) // waits for 5 seconds before each new attempt and eventually terminates after 10 failed attempts. -func waitForTiller() { +func waitForTiller(namespace string) { attempt := 0 cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm list"}, - Description: "checking if helm Tiller is ready.", + Args: []string{"-c", "helm list --tiller-namespace " + namespace + getNSTLSFlags(namespace)}, + Description: "checking if helm Tiller is ready in namespace [ " + namespace + " ].", } exitCode, err := cmd.exec(debug, verbose) @@ -362,13 +395,15 @@ func waitForTiller() { time.Sleep(5 * time.Second) exitCode, err = cmd.exec(debug, verbose) } else { - log.Fatal("ERROR: while waiting for helm Tiller to be ready : " + err) + log.Fatal("ERROR: while waiting for helm Tiller to be ready in namespace [ " + namespace + " ] : " + err) } attempt = attempt + 1 } - log.Fatal("ERROR: timeout reached while waiting for helm Tiller to be ready. Aborting!") + log.Fatal("ERROR: timeout reached while waiting for helm Tiller to be ready in namespace [ " + namespace + " ]. Aborting!") } +// cleanup deletes the k8s certificates and keys files +// It also deletes any Tiller TLS certs and keys func cleanup() { if _, err := os.Stat("ca.crt"); err == nil { deleteFile("ca.crt") @@ -381,4 +416,22 @@ func cleanup() { if _, err := os.Stat("client.crt"); err == nil { deleteFile("client.crt") } + + for k := range s.Namespaces { + if _, err := os.Stat(k + "-tiller.cert"); err == nil { + deleteFile(k + "-tiller.cert") + } + if _, err := os.Stat(k + "-tiller.key"); err == nil { + deleteFile(k + "-tiller.key") + } + if _, err := os.Stat(k + "-ca.cert"); err == nil { + deleteFile(k + "-ca.cert") + } + if _, err := os.Stat(k + "-client.cert"); err == nil { + deleteFile(k + "-client.cert") + } + if _, err := os.Stat(k + "-client.key"); err == nil { + deleteFile(k + "-client.key") + } + } } From a7d331a15fa4e7f08ca319a74de1cda4a05daaf8 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 28 Apr 2018 19:29:08 +0200 Subject: [PATCH 0146/1127] updating the example for v1.2.0-rc --- example.toml | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/example.toml b/example.toml index a7c8a5fe..6864314f 100644 --- a/example.toml +++ b/example.toml @@ -1,4 +1,4 @@ -# version: v1.0.0 +# version: v1.2.0-rc # metadata -- add as many key/value pairs as you want [metadata] org = "example.com" @@ -20,16 +20,24 @@ kubeContext = "minikube" # will try connect to this context first, if it does no #clusterURI = "$K8S_URI" # the name of an environment variable containing the cluster API ##clusterURI = "https://192.168.99.100:8443" # equivalent to the above #serviceAccount = "foo" # k8s serviceaccount must be already defined, validation error will be thrown otherwise +storageBackend = "secret" # default is configMap # define your environments and their k8s namespaces # syntax: # [namespaces.] -- whitespace before this entry does not matter, use whatever indentation style you like # protected = -- default to false [namespaces] - [namespaces.staging] - protected = false [namespaces.production] protected = true + [namespaces.staging] + protected = false + installTiller = true + # tillerServiceAccount = "tiller-staging" # should already exist in the staging namespace + # caCert = "secrets/ca.cert.pem" # or an env var, e.g. "$CA_CERT_PATH" + # tillerCert = "secrets/tiller.cert.pem" # or S3 bucket s3://mybucket/tiller.crt + # tillerKey = "secrets/tiller.key.pem" # or GCS bucket gs://mybucket/tiller.key + # clientCert = "secrets/helm.cert.pem" + # clientKey = "secrets/helm.key.pem" # define any private/public helm charts repos you would like to get charts from @@ -47,10 +55,11 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" [apps] + # jenkins will be deployed using the Tiller in the staging namespace [apps.jenkins] name = "jenkins" # should be unique across all apps description = "jenkins" - namespace = "staging" # maps to the namespace as defined in environments above + namespace = "staging" # maps to the namespace as defined in namespaces above enabled = true # change to false if you want to delete this app release [empty = flase] chart = "stable/jenkins" # changing the chart name means delete and recreate this chart version = "0.14.3" # chart version @@ -58,17 +67,17 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" purge = false # will only be considered when there is a delete operation test = false # run the tests when this release is installed for the first time only protected = true - # [apps.jenkins.set] # values to override values from values.yaml with values from env vars-- useful for passing secrets to charts - # db_username="$DB_USERNAME" - # db_password="$DB_PASSWORD" priority= -3 wait = true + # [apps.jenkins.set] # values to override values from values.yaml with values from env vars-- useful for passing secrets to charts + # AdminPassword="$JENKINS_PASSWORD" # $JENKINS_PASSWORD must exist in the environment + # artifactory will be deployed using the Tiller in the kube-system namespace [apps.artifactory] name = "artifactory" # should be unique across all apps description = "artifactory" - namespace = "staging" # maps to the namespace as defined in environments above + namespace = "production" # maps to the namespace as defined in namespaces above enabled = true # change to false if you want to delete this app release [empty = flase] chart = "stable/artifactory" # changing the chart name means delete and recreate this chart version = "7.0.6" # chart version From 980ef7f4e16dfa09226c6f45de9bd1cea876dc11 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 28 Apr 2018 19:37:38 +0200 Subject: [PATCH 0147/1127] updating release notes --- release-notes.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/release-notes.md b/release-notes.md index 6e6120fa..83ec37f4 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,3 +1,13 @@ -# v1.1.1 +# v1.2.0-rc -- Fixing charts validation not allowing the use of older chart versions. \ No newline at end of file +This release focuses on improving Helmsman latency and supporting multi-tenant clusters. + +- Up to 7x faster than previous version. +- Introducing the `--skip-validation` flag which skips validating the desired state. +- Support for multi-tenant k8s clusters through: + - Supporting deployment of Tiller in several namespaces with different service accounts + - Supporting securing Tillers with TLS. + - Supporting using `Secrets` as a storage background instead of configMaps. + - Upgrading rolledback releases automatically after rollback to avoid missing changed values. + - More concise logs. + - Several minor enhancements. \ No newline at end of file From 0a5f0af46a3f7c3c87669e0e2a35b67f1ea179b9 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sat, 28 Apr 2018 19:48:31 +0200 Subject: [PATCH 0148/1127] fixing tests --- release_test.go | 2 +- state_test.go | 32 ++++++++++++++++---------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/release_test.go b/release_test.go index 0874bc05..ac83b8d7 100644 --- a/release_test.go +++ b/release_test.go @@ -10,7 +10,7 @@ func Test_validateRelease(t *testing.T) { Metadata: make(map[string]string), Certificates: make(map[string]string), Settings: make(map[string]string), - Namespaces: map[string]namespace{"namespace": namespace{false}}, + Namespaces: map[string]namespace{"namespace": namespace{false, false, "", "", "", "", "", ""}}, HelmRepos: make(map[string]string), Apps: make(map[string]*release), } diff --git a/state_test.go b/state_test.go index e3185fb0..4c7259a0 100644 --- a/state_test.go +++ b/state_test.go @@ -34,7 +34,7 @@ func Test_state_validate(t *testing.T) { "clusterURI": "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false}, + "staging": namespace{false, false, "", "", "", "", "", ""}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -53,7 +53,7 @@ func Test_state_validate(t *testing.T) { }, Settings: nil, Namespaces: map[string]namespace{ - "staging": namespace{false}, + "staging": namespace{false, false, "", "", "", "", "", ""}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -77,7 +77,7 @@ func Test_state_validate(t *testing.T) { "clusterURI": "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false}, + "staging": namespace{false, false, "", "", "", "", "", ""}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -98,7 +98,7 @@ func Test_state_validate(t *testing.T) { "kubeContext": "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false}, + "staging": namespace{false, false, "", "", "", "", "", ""}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -122,7 +122,7 @@ func Test_state_validate(t *testing.T) { "clusterURI": "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false}, + "staging": namespace{false, false, "", "", "", "", "", ""}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -146,7 +146,7 @@ func Test_state_validate(t *testing.T) { "clusterURI": "$URI", // unset env }, Namespaces: map[string]namespace{ - "staging": namespace{false}, + "staging": namespace{false, false, "", "", "", "", "", ""}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -170,7 +170,7 @@ func Test_state_validate(t *testing.T) { "clusterURI": "$SET_URI", // set env var }, Namespaces: map[string]namespace{ - "staging": namespace{false}, + "staging": namespace{false, false, "", "", "", "", "", ""}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -194,7 +194,7 @@ func Test_state_validate(t *testing.T) { "clusterURI": "https//192.168.99.100:8443", // invalid url }, Namespaces: map[string]namespace{ - "staging": namespace{false}, + "staging": namespace{false, false, "", "", "", "", "", ""}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -217,7 +217,7 @@ func Test_state_validate(t *testing.T) { "clusterURI": "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false}, + "staging": namespace{false, false, "", "", "", "", "", ""}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -238,7 +238,7 @@ func Test_state_validate(t *testing.T) { "clusterURI": "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false}, + "staging": namespace{false, false, "", "", "", "", "", ""}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -262,7 +262,7 @@ func Test_state_validate(t *testing.T) { "clusterURI": "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false}, + "staging": namespace{false, false, "", "", "", "", "", ""}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -280,7 +280,7 @@ func Test_state_validate(t *testing.T) { "kubeContext": "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false}, + "staging": namespace{false, false, "", "", "", "", "", ""}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -330,7 +330,7 @@ func Test_state_validate(t *testing.T) { "kubeContext": "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false}, + "staging": namespace{false, false, "", "", "", "", "", ""}, }, HelmRepos: nil, Apps: make(map[string]*release), @@ -345,7 +345,7 @@ func Test_state_validate(t *testing.T) { "kubeContext": "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false}, + "staging": namespace{false, false, "", "", "", "", "", ""}, }, HelmRepos: map[string]string{}, Apps: make(map[string]*release), @@ -360,7 +360,7 @@ func Test_state_validate(t *testing.T) { "kubeContext": "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false}, + "staging": namespace{false, false, "", "", "", "", "", ""}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -378,7 +378,7 @@ func Test_state_validate(t *testing.T) { "kubeContext": "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false}, + "staging": namespace{false, false, "", "", "", "", "", ""}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", From b3a0e1a68e1fcc3dd825905a5421b2f5b1d58838 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sun, 29 Apr 2018 20:23:24 +0200 Subject: [PATCH 0149/1127] updating docs for v1.2.0-rc [ci skip] --- README.md | 8 +- docs/desired_state_specification.md | 27 +++++- docs/how_to/define_namespaces.md | 36 ++++++-- docs/how_to/manipulate_apps.md | 2 +- docs/how_to/move_charts_across_namespaces.md | 8 +- docs/how_to/multitenant_clusters_guide.md | 92 +++++++++++++++++++ docs/how_to/override_defined_namespaces.md | 2 +- .../how_to/pass_secrets_from_env_variables.md | 5 +- .../how_to/protect_namespaces_and_releases.md | 6 +- docs/how_to/run_helmsman_in_ci.md | 4 +- .../run_helmsman_with_hosted_cluster.md | 9 +- docs/how_to/run_helmsman_with_minikube.md | 5 +- docs/how_to/test_charts.md | 5 +- docs/how_to/use_local_charts.md | 5 +- docs/how_to/use_private_helm_charts.md | 5 +- docs/how_to/use_the_priority_key.md | 4 +- 16 files changed, 185 insertions(+), 38 deletions(-) create mode 100644 docs/how_to/multitenant_clusters_guide.md diff --git a/README.md b/README.md index 5842a7ea..34163594 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ --- -version: v1.1.0 +version: v1.2.0-rc --- ![helmsman-logo](docs/images/helmsman.png) @@ -29,6 +29,7 @@ To show debugging details: - **Built for CD**: Helmsman can be used as a docker image or a binary. - **Applications as code**: describe your desired applications and manage them from a single version-controlled declarative file. +- **Suitable for Multitenant Clusters**: deploy Tiller in different namespaces with service accounts and TLS. - **Easy to use**: deep knowledge of Helm CLI and Kubectl is NOT manadatory to use Helmsman. - **Plan, View, apply**: you can run Helmsman to generate and view a plan with/without executing it. - **Portable**: Helmsman can be used to manage charts deployments on any k8s cluster. @@ -44,9 +45,9 @@ To show debugging details: Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. ``` # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v1.1.0/helmsman_1.1.0_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.2.0-rc/helmsman_1.2.0-rc_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v1.1.0/helmsman_1.1.0_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.2.0-rc/helmsman_1.2.0-rc_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` @@ -60,6 +61,7 @@ Documentation and How-Tos can be found [here](https://github.com/Praqma/helmsman Helmsman lets you: - [install/delete/upgrade/rollback your helm charts from code](https://github.com/Praqma/helmsman/blob/master/docs/how_to/manipulate_apps.md). +- [work safely in a multitenant cluster](https://github.com/Praqma/helmsman/blob/master/docs/how_to/multitenant_clusters_guide.md). - [pass secrets/user input to helm charts from environment variables](https://github.com/Praqma/helmsman/blob/master/docs/how_to/pass_secrets_from_env_variables.md). - [test releases when they are first installed](https://github.com/Praqma/helmsman/blob/master/docs/how_to/test_charts.md). - [use public and private helm charts](https://github.com/Praqma/helmsman/blob/master/docs/how_to/use_private_helm_charts.md). diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 3650e418..ddf48b1d 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -1,5 +1,5 @@ --- -version: v1.1.1 +version: v1.2.0-rc --- # Helmsman desired state specification @@ -59,7 +59,7 @@ caClient ="../path/to/my/local/client-certificate.crt" Optional : No. -Synopsis: provides data about your k8s cluster. +Synopsis: provides settings for connecting to your k8s cluster and configuring Helm's Tiller in the cluster. Options: - kubeContext : this is always required and defines what context to use in kubectl. Helmsman will try connect to this context first, if it does not exist, it will try to create it (i.e. connect to a k8s cluster) using the options below. @@ -70,6 +70,9 @@ The following options can be skipped if your kubectl context is already created - password : an environment variable name (starting with `$`) where your password is stored. Get the password from your k8s admin or consult k8s docs on how to get/set it. - clusterURI : the URI for your cluster API or the name of an environment variable (starting with `$`) containing the URI. - serviceAccount: the name of the service account to use to initiate helm. This should have enough permissions to allow Helm to work and should exist already in the cluster. More details can be found in [helm's RBAC guide](https://github.com/kubernetes/helm/blob/master/docs/rbac.md) +- storageBackend : by default Helm stores release information in configMaps, using secrets is for storage is recommended for security. Setting this flag to `secret` will deploy/upgrade Tiller with the `--storage=secret`. Other values will be skipped and configMaps will be used. + +> If you use `storageBackend` with a Tiller that has been previously deployed with configMaps as storage backend, you need to migrate your release information from the configMap to the new secret on your own. Example: @@ -81,17 +84,28 @@ kubeContext = "minikube" # clusterURI = "https://192.168.99.100:8443" ## clusterURI= "$K8S_URI" # serviceAccount = "my-service-account" +# storageBackend = "secret" ``` ## Namespaces Optional : No. -Synopsis: defines the namespaces to be used/created in your k8s cluster and wether they are protected or not. You can add as many namespaces as you like. +Synopsis: defines the namespaces to be used/created in your k8s cluster and wether they are protected or not. It also defines if Tiller should be deployed in these namespaces and with what configurations (TLS and service account). You can add as many namespaces as you like. If a namespaces does not already exist, Helmsman will create it. Options: - protected : defines if a namespace is protected (true or false). Default false. +- installTiller: defines if Tiller should be deployed in this namespace or not. Default is false. Any chart desired to be deployed into a namespace with a Tiller deployed, will be deployed using that Tiller and not the one in kube-system. +> Tiller will always be deployed into `kube-system`, even if you set installTiller for kube-system to false. + +- tillerServiceAccount: defines what service account to use when deploying Tiller. If not set, the `serviceAccount` defined in the `settings` section will be used. If that is also not defined, the namespace `default` service account will be used. If `installTiller` is not defined or set to false, this flag is ignored. +- The following options are `ALL` needed for deploying Tiller with TLS enabled. If they are not all defined, they will be ignored and Tiller will be deployed without TLS. All of these options can be provided as either: a valid local file path, a valid GCS or S3 bucket URI or an environment variable containing a file path or bucket URI. + - caCert: the CA certificate. + - tillerCert: the SSL certificate for Tiller. + - tillerKey: the SSL certificate private key for Tiller. + - clientCert: the SSL certificate for the Helm client. + - clientKey: the SSL certificate private key for the Helm client. > For the defintion of what a protected namespace means, check the [protection guide](how_to/protect_namespaces_and_releases.md) @@ -104,6 +118,13 @@ Example: protected = false [namespaces.production] protected = true +installTiller = true +tillerServiceAccount = "tiller-production" +caCert = "secrets/ca.cert.pem" +tillerCert = "secrets/tiller.cert.pem" +tillerKey = "$TILLER_KEY" # where TILLER_KEY=secrets/tiller.key.pem +clientCert = "gs://mybucket/mydir/helm.cert.pem" +clientKey = "s3://mybucket/mydir/helm.key.pem" ``` ## Helm Repos diff --git a/docs/how_to/define_namespaces.md b/docs/how_to/define_namespaces.md index 9594c18b..0a6a3eed 100644 --- a/docs/how_to/define_namespaces.md +++ b/docs/how_to/define_namespaces.md @@ -1,31 +1,50 @@ --- -version: v1.1.0 +version: v1.2.0-rc --- # define namespaces You can define namespaces to be used in your cluster. If they don't exist, Helmsman will create them for you. -``` +```toml ... [namespaces] [namespaces.staging] [namespaces.production] protected = true # default is false + ... -``` -You can then tell Helmsman to put specific releases in a specific namespace: +``` + +>For details on protecting a namespace, please check the [namespace/release protection guide](protect_namespaces_and_releases.md) + +As of `v1.2.0-rc`, you can instruct Helmsman to deploy Tiller into specific namespaces (with or without TLS). +```toml +[namespaces] +[namespaces.production] + protected = true + installTiller = true + tillerServiceAccount = "tiller-production" + caCert = "secrets/ca.cert.pem" + tillerCert = "secrets/tiller.cert.pem" + tillerKey = "$TILLER_KEY" # where TILLER_KEY=secrets/tiller.key.pem + clientCert = "gs://mybucket/mydir/helm.cert.pem" + clientKey = "s3://mybucket/mydir/helm.key.pem" ``` + +You can then tell Helmsman to deploy specific releases in a specific namespace: + +```toml ... [apps] [apps.jenkins] name = "jenkins" description = "jenkins" - namespace = "myOtherNamespace" # this is the pointer to the namespace defined above -- i.e. it deploys to namespace 'namespaceX' + namespace = "production" # pointing to the namespace defined above enabled = true chart = "stable/jenkins" version = "0.9.1" @@ -34,4 +53,9 @@ You can then tell Helmsman to put specific releases in a specific namespace: test = true ... -``` \ No newline at end of file + +``` + + +In the above example, `Jenkins` will be deployed in the production namespace using the Tiller deployed in the production namespace. If the production namespace was not configured to have Tiller deployed there, Jenkins will be deployed using the Tiller in `kube-system`. + diff --git a/docs/how_to/manipulate_apps.md b/docs/how_to/manipulate_apps.md index 86240207..9bbae3f4 100644 --- a/docs/how_to/manipulate_apps.md +++ b/docs/how_to/manipulate_apps.md @@ -1,5 +1,5 @@ --- -version: v1.1.0 +version: v1.2.0-rc --- # install releases diff --git a/docs/how_to/move_charts_across_namespaces.md b/docs/how_to/move_charts_across_namespaces.md index 5c939ab2..cc577336 100644 --- a/docs/how_to/move_charts_across_namespaces.md +++ b/docs/how_to/move_charts_across_namespaces.md @@ -1,5 +1,5 @@ --- -version: v1.1.0 +version: v1.2.0-rc --- # move charts across namespaces @@ -8,7 +8,7 @@ If you have a workflow for testing a release first in the `staging` namespace th > NOTE: If your chart uses a persistent volume, then you have to read the note on PVs below first. -``` +```toml ... [namespaces] @@ -30,11 +30,12 @@ If you have a workflow for testing a release first in the `staging` namespace th test = true ... + ``` Then if you change the namespace key for jenkins: -``` +```toml ... [namespaces] @@ -55,6 +56,7 @@ Then if you change the namespace key for jenkins: test = true ... + ``` Helmsman will delete the jenkins release from the `staging` namespace and install it in the `production` namespace (default in the above setup). diff --git a/docs/how_to/multitenant_clusters_guide.md b/docs/how_to/multitenant_clusters_guide.md new file mode 100644 index 00000000..d22c155e --- /dev/null +++ b/docs/how_to/multitenant_clusters_guide.md @@ -0,0 +1,92 @@ +--- +version: v1.2.0-rc +--- + +# Multitenant Clusters Guide + +This guide helps you use Helmsman to secure your Helm deployment with service accounts and TLS. + +>Checkout Helm's [security guide](https://github.com/kubernetes/helm/blob/master/docs/securing_installation.md) + +> These features are available starting from v1.2.0-rc + +## Deploying Tiller in multiple namespaces + +In a multitenant cluster, it is a good idea to separate the Helm work of different users. You can achieve that by deploying Tiller in multiple namespaces. This is done in the `namespaces` section using the `installTiller` flag: + +```toml + +[namespaces] + [namespaces.staging] + installTiller = true + [namespaces.production] + installTiller = true + [namespaces.developer1] + installTiller = true + [namespaces.developer2] + installTiller = true + +``` + +## Deploying Tiller with a service account + +You can also deploy each of the Tillers with a different k8s service account Or with a default service account of your choice. + +```toml + +[settings] +# other options +serviceAccount = "default-tiller-sa" + +[namespaces] + [namespaces.staging] + installTiller = true + tillerServiceAccount = "custom-sa" + + [namespaces.production] + installTiller = true + + [namespaces.developer1] + installTiller = true + tillerServiceAccount = "dev1-sa" + + [namespaces.developer2] + installTiller = true + tillerServiceAccount = "dev2-sa" + +``` + +In the example above, namespaces `staging, developer1 & developer2` will have Tiller deployed with different service accounts. +The `production` namespace ,however, will be deployed using the `default-tiller-sa` service account defined in the `settings` section. If this one is not defined, the production namespace Tiller will be deployed with k8s default service account. + +## Deploying Tiller with TLS enabled + +In a multitenant setting, it is also recommended to deploy Tiller with TLS enabled. This is also done in the `namespaces` section: + +```toml + +[namespaces] + [namespaces.kube-system] + installTiller = false # has no effect. Tiller is always deployed in kube-system + caCert = "secrets/kube-system/ca.cert.pem" + tillerCert = "secrets/kube-system/tiller.cert.pem" + tillerKey = "$TILLER_KEY" # where TILLER_KEY=secrets/kube-system/tiller.key.pem + clientCert = "gs://mybucket/mydir/helm.cert.pem" + clientKey = "s3://mybucket/mydir/helm.key.pem" + + [namespaces.staging] + installTiller = true + + [namespaces.production] + installTiller = true + tillerServiceAccount = "tiller-production" + caCert = "secrets/ca.cert.pem" + tillerCert = "secrets/tiller.cert.pem" + tillerKey = "$TILLER_KEY" # where TILLER_KEY=secrets/tiller.key.pem + clientCert = "gs://mybucket/mydir/helm.cert.pem" + clientKey = "s3://mybucket/mydir/helm.key.pem" + +``` + + + diff --git a/docs/how_to/override_defined_namespaces.md b/docs/how_to/override_defined_namespaces.md index b12b3681..6aa32050 100644 --- a/docs/how_to/override_defined_namespaces.md +++ b/docs/how_to/override_defined_namespaces.md @@ -11,7 +11,7 @@ This flag overrides all namespaces defined in your DSF with the single one you p # Example dsf.toml -``` +```toml [metadata] org = "example.com" description = "example Desired State File for demo purposes." diff --git a/docs/how_to/pass_secrets_from_env_variables.md b/docs/how_to/pass_secrets_from_env_variables.md index 64152e3b..3821395d 100644 --- a/docs/how_to/pass_secrets_from_env_variables.md +++ b/docs/how_to/pass_secrets_from_env_variables.md @@ -1,12 +1,12 @@ --- -version: v1.1.0 +version: v1.2.0-rc --- # pass secrets from env. variables: Starting from v0.1.3, Helmsman allows you to pass secrets and other user input to helm charts from environment variables as follows: -``` +```toml ... [apps] @@ -24,6 +24,7 @@ Starting from v0.1.3, Helmsman allows you to pass secrets and other user input t db_username= "$JIRA_DB_USERNAME" # pass any number of key/value pairs where the key is the input expected by the helm charts and the value is an env variable name starting with $ db_password= "$JIRA_DB_PASSWORD" ... + ``` These input variables will be passed to the chart when it is deployed/upgraded using helm's `--set <>=<>` \ No newline at end of file diff --git a/docs/how_to/protect_namespaces_and_releases.md b/docs/how_to/protect_namespaces_and_releases.md index fb21b6e2..8560433b 100644 --- a/docs/how_to/protect_namespaces_and_releases.md +++ b/docs/how_to/protect_namespaces_and_releases.md @@ -1,5 +1,5 @@ --- -version: v1.1.0 +version: v1.2.0-rc --- # Namespace and Release Protection @@ -27,7 +27,7 @@ Protection is supported in two forms: - **Namespace-level Protection**: is defined at the namespace level. A namespace can be declaratively defined to be protected in the desired state file as in the example below: -``` +```toml [namespaces] [namespaces.staging] protected = false @@ -38,7 +38,7 @@ Protection is supported in two forms: - **Release-level Protection** is defined at the release level as in the example below: -``` +```toml [apps] [apps.jenkins] diff --git a/docs/how_to/run_helmsman_in_ci.md b/docs/how_to/run_helmsman_in_ci.md index b144f206..930e90be 100644 --- a/docs/how_to/run_helmsman_in_ci.md +++ b/docs/how_to/run_helmsman_in_ci.md @@ -1,5 +1,5 @@ --- -version: v1.1.0 +version: v1.2.0-rc --- # Run Helmsman in CI @@ -13,7 +13,7 @@ jobs: deploy-apps: docker: - - image: praqma/helmsman:v1.1.0 + - image: praqma/helmsman:v1.2.0-rc steps: - checkout - run: diff --git a/docs/how_to/run_helmsman_with_hosted_cluster.md b/docs/how_to/run_helmsman_with_hosted_cluster.md index aa7e657f..7adf8629 100644 --- a/docs/how_to/run_helmsman_with_hosted_cluster.md +++ b/docs/how_to/run_helmsman_with_hosted_cluster.md @@ -1,5 +1,5 @@ --- -version: v1.1.0 +version: v1.2.0-rc --- You can manage Helm charts deployment on a hosted K8S cluster in the cloud or on-prem. You need to include the required information to connect to the cluster in your state file. @@ -28,7 +28,7 @@ The K8S user password is expected in an environment variable which you can give Below is an example state file: -``` +```toml [metadata] org = "orgX" maintainer = "k8s-admin" @@ -88,4 +88,7 @@ The above example requires the following environment variables to be set: - GOOGLE_APPLICATION_CREDENTIALS (since GCS is used for helm repo and certificates) - K8S_CLIENT_KEY (used in the file) - K8S_PASSWORD (used in the file) -- K8S_URI (used in the file) \ No newline at end of file +- K8S_URI (used in the file) + + +For secure Helm configurations to fit for multi-tenant clusters, check the [multitenancy guide](multitenant_clusters_guide.md). \ No newline at end of file diff --git a/docs/how_to/run_helmsman_with_minikube.md b/docs/how_to/run_helmsman_with_minikube.md index 381a140d..0e9417fb 100644 --- a/docs/how_to/run_helmsman_with_minikube.md +++ b/docs/how_to/run_helmsman_with_minikube.md @@ -1,11 +1,11 @@ --- -version: v1.1.0 +version: v1.2.0-rc --- You can run Helmsman locally as a binary application with Minikube, you just need to skip all the cluster connection settings in your desired state file. Below is the example.toml desired state file adapted to work with Minikube. -``` +```toml [metadata] org = "orgX" maintainer = "k8s-admin" @@ -18,7 +18,6 @@ kubeContext = "minikube" [helmRepos] stable = "https://kubernetes-charts.storage.googleapis.com" -incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" [apps] diff --git a/docs/how_to/test_charts.md b/docs/how_to/test_charts.md index 3210f643..ddf9b4fe 100644 --- a/docs/how_to/test_charts.md +++ b/docs/how_to/test_charts.md @@ -1,12 +1,12 @@ --- -version: v1.1.0 +version: v1.2.0-rc --- # test charts You can specifiy that you would like a chart to be tested whenever it is installed for the first time using the `test` key as follows: -``` +```toml ... [apps] @@ -22,4 +22,5 @@ You can specifiy that you would like a chart to be tested whenever it is install test = true # setting this to true, means you want the charts tests to be run on this release when it is intalled. ... + ``` \ No newline at end of file diff --git a/docs/how_to/use_local_charts.md b/docs/how_to/use_local_charts.md index ee2dc93d..d248e989 100644 --- a/docs/how_to/use_local_charts.md +++ b/docs/how_to/use_local_charts.md @@ -1,12 +1,12 @@ --- -version: v1.1.0 +version: v1.2.0-rc --- # use local helm charts You can use your locally developed charts. But first, you have to serve them on localhost using helm's `serve` option. -``` +```toml ... [helmRepos] @@ -15,4 +15,5 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" local = http://127.0.0.1:8879 ... + ``` \ No newline at end of file diff --git a/docs/how_to/use_private_helm_charts.md b/docs/how_to/use_private_helm_charts.md index a055c532..a6fe07ff 100644 --- a/docs/how_to/use_private_helm_charts.md +++ b/docs/how_to/use_private_helm_charts.md @@ -1,5 +1,5 @@ --- -version: v1.1.0 +version: v1.2.0-rc --- # use private helm charts @@ -10,7 +10,7 @@ Other hosting options might be supported in the future. Please open an issue if define your private repo: -``` +```toml ... [helmRepos] @@ -19,6 +19,7 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" myPrivateRepo = s3://this-is-a-private-repo/charts ... + ``` ## S3 diff --git a/docs/how_to/use_the_priority_key.md b/docs/how_to/use_the_priority_key.md index a36e5eb6..26877757 100644 --- a/docs/how_to/use_the_priority_key.md +++ b/docs/how_to/use_the_priority_key.md @@ -1,5 +1,5 @@ --- -version: v1.1.0 +version: v1.2.0-rc --- # Using the priority key for Apps @@ -10,7 +10,7 @@ Priority is an optinal flag and has a default value of 0 (zero). If set, it can ## Example -``` +```toml [metadata] org = "example.com" description = "example Desired State File for demo purposes." From c5e279a6284bd5f33bb35652612acf59766b5d1a Mon Sep 17 00:00:00 2001 From: bgeesaman Date: Mon, 30 Apr 2018 12:03:34 -0400 Subject: [PATCH 0150/1127] Fix index out of range issue Before: ```NAME REVISION UPDATED STATUS CHART NAMESPACE test-ingress 3 Mon Apr 30 13:43:05 2018 DEPLOYED k8s-ingress-0.1.1 kube-systemkube-system``` When `helm_helpers.go:82` goes to pick up the 10th field, it doesn't exist because it's a part of field 9. Adding a space ensures the columns don't run together. After: ```NAME REVISION UPDATED STATUS CHART NAMESPACE test-ingress 3 Mon Apr 30 13:43:05 2018 DEPLOYED k8s-ingress-0.1.1 kube-system kube-system`` --- helm_helpers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helm_helpers.go b/helm_helpers.go index c074ca2c..6853abe3 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -47,7 +47,7 @@ func getTillerReleases(tillerNS string) string { lines := strings.Split(result, "\n") for i, l := range lines { if l != "" && !strings.HasPrefix(l, "NAME") && !strings.HasSuffix(l, "NAMESPACE") { - lines[i] = strings.TrimSuffix(l, "\n") + tillerNS + lines[i] = strings.TrimSuffix(l, "\n") + " " + tillerNS } } return strings.Join(lines, "\n") From b963988020d4392abe5dfc5aba8ed6ea7cf2fff5 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Wed, 2 May 2018 20:54:24 +0200 Subject: [PATCH 0151/1127] fixing buildState for loop --- helm_helpers.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/helm_helpers.go b/helm_helpers.go index c074ca2c..82ea6b9f 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -31,6 +31,7 @@ func getAllReleases() string { return result } +// getTillerReleases gets releases deployed with a given Tiller (in agiven namespace) func getTillerReleases(tillerNS string) string { cmd := command{ Cmd: "bash", @@ -59,10 +60,8 @@ func buildState() { currentState = make(map[string]releaseState) lines := strings.Split(getAllReleases(), "\n") - // length -2 because of the first header line and the last line has a '\n' - // resulting in an empty line added at the end of the slice - for i := 1; i <= len(lines)-2; i++ { - if strings.HasPrefix(lines[i], "NAME") && strings.HasSuffix(lines[i], "NAMESPACE") { + for i := 0; i < len(lines); i++ { + if lines[i] == "" || (strings.HasPrefix(lines[i], "NAME") && strings.HasSuffix(lines[i], "NAMESPACE")) { continue } r, _ := strconv.Atoi(strings.Fields(lines[i])[1]) From 1f2e0a689606d4a99be535167fce6a2a34a3a158 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Wed, 2 May 2018 20:54:58 +0200 Subject: [PATCH 0152/1127] adding another possible tiller waiting message. --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 211c71ec..466a7053 100644 --- a/main.go +++ b/main.go @@ -390,7 +390,7 @@ func waitForTiller(namespace string) { for attempt < 10 { if exitCode == 0 { return - } else if strings.Contains(err, "could not find a ready tiller pod") { + } else if strings.Contains(err, "could not find a ready tiller pod") || strings.Contains(err, "could not find tiller") { log.Println("INFO: waiting for helm Tiller to be ready ...") time.Sleep(5 * time.Second) exitCode, err = cmd.exec(debug, verbose) From 4a2872710cb0cf0a2e48539f33f8e60a773c07de Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Wed, 2 May 2018 20:57:41 +0200 Subject: [PATCH 0153/1127] bumping version --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 466a7053..c706f677 100644 --- a/main.go +++ b/main.go @@ -19,7 +19,7 @@ var verbose bool var nsOverride string var checkCleanup bool var skipValidation bool -var version = "v1.2.0-rc" +var version = "v1.2.0-rc1" func main() { From 7ee0a5c4de0b7229e1a84ba301cfb359d48f5f35 Mon Sep 17 00:00:00 2001 From: daplho Date: Thu, 3 May 2018 13:47:16 -0700 Subject: [PATCH 0154/1127] fixing compare here as NAMESPACE comes with extra space at the end of each line --- helm_helpers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helm_helpers.go b/helm_helpers.go index 39236287..e0731099 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -61,7 +61,7 @@ func buildState() { lines := strings.Split(getAllReleases(), "\n") for i := 0; i < len(lines); i++ { - if lines[i] == "" || (strings.HasPrefix(lines[i], "NAME") && strings.HasSuffix(lines[i], "NAMESPACE")) { + if lines[i] == "" || (strings.HasPrefix(strings.TrimSpace(lines[i]), "NAME") && strings.HasSuffix(strings.TrimSpace(lines[i]), "NAMESPACE")) { continue } r, _ := strconv.Atoi(strings.Fields(lines[i])[1]) From 4639fe268e03c7920ec70ab260f6323ec733f959 Mon Sep 17 00:00:00 2001 From: daplho Date: Thu, 3 May 2018 16:34:56 -0700 Subject: [PATCH 0155/1127] decision maker will decide to reInstall if Version has dashes within the name as well as between the name and the version --- decision_maker.go | 4 ++-- helm_helpers.go | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index a8cfe3e9..fc8a8b6a 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -192,12 +192,12 @@ func inspectUpgradeScenario(namespace string, r *release) { releaseName := r.Name if getReleaseNamespace(releaseName) == namespace { - if extractChartName(r.Chart) == getReleaseChartName(releaseName) && r.Version != getReleaseChartVersion(releaseName) { + if extractChartName(r.Chart) == getReleaseChartName(releaseName, r.Version) && r.Version != getReleaseChartVersion(releaseName) { // upgrade upgradeRelease(r) logDecision("DECISION: release [ "+releaseName+" ] is desired to be upgraded. Planing this for you!", r.Priority) - } else if extractChartName(r.Chart) != getReleaseChartName(releaseName) { + } else if extractChartName(r.Chart) != getReleaseChartName(releaseName, r.Version) { reInstallRelease(namespace, r) logDecision("DECISION: release [ "+releaseName+" ] is desired to use a new Chart [ "+r.Chart+ " ]. I am planning a purge delete of the current release and will install it with the new chart in namespace [[ "+ diff --git a/helm_helpers.go b/helm_helpers.go index e0731099..2106feb4 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -189,10 +189,9 @@ func getReleaseRevision(releaseName string, state string) string { // getReleaseChartName extracts and returns the Helm chart name from the chart info retrieved by getReleaseChart(). // example: getReleaseChart() returns "jenkins-0.9.0" and this functions will extract "jenkins" from it. -func getReleaseChartName(releaseName string) string { +func getReleaseChartName(releaseName string, version string) string { chart := getReleaseChart(releaseName) - runes := []rune(chart) - return string(runes[0:strings.LastIndexByte(chart, '-')]) + return strings.TrimSuffix(strings.TrimSuffix(chart, version), "-") } // getReleaseChartVersion extracts and returns the Helm chart version from the chart info retrieved by getReleaseChart(). From a00de7a6bfb602983d342d5d0f5bed8eb9914839 Mon Sep 17 00:00:00 2001 From: daplho Date: Thu, 3 May 2018 16:40:09 -0700 Subject: [PATCH 0156/1127] Revert "decision maker will decide to reInstall if Version has dashes within the name as well as between the name and the version" This reverts commit 4639fe268e03c7920ec70ab260f6323ec733f959. --- decision_maker.go | 4 ++-- helm_helpers.go | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index fc8a8b6a..a8cfe3e9 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -192,12 +192,12 @@ func inspectUpgradeScenario(namespace string, r *release) { releaseName := r.Name if getReleaseNamespace(releaseName) == namespace { - if extractChartName(r.Chart) == getReleaseChartName(releaseName, r.Version) && r.Version != getReleaseChartVersion(releaseName) { + if extractChartName(r.Chart) == getReleaseChartName(releaseName) && r.Version != getReleaseChartVersion(releaseName) { // upgrade upgradeRelease(r) logDecision("DECISION: release [ "+releaseName+" ] is desired to be upgraded. Planing this for you!", r.Priority) - } else if extractChartName(r.Chart) != getReleaseChartName(releaseName, r.Version) { + } else if extractChartName(r.Chart) != getReleaseChartName(releaseName) { reInstallRelease(namespace, r) logDecision("DECISION: release [ "+releaseName+" ] is desired to use a new Chart [ "+r.Chart+ " ]. I am planning a purge delete of the current release and will install it with the new chart in namespace [[ "+ diff --git a/helm_helpers.go b/helm_helpers.go index 2106feb4..e0731099 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -189,9 +189,10 @@ func getReleaseRevision(releaseName string, state string) string { // getReleaseChartName extracts and returns the Helm chart name from the chart info retrieved by getReleaseChart(). // example: getReleaseChart() returns "jenkins-0.9.0" and this functions will extract "jenkins" from it. -func getReleaseChartName(releaseName string, version string) string { +func getReleaseChartName(releaseName string) string { chart := getReleaseChart(releaseName) - return strings.TrimSuffix(strings.TrimSuffix(chart, version), "-") + runes := []rune(chart) + return string(runes[0:strings.LastIndexByte(chart, '-')]) } // getReleaseChartVersion extracts and returns the Helm chart version from the chart info retrieved by getReleaseChart(). From 12e00647c34251d031b722d103926f8a47a31bcb Mon Sep 17 00:00:00 2001 From: daplho Date: Thu, 3 May 2018 16:50:34 -0700 Subject: [PATCH 0157/1127] decision maker will erroneously decide to reinstall if Version has dashes within the name as well as between the name and the version --- helm_helpers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helm_helpers.go b/helm_helpers.go index e0731099..2b046874 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -192,7 +192,7 @@ func getReleaseRevision(releaseName string, state string) string { func getReleaseChartName(releaseName string) string { chart := getReleaseChart(releaseName) runes := []rune(chart) - return string(runes[0:strings.LastIndexByte(chart, '-')]) + return string(runes[0:strings.LastIndexByte(chart[0:strings.IndexByte(chart, '.')], '-')]) } // getReleaseChartVersion extracts and returns the Helm chart version from the chart info retrieved by getReleaseChart(). From c69d3e9792c7a763b4c16237d82e88cc727103b3 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sun, 6 May 2018 18:19:52 +0200 Subject: [PATCH 0158/1127] bumping version --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index c706f677..47b5ef16 100644 --- a/main.go +++ b/main.go @@ -19,7 +19,7 @@ var verbose bool var nsOverride string var checkCleanup bool var skipValidation bool -var version = "v1.2.0-rc1" +var version = "v1.2.0-rc2" func main() { From c640f23346ee79b143a1567a9c64d8d281929f13 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sun, 6 May 2018 18:21:00 +0200 Subject: [PATCH 0159/1127] trimming spaces in the if statement when getting releases from a specific tiller. --- helm_helpers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helm_helpers.go b/helm_helpers.go index 2b046874..aac606b5 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -47,7 +47,7 @@ func getTillerReleases(tillerNS string) string { // appending tiller-namespace to each release found lines := strings.Split(result, "\n") for i, l := range lines { - if l != "" && !strings.HasPrefix(l, "NAME") && !strings.HasSuffix(l, "NAMESPACE") { + if l != "" && !strings.HasPrefix(strings.TrimSpace(l), "NAME") && !strings.HasSuffix(strings.TrimSpace(l), "NAMESPACE") { lines[i] = strings.TrimSuffix(l, "\n") + " " + tillerNS } } From d17b2fdf22c762cb60f6749a39def05dca30e3bf Mon Sep 17 00:00:00 2001 From: Johan Sigfred Abildskov Date: Mon, 14 May 2018 19:13:00 +0200 Subject: [PATCH 0160/1127] Fix trivial typo --- example.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example.toml b/example.toml index 6864314f..a6dd09f1 100644 --- a/example.toml +++ b/example.toml @@ -60,7 +60,7 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" name = "jenkins" # should be unique across all apps description = "jenkins" namespace = "staging" # maps to the namespace as defined in namespaces above - enabled = true # change to false if you want to delete this app release [empty = flase] + enabled = true # change to false if you want to delete this app release [empty = false] chart = "stable/jenkins" # changing the chart name means delete and recreate this chart version = "0.14.3" # chart version valuesFile = "" # leaving it empty uses the default chart values From 9c7cd32d40618f9c4213d39237650bb448d37c68 Mon Sep 17 00:00:00 2001 From: krogon Date: Tue, 22 May 2018 10:25:32 +0200 Subject: [PATCH 0161/1127] multiple yaml value files --- decision_maker.go | 12 +++-- decision_maker_test.go | 76 +++++++++++++++++++++++++++++ docs/deplyment_strategies.md | 4 +- docs/desired_state_specification.md | 3 +- docs/how_to/multiple_value_files.md | 40 +++++++++++++++ release.go | 12 ++++- release_test.go | 55 +++++++++++++++++++++ test_files/values2.yaml | 0 8 files changed, 193 insertions(+), 9 deletions(-) create mode 100644 decision_maker_test.go create mode 100644 docs/how_to/multiple_value_files.md create mode 100644 test_files/values2.yaml diff --git a/decision_maker.go b/decision_maker.go index a8cfe3e9..0a4fc968 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -105,7 +105,7 @@ func installRelease(namespace string, r *release) { releaseName := r.Name cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm install " + r.Chart + " -n " + releaseName + " --namespace " + namespace + getValuesFile(r) + " --version " + r.Version + getSetValues(r) + getWait(r) + getDesiredTillerNamespace(r) + getTLSFlags(r)}, + Args: []string{"-c", "helm install " + r.Chart + " -n " + releaseName + " --namespace " + namespace + getValuesFiles(r) + " --version " + r.Version + getSetValues(r) + getWait(r) + getDesiredTillerNamespace(r) + getTLSFlags(r)}, Description: "installing release [ " + releaseName + " ] in namespace [[ " + namespace + " ]]", } outcome.addCommand(cmd, r.Priority) @@ -223,7 +223,7 @@ func inspectUpgradeScenario(namespace string, r *release) { func upgradeRelease(r *release) { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm upgrade " + r.Name + " " + r.Chart + getValuesFile(r) + " --version " + r.Version + " --force " + getSetValues(r) + getWait(r) + getDesiredTillerNamespace(r) + getTLSFlags(r)}, + Args: []string{"-c", "helm upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + r.Version + " --force " + getSetValues(r) + getWait(r) + getDesiredTillerNamespace(r) + getTLSFlags(r)}, Description: "upgrading release [ " + r.Name + " ]", } @@ -244,7 +244,7 @@ func reInstallRelease(namespace string, r *release) { installCmd := command{ Cmd: "bash", - Args: []string{"-c", "helm install " + r.Chart + " --version " + r.Version + " -n " + releaseName + " --namespace " + namespace + getValuesFile(r) + getSetValues(r) + getWait(r) + getDesiredTillerNamespace(r) + getTLSFlags(r)}, + Args: []string{"-c", "helm install " + r.Chart + " --version " + r.Version + " -n " + releaseName + " --namespace " + namespace + getValuesFiles(r) + getSetValues(r) + getWait(r) + getDesiredTillerNamespace(r) + getTLSFlags(r)}, Description: "installing release [ " + releaseName + " ] in namespace [[ " + namespace + " ]]", } outcome.addCommand(installCmd, r.Priority) @@ -268,10 +268,12 @@ func extractChartName(releaseChart string) string { } -// getValuesFile return partial install/upgrade release command to substitute the -f flag in Helm. -func getValuesFile(r *release) string { +// getValuesFiles return partial install/upgrade release command to substitute the -f flag in Helm. +func getValuesFiles(r *release) string { if r.ValuesFile != "" { return " -f " + r.ValuesFile + } else if len(r.ValuesFiles) > 0 { + return " -f " + strings.Join(r.ValuesFiles, " -f ") } return "" } diff --git a/decision_maker_test.go b/decision_maker_test.go new file mode 100644 index 00000000..def018b0 --- /dev/null +++ b/decision_maker_test.go @@ -0,0 +1,76 @@ +package main + +import "testing" + +func Test_getValuesFiles(t *testing.T) { + type args struct { + r *release + } + tests := []struct { + name string + args args + want string + }{ + { + name: "test case 1", + args: args{ + r: &release{ + Name: "release1", + Description: "", + Namespace: "namespace", + Enabled: true, + Chart: "repo/chartX", + Version: "1.0", + ValuesFile: "test_files/values.yaml", + Purge: true, + Test: true, + }, + //s: st, + }, + want: " -f test_files/values.yaml", + }, + { + name: "test case 2", + args: args{ + r: &release{ + Name: "release1", + Description: "", + Namespace: "namespace", + Enabled: true, + Chart: "repo/chartX", + Version: "1.0", + ValuesFiles: []string{"test_files/values.yaml"}, + Purge: true, + Test: true, + }, + //s: st, + }, + want: " -f test_files/values.yaml", + }, + { + name: "test case 1", + args: args{ + r: &release{ + Name: "release1", + Description: "", + Namespace: "namespace", + Enabled: true, + Chart: "repo/chartX", + Version: "1.0", + ValuesFiles: []string{"test_files/values.yaml", "test_files/values2.yaml"}, + Purge: true, + Test: true, + }, + //s: st, + }, + want: " -f test_files/values.yaml -f test_files/values2.yaml", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := getValuesFiles(tt.args.r); got != tt.want { + t.Errorf("getValuesFiles() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/docs/deplyment_strategies.md b/docs/deplyment_strategies.md index 855be5ed..531c5bbc 100644 --- a/docs/deplyment_strategies.md +++ b/docs/deplyment_strategies.md @@ -39,7 +39,7 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" enabled = true chart = "stable/jenkins" version = "0.9.1" # chart version - valuesFile = "../my-jenkins-production-values.yaml" + valuesFiles = [ "../my-jenkins-common-values.yaml", "../my-jenkins-production-values.yaml" ] [apps.artifactory] @@ -60,7 +60,7 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" enabled = true chart = "stable/jenkins" version = "0.9.1" # chart version - valuesFile = "../my-jenkins-testing-values.yaml" + valuesFiles = [ "../my-jenkins-common-values.yaml", "../my-jenkins-testing-values.yaml" ] ``` diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index ddf48b1d..14f4008d 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -169,7 +169,8 @@ Options: - enabled : describes the required state of the release (true for enabled, false for disabled). Once a release is deployed, you can change it to false if you want to delete this app release [empty = flase]. - chart : the chart name. It should contain the repo name as well. Example: repoName/chartName. Changing the chart name means delete and reinstall this release using the new Chart. - version : the chart version. -- valuesFile : a valid path to custom Helm values.yaml file. File extension must be `yaml`. Leaving it empty uses the default chart values. +- valuesFile : a valid path to custom Helm values.yaml file. File extension must be `yaml`. Cannot be used with valuesFiles together. Leaving it empty uses the default chart values. +- valuesFiles : array of valid paths to custom Helm values.yaml file. File extension must be `yaml`. Cannot be used with valuesFile together. Leaving it empty uses the default chart values. - purge : defines whether to use the Helm purge flag wgen deleting the release. (true/false) - test : defines whether to run the chart tests whenever the release is installed/upgraded/rolledback. - protected : defines if the release should be protected against changes. Namespace-level protection has higher priority than this flag. Check the [protection guide](how_to/protect_namespaces_and_releases.md) for more details. diff --git a/docs/how_to/multiple_value_files.md b/docs/how_to/multiple_value_files.md new file mode 100644 index 00000000..2303564b --- /dev/null +++ b/docs/how_to/multiple_value_files.md @@ -0,0 +1,40 @@ +--- +version: v1.2.0-rc +--- + +# multiple value files + +You can include multiple yaml value files to separate configuration for different environments. + +```toml +... +[apps] + + [apps.jenkins] + name = "jenkins-prod" # should be unique across all apps + description = "production jenkins" + namespace = "production" + enabled = true + chart = "stable/jenkins" + version = "0.9.1" # chart version + valuesFiles = [ + "../my-jenkins-common-values.yaml", + "../my-jenkins-production-values.yaml" + ] + + # the jenkins release below is being tested in the staging namespace + [apps.jenkins-test] + name = "jenkins-test" # should be unique across all apps + description = "test release of jenkins, testing xyz feature" + namespace = "staging" + enabled = true + chart = "stable/jenkins" + version = "0.9.1" # chart version + valuesFiles = [ + "../my-jenkins-common-values.yaml", + "../my-jenkins-testing-values.yaml" + ] + +... + +``` \ No newline at end of file diff --git a/release.go b/release.go index 5052f6a6..c559b6d3 100644 --- a/release.go +++ b/release.go @@ -15,7 +15,8 @@ type release struct { Enabled bool Chart string Version string - ValuesFile string + ValuesFile string `yaml:"valuesFile"` + ValuesFiles []string `yaml:"valuesFiles"` Purge bool Test bool Protected bool @@ -41,6 +42,14 @@ func validateRelease(r *release, names map[string]bool, s state) (bool, string) return false, "version can't be empty." } else if r.ValuesFile != "" && (!isOfType(r.ValuesFile, ".yaml") || err != nil) { return false, "valuesFile must be a valid file path for a yaml file, Or can be left empty." + } else if r.ValuesFile != "" && len(r.ValuesFiles) > 0 { + return false, "valuesFile and valuesFiles should not be used together." + } else if len(r.ValuesFiles) > 0 { + for _, filePath := range r.ValuesFiles { + if _, pathErr := os.Stat(filePath); !isOfType(filePath, ".yaml") || pathErr != nil { + return false, "the value for valueFile '" + filePath + "' must be a valid file path for a yaml file." + } + } } else if len(r.Set) > 0 { for k, v := range r.Set { if !strings.HasPrefix(v, "$") { @@ -82,6 +91,7 @@ func (r release) print() { fmt.Println("\tchart : ", r.Chart) fmt.Println("\tversion : ", r.Version) fmt.Println("\tvaluesFile : ", r.ValuesFile) + fmt.Println("\tvaluesFiles : ", strings.Join(r.ValuesFiles, ",")) fmt.Println("\tpurge : ", r.Purge) fmt.Println("\ttest : ", r.Test) fmt.Println("\tprotected : ", r.Protected) diff --git a/release_test.go b/release_test.go index ac83b8d7..21e4a6e9 100644 --- a/release_test.go +++ b/release_test.go @@ -205,6 +205,61 @@ func Test_validateRelease(t *testing.T) { }, want: true, want1: "", + }, { + name: "test case 11", + args: args{ + r: &release{ + Name: "release11", + Description: "", + Namespace: "namespace", + Enabled: true, + Chart: "repo/chartX", + Version: "1.0", + ValuesFile: "test_files/values.yaml", + ValuesFiles: []string{"xyz.yaml"}, + Purge: true, + Test: true, + }, + s: st, + }, + want: false, + want1: "valuesFile and valuesFiles should not be used together.", + }, { + name: "test case 12", + args: args{ + r: &release{ + Name: "release12", + Description: "", + Namespace: "namespace", + Enabled: true, + Chart: "repo/chartX", + Version: "1.0", + ValuesFiles: []string{"xyz.yaml"}, + Purge: true, + Test: true, + }, + s: st, + }, + want: false, + want1: "the value for valueFile 'xyz.yaml' must be a valid file path for a yaml file.", + }, { + name: "test case 13", + args: args{ + r: &release{ + Name: "release13", + Description: "", + Namespace: "namespace", + Enabled: true, + Chart: "repo/chartX", + Version: "1.0", + ValuesFiles: []string{"test_files/values.yaml", "test_files/values2.yaml"}, + Purge: true, + Test: true, + }, + s: st, + }, + want: true, + want1: "", }, } names := make(map[string]bool) diff --git a/test_files/values2.yaml b/test_files/values2.yaml new file mode 100644 index 00000000..e69de29b From 48b001870210c13a5e0aacd93461dde125042711 Mon Sep 17 00:00:00 2001 From: krogon Date: Tue, 22 May 2018 16:15:32 +0200 Subject: [PATCH 0162/1127] support for yaml files --- README.md | 3 +- dockerfile/README.md | 2 +- dockerfile/dockerfile | 1 + docs/deplyment_strategies.md | 51 ++++++++++- docs/desired_state_specification.md | 83 ++++++++++++++++-- docs/how_to/define_namespaces.md | 39 +++++++++ docs/how_to/manipulate_apps.md | 21 ++++- docs/how_to/move_charts_across_namespaces.md | 46 ++++++++++ docs/how_to/multiple_value_files.md | 70 ++++++++++++++++ docs/how_to/multitenant_clusters_guide.md | 62 ++++++++++++++ docs/how_to/override_defined_namespaces.md | 42 ++++++++++ .../how_to/pass_secrets_from_env_variables.md | 21 +++++ .../how_to/protect_namespaces_and_releases.md | 16 ++++ .../run_helmsman_with_hosted_cluster.md | 52 ++++++++++++ docs/how_to/run_helmsman_with_minikube.md | 40 +++++++++ docs/how_to/test_charts.md | 21 ++++- docs/how_to/use_local_charts.md | 14 +++- docs/how_to/use_private_helm_charts.md | 12 +++ example.yaml | 84 +++++++++++++++++++ init.go | 4 +- release.go | 2 +- state.go | 30 +++---- test_files/invalid_example.yaml | 19 +++++ utils.go | 62 +++++++++++++- utils_test.go | 53 ++++++++++++ 25 files changed, 817 insertions(+), 33 deletions(-) create mode 100644 docs/how_to/multiple_value_files.md create mode 100644 example.yaml create mode 100644 test_files/invalid_example.yaml diff --git a/README.md b/README.md index 34163594..333e8c1a 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,8 @@ Helmsman is a Helm Charts (k8s applications) as Code tool which allows you to au # How does it work? -Helmsman uses a simple declarative [TOML](https://github.com/toml-lang/toml) file to allow you to describe a desired state for your k8s applications as in the [example file](https://github.com/Praqma/helmsman/blob/master/example.toml). +Helmsman uses a simple declarative [TOML](https://github.com/toml-lang/toml) file to allow you to describe a desired state for your k8s applications as in the [example toml file](https://github.com/Praqma/helmsman/blob/master/example.toml). +Alternatively YAML declaration is also acceptable [example yaml file](https://github.com/Praqma/helmsman/blob/master/example.yaml). The desired state file (DSF) follows the [desired state specification](https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md). diff --git a/dockerfile/README.md b/dockerfile/README.md index ae4fe3ca..f70b42d9 100644 --- a/dockerfile/README.md +++ b/dockerfile/README.md @@ -11,7 +11,7 @@ docker run -v $(pwd):/tmp --rm -it \ -e AWS_DEFAULT_REGION= \ -e AWS_SECRET_ACCESS_KEY= \ praqma/helmsman:v0.1.2 \ -helmsman -debug -apply -f +helmsman -debug -apply -f . ``` Check the different image tags on [Dockerhub](https://hub.docker.com/r/praqma/helmsman/) \ No newline at end of file diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile index dba8a34b..1bb6f2df 100644 --- a/dockerfile/dockerfile +++ b/dockerfile/dockerfile @@ -1,6 +1,7 @@ FROM golang:1.9 as builder WORKDIR /go/src/ RUN go get github.com/BurntSushi/toml +RUN go get gopkg.in/yaml.v2 RUN git clone https://github.com/Praqma/helmsman.git RUN go get github.com/Praqma/helmsman/gcs RUN go get github.com/Praqma/helmsman/aws diff --git a/docs/deplyment_strategies.md b/docs/deplyment_strategies.md index 855be5ed..c2f4ff74 100644 --- a/docs/deplyment_strategies.md +++ b/docs/deplyment_strategies.md @@ -12,7 +12,7 @@ Suppose you are deploying 3rd party charts (e.g. Jenkins, Jira ... etc.) in your You can test 3rd party charts in designated namespaces (e.g, staging) within the same production cluster. This also can be defined in the same desired state file. Below is an example of a desired state file for deploying 3rd party apps in production and staging namespaces: -``` +```toml [metadata] org = "example" @@ -64,6 +64,55 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" ``` +```yaml +metadata: + org: "example" + +# using a minikube cluster +settings: + kubeContext: "minikube" + +namespaces: + staging: + protected: false + production: + protected: true + +helmRepos: + stable: "https://kubernetes-charts.storage.googleapis.com" + incubator: "http://storage.googleapis.com/kubernetes-charts-incubator" + +apps: + jenkins: + name: "jenkins-prod" # should be unique across all apps + description: "production jenkins" + namespace: "production" + enabled: true + chart: "stable/jenkins" + version: "0.9.1" # chart version + valuesFile: "../my-jenkins-production-values.yaml" + + artifactory: + name: "artifactory-prod" # should be unique across all apps + description: "production artifactory" + namespace: "production" + enabled: true + chart: "stable/artifactory" + version: "6.2.0" # chart version + valuesFile: "../my-artificatory-production-values.yaml" + + # the jenkins release below is being tested in the staging namespace + jenkins-test: + name: "jenkins-test" # should be unique across all apps + description: "test release of jenkins, testing xyz feature" + namespace: "staging" + enabled: true + chart: "stable/jenkins" + version: "0.9.1" # chart version + valuesFile: "../my-jenkins-testing-values.yaml" + +``` + You can split the desired state file into multiple files if your deployment pipelines requires that, but it is important to read the notes below on using multiple desired state files with one cluster. ## Working with multiple clusters diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index ddf48b1d..eed11971 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -4,7 +4,7 @@ version: v1.2.0-rc # Helmsman desired state specification -This document describes the specification for how to write your Helm charts desired state file. The desired state file consists of: +This document describes the specification for how to write your Helm charts desired state file. This can be either toml or yaml file. The desired state file consists of: - [Metadata](#metadata) [Optional] -- metadata for any human reader of the desired state file. - [Certificates](#certificates) [Optional] -- only needed when you want Helmsman to connect kubectl to your cluster for you. @@ -24,12 +24,18 @@ Options: Example: -``` +```toml [metadata] scope = "cluster foo" maintainer = "k8s-admin" ``` +```yaml +metadata: + scope: "cluster foo" + maintainer: "k8s-admin" +``` + ## Certificates Optional : Yes, only needed if you want Helmsman to connect kubectl to your cluster for you. @@ -47,7 +53,7 @@ Options: Example: -``` +```toml [certificates] caCrt = "s3://myS3bucket/mydir/ca.crt" caKey = "gs://myGCSbucket/ca.key" @@ -55,6 +61,14 @@ caClient ="../path/to/my/local/client-certificate.crt" #caClient = "$CA_CLIENT" ``` +```yaml +certificates: + caCrt: "s3://myS3bucket/mydir/ca.crt" + caKey: "gs://myGCSbucket/ca.key" + caClient: "../path/to/my/local/client-certificate.crt" + #caClient: "$CA_CLIENT" +``` + ## Settings Optional : No. @@ -76,7 +90,7 @@ The following options can be skipped if your kubectl context is already created Example: -``` +```toml [settings] kubeContext = "minikube" # username = "admin" @@ -87,6 +101,17 @@ kubeContext = "minikube" # storageBackend = "secret" ``` +```yaml +settings: + kubeContext = "minikube" + #username: "admin" + #password: "$K8S_PASSWORD" + #clusterURI: "https://192.168.99.100:8443" + ##clusterURI: "$K8S_URI" + #serviceAccount: "my-service-account" + #storageBackend: "secret" +``` + ## Namespaces Optional : No. @@ -111,7 +136,7 @@ Options: Example: -``` +```toml [namespaces] [namespaces.staging] [namespaces.dev] @@ -127,6 +152,22 @@ clientCert = "gs://mybucket/mydir/helm.cert.pem" clientKey = "s3://mybucket/mydir/helm.key.pem" ``` +```yaml +namespaces: + staging: + dev: + protected: false + production: + protected: true + installTiller: true + tillerServiceAccount: "tiller-production" + caCert: "secrets/ca.cert.pem" + tillerCert: "secrets/tiller.cert.pem" + tillerKey: "$TILLER_KEY" # where TILLER_KEY=secrets/tiller.key.pem + clientCert: "gs://mybucket/mydir/helm.cert.pem" + clientKey: "s3://mybucket/mydir/helm.key.pem" +``` + ## Helm Repos Optional : No. @@ -146,7 +187,7 @@ Options: Example: -``` +```toml [helmRepos] stable = "https://kubernetes-charts.storage.googleapis.com" incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" @@ -154,6 +195,14 @@ myS3repo = "s3://my-S3-private-repo/charts" myGCSrepo = "gs://my-GCS-private-repo/charts" ``` +```yaml +helmRepos: + stable: "https://kubernetes-charts.storage.googleapis.com" + incubator: "http://storage.googleapis.com/kubernetes-charts-incubator" + myS3repo: "s3://my-S3-private-repo/charts" + myGCSrepo: "gs://my-GCS-private-repo/charts" +``` + ## Apps Optional : Yes. @@ -181,7 +230,7 @@ Example: > Whitespace does not matter in TOML files. You could use whatever indentation style you prefer for readability. -``` +```toml [apps] [apps.jenkins] @@ -203,3 +252,23 @@ Example: ``` +```yaml +apps: + jenkins: + name: "jenkins" + description: "jenkins" + namespace: "staging" + enabled: true + chart: "stable/jenkins" + version: "0.9.0" + valuesFile: "" + purge: false + test: true + protected: false + wait: true + priority: -3 + set: + secret1: "$SECRET_ENV_VAR1" + secret2: "$SECRET_ENV_VAR2" + +``` diff --git a/docs/how_to/define_namespaces.md b/docs/how_to/define_namespaces.md index 0a6a3eed..a145c14b 100644 --- a/docs/how_to/define_namespaces.md +++ b/docs/how_to/define_namespaces.md @@ -15,6 +15,15 @@ You can define namespaces to be used in your cluster. If they don't exist, Helms protected = true # default is false ... +``` + +```yaml + +namespaces: + staging: + production: + protected: true # default is false + ``` @@ -35,6 +44,19 @@ As of `v1.2.0-rc`, you can instruct Helmsman to deploy Tiller into specific name clientKey = "s3://mybucket/mydir/helm.key.pem" ``` +```yaml +namespaces: + production: + protected: true + installTiller: true + tillerServiceAccount: "tiller-production" + caCert: "secrets/ca.cert.pem" + tillerCert: "secrets/tiller.cert.pem" + tillerKey: "$TILLER_KEY" # where TILLER_KEY=secrets/tiller.key.pem + clientCert: "gs://mybucket/mydir/helm.cert.pem" + clientKey: "s3://mybucket/mydir/helm.key.pem" +``` + You can then tell Helmsman to deploy specific releases in a specific namespace: ```toml @@ -56,6 +78,23 @@ You can then tell Helmsman to deploy specific releases in a specific namespace: ``` +```yaml +... +apps: + jenkins: + name: "jenkins" + description: "jenkins" + namespace: "production" # pointing to the namespace defined above + enabled: true + chart: "stable/jenkins" + version: "0.9.1" + valuesFile: "" + purge: false + test: true + +... + +``` In the above example, `Jenkins` will be deployed in the production namespace using the Tiller deployed in the production namespace. If the production namespace was not configured to have Tiller deployed there, Jenkins will be deployed using the Tiller in `kube-system`. diff --git a/docs/how_to/manipulate_apps.md b/docs/how_to/manipulate_apps.md index 9bbae3f4..79ab40a2 100644 --- a/docs/how_to/manipulate_apps.md +++ b/docs/how_to/manipulate_apps.md @@ -4,7 +4,7 @@ version: v1.2.0-rc # install releases -You can run helmsman with the [example.toml](https://github.com/Praqma/helmsman/blob/master/example.toml) file. +You can run helmsman with the [example.toml](https://github.com/Praqma/helmsman/blob/master/example.toml) or [example.yaml](https://github.com/Praqma/helmsman/blob/master/example.yaml) file. ``` @@ -61,7 +61,7 @@ If you would like the release to be deleted along with its history, you can use > NOTE: purge deleting a release means you can't roll it back. -``` +```toml ... [apps] @@ -79,6 +79,23 @@ If you would like the release to be deleted along with its history, you can use ... ``` +```yaml +... +apps: + jenkins: + name: "jenkins" + description: "jenkins" + namespace: "staging" + enabled: false # this tells helmsman to delete it + chart: "stable/jenkins" + version: "0.9.1" + valuesFile: "" + purge: true # this means purge delete this release whenever it is required to be deleted + test: flase + +... +``` + # rollback releases Similarly, if you change `enabled` back to `true`, it will figure out that you would like to roll it back. diff --git a/docs/how_to/move_charts_across_namespaces.md b/docs/how_to/move_charts_across_namespaces.md index cc577336..db1c0f16 100644 --- a/docs/how_to/move_charts_across_namespaces.md +++ b/docs/how_to/move_charts_across_namespaces.md @@ -33,6 +33,29 @@ If you have a workflow for testing a release first in the `staging` namespace th ``` +```yaml +... + +namespaces: + staging: + production: + +apps: + jenkins: + name: "jenkins" + description: "jenkins" + namespace: "staging" # this is where it is deployed + enabled: true + chart: "stable/jenkins" + version: "0.9.1" + valuesFile: "" + purge: false + test: true + +... + +``` + Then if you change the namespace key for jenkins: ```toml @@ -59,6 +82,29 @@ Then if you change the namespace key for jenkins: ``` +```yaml +... + +namespaces: + staging: + production: + +apps: + jenkins: + name: "jenkins" + description: "jenkins" + namespace: "production" # we want to move it to production + enabled: true + chart: "stable/jenkins" + version: "0.9.1" + valuesFile: "" + purge: false + test: true + +... + +``` + Helmsman will delete the jenkins release from the `staging` namespace and install it in the `production` namespace (default in the above setup). ## Note on Persistent Volumes diff --git a/docs/how_to/multiple_value_files.md b/docs/how_to/multiple_value_files.md new file mode 100644 index 00000000..b69565b3 --- /dev/null +++ b/docs/how_to/multiple_value_files.md @@ -0,0 +1,70 @@ +--- +version: v1.2.0-rc +--- + +# multiple value files + +You can include multiple yaml value files to separate configuration for different environments. + +```toml +... +[apps] + + [apps.jenkins] + name = "jenkins-prod" # should be unique across all apps + description = "production jenkins" + namespace = "production" + enabled = true + chart = "stable/jenkins" + version = "0.9.1" # chart version + valuesFiles = [ + "../my-jenkins-common-values.yaml", + "../my-jenkins-production-values.yaml" + ] + + # the jenkins release below is being tested in the staging namespace + [apps.jenkins-test] + name = "jenkins-test" # should be unique across all apps + description = "test release of jenkins, testing xyz feature" + namespace = "staging" + enabled = true + chart = "stable/jenkins" + version = "0.9.1" # chart version + valuesFiles = [ + "../my-jenkins-common-values.yaml", + "../my-jenkins-testing-values.yaml" + ] + +... + +``` + +```yaml +... +apps: + + jenkins: + name: "jenkins-prod" # should be unique across all apps + description: "production jenkins" + namespace: "production" + enabled: true + chart: "stable/jenkins" + version: "0.9.1" # chart version + valuesFiles: + - "../my-jenkins-common-values.yaml" + - "../my-jenkins-production-values.yaml" + + # the jenkins release below is being tested in the staging namespace + jenkins-test: + name: "jenkins-test" # should be unique across all apps + description: "test release of jenkins, testing xyz feature" + namespace: "staging" + enabled: true + chart: "stable/jenkins" + version: "0.9.1" # chart version + valuesFiles: + - "../my-jenkins-common-values.yaml" + - "../my-jenkins-testing-values.yaml" +... + +``` \ No newline at end of file diff --git a/docs/how_to/multitenant_clusters_guide.md b/docs/how_to/multitenant_clusters_guide.md index d22c155e..c7363c44 100644 --- a/docs/how_to/multitenant_clusters_guide.md +++ b/docs/how_to/multitenant_clusters_guide.md @@ -28,6 +28,20 @@ In a multitenant cluster, it is a good idea to separate the Helm work of differe ``` +```yaml + +namespaces: + staging: + installTiller: true + production: + installTiller: true + developer1: + installTiller: true + developer2: + installTiller: true + +``` + ## Deploying Tiller with a service account You can also deploy each of the Tillers with a different k8s service account Or with a default service account of your choice. @@ -56,6 +70,30 @@ serviceAccount = "default-tiller-sa" ``` +```yaml + +settings: + # other options + serviceAccount: "default-tiller-sa" + +namespaces: + staging: + installTiller: true + tillerServiceAccount: "custom-sa" + + production: + installTiller: true + + developer1: + installTiller: true + tillerServiceAccount: "dev1-sa" + + developer2: + installTiller: true + tillerServiceAccount: "dev2-sa" + +``` + In the example above, namespaces `staging, developer1 & developer2` will have Tiller deployed with different service accounts. The `production` namespace ,however, will be deployed using the `default-tiller-sa` service account defined in the `settings` section. If this one is not defined, the production namespace Tiller will be deployed with k8s default service account. @@ -88,5 +126,29 @@ In a multitenant setting, it is also recommended to deploy Tiller with TLS enabl ``` +```yaml + +namespaces: + kube-system: + installTiller: false # has no effect. Tiller is always deployed in kube-system + caCert: "secrets/kube-system/ca.cert.pem" + tillerCert: "secrets/kube-system/tiller.cert.pem" + tillerKey: "$TILLER_KEY" # where TILLER_KEY=secrets/kube-system/tiller.key.pem + clientCert: "gs://mybucket/mydir/helm.cert.pem" + clientKey: "s3://mybucket/mydir/helm.key.pem" + + staging: + installTiller: true + + production: + installTiller: true + tillerServiceAccount: "tiller-production" + caCert: "secrets/ca.cert.pem" + tillerCert: "secrets/tiller.cert.pem" + tillerKey: "$TILLER_KEY" # where TILLER_KEY=secrets/tiller.key.pem + clientCert: "gs://mybucket/mydir/helm.cert.pem" + clientKey: "s3://mybucket/mydir/helm.key.pem" + +``` diff --git a/docs/how_to/override_defined_namespaces.md b/docs/how_to/override_defined_namespaces.md index 6aa32050..4c2ce09c 100644 --- a/docs/how_to/override_defined_namespaces.md +++ b/docs/how_to/override_defined_namespaces.md @@ -52,6 +52,48 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" valuesFile = "" # leaving it empty uses the default chart values ``` +dsf.yaml +```yaml +metadata: + org: "example.com" + description: "example Desired State File for demo purposes." + + +settings: + kubeContext: "minikube" + +namespaces: + staging: + protected: false + production: + protected: true + +helmRepos: + stable: "https://kubernetes-charts.storage.googleapis.com" + incubator: "http://storage.googleapis.com/kubernetes-charts-incubator" + + +apps: + + jenkins: + name: "jenkins" # should be unique across all apps + description: "jenkins" + namespace: "production" # maps to the namespace as defined in environments above + enabled: true # change to false if you want to delete this app release [empty: false] + chart: "stable/jenkins" # changing the chart name means delete and recreate this chart + version: "0.14.3" # chart version + valuesFile: "" # leaving it empty uses the default chart values + + artifactory: + name: "artifactory" # should be unique across all apps + description: "artifactory" + namespace: "staging" # maps to the namespace as defined in environments above + enabled: true # change to false if you want to delete this app release [empty: false] + chart: "stable/artifactory" # changing the chart name means delete and recreate this chart + version: "7.0.6" # chart version + valuesFile: "" # leaving it empty uses the default chart values +``` + In command line, we run : ``` diff --git a/docs/how_to/pass_secrets_from_env_variables.md b/docs/how_to/pass_secrets_from_env_variables.md index 3821395d..89c77226 100644 --- a/docs/how_to/pass_secrets_from_env_variables.md +++ b/docs/how_to/pass_secrets_from_env_variables.md @@ -27,4 +27,25 @@ Starting from v0.1.3, Helmsman allows you to pass secrets and other user input t ``` +```yaml +... +apps: + + jira: + name: "jira" + description: "jira" + namespace: "staging" + enabled: true + chart: "myrepo/jira" + version: "0.1.5" + valuesFile: "applications/jira-values.yaml" + purge: false + test: true + set: + db_username: "$JIRA_DB_USERNAME" # pass any number of key/value pairs where the key is the input expected by the helm charts and the value is an env variable name starting with $ + db_password: "$JIRA_DB_PASSWORD" +... + +``` + These input variables will be passed to the chart when it is deployed/upgraded using helm's `--set <>=<>` \ No newline at end of file diff --git a/docs/how_to/protect_namespaces_and_releases.md b/docs/how_to/protect_namespaces_and_releases.md index 8560433b..e94522a9 100644 --- a/docs/how_to/protect_namespaces_and_releases.md +++ b/docs/how_to/protect_namespaces_and_releases.md @@ -54,6 +54,22 @@ Protection is supported in two forms: protected = true # defining this release to be protected. ``` +```yaml +apps: + + jenkins: + name: "jenkins" + description: "jenkins" + namespace: "staging" + enabled: true + chart: "stable/jenkins" + version: "0.9.1" + valuesFile: "" + purge: false + test: false + protected: true # defining this release to be protected. +``` + > All releases in a protected namespace are automatically protected. Namespace protection has higher priority than the relase-level protection. ## Important Notes diff --git a/docs/how_to/run_helmsman_with_hosted_cluster.md b/docs/how_to/run_helmsman_with_hosted_cluster.md index 7adf8629..7673f1fd 100644 --- a/docs/how_to/run_helmsman_with_hosted_cluster.md +++ b/docs/how_to/run_helmsman_with_hosted_cluster.md @@ -80,6 +80,58 @@ myGCSrepo = "gs://my-GCS-repo/charts" test = false ``` +```yaml +metadata: + org: "orgX" + maintainer: "k8s-admin" + +# Certificates are used to connect to the cluster. Currently, they can only be retrieved from s3 buckets. +certificates: + caCrt: "s3://your-bucket/ca.crt" # s3 bucket + caKey: "$K8S_CLIENT_KEY" # relative file path + caClient: "gs://your-GCS-bucket/caClient.crt" # GCS bucket + +settings: + kubeContext: "mycontext" + username: "<>" + password: "$K8S_PASSWORD" # the name of an environment variable containing the k8s password + clusterURI: "$K8S_URI" # cluster API + +namespaces: + staging: + +helmRepos: + stable: "https://kubernetes-charts.storage.googleapis.com" + incubator: "http://storage.googleapis.com/kubernetes-charts-incubator" + myrepo: "s3://my-private-repo/charts" + myGCSrepo: "gs://my-GCS-repo/charts" + +apps: + + jenkins: + name: "jenkins" + description: "jenkins" + namespace: "staging" + enabled: true + chart: "stable/jenkins" + version: "0.9.1" + valuesFile: "" + purge: false + test: false + + + artifactory: + name: "artifactory" + description: "artifactory" + namespace: "staging" + enabled: true + chart: "stable/artifactory" + version: "6.2.0" + valuesFile: "" + purge: false + test: false +``` + The above example requires the following environment variables to be set: - AWS_ACCESS_KEY_ID (since S3 is used for helm repo and certificates) diff --git a/docs/how_to/run_helmsman_with_minikube.md b/docs/how_to/run_helmsman_with_minikube.md index 0e9417fb..64ee3256 100644 --- a/docs/how_to/run_helmsman_with_minikube.md +++ b/docs/how_to/run_helmsman_with_minikube.md @@ -43,4 +43,44 @@ stable = "https://kubernetes-charts.storage.googleapis.com" valuesFile = "" purge = false test = false +``` + +```yaml +metadata: + org: "orgX" + maintainer: "k8s-admin" + +settings: + kubeContext: "minikube" + +namespaces: + staging: + +helmRepos: + stable: "https://kubernetes-charts.storage.googleapis.com" + +apps: + + jenkins: + name: "jenkins" + description: "jenkins" + namespace: "staging" + enabled: true + chart: "stable/jenkins" + version: "0.9.1" + valuesFile: "" + purge: false + test: false + + + artifactory: + name: "artifactory" + description: "artifactory" + namespace: "staging" + enabled: true + chart: "stable/artifactory" + version: "6.2.0" + valuesFile: "" + purge: false + test: false ``` \ No newline at end of file diff --git a/docs/how_to/test_charts.md b/docs/how_to/test_charts.md index ddf9b4fe..ccd2dca0 100644 --- a/docs/how_to/test_charts.md +++ b/docs/how_to/test_charts.md @@ -23,4 +23,23 @@ You can specifiy that you would like a chart to be tested whenever it is install ... -``` \ No newline at end of file +``` + +```yaml +... +apps: + + jenkins: + name: "jenkins" + description: "jenkins" + namespace: "staging" + enabled: true + chart: "stable/jenkins" + version: "0.9.1" + valuesFile: "" + purge: false + test: true # setting this to true, means you want the charts tests to be run on this release when it is installed. + +... + +``` \ No newline at end of file diff --git a/docs/how_to/use_local_charts.md b/docs/how_to/use_local_charts.md index d248e989..2d7a6d95 100644 --- a/docs/how_to/use_local_charts.md +++ b/docs/how_to/use_local_charts.md @@ -16,4 +16,16 @@ local = http://127.0.0.1:8879 ... -``` \ No newline at end of file +``` + +```yaml +... + +helmRepos: + stable: "https://kubernetes-charts.storage.googleapis.com" + incubator: "http://storage.googleapis.com/kubernetes-charts-incubator" + local: http://127.0.0.1:8879 + +... + +``` \ No newline at end of file diff --git a/docs/how_to/use_private_helm_charts.md b/docs/how_to/use_private_helm_charts.md index a6fe07ff..ed418227 100644 --- a/docs/how_to/use_private_helm_charts.md +++ b/docs/how_to/use_private_helm_charts.md @@ -22,6 +22,18 @@ myPrivateRepo = s3://this-is-a-private-repo/charts ``` +```yaml +... + +helmRepos: + stable: "https://kubernetes-charts.storage.googleapis.com" + incubator: "http://storage.googleapis.com/kubernetes-charts-incubator" + myPrivateRepo: s3://this-is-a-private-repo/charts + +... + +``` + ## S3 If you are using S3 private repos, you need to provide the following AWS env variables: diff --git a/example.yaml b/example.yaml new file mode 100644 index 00000000..e0d1e79d --- /dev/null +++ b/example.yaml @@ -0,0 +1,84 @@ +# version: v1.2.0-rc +# metadata -- add as many key/value pairs as you want +metadata: + org: "example.com" + maintainer: "k8s-admin (me@example.com)" + description: "example Desired State File for demo purposes." + +# paths to the certificate for connecting to the cluster +# You can skip this if you use Helmsman on a machine with kubectl already connected to your k8s cluster. +# you have to use exact key names here : 'caCrt' for certificate and 'caKey' for the key and caClient for the client certificate +certificates: + #caClient: "gs://mybucket/client.crt" # GCS bucket path + #caCrt: "s3://mybucket/ca.crt" # S3 bucket path + #caKey: "../ca.key" # valid local file relative path + +settings: + kubeContext: "minikube" # will try connect to this context first, if it does not exist, it will be created using the details below + #username: "admin" + #password: "$K8S_PASSWORD" # the name of an environment variable containing the k8s password + #clusterURI: "$K8S_URI" # the name of an environment variable containing the cluster API + #clusterURI: "https://192.168.99.100:8443" # equivalent to the above + #serviceAccount: "foo" # k8s serviceaccount must be already defined, validation error will be thrown otherwise + storageBackend: "secret" # default is configMap + +# define your environments and their k8s namespaces +namespaces: + production: + protected: true + staging: + protected: false + installTiller: true + #tillerServiceAccount: "tiller-staging" # should already exist in the staging namespace + #caCert: "secrets/ca.cert.pem" # or an env var, e.g. "$CA_CERT_PATH" + #tillerCert: "secrets/tiller.cert.pem" # or S3 bucket s3://mybucket/tiller.crt + #tillerKey: "secrets/tiller.key.pem" # or GCS bucket gs://mybucket/tiller.key + #clientCert: "secrets/helm.cert.pem" + #clientKey: "secrets/helm.key.pem" + + +# define any private/public helm charts repos you would like to get charts from +# syntax: repo_name: "repo_url" +# only private repos hosted in s3 buckets are now supported +helmRepos: + stable: "https://kubernetes-charts.storage.googleapis.com" + incubator: "http://storage.googleapis.com/kubernetes-charts-incubator" + #myS3repo: "s3://my-S3-private-repo/charts" + #myGCSrepo: "gs://my-GCS-private-repo/charts" + +# define the desired state of your applications helm charts +# each contains the following: + + +apps: + + # jenkins will be deployed using the Tiller in the staging namespace + jenkins: + name: "jenkins" # should be unique across all apps + description: "jenkins" + namespace: "staging" # maps to the namespace as defined in namespaces above + enabled: true # change to false if you want to delete this app release empty: flase: + chart: "stable/jenkins" # changing the chart name means delete and recreate this chart + version: "0.14.3" # chart version + valuesFile: "" # leaving it empty uses the default chart values + purge: false # will only be considered when there is a delete operation + test: false # run the tests when this release is installed for the first time only + protected: true + priority: -3 + wait: true + #set: # values to override values from values.yaml with values from env vars-- useful for passing secrets to charts + #AdminPassword: "$JENKINS_PASSWORD" # $JENKINS_PASSWORD must exist in the environment + + + # artifactory will be deployed using the Tiller in the kube-system namespace + artifactory: + name: "artifactory" # should be unique across all apps + description: "artifactory" + namespace: "production" # maps to the namespace as defined in namespaces above + enabled: true # change to false if you want to delete this app release empty: flase: + chart: "stable/artifactory" # changing the chart name means delete and recreate this chart + version: "7.0.6" # chart version + valuesFile: "" # leaving it empty uses the default chart values + purge: false # will only be considered when there is a delete operation + test: false # run the tests when this release is installed for the first time only + priority: -2 diff --git a/init.go b/init.go index e28f54eb..574dc36d 100644 --- a/init.go +++ b/init.go @@ -54,8 +54,8 @@ func init() { log.Fatal("ERROR: kubectl is not installed/configured correctly. Aborting!") } - // read the TOML desired state file - result, msg := fromTOML(file, &s) + // read the TOML/YAML desired state file + result, msg := fromFile(file, &s) if result { log.Printf(msg) } else { diff --git a/release.go b/release.go index 5052f6a6..5e1a4e71 100644 --- a/release.go +++ b/release.go @@ -15,7 +15,7 @@ type release struct { Enabled bool Chart string Version string - ValuesFile string + ValuesFile string `yaml:"valuesFile"` Purge bool Test bool Protected bool diff --git a/state.go b/state.go index 75a079ef..bc6e943e 100644 --- a/state.go +++ b/state.go @@ -10,24 +10,24 @@ import ( // namespace type represents the fields of a namespace type namespace struct { - Protected bool - InstallTiller bool - TillerServiceAccount string - CaCert string - TillerCert string - TillerKey string - ClientCert string - ClientKey string + Protected bool `yaml:"protected"` + InstallTiller bool `yaml:"installTiller"` + TillerServiceAccount string `yaml:"tillerServiceAccount"` + CaCert string `yaml:"caCert"` + TillerCert string `yaml:"tillerCert"` + TillerKey string `yaml:"tillerKey"` + ClientCert string `yaml:"clientCert"` + ClientKey string `yaml:"clientKey"` } // state type represents the desired state of applications on a k8s cluster. type state struct { - Metadata map[string]string - Certificates map[string]string - Settings map[string]string - Namespaces map[string]namespace - HelmRepos map[string]string - Apps map[string]*release + Metadata map[string]string `yaml:"metadata"` + Certificates map[string]string `yaml:"certificates"` + Settings map[string]string `yaml:"settings"` + Namespaces map[string]namespace `yaml:"namespaces"` + HelmRepos map[string]string `yaml:"helmRepos"` + Apps map[string]*release `yaml:"apps"` } // validate validates that the values specified in the desired state are valid according to the desired state spec. @@ -36,7 +36,7 @@ func (s state) validate() (bool, string) { // settings if s.Settings == nil || len(s.Settings) == 0 { - return false, "ERROR: settings validation failed -- no settings table provided in TOML." + return false, "ERROR: settings validation failed -- no settings table provided in state file." } else if value, ok := s.Settings["kubeContext"]; !ok || value == "" { return false, "ERROR: settings validation failed -- you have not provided a " + "kubeContext to use. Can't work without it. Sorry!" diff --git a/test_files/invalid_example.yaml b/test_files/invalid_example.yaml new file mode 100644 index 00000000..b0d964de --- /dev/null +++ b/test_files/invalid_example.yaml @@ -0,0 +1,19 @@ +# THIS IS AN INVALID YAML FILE USED FOR TESTING PURPOSES ONLY. +metadata: + org: orgX + maintainer: "k8s-admin" + +certificates: + +settings: + kubeContext: + +namespaces: + staging: "staging" + production: "default" + +helmRepos: + stable: "https://kubernetes-charts.storage.googleapis.com" + incubator: "http://storage.googleapis.com/kubernetes-charts-incubator" + +apps: diff --git a/utils.go b/utils.go index 1ad40fb9..725f9e2e 100644 --- a/utils.go +++ b/utils.go @@ -11,6 +11,8 @@ import ( "strconv" "strings" + "gopkg.in/yaml.v2" + "github.com/BurntSushi/toml" "github.com/Praqma/helmsman/aws" "github.com/Praqma/helmsman/gcs" @@ -37,7 +39,7 @@ func fromTOML(file string, s *state) (bool, string) { if _, err := toml.DecodeFile(file, s); err != nil { return false, err.Error() } - return true, "INFO: Parsed [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps." + return true, "INFO: Parsed TOML [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps." } @@ -67,6 +69,64 @@ func toTOML(file string, s *state) { newFile.Close() } +// fromYAML reads a yaml file and decodes it to a state type. +// parser which throws an error if the YAML file is not valid. +func fromYAML(file string, s *state) (bool, string) { + yamlFile, err := ioutil.ReadFile(file) + if err != nil { + return false, err.Error() + } + if err = yaml.Unmarshal(yamlFile, s); err != nil { + return false, err.Error() + } + return true, "INFO: Parsed YAML [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps." +} + +func toYAML(file string, s *state) { + log.Println("printing generated yaml ... ") + var buff bytes.Buffer + var ( + newFile *os.File + err error + ) + + if err := yaml.NewEncoder(&buff).Encode(s); err != nil { + log.Fatal(err) + os.Exit(1) + } + newFile, err = os.Create(file) + if err != nil { + log.Fatal(err) + } + bytesWritten, err := newFile.Write(buff.Bytes()) + if err != nil { + log.Fatal(err) + } + log.Printf("Wrote %d bytes.\n", bytesWritten) + newFile.Close() +} + +// invokes either yaml or toml parser considering file extension +func fromFile(file string, s *state) (bool, string) { + if isOfType(file, ".toml") { + return fromTOML(file, s) + } else if isOfType(file, ".yaml") { + return fromYAML(file, s) + } else { + return false, "State file does not have toml/yaml extension." + } +} + +func toFile(file string, s *state) { + if isOfType(file, ".toml") { + toTOML(file, s) + } else if isOfType(file, ".yaml") { + fromYAML(file, s) + } else { + log.Fatal("State file does not have toml/yaml extension.") + } +} + // isOfType checks if the file extension of a filename/path is the same as "filetype". // isisOfType is case insensitive. filetype should contain the "." e.g. ".yaml" func isOfType(filename string, filetype string) bool { diff --git a/utils_test.go b/utils_test.go index ccebaab7..390df6b2 100644 --- a/utils_test.go +++ b/utils_test.go @@ -72,6 +72,59 @@ func Test_fromTOML(t *testing.T) { // } // } +func Test_fromYAML(t *testing.T) { + type args struct { + file string + s *state + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "test case 1 -- invalid YAML", + args: args{ + file: "test_files/invalid_example.yaml", + s: new(state), + }, + want: false, + }, { + name: "test case 2 -- valid TOML", + args: args{ + file: "example.yaml", + s: new(state), + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got, _ := fromYAML(tt.args.file, tt.args.s); got != tt.want { + t.Errorf("fromYaml() = %v, want %v", got, tt.want) + } + }) + } +} + +// func Test_toYAML(t *testing.T) { +// type args struct { +// file string +// s *state +// } +// tests := []struct { +// name string +// args args +// }{ +// // TODO: Add test cases. +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// toYAML(tt.args.file, tt.args.s) +// }) +// } +// } + func Test_isOfType(t *testing.T) { type args struct { filename string From e5e15bbeb1859b3cf043e1fc36ba4eee8ecd9b1a Mon Sep 17 00:00:00 2001 From: Joshua Knarr Date: Fri, 25 May 2018 15:56:49 -0400 Subject: [PATCH 0163/1127] trying to enable environmental parsing across the board in helmsman --- state.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/state.go b/state.go index 75a079ef..a9eb92b8 100644 --- a/state.go +++ b/state.go @@ -159,8 +159,8 @@ func (s state) validate() (bool, string) { // if the env variable is empty or unset, an empty string is returned // if the string does not start with '$', it is returned as is. func subsituteEnv(name string) string { - if strings.HasPrefix(name, "$") { - return os.Getenv(strings.SplitAfterN(name, "$", 2)[1]) + if strings.Contains(name, "$") { + return os.ExpandEnv(name) } return name } From 79aaf5d3d4e2265c381cb50e626538ccdc667a02 Mon Sep 17 00:00:00 2001 From: Joshua Knarr Date: Sat, 26 May 2018 08:12:37 -0400 Subject: [PATCH 0164/1127] why not expand them all the time? --- state.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/state.go b/state.go index a9eb92b8..c486791b 100644 --- a/state.go +++ b/state.go @@ -155,14 +155,9 @@ func (s state) validate() (bool, string) { return true, "" } -// substitueEnv checks if a string is an env variable (starts with '$'), then it returns its value -// if the env variable is empty or unset, an empty string is returned -// if the string does not start with '$', it is returned as is. +// expand the environment variables if present. Useful for pipelines and gitlab runners. func subsituteEnv(name string) string { - if strings.Contains(name, "$") { - return os.ExpandEnv(name) - } - return name + return os.ExpandEnv(name) } // isValidCert checks if a certificate/key path/URI is valid From d400ed52f3997cabdce38abf49f63fb37f2a3140 Mon Sep 17 00:00:00 2001 From: Joshua Knarr Date: Sun, 27 May 2018 19:03:13 -0400 Subject: [PATCH 0165/1127] fixes for naemspaces --- state.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/state.go b/state.go index c486791b..d4ee6210 100644 --- a/state.go +++ b/state.go @@ -97,7 +97,7 @@ func (s state) validate() (bool, string) { for k, v := range s.Namespaces { if !v.InstallTiller && k != "kube-system" { - log.Println("INFO: naemspace validation -- Tiller is not desired to be deployed in namespace [ " + k + " ].") + log.Println("INFO: namespace validation -- Tiller is not desired to be deployed in namespace [ " + k + " ].") } else { if tillerTLSEnabled(k) { // validating the TLS certs and keys for Tiller From 8a13d3f2c61800582de10db15f07eab701cfc695 Mon Sep 17 00:00:00 2001 From: Joshua Knarr Date: Tue, 29 May 2018 10:47:58 -0400 Subject: [PATCH 0166/1127] Thanks to Dave for the fixups --- .utils.go.swp | Bin 0 -> 16384 bytes decision_maker.go | 428 +++++++++++++++++++++++----------------------- release.go | 127 +++++++------- state.go | 346 ++++++++++++++++++------------------- utils.go | 292 +++++++++++++++---------------- 5 files changed, 586 insertions(+), 607 deletions(-) create mode 100644 .utils.go.swp diff --git a/.utils.go.swp b/.utils.go.swp new file mode 100644 index 0000000000000000000000000000000000000000..764c0c5349a48e115d957e3b1c188e915e93490a GIT binary patch literal 16384 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 C{JvNK literal 0 HcmV?d00001 diff --git a/decision_maker.go b/decision_maker.go index a8cfe3e9..60765d62 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -1,7 +1,8 @@ package main import ( - "strings" + "os" + "strings" ) var outcome plan @@ -9,112 +10,112 @@ var releases string // makePlan creates a plan of the actions needed to make the desired state come true. func makePlan(s *state) *plan { - outcome = createPlan() - buildState() + outcome = createPlan() + buildState() - for _, r := range s.Apps { - decide(r, s) - } + for _, r := range s.Apps { + decide(r, s) + } - return &outcome + return &outcome } // decide makes a decision about what commands (actions) need to be executed // to make a release section of the desired state come true. func decide(r *release, s *state) { - // check for deletion - if !r.Enabled { - if !isProtected(r) { - inspectDeleteScenario(getDesiredNamespace(r), r) - } else { - logDecision("DECISION: release "+r.Name+" is PROTECTED. Operations are not allowed on this release until "+ - "you remove its protection.", r.Priority) - } + // check for deletion + if !r.Enabled { + if !isProtected(r) { + inspectDeleteScenario(getDesiredNamespace(r), r) + } else { + logDecision("DECISION: release "+r.Name+" is PROTECTED. Operations are not allowed on this release until "+ + "you remove its protection.", r.Priority) + } - } else { // check for install/upgrade/rollback - if helmReleaseExists(getDesiredNamespace(r), r.Name, "deployed") { - if !isProtected(r) { - inspectUpgradeScenario(getDesiredNamespace(r), r) // upgrade - } else { - logDecision("DECISION: release "+r.Name+" is PROTECTED. Operations are not allowed on this release until "+ - "you remove its protection.", r.Priority) - } + } else { // check for install/upgrade/rollback + if helmReleaseExists(getDesiredNamespace(r), r.Name, "deployed") { + if !isProtected(r) { + inspectUpgradeScenario(getDesiredNamespace(r), r) // upgrade + } else { + logDecision("DECISION: release "+r.Name+" is PROTECTED. Operations are not allowed on this release until "+ + "you remove its protection.", r.Priority) + } - } else if helmReleaseExists("", r.Name, "deleted") { - if !isProtected(r) { + } else if helmReleaseExists("", r.Name, "deleted") { + if !isProtected(r) { - rollbackRelease(getDesiredNamespace(r), r) // rollback + rollbackRelease(getDesiredNamespace(r), r) // rollback - } else { - logDecision("DECISION: release "+r.Name+" is PROTECTED. Operations are not allowed on this release until "+ - "you remove its protection.", r.Priority) - } + } else { + logDecision("DECISION: release "+r.Name+" is PROTECTED. Operations are not allowed on this release until "+ + "you remove its protection.", r.Priority) + } - } else if helmReleaseExists("", r.Name, "failed") { + } else if helmReleaseExists("", r.Name, "failed") { - if !isProtected(r) { + if !isProtected(r) { - reInstallRelease(getDesiredNamespace(r), r) // re-install failed release + reInstallRelease(getDesiredNamespace(r), r) // re-install failed release - } else { - logDecision("DECISION: release "+r.Name+" is PROTECTED. Operations are not allowed on this release until "+ - "you remove its protection.", r.Priority) - } + } else { + logDecision("DECISION: release "+r.Name+" is PROTECTED. Operations are not allowed on this release until "+ + "you remove its protection.", r.Priority) + } - } else if helmReleaseExists("", r.Name, "") { // not deployed in the desired namespace but deployed elsewhere + } else if helmReleaseExists("", r.Name, "") { // not deployed in the desired namespace but deployed elsewhere - if !isProtected(r) { + if !isProtected(r) { - reInstallRelease(getDesiredNamespace(r), r) // move the release to a new (the desired) namespace - logDecision("WARNING: moving release [ "+r.Name+" ] from [[ "+getReleaseNamespace(r.Name)+" ]] to [[ "+getDesiredNamespace(r)+ - " ]] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md"+ - " for details if this release uses PV and PVC.", r.Priority) + reInstallRelease(getDesiredNamespace(r), r) // move the release to a new (the desired) namespace + logDecision("WARNING: moving release [ "+r.Name+" ] from [[ "+getReleaseNamespace(r.Name)+" ]] to [[ "+getDesiredNamespace(r)+ + " ]] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md"+ + " for details if this release uses PV and PVC.", r.Priority) - } else { - logDecision("DECISION: release "+r.Name+" is PROTECTED. Operations are not allowed on this release until "+ - "you remove its protection.", r.Priority) - } + } else { + logDecision("DECISION: release "+r.Name+" is PROTECTED. Operations are not allowed on this release until "+ + "you remove its protection.", r.Priority) + } - } else { + } else { - installRelease(getDesiredNamespace(r), r) // install a new release + installRelease(getDesiredNamespace(r), r) // install a new release - } + } - } + } } // testRelease creates a Helm command to test a particular release. func testRelease(r *release) { - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm test " + r.Name + getDesiredTillerNamespace(r) + getTLSFlags(r)}, - Description: "running tests for release [ " + r.Name + " ]", - } - outcome.addCommand(cmd, r.Priority) - logDecision("DECISION: release [ "+r.Name+" ] is required to be tested when installed. Got it!", r.Priority) + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm test " + r.Name + getDesiredTillerNamespace(r) + getTLSFlags(r)}, + Description: "running tests for release [ " + r.Name + " ]", + } + outcome.addCommand(cmd, r.Priority) + logDecision("DECISION: release [ "+r.Name+" ] is required to be tested when installed. Got it!", r.Priority) } // installRelease creates a Helm command to install a particular release in a particular namespace. func installRelease(namespace string, r *release) { - releaseName := r.Name - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm install " + r.Chart + " -n " + releaseName + " --namespace " + namespace + getValuesFile(r) + " --version " + r.Version + getSetValues(r) + getWait(r) + getDesiredTillerNamespace(r) + getTLSFlags(r)}, - Description: "installing release [ " + releaseName + " ] in namespace [[ " + namespace + " ]]", - } - outcome.addCommand(cmd, r.Priority) - logDecision("DECISION: release [ "+releaseName+" ] is not present in the current k8s context. Will install it in namespace [[ "+ - namespace+" ]]", r.Priority) - - if r.Test { - testRelease(r) - } + releaseName := r.Name + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm install " + r.Chart + " -n " + releaseName + " --namespace " + namespace + getValuesFile(r) + " --version " + r.Version + getSetValues(r) + getWait(r) + getDesiredTillerNamespace(r) + getTLSFlags(r)}, + Description: "installing release [ " + releaseName + " ] in namespace [[ " + namespace + " ]]", + } + outcome.addCommand(cmd, r.Priority) + logDecision("DECISION: release [ "+releaseName+" ] is not present in the current k8s context. Will install it in namespace [[ "+ + namespace+" ]]", r.Priority) + + if r.Test { + testRelease(r) + } } // rollbackRelease evaluates if a rollback action needs to be taken for a given release. @@ -122,29 +123,29 @@ func installRelease(namespace string, r *release) { // it purge deletes it and create it in the spcified namespace. func rollbackRelease(namespace string, r *release) { - releaseName := r.Name - if getReleaseNamespace(r.Name) == namespace { - - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm rollback " + releaseName + " " + getReleaseRevision(releaseName, "deleted") + getWait(r) + getDesiredTillerNamespace(r) + getTLSFlags(r)}, - Description: "rolling back release [ " + releaseName + " ]", - } - outcome.addCommand(cmd, r.Priority) - upgradeRelease(r) - logDecision("DECISION: release [ "+releaseName+" ] is currently deleted and is desired to be rolledback to "+ - "namespace [[ "+namespace+" ]] . It will also be upgraded in case values have changed.", r.Priority) - - } else { - - reInstallRelease(namespace, r) - logDecision("DECISION: release [ "+releaseName+" ] is deleted BUT from namespace [[ "+getReleaseNamespace(releaseName)+ - " ]]. Will purge delete it from there and install it in namespace [[ "+namespace+" ]]", r.Priority) - logDecision("WARNING: rolling back release [ "+releaseName+" ] from [[ "+getReleaseNamespace(releaseName)+" ]] to [[ "+namespace+ - " ]] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md"+ - " for details if this release uses PV and PVC.", r.Priority) - - } + releaseName := r.Name + if getReleaseNamespace(r.Name) == namespace { + + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm rollback " + releaseName + " " + getReleaseRevision(releaseName, "deleted") + getWait(r) + getDesiredTillerNamespace(r) + getTLSFlags(r)}, + Description: "rolling back release [ " + releaseName + " ]", + } + outcome.addCommand(cmd, r.Priority) + upgradeRelease(r) + logDecision("DECISION: release [ "+releaseName+" ] is currently deleted and is desired to be rolledback to "+ + "namespace [[ "+namespace+" ]] . It will also be upgraded in case values have changed.", r.Priority) + + } else { + + reInstallRelease(namespace, r) + logDecision("DECISION: release [ "+releaseName+" ] is deleted BUT from namespace [[ "+getReleaseNamespace(releaseName)+ + " ]]. Will purge delete it from there and install it in namespace [[ "+namespace+" ]]", r.Priority) + logDecision("WARNING: rolling back release [ "+releaseName+" ] from [[ "+getReleaseNamespace(releaseName)+" ]] to [[ "+namespace+ + " ]] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md"+ + " for details if this release uses PV and PVC.", r.Priority) + + } } // inspectDeleteScenario evaluates if a delete action needs to be taken for a given release. @@ -152,33 +153,33 @@ func rollbackRelease(namespace string, r *release) { // If the release is not deployed, it will be skipped. func inspectDeleteScenario(namespace string, r *release) { - releaseName := r.Name - //if it exists in helm list , add command to delete it, else log that it is skipped - if helmReleaseExists(namespace, releaseName, "deployed") { - // delete it - deleteRelease(r) + releaseName := r.Name + //if it exists in helm list , add command to delete it, else log that it is skipped + if helmReleaseExists(namespace, releaseName, "deployed") { + // delete it + deleteRelease(r) - } else { - logDecision("DECISION: release [ "+releaseName+" ] is set to be disabled but is not yet deployed. Skipping.", r.Priority) - } + } else { + logDecision("DECISION: release [ "+releaseName+" ] is set to be disabled but is not yet deployed. Skipping.", r.Priority) + } } // deleteRelease deletes a release from a k8s cluster func deleteRelease(r *release) { - p := "" - purgeDesc := "" - if r.Purge { - p = "--purge" - purgeDesc = "and purged!" - } - - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm delete " + p + " " + r.Name + getCurrentTillerNamespace(r) + getTLSFlags(r)}, - Description: "deleting release [ " + r.Name + " ]", - } - outcome.addCommand(cmd, r.Priority) - logDecision("DECISION: release [ "+r.Name+" ] is desired to be deleted "+purgeDesc+". Planing this for you!", r.Priority) + p := "" + purgeDesc := "" + if r.Purge { + p = "--purge" + purgeDesc = "and purged!" + } + + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm delete " + p + " " + r.Name + getCurrentTillerNamespace(r) + getTLSFlags(r)}, + Description: "deleting release [ " + r.Name + " ]", + } + outcome.addCommand(cmd, r.Priority) + logDecision("DECISION: release [ "+r.Name+" ] is desired to be deleted "+purgeDesc+". Planing this for you!", r.Priority) } // inspectUpgradeScenario evaluates if a release should be upgraded. @@ -190,65 +191,65 @@ func deleteRelease(r *release) { // it will be purge deleted and installed in the new namespace. func inspectUpgradeScenario(namespace string, r *release) { - releaseName := r.Name - if getReleaseNamespace(releaseName) == namespace { - if extractChartName(r.Chart) == getReleaseChartName(releaseName) && r.Version != getReleaseChartVersion(releaseName) { - // upgrade - upgradeRelease(r) - logDecision("DECISION: release [ "+releaseName+" ] is desired to be upgraded. Planing this for you!", r.Priority) - - } else if extractChartName(r.Chart) != getReleaseChartName(releaseName) { - reInstallRelease(namespace, r) - logDecision("DECISION: release [ "+releaseName+" ] is desired to use a new Chart [ "+r.Chart+ - " ]. I am planning a purge delete of the current release and will install it with the new chart in namespace [[ "+ - namespace+" ]]", r.Priority) - - } else { - upgradeRelease(r) - logDecision("DECISION: release [ "+releaseName+" ] is desired to be enabled and is currently enabled."+ - "I will upgrade it in case you changed your values.yaml!", r.Priority) - } - } else { - reInstallRelease(namespace, r) - logDecision("DECISION: release [ "+releaseName+" ] is desired to be enabled in a new namespace [[ "+namespace+ - " ]]. I am planning a purge delete of the current release from namespace [[ "+getReleaseNamespace(releaseName)+" ]] "+ - "and will install it for you in namespace [[ "+namespace+" ]]", r.Priority) - logDecision("WARNING: moving release [ "+releaseName+" ] from [[ "+getReleaseNamespace(releaseName)+" ]] to [[ "+namespace+ - " ]] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md"+ - " for details if this release uses PV and PVC.", r.Priority) - } + releaseName := r.Name + if getReleaseNamespace(releaseName) == namespace { + if extractChartName(r.Chart) == getReleaseChartName(releaseName) && r.Version != getReleaseChartVersion(releaseName) { + // upgrade + upgradeRelease(r) + logDecision("DECISION: release [ "+releaseName+" ] is desired to be upgraded. Planing this for you!", r.Priority) + + } else if extractChartName(r.Chart) != getReleaseChartName(releaseName) { + reInstallRelease(namespace, r) + logDecision("DECISION: release [ "+releaseName+" ] is desired to use a new Chart [ "+r.Chart+ + " ]. I am planning a purge delete of the current release and will install it with the new chart in namespace [[ "+ + namespace+" ]]", r.Priority) + + } else { + upgradeRelease(r) + logDecision("DECISION: release [ "+releaseName+" ] is desired to be enabled and is currently enabled."+ + "I will upgrade it in case you changed your values.yaml!", r.Priority) + } + } else { + reInstallRelease(namespace, r) + logDecision("DECISION: release [ "+releaseName+" ] is desired to be enabled in a new namespace [[ "+namespace+ + " ]]. I am planning a purge delete of the current release from namespace [[ "+getReleaseNamespace(releaseName)+" ]] "+ + "and will install it for you in namespace [[ "+namespace+" ]]", r.Priority) + logDecision("WARNING: moving release [ "+releaseName+" ] from [[ "+getReleaseNamespace(releaseName)+" ]] to [[ "+namespace+ + " ]] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md"+ + " for details if this release uses PV and PVC.", r.Priority) + } } // upgradeRelease upgrades an existing release with the specified values.yaml func upgradeRelease(r *release) { - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm upgrade " + r.Name + " " + r.Chart + getValuesFile(r) + " --version " + r.Version + " --force " + getSetValues(r) + getWait(r) + getDesiredTillerNamespace(r) + getTLSFlags(r)}, - Description: "upgrading release [ " + r.Name + " ]", - } + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm upgrade " + r.Name + " " + r.Chart + getValuesFile(r) + " --version " + r.Version + " --force " + getSetValues(r) + getWait(r) + getDesiredTillerNamespace(r) + getTLSFlags(r)}, + Description: "upgrading release [ " + r.Name + " ]", + } - outcome.addCommand(cmd, r.Priority) + outcome.addCommand(cmd, r.Priority) } // reInstallRelease purge deletes a release and reinstalls it. // This is used when moving a release to another namespace or when changing the chart used for it. func reInstallRelease(namespace string, r *release) { - releaseName := r.Name - delCmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm delete --purge " + releaseName + getCurrentTillerNamespace(r) + getTLSFlags(r)}, - Description: "deleting release [ " + releaseName + " ]", - } - outcome.addCommand(delCmd, r.Priority) - - installCmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm install " + r.Chart + " --version " + r.Version + " -n " + releaseName + " --namespace " + namespace + getValuesFile(r) + getSetValues(r) + getWait(r) + getDesiredTillerNamespace(r) + getTLSFlags(r)}, - Description: "installing release [ " + releaseName + " ] in namespace [[ " + namespace + " ]]", - } - outcome.addCommand(installCmd, r.Priority) - logDecision("DECISION: release [ "+releaseName+" ] will be deleted from namespace [[ "+getReleaseNamespace(releaseName)+" ]] and reinstalled in [[ "+namespace+"]].", r.Priority) + releaseName := r.Name + delCmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm delete --purge " + releaseName + getCurrentTillerNamespace(r) + getTLSFlags(r)}, + Description: "deleting release [ " + releaseName + " ]", + } + outcome.addCommand(delCmd, r.Priority) + + installCmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm install " + r.Chart + " --version " + r.Version + " -n " + releaseName + " --namespace " + namespace + getValuesFile(r) + getSetValues(r) + getWait(r) + getDesiredTillerNamespace(r) + getTLSFlags(r)}, + Description: "installing release [ " + releaseName + " ] in namespace [[ " + namespace + " ]]", + } + outcome.addCommand(installCmd, r.Priority) + logDecision("DECISION: release [ "+releaseName+" ] will be deleted from namespace [[ "+getReleaseNamespace(releaseName)+" ]] and reinstalled in [[ "+namespace+"]].", r.Priority) } @@ -256,7 +257,7 @@ func reInstallRelease(namespace string, r *release) { // Depending on the debug flag being set or not, it will either log the the decision to output or not. func logDecision(decision string, priority int) { - outcome.addDecision(decision, priority) + outcome.addDecision(decision, priority) } @@ -264,49 +265,49 @@ func logDecision(decision string, priority int) { // example: it extracts "chartY" from "repoX/chartY" func extractChartName(releaseChart string) string { - return strings.TrimSpace(strings.Split(releaseChart, "/")[1]) + return strings.TrimSpace(strings.Split(releaseChart, "/")[1]) } // getValuesFile return partial install/upgrade release command to substitute the -f flag in Helm. func getValuesFile(r *release) string { - if r.ValuesFile != "" { - return " -f " + r.ValuesFile - } - return "" + if r.ValuesFile != "" { + return " -f " + r.ValuesFile + } + return "" } // getSetValues returns --set params to be used with helm install/upgrade commands func getSetValues(r *release) string { - result := "" - for k, v := range r.Set { - _, value := envVarExists(v) - result = result + " --set " + k + "=\"" + strings.Replace(value, ",", "\\,", -1) + "\"" - } - return result + result := "" + for k, v := range r.Set { + value := os.ExpandEnv(v) + result = result + " --set " + k + "=\"" + strings.Replace(value, ",", "\\,", -1) + "\"" + } + return result } // getWait returns a partial helm command containing the helm wait flag (--wait) if the wait flag for the release was set to true // Otherwise, retruns an empty string func getWait(r *release) string { - result := "" - if r.Wait { - result = " --wait" - } - return result + result := "" + if r.Wait { + result = " --wait" + } + return result } // getDesiredNamespace returns the namespace of a release func getDesiredNamespace(r *release) string { - return r.Namespace + return r.Namespace } // getCurrentNamespaceProtection returns the protection state for the namespace where a release is currently installed. // It returns true if a namespace is defined as protected in the desired state file, false otherwise. func getCurrentNamespaceProtection(r *release) bool { - return s.Namespaces[getReleaseNamespace(r.Name)].Protected + return s.Namespaces[getReleaseNamespace(r.Name)].Protected } // isProtected checks if a release is protected or not. @@ -315,57 +316,58 @@ func getCurrentNamespaceProtection(r *release) bool { // returns true if a release is protected, false otherwise func isProtected(r *release) bool { - // if the release does not exist in the cluster, it is not protected - if !helmReleaseExists("", r.Name, "") { - return false - } + // if the release does not exist in the cluster, it is not protected + if !helmReleaseExists("", r.Name, "") { + return false + } - if getCurrentNamespaceProtection(r) { - return true - } + if getCurrentNamespaceProtection(r) { + return true + } - if r.Protected { - return true - } + if r.Protected { + return true + } - return false + return false } // getDesiredTillerNamespace returns a tiller-namespace flag with which a release is desired to be maintained func getDesiredTillerNamespace(r *release) string { - if s.Namespaces[r.Namespace].InstallTiller { - return " --tiller-namespace " + r.Namespace - } + if s.Namespaces[r.Namespace].InstallTiller { + return " --tiller-namespace " + r.Namespace + } - return "" // same as return " --tiller-namespace kube-system" + return "" // same as return " --tiller-namespace kube-system" } // getCurrentTillerNamespace returns the tiller-namespace with which a release is currently maintained func getCurrentTillerNamespace(r *release) string { - if v, ok := currentState[r.Name]; ok { - return " --tiller-namespace " + v.TillerNamespace - } - return "" + if v, ok := currentState[r.Name]; ok { + return " --tiller-namespace " + v.TillerNamespace + } + return "" } // getTLSFlags returns TLS flags with which a release is maintained // If the release where the namespace is to be deployed has Tiller deployed, the TLS flages will use certs/keys for that namespace (if any) // otherwise, it will be the certs/keys for the kube-system namespace. func getTLSFlags(r *release) string { - tls := "" - if s.Namespaces[r.Namespace].InstallTiller { - if tillerTLSEnabled(r.Namespace) { + tls := "" + if s.Namespaces[r.Namespace].InstallTiller { + if tillerTLSEnabled(r.Namespace) { - tls = " --tls --tls-ca-cert " + r.Namespace + "-ca.cert --tls-cert " + r.Namespace + "-client.cert --tls-key " + r.Namespace + "-client.key " - } - } else { - if tillerTLSEnabled("kube-system") { + tls = " --tls --tls-ca-cert " + r.Namespace + "-ca.cert --tls-cert " + r.Namespace + "-client.cert --tls-key " + r.Namespace + "-client.key " + } + } else { + if tillerTLSEnabled("kube-system") { - tls = " --tls --tls-ca-cert kube-system-ca.cert --tls-cert kube-system-client.cert --tls-key kube-system-client.key " - } - } + tls = " --tls --tls-ca-cert kube-system-ca.cert --tls-cert kube-system-client.cert --tls-key kube-system-client.key " + } + } - return tls + return tls } + diff --git a/release.go b/release.go index 5052f6a6..757518bb 100644 --- a/release.go +++ b/release.go @@ -1,93 +1,86 @@ package main import ( - "fmt" - "log" - "os" - "strings" + "fmt" + "log" + "os" + "strings" ) // release type representing Helm releases which are described in the desired state type release struct { - Name string - Description string - Namespace string - Enabled bool - Chart string - Version string - ValuesFile string - Purge bool - Test bool - Protected bool - Wait bool - Priority int - Set map[string]string + Name string + Description string + Namespace string + Enabled bool + Chart string + Version string + ValuesFile string + Purge bool + Test bool + Protected bool + Wait bool + Priority int + Set map[string]string } // validateRelease validates if a release inside a desired state meets the specifications or not. // check the full specification @ https://github.com/Praqma/helmsman/docs/desired_state_spec.md func validateRelease(r *release, names map[string]bool, s state) (bool, string) { - _, err := os.Stat(r.ValuesFile) - if r.Name == "" || names[r.Name] { - return false, "release name can't be empty and must be unique." - } else if nsOverride == "" && r.Namespace == "" { - return false, "release targeted namespace can't be empty." - } else if nsOverride == "" && r.Namespace != "" && !checkNamespaceDefined(r.Namespace, s) { - return false, "release " + r.Name + " is using namespace [ " + r.Namespace + " ] which is not defined in the Namespaces section of your desired state file." + - " Release [ " + r.Name + " ] can't be installed in that Namespace until its defined." - } else if r.Chart == "" || !strings.ContainsAny(r.Chart, "/") { - return false, "chart can't be empty and must be of the format: repo/chart." - } else if r.Version == "" { - return false, "version can't be empty." - } else if r.ValuesFile != "" && (!isOfType(r.ValuesFile, ".yaml") || err != nil) { - return false, "valuesFile must be a valid file path for a yaml file, Or can be left empty." - } else if len(r.Set) > 0 { - for k, v := range r.Set { - if !strings.HasPrefix(v, "$") { - return false, "the value for variable [ " + k + " ] must be an environment variable name and start with '$'." - } else if ok, _ := envVarExists(v); !ok { - return false, "env variable [ " + v + " ] is not found in the environment." - } - } - } else if r.Priority != 0 && r.Priority > 0 { - return false, "priority can only be 0 or negative value, positive values are not allowed." - } + _, err := os.Stat(r.ValuesFile) + if r.Name == "" || names[r.Name] { + return false, "release name can't be empty and must be unique." + } else if nsOverride == "" && r.Namespace == "" { + return false, "release targeted namespace can't be empty." + } else if nsOverride == "" && r.Namespace != "" && !checkNamespaceDefined(r.Namespace, s) { + return false, "release " + r.Name + " is using namespace [ " + r.Namespace + " ] which is not defined in the Namespaces section of your desired state file." + + " Release [ " + r.Name + " ] can't be installed in that Namespace until its defined." + } else if r.Chart == "" || !strings.ContainsAny(r.Chart, "/") { + return false, "chart can't be empty and must be of the format: repo/chart." + } else if r.Version == "" { + return false, "version can't be empty." + } else if r.ValuesFile != "" && (!isOfType(r.ValuesFile, ".yaml") || err != nil) { + return false, "valuesFile must be a valid file path for a yaml file, Or can be left empty." + } else if r.Priority != 0 && r.Priority > 0 { + return false, "priority can only be 0 or negative value, positive values are not allowed." + } - names[r.Name] = true - return true, "" + names[r.Name] = true + return true, "" } // checkNamespaceDefined checks if a given namespace is defined in the namespaces section of the desired state file func checkNamespaceDefined(ns string, s state) bool { - _, ok := s.Namespaces[ns] - if !ok { - return false - } - return true + _, ok := s.Namespaces[ns] + if !ok { + return false + } + return true } // overrideNamespace overrides a release defined namespace with a new given one func overrideNamespace(r *release, newNs string) { - log.Println("INFO: overriding namespace for app: " + r.Name) - r.Namespace = newNs + log.Println("INFO: overriding namespace for app: " + r.Name) + r.Namespace = newNs } // print prints the details of the release func (r release) print() { - fmt.Println("") - fmt.Println("\tname : ", r.Name) - fmt.Println("\tdescription : ", r.Description) - fmt.Println("\tnamespace : ", r.Namespace) - fmt.Println("\tenabled : ", r.Enabled) - fmt.Println("\tchart : ", r.Chart) - fmt.Println("\tversion : ", r.Version) - fmt.Println("\tvaluesFile : ", r.ValuesFile) - fmt.Println("\tpurge : ", r.Purge) - fmt.Println("\ttest : ", r.Test) - fmt.Println("\tprotected : ", r.Protected) - fmt.Println("\twait : ", r.Wait) - fmt.Println("\tpriority : ", r.Priority) - fmt.Println("\tvalues to override from env:") - printMap(r.Set) - fmt.Println("------------------- ") + fmt.Println("") + fmt.Println("\tname : ", r.Name) + fmt.Println("\tdescription : ", r.Description) + fmt.Println("\tnamespace : ", r.Namespace) + fmt.Println("\tenabled : ", r.Enabled) + fmt.Println("\tchart : ", r.Chart) + fmt.Println("\tversion : ", r.Version) + fmt.Println("\tvaluesFile : ", r.ValuesFile) + fmt.Println("\tpurge : ", r.Purge) + fmt.Println("\ttest : ", r.Test) + fmt.Println("\tprotected : ", r.Protected) + fmt.Println("\twait : ", r.Wait) + fmt.Println("\tpriority : ", r.Priority) + fmt.Println("\tvalues to override from env:") + printMap(r.Set) + fmt.Println("------------------- ") } + diff --git a/state.go b/state.go index d4ee6210..404281aa 100644 --- a/state.go +++ b/state.go @@ -1,208 +1,204 @@ package main import ( - "fmt" - "log" - "net/url" - "os" - "strings" + "fmt" + "log" + "net/url" + "os" + "strings" ) // namespace type represents the fields of a namespace type namespace struct { - Protected bool - InstallTiller bool - TillerServiceAccount string - CaCert string - TillerCert string - TillerKey string - ClientCert string - ClientKey string + Protected bool + InstallTiller bool + TillerServiceAccount string + CaCert string + TillerCert string + TillerKey string + ClientCert string + ClientKey string } // state type represents the desired state of applications on a k8s cluster. type state struct { - Metadata map[string]string - Certificates map[string]string - Settings map[string]string - Namespaces map[string]namespace - HelmRepos map[string]string - Apps map[string]*release + Metadata map[string]string + Certificates map[string]string + Settings map[string]string + Namespaces map[string]namespace + HelmRepos map[string]string + Apps map[string]*release } // validate validates that the values specified in the desired state are valid according to the desired state spec. // check https://github.com/Praqma/Helmsman/docs/desired_state_spec.md for the detailed specification func (s state) validate() (bool, string) { - // settings - if s.Settings == nil || len(s.Settings) == 0 { - return false, "ERROR: settings validation failed -- no settings table provided in TOML." - } else if value, ok := s.Settings["kubeContext"]; !ok || value == "" { - return false, "ERROR: settings validation failed -- you have not provided a " + - "kubeContext to use. Can't work without it. Sorry!" - } else if value, ok = s.Settings["clusterURI"]; ok { - - s.Settings["clusterURI"] = subsituteEnv(value) - if _, err := url.ParseRequestURI(s.Settings["clusterURI"]); err != nil { - return false, "ERROR: settings validation failed -- clusterURI must have a valid URL set in an env varibale or passed directly. Either the env var is missing/empty or the URL is invalid." - } - - if _, ok = s.Settings["username"]; !ok { - return false, "ERROR: settings validation failed -- username must be provided if clusterURI is defined." - } - if value, ok = s.Settings["password"]; ok { - s.Settings["password"] = subsituteEnv(value) - } else { - return false, "ERROR: settings validation failed -- password must be provided if clusterURI is defined." - } - - if s.Settings["password"] == "" { - return false, "ERROR: settings validation failed -- password should be set as an env variable. It is currently missing or empty. " - } - } - - // certificates - if s.Certificates != nil && len(s.Certificates) != 0 { - _, ok1 := s.Settings["clusterURI"] - _, ok2 := s.Certificates["caCrt"] - _, ok3 := s.Certificates["caKey"] - if ok1 && (!ok2 || !ok3) { - return false, "ERROR: certifications validation failed -- You want me to connect to your cluster for you " + - "but have not given me the cert/key to do so. Please add [caCrt] and [caKey] under Certifications. You might also need to provide [clientCrt]." - } else if ok1 { - for key, value := range s.Certificates { - r, path := isValidCert(value) - if !r { - return false, "ERROR: certifications validation failed -- [ " + key + " ] must be a valid S3 or GCS bucket URL or a valid relative file path." - } - s.Certificates[key] = path - } - } else { - log.Println("INFO: certificates provided but not needed. Skipping certificates validation.") - } - - } else { - if _, ok := s.Settings["clusterURI"]; ok { - return false, "ERROR: certifications validation failed -- You want me to connect to your cluster for you " + - "but have not given me the cert/key to do so. Please add [caCrt] and [caKey] under Certifications. You might also need to provide [clientCrt]." - } - } - - // namespaces - if nsOverride == "" { - if s.Namespaces == nil || len(s.Namespaces) == 0 { - return false, "ERROR: namespaces validation failed -- I need at least one namespace " + - "to work with!" - } - - for k, v := range s.Namespaces { - if !v.InstallTiller && k != "kube-system" { - log.Println("INFO: namespace validation -- Tiller is not desired to be deployed in namespace [ " + k + " ].") - } else { - if tillerTLSEnabled(k) { - // validating the TLS certs and keys for Tiller - // if they are valid, their values (if they are env vars) are substituted - var ok1, ok2, ok3, ok4, ok5 bool - ok1, v.CaCert = isValidCert(v.CaCert) - ok2, v.ClientCert = isValidCert(v.ClientCert) - ok3, v.ClientKey = isValidCert(v.ClientKey) - ok4, v.TillerCert = isValidCert(v.TillerCert) - ok5, v.TillerKey = isValidCert(v.TillerKey) - if !ok1 || !ok2 || !ok3 || !ok4 || !ok5 { - return false, "ERROR: namespaces validation failed -- some certs/keys are not valid for Tiller TLS in namespace [ " + k + " ]." - } - log.Println("INFO: namespace validation -- Tiller is desired to be deployed with TLS in namespace [ " + k + " ]. ") - } else { - log.Println("INFO: namespace validation -- Tiller is desired to be deployed WITHOUT TLS in namespace [ " + k + " ]. ") - } - } - } - } else { - log.Println("INFO: ns-override is used to override all namespaces with [ " + nsOverride + " ] Skipping defined namespaces validation.") - } - - // repos - if s.HelmRepos == nil || len(s.HelmRepos) == 0 { - return false, "ERROR: repos validation failed -- I need at least one helm repo " + - "to work with!" - } - for k, v := range s.HelmRepos { - _, err := url.ParseRequestURI(v) - if err != nil { - return false, "ERROR: repos validation failed -- repo [" + k + " ] " + - "must have a valid URL." - } - - continue - - } - - // apps - if s.Apps == nil { - log.Println("INFO: You have not specified any apps. I have nothing to do. ", - "Horraayyy!.") - os.Exit(0) - } - - names := make(map[string]bool) - for appLabel, r := range s.Apps { - result, errMsg := validateRelease(r, names, s) - if !result { - return false, "ERROR: apps validation failed -- for app [" + appLabel + " ]. " + errMsg - } - } - - return true, "" -} - -// expand the environment variables if present. Useful for pipelines and gitlab runners. -func subsituteEnv(name string) string { - return os.ExpandEnv(name) + // settings + if s.Settings == nil || len(s.Settings) == 0 { + return false, "ERROR: settings validation failed -- no settings table provided in TOML." + } else if value, ok := s.Settings["kubeContext"]; !ok || value == "" { + return false, "ERROR: settings validation failed -- you have not provided a " + + "kubeContext to use. Can't work without it. Sorry!" + } else if value, ok = s.Settings["clusterURI"]; ok { + + s.Settings["clusterURI"] = os.ExpandEnv(value) + if _, err := url.ParseRequestURI(s.Settings["clusterURI"]); err != nil { + return false, "ERROR: settings validation failed -- clusterURI must have a valid URL set in an env varibale or passed directly. Either the env var is missing/empty or the URL is invalid." + } + + if _, ok = s.Settings["username"]; !ok { + return false, "ERROR: settings validation failed -- username must be provided if clusterURI is defined." + } + if value, ok = s.Settings["password"]; ok { + s.Settings["password"] = os.ExpandEnv(value) + } else { + return false, "ERROR: settings validation failed -- password must be provided if clusterURI is defined." + } + + if s.Settings["password"] == "" { + return false, "ERROR: settings validation failed -- password should be set as an env variable. It is currently missing or empty. " + } + } + + // certificates + if s.Certificates != nil && len(s.Certificates) != 0 { + _, ok1 := s.Settings["clusterURI"] + _, ok2 := s.Certificates["caCrt"] + _, ok3 := s.Certificates["caKey"] + if ok1 && (!ok2 || !ok3) { + return false, "ERROR: certifications validation failed -- You want me to connect to your cluster for you " + + "but have not given me the cert/key to do so. Please add [caCrt] and [caKey] under Certifications. You might also need to provide [clientCrt]." + } else if ok1 { + for key, value := range s.Certificates { + r, path := isValidCert(value) + if !r { + return false, "ERROR: certifications validation failed -- [ " + key + " ] must be a valid S3 or GCS bucket URL or a valid relative file path." + } + s.Certificates[key] = path + } + } else { + log.Println("INFO: certificates provided but not needed. Skipping certificates validation.") + } + + } else { + if _, ok := s.Settings["clusterURI"]; ok { + return false, "ERROR: certifications validation failed -- You want me to connect to your cluster for you " + + "but have not given me the cert/key to do so. Please add [caCrt] and [caKey] under Certifications. You might also need to provide [clientCrt]." + } + } + + // namespaces + if nsOverride == "" { + if s.Namespaces == nil || len(s.Namespaces) == 0 { + return false, "ERROR: namespaces validation failed -- I need at least one namespace " + + "to work with!" + } + + for k, v := range s.Namespaces { + if !v.InstallTiller && k != "kube-system" { + log.Println("INFO: naemspace validation -- Tiller is not desired to be deployed in namespace [ " + k + " ].") + } else { + if tillerTLSEnabled(k) { + // validating the TLS certs and keys for Tiller + // if they are valid, their values (if they are env vars) are substituted + var ok1, ok2, ok3, ok4, ok5 bool + ok1, v.CaCert = isValidCert(v.CaCert) + ok2, v.ClientCert = isValidCert(v.ClientCert) + ok3, v.ClientKey = isValidCert(v.ClientKey) + ok4, v.TillerCert = isValidCert(v.TillerCert) + ok5, v.TillerKey = isValidCert(v.TillerKey) + if !ok1 || !ok2 || !ok3 || !ok4 || !ok5 { + return false, "ERROR: namespaces validation failed -- some certs/keys are not valid for Tiller TLS in namespace [ " + k + " ]." + } + log.Println("INFO: namespace validation -- Tiller is desired to be deployed with TLS in namespace [ " + k + " ]. ") + } else { + log.Println("INFO: namespace validation -- Tiller is desired to be deployed WITHOUT TLS in namespace [ " + k + " ]. ") + } + } + } + } else { + log.Println("INFO: ns-override is used to override all namespaces with [ " + nsOverride + " ] Skipping defined namespaces validation.") + } + + // repos + if s.HelmRepos == nil || len(s.HelmRepos) == 0 { + return false, "ERROR: repos validation failed -- I need at least one helm repo " + + "to work with!" + } + for k, v := range s.HelmRepos { + _, err := url.ParseRequestURI(v) + if err != nil { + return false, "ERROR: repos validation failed -- repo [" + k + " ] " + + "must have a valid URL." + } + + continue + + } + + // apps + if s.Apps == nil { + log.Println("INFO: You have not specified any apps. I have nothing to do. ", + "Horraayyy!.") + os.Exit(0) + } + + names := make(map[string]bool) + for appLabel, r := range s.Apps { + result, errMsg := validateRelease(r, names, s) + if !result { + return false, "ERROR: apps validation failed -- for app [" + appLabel + " ]. " + errMsg + } + } + + return true, "" } // isValidCert checks if a certificate/key path/URI is valid func isValidCert(value string) (bool, string) { - tmp := subsituteEnv(value) - _, err1 := url.ParseRequestURI(tmp) - _, err2 := os.Stat(tmp) - if err2 != nil && (err1 != nil || (!strings.HasPrefix(tmp, "s3://") && !strings.HasPrefix(tmp, "gs://"))) { - return false, "" - } - return true, tmp + tmp := os.ExpandEnv(value) + _, err1 := url.ParseRequestURI(tmp) + _, err2 := os.Stat(tmp) + if err2 != nil && (err1 != nil || (!strings.HasPrefix(tmp, "s3://") && !strings.HasPrefix(tmp, "gs://"))) { + return false, "" + } + return true, tmp } // tillerTLSEnabled checks if Tiller is desired to be deployed with TLS enabled for a given namespace // TLS is considered desired ONLY if all certs and keys for both Tiller and the Helm client are defined. func tillerTLSEnabled(namespace string) bool { - ns := s.Namespaces[namespace] - if ns.CaCert != "" && ns.TillerCert != "" && ns.TillerKey != "" && ns.ClientCert != "" && ns.ClientKey != "" { - return true - } - return false + ns := s.Namespaces[namespace] + if ns.CaCert != "" && ns.TillerCert != "" && ns.TillerKey != "" && ns.ClientCert != "" && ns.ClientKey != "" { + return true + } + return false } // print prints the desired state func (s state) print() { - fmt.Println("\nMetadata: ") - fmt.Println("--------- ") - printMap(s.Metadata) - fmt.Println("\nCertificates: ") - fmt.Println("--------- ") - printMap(s.Certificates) - fmt.Println("\nSettings: ") - fmt.Println("--------- ") - printMap(s.Settings) - fmt.Println("\nNamespaces: ") - fmt.Println("------------- ") - printNamespacesMap(s.Namespaces) - fmt.Println("\nRepositories: ") - fmt.Println("------------- ") - printMap(s.HelmRepos) - fmt.Println("\nApplications: ") - fmt.Println("--------------- ") - for _, r := range s.Apps { - r.print() - } + fmt.Println("\nMetadata: ") + fmt.Println("--------- ") + printMap(s.Metadata) + fmt.Println("\nCertificates: ") + fmt.Println("--------- ") + printMap(s.Certificates) + fmt.Println("\nSettings: ") + fmt.Println("--------- ") + printMap(s.Settings) + fmt.Println("\nNamespaces: ") + fmt.Println("------------- ") + printNamespacesMap(s.Namespaces) + fmt.Println("\nRepositories: ") + fmt.Println("------------- ") + printMap(s.HelmRepos) + fmt.Println("\nApplications: ") + fmt.Println("--------------- ") + for _, r := range s.Apps { + r.print() + } } + diff --git a/utils.go b/utils.go index 1ad40fb9..f1037a7a 100644 --- a/utils.go +++ b/utils.go @@ -1,220 +1,208 @@ package main import ( - "bytes" - "fmt" - "io" - "io/ioutil" - "log" - "os" - "path/filepath" - "strconv" - "strings" - - "github.com/BurntSushi/toml" - "github.com/Praqma/helmsman/aws" - "github.com/Praqma/helmsman/gcs" + "bytes" + "fmt" + "io" + "io/ioutil" + "log" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/BurntSushi/toml" + "github.com/Praqma/helmsman/aws" + "github.com/Praqma/helmsman/gcs" ) // printMap prints to the console any map of string keys and values. func printMap(m map[string]string) { - for key, value := range m { - fmt.Println(key, " : ", value) - } + for key, value := range m { + fmt.Println(key, " : ", value) + } } // printObjectMap prints to the console any map of string keys and object values. func printNamespacesMap(m map[string]namespace) { - for key, value := range m { - fmt.Println(key, " : protected = ", value) - } + for key, value := range m { + fmt.Println(key, " : protected = ", value) + } } // fromTOML reads a toml file and decodes it to a state type. // It uses the BurntSuchi TOML parser which throws an error if the TOML file is not valid. func fromTOML(file string, s *state) (bool, string) { - if _, err := toml.DecodeFile(file, s); err != nil { - return false, err.Error() - } - return true, "INFO: Parsed [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps." + if _, err := toml.DecodeFile(file, s); err != nil { + return false, err.Error() + } + return true, "INFO: Parsed [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps." } // toTOML encodes a state type into a TOML file. // It uses the BurntSuchi TOML parser. func toTOML(file string, s *state) { - log.Println("printing generated toml ... ") - var buff bytes.Buffer - var ( - newFile *os.File - err error - ) - - if err := toml.NewEncoder(&buff).Encode(s); err != nil { - log.Fatal(err) - os.Exit(1) - } - newFile, err = os.Create(file) - if err != nil { - log.Fatal(err) - } - bytesWritten, err := newFile.Write(buff.Bytes()) - if err != nil { - log.Fatal(err) - } - log.Printf("Wrote %d bytes.\n", bytesWritten) - newFile.Close() + log.Println("printing generated toml ... ") + var buff bytes.Buffer + var ( + newFile *os.File + err error + ) + + if err := toml.NewEncoder(&buff).Encode(s); err != nil { + log.Fatal(err) + os.Exit(1) + } + newFile, err = os.Create(file) + if err != nil { + log.Fatal(err) + } + bytesWritten, err := newFile.Write(buff.Bytes()) + if err != nil { + log.Fatal(err) + } + log.Printf("Wrote %d bytes.\n", bytesWritten) + newFile.Close() } // isOfType checks if the file extension of a filename/path is the same as "filetype". // isisOfType is case insensitive. filetype should contain the "." e.g. ".yaml" func isOfType(filename string, filetype string) bool { - return filepath.Ext(strings.ToLower(filename)) == strings.ToLower(filetype) + return filepath.Ext(strings.ToLower(filename)) == strings.ToLower(filetype) } // readFile returns the content of a file as a string. // takes a file path as input. It throws an error and breaks the program execution if it failes to read the file. func readFile(filepath string) string { - data, err := ioutil.ReadFile(filepath) - if err != nil { - log.Fatal("ERROR: failed to read [ " + filepath + " ] file content: " + err.Error()) - } - return string(data) + data, err := ioutil.ReadFile(filepath) + if err != nil { + log.Fatal("ERROR: failed to read [ " + filepath + " ] file content: " + err.Error()) + } + return string(data) } // printHelp prints Helmsman commands func printHelp() { - fmt.Println("Helmsman version: " + version) - fmt.Println("Helmsman is a Helm Charts as Code tool which allows you to automate the deployment/management of your Helm charts.") - fmt.Println("Usage: helmsman [options]") - fmt.Println() - fmt.Println("Options:") - fmt.Println("--f specifies the desired state TOML file.") - fmt.Println("--debug prints basic logs during execution.") - fmt.Println("--apply generates and applies an action plan.") - fmt.Println("--verbose prints more verbose logs during execution.") - fmt.Println("--ns-override override defined namespaces with a provided one.") - fmt.Println("--skip-validation generates and applies an action plan.") - fmt.Println("--help prints Helmsman help.") - fmt.Println("--v prints Helmsman version.") + fmt.Println("Helmsman version: " + version) + fmt.Println("Helmsman is a Helm Charts as Code tool which allows you to automate the deployment/management of your Helm charts.") + fmt.Println("Usage: helmsman [options]") + fmt.Println() + fmt.Println("Options:") + fmt.Println("--f specifies the desired state TOML file.") + fmt.Println("--debug prints basic logs during execution.") + fmt.Println("--apply generates and applies an action plan.") + fmt.Println("--verbose prints more verbose logs during execution.") + fmt.Println("--ns-override override defined namespaces with a provided one.") + fmt.Println("--skip-validation generates and applies an action plan.") + fmt.Println("--help prints Helmsman help.") + fmt.Println("--v prints Helmsman version.") } // logVersions prints the versions of kubectl and helm to the logs func logVersions() { - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "kubectl version"}, - Description: "Kubectl version: ", - } - - exitCode, result := cmd.exec(debug, false) - if exitCode != 0 { - log.Fatal("ERROR: while checking kubectl version: " + result) - } - - log.Println("VERBOSE: kubectl version: \n " + result + "\n") - - cmd = command{ - Cmd: "bash", - Args: []string{"-c", "helm version"}, - Description: "Helm version: ", - } - - exitCode, result = cmd.exec(debug, false) - if exitCode != 0 { - log.Fatal("ERROR: while checking helm version: " + result) - } - log.Println("VERBOSE: helm version: \n" + result + "\n") -} - -// envVarExists checks if an environment variable is set or not and returns it. -// empty string is returned for unset env vars -// it accepts env var with/without '$' at the beginning -func envVarExists(v string) (bool, string) { - - if strings.HasPrefix(v, "$") { - v = strings.SplitAfter(v, "$")[1] - } - - value, ok := os.LookupEnv(v) - return ok, value + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "kubectl version"}, + Description: "Kubectl version: ", + } + + exitCode, result := cmd.exec(debug, false) + if exitCode != 0 { + log.Fatal("ERROR: while checking kubectl version: " + result) + } + + log.Println("VERBOSE: kubectl version: \n " + result + "\n") + + cmd = command{ + Cmd: "bash", + Args: []string{"-c", "helm version"}, + Description: "Helm version: ", + } + + exitCode, result = cmd.exec(debug, false) + if exitCode != 0 { + log.Fatal("ERROR: while checking helm version: " + result) + } + log.Println("VERBOSE: helm version: \n" + result + "\n") } // sliceContains checks if a string slice contains a given string func sliceContains(slice []string, s string) bool { - for _, a := range slice { - if strings.TrimSpace(a) == s { - return true - } - } - return false + for _, a := range slice { + if strings.TrimSpace(a) == s { + return true + } + } + return false } // validateServiceAccount checks if k8s service account exists in a given namespace func validateServiceAccount(sa string, namespace string) (bool, string) { - if namespace == "" { - namespace = "default" - } - ns := " -n " + namespace - - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "kubectl get serviceaccount " + sa + ns}, - Description: "validating that serviceaccount [ " + sa + " ] exists in namespace [ " + namespace + " ].", - } - - if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { - return false, err - } - return true, "" + if namespace == "" { + namespace = "default" + } + ns := " -n " + namespace + + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "kubectl get serviceaccount " + sa + ns}, + Description: "validating that serviceaccount [ " + sa + " ] exists in namespace [ " + namespace + " ].", + } + + if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { + return false, err + } + return true, "" } // downloadFile downloads a file from GCS or AWS buckets and name it with a given outfile // if downloaded, returns the outfile name. If the file path is local file system path, it is returned as is. func downloadFile(path string, outfile string) string { - if strings.HasPrefix(path, "s3") { + if strings.HasPrefix(path, "s3") { - tmp := getBucketElements(path) - aws.ReadFile(tmp["bucketName"], tmp["filePath"], outfile) + tmp := getBucketElements(path) + aws.ReadFile(tmp["bucketName"], tmp["filePath"], outfile) - } else if strings.HasPrefix(path, "gs") { + } else if strings.HasPrefix(path, "gs") { - tmp := getBucketElements(path) - gcs.ReadFile(tmp["bucketName"], tmp["filePath"], outfile) + tmp := getBucketElements(path) + gcs.ReadFile(tmp["bucketName"], tmp["filePath"], outfile) - } else { + } else { - log.Println("INFO: " + outfile + " will be used from local file system.") - copyFile(path, outfile) - } - return outfile + log.Println("INFO: " + outfile + " will be used from local file system.") + copyFile(path, outfile) + } + return outfile } // copyFile copies a file from source to destination func copyFile(source string, destination string) { - from, err := os.Open(source) - if err != nil { - log.Fatal("ERROR: while copying " + source + " to " + destination + " : " + err.Error()) - } - defer from.Close() - - to, err := os.OpenFile(destination, os.O_RDWR|os.O_CREATE, 0666) - if err != nil { - log.Fatal("ERROR: while copying " + source + " to " + destination + " : " + err.Error()) - } - defer to.Close() - - _, err = io.Copy(to, from) - if err != nil { - log.Fatal("ERROR: while copying " + source + " to " + destination + " : " + err.Error()) - } + from, err := os.Open(source) + if err != nil { + log.Fatal("ERROR: while copying " + source + " to " + destination + " : " + err.Error()) + } + defer from.Close() + + to, err := os.OpenFile(destination, os.O_RDWR|os.O_CREATE, 0666) + if err != nil { + log.Fatal("ERROR: while copying " + source + " to " + destination + " : " + err.Error()) + } + defer to.Close() + + _, err = io.Copy(to, from) + if err != nil { + log.Fatal("ERROR: while copying " + source + " to " + destination + " : " + err.Error()) + } } // deleteFile deletes a file func deleteFile(path string) { - log.Println("INFO: cleaning up ... deleting " + path) - if err := os.Remove(path); err != nil { - log.Fatal("ERROR: could not delete file: " + path) - } + log.Println("INFO: cleaning up ... deleting " + path) + if err := os.Remove(path); err != nil { + log.Fatal("ERROR: could not delete file: " + path) + } } + From f65b37c871dd69b63edf70e2aa053c2c3cf2af3c Mon Sep 17 00:00:00 2001 From: Joshua Knarr Date: Tue, 29 May 2018 10:54:48 -0400 Subject: [PATCH 0167/1127] cherry pick from internal working repo --- .utils.go.swp | Bin 16384 -> 0 bytes decision_maker.go | 428 +++++++++++++++++++++++----------------------- release.go | 127 +++++++------- state.go | 351 +++++++++++++++++++------------------ utils.go | 292 ++++++++++++++++--------------- 5 files changed, 612 insertions(+), 586 deletions(-) delete mode 100644 .utils.go.swp diff --git a/.utils.go.swp b/.utils.go.swp deleted file mode 100644 index 764c0c5349a48e115d957e3b1c188e915e93490a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16384 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 C{JvNK diff --git a/decision_maker.go b/decision_maker.go index 60765d62..a8cfe3e9 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -1,8 +1,7 @@ package main import ( - "os" - "strings" + "strings" ) var outcome plan @@ -10,112 +9,112 @@ var releases string // makePlan creates a plan of the actions needed to make the desired state come true. func makePlan(s *state) *plan { - outcome = createPlan() - buildState() + outcome = createPlan() + buildState() - for _, r := range s.Apps { - decide(r, s) - } + for _, r := range s.Apps { + decide(r, s) + } - return &outcome + return &outcome } // decide makes a decision about what commands (actions) need to be executed // to make a release section of the desired state come true. func decide(r *release, s *state) { - // check for deletion - if !r.Enabled { - if !isProtected(r) { - inspectDeleteScenario(getDesiredNamespace(r), r) - } else { - logDecision("DECISION: release "+r.Name+" is PROTECTED. Operations are not allowed on this release until "+ - "you remove its protection.", r.Priority) - } + // check for deletion + if !r.Enabled { + if !isProtected(r) { + inspectDeleteScenario(getDesiredNamespace(r), r) + } else { + logDecision("DECISION: release "+r.Name+" is PROTECTED. Operations are not allowed on this release until "+ + "you remove its protection.", r.Priority) + } - } else { // check for install/upgrade/rollback - if helmReleaseExists(getDesiredNamespace(r), r.Name, "deployed") { - if !isProtected(r) { - inspectUpgradeScenario(getDesiredNamespace(r), r) // upgrade - } else { - logDecision("DECISION: release "+r.Name+" is PROTECTED. Operations are not allowed on this release until "+ - "you remove its protection.", r.Priority) - } + } else { // check for install/upgrade/rollback + if helmReleaseExists(getDesiredNamespace(r), r.Name, "deployed") { + if !isProtected(r) { + inspectUpgradeScenario(getDesiredNamespace(r), r) // upgrade + } else { + logDecision("DECISION: release "+r.Name+" is PROTECTED. Operations are not allowed on this release until "+ + "you remove its protection.", r.Priority) + } - } else if helmReleaseExists("", r.Name, "deleted") { - if !isProtected(r) { + } else if helmReleaseExists("", r.Name, "deleted") { + if !isProtected(r) { - rollbackRelease(getDesiredNamespace(r), r) // rollback + rollbackRelease(getDesiredNamespace(r), r) // rollback - } else { - logDecision("DECISION: release "+r.Name+" is PROTECTED. Operations are not allowed on this release until "+ - "you remove its protection.", r.Priority) - } + } else { + logDecision("DECISION: release "+r.Name+" is PROTECTED. Operations are not allowed on this release until "+ + "you remove its protection.", r.Priority) + } - } else if helmReleaseExists("", r.Name, "failed") { + } else if helmReleaseExists("", r.Name, "failed") { - if !isProtected(r) { + if !isProtected(r) { - reInstallRelease(getDesiredNamespace(r), r) // re-install failed release + reInstallRelease(getDesiredNamespace(r), r) // re-install failed release - } else { - logDecision("DECISION: release "+r.Name+" is PROTECTED. Operations are not allowed on this release until "+ - "you remove its protection.", r.Priority) - } + } else { + logDecision("DECISION: release "+r.Name+" is PROTECTED. Operations are not allowed on this release until "+ + "you remove its protection.", r.Priority) + } - } else if helmReleaseExists("", r.Name, "") { // not deployed in the desired namespace but deployed elsewhere + } else if helmReleaseExists("", r.Name, "") { // not deployed in the desired namespace but deployed elsewhere - if !isProtected(r) { + if !isProtected(r) { - reInstallRelease(getDesiredNamespace(r), r) // move the release to a new (the desired) namespace - logDecision("WARNING: moving release [ "+r.Name+" ] from [[ "+getReleaseNamespace(r.Name)+" ]] to [[ "+getDesiredNamespace(r)+ - " ]] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md"+ - " for details if this release uses PV and PVC.", r.Priority) + reInstallRelease(getDesiredNamespace(r), r) // move the release to a new (the desired) namespace + logDecision("WARNING: moving release [ "+r.Name+" ] from [[ "+getReleaseNamespace(r.Name)+" ]] to [[ "+getDesiredNamespace(r)+ + " ]] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md"+ + " for details if this release uses PV and PVC.", r.Priority) - } else { - logDecision("DECISION: release "+r.Name+" is PROTECTED. Operations are not allowed on this release until "+ - "you remove its protection.", r.Priority) - } + } else { + logDecision("DECISION: release "+r.Name+" is PROTECTED. Operations are not allowed on this release until "+ + "you remove its protection.", r.Priority) + } - } else { + } else { - installRelease(getDesiredNamespace(r), r) // install a new release + installRelease(getDesiredNamespace(r), r) // install a new release - } + } - } + } } // testRelease creates a Helm command to test a particular release. func testRelease(r *release) { - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm test " + r.Name + getDesiredTillerNamespace(r) + getTLSFlags(r)}, - Description: "running tests for release [ " + r.Name + " ]", - } - outcome.addCommand(cmd, r.Priority) - logDecision("DECISION: release [ "+r.Name+" ] is required to be tested when installed. Got it!", r.Priority) + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm test " + r.Name + getDesiredTillerNamespace(r) + getTLSFlags(r)}, + Description: "running tests for release [ " + r.Name + " ]", + } + outcome.addCommand(cmd, r.Priority) + logDecision("DECISION: release [ "+r.Name+" ] is required to be tested when installed. Got it!", r.Priority) } // installRelease creates a Helm command to install a particular release in a particular namespace. func installRelease(namespace string, r *release) { - releaseName := r.Name - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm install " + r.Chart + " -n " + releaseName + " --namespace " + namespace + getValuesFile(r) + " --version " + r.Version + getSetValues(r) + getWait(r) + getDesiredTillerNamespace(r) + getTLSFlags(r)}, - Description: "installing release [ " + releaseName + " ] in namespace [[ " + namespace + " ]]", - } - outcome.addCommand(cmd, r.Priority) - logDecision("DECISION: release [ "+releaseName+" ] is not present in the current k8s context. Will install it in namespace [[ "+ - namespace+" ]]", r.Priority) - - if r.Test { - testRelease(r) - } + releaseName := r.Name + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm install " + r.Chart + " -n " + releaseName + " --namespace " + namespace + getValuesFile(r) + " --version " + r.Version + getSetValues(r) + getWait(r) + getDesiredTillerNamespace(r) + getTLSFlags(r)}, + Description: "installing release [ " + releaseName + " ] in namespace [[ " + namespace + " ]]", + } + outcome.addCommand(cmd, r.Priority) + logDecision("DECISION: release [ "+releaseName+" ] is not present in the current k8s context. Will install it in namespace [[ "+ + namespace+" ]]", r.Priority) + + if r.Test { + testRelease(r) + } } // rollbackRelease evaluates if a rollback action needs to be taken for a given release. @@ -123,29 +122,29 @@ func installRelease(namespace string, r *release) { // it purge deletes it and create it in the spcified namespace. func rollbackRelease(namespace string, r *release) { - releaseName := r.Name - if getReleaseNamespace(r.Name) == namespace { - - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm rollback " + releaseName + " " + getReleaseRevision(releaseName, "deleted") + getWait(r) + getDesiredTillerNamespace(r) + getTLSFlags(r)}, - Description: "rolling back release [ " + releaseName + " ]", - } - outcome.addCommand(cmd, r.Priority) - upgradeRelease(r) - logDecision("DECISION: release [ "+releaseName+" ] is currently deleted and is desired to be rolledback to "+ - "namespace [[ "+namespace+" ]] . It will also be upgraded in case values have changed.", r.Priority) - - } else { - - reInstallRelease(namespace, r) - logDecision("DECISION: release [ "+releaseName+" ] is deleted BUT from namespace [[ "+getReleaseNamespace(releaseName)+ - " ]]. Will purge delete it from there and install it in namespace [[ "+namespace+" ]]", r.Priority) - logDecision("WARNING: rolling back release [ "+releaseName+" ] from [[ "+getReleaseNamespace(releaseName)+" ]] to [[ "+namespace+ - " ]] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md"+ - " for details if this release uses PV and PVC.", r.Priority) - - } + releaseName := r.Name + if getReleaseNamespace(r.Name) == namespace { + + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm rollback " + releaseName + " " + getReleaseRevision(releaseName, "deleted") + getWait(r) + getDesiredTillerNamespace(r) + getTLSFlags(r)}, + Description: "rolling back release [ " + releaseName + " ]", + } + outcome.addCommand(cmd, r.Priority) + upgradeRelease(r) + logDecision("DECISION: release [ "+releaseName+" ] is currently deleted and is desired to be rolledback to "+ + "namespace [[ "+namespace+" ]] . It will also be upgraded in case values have changed.", r.Priority) + + } else { + + reInstallRelease(namespace, r) + logDecision("DECISION: release [ "+releaseName+" ] is deleted BUT from namespace [[ "+getReleaseNamespace(releaseName)+ + " ]]. Will purge delete it from there and install it in namespace [[ "+namespace+" ]]", r.Priority) + logDecision("WARNING: rolling back release [ "+releaseName+" ] from [[ "+getReleaseNamespace(releaseName)+" ]] to [[ "+namespace+ + " ]] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md"+ + " for details if this release uses PV and PVC.", r.Priority) + + } } // inspectDeleteScenario evaluates if a delete action needs to be taken for a given release. @@ -153,33 +152,33 @@ func rollbackRelease(namespace string, r *release) { // If the release is not deployed, it will be skipped. func inspectDeleteScenario(namespace string, r *release) { - releaseName := r.Name - //if it exists in helm list , add command to delete it, else log that it is skipped - if helmReleaseExists(namespace, releaseName, "deployed") { - // delete it - deleteRelease(r) + releaseName := r.Name + //if it exists in helm list , add command to delete it, else log that it is skipped + if helmReleaseExists(namespace, releaseName, "deployed") { + // delete it + deleteRelease(r) - } else { - logDecision("DECISION: release [ "+releaseName+" ] is set to be disabled but is not yet deployed. Skipping.", r.Priority) - } + } else { + logDecision("DECISION: release [ "+releaseName+" ] is set to be disabled but is not yet deployed. Skipping.", r.Priority) + } } // deleteRelease deletes a release from a k8s cluster func deleteRelease(r *release) { - p := "" - purgeDesc := "" - if r.Purge { - p = "--purge" - purgeDesc = "and purged!" - } - - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm delete " + p + " " + r.Name + getCurrentTillerNamespace(r) + getTLSFlags(r)}, - Description: "deleting release [ " + r.Name + " ]", - } - outcome.addCommand(cmd, r.Priority) - logDecision("DECISION: release [ "+r.Name+" ] is desired to be deleted "+purgeDesc+". Planing this for you!", r.Priority) + p := "" + purgeDesc := "" + if r.Purge { + p = "--purge" + purgeDesc = "and purged!" + } + + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm delete " + p + " " + r.Name + getCurrentTillerNamespace(r) + getTLSFlags(r)}, + Description: "deleting release [ " + r.Name + " ]", + } + outcome.addCommand(cmd, r.Priority) + logDecision("DECISION: release [ "+r.Name+" ] is desired to be deleted "+purgeDesc+". Planing this for you!", r.Priority) } // inspectUpgradeScenario evaluates if a release should be upgraded. @@ -191,65 +190,65 @@ func deleteRelease(r *release) { // it will be purge deleted and installed in the new namespace. func inspectUpgradeScenario(namespace string, r *release) { - releaseName := r.Name - if getReleaseNamespace(releaseName) == namespace { - if extractChartName(r.Chart) == getReleaseChartName(releaseName) && r.Version != getReleaseChartVersion(releaseName) { - // upgrade - upgradeRelease(r) - logDecision("DECISION: release [ "+releaseName+" ] is desired to be upgraded. Planing this for you!", r.Priority) - - } else if extractChartName(r.Chart) != getReleaseChartName(releaseName) { - reInstallRelease(namespace, r) - logDecision("DECISION: release [ "+releaseName+" ] is desired to use a new Chart [ "+r.Chart+ - " ]. I am planning a purge delete of the current release and will install it with the new chart in namespace [[ "+ - namespace+" ]]", r.Priority) - - } else { - upgradeRelease(r) - logDecision("DECISION: release [ "+releaseName+" ] is desired to be enabled and is currently enabled."+ - "I will upgrade it in case you changed your values.yaml!", r.Priority) - } - } else { - reInstallRelease(namespace, r) - logDecision("DECISION: release [ "+releaseName+" ] is desired to be enabled in a new namespace [[ "+namespace+ - " ]]. I am planning a purge delete of the current release from namespace [[ "+getReleaseNamespace(releaseName)+" ]] "+ - "and will install it for you in namespace [[ "+namespace+" ]]", r.Priority) - logDecision("WARNING: moving release [ "+releaseName+" ] from [[ "+getReleaseNamespace(releaseName)+" ]] to [[ "+namespace+ - " ]] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md"+ - " for details if this release uses PV and PVC.", r.Priority) - } + releaseName := r.Name + if getReleaseNamespace(releaseName) == namespace { + if extractChartName(r.Chart) == getReleaseChartName(releaseName) && r.Version != getReleaseChartVersion(releaseName) { + // upgrade + upgradeRelease(r) + logDecision("DECISION: release [ "+releaseName+" ] is desired to be upgraded. Planing this for you!", r.Priority) + + } else if extractChartName(r.Chart) != getReleaseChartName(releaseName) { + reInstallRelease(namespace, r) + logDecision("DECISION: release [ "+releaseName+" ] is desired to use a new Chart [ "+r.Chart+ + " ]. I am planning a purge delete of the current release and will install it with the new chart in namespace [[ "+ + namespace+" ]]", r.Priority) + + } else { + upgradeRelease(r) + logDecision("DECISION: release [ "+releaseName+" ] is desired to be enabled and is currently enabled."+ + "I will upgrade it in case you changed your values.yaml!", r.Priority) + } + } else { + reInstallRelease(namespace, r) + logDecision("DECISION: release [ "+releaseName+" ] is desired to be enabled in a new namespace [[ "+namespace+ + " ]]. I am planning a purge delete of the current release from namespace [[ "+getReleaseNamespace(releaseName)+" ]] "+ + "and will install it for you in namespace [[ "+namespace+" ]]", r.Priority) + logDecision("WARNING: moving release [ "+releaseName+" ] from [[ "+getReleaseNamespace(releaseName)+" ]] to [[ "+namespace+ + " ]] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md"+ + " for details if this release uses PV and PVC.", r.Priority) + } } // upgradeRelease upgrades an existing release with the specified values.yaml func upgradeRelease(r *release) { - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm upgrade " + r.Name + " " + r.Chart + getValuesFile(r) + " --version " + r.Version + " --force " + getSetValues(r) + getWait(r) + getDesiredTillerNamespace(r) + getTLSFlags(r)}, - Description: "upgrading release [ " + r.Name + " ]", - } + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm upgrade " + r.Name + " " + r.Chart + getValuesFile(r) + " --version " + r.Version + " --force " + getSetValues(r) + getWait(r) + getDesiredTillerNamespace(r) + getTLSFlags(r)}, + Description: "upgrading release [ " + r.Name + " ]", + } - outcome.addCommand(cmd, r.Priority) + outcome.addCommand(cmd, r.Priority) } // reInstallRelease purge deletes a release and reinstalls it. // This is used when moving a release to another namespace or when changing the chart used for it. func reInstallRelease(namespace string, r *release) { - releaseName := r.Name - delCmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm delete --purge " + releaseName + getCurrentTillerNamespace(r) + getTLSFlags(r)}, - Description: "deleting release [ " + releaseName + " ]", - } - outcome.addCommand(delCmd, r.Priority) - - installCmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm install " + r.Chart + " --version " + r.Version + " -n " + releaseName + " --namespace " + namespace + getValuesFile(r) + getSetValues(r) + getWait(r) + getDesiredTillerNamespace(r) + getTLSFlags(r)}, - Description: "installing release [ " + releaseName + " ] in namespace [[ " + namespace + " ]]", - } - outcome.addCommand(installCmd, r.Priority) - logDecision("DECISION: release [ "+releaseName+" ] will be deleted from namespace [[ "+getReleaseNamespace(releaseName)+" ]] and reinstalled in [[ "+namespace+"]].", r.Priority) + releaseName := r.Name + delCmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm delete --purge " + releaseName + getCurrentTillerNamespace(r) + getTLSFlags(r)}, + Description: "deleting release [ " + releaseName + " ]", + } + outcome.addCommand(delCmd, r.Priority) + + installCmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm install " + r.Chart + " --version " + r.Version + " -n " + releaseName + " --namespace " + namespace + getValuesFile(r) + getSetValues(r) + getWait(r) + getDesiredTillerNamespace(r) + getTLSFlags(r)}, + Description: "installing release [ " + releaseName + " ] in namespace [[ " + namespace + " ]]", + } + outcome.addCommand(installCmd, r.Priority) + logDecision("DECISION: release [ "+releaseName+" ] will be deleted from namespace [[ "+getReleaseNamespace(releaseName)+" ]] and reinstalled in [[ "+namespace+"]].", r.Priority) } @@ -257,7 +256,7 @@ func reInstallRelease(namespace string, r *release) { // Depending on the debug flag being set or not, it will either log the the decision to output or not. func logDecision(decision string, priority int) { - outcome.addDecision(decision, priority) + outcome.addDecision(decision, priority) } @@ -265,49 +264,49 @@ func logDecision(decision string, priority int) { // example: it extracts "chartY" from "repoX/chartY" func extractChartName(releaseChart string) string { - return strings.TrimSpace(strings.Split(releaseChart, "/")[1]) + return strings.TrimSpace(strings.Split(releaseChart, "/")[1]) } // getValuesFile return partial install/upgrade release command to substitute the -f flag in Helm. func getValuesFile(r *release) string { - if r.ValuesFile != "" { - return " -f " + r.ValuesFile - } - return "" + if r.ValuesFile != "" { + return " -f " + r.ValuesFile + } + return "" } // getSetValues returns --set params to be used with helm install/upgrade commands func getSetValues(r *release) string { - result := "" - for k, v := range r.Set { - value := os.ExpandEnv(v) - result = result + " --set " + k + "=\"" + strings.Replace(value, ",", "\\,", -1) + "\"" - } - return result + result := "" + for k, v := range r.Set { + _, value := envVarExists(v) + result = result + " --set " + k + "=\"" + strings.Replace(value, ",", "\\,", -1) + "\"" + } + return result } // getWait returns a partial helm command containing the helm wait flag (--wait) if the wait flag for the release was set to true // Otherwise, retruns an empty string func getWait(r *release) string { - result := "" - if r.Wait { - result = " --wait" - } - return result + result := "" + if r.Wait { + result = " --wait" + } + return result } // getDesiredNamespace returns the namespace of a release func getDesiredNamespace(r *release) string { - return r.Namespace + return r.Namespace } // getCurrentNamespaceProtection returns the protection state for the namespace where a release is currently installed. // It returns true if a namespace is defined as protected in the desired state file, false otherwise. func getCurrentNamespaceProtection(r *release) bool { - return s.Namespaces[getReleaseNamespace(r.Name)].Protected + return s.Namespaces[getReleaseNamespace(r.Name)].Protected } // isProtected checks if a release is protected or not. @@ -316,58 +315,57 @@ func getCurrentNamespaceProtection(r *release) bool { // returns true if a release is protected, false otherwise func isProtected(r *release) bool { - // if the release does not exist in the cluster, it is not protected - if !helmReleaseExists("", r.Name, "") { - return false - } + // if the release does not exist in the cluster, it is not protected + if !helmReleaseExists("", r.Name, "") { + return false + } - if getCurrentNamespaceProtection(r) { - return true - } + if getCurrentNamespaceProtection(r) { + return true + } - if r.Protected { - return true - } + if r.Protected { + return true + } - return false + return false } // getDesiredTillerNamespace returns a tiller-namespace flag with which a release is desired to be maintained func getDesiredTillerNamespace(r *release) string { - if s.Namespaces[r.Namespace].InstallTiller { - return " --tiller-namespace " + r.Namespace - } + if s.Namespaces[r.Namespace].InstallTiller { + return " --tiller-namespace " + r.Namespace + } - return "" // same as return " --tiller-namespace kube-system" + return "" // same as return " --tiller-namespace kube-system" } // getCurrentTillerNamespace returns the tiller-namespace with which a release is currently maintained func getCurrentTillerNamespace(r *release) string { - if v, ok := currentState[r.Name]; ok { - return " --tiller-namespace " + v.TillerNamespace - } - return "" + if v, ok := currentState[r.Name]; ok { + return " --tiller-namespace " + v.TillerNamespace + } + return "" } // getTLSFlags returns TLS flags with which a release is maintained // If the release where the namespace is to be deployed has Tiller deployed, the TLS flages will use certs/keys for that namespace (if any) // otherwise, it will be the certs/keys for the kube-system namespace. func getTLSFlags(r *release) string { - tls := "" - if s.Namespaces[r.Namespace].InstallTiller { - if tillerTLSEnabled(r.Namespace) { + tls := "" + if s.Namespaces[r.Namespace].InstallTiller { + if tillerTLSEnabled(r.Namespace) { - tls = " --tls --tls-ca-cert " + r.Namespace + "-ca.cert --tls-cert " + r.Namespace + "-client.cert --tls-key " + r.Namespace + "-client.key " - } - } else { - if tillerTLSEnabled("kube-system") { + tls = " --tls --tls-ca-cert " + r.Namespace + "-ca.cert --tls-cert " + r.Namespace + "-client.cert --tls-key " + r.Namespace + "-client.key " + } + } else { + if tillerTLSEnabled("kube-system") { - tls = " --tls --tls-ca-cert kube-system-ca.cert --tls-cert kube-system-client.cert --tls-key kube-system-client.key " - } - } + tls = " --tls --tls-ca-cert kube-system-ca.cert --tls-cert kube-system-client.cert --tls-key kube-system-client.key " + } + } - return tls + return tls } - diff --git a/release.go b/release.go index 757518bb..5052f6a6 100644 --- a/release.go +++ b/release.go @@ -1,86 +1,93 @@ package main import ( - "fmt" - "log" - "os" - "strings" + "fmt" + "log" + "os" + "strings" ) // release type representing Helm releases which are described in the desired state type release struct { - Name string - Description string - Namespace string - Enabled bool - Chart string - Version string - ValuesFile string - Purge bool - Test bool - Protected bool - Wait bool - Priority int - Set map[string]string + Name string + Description string + Namespace string + Enabled bool + Chart string + Version string + ValuesFile string + Purge bool + Test bool + Protected bool + Wait bool + Priority int + Set map[string]string } // validateRelease validates if a release inside a desired state meets the specifications or not. // check the full specification @ https://github.com/Praqma/helmsman/docs/desired_state_spec.md func validateRelease(r *release, names map[string]bool, s state) (bool, string) { - _, err := os.Stat(r.ValuesFile) - if r.Name == "" || names[r.Name] { - return false, "release name can't be empty and must be unique." - } else if nsOverride == "" && r.Namespace == "" { - return false, "release targeted namespace can't be empty." - } else if nsOverride == "" && r.Namespace != "" && !checkNamespaceDefined(r.Namespace, s) { - return false, "release " + r.Name + " is using namespace [ " + r.Namespace + " ] which is not defined in the Namespaces section of your desired state file." + - " Release [ " + r.Name + " ] can't be installed in that Namespace until its defined." - } else if r.Chart == "" || !strings.ContainsAny(r.Chart, "/") { - return false, "chart can't be empty and must be of the format: repo/chart." - } else if r.Version == "" { - return false, "version can't be empty." - } else if r.ValuesFile != "" && (!isOfType(r.ValuesFile, ".yaml") || err != nil) { - return false, "valuesFile must be a valid file path for a yaml file, Or can be left empty." - } else if r.Priority != 0 && r.Priority > 0 { - return false, "priority can only be 0 or negative value, positive values are not allowed." - } + _, err := os.Stat(r.ValuesFile) + if r.Name == "" || names[r.Name] { + return false, "release name can't be empty and must be unique." + } else if nsOverride == "" && r.Namespace == "" { + return false, "release targeted namespace can't be empty." + } else if nsOverride == "" && r.Namespace != "" && !checkNamespaceDefined(r.Namespace, s) { + return false, "release " + r.Name + " is using namespace [ " + r.Namespace + " ] which is not defined in the Namespaces section of your desired state file." + + " Release [ " + r.Name + " ] can't be installed in that Namespace until its defined." + } else if r.Chart == "" || !strings.ContainsAny(r.Chart, "/") { + return false, "chart can't be empty and must be of the format: repo/chart." + } else if r.Version == "" { + return false, "version can't be empty." + } else if r.ValuesFile != "" && (!isOfType(r.ValuesFile, ".yaml") || err != nil) { + return false, "valuesFile must be a valid file path for a yaml file, Or can be left empty." + } else if len(r.Set) > 0 { + for k, v := range r.Set { + if !strings.HasPrefix(v, "$") { + return false, "the value for variable [ " + k + " ] must be an environment variable name and start with '$'." + } else if ok, _ := envVarExists(v); !ok { + return false, "env variable [ " + v + " ] is not found in the environment." + } + } + } else if r.Priority != 0 && r.Priority > 0 { + return false, "priority can only be 0 or negative value, positive values are not allowed." + } - names[r.Name] = true - return true, "" + names[r.Name] = true + return true, "" } // checkNamespaceDefined checks if a given namespace is defined in the namespaces section of the desired state file func checkNamespaceDefined(ns string, s state) bool { - _, ok := s.Namespaces[ns] - if !ok { - return false - } - return true + _, ok := s.Namespaces[ns] + if !ok { + return false + } + return true } // overrideNamespace overrides a release defined namespace with a new given one func overrideNamespace(r *release, newNs string) { - log.Println("INFO: overriding namespace for app: " + r.Name) - r.Namespace = newNs + log.Println("INFO: overriding namespace for app: " + r.Name) + r.Namespace = newNs } // print prints the details of the release func (r release) print() { - fmt.Println("") - fmt.Println("\tname : ", r.Name) - fmt.Println("\tdescription : ", r.Description) - fmt.Println("\tnamespace : ", r.Namespace) - fmt.Println("\tenabled : ", r.Enabled) - fmt.Println("\tchart : ", r.Chart) - fmt.Println("\tversion : ", r.Version) - fmt.Println("\tvaluesFile : ", r.ValuesFile) - fmt.Println("\tpurge : ", r.Purge) - fmt.Println("\ttest : ", r.Test) - fmt.Println("\tprotected : ", r.Protected) - fmt.Println("\twait : ", r.Wait) - fmt.Println("\tpriority : ", r.Priority) - fmt.Println("\tvalues to override from env:") - printMap(r.Set) - fmt.Println("------------------- ") + fmt.Println("") + fmt.Println("\tname : ", r.Name) + fmt.Println("\tdescription : ", r.Description) + fmt.Println("\tnamespace : ", r.Namespace) + fmt.Println("\tenabled : ", r.Enabled) + fmt.Println("\tchart : ", r.Chart) + fmt.Println("\tversion : ", r.Version) + fmt.Println("\tvaluesFile : ", r.ValuesFile) + fmt.Println("\tpurge : ", r.Purge) + fmt.Println("\ttest : ", r.Test) + fmt.Println("\tprotected : ", r.Protected) + fmt.Println("\twait : ", r.Wait) + fmt.Println("\tpriority : ", r.Priority) + fmt.Println("\tvalues to override from env:") + printMap(r.Set) + fmt.Println("------------------- ") } - diff --git a/state.go b/state.go index 404281aa..a9eb92b8 100644 --- a/state.go +++ b/state.go @@ -1,204 +1,213 @@ package main import ( - "fmt" - "log" - "net/url" - "os" - "strings" + "fmt" + "log" + "net/url" + "os" + "strings" ) // namespace type represents the fields of a namespace type namespace struct { - Protected bool - InstallTiller bool - TillerServiceAccount string - CaCert string - TillerCert string - TillerKey string - ClientCert string - ClientKey string + Protected bool + InstallTiller bool + TillerServiceAccount string + CaCert string + TillerCert string + TillerKey string + ClientCert string + ClientKey string } // state type represents the desired state of applications on a k8s cluster. type state struct { - Metadata map[string]string - Certificates map[string]string - Settings map[string]string - Namespaces map[string]namespace - HelmRepos map[string]string - Apps map[string]*release + Metadata map[string]string + Certificates map[string]string + Settings map[string]string + Namespaces map[string]namespace + HelmRepos map[string]string + Apps map[string]*release } // validate validates that the values specified in the desired state are valid according to the desired state spec. // check https://github.com/Praqma/Helmsman/docs/desired_state_spec.md for the detailed specification func (s state) validate() (bool, string) { - // settings - if s.Settings == nil || len(s.Settings) == 0 { - return false, "ERROR: settings validation failed -- no settings table provided in TOML." - } else if value, ok := s.Settings["kubeContext"]; !ok || value == "" { - return false, "ERROR: settings validation failed -- you have not provided a " + - "kubeContext to use. Can't work without it. Sorry!" - } else if value, ok = s.Settings["clusterURI"]; ok { - - s.Settings["clusterURI"] = os.ExpandEnv(value) - if _, err := url.ParseRequestURI(s.Settings["clusterURI"]); err != nil { - return false, "ERROR: settings validation failed -- clusterURI must have a valid URL set in an env varibale or passed directly. Either the env var is missing/empty or the URL is invalid." - } - - if _, ok = s.Settings["username"]; !ok { - return false, "ERROR: settings validation failed -- username must be provided if clusterURI is defined." - } - if value, ok = s.Settings["password"]; ok { - s.Settings["password"] = os.ExpandEnv(value) - } else { - return false, "ERROR: settings validation failed -- password must be provided if clusterURI is defined." - } - - if s.Settings["password"] == "" { - return false, "ERROR: settings validation failed -- password should be set as an env variable. It is currently missing or empty. " - } - } - - // certificates - if s.Certificates != nil && len(s.Certificates) != 0 { - _, ok1 := s.Settings["clusterURI"] - _, ok2 := s.Certificates["caCrt"] - _, ok3 := s.Certificates["caKey"] - if ok1 && (!ok2 || !ok3) { - return false, "ERROR: certifications validation failed -- You want me to connect to your cluster for you " + - "but have not given me the cert/key to do so. Please add [caCrt] and [caKey] under Certifications. You might also need to provide [clientCrt]." - } else if ok1 { - for key, value := range s.Certificates { - r, path := isValidCert(value) - if !r { - return false, "ERROR: certifications validation failed -- [ " + key + " ] must be a valid S3 or GCS bucket URL or a valid relative file path." - } - s.Certificates[key] = path - } - } else { - log.Println("INFO: certificates provided but not needed. Skipping certificates validation.") - } - - } else { - if _, ok := s.Settings["clusterURI"]; ok { - return false, "ERROR: certifications validation failed -- You want me to connect to your cluster for you " + - "but have not given me the cert/key to do so. Please add [caCrt] and [caKey] under Certifications. You might also need to provide [clientCrt]." - } - } - - // namespaces - if nsOverride == "" { - if s.Namespaces == nil || len(s.Namespaces) == 0 { - return false, "ERROR: namespaces validation failed -- I need at least one namespace " + - "to work with!" - } - - for k, v := range s.Namespaces { - if !v.InstallTiller && k != "kube-system" { - log.Println("INFO: naemspace validation -- Tiller is not desired to be deployed in namespace [ " + k + " ].") - } else { - if tillerTLSEnabled(k) { - // validating the TLS certs and keys for Tiller - // if they are valid, their values (if they are env vars) are substituted - var ok1, ok2, ok3, ok4, ok5 bool - ok1, v.CaCert = isValidCert(v.CaCert) - ok2, v.ClientCert = isValidCert(v.ClientCert) - ok3, v.ClientKey = isValidCert(v.ClientKey) - ok4, v.TillerCert = isValidCert(v.TillerCert) - ok5, v.TillerKey = isValidCert(v.TillerKey) - if !ok1 || !ok2 || !ok3 || !ok4 || !ok5 { - return false, "ERROR: namespaces validation failed -- some certs/keys are not valid for Tiller TLS in namespace [ " + k + " ]." - } - log.Println("INFO: namespace validation -- Tiller is desired to be deployed with TLS in namespace [ " + k + " ]. ") - } else { - log.Println("INFO: namespace validation -- Tiller is desired to be deployed WITHOUT TLS in namespace [ " + k + " ]. ") - } - } - } - } else { - log.Println("INFO: ns-override is used to override all namespaces with [ " + nsOverride + " ] Skipping defined namespaces validation.") - } - - // repos - if s.HelmRepos == nil || len(s.HelmRepos) == 0 { - return false, "ERROR: repos validation failed -- I need at least one helm repo " + - "to work with!" - } - for k, v := range s.HelmRepos { - _, err := url.ParseRequestURI(v) - if err != nil { - return false, "ERROR: repos validation failed -- repo [" + k + " ] " + - "must have a valid URL." - } - - continue - - } - - // apps - if s.Apps == nil { - log.Println("INFO: You have not specified any apps. I have nothing to do. ", - "Horraayyy!.") - os.Exit(0) - } - - names := make(map[string]bool) - for appLabel, r := range s.Apps { - result, errMsg := validateRelease(r, names, s) - if !result { - return false, "ERROR: apps validation failed -- for app [" + appLabel + " ]. " + errMsg - } - } - - return true, "" + // settings + if s.Settings == nil || len(s.Settings) == 0 { + return false, "ERROR: settings validation failed -- no settings table provided in TOML." + } else if value, ok := s.Settings["kubeContext"]; !ok || value == "" { + return false, "ERROR: settings validation failed -- you have not provided a " + + "kubeContext to use. Can't work without it. Sorry!" + } else if value, ok = s.Settings["clusterURI"]; ok { + + s.Settings["clusterURI"] = subsituteEnv(value) + if _, err := url.ParseRequestURI(s.Settings["clusterURI"]); err != nil { + return false, "ERROR: settings validation failed -- clusterURI must have a valid URL set in an env varibale or passed directly. Either the env var is missing/empty or the URL is invalid." + } + + if _, ok = s.Settings["username"]; !ok { + return false, "ERROR: settings validation failed -- username must be provided if clusterURI is defined." + } + if value, ok = s.Settings["password"]; ok { + s.Settings["password"] = subsituteEnv(value) + } else { + return false, "ERROR: settings validation failed -- password must be provided if clusterURI is defined." + } + + if s.Settings["password"] == "" { + return false, "ERROR: settings validation failed -- password should be set as an env variable. It is currently missing or empty. " + } + } + + // certificates + if s.Certificates != nil && len(s.Certificates) != 0 { + _, ok1 := s.Settings["clusterURI"] + _, ok2 := s.Certificates["caCrt"] + _, ok3 := s.Certificates["caKey"] + if ok1 && (!ok2 || !ok3) { + return false, "ERROR: certifications validation failed -- You want me to connect to your cluster for you " + + "but have not given me the cert/key to do so. Please add [caCrt] and [caKey] under Certifications. You might also need to provide [clientCrt]." + } else if ok1 { + for key, value := range s.Certificates { + r, path := isValidCert(value) + if !r { + return false, "ERROR: certifications validation failed -- [ " + key + " ] must be a valid S3 or GCS bucket URL or a valid relative file path." + } + s.Certificates[key] = path + } + } else { + log.Println("INFO: certificates provided but not needed. Skipping certificates validation.") + } + + } else { + if _, ok := s.Settings["clusterURI"]; ok { + return false, "ERROR: certifications validation failed -- You want me to connect to your cluster for you " + + "but have not given me the cert/key to do so. Please add [caCrt] and [caKey] under Certifications. You might also need to provide [clientCrt]." + } + } + + // namespaces + if nsOverride == "" { + if s.Namespaces == nil || len(s.Namespaces) == 0 { + return false, "ERROR: namespaces validation failed -- I need at least one namespace " + + "to work with!" + } + + for k, v := range s.Namespaces { + if !v.InstallTiller && k != "kube-system" { + log.Println("INFO: naemspace validation -- Tiller is not desired to be deployed in namespace [ " + k + " ].") + } else { + if tillerTLSEnabled(k) { + // validating the TLS certs and keys for Tiller + // if they are valid, their values (if they are env vars) are substituted + var ok1, ok2, ok3, ok4, ok5 bool + ok1, v.CaCert = isValidCert(v.CaCert) + ok2, v.ClientCert = isValidCert(v.ClientCert) + ok3, v.ClientKey = isValidCert(v.ClientKey) + ok4, v.TillerCert = isValidCert(v.TillerCert) + ok5, v.TillerKey = isValidCert(v.TillerKey) + if !ok1 || !ok2 || !ok3 || !ok4 || !ok5 { + return false, "ERROR: namespaces validation failed -- some certs/keys are not valid for Tiller TLS in namespace [ " + k + " ]." + } + log.Println("INFO: namespace validation -- Tiller is desired to be deployed with TLS in namespace [ " + k + " ]. ") + } else { + log.Println("INFO: namespace validation -- Tiller is desired to be deployed WITHOUT TLS in namespace [ " + k + " ]. ") + } + } + } + } else { + log.Println("INFO: ns-override is used to override all namespaces with [ " + nsOverride + " ] Skipping defined namespaces validation.") + } + + // repos + if s.HelmRepos == nil || len(s.HelmRepos) == 0 { + return false, "ERROR: repos validation failed -- I need at least one helm repo " + + "to work with!" + } + for k, v := range s.HelmRepos { + _, err := url.ParseRequestURI(v) + if err != nil { + return false, "ERROR: repos validation failed -- repo [" + k + " ] " + + "must have a valid URL." + } + + continue + + } + + // apps + if s.Apps == nil { + log.Println("INFO: You have not specified any apps. I have nothing to do. ", + "Horraayyy!.") + os.Exit(0) + } + + names := make(map[string]bool) + for appLabel, r := range s.Apps { + result, errMsg := validateRelease(r, names, s) + if !result { + return false, "ERROR: apps validation failed -- for app [" + appLabel + " ]. " + errMsg + } + } + + return true, "" +} + +// substitueEnv checks if a string is an env variable (starts with '$'), then it returns its value +// if the env variable is empty or unset, an empty string is returned +// if the string does not start with '$', it is returned as is. +func subsituteEnv(name string) string { + if strings.Contains(name, "$") { + return os.ExpandEnv(name) + } + return name } // isValidCert checks if a certificate/key path/URI is valid func isValidCert(value string) (bool, string) { - tmp := os.ExpandEnv(value) - _, err1 := url.ParseRequestURI(tmp) - _, err2 := os.Stat(tmp) - if err2 != nil && (err1 != nil || (!strings.HasPrefix(tmp, "s3://") && !strings.HasPrefix(tmp, "gs://"))) { - return false, "" - } - return true, tmp + tmp := subsituteEnv(value) + _, err1 := url.ParseRequestURI(tmp) + _, err2 := os.Stat(tmp) + if err2 != nil && (err1 != nil || (!strings.HasPrefix(tmp, "s3://") && !strings.HasPrefix(tmp, "gs://"))) { + return false, "" + } + return true, tmp } // tillerTLSEnabled checks if Tiller is desired to be deployed with TLS enabled for a given namespace // TLS is considered desired ONLY if all certs and keys for both Tiller and the Helm client are defined. func tillerTLSEnabled(namespace string) bool { - ns := s.Namespaces[namespace] - if ns.CaCert != "" && ns.TillerCert != "" && ns.TillerKey != "" && ns.ClientCert != "" && ns.ClientKey != "" { - return true - } - return false + ns := s.Namespaces[namespace] + if ns.CaCert != "" && ns.TillerCert != "" && ns.TillerKey != "" && ns.ClientCert != "" && ns.ClientKey != "" { + return true + } + return false } // print prints the desired state func (s state) print() { - fmt.Println("\nMetadata: ") - fmt.Println("--------- ") - printMap(s.Metadata) - fmt.Println("\nCertificates: ") - fmt.Println("--------- ") - printMap(s.Certificates) - fmt.Println("\nSettings: ") - fmt.Println("--------- ") - printMap(s.Settings) - fmt.Println("\nNamespaces: ") - fmt.Println("------------- ") - printNamespacesMap(s.Namespaces) - fmt.Println("\nRepositories: ") - fmt.Println("------------- ") - printMap(s.HelmRepos) - fmt.Println("\nApplications: ") - fmt.Println("--------------- ") - for _, r := range s.Apps { - r.print() - } + fmt.Println("\nMetadata: ") + fmt.Println("--------- ") + printMap(s.Metadata) + fmt.Println("\nCertificates: ") + fmt.Println("--------- ") + printMap(s.Certificates) + fmt.Println("\nSettings: ") + fmt.Println("--------- ") + printMap(s.Settings) + fmt.Println("\nNamespaces: ") + fmt.Println("------------- ") + printNamespacesMap(s.Namespaces) + fmt.Println("\nRepositories: ") + fmt.Println("------------- ") + printMap(s.HelmRepos) + fmt.Println("\nApplications: ") + fmt.Println("--------------- ") + for _, r := range s.Apps { + r.print() + } } - diff --git a/utils.go b/utils.go index f1037a7a..1ad40fb9 100644 --- a/utils.go +++ b/utils.go @@ -1,208 +1,220 @@ package main import ( - "bytes" - "fmt" - "io" - "io/ioutil" - "log" - "os" - "path/filepath" - "strconv" - "strings" - - "github.com/BurntSushi/toml" - "github.com/Praqma/helmsman/aws" - "github.com/Praqma/helmsman/gcs" + "bytes" + "fmt" + "io" + "io/ioutil" + "log" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/BurntSushi/toml" + "github.com/Praqma/helmsman/aws" + "github.com/Praqma/helmsman/gcs" ) // printMap prints to the console any map of string keys and values. func printMap(m map[string]string) { - for key, value := range m { - fmt.Println(key, " : ", value) - } + for key, value := range m { + fmt.Println(key, " : ", value) + } } // printObjectMap prints to the console any map of string keys and object values. func printNamespacesMap(m map[string]namespace) { - for key, value := range m { - fmt.Println(key, " : protected = ", value) - } + for key, value := range m { + fmt.Println(key, " : protected = ", value) + } } // fromTOML reads a toml file and decodes it to a state type. // It uses the BurntSuchi TOML parser which throws an error if the TOML file is not valid. func fromTOML(file string, s *state) (bool, string) { - if _, err := toml.DecodeFile(file, s); err != nil { - return false, err.Error() - } - return true, "INFO: Parsed [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps." + if _, err := toml.DecodeFile(file, s); err != nil { + return false, err.Error() + } + return true, "INFO: Parsed [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps." } // toTOML encodes a state type into a TOML file. // It uses the BurntSuchi TOML parser. func toTOML(file string, s *state) { - log.Println("printing generated toml ... ") - var buff bytes.Buffer - var ( - newFile *os.File - err error - ) - - if err := toml.NewEncoder(&buff).Encode(s); err != nil { - log.Fatal(err) - os.Exit(1) - } - newFile, err = os.Create(file) - if err != nil { - log.Fatal(err) - } - bytesWritten, err := newFile.Write(buff.Bytes()) - if err != nil { - log.Fatal(err) - } - log.Printf("Wrote %d bytes.\n", bytesWritten) - newFile.Close() + log.Println("printing generated toml ... ") + var buff bytes.Buffer + var ( + newFile *os.File + err error + ) + + if err := toml.NewEncoder(&buff).Encode(s); err != nil { + log.Fatal(err) + os.Exit(1) + } + newFile, err = os.Create(file) + if err != nil { + log.Fatal(err) + } + bytesWritten, err := newFile.Write(buff.Bytes()) + if err != nil { + log.Fatal(err) + } + log.Printf("Wrote %d bytes.\n", bytesWritten) + newFile.Close() } // isOfType checks if the file extension of a filename/path is the same as "filetype". // isisOfType is case insensitive. filetype should contain the "." e.g. ".yaml" func isOfType(filename string, filetype string) bool { - return filepath.Ext(strings.ToLower(filename)) == strings.ToLower(filetype) + return filepath.Ext(strings.ToLower(filename)) == strings.ToLower(filetype) } // readFile returns the content of a file as a string. // takes a file path as input. It throws an error and breaks the program execution if it failes to read the file. func readFile(filepath string) string { - data, err := ioutil.ReadFile(filepath) - if err != nil { - log.Fatal("ERROR: failed to read [ " + filepath + " ] file content: " + err.Error()) - } - return string(data) + data, err := ioutil.ReadFile(filepath) + if err != nil { + log.Fatal("ERROR: failed to read [ " + filepath + " ] file content: " + err.Error()) + } + return string(data) } // printHelp prints Helmsman commands func printHelp() { - fmt.Println("Helmsman version: " + version) - fmt.Println("Helmsman is a Helm Charts as Code tool which allows you to automate the deployment/management of your Helm charts.") - fmt.Println("Usage: helmsman [options]") - fmt.Println() - fmt.Println("Options:") - fmt.Println("--f specifies the desired state TOML file.") - fmt.Println("--debug prints basic logs during execution.") - fmt.Println("--apply generates and applies an action plan.") - fmt.Println("--verbose prints more verbose logs during execution.") - fmt.Println("--ns-override override defined namespaces with a provided one.") - fmt.Println("--skip-validation generates and applies an action plan.") - fmt.Println("--help prints Helmsman help.") - fmt.Println("--v prints Helmsman version.") + fmt.Println("Helmsman version: " + version) + fmt.Println("Helmsman is a Helm Charts as Code tool which allows you to automate the deployment/management of your Helm charts.") + fmt.Println("Usage: helmsman [options]") + fmt.Println() + fmt.Println("Options:") + fmt.Println("--f specifies the desired state TOML file.") + fmt.Println("--debug prints basic logs during execution.") + fmt.Println("--apply generates and applies an action plan.") + fmt.Println("--verbose prints more verbose logs during execution.") + fmt.Println("--ns-override override defined namespaces with a provided one.") + fmt.Println("--skip-validation generates and applies an action plan.") + fmt.Println("--help prints Helmsman help.") + fmt.Println("--v prints Helmsman version.") } // logVersions prints the versions of kubectl and helm to the logs func logVersions() { - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "kubectl version"}, - Description: "Kubectl version: ", - } - - exitCode, result := cmd.exec(debug, false) - if exitCode != 0 { - log.Fatal("ERROR: while checking kubectl version: " + result) - } - - log.Println("VERBOSE: kubectl version: \n " + result + "\n") - - cmd = command{ - Cmd: "bash", - Args: []string{"-c", "helm version"}, - Description: "Helm version: ", - } - - exitCode, result = cmd.exec(debug, false) - if exitCode != 0 { - log.Fatal("ERROR: while checking helm version: " + result) - } - log.Println("VERBOSE: helm version: \n" + result + "\n") + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "kubectl version"}, + Description: "Kubectl version: ", + } + + exitCode, result := cmd.exec(debug, false) + if exitCode != 0 { + log.Fatal("ERROR: while checking kubectl version: " + result) + } + + log.Println("VERBOSE: kubectl version: \n " + result + "\n") + + cmd = command{ + Cmd: "bash", + Args: []string{"-c", "helm version"}, + Description: "Helm version: ", + } + + exitCode, result = cmd.exec(debug, false) + if exitCode != 0 { + log.Fatal("ERROR: while checking helm version: " + result) + } + log.Println("VERBOSE: helm version: \n" + result + "\n") +} + +// envVarExists checks if an environment variable is set or not and returns it. +// empty string is returned for unset env vars +// it accepts env var with/without '$' at the beginning +func envVarExists(v string) (bool, string) { + + if strings.HasPrefix(v, "$") { + v = strings.SplitAfter(v, "$")[1] + } + + value, ok := os.LookupEnv(v) + return ok, value } // sliceContains checks if a string slice contains a given string func sliceContains(slice []string, s string) bool { - for _, a := range slice { - if strings.TrimSpace(a) == s { - return true - } - } - return false + for _, a := range slice { + if strings.TrimSpace(a) == s { + return true + } + } + return false } // validateServiceAccount checks if k8s service account exists in a given namespace func validateServiceAccount(sa string, namespace string) (bool, string) { - if namespace == "" { - namespace = "default" - } - ns := " -n " + namespace - - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "kubectl get serviceaccount " + sa + ns}, - Description: "validating that serviceaccount [ " + sa + " ] exists in namespace [ " + namespace + " ].", - } - - if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { - return false, err - } - return true, "" + if namespace == "" { + namespace = "default" + } + ns := " -n " + namespace + + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "kubectl get serviceaccount " + sa + ns}, + Description: "validating that serviceaccount [ " + sa + " ] exists in namespace [ " + namespace + " ].", + } + + if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { + return false, err + } + return true, "" } // downloadFile downloads a file from GCS or AWS buckets and name it with a given outfile // if downloaded, returns the outfile name. If the file path is local file system path, it is returned as is. func downloadFile(path string, outfile string) string { - if strings.HasPrefix(path, "s3") { + if strings.HasPrefix(path, "s3") { - tmp := getBucketElements(path) - aws.ReadFile(tmp["bucketName"], tmp["filePath"], outfile) + tmp := getBucketElements(path) + aws.ReadFile(tmp["bucketName"], tmp["filePath"], outfile) - } else if strings.HasPrefix(path, "gs") { + } else if strings.HasPrefix(path, "gs") { - tmp := getBucketElements(path) - gcs.ReadFile(tmp["bucketName"], tmp["filePath"], outfile) + tmp := getBucketElements(path) + gcs.ReadFile(tmp["bucketName"], tmp["filePath"], outfile) - } else { + } else { - log.Println("INFO: " + outfile + " will be used from local file system.") - copyFile(path, outfile) - } - return outfile + log.Println("INFO: " + outfile + " will be used from local file system.") + copyFile(path, outfile) + } + return outfile } // copyFile copies a file from source to destination func copyFile(source string, destination string) { - from, err := os.Open(source) - if err != nil { - log.Fatal("ERROR: while copying " + source + " to " + destination + " : " + err.Error()) - } - defer from.Close() - - to, err := os.OpenFile(destination, os.O_RDWR|os.O_CREATE, 0666) - if err != nil { - log.Fatal("ERROR: while copying " + source + " to " + destination + " : " + err.Error()) - } - defer to.Close() - - _, err = io.Copy(to, from) - if err != nil { - log.Fatal("ERROR: while copying " + source + " to " + destination + " : " + err.Error()) - } + from, err := os.Open(source) + if err != nil { + log.Fatal("ERROR: while copying " + source + " to " + destination + " : " + err.Error()) + } + defer from.Close() + + to, err := os.OpenFile(destination, os.O_RDWR|os.O_CREATE, 0666) + if err != nil { + log.Fatal("ERROR: while copying " + source + " to " + destination + " : " + err.Error()) + } + defer to.Close() + + _, err = io.Copy(to, from) + if err != nil { + log.Fatal("ERROR: while copying " + source + " to " + destination + " : " + err.Error()) + } } // deleteFile deletes a file func deleteFile(path string) { - log.Println("INFO: cleaning up ... deleting " + path) - if err := os.Remove(path); err != nil { - log.Fatal("ERROR: could not delete file: " + path) - } + log.Println("INFO: cleaning up ... deleting " + path) + if err := os.Remove(path); err != nil { + log.Fatal("ERROR: could not delete file: " + path) + } } - From 4da7513d746c602c6e6e870d8fd03311af37a0d1 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Tue, 5 Jun 2018 14:01:01 +0200 Subject: [PATCH 0168/1127] preventing goreleaser from trying to release when tag sha does not match commit sha --- .circleci/config.yml | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7d64dc01..2434d57c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -39,11 +39,18 @@ jobs: - run: name: Release helmsman command: | - echo "fetching dependencies ..." - go get github.com/Praqma/helmsman/gcs - go get github.com/Praqma/helmsman/aws - echo "releasing ..." - goreleaser --release-notes release-notes.md + TAG_SHA=git rev-parse $(git describe --abbrev=0 --tags) + LAST_COMMIT=$(git rev-parse HEAD) + if [ "${TAG_SHA}" == "${LAST_COMMIT}" ]; then + echo "fetching dependencies ..." + go get github.com/Praqma/helmsman/gcs + go get github.com/Praqma/helmsman/aws + echo "releasing ..." + goreleaser --release-notes release-notes.md + else + echo "No release is needed yet." + exit 0 + fi # - setup_remote_docker # - run: From 5715a73e4ebdbba840357330707ea7c77646e219 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Tue, 5 Jun 2018 14:09:59 +0200 Subject: [PATCH 0169/1127] fixing shell typos in circleci config --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2434d57c..c1479234 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -39,7 +39,7 @@ jobs: - run: name: Release helmsman command: | - TAG_SHA=git rev-parse $(git describe --abbrev=0 --tags) + TAG_SHA=$(git rev-parse $(git describe --abbrev=0 --tags)) LAST_COMMIT=$(git rev-parse HEAD) if [ "${TAG_SHA}" == "${LAST_COMMIT}" ]; then echo "fetching dependencies ..." From b653dff3b38cceb35922fde3d8156f5c1c58c720 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Tue, 5 Jun 2018 14:55:07 +0200 Subject: [PATCH 0170/1127] fixes #37 --- decision_maker.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/decision_maker.go b/decision_maker.go index 0a4fc968..39b5420a 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -55,7 +55,8 @@ func decide(r *release, s *state) { if !isProtected(r) { - reInstallRelease(getDesiredNamespace(r), r) // re-install failed release + logDecision("DECISION: release [ "+r.Name+" ] is in FAILED state. I will upgrade it for you. Hope it gets fixed!", r.Priority) + upgradeRelease(r) } else { logDecision("DECISION: release "+r.Name+" is PROTECTED. Operations are not allowed on this release until "+ From eb240a8d794ffd169d9111a4eb6feac6ca4e733b Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Tue, 5 Jun 2018 14:58:06 +0200 Subject: [PATCH 0171/1127] updating some docs with the latest tag --- README.md | 2 +- docs/desired_state_specification.md | 2 +- release-notes.md | 17 +++++------------ 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 34163594..e01acb94 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ --- -version: v1.2.0-rc +version: v1.2.0 --- ![helmsman-logo](docs/images/helmsman.png) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 14f4008d..29dcd915 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -1,5 +1,5 @@ --- -version: v1.2.0-rc +version: v1.2.0 --- # Helmsman desired state specification diff --git a/release-notes.md b/release-notes.md index 83ec37f4..729a194f 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,13 +1,6 @@ -# v1.2.0-rc +# v1.2.0 -This release focuses on improving Helmsman latency and supporting multi-tenant clusters. - -- Up to 7x faster than previous version. -- Introducing the `--skip-validation` flag which skips validating the desired state. -- Support for multi-tenant k8s clusters through: - - Supporting deployment of Tiller in several namespaces with different service accounts - - Supporting securing Tillers with TLS. - - Supporting using `Secrets` as a storage background instead of configMaps. - - Upgrading rolledback releases automatically after rollback to avoid missing changed values. - - More concise logs. - - Several minor enhancements. \ No newline at end of file +- Stable TLS and multitenancy support. +- Support for multiple values files when deploying charts. +- Fix environment variables names issue #35 +- Fix #37 \ No newline at end of file From f340075405c0cc15c01db44e46f4a87014bc75c1 Mon Sep 17 00:00:00 2001 From: Evan Locke Date: Wed, 6 Jun 2018 10:18:04 -0600 Subject: [PATCH 0172/1127] Update README.md to 1.2.0 release Docs were not updated to reflect final 1.2.0 release. --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e01acb94..93d76baa 100644 --- a/README.md +++ b/README.md @@ -45,9 +45,9 @@ To show debugging details: Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. ``` # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v1.2.0-rc/helmsman_1.2.0-rc_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.2.0/helmsman_1.2.0_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v1.2.0-rc/helmsman_1.2.0-rc_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.2.0/helmsman_1.2.0_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` @@ -84,4 +84,4 @@ Helmsman can be used in three different settings: # Contributing -Pull requests, feeback/feature requests are welcome. Please check our [contribution guide](CONTRIBUTION.md). \ No newline at end of file +Pull requests, feeback/feature requests are welcome. Please check our [contribution guide](CONTRIBUTION.md). From dac20db93705a148069e744b6c7706efac19e255 Mon Sep 17 00:00:00 2001 From: Evan Locke Date: Wed, 6 Jun 2018 10:23:43 -0600 Subject: [PATCH 0173/1127] Bumping to 1.2.0 final. --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 47b5ef16..532db1c2 100644 --- a/main.go +++ b/main.go @@ -19,7 +19,7 @@ var verbose bool var nsOverride string var checkCleanup bool var skipValidation bool -var version = "v1.2.0-rc2" +var version = "v1.2.0" func main() { From 87642bf98d90417b6baaa2431b1eb411ba1524e9 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Tue, 12 Jun 2018 18:38:50 +0200 Subject: [PATCH 0174/1127] allowing non-env var values in DSF set #40 --- docs/desired_state_specification.md | 4 ++-- example.toml | 3 ++- main.go | 2 +- release.go | 8 -------- utils.go | 7 +++++++ 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 29dcd915..ca192808 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -176,7 +176,7 @@ Options: - protected : defines if the release should be protected against changes. Namespace-level protection has higher priority than this flag. Check the [protection guide](how_to/protect_namespaces_and_releases.md) for more details. - wait : defines whether helmsman should block execution until all k8s resources are in a ready state. Default is false. - priority : defines the priority of applying operations on this release. Only negative values allowed and the lower the value, the higher the priority. Default priority is 0. Apps with equal priorities will be applied in the order they were added in your state file (DSF). -- [apps..set] : is used to override certain values from values.yaml with values from environment variables. This is particularily useful for passing secrets to charts. +- [apps..set] : is used to override certain values from values.yaml with values from environment variables (or ,starting from v1.3.0-rc, directly provided in the Desired State File). This is particularily useful for passing secrets to charts. If the an environment variable with the same name as the provided value exists, the environment variable value will be used, otherwise, the provided value will be used as is. Example: @@ -200,7 +200,7 @@ Example: priority = -3 [apps.jenkins.set] secret1="$SECRET_ENV_VAR1" - secret2="$SECRET_ENV_VAR2" + secret2="SECRET_ENV_VAR2" # works with/without $ at the beginning ``` diff --git a/example.toml b/example.toml index a6dd09f1..81c1fbdf 100644 --- a/example.toml +++ b/example.toml @@ -69,8 +69,9 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" protected = true priority= -3 wait = true - # [apps.jenkins.set] # values to override values from values.yaml with values from env vars-- useful for passing secrets to charts + # [apps.jenkins.set] # values to override values from values.yaml with values from env vars or directly entered-- useful for passing secrets to charts # AdminPassword="$JENKINS_PASSWORD" # $JENKINS_PASSWORD must exist in the environment + # AdminUser="admin" # artifactory will be deployed using the Tiller in the kube-system namespace diff --git a/main.go b/main.go index 532db1c2..8e3994d9 100644 --- a/main.go +++ b/main.go @@ -19,7 +19,7 @@ var verbose bool var nsOverride string var checkCleanup bool var skipValidation bool -var version = "v1.2.0" +var version = "v1.3.0-rc" func main() { diff --git a/release.go b/release.go index c559b6d3..681ed18b 100644 --- a/release.go +++ b/release.go @@ -50,14 +50,6 @@ func validateRelease(r *release, names map[string]bool, s state) (bool, string) return false, "the value for valueFile '" + filePath + "' must be a valid file path for a yaml file." } } - } else if len(r.Set) > 0 { - for k, v := range r.Set { - if !strings.HasPrefix(v, "$") { - return false, "the value for variable [ " + k + " ] must be an environment variable name and start with '$'." - } else if ok, _ := envVarExists(v); !ok { - return false, "env variable [ " + v + " ] is not found in the environment." - } - } } else if r.Priority != 0 && r.Priority > 0 { return false, "priority can only be 0 or negative value, positive values are not allowed." } diff --git a/utils.go b/utils.go index 1ad40fb9..80029143 100644 --- a/utils.go +++ b/utils.go @@ -131,6 +131,7 @@ func logVersions() { // envVarExists checks if an environment variable is set or not and returns it. // empty string is returned for unset env vars // it accepts env var with/without '$' at the beginning +// if an env var 'v' does not exist, 'v' is returned as the value func envVarExists(v string) (bool, string) { if strings.HasPrefix(v, "$") { @@ -138,6 +139,12 @@ func envVarExists(v string) (bool, string) { } value, ok := os.LookupEnv(v) + + // return the value as is if no env var with that key is set. + if !ok { + return ok, v + } + return ok, value } From f8c71930ee9877252eb2bf70d012b627b074162b Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Tue, 12 Jun 2018 19:14:04 +0200 Subject: [PATCH 0175/1127] updating docs and release notes for v1.3.0-rc --- README.md | 2 +- docs/desired_state_specification.md | 2 +- docs/how_to/define_namespaces.md | 2 +- docs/how_to/manipulate_apps.md | 2 +- docs/how_to/move_charts_across_namespaces.md | 2 +- docs/how_to/multiple_value_files.md | 2 +- docs/how_to/multitenant_clusters_guide.md | 2 +- docs/how_to/override_defined_namespaces.md | 2 +- docs/how_to/pass_secrets_from_env_variables.md | 2 +- docs/how_to/protect_namespaces_and_releases.md | 2 +- docs/how_to/run_helmsman_in_ci.md | 2 +- docs/how_to/run_helmsman_with_hosted_cluster.md | 2 +- docs/how_to/run_helmsman_with_minikube.md | 2 +- docs/how_to/test_charts.md | 2 +- docs/how_to/use_local_charts.md | 2 +- docs/how_to/use_private_helm_charts.md | 2 +- docs/how_to/use_the_priority_key.md | 2 +- release-notes.md | 8 +++----- 18 files changed, 20 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index f3ee6672..9f222759 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ --- -version: v1.2.0 +version: v1.3.0-rc --- ![helmsman-logo](docs/images/helmsman.png) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index c7835184..9ec1cb17 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -1,5 +1,5 @@ --- -version: v1.2.0 +version: v1.3.0-rc --- # Helmsman desired state specification diff --git a/docs/how_to/define_namespaces.md b/docs/how_to/define_namespaces.md index a145c14b..f4eb2bb9 100644 --- a/docs/how_to/define_namespaces.md +++ b/docs/how_to/define_namespaces.md @@ -1,5 +1,5 @@ --- -version: v1.2.0-rc +version: v1.3.0-rc --- # define namespaces diff --git a/docs/how_to/manipulate_apps.md b/docs/how_to/manipulate_apps.md index 79ab40a2..b36445fb 100644 --- a/docs/how_to/manipulate_apps.md +++ b/docs/how_to/manipulate_apps.md @@ -1,5 +1,5 @@ --- -version: v1.2.0-rc +version: v1.3.0-rc --- # install releases diff --git a/docs/how_to/move_charts_across_namespaces.md b/docs/how_to/move_charts_across_namespaces.md index db1c0f16..3a0cf858 100644 --- a/docs/how_to/move_charts_across_namespaces.md +++ b/docs/how_to/move_charts_across_namespaces.md @@ -1,5 +1,5 @@ --- -version: v1.2.0-rc +version: v1.3.0-rc --- # move charts across namespaces diff --git a/docs/how_to/multiple_value_files.md b/docs/how_to/multiple_value_files.md index b69565b3..689e8f4e 100644 --- a/docs/how_to/multiple_value_files.md +++ b/docs/how_to/multiple_value_files.md @@ -1,5 +1,5 @@ --- -version: v1.2.0-rc +version: v1.3.0-rc --- # multiple value files diff --git a/docs/how_to/multitenant_clusters_guide.md b/docs/how_to/multitenant_clusters_guide.md index c7363c44..710e09db 100644 --- a/docs/how_to/multitenant_clusters_guide.md +++ b/docs/how_to/multitenant_clusters_guide.md @@ -1,5 +1,5 @@ --- -version: v1.2.0-rc +version: v1.3.0-rc --- # Multitenant Clusters Guide diff --git a/docs/how_to/override_defined_namespaces.md b/docs/how_to/override_defined_namespaces.md index 4c2ce09c..c85426d7 100644 --- a/docs/how_to/override_defined_namespaces.md +++ b/docs/how_to/override_defined_namespaces.md @@ -1,5 +1,5 @@ --- -version: v1.1.0 +version: v1.3.0-rc --- # Override defined namespaces from command line diff --git a/docs/how_to/pass_secrets_from_env_variables.md b/docs/how_to/pass_secrets_from_env_variables.md index 89c77226..757e0b9a 100644 --- a/docs/how_to/pass_secrets_from_env_variables.md +++ b/docs/how_to/pass_secrets_from_env_variables.md @@ -1,5 +1,5 @@ --- -version: v1.2.0-rc +version: v1.3.0-rc --- # pass secrets from env. variables: diff --git a/docs/how_to/protect_namespaces_and_releases.md b/docs/how_to/protect_namespaces_and_releases.md index e94522a9..4b66097d 100644 --- a/docs/how_to/protect_namespaces_and_releases.md +++ b/docs/how_to/protect_namespaces_and_releases.md @@ -1,5 +1,5 @@ --- -version: v1.2.0-rc +version: v1.3.0-rc --- # Namespace and Release Protection diff --git a/docs/how_to/run_helmsman_in_ci.md b/docs/how_to/run_helmsman_in_ci.md index 930e90be..fc1e6bfc 100644 --- a/docs/how_to/run_helmsman_in_ci.md +++ b/docs/how_to/run_helmsman_in_ci.md @@ -1,5 +1,5 @@ --- -version: v1.2.0-rc +version: v1.3.0-rc --- # Run Helmsman in CI diff --git a/docs/how_to/run_helmsman_with_hosted_cluster.md b/docs/how_to/run_helmsman_with_hosted_cluster.md index 7673f1fd..047155e6 100644 --- a/docs/how_to/run_helmsman_with_hosted_cluster.md +++ b/docs/how_to/run_helmsman_with_hosted_cluster.md @@ -1,5 +1,5 @@ --- -version: v1.2.0-rc +version: v1.3.0-rc --- You can manage Helm charts deployment on a hosted K8S cluster in the cloud or on-prem. You need to include the required information to connect to the cluster in your state file. diff --git a/docs/how_to/run_helmsman_with_minikube.md b/docs/how_to/run_helmsman_with_minikube.md index 64ee3256..adf5741f 100644 --- a/docs/how_to/run_helmsman_with_minikube.md +++ b/docs/how_to/run_helmsman_with_minikube.md @@ -1,5 +1,5 @@ --- -version: v1.2.0-rc +version: v1.3.0-rc --- You can run Helmsman locally as a binary application with Minikube, you just need to skip all the cluster connection settings in your desired state file. Below is the example.toml desired state file adapted to work with Minikube. diff --git a/docs/how_to/test_charts.md b/docs/how_to/test_charts.md index ccd2dca0..3c12d0c2 100644 --- a/docs/how_to/test_charts.md +++ b/docs/how_to/test_charts.md @@ -1,5 +1,5 @@ --- -version: v1.2.0-rc +version: v1.3.0-rc --- # test charts diff --git a/docs/how_to/use_local_charts.md b/docs/how_to/use_local_charts.md index 2d7a6d95..cc9c0574 100644 --- a/docs/how_to/use_local_charts.md +++ b/docs/how_to/use_local_charts.md @@ -1,5 +1,5 @@ --- -version: v1.2.0-rc +version: v1.3.0-rc --- # use local helm charts diff --git a/docs/how_to/use_private_helm_charts.md b/docs/how_to/use_private_helm_charts.md index ed418227..4ff2e0bd 100644 --- a/docs/how_to/use_private_helm_charts.md +++ b/docs/how_to/use_private_helm_charts.md @@ -1,5 +1,5 @@ --- -version: v1.2.0-rc +version: v1.3.0-rc --- # use private helm charts diff --git a/docs/how_to/use_the_priority_key.md b/docs/how_to/use_the_priority_key.md index 26877757..b1ce82c6 100644 --- a/docs/how_to/use_the_priority_key.md +++ b/docs/how_to/use_the_priority_key.md @@ -1,5 +1,5 @@ --- -version: v1.2.0-rc +version: v1.3.0-rc --- # Using the priority key for Apps diff --git a/release-notes.md b/release-notes.md index 729a194f..3541d466 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,6 +1,4 @@ -# v1.2.0 +# v1.3.0-rc -- Stable TLS and multitenancy support. -- Support for multiple values files when deploying charts. -- Fix environment variables names issue #35 -- Fix #37 \ No newline at end of file +- Support for using YAML for desired state files. +- Support for passing values directly through the `set` flag in the desired state file. \ No newline at end of file From 4b0558179026b8c434219083868dd8c49498af72 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Tue, 12 Jun 2018 19:27:27 +0200 Subject: [PATCH 0176/1127] updating installation link [ci skip] --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9f222759..3b985f8f 100644 --- a/README.md +++ b/README.md @@ -46,9 +46,9 @@ To show debugging details: Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. ``` # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v1.2.0/helmsman_1.2.0_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.3.0-rc/helmsman_1.3.0-rc_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v1.2.0/helmsman_1.2.0_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.3.0-rc/helmsman_1.3.0-rc_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` From f7d8d4abf4fd7c8c63ff4298c70b1f7b4a0eb0f3 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Wed, 11 Jul 2018 14:05:31 +0200 Subject: [PATCH 0177/1127] fixing typos --- decision_maker.go | 10 +++++----- init.go | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index 39b5420a..b027672b 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -120,7 +120,7 @@ func installRelease(namespace string, r *release) { // rollbackRelease evaluates if a rollback action needs to be taken for a given release. // if the release is already deleted but from a different namespace than the one specified in input, -// it purge deletes it and create it in the spcified namespace. +// it purge deletes it and create it in the specified namespace. func rollbackRelease(namespace string, r *release) { releaseName := r.Name @@ -149,7 +149,7 @@ func rollbackRelease(namespace string, r *release) { } // inspectDeleteScenario evaluates if a delete action needs to be taken for a given release. -// If the purge flage is set to true for the release in question, then it will be permenantly removed. +// If the purge flag is set to true for the release in question, then it will be permanently removed. // If the release is not deployed, it will be skipped. func inspectDeleteScenario(namespace string, r *release) { @@ -183,9 +183,9 @@ func deleteRelease(r *release) { } // inspectUpgradeScenario evaluates if a release should be upgraded. -// - If the relase is already in the same namespace specified in the input, +// - If the release is already in the same namespace specified in the input, // it will be upgraded using the values file specified in the release info. -// - If the relase is already in the same namespace specified in the input but is using a different chart, +// - If the release is already in the same namespace specified in the input but is using a different chart, // it will be purge deleted and installed in the same namespace using the new chart. // - If the release is NOT in the same namespace specified in the input, // it will be purge deleted and installed in the new namespace. @@ -354,7 +354,7 @@ func getCurrentTillerNamespace(r *release) string { } // getTLSFlags returns TLS flags with which a release is maintained -// If the release where the namespace is to be deployed has Tiller deployed, the TLS flages will use certs/keys for that namespace (if any) +// If the release where the namespace is to be deployed has Tiller deployed, the TLS flags will use certs/keys for that namespace (if any) // otherwise, it will be the certs/keys for the kube-system namespace. func getTLSFlags(r *release) string { tls := "" diff --git a/init.go b/init.go index 574dc36d..3aa7479a 100644 --- a/init.go +++ b/init.go @@ -68,7 +68,7 @@ func init() { log.Fatal(msg) } } else { - log.Println("INFO: desried state validation is skipped.") + log.Println("INFO: desired state validation is skipped.") } } From 1ebe9d6ee812199c447a7eb7126b67463849ed62 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Thu, 12 Jul 2018 15:20:25 +0200 Subject: [PATCH 0178/1127] updating multitenancy guide with RBAC notes fixes #43 --- docs/how_to/multitenant_clusters_guide.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/how_to/multitenant_clusters_guide.md b/docs/how_to/multitenant_clusters_guide.md index 710e09db..7a206cc2 100644 --- a/docs/how_to/multitenant_clusters_guide.md +++ b/docs/how_to/multitenant_clusters_guide.md @@ -1,5 +1,5 @@ --- -version: v1.3.0-rc +version: v1.3.0 --- # Multitenant Clusters Guide @@ -44,7 +44,9 @@ namespaces: ## Deploying Tiller with a service account -You can also deploy each of the Tillers with a different k8s service account Or with a default service account of your choice. +For K8S clusters with RBAC enabled, you will need to initialize Helm with a service account. Check [Helm's RBAC guide](https://github.com/kubernetes/helm/blob/master/docs/rbac.md). + +Helmsman lets you deploy each of the Tillers with a different k8s service account Or with a default service account of your choice. ```toml @@ -93,6 +95,9 @@ namespaces: tillerServiceAccount: "dev2-sa" ``` +> Currently, Helmsman does not create the service accounts and expects them to be available in the namespace before hand. This should be fixed in upcoming releases and you can track it in [this issue](https://github.com/Praqma/helmsman/issues/48) + +> If you don't specify `tillerServiceAccount` option for a namespace, it will try to use the service account you defined in your settings section (`default-tiller-sa` in the example above) In the example above, namespaces `staging, developer1 & developer2` will have Tiller deployed with different service accounts. The `production` namespace ,however, will be deployed using the `default-tiller-sa` service account defined in the `settings` section. If this one is not defined, the production namespace Tiller will be deployed with k8s default service account. From 9720c03f097711724b04a10b2480e482753baffb Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Mon, 16 Jul 2018 14:45:29 +0200 Subject: [PATCH 0179/1127] adding support for sending slack notifications #49 --- example.toml | 1 + example.yaml | 1 + main.go | 13 ++++++----- plan.go | 28 ++++++++++++++++++---- state.go | 16 +++++++++---- utils.go | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++- 6 files changed, 109 insertions(+), 16 deletions(-) diff --git a/example.toml b/example.toml index 81c1fbdf..ded4b121 100644 --- a/example.toml +++ b/example.toml @@ -21,6 +21,7 @@ kubeContext = "minikube" # will try connect to this context first, if it does no ##clusterURI = "https://192.168.99.100:8443" # equivalent to the above #serviceAccount = "foo" # k8s serviceaccount must be already defined, validation error will be thrown otherwise storageBackend = "secret" # default is configMap +#slackWebhook = "$slack" # or "your slack webhook url" # define your environments and their k8s namespaces # syntax: diff --git a/example.yaml b/example.yaml index e0d1e79d..6ff78cdb 100644 --- a/example.yaml +++ b/example.yaml @@ -21,6 +21,7 @@ settings: #clusterURI: "https://192.168.99.100:8443" # equivalent to the above #serviceAccount: "foo" # k8s serviceaccount must be already defined, validation error will be thrown otherwise storageBackend: "secret" # default is configMap + #slackWebhook: "$slack" # or your slack webhook url # define your environments and their k8s namespaces namespaces: diff --git a/main.go b/main.go index 8e3994d9..7e18f042 100644 --- a/main.go +++ b/main.go @@ -19,14 +19,14 @@ var verbose bool var nsOverride string var checkCleanup bool var skipValidation bool -var version = "v1.3.0-rc" +var version = "v1.3.1" func main() { // set the kubecontext to be used Or create it if it does not exist if !setKubeContext(s.Settings["kubeContext"]) { if r, msg := createContext(); !r { - log.Fatal(msg) + logError(msg) } checkCleanup = true } @@ -35,7 +35,7 @@ func main() { addNamespaces(s.Namespaces) if r, msg := initHelm(); !r { - log.Fatal(msg) + logError(msg) } // check if helm Tiller is ready @@ -53,13 +53,13 @@ func main() { // add repos -- fails if they are not valid if r, msg := addHelmRepos(s.HelmRepos); !r { - log.Fatal(msg) + logError(msg) } if !skipValidation { // validate charts-versions exist in defined repos if r, msg := validateReleaseCharts(s.Apps); !r { - log.Fatal(msg) + logError(msg) } } else { log.Println("INFO: charts validation is skipped.") @@ -70,6 +70,7 @@ func main() { p.sortPlan() p.printPlan() + p.sendPlanToSlack() if apply { p.execPlan() @@ -399,7 +400,7 @@ func waitForTiller(namespace string) { } attempt = attempt + 1 } - log.Fatal("ERROR: timeout reached while waiting for helm Tiller to be ready in namespace [ " + namespace + " ]. Aborting!") + logError("ERROR: timeout reached while waiting for helm Tiller to be ready in namespace [ " + namespace + " ]. Aborting!") } // cleanup deletes the k8s certificates and keys files diff --git a/plan.go b/plan.go index 480ac533..eb5b0a67 100644 --- a/plan.go +++ b/plan.go @@ -3,12 +3,14 @@ package main import ( "fmt" "log" + "net/url" "sort" "strconv" + "strings" "time" ) -// orderedDecision type representing a Descsion and it's priority weight +// orderedDecision type representing a Decision and it's priority weight type orderedDecision struct { Description string Priority int @@ -33,7 +35,7 @@ func createPlan() plan { p := plan{ Commands: []orderedCommand{}, Decisions: []orderedDecision{}, - Created: time.Now(), + Created: time.Now().UTC(), } return p } @@ -60,11 +62,14 @@ func (p *plan) addDecision(decision string, priority int) { // execPlan executes the commands (actions) which were added to the plan. func (p plan) execPlan() { p.sortPlan() - log.Println("INFO: Executing the following plan ... ") - p.printPlan() + log.Println("INFO: Executing the plan ... ") for _, cmd := range p.Commands { if exitCode, msg := cmd.Command.exec(debug, verbose); exitCode != 0 { - log.Fatal("Command returned with exit code: " + string(exitCode) + ". And error message: " + msg) + logError("Command returned with exit code: " + string(exitCode) + ". And error message: " + msg) + } else { + if _, err := url.ParseRequestURI(s.Settings["slackWebhook"]); err == nil { + notifySlack(cmd.Command.Description+" ... SUCCESS!", s.Settings["slackWebhook"], false, true) + } } } } @@ -86,6 +91,19 @@ func (p plan) printPlan() { } } +// sendPlanToSlack sends the description of plan commands to slack if a webhook is provided. +func (p plan) sendPlanToSlack() { + if _, err := url.ParseRequestURI(s.Settings["slackWebhook"]); err == nil { + str := "" + for _, c := range p.Commands { + str = str + c.Command.Description + "\n" + } + + notifySlack(strings.TrimRight(str, "\n"), s.Settings["slackWebhook"], false, false) + } + +} + // sortPlan sorts the slices of commands and decisions based on priorities // the lower the priority value the earlier a command should be attempted func (p plan) sortPlan() { diff --git a/state.go b/state.go index d1eb5f36..21c573d9 100644 --- a/state.go +++ b/state.go @@ -44,7 +44,7 @@ func (s state) validate() (bool, string) { s.Settings["clusterURI"] = subsituteEnv(value) if _, err := url.ParseRequestURI(s.Settings["clusterURI"]); err != nil { - return false, "ERROR: settings validation failed -- clusterURI must have a valid URL set in an env varibale or passed directly. Either the env var is missing/empty or the URL is invalid." + return false, "ERROR: settings validation failed -- clusterURI must have a valid URL set in an env variable or passed directly. Either the env var is missing/empty or the URL is invalid." } if _, ok = s.Settings["username"]; !ok { @@ -61,6 +61,14 @@ func (s state) validate() (bool, string) { } } + // slack webhook validation (if provided) + if value, ok := s.Settings["slackWebhook"]; ok { + s.Settings["slackWebhook"] = subsituteEnv(value) + if _, err := url.ParseRequestURI(s.Settings["slackWebhook"]); err != nil { + return false, "ERROR: settings validation failed -- slackWebhook must be a valid URL." + } + } + // certificates if s.Certificates != nil && len(s.Certificates) != 0 { _, ok1 := s.Settings["clusterURI"] @@ -97,7 +105,7 @@ func (s state) validate() (bool, string) { for k, v := range s.Namespaces { if !v.InstallTiller && k != "kube-system" { - log.Println("INFO: naemspace validation -- Tiller is not desired to be deployed in namespace [ " + k + " ].") + log.Println("INFO: namespace validation -- Tiller is not desired to be deployed in namespace [ " + k + " ].") } else { if tillerTLSEnabled(k) { // validating the TLS certs and keys for Tiller @@ -155,9 +163,9 @@ func (s state) validate() (bool, string) { return true, "" } -// substitueEnv checks if a string is an env variable (starts with '$'), then it returns its value +// subsituteEnv checks if a string is an env variable (contains '$'), then it returns its value // if the env variable is empty or unset, an empty string is returned -// if the string does not start with '$', it is returned as is. +// if the string does not contain '$', it is returned as is. func subsituteEnv(name string) string { if strings.Contains(name, "$") { return os.ExpandEnv(name) diff --git a/utils.go b/utils.go index ae1ae0de..c2a33539 100644 --- a/utils.go +++ b/utils.go @@ -6,10 +6,13 @@ import ( "io" "io/ioutil" "log" + "net/http" + "net/url" "os" "path/filepath" "strconv" "strings" + "time" "gopkg.in/yaml.v2" @@ -134,7 +137,7 @@ func isOfType(filename string, filetype string) bool { } // readFile returns the content of a file as a string. -// takes a file path as input. It throws an error and breaks the program execution if it failes to read the file. +// takes a file path as input. It throws an error and breaks the program execution if it fails to read the file. func readFile(filepath string) string { data, err := ioutil.ReadFile(filepath) if err != nil { @@ -285,3 +288,64 @@ func deleteFile(path string) { log.Fatal("ERROR: could not delete file: " + path) } } + +// notifySlack sends a JSON formatted message to Slack over a webhook url +// It takes the content of the message (what changes helmsman is going to do or have done separated by \n) +// and the webhook URL as well as a flag specifying if this is a failure message or not +// It returns true if the sending of the message is successful, otherwise returns false +func notifySlack(content string, url string, failure bool, executing bool) bool { + log.Println("INFO: posting notifications to slack ... ") + + color := "#36a64f" // green + if failure { + color = "#FF0000" // red + } + + var pretext string + if content == "" { + pretext = "No actions to perform!" + } else if failure { + pretext = "Failed to generate/execute a plan: " + } else if executing && !failure { + pretext = "Here is what I have done: " + } else { + pretext = "Here is what I am going to do:" + } + + t := time.Now().UTC() + + var jsonStr = []byte(`{ + "attachments": [ + { + "fallback": "Helmsman results.", + "color": "` + color + `" , + "pretext": "` + pretext + `", + "title": "` + content + `", + + "footer": "Helmsman", + "ts": ` + strconv.FormatInt(t.Unix(), 10) + ` + } + ] + }`) + req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr)) + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + log.Fatal("ERROR: while sending notifications to slack" + err.Error()) + } + defer resp.Body.Close() + + if resp.StatusCode == 200 { + return true + } + return false +} + +func logError(msg string) { + if _, err := url.ParseRequestURI(s.Settings["slackWebhook"]); err == nil { + notifySlack(msg, s.Settings["slackWebhook"], true, apply) + } + log.Fatal(msg) +} From 81b9e441137e955ba3f3106a3bfa95451eac5c19 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Mon, 16 Jul 2018 15:30:26 +0200 Subject: [PATCH 0180/1127] fixing #47 --- example.toml | 2 +- main.go | 11 +++++++---- utils.go | 1 + 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/example.toml b/example.toml index ded4b121..f026ce08 100644 --- a/example.toml +++ b/example.toml @@ -19,7 +19,7 @@ kubeContext = "minikube" # will try connect to this context first, if it does no #password = "$K8S_PASSWORD" # the name of an environment variable containing the k8s password #clusterURI = "$K8S_URI" # the name of an environment variable containing the cluster API ##clusterURI = "https://192.168.99.100:8443" # equivalent to the above -#serviceAccount = "foo" # k8s serviceaccount must be already defined, validation error will be thrown otherwise +#serviceAccount = "tiller" # k8s serviceaccount must be already defined, validation error will be thrown otherwise storageBackend = "secret" # default is configMap #slackWebhook = "$slack" # or "your slack webhook url" diff --git a/main.go b/main.go index 7e18f042..919da5d2 100644 --- a/main.go +++ b/main.go @@ -107,6 +107,7 @@ func setKubeContext(context string) bool { // If no defaultServiceAccount is provided, Tiller is deployed with the namespace default service account // If no namespace is provided, Tiller is deployed to kube-system func deployTiller(namespace string, serviceAccount string, defaultServiceAccount string) (bool, string) { + log.Println("INFO: deploying Tiller in namespace [ " + namespace + " ].") sa := "" if serviceAccount != "" { if ok, err := validateServiceAccount(serviceAccount, namespace); ok { @@ -115,7 +116,11 @@ func deployTiller(namespace string, serviceAccount string, defaultServiceAccount return false, "ERROR: while deploying Helm Tiller in namespace [" + namespace + "]: " + err } } else if defaultServiceAccount != "" { - sa = "--service-account " + defaultServiceAccount + if ok, err := validateServiceAccount(defaultServiceAccount, namespace); ok { + sa = "--service-account " + defaultServiceAccount + } else { + return false, "ERROR: while deploying Helm Tiller in namespace [" + namespace + "]: " + err + } } if namespace == "" { @@ -162,7 +167,6 @@ func initHelm() (bool, string) { } } - log.Println("INFO: deploying shared Tiller on namespace [ kube-system ].") if v, ok := s.Namespaces["kube-system"]; ok { if ok, err := deployTiller("kube-system", v.TillerServiceAccount, defaultSA); !ok { return false, err @@ -175,7 +179,6 @@ func initHelm() (bool, string) { for k, ns := range s.Namespaces { if ns.InstallTiller && k != "kube-system" { - log.Println("INFO: deploying Tiller on namespace [ " + k + " ].") if ok, err := deployTiller(k, ns.TillerServiceAccount, defaultSA); !ok { return false, err } @@ -392,7 +395,7 @@ func waitForTiller(namespace string) { if exitCode == 0 { return } else if strings.Contains(err, "could not find a ready tiller pod") || strings.Contains(err, "could not find tiller") { - log.Println("INFO: waiting for helm Tiller to be ready ...") + log.Println("INFO: waiting for helm Tiller to be ready in namespace [" + namespace + "] ...") time.Sleep(5 * time.Second) exitCode, err = cmd.exec(debug, verbose) } else { diff --git a/utils.go b/utils.go index c2a33539..59fbe408 100644 --- a/utils.go +++ b/utils.go @@ -223,6 +223,7 @@ func sliceContains(slice []string, s string) bool { // validateServiceAccount checks if k8s service account exists in a given namespace func validateServiceAccount(sa string, namespace string) (bool, string) { + log.Println("INFO: validating if service account [" + sa + "] exists in namespace [" + namespace + "]") if namespace == "" { namespace = "default" } From ec0a124a7615763e30c95e3aa9b65266e4aeb27b Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Mon, 16 Jul 2018 15:50:36 +0200 Subject: [PATCH 0181/1127] cleaning main.go by moving function to helm and kube helpers --- helm_helpers.go | 172 +++++++++++++++++++++++++ kube_helpers.go | 166 ++++++++++++++++++++++++ main.go | 327 ------------------------------------------------ utils.go | 34 +++-- 4 files changed, 352 insertions(+), 347 deletions(-) create mode 100644 kube_helpers.go diff --git a/helm_helpers.go b/helm_helpers.go index aac606b5..1cd93720 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -5,6 +5,8 @@ import ( "strconv" "strings" "time" + + "github.com/Praqma/helmsman/gcs" ) var currentState map[string]releaseState @@ -231,3 +233,173 @@ func getNSTLSFlags(ns string) string { } return tls } + +// validateReleaseCharts validates if the charts defined in a release are valid. +// Valid charts are the ones that can be found in the defined repos. +// This function uses Helm search to verify if the chart can be found or not. +func validateReleaseCharts(apps map[string]*release) (bool, string) { + + for app, r := range apps { + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm search " + r.Chart + " --version " + r.Version + " -l"}, + Description: "validating if chart " + r.Chart + "-" + r.Version + " is available in the defined repos.", + } + + if exitCode, result := cmd.exec(debug, verbose); exitCode != 0 || strings.Contains(result, "No results found") { + return false, "ERROR: chart " + r.Chart + "-" + r.Version + " is specified for " + + "app [" + app + "] but is not found in the defined repos." + } + } + return true, "" +} + +// waitForTiller keeps checking if the helm Tiller is ready or not by executing helm list and checking its error (if any) +// waits for 5 seconds before each new attempt and eventually terminates after 10 failed attempts. +func waitForTiller(namespace string) { + + attempt := 0 + + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm list --tiller-namespace " + namespace + getNSTLSFlags(namespace)}, + Description: "checking if helm Tiller is ready in namespace [ " + namespace + " ].", + } + + exitCode, err := cmd.exec(debug, verbose) + + for attempt < 10 { + if exitCode == 0 { + return + } else if strings.Contains(err, "could not find a ready tiller pod") || strings.Contains(err, "could not find tiller") { + log.Println("INFO: waiting for helm Tiller to be ready in namespace [" + namespace + "] ...") + time.Sleep(5 * time.Second) + exitCode, err = cmd.exec(debug, verbose) + } else { + log.Fatal("ERROR: while waiting for helm Tiller to be ready in namespace [ " + namespace + " ] : " + err) + } + attempt = attempt + 1 + } + logError("ERROR: timeout reached while waiting for helm Tiller to be ready in namespace [ " + namespace + " ]. Aborting!") +} + +// addHelmRepos adds repositories to Helm if they don't exist already. +// Helm does not mind if a repo with the same name exists. It treats it as an update. +func addHelmRepos(repos map[string]string) (bool, string) { + + for repoName, url := range repos { + // check if repo is in GCS, then perform GCS auth -- needed for private GCS helm repos + // failed auth would not throw an error here, as it is possible that the repo is public and does not need authentication + if strings.HasPrefix(url, "gs://") { + gcs.Auth() + } + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm repo add " + repoName + " " + url}, + Description: "adding repo " + repoName, + } + + if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { + return false, "ERROR: while adding repo [" + repoName + "]: " + err + } + + } + + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm repo update "}, + Description: "updating helm repos", + } + + if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { + return false, "ERROR: while updating helm repos : " + err + } + + return true, "" +} + +// deployTiller deploys Helm's Tiller in a specific namespace with a serviceAccount +// If serviceAccount is not provided (empty string), the defaultServiceAccount is used. +// If no defaultServiceAccount is provided, Tiller is deployed with the namespace default service account +// If no namespace is provided, Tiller is deployed to kube-system +func deployTiller(namespace string, serviceAccount string, defaultServiceAccount string) (bool, string) { + log.Println("INFO: deploying Tiller in namespace [ " + namespace + " ].") + sa := "" + if serviceAccount != "" { + if ok, err := validateServiceAccount(serviceAccount, namespace); ok { + sa = "--service-account " + serviceAccount + } else { + return false, "ERROR: while deploying Helm Tiller in namespace [" + namespace + "]: " + err + } + } else if defaultServiceAccount != "" { + if ok, err := validateServiceAccount(defaultServiceAccount, namespace); ok { + sa = "--service-account " + defaultServiceAccount + } else { + return false, "ERROR: while deploying Helm Tiller in namespace [" + namespace + "]: " + err + } + } + + if namespace == "" { + namespace = "kube-system" + } + tillerNameSpace := " --tiller-namespace " + namespace + + tls := "" + if tillerTLSEnabled(namespace) { + tillerCert := downloadFile(s.Namespaces[namespace].TillerCert, namespace+"-tiller.cert") + tillerKey := downloadFile(s.Namespaces[namespace].TillerKey, namespace+"-tiller.key") + caCert := downloadFile(s.Namespaces[namespace].CaCert, namespace+"-ca.cert") + // client cert and key + downloadFile(s.Namespaces[namespace].ClientCert, namespace+"-client.cert") + downloadFile(s.Namespaces[namespace].ClientKey, namespace+"-client.key") + tls = " --tiller-tls --tiller-tls-cert " + tillerCert + " --tiller-tls-key " + tillerKey + " --tiller-tls-verify --tls-ca-cert " + caCert + } + + storageBackend := "" + if v, ok := s.Settings["storageBackend"]; ok && v == "secret" { + storageBackend = " --override 'spec.template.spec.containers[0].command'='{/tiller,--storage=secret}'" + } + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm init --upgrade " + sa + tillerNameSpace + tls + storageBackend}, + Description: "initializing helm on the current context and upgrading Tiller on namespace [ " + namespace + " ].", + } + + if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { + return false, "ERROR: while deploying Helm Tiller in namespace [" + namespace + "]: " + err + } + return true, "" +} + +// initHelm initializes helm on a k8s cluster and deploys Tiller in one or more namespaces +func initHelm() (bool, string) { + + defaultSA := "" + if value, ok := s.Settings["serviceAccount"]; ok { + if ok, err := validateServiceAccount(value, "kube-system"); ok { + defaultSA = value + } else { + return false, "ERROR: while validating service account: " + err + } + } + + if v, ok := s.Namespaces["kube-system"]; ok { + if ok, err := deployTiller("kube-system", v.TillerServiceAccount, defaultSA); !ok { + return false, err + } + } else { + if ok, err := deployTiller("kube-system", "", defaultSA); !ok { + return false, err + } + } + + for k, ns := range s.Namespaces { + if ns.InstallTiller && k != "kube-system" { + if ok, err := deployTiller(k, ns.TillerServiceAccount, defaultSA); !ok { + return false, err + } + } + } + + return true, "" +} diff --git a/kube_helpers.go b/kube_helpers.go new file mode 100644 index 00000000..80ba4ae1 --- /dev/null +++ b/kube_helpers.go @@ -0,0 +1,166 @@ +package main + +import ( + "log" +) + +// validateServiceAccount checks if k8s service account exists in a given namespace +func validateServiceAccount(sa string, namespace string) (bool, string) { + log.Println("INFO: validating if service account [" + sa + "] exists in namespace [" + namespace + "]") + if namespace == "" { + namespace = "default" + } + ns := " -n " + namespace + + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "kubectl get serviceaccount " + sa + ns}, + Description: "validating that serviceaccount [ " + sa + " ] exists in namespace [ " + namespace + " ].", + } + + if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { + return false, err + } + return true, "" +} + +// addNamespaces creates a set of namespaces in your k8s cluster. +// If a namespace with the same name exsts, it will skip it. +// If --ns-override flag is used, it only creates the provided namespace in that flag +func addNamespaces(namespaces map[string]namespace) { + if nsOverride == "" { + for ns := range namespaces { + createNamespace(ns) + } + } else { + createNamespace(nsOverride) + overrideAppsNamespace(nsOverride) + } +} + +// overrideAppsNamespace replaces all apps namespaces with one specific namespace +func overrideAppsNamespace(newNs string) { + log.Println("INFO: overriding apps namespaces with [ " + newNs + " ] ...") + for _, r := range s.Apps { + overrideNamespace(r, newNs) + } +} + +// createNamespace creates a namespace in the k8s cluster +func createNamespace(ns string) { + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "kubectl create namespace " + ns}, + Description: "creating namespace " + ns, + } + + if exitCode, _ := cmd.exec(debug, verbose); exitCode != 0 { + log.Println("WARN: I could not create namespace [" + + ns + " ]. It already exists. I am skipping this.") + } +} + +// createContext creates a context -connecting to a k8s cluster- in kubectl config. +// It returns true if successful, false otherwise +func createContext() (bool, string) { + + if s.Settings["password"] == "" || s.Settings["username"] == "" || s.Settings["clusterURI"] == "" { + return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ] " + + "as you did not specify enough information in the Settings section of your desired state file." + } else if s.Certificates == nil || s.Certificates["caCrt"] == "" || s.Certificates["caKey"] == "" { + return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ] " + + "as you did not provide Certifications to use in your desired state file." + } + + // set certs locations (relative filepath, GCS bucket, AWS bucket) + caCrt := s.Certificates["caCrt"] + caKey := s.Certificates["caKey"] + caClient := s.Certificates["caClient"] + + // download certs and keys + // GCS bucket+file format should be: gs://bucket-name/dir.../filename.ext + // S3 bucket+file format should be: s3://bucket-name/dir.../filename.ext + + // CA cert + if caCrt != "" { + + caCrt = downloadFile(caCrt, "ca.crt") + + } + + // CA key + if caKey != "" { + caKey = downloadFile(caKey, "ca.key") + + } + + // client certificate + if caClient != "" { + + caClient = downloadFile(caClient, "client.crt") + + } + + // connecting to the cluster + setCredentialsCmd := "kubectl config set-credentials " + s.Settings["username"] + " --username=" + s.Settings["username"] + + " --password=" + s.Settings["password"] + " --client-key=" + caKey + if caClient != "" { + setCredentialsCmd = setCredentialsCmd + " --client-certificate=" + caClient + } + cmd := command{ + Cmd: "bash", + Args: []string{"-c", setCredentialsCmd}, + Description: "creating kubectl context - setting credentials.", + } + + if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { + return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ]: " + err + } + + cmd = command{ + Cmd: "bash", + Args: []string{"-c", "kubectl config set-cluster " + s.Settings["kubeContext"] + " --server=" + s.Settings["clusterURI"] + + " --certificate-authority=" + caCrt}, + Description: "creating kubectl context - setting cluster.", + } + + if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { + return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ]: " + err + } + + cmd = command{ + Cmd: "bash", + Args: []string{"-c", "kubectl config set-context " + s.Settings["kubeContext"] + " --cluster=" + s.Settings["kubeContext"] + + " --user=" + s.Settings["username"]}, + Description: "creating kubectl context - setting context.", + } + + if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { + return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ]: " + err + } + + if setKubeContext(s.Settings["kubeContext"]) { + return true, "" + } + + return false, "ERROR: something went wrong while setting the kube context to the newly created one." +} + +// setKubeContext sets your kubectl context to the one specified in the desired state file. +// It returns false if it fails to set the context. This means the context does not exist. +func setKubeContext(context string) bool { + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "kubectl config use-context " + context}, + Description: "setting kubectl context to [ " + context + " ]", + } + + exitCode, _ := cmd.exec(debug, verbose) + + if exitCode != 0 { + log.Println("INFO: KubeContext: " + context + " does not exist. I will try to create it.") + return false + } + + return true +} diff --git a/main.go b/main.go index 919da5d2..70ab1344 100644 --- a/main.go +++ b/main.go @@ -3,10 +3,6 @@ package main import ( "log" "os" - "strings" - "time" - - "github.com/Praqma/helmsman/gcs" ) var s state @@ -83,329 +79,6 @@ func main() { log.Println("INFO: completed successfully!") } -// setKubeContext sets your kubectl context to the one specified in the desired state file. -// It returns false if it fails to set the context. This means the context does not exist. -func setKubeContext(context string) bool { - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "kubectl config use-context " + context}, - Description: "setting kubectl context to [ " + context + " ]", - } - - exitCode, _ := cmd.exec(debug, verbose) - - if exitCode != 0 { - log.Println("INFO: KubeContext: " + context + " does not exist. I will try to create it.") - return false - } - - return true -} - -// deployTiller deploys Helm's Tiller in a specific namespace with a serviceAccount -// If serviceAccount is not provided (empty string), the defaultServiceAccount is used. -// If no defaultServiceAccount is provided, Tiller is deployed with the namespace default service account -// If no namespace is provided, Tiller is deployed to kube-system -func deployTiller(namespace string, serviceAccount string, defaultServiceAccount string) (bool, string) { - log.Println("INFO: deploying Tiller in namespace [ " + namespace + " ].") - sa := "" - if serviceAccount != "" { - if ok, err := validateServiceAccount(serviceAccount, namespace); ok { - sa = "--service-account " + serviceAccount - } else { - return false, "ERROR: while deploying Helm Tiller in namespace [" + namespace + "]: " + err - } - } else if defaultServiceAccount != "" { - if ok, err := validateServiceAccount(defaultServiceAccount, namespace); ok { - sa = "--service-account " + defaultServiceAccount - } else { - return false, "ERROR: while deploying Helm Tiller in namespace [" + namespace + "]: " + err - } - } - - if namespace == "" { - namespace = "kube-system" - } - tillerNameSpace := " --tiller-namespace " + namespace - - tls := "" - if tillerTLSEnabled(namespace) { - tillerCert := downloadFile(s.Namespaces[namespace].TillerCert, namespace+"-tiller.cert") - tillerKey := downloadFile(s.Namespaces[namespace].TillerKey, namespace+"-tiller.key") - caCert := downloadFile(s.Namespaces[namespace].CaCert, namespace+"-ca.cert") - // client cert and key - downloadFile(s.Namespaces[namespace].ClientCert, namespace+"-client.cert") - downloadFile(s.Namespaces[namespace].ClientKey, namespace+"-client.key") - tls = " --tiller-tls --tiller-tls-cert " + tillerCert + " --tiller-tls-key " + tillerKey + " --tiller-tls-verify --tls-ca-cert " + caCert - } - - storageBackend := "" - if v, ok := s.Settings["storageBackend"]; ok && v == "secret" { - storageBackend = " --override 'spec.template.spec.containers[0].command'='{/tiller,--storage=secret}'" - } - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm init --upgrade " + sa + tillerNameSpace + tls + storageBackend}, - Description: "initializing helm on the current context and upgrading Tiller on namespace [ " + namespace + " ].", - } - - if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { - return false, "ERROR: while deploying Helm Tiller in namespace [" + namespace + "]: " + err - } - return true, "" -} - -// initHelm initializes helm on a k8s cluster and deploys Tiller in one or more namespaces -func initHelm() (bool, string) { - - defaultSA := "" - if value, ok := s.Settings["serviceAccount"]; ok { - if ok, err := validateServiceAccount(value, "kube-system"); ok { - defaultSA = value - } else { - return false, "ERROR: while validating service account: " + err - } - } - - if v, ok := s.Namespaces["kube-system"]; ok { - if ok, err := deployTiller("kube-system", v.TillerServiceAccount, defaultSA); !ok { - return false, err - } - } else { - if ok, err := deployTiller("kube-system", "", defaultSA); !ok { - return false, err - } - } - - for k, ns := range s.Namespaces { - if ns.InstallTiller && k != "kube-system" { - if ok, err := deployTiller(k, ns.TillerServiceAccount, defaultSA); !ok { - return false, err - } - } - } - - return true, "" -} - -// addHelmRepos adds repositories to Helm if they don't exist already. -// Helm does not mind if a repo with the same name exists. It treats it as an update. -func addHelmRepos(repos map[string]string) (bool, string) { - - for repoName, url := range repos { - // check if repo is in GCS, then perform GCS auth -- needed for private GCS helm repos - // failed auth would not throw an error here, as it is possible that the repo is public and does not need authentication - if strings.HasPrefix(url, "gs://") { - gcs.Auth() - } - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm repo add " + repoName + " " + url}, - Description: "adding repo " + repoName, - } - - if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { - return false, "ERROR: while adding repo [" + repoName + "]: " + err - } - - } - - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm repo update "}, - Description: "updating helm repos", - } - - if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { - return false, "ERROR: while updating helm repos : " + err - } - - return true, "" -} - -// validateReleaseCharts validates if the charts defined in a release are valid. -// Valid charts are the ones that can be found in the defined repos. -// This function uses Helm search to verify if the chart can be found or not. -func validateReleaseCharts(apps map[string]*release) (bool, string) { - - for app, r := range apps { - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm search " + r.Chart + " --version " + r.Version + " -l"}, - Description: "validating if chart " + r.Chart + "-" + r.Version + " is available in the defined repos.", - } - - if exitCode, result := cmd.exec(debug, verbose); exitCode != 0 || strings.Contains(result, "No results found") { - return false, "ERROR: chart " + r.Chart + "-" + r.Version + " is specified for " + - "app [" + app + "] but is not found in the defined repos." - } - } - return true, "" -} - -// addNamespaces creates a set of namespaces in your k8s cluster. -// If a namespace with the same name exsts, it will skip it. -// If --ns-override flag is used, it only creates the provided namespace in that flag -func addNamespaces(namespaces map[string]namespace) { - if nsOverride == "" { - for ns := range namespaces { - createNamespace(ns) - } - } else { - createNamespace(nsOverride) - overrideAppsNamespace(nsOverride) - } -} - -// overrideAppsNamespace replaces all apps namespaces with one specific namespace -func overrideAppsNamespace(newNs string) { - log.Println("INFO: overriding apps namespaces with [ " + newNs + " ] ...") - for _, r := range s.Apps { - overrideNamespace(r, newNs) - } -} - -// createNamespace creates a namespace in the k8s cluster -func createNamespace(ns string) { - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "kubectl create namespace " + ns}, - Description: "creating namespace " + ns, - } - - if exitCode, _ := cmd.exec(debug, verbose); exitCode != 0 { - log.Println("WARN: I could not create namespace [" + - ns + " ]. It already exists. I am skipping this.") - } -} - -// createContext creates a context -connecting to a k8s cluster- in kubectl config. -// It returns true if successful, false otherwise -func createContext() (bool, string) { - - if s.Settings["password"] == "" || s.Settings["username"] == "" || s.Settings["clusterURI"] == "" { - return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ] " + - "as you did not specify enough information in the Settings section of your desired state file." - } else if s.Certificates == nil || s.Certificates["caCrt"] == "" || s.Certificates["caKey"] == "" { - return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ] " + - "as you did not provide Certifications to use in your desired state file." - } - - // set certs locations (relative filepath, GCS bucket, AWS bucket) - caCrt := s.Certificates["caCrt"] - caKey := s.Certificates["caKey"] - caClient := s.Certificates["caClient"] - - // download certs and keys - // GCS bucket+file format should be: gs://bucket-name/dir.../filename.ext - // S3 bucket+file format should be: s3://bucket-name/dir.../filename.ext - - // CA cert - if caCrt != "" { - - caCrt = downloadFile(caCrt, "ca.crt") - - } - - // CA key - if caKey != "" { - caKey = downloadFile(caKey, "ca.key") - - } - - // client certificate - if caClient != "" { - - caClient = downloadFile(caClient, "client.crt") - - } - - // connecting to the cluster - setCredentialsCmd := "kubectl config set-credentials " + s.Settings["username"] + " --username=" + s.Settings["username"] + - " --password=" + s.Settings["password"] + " --client-key=" + caKey - if caClient != "" { - setCredentialsCmd = setCredentialsCmd + " --client-certificate=" + caClient - } - cmd := command{ - Cmd: "bash", - Args: []string{"-c", setCredentialsCmd}, - Description: "creating kubectl context - setting credentials.", - } - - if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { - return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ]: " + err - } - - cmd = command{ - Cmd: "bash", - Args: []string{"-c", "kubectl config set-cluster " + s.Settings["kubeContext"] + " --server=" + s.Settings["clusterURI"] + - " --certificate-authority=" + caCrt}, - Description: "creating kubectl context - setting cluster.", - } - - if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { - return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ]: " + err - } - - cmd = command{ - Cmd: "bash", - Args: []string{"-c", "kubectl config set-context " + s.Settings["kubeContext"] + " --cluster=" + s.Settings["kubeContext"] + - " --user=" + s.Settings["username"]}, - Description: "creating kubectl context - setting context.", - } - - if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { - return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ]: " + err - } - - if setKubeContext(s.Settings["kubeContext"]) { - return true, "" - } - - return false, "ERROR: something went wrong while setting the kube context to the newly created one." -} - -// getBucketElements returns a map containing the bucket name and the file path inside the bucket -// this func works for S3 and GCS bucket links of the format: -// s3 or gs://bucketname/dir.../file.ext -func getBucketElements(link string) map[string]string { - - tmp := strings.SplitAfterN(link, "//", 2)[1] - m := make(map[string]string) - m["bucketName"] = strings.SplitN(tmp, "/", 2)[0] - m["filePath"] = strings.SplitN(tmp, "/", 2)[1] - return m -} - -// waitForTiller keeps checking if the helm Tiller is ready or not by executing helm list and checking its error (if any) -// waits for 5 seconds before each new attempt and eventually terminates after 10 failed attempts. -func waitForTiller(namespace string) { - - attempt := 0 - - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm list --tiller-namespace " + namespace + getNSTLSFlags(namespace)}, - Description: "checking if helm Tiller is ready in namespace [ " + namespace + " ].", - } - - exitCode, err := cmd.exec(debug, verbose) - - for attempt < 10 { - if exitCode == 0 { - return - } else if strings.Contains(err, "could not find a ready tiller pod") || strings.Contains(err, "could not find tiller") { - log.Println("INFO: waiting for helm Tiller to be ready in namespace [" + namespace + "] ...") - time.Sleep(5 * time.Second) - exitCode, err = cmd.exec(debug, verbose) - } else { - log.Fatal("ERROR: while waiting for helm Tiller to be ready in namespace [ " + namespace + " ] : " + err) - } - attempt = attempt + 1 - } - logError("ERROR: timeout reached while waiting for helm Tiller to be ready in namespace [ " + namespace + " ]. Aborting!") -} - // cleanup deletes the k8s certificates and keys files // It also deletes any Tiller TLS certs and keys func cleanup() { diff --git a/utils.go b/utils.go index 59fbe408..abe5360e 100644 --- a/utils.go +++ b/utils.go @@ -85,6 +85,7 @@ func fromYAML(file string, s *state) (bool, string) { return true, "INFO: Parsed YAML [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps." } +// toYaml encodes a state type into a YAML file func toYAML(file string, s *state) { log.Println("printing generated yaml ... ") var buff bytes.Buffer @@ -221,26 +222,6 @@ func sliceContains(slice []string, s string) bool { return false } -// validateServiceAccount checks if k8s service account exists in a given namespace -func validateServiceAccount(sa string, namespace string) (bool, string) { - log.Println("INFO: validating if service account [" + sa + "] exists in namespace [" + namespace + "]") - if namespace == "" { - namespace = "default" - } - ns := " -n " + namespace - - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "kubectl get serviceaccount " + sa + ns}, - Description: "validating that serviceaccount [ " + sa + " ] exists in namespace [ " + namespace + " ].", - } - - if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { - return false, err - } - return true, "" -} - // downloadFile downloads a file from GCS or AWS buckets and name it with a given outfile // if downloaded, returns the outfile name. If the file path is local file system path, it is returned as is. func downloadFile(path string, outfile string) string { @@ -344,9 +325,22 @@ func notifySlack(content string, url string, failure bool, executing bool) bool return false } +// logError sends a notification on slack if a webhook URL is provided and logs the error before terminating. func logError(msg string) { if _, err := url.ParseRequestURI(s.Settings["slackWebhook"]); err == nil { notifySlack(msg, s.Settings["slackWebhook"], true, apply) } log.Fatal(msg) } + +// getBucketElements returns a map containing the bucket name and the file path inside the bucket +// this func works for S3 and GCS bucket links of the format: +// s3 or gs://bucketname/dir.../file.ext +func getBucketElements(link string) map[string]string { + + tmp := strings.SplitAfterN(link, "//", 2)[1] + m := make(map[string]string) + m["bucketName"] = strings.SplitN(tmp, "/", 2)[0] + m["filePath"] = strings.SplitN(tmp, "/", 2)[1] + return m +} From c2267ad3ec433b77001aa6600255bd3814b52e89 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Tue, 17 Jul 2018 13:07:51 +0200 Subject: [PATCH 0182/1127] fixing #48 --- bindata.go | 218 ++++++++++++++++++++++++++++++++++++++++++++++++ data/role.yaml | 9 ++ helm_helpers.go | 40 ++++++--- kube_helpers.go | 115 ++++++++++++++++++++++++- utils.go | 13 +++ 5 files changed, 381 insertions(+), 14 deletions(-) create mode 100644 bindata.go create mode 100644 data/role.yaml diff --git a/bindata.go b/bindata.go new file mode 100644 index 00000000..6369278b --- /dev/null +++ b/bindata.go @@ -0,0 +1,218 @@ +// Code generated by go-bindata. +// sources: +// data/role.yaml +// DO NOT EDIT! + +package main + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" +) +type asset struct { + bytes []byte + info os.FileInfo +} + +type bindataFileInfo struct { + name string + size int64 + mode os.FileMode + modTime time.Time +} + +func (fi bindataFileInfo) Name() string { + return fi.name +} +func (fi bindataFileInfo) Size() int64 { + return fi.size +} +func (fi bindataFileInfo) Mode() os.FileMode { + return fi.mode +} +func (fi bindataFileInfo) ModTime() time.Time { + return fi.modTime +} +func (fi bindataFileInfo) IsDir() bool { + return false +} +func (fi bindataFileInfo) Sys() interface{} { + return nil +} + +var _dataRoleYaml = []byte(`kind: Role +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: helmsman-tiller + namespace: <> +rules: +- apiGroups: ["", "extensions", "apps"] + resources: ["*"] + verbs: ["*"]`) + +func dataRoleYamlBytes() ([]byte, error) { + return _dataRoleYaml, nil +} + +func dataRoleYaml() (*asset, error) { + bytes, err := dataRoleYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "data/role.yaml", size: 198, mode: os.FileMode(420), modTime: time.Unix(1531758991, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +// Asset loads and returns the asset for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func Asset(name string) ([]byte, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) + } + return a.bytes, nil + } + return nil, fmt.Errorf("Asset %s not found", name) +} + +// MustAsset is like Asset but panics when Asset would return an error. +// It simplifies safe initialization of global variables. +func MustAsset(name string) []byte { + a, err := Asset(name) + if err != nil { + panic("asset: Asset(" + name + "): " + err.Error()) + } + + return a +} + +// AssetInfo loads and returns the asset info for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func AssetInfo(name string) (os.FileInfo, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) + } + return a.info, nil + } + return nil, fmt.Errorf("AssetInfo %s not found", name) +} + +// AssetNames returns the names of the assets. +func AssetNames() []string { + names := make([]string, 0, len(_bindata)) + for name := range _bindata { + names = append(names, name) + } + return names +} + +// _bindata is a table, holding each asset generator, mapped to its name. +var _bindata = map[string]func() (*asset, error){ + "data/role.yaml": dataRoleYaml, +} + +// AssetDir returns the file names below a certain +// directory embedded in the file by go-bindata. +// For example if you run go-bindata on data/... and data contains the +// following hierarchy: +// data/ +// foo.txt +// img/ +// a.png +// b.png +// then AssetDir("data") would return []string{"foo.txt", "img"} +// AssetDir("data/img") would return []string{"a.png", "b.png"} +// AssetDir("foo.txt") and AssetDir("notexist") would return an error +// AssetDir("") will return []string{"data"}. +func AssetDir(name string) ([]string, error) { + node := _bintree + if len(name) != 0 { + cannonicalName := strings.Replace(name, "\\", "/", -1) + pathList := strings.Split(cannonicalName, "/") + for _, p := range pathList { + node = node.Children[p] + if node == nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + } + } + if node.Func != nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + rv := make([]string, 0, len(node.Children)) + for childName := range node.Children { + rv = append(rv, childName) + } + return rv, nil +} + +type bintree struct { + Func func() (*asset, error) + Children map[string]*bintree +} +var _bintree = &bintree{nil, map[string]*bintree{ + "data": &bintree{nil, map[string]*bintree{ + "role.yaml": &bintree{dataRoleYaml, map[string]*bintree{}}, + }}, +}} + +// RestoreAsset restores an asset under the given directory +func RestoreAsset(dir, name string) error { + data, err := Asset(name) + if err != nil { + return err + } + info, err := AssetInfo(name) + if err != nil { + return err + } + err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) + if err != nil { + return err + } + err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) + if err != nil { + return err + } + err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) + if err != nil { + return err + } + return nil +} + +// RestoreAssets restores an asset under the given directory recursively +func RestoreAssets(dir, name string) error { + children, err := AssetDir(name) + // File + if err != nil { + return RestoreAsset(dir, name) + } + // Dir + for _, child := range children { + err = RestoreAssets(dir, filepath.Join(name, child)) + if err != nil { + return err + } + } + return nil +} + +func _filePath(dir, name string) string { + cannonicalName := strings.Replace(name, "\\", "/", -1) + return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) +} + diff --git a/data/role.yaml b/data/role.yaml new file mode 100644 index 00000000..59ad77ab --- /dev/null +++ b/data/role.yaml @@ -0,0 +1,9 @@ +kind: Role +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: helmsman-tiller + namespace: <> +rules: +- apiGroups: ["", "extensions", "apps"] + resources: ["*"] + verbs: ["*"] \ No newline at end of file diff --git a/helm_helpers.go b/helm_helpers.go index 1cd93720..3f600fc2 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -325,18 +325,36 @@ func addHelmRepos(repos map[string]string) (bool, string) { func deployTiller(namespace string, serviceAccount string, defaultServiceAccount string) (bool, string) { log.Println("INFO: deploying Tiller in namespace [ " + namespace + " ].") sa := "" + sharedTiller := false + if namespace == "kube-system" && serviceAccount == "" { + sharedTiller = true + } if serviceAccount != "" { - if ok, err := validateServiceAccount(serviceAccount, namespace); ok { - sa = "--service-account " + serviceAccount - } else { - return false, "ERROR: while deploying Helm Tiller in namespace [" + namespace + "]: " + err + if ok, err := validateServiceAccount(serviceAccount, namespace); !ok { + if strings.Contains(err, "NotFound") || strings.Contains(err, "not found") { + + log.Println("INFO: service account [ " + serviceAccount + " ] does not exist in namespace [ " + namespace + " ] .. attempting to create it ... ") + if _, rbacErr := createRBAC(serviceAccount, namespace, sharedTiller); rbacErr != "" { + return false, rbacErr + } + } else { + return false, "ERROR: while validating/creating service account [ " + serviceAccount + " ] in namespace [" + namespace + "]: " + err + } } + sa = "--service-account " + serviceAccount } else if defaultServiceAccount != "" { - if ok, err := validateServiceAccount(defaultServiceAccount, namespace); ok { - sa = "--service-account " + defaultServiceAccount - } else { - return false, "ERROR: while deploying Helm Tiller in namespace [" + namespace + "]: " + err + if ok, err := validateServiceAccount(defaultServiceAccount, namespace); !ok { + if strings.Contains(err, "NotFound") || strings.Contains(err, "not found") { + + log.Println("INFO: service account [ " + defaultServiceAccount + " ] does not exist in namespace [ " + namespace + " ] .. attempting to create it ... ") + if _, rbacErr := createRBAC(defaultServiceAccount, namespace, sharedTiller); rbacErr != "" { + return false, rbacErr + } + } else { + return false, "ERROR: while validating/creating service account [ " + defaultServiceAccount + " ] in namespace [ " + namespace + "]: " + err + } } + sa = "--service-account " + defaultServiceAccount } if namespace == "" { @@ -376,11 +394,7 @@ func initHelm() (bool, string) { defaultSA := "" if value, ok := s.Settings["serviceAccount"]; ok { - if ok, err := validateServiceAccount(value, "kube-system"); ok { - defaultSA = value - } else { - return false, "ERROR: while validating service account: " + err - } + defaultSA = value } if v, ok := s.Namespaces["kube-system"]; ok { diff --git a/kube_helpers.go b/kube_helpers.go index 80ba4ae1..5efa6be8 100644 --- a/kube_helpers.go +++ b/kube_helpers.go @@ -5,6 +5,8 @@ import ( ) // validateServiceAccount checks if k8s service account exists in a given namespace +// if the provided namespace is empty, it checks in the "default" namespace +// if the serviceaccount does not exist, it will be created func validateServiceAccount(sa string, namespace string) (bool, string) { log.Println("INFO: validating if service account [" + sa + "] exists in namespace [" + namespace + "]") if namespace == "" { @@ -20,12 +22,44 @@ func validateServiceAccount(sa string, namespace string) (bool, string) { if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { return false, err + // if strings.Contains(err, "NotFound") || strings.Contains(err, "not found") { + // log.Println("INFO: service account [ " + sa + " ] does not exist in namespace [ " + namespace + " ] .. attempting to create it ... ") + + // if _, rbacErr := createRBAC(sa, namespace); rbacErr != "" { + // return false, rbacErr + // } + // return true, "" + + // } + // return false, err } return true, "" } +func createRBAC(sa string, namespace string, sharedTiller bool) (bool, string) { + var ok bool + var err string + if ok, err = createServiceAccount(sa, namespace); ok { + if sharedTiller { + if ok, err = createRoleBinding("cluster-admin", sa, namespace); ok { + return true, "" + } + return false, err + } + if ok, err = createRole(namespace); ok { + if ok, err = createRoleBinding("helmsman-tiller", sa, namespace); ok { + return true, "" + } + return false, err + } + + return false, err + } + return false, err +} + // addNamespaces creates a set of namespaces in your k8s cluster. -// If a namespace with the same name exsts, it will skip it. +// If a namespace with the same name exists, it will skip it. // If --ns-override flag is used, it only creates the provided namespace in that flag func addNamespaces(namespaces map[string]namespace) { if nsOverride == "" { @@ -164,3 +198,82 @@ func setKubeContext(context string) bool { return true } + +// createServiceAccount creates a service account in a given namespace and associates it with a cluster-admin role +func createServiceAccount(saName string, namespace string) (bool, string) { + log.Println("INFO: creating service account [ " + saName + " ] in namespace [ " + namespace + " ]") + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "kubectl create serviceaccount -n " + namespace + " " + saName}, + Description: "creating service account [ " + saName + " ] in namespace [ " + namespace + " ]", + } + + exitCode, err := cmd.exec(debug, verbose) + + if exitCode != 0 { + //logError("ERROR: failed to create service account " + saName + " in namespace [ " + namespace + " ]: " + err) + return false, err + } + + return true, "" +} + +func createRoleBinding(role string, saName string, namespace string) (bool, string) { + clusterRole := false + resource := "rolebinding" + if role == "cluster-admin" { + clusterRole = true + resource = "clusterrolebinding" + } + + bindingOption := "--role=" + role + if clusterRole { + bindingOption = "--clusterrole=" + role + } + + log.Println("INFO: creating " + resource + " for service account [ " + saName + " ] in namespace [ " + namespace + " ] with role: " + role) + + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "kubectl create " + resource + " " + saName + "-binding " + bindingOption + " --serviceaccount " + namespace + ":" + saName + " -n " + namespace}, + Description: "creating " + resource + " for [ " + saName + " ] in namespace [ " + namespace + " ]", + } + + exitCode, err := cmd.exec(debug, verbose) + + if exitCode != 0 { + //logError("ERROR: failed to bind service account " + saName + " in namespace [ " + namespace + " ] to role " + role + " : " + err) + return false, err + } + + return true, "" +} + +func createRole(namespace string) (bool, string) { + + // load static resource + resource, e := Asset("data/role.yaml") + if e != nil { + logError(e.Error()) + } + replaceStringInFile(resource, "temp-modified-role.yaml", map[string]string{"<>": namespace}) + + log.Println("INFO: creating role [helmsman-tiller] in namespace [ " + namespace + " ] ") + + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "kubectl apply -f temp-modified-role.yaml "}, + Description: "creating role [helmsman-tiller] in namespace [ " + namespace + " ]", + } + + exitCode, err := cmd.exec(debug, verbose) + + if exitCode != 0 { + //logError("ERROR: failed to create Tiller role in namespace [ " + namespace + " ]: " + err) + return false, err + } + + deleteFile("temp-modified-role.yaml") + + return true, "" +} diff --git a/utils.go b/utils.go index abe5360e..baeba82f 100644 --- a/utils.go +++ b/utils.go @@ -344,3 +344,16 @@ func getBucketElements(link string) map[string]string { m["filePath"] = strings.SplitN(tmp, "/", 2)[1] return m } + +// replaceStringInFile takes a map of keys and values and replaces the keys with values within a given file. +// It saves the modified content in a new file +func replaceStringInFile(input []byte, outfile string, replacements map[string]string) { + output := input + for k, v := range replacements { + output = bytes.Replace(output, []byte(k), []byte(v), -1) + } + + if err := ioutil.WriteFile(outfile, output, 0666); err != nil { + logError(err.Error()) + } +} From cf92f897738fc45df1c3935b01c30372ebf9ee9d Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Tue, 17 Jul 2018 16:43:50 +0200 Subject: [PATCH 0183/1127] fixes #50 --- helm_helpers.go | 2 +- release.go | 18 ++++++++++++++---- state.go | 2 +- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/helm_helpers.go b/helm_helpers.go index 3f600fc2..55c48bc6 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -33,7 +33,7 @@ func getAllReleases() string { return result } -// getTillerReleases gets releases deployed with a given Tiller (in agiven namespace) +// getTillerReleases gets releases deployed with a given Tiller (in a given namespace) func getTillerReleases(tillerNS string) string { cmd := command{ Cmd: "bash", diff --git a/release.go b/release.go index 681ed18b..354a556e 100644 --- a/release.go +++ b/release.go @@ -27,10 +27,12 @@ type release struct { // validateRelease validates if a release inside a desired state meets the specifications or not. // check the full specification @ https://github.com/Praqma/helmsman/docs/desired_state_spec.md -func validateRelease(r *release, names map[string]bool, s state) (bool, string) { +func validateRelease(r *release, names map[string]map[string]bool, s state) (bool, string) { _, err := os.Stat(r.ValuesFile) - if r.Name == "" || names[r.Name] { - return false, "release name can't be empty and must be unique." + if r.Name == "" { + return false, "release name can't be empty." + } else if (s.Namespaces[r.Namespace].InstallTiller && names[r.Name][r.Namespace]) || (!s.Namespaces[r.Namespace].InstallTiller && names[r.Name]["kube-system"]) { + return false, "release name must be unique within a given namespace." } else if nsOverride == "" && r.Namespace == "" { return false, "release targeted namespace can't be empty." } else if nsOverride == "" && r.Namespace != "" && !checkNamespaceDefined(r.Namespace, s) { @@ -54,7 +56,15 @@ func validateRelease(r *release, names map[string]bool, s state) (bool, string) return false, "priority can only be 0 or negative value, positive values are not allowed." } - names[r.Name] = true + if names[r.Name] == nil { + names[r.Name] = make(map[string]bool) + } + if s.Namespaces[r.Namespace].InstallTiller { + names[r.Name][r.Namespace] = true + } else { + names[r.Name]["kube-system"] = true + } + return true, "" } diff --git a/state.go b/state.go index 21c573d9..c35fdacb 100644 --- a/state.go +++ b/state.go @@ -152,7 +152,7 @@ func (s state) validate() (bool, string) { os.Exit(0) } - names := make(map[string]bool) + names := make(map[string]map[string]bool) for appLabel, r := range s.Apps { result, errMsg := validateRelease(r, names, s) if !result { From e392702af69427f209030e47ff3a4761275881f2 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Tue, 24 Jul 2018 13:49:49 +0200 Subject: [PATCH 0184/1127] #32 adding --apply-labels option --- init.go | 8 ++++++++ utils.go | 7 ++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/init.go b/init.go index 3aa7479a..5b799fb1 100644 --- a/init.go +++ b/init.go @@ -29,6 +29,7 @@ func init() { flag.BoolVar(&verbose, "verbose", false, "show verbose execution logs") flag.StringVar(&nsOverride, "ns-override", "", "override defined namespaces with this one") flag.BoolVar(&skipValidation, "skip-validation", false, "skip desired state validation") + flag.BoolVar(&applyLabels, "apply-labels", false, "apply Helmsman labels to Helm state for all defined apps.") flag.Parse() @@ -71,6 +72,13 @@ func init() { log.Println("INFO: desired state validation is skipped.") } + if applyLabels { + for _, r := range s.Apps { + + labelResource(r) + } + } + } // toolExists returns true if the tool is present in the environment and false otherwise. diff --git a/utils.go b/utils.go index baeba82f..dda0dfa6 100644 --- a/utils.go +++ b/utils.go @@ -154,14 +154,15 @@ func printHelp() { fmt.Println("Usage: helmsman [options]") fmt.Println() fmt.Println("Options:") - fmt.Println("--f specifies the desired state TOML file.") + fmt.Println("-f specifies the desired state TOML file.") fmt.Println("--debug prints basic logs during execution.") fmt.Println("--apply generates and applies an action plan.") fmt.Println("--verbose prints more verbose logs during execution.") - fmt.Println("--ns-override override defined namespaces with a provided one.") + fmt.Println("--ns-override overrides defined namespaces with a provided one.") fmt.Println("--skip-validation generates and applies an action plan.") + fmt.Println("--apply-labels applies Helmsman labels to Helm state for all defined apps.") fmt.Println("--help prints Helmsman help.") - fmt.Println("--v prints Helmsman version.") + fmt.Println("-v prints Helmsman version.") } // logVersions prints the versions of kubectl and helm to the logs From d6e96f97143b135672108ec9daa2253e8dd81d5f Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Tue, 24 Jul 2018 13:52:02 +0200 Subject: [PATCH 0185/1127] removing some duplicate logs --- command.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/command.go b/command.go index 1873bbc0..a44e4dff 100644 --- a/command.go +++ b/command.go @@ -9,7 +9,7 @@ import ( ) // command type representing all executable commands Helmsman needs -// to execute in order to inspect the environement/ releases/ charts etc. +// to execute in order to inspect the environment/ releases/ charts etc. type command struct { Cmd string Args []string @@ -31,7 +31,7 @@ func (c command) printFullCommand() { // exec executes the executable command and returns the exit code and execution result func (c command) exec(debug bool, verbose bool) (int, string) { - if debug || verbose { + if debug { log.Println("INFO: " + c.Description) } if verbose { From 4798b39eef027d92d583dbd77899d0903e4c314e Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Tue, 24 Jul 2018 14:02:14 +0200 Subject: [PATCH 0186/1127] improvements for multitenant clusters. fixes #32 #50 --- decision_maker.go | 220 +++++++++++++++++++++------------------------- helm_helpers.go | 139 ++++++++++++++++------------- kube_helpers.go | 84 +++++++++++++++--- main.go | 5 +- plan.go | 17 ++-- release.go | 44 ++++++---- 6 files changed, 295 insertions(+), 214 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index b027672b..4cdf5520 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -25,61 +25,54 @@ func decide(r *release, s *state) { // check for deletion if !r.Enabled { - if !isProtected(r) { - inspectDeleteScenario(getDesiredNamespace(r), r) - } else { - logDecision("DECISION: release "+r.Name+" is PROTECTED. Operations are not allowed on this release until "+ - "you remove its protection.", r.Priority) - } + if ok, rs := helmReleaseExists(r, ""); ok { + if !isProtected(r, rs) { + + // delete it + deleteRelease(r, rs) - } else { // check for install/upgrade/rollback - if helmReleaseExists(getDesiredNamespace(r), r.Name, "deployed") { - if !isProtected(r) { - inspectUpgradeScenario(getDesiredNamespace(r), r) // upgrade } else { - logDecision("DECISION: release "+r.Name+" is PROTECTED. Operations are not allowed on this release until "+ + logDecision("DECISION: release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ "you remove its protection.", r.Priority) } + } else { + logDecision("DECISION: release [ "+r.Name+" ] is set to be disabled but is not yet deployed. Skipping.", r.Priority) + } - } else if helmReleaseExists("", r.Name, "deleted") { - if !isProtected(r) { - - rollbackRelease(getDesiredNamespace(r), r) // rollback + } else { // check for install/upgrade/rollback + if ok, rs := helmReleaseExists(r, "deployed"); ok { + if !isProtected(r, rs) { + inspectUpgradeScenario(r, rs) // upgrade or move } else { - logDecision("DECISION: release "+r.Name+" is PROTECTED. Operations are not allowed on this release until "+ + logDecision("DECISION: release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ "you remove its protection.", r.Priority) } - } else if helmReleaseExists("", r.Name, "failed") { - - if !isProtected(r) { + } else if ok, rs := helmReleaseExists(r, "deleted"); ok { + if !isProtected(r, rs) { - logDecision("DECISION: release [ "+r.Name+" ] is in FAILED state. I will upgrade it for you. Hope it gets fixed!", r.Priority) - upgradeRelease(r) + rollbackRelease(r, rs) // rollback } else { - logDecision("DECISION: release "+r.Name+" is PROTECTED. Operations are not allowed on this release until "+ + logDecision("DECISION: release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ "you remove its protection.", r.Priority) } - } else if helmReleaseExists("", r.Name, "") { // not deployed in the desired namespace but deployed elsewhere + } else if ok, rs := helmReleaseExists(r, "failed"); ok { - if !isProtected(r) { + if !isProtected(r, rs) { - reInstallRelease(getDesiredNamespace(r), r) // move the release to a new (the desired) namespace - logDecision("WARNING: moving release [ "+r.Name+" ] from [[ "+getReleaseNamespace(r.Name)+" ]] to [[ "+getDesiredNamespace(r)+ - " ]] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md"+ - " for details if this release uses PV and PVC.", r.Priority) + logDecision("DECISION: release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is in FAILED state. I will upgrade it for you. Hope it gets fixed!", r.Priority) + upgradeRelease(r) } else { - logDecision("DECISION: release "+r.Name+" is PROTECTED. Operations are not allowed on this release until "+ + logDecision("DECISION: release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ "you remove its protection.", r.Priority) } - } else { - installRelease(getDesiredNamespace(r), r) // install a new release + installRelease(r) // install a new release } @@ -92,26 +85,25 @@ func testRelease(r *release) { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm test " + r.Name + getDesiredTillerNamespace(r) + getTLSFlags(r)}, + Args: []string{"-c", "helm test " + r.Name + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r)}, Description: "running tests for release [ " + r.Name + " ]", } - outcome.addCommand(cmd, r.Priority) - logDecision("DECISION: release [ "+r.Name+" ] is required to be tested when installed. Got it!", r.Priority) + outcome.addCommand(cmd, r.Priority, r) + logDecision("DECISION: release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is required to be tested when installed. Got it!", r.Priority) } -// installRelease creates a Helm command to install a particular release in a particular namespace. -func installRelease(namespace string, r *release) { +// installRelease creates a Helm command to install a particular release in a particular namespace using a particular Tiller. +func installRelease(r *release) { - releaseName := r.Name cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm install " + r.Chart + " -n " + releaseName + " --namespace " + namespace + getValuesFiles(r) + " --version " + r.Version + getSetValues(r) + getWait(r) + getDesiredTillerNamespace(r) + getTLSFlags(r)}, - Description: "installing release [ " + releaseName + " ] in namespace [[ " + namespace + " ]]", + Args: []string{"-c", "helm install " + r.Chart + " -n " + r.Name + " --namespace " + r.Namespace + getValuesFiles(r) + " --version " + r.Version + getSetValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r)}, + Description: "installing release [ " + r.Name + " ] in namespace [[ " + r.Namespace + " ]] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } - outcome.addCommand(cmd, r.Priority) - logDecision("DECISION: release [ "+releaseName+" ] is not present in the current k8s context. Will install it in namespace [[ "+ - namespace+" ]]", r.Priority) + outcome.addCommand(cmd, r.Priority, r) + logDecision("DECISION: release [ "+r.Name+" ] is not installed. Will install it in namespace [[ "+ + r.Namespace+" ]] using Tiller in [ "+getDesiredTillerNamespace(r)+" ]", r.Priority) if r.Test { testRelease(r) @@ -121,51 +113,34 @@ func installRelease(namespace string, r *release) { // rollbackRelease evaluates if a rollback action needs to be taken for a given release. // if the release is already deleted but from a different namespace than the one specified in input, // it purge deletes it and create it in the specified namespace. -func rollbackRelease(namespace string, r *release) { +func rollbackRelease(r *release, rs releaseState) { - releaseName := r.Name - if getReleaseNamespace(r.Name) == namespace { + if r.Namespace == rs.Namespace { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm rollback " + releaseName + " " + getReleaseRevision(releaseName, "deleted") + getWait(r) + getDesiredTillerNamespace(r) + getTLSFlags(r)}, - Description: "rolling back release [ " + releaseName + " ]", + Args: []string{"-c", "helm rollback " + r.Name + " " + getReleaseRevision(rs) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r)}, + Description: "rolling back release [ " + r.Name + " ] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } - outcome.addCommand(cmd, r.Priority) - upgradeRelease(r) - logDecision("DECISION: release [ "+releaseName+" ] is currently deleted and is desired to be rolledback to "+ - "namespace [[ "+namespace+" ]] . It will also be upgraded in case values have changed.", r.Priority) + outcome.addCommand(cmd, r.Priority, r) + upgradeRelease(r) // this is to reflect any changes in values file(s) + logDecision("DECISION: release [ "+r.Name+" ] is currently deleted and is desired to be rolledback to "+ + "namespace [[ "+r.Namespace+" ]] . It will also be upgraded in case values have changed.", r.Priority) } else { - reInstallRelease(namespace, r) - logDecision("DECISION: release [ "+releaseName+" ] is deleted BUT from namespace [[ "+getReleaseNamespace(releaseName)+ - " ]]. Will purge delete it from there and install it in namespace [[ "+namespace+" ]]", r.Priority) - logDecision("WARNING: rolling back release [ "+releaseName+" ] from [[ "+getReleaseNamespace(releaseName)+" ]] to [[ "+namespace+ + reInstallRelease(r, rs) + logDecision("DECISION: release [ "+r.Name+" ] is deleted BUT from namespace [[ "+rs.Namespace+ + " ]]. Will purge delete it from there and install it in namespace [[ "+r.Namespace+" ]]", r.Priority) + logDecision("WARNING: rolling back release [ "+r.Name+" ] from [[ "+rs.Namespace+" ]] to [[ "+r.Namespace+ " ]] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md"+ " for details if this release uses PV and PVC.", r.Priority) } } -// inspectDeleteScenario evaluates if a delete action needs to be taken for a given release. -// If the purge flag is set to true for the release in question, then it will be permanently removed. -// If the release is not deployed, it will be skipped. -func inspectDeleteScenario(namespace string, r *release) { - - releaseName := r.Name - //if it exists in helm list , add command to delete it, else log that it is skipped - if helmReleaseExists(namespace, releaseName, "deployed") { - // delete it - deleteRelease(r) - - } else { - logDecision("DECISION: release [ "+releaseName+" ] is set to be disabled but is not yet deployed. Skipping.", r.Priority) - } -} - -// deleteRelease deletes a release from a k8s cluster -func deleteRelease(r *release) { +// deleteRelease deletes a release from a particular Tiller in a k8s cluster +func deleteRelease(r *release, rs releaseState) { p := "" purgeDesc := "" if r.Purge { @@ -175,10 +150,10 @@ func deleteRelease(r *release) { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm delete " + p + " " + r.Name + getCurrentTillerNamespace(r) + getTLSFlags(r)}, - Description: "deleting release [ " + r.Name + " ]", + Args: []string{"-c", "helm delete " + p + " " + r.Name + getCurrentTillerNamespaceFlag(rs) + getTLSFlags(r)}, + Description: "deleting release [ " + r.Name + " ] from namespace [[ " + r.Namespace + " ]] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } - outcome.addCommand(cmd, r.Priority) + outcome.addCommand(cmd, r.Priority, r) logDecision("DECISION: release [ "+r.Name+" ] is desired to be deleted "+purgeDesc+". Planing this for you!", r.Priority) } @@ -189,32 +164,31 @@ func deleteRelease(r *release) { // it will be purge deleted and installed in the same namespace using the new chart. // - If the release is NOT in the same namespace specified in the input, // it will be purge deleted and installed in the new namespace. -func inspectUpgradeScenario(namespace string, r *release) { +func inspectUpgradeScenario(r *release, rs releaseState) { - releaseName := r.Name - if getReleaseNamespace(releaseName) == namespace { - if extractChartName(r.Chart) == getReleaseChartName(releaseName) && r.Version != getReleaseChartVersion(releaseName) { + if r.Namespace == rs.Namespace { + if extractChartName(r.Chart) == getReleaseChartName(rs) && r.Version != getReleaseChartVersion(rs) { // upgrade upgradeRelease(r) - logDecision("DECISION: release [ "+releaseName+" ] is desired to be upgraded. Planing this for you!", r.Priority) + logDecision("DECISION: release [ "+r.Name+" ] is desired to be upgraded. Planing this for you!", r.Priority) - } else if extractChartName(r.Chart) != getReleaseChartName(releaseName) { - reInstallRelease(namespace, r) - logDecision("DECISION: release [ "+releaseName+" ] is desired to use a new Chart [ "+r.Chart+ + } else if extractChartName(r.Chart) != getReleaseChartName(rs) { + reInstallRelease(r, rs) + logDecision("DECISION: release [ "+r.Name+" ] is desired to use a new Chart [ "+r.Chart+ " ]. I am planning a purge delete of the current release and will install it with the new chart in namespace [[ "+ - namespace+" ]]", r.Priority) + r.Namespace+" ]]", r.Priority) } else { upgradeRelease(r) - logDecision("DECISION: release [ "+releaseName+" ] is desired to be enabled and is currently enabled."+ + logDecision("DECISION: release [ "+r.Name+" ] is desired to be enabled and is currently enabled."+ "I will upgrade it in case you changed your values.yaml!", r.Priority) } } else { - reInstallRelease(namespace, r) - logDecision("DECISION: release [ "+releaseName+" ] is desired to be enabled in a new namespace [[ "+namespace+ - " ]]. I am planning a purge delete of the current release from namespace [[ "+getReleaseNamespace(releaseName)+" ]] "+ - "and will install it for you in namespace [[ "+namespace+" ]]", r.Priority) - logDecision("WARNING: moving release [ "+releaseName+" ] from [[ "+getReleaseNamespace(releaseName)+" ]] to [[ "+namespace+ + reInstallRelease(r, rs) + logDecision("DECISION: release [ "+r.Name+" ] is desired to be enabled in a new namespace [[ "+r.Namespace+ + " ]]. I am planning a purge delete of the current release from namespace [[ "+rs.Namespace+" ]] "+ + "and will install it for you in namespace [[ "+r.Namespace+" ]]", r.Priority) + logDecision("WARNING: moving release [ "+r.Name+" ] from [[ "+rs.Namespace+" ]] to [[ "+r.Namespace+ " ]] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md"+ " for details if this release uses PV and PVC.", r.Priority) } @@ -224,33 +198,30 @@ func inspectUpgradeScenario(namespace string, r *release) { func upgradeRelease(r *release) { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + r.Version + " --force " + getSetValues(r) + getWait(r) + getDesiredTillerNamespace(r) + getTLSFlags(r)}, - Description: "upgrading release [ " + r.Name + " ]", + Args: []string{"-c", "helm upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + r.Version + " --force " + getSetValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r)}, + Description: "upgrading release [ " + r.Name + " ] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } - outcome.addCommand(cmd, r.Priority) + outcome.addCommand(cmd, r.Priority, r) } // reInstallRelease purge deletes a release and reinstalls it. // This is used when moving a release to another namespace or when changing the chart used for it. -func reInstallRelease(namespace string, r *release) { +func reInstallRelease(r *release, rs releaseState) { - releaseName := r.Name delCmd := command{ Cmd: "bash", - Args: []string{"-c", "helm delete --purge " + releaseName + getCurrentTillerNamespace(r) + getTLSFlags(r)}, - Description: "deleting release [ " + releaseName + " ]", + Args: []string{"-c", "helm delete --purge " + r.Name + getCurrentTillerNamespaceFlag(rs) + getTLSFlags(r)}, + Description: "deleting release [ " + r.Name + " ] from namespace [[ " + r.Namespace + " ]] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } - outcome.addCommand(delCmd, r.Priority) + outcome.addCommand(delCmd, r.Priority, r) installCmd := command{ Cmd: "bash", - Args: []string{"-c", "helm install " + r.Chart + " --version " + r.Version + " -n " + releaseName + " --namespace " + namespace + getValuesFiles(r) + getSetValues(r) + getWait(r) + getDesiredTillerNamespace(r) + getTLSFlags(r)}, - Description: "installing release [ " + releaseName + " ] in namespace [[ " + namespace + " ]]", + Args: []string{"-c", "helm install " + r.Chart + " --version " + r.Version + " -n " + r.Name + " --namespace " + r.Namespace + getValuesFiles(r) + getSetValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r)}, + Description: "installing release [ " + r.Name + " ] in namespace [[ " + r.Namespace + " ]] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } - outcome.addCommand(installCmd, r.Priority) - logDecision("DECISION: release [ "+releaseName+" ] will be deleted from namespace [[ "+getReleaseNamespace(releaseName)+" ]] and reinstalled in [[ "+namespace+"]].", r.Priority) - + outcome.addCommand(installCmd, r.Priority, r) } // logDecision adds the decisions made to the plan. @@ -307,23 +278,23 @@ func getDesiredNamespace(r *release) string { // getCurrentNamespaceProtection returns the protection state for the namespace where a release is currently installed. // It returns true if a namespace is defined as protected in the desired state file, false otherwise. -func getCurrentNamespaceProtection(r *release) bool { +func getCurrentNamespaceProtection(rs releaseState) bool { - return s.Namespaces[getReleaseNamespace(r.Name)].Protected + return s.Namespaces[rs.Namespace].Protected } // isProtected checks if a release is protected or not. // A protected is release is either: a) deployed in a protected namespace b) flagged as protected in the desired state file // Any release in a protected namespace is protected by default regardless of its flag // returns true if a release is protected, false otherwise -func isProtected(r *release) bool { +func isProtected(r *release, rs releaseState) bool { // if the release does not exist in the cluster, it is not protected - if !helmReleaseExists("", r.Name, "") { + if ok, _ := helmReleaseExists(r, ""); !ok { return false } - if getCurrentNamespaceProtection(r) { + if getCurrentNamespaceProtection(rs) { return true } @@ -335,20 +306,26 @@ func isProtected(r *release) bool { } -// getDesiredTillerNamespace returns a tiller-namespace flag with which a release is desired to be maintained -func getDesiredTillerNamespace(r *release) string { +// getDesiredTillerNamespaceFlag returns a tiller-namespace flag with which a release is desired to be maintained +func getDesiredTillerNamespaceFlag(r *release) string { + return " --tiller-namespace " + getDesiredTillerNamespace(r) +} - if s.Namespaces[r.Namespace].InstallTiller { - return " --tiller-namespace " + r.Namespace +// getDesiredTillerNamespace returns the Tiller namespace with which a release should be managed +func getDesiredTillerNamespace(r *release) string { + if r.TillerNamespace != "" { + return r.TillerNamespace + } else if v, ok := s.Namespaces[r.Namespace]; ok && v.InstallTiller { + return r.Namespace } - return "" // same as return " --tiller-namespace kube-system" + return "kube-system" } -// getCurrentTillerNamespace returns the tiller-namespace with which a release is currently maintained -func getCurrentTillerNamespace(r *release) string { - if v, ok := currentState[r.Name]; ok { - return " --tiller-namespace " + v.TillerNamespace +// getCurrentTillerNamespaceFlag returns the tiller-namespace with which a release is currently maintained +func getCurrentTillerNamespaceFlag(rs releaseState) string { + if rs.TillerNamespace != "" { + return " --tiller-namespace " + rs.TillerNamespace } return "" } @@ -358,7 +335,12 @@ func getCurrentTillerNamespace(r *release) string { // otherwise, it will be the certs/keys for the kube-system namespace. func getTLSFlags(r *release) string { tls := "" - if s.Namespaces[r.Namespace].InstallTiller { + if r.TillerNamespace != "" { + if tillerTLSEnabled(r.TillerNamespace) { + + tls = " --tls --tls-ca-cert " + r.TillerNamespace + "-ca.cert --tls-cert " + r.TillerNamespace + "-client.cert --tls-key " + r.TillerNamespace + "-client.key " + } + } else if s.Namespaces[r.Namespace].InstallTiller { if tillerTLSEnabled(r.Namespace) { tls = " --tls --tls-ca-cert " + r.Namespace + "-ca.cert --tls-cert " + r.Namespace + "-client.cert --tls-key " + r.Namespace + "-client.key " diff --git a/helm_helpers.go b/helm_helpers.go index 55c48bc6..4e742b44 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -63,6 +63,7 @@ func buildState() { lines := strings.Split(getAllReleases(), "\n") for i := 0; i < len(lines); i++ { + // skipping the header from helm output if lines[i] == "" || (strings.HasPrefix(strings.TrimSpace(lines[i]), "NAME") && strings.HasSuffix(strings.TrimSpace(lines[i]), "NAMESPACE")) { continue } @@ -74,7 +75,7 @@ func buildState() { log.Fatal("ERROR: while converting release time: " + err.Error()) } - currentState[strings.Fields(lines[i])[0]] = releaseState{ + currentState[strings.Fields(lines[i])[0]+"-"+strings.Fields(lines[i])[10]] = releaseState{ Revision: r, Updated: time, Status: strings.Fields(lines[i])[7], @@ -127,84 +128,50 @@ func listReleases(namespace string, scope string) string { } // helmRealseExists checks if a Helm release is/was deployed in a k8s cluster. -// The search criteria is: -// -// -releaseName: the name of the release to look for. Helm releases have unique names within a k8s cluster. -// -scope: defines where to search for the release. Options are: [deleted, deployed, all, failed] -// -namespace: search in that namespace (only applicable if searching for currently deployed releases) -func helmReleaseExists(namespace string, releaseName string, status string) bool { - v, ok := currentState[releaseName] +// It searches the Current State for releases. +// The key format for releases uniqueness is: +// If status is provided as an input [deployed, deleted, failed], then the search will verify the release status matches the search status. +func helmReleaseExists(r *release, status string) (bool, releaseState) { + compositeReleaseName := r.Name + "-" + getDesiredTillerNamespace(r) + + v, ok := currentState[compositeReleaseName] if !ok { - return false + return false, v } - if namespace != "" && status != "" { - if v.Namespace == namespace && v.Status == strings.ToUpper(status) { - return true - } - return false - } else if namespace != "" { - if v.Namespace == namespace { - return true - } - return false - } else if status != "" { + if status != "" { if v.Status == strings.ToUpper(status) { - return true + return true, v } - return false - } - return true -} - -// getReleaseNamespace returns the namespace in which a release is deployed. -// throws an error and exits the program if the release does not exist. -func getReleaseNamespace(releaseName string) string { - - v, ok := currentState[releaseName] - if !ok { - log.Fatal("ERROR: seems release [ " + releaseName + " ] does not exist.") + return false, v } - return v.Namespace + return true, v } -// getReleaseChart returns the Helm chart which is used by a deployed release. -// throws an error and exits the program if the release does not exist. -func getReleaseChart(releaseName string) string { +// getReleaseRevision returns the revision number for a release +func getReleaseRevision(rs releaseState) string { - v, ok := currentState[releaseName] - if !ok { - log.Fatal("ERROR: seems release [ " + releaseName + " ] does not exist.") - } - return v.Chart + return strconv.Itoa(rs.Revision) } -// getReleaseRevision returns the revision number for a release (if it exists) -func getReleaseRevision(releaseName string, state string) string { +// getReleaseChartName extracts and returns the Helm chart name from the chart info in a release state. +// example: chart in release state is "jenkins-0.9.0" and this function will extract "jenkins" from it. +func getReleaseChartName(rs releaseState) string { - v, ok := currentState[releaseName] - if !ok { - log.Fatal("ERROR: seems release [ " + releaseName + " ] does not exist.") - } - return strconv.Itoa(v.Revision) -} - -// getReleaseChartName extracts and returns the Helm chart name from the chart info retrieved by getReleaseChart(). -// example: getReleaseChart() returns "jenkins-0.9.0" and this functions will extract "jenkins" from it. -func getReleaseChartName(releaseName string) string { - chart := getReleaseChart(releaseName) + chart := rs.Chart runes := []rune(chart) return string(runes[0:strings.LastIndexByte(chart[0:strings.IndexByte(chart, '.')], '-')]) } -// getReleaseChartVersion extracts and returns the Helm chart version from the chart info retrieved by getReleaseChart(). -// example: getReleaseChart() returns "jenkins-0.9.0" and this functions will extract "0.9.0" from it. -func getReleaseChartVersion(releaseName string) string { - chart := getReleaseChart(releaseName) +// getReleaseChartVersion extracts and returns the Helm chart version from the chart info in a release state. +// example: chart in release state is returns "jenkins-0.9.0" and this functions will extract "0.9.0" from it. +func getReleaseChartVersion(rs releaseState) string { + chart := rs.Chart runes := []rune(chart) return string(runes[strings.LastIndexByte(chart, '-')+1 : len(chart)]) } +// DEPRECATED // getReleaseStatus returns the output of Helm status command for a release. // if the release does not exist, it returns an empty string without breaking the program execution. func getReleaseStatus(releaseName string) string { @@ -417,3 +384,57 @@ func initHelm() (bool, string) { return true, "" } + +// cleanUntrackedReleases checks for any releases that are managed by Helmsman and is no longer tracked by the desired state +// It compares the currently deployed releases with "MANAGED-BY=HELMSMAN" labels with Apps defined in the desired state +// For all untracked releases found, a decision is made to delete them and is added to the Helmsman plan +// NOTE: Untracked releases don't benefit from either namespace or application protection. +// NOTE: Removing/Commenting out an app from the desired state makes it untracked. +func cleanUntrackedReleases() { + toDelete := make(map[string]map[string]bool) + log.Println("INFO: checking if any Helmsman managed releases are no longer tracked by your desired state ...") + for ns, releases := range getHelmsmanReleases() { + for r := range releases { + tracked := false + for _, app := range s.Apps { + if app.Name == r && getDesiredTillerNamespace(app) == ns { + tracked = true + } + } + if !tracked { + if _, ok := toDelete[ns]; !ok { + toDelete[ns] = make(map[string]bool) + } + toDelete[ns][r] = true + } + } + } + + if len(toDelete) == 0 { + log.Println("INFO: no untracked releases found.") + } else { + for ns, releases := range toDelete { + for r := range releases { + logDecision("DECISION: untracked release found: release [ "+r+" ] from Tiller in namespace [ "+ns+" ]. It will be deleted.", -800) + deleteUntrackedRelease(r, ns) + } + } + } +} + +// deleteUntrackedRelease creates the helm command to purge delete an untracked release +func deleteUntrackedRelease(release string, tillerNamespace string) { + + tls := "" + if tillerTLSEnabled(tillerNamespace) { + + tls = " --tls --tls-ca-cert " + tillerNamespace + "-ca.cert --tls-cert " + tillerNamespace + "-client.cert --tls-key " + tillerNamespace + "-client.key " + } + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm delete --purge " + release + " --tiller-namespace " + tillerNamespace + tls}, + Description: "deleting untracked release [ " + release + " ] from Tiller in namespace [[ " + tillerNamespace + " ]]", + } + + outcome.addCommand(cmd, -800, nil) +} diff --git a/kube_helpers.go b/kube_helpers.go index 5efa6be8..223c9636 100644 --- a/kube_helpers.go +++ b/kube_helpers.go @@ -2,11 +2,11 @@ package main import ( "log" + "strings" ) // validateServiceAccount checks if k8s service account exists in a given namespace // if the provided namespace is empty, it checks in the "default" namespace -// if the serviceaccount does not exist, it will be created func validateServiceAccount(sa string, namespace string) (bool, string) { log.Println("INFO: validating if service account [" + sa + "] exists in namespace [" + namespace + "]") if namespace == "" { @@ -22,20 +22,13 @@ func validateServiceAccount(sa string, namespace string) (bool, string) { if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { return false, err - // if strings.Contains(err, "NotFound") || strings.Contains(err, "not found") { - // log.Println("INFO: service account [ " + sa + " ] does not exist in namespace [ " + namespace + " ] .. attempting to create it ... ") - - // if _, rbacErr := createRBAC(sa, namespace); rbacErr != "" { - // return false, rbacErr - // } - // return true, "" - - // } - // return false, err } return true, "" } +// createRBAC creates a k8s service account and bind it to a (Cluster)Role +// If sharedTiller is true , it binds the service account to cluster-admin role. Otherwise, +// It binds it to a new role called "helmsman-tiller" func createRBAC(sa string, namespace string, sharedTiller bool) (bool, string) { var ok bool var err string @@ -218,6 +211,7 @@ func createServiceAccount(saName string, namespace string) (bool, string) { return true, "" } +// createRoleBinding creates a role binding in a given namespace for a service account with a cluster-role/role in the cluster. func createRoleBinding(role string, saName string, namespace string) (bool, string) { clusterRole := false resource := "rolebinding" @@ -242,13 +236,13 @@ func createRoleBinding(role string, saName string, namespace string) (bool, stri exitCode, err := cmd.exec(debug, verbose) if exitCode != 0 { - //logError("ERROR: failed to bind service account " + saName + " in namespace [ " + namespace + " ] to role " + role + " : " + err) return false, err } return true, "" } +// createRole creates a k8s Role in a given namespace func createRole(namespace string) (bool, string) { // load static resource @@ -277,3 +271,69 @@ func createRole(namespace string) (bool, string) { return true, "" } + +// labelResource applies Helmsman specific labels to Helm's state resources (secrets/configmaps) +func labelResource(r *release) { + if r.Enabled { + log.Println("INFO: applying Helmsman lables to [ " + r.Name + " ] in namespace [ " + r.Namespace + " ] ") + storageBackend := "configmap" + + if v, ok := s.Settings["storageBackend"]; ok && v == "secret" { + storageBackend = "secret" + } + + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "kubectl label " + storageBackend + " -n " + getDesiredTillerNamespace(r) + " -l NAME=" + r.Name + " MANAGED-BY=HELMSMAN NAMESPACE=" + r.Namespace + " TILLER_NAMESPACE=" + getDesiredTillerNamespace(r) + " --overwrite"}, + Description: "applying labels to Helm state in [ " + getDesiredTillerNamespace(r) + " ] for " + r.Name, + } + + exitCode, err := cmd.exec(debug, verbose) + + if exitCode != 0 { + logError(err) + } + } +} + +// getHelmsmanReleases returns a map of all releases that are labeled with "MANAGED-BY=HELMSMAN" +// The releases are categorized by the namespaces in which their Tiller is running +// The returned map format is: map[:map[:true]] +func getHelmsmanReleases() map[string]map[string]bool { + releases := make(map[string]map[string]bool) + storageBackend := "configmap" + + if v, ok := s.Settings["storageBackend"]; ok && v == "secret" { + storageBackend = "secret" + } + + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "kubectl get " + storageBackend + " --all-namespaces -l MANAGED-BY=HELMSMAN"}, + Description: "getting helm releases which are managed by Helmsman.", + } + + exitCode, output := cmd.exec(debug, verbose) + + if exitCode != 0 { + logError(output) + } + + lines := strings.Split(output, "\n") + if strings.ToUpper("No resources found") == strings.ToUpper(strings.TrimSpace(output)) { + return releases + } + for i := 0; i < len(lines); i++ { + if lines[i] == "" || (strings.HasPrefix(strings.TrimSpace(lines[i]), "NAMESPACE") && strings.HasSuffix(strings.TrimSpace(lines[i]), "AGE")) { + continue + } else { + fields := strings.Fields(lines[i]) + if _, ok := releases[fields[0]]; !ok { + releases[fields[0]] = make(map[string]bool) + } + releases[fields[0]][fields[1][0:strings.LastIndex(fields[1], ".v")]] = true + } + } + + return releases +} diff --git a/main.go b/main.go index 70ab1344..573dba43 100644 --- a/main.go +++ b/main.go @@ -15,7 +15,8 @@ var verbose bool var nsOverride string var checkCleanup bool var skipValidation bool -var version = "v1.3.1" +var applyLabels bool +var version = "v1.4.0-rc" func main() { @@ -62,7 +63,9 @@ func main() { } log.Println("INFO: checking what I need to do for your charts ... ") + p := makePlan(&s) + cleanUntrackedReleases() p.sortPlan() p.printPlan() diff --git a/plan.go b/plan.go index eb5b0a67..b3e77e22 100644 --- a/plan.go +++ b/plan.go @@ -16,10 +16,11 @@ type orderedDecision struct { Priority int } -// orderedCommand type representing a Command and it's priority weight +// orderedCommand type representing a Command and it's priority weight and the targeted release from the desired state type orderedCommand struct { - Command command - Priority int + Command command + Priority int + targetRelease *release } // plan type representing the plan of actions to make the desired state come true. @@ -41,10 +42,11 @@ func createPlan() plan { } // addCommand adds a command type to the plan -func (p *plan) addCommand(cmd command, priority int) { +func (p *plan) addCommand(cmd command, priority int, r *release) { oc := orderedCommand{ - Command: cmd, - Priority: priority, + Command: cmd, + Priority: priority, + targetRelease: r, } p.Commands = append(p.Commands, oc) @@ -67,6 +69,9 @@ func (p plan) execPlan() { if exitCode, msg := cmd.Command.exec(debug, verbose); exitCode != 0 { logError("Command returned with exit code: " + string(exitCode) + ". And error message: " + msg) } else { + if cmd.targetRelease != nil { + labelResource(cmd.targetRelease) + } if _, err := url.ParseRequestURI(s.Settings["slackWebhook"]); err == nil { notifySlack(cmd.Command.Description+" ... SUCCESS!", s.Settings["slackWebhook"], false, true) } diff --git a/release.go b/release.go index 354a556e..067c5684 100644 --- a/release.go +++ b/release.go @@ -9,20 +9,21 @@ import ( // release type representing Helm releases which are described in the desired state type release struct { - Name string - Description string - Namespace string - Enabled bool - Chart string - Version string - ValuesFile string `yaml:"valuesFile"` - ValuesFiles []string `yaml:"valuesFiles"` - Purge bool - Test bool - Protected bool - Wait bool - Priority int - Set map[string]string + Name string + Description string + Namespace string + Enabled bool + Chart string + Version string + ValuesFile string `yaml:"valuesFile"` + ValuesFiles []string `yaml:"valuesFiles"` + Purge bool + Test bool + Protected bool + Wait bool + Priority int + TillerNamespace string + Set map[string]string } // validateRelease validates if a release inside a desired state meets the specifications or not. @@ -31,8 +32,14 @@ func validateRelease(r *release, names map[string]map[string]bool, s state) (boo _, err := os.Stat(r.ValuesFile) if r.Name == "" { return false, "release name can't be empty." - } else if (s.Namespaces[r.Namespace].InstallTiller && names[r.Name][r.Namespace]) || (!s.Namespaces[r.Namespace].InstallTiller && names[r.Name]["kube-system"]) { - return false, "release name must be unique within a given namespace." + } else if r.TillerNamespace != "" && r.TillerNamespace != "kube-system" { + if v, ok := s.Namespaces[r.TillerNamespace]; !ok { + return false, "tillerNamespace specified, but the namespace specified does not exist!" + } else if !v.InstallTiller { + return false, "tillerNamespace specified, but that namespace does not have installTiller set to true." + } + } else if names[r.Name][getDesiredTillerNamespace(r)] { + return false, "release name must be unique within a given Tiller." } else if nsOverride == "" && r.Namespace == "" { return false, "release targeted namespace can't be empty." } else if nsOverride == "" && r.Namespace != "" && !checkNamespaceDefined(r.Namespace, s) { @@ -59,7 +66,9 @@ func validateRelease(r *release, names map[string]map[string]bool, s state) (boo if names[r.Name] == nil { names[r.Name] = make(map[string]bool) } - if s.Namespaces[r.Namespace].InstallTiller { + if r.TillerNamespace != "" { + names[r.Name][r.TillerNamespace] = true + } else if s.Namespaces[r.Namespace].InstallTiller { names[r.Name][r.Namespace] = true } else { names[r.Name]["kube-system"] = true @@ -99,6 +108,7 @@ func (r release) print() { fmt.Println("\tprotected : ", r.Protected) fmt.Println("\twait : ", r.Wait) fmt.Println("\tpriority : ", r.Priority) + fmt.Println("\ttillerNamespace : ", r.TillerNamespace) fmt.Println("\tvalues to override from env:") printMap(r.Set) fmt.Println("------------------- ") From 62047252f5cb38acbd3658095503ed494415b185 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Tue, 24 Jul 2018 15:31:25 +0200 Subject: [PATCH 0187/1127] making release name an optional flag #45 --- release.go | 4 ++-- state.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/release.go b/release.go index 067c5684..b82d5634 100644 --- a/release.go +++ b/release.go @@ -28,10 +28,10 @@ type release struct { // validateRelease validates if a release inside a desired state meets the specifications or not. // check the full specification @ https://github.com/Praqma/helmsman/docs/desired_state_spec.md -func validateRelease(r *release, names map[string]map[string]bool, s state) (bool, string) { +func validateRelease(appLabel string, r *release, names map[string]map[string]bool, s state) (bool, string) { _, err := os.Stat(r.ValuesFile) if r.Name == "" { - return false, "release name can't be empty." + r.Name = appLabel } else if r.TillerNamespace != "" && r.TillerNamespace != "kube-system" { if v, ok := s.Namespaces[r.TillerNamespace]; !ok { return false, "tillerNamespace specified, but the namespace specified does not exist!" diff --git a/state.go b/state.go index c35fdacb..7626286a 100644 --- a/state.go +++ b/state.go @@ -154,7 +154,7 @@ func (s state) validate() (bool, string) { names := make(map[string]map[string]bool) for appLabel, r := range s.Apps { - result, errMsg := validateRelease(r, names, s) + result, errMsg := validateRelease(appLabel, r, names, s) if !result { return false, "ERROR: apps validation failed -- for app [" + appLabel + " ]. " + errMsg } From 2b291b5b7583d160e26c5ac65b01d9cb9639ecae Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Tue, 24 Jul 2018 15:32:24 +0200 Subject: [PATCH 0188/1127] updating example --- example.toml | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/example.toml b/example.toml index f026ce08..aede317d 100644 --- a/example.toml +++ b/example.toml @@ -1,4 +1,4 @@ -# version: v1.2.0-rc +# version: v1.4.0-rc # metadata -- add as many key/value pairs as you want [metadata] org = "example.com" @@ -19,7 +19,7 @@ kubeContext = "minikube" # will try connect to this context first, if it does no #password = "$K8S_PASSWORD" # the name of an environment variable containing the k8s password #clusterURI = "$K8S_URI" # the name of an environment variable containing the cluster API ##clusterURI = "https://192.168.99.100:8443" # equivalent to the above -#serviceAccount = "tiller" # k8s serviceaccount must be already defined, validation error will be thrown otherwise +serviceAccount = "tiller" # k8s serviceaccount. If it does not exist, it will be created. storageBackend = "secret" # default is configMap #slackWebhook = "$slack" # or "your slack webhook url" @@ -58,31 +58,32 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" # jenkins will be deployed using the Tiller in the staging namespace [apps.jenkins] - name = "jenkins" # should be unique across all apps - description = "jenkins" namespace = "staging" # maps to the namespace as defined in namespaces above - enabled = true # change to false if you want to delete this app release [empty = false] - chart = "stable/jenkins" # changing the chart name means delete and recreate this chart + enabled = true # change to false if you want to delete this app release [default = false] + chart = "stable/jenkins" # changing the chart name means delete and recreate this release version = "0.14.3" # chart version + ### Optional values below + name = "jenkins" # should be unique across all apps which are managed by the same Tiller valuesFile = "" # leaving it empty uses the default chart values + #tillerNamespace = "kube-system" # which Tiller to use to deploy this release purge = false # will only be considered when there is a delete operation test = false # run the tests when this release is installed for the first time only protected = true priority= -3 wait = true - # [apps.jenkins.set] # values to override values from values.yaml with values from env vars or directly entered-- useful for passing secrets to charts - # AdminPassword="$JENKINS_PASSWORD" # $JENKINS_PASSWORD must exist in the environment - # AdminUser="admin" + [apps.jenkins.set] # values to override values from values.yaml with values from env vars or directly entered-- useful for passing secrets to charts + AdminPassword="$JENKINS_PASSWORD" # $JENKINS_PASSWORD must exist in the environment + AdminUser="admin" # artifactory will be deployed using the Tiller in the kube-system namespace [apps.artifactory] - name = "artifactory" # should be unique across all apps - description = "artifactory" namespace = "production" # maps to the namespace as defined in namespaces above - enabled = true # change to false if you want to delete this app release [empty = flase] - chart = "stable/artifactory" # changing the chart name means delete and recreate this chart + enabled = true # change to false if you want to delete this app release [default = flase] + chart = "stable/artifactory" # changing the chart name means delete and recreate this release version = "7.0.6" # chart version + ### Optional values below + name = "artifactory" # should be unique across all apps which are managed by the same Tiller valuesFile = "" # leaving it empty uses the default chart values purge = false # will only be considered when there is a delete operation test = false # run the tests when this release is installed for the first time only From aa9627c97ac7dd6ba06e7fc317a8a352acda39fe Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Wed, 1 Aug 2018 15:07:37 +0200 Subject: [PATCH 0189/1127] adding helmsman version in slack messages. --- utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils.go b/utils.go index dda0dfa6..3d169175 100644 --- a/utils.go +++ b/utils.go @@ -305,7 +305,7 @@ func notifySlack(content string, url string, failure bool, executing bool) bool "pretext": "` + pretext + `", "title": "` + content + `", - "footer": "Helmsman", + "footer": "Helmsman "` + version + `", "ts": ` + strconv.FormatInt(t.Unix(), 10) + ` } ] From 6bd96ff55c3e2854b13e841d3f9bf8f669db2c28 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Wed, 1 Aug 2018 16:48:20 +0200 Subject: [PATCH 0190/1127] updating examples and documentation --- docs/desired_state_specification.md | 48 ++++++++++++++++++++--------- example.toml | 2 ++ example.yaml | 21 ++++++++----- 3 files changed, 48 insertions(+), 23 deletions(-) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 9ec1cb17..c29468a1 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -1,10 +1,10 @@ --- -version: v1.3.0-rc +version: v1.4.0-rc --- # Helmsman desired state specification -This document describes the specification for how to write your Helm charts desired state file. This can be either toml or yaml file. The desired state file consists of: +This document describes the specification for how to write your Helm charts desired state file. This can be either [Toml](https://github.com/toml-lang/toml) or [Yaml](http://yaml.org/) file. The desired state file consists of: - [Metadata](#metadata) [Optional] -- metadata for any human reader of the desired state file. - [Certificates](#certificates) [Optional] -- only needed when you want Helmsman to connect kubectl to your cluster for you. @@ -40,7 +40,7 @@ metadata: Optional : Yes, only needed if you want Helmsman to connect kubectl to your cluster for you. -Synopsis: defines where to find the certifactions needed for connecting kubectl to a k8s cluster. If connection settings (username/password/clusterAPI) are provided in the Settings section below, then you need AT LEAST to provide caCrt and caKey. You can optionally provide a client certificate (caClient) depending on your cluster connection setup. +Synopsis: defines where to find the certificates needed for connecting kubectl to a k8s cluster. If connection settings (username/password/clusterAPI) are provided in the Settings section below, then you need AT LEAST to provide caCrt and caKey. You can optionally provide a client certificate (caClient) depending on your cluster connection setup. Options: - caCrt : a valid S3/GCS bucket or local relative file path to a certificate file. @@ -78,15 +78,16 @@ Synopsis: provides settings for connecting to your k8s cluster and configuring H Options: - kubeContext : this is always required and defines what context to use in kubectl. Helmsman will try connect to this context first, if it does not exist, it will try to create it (i.e. connect to a k8s cluster) using the options below. -The following options can be skipped if your kubectl context is already created and you don't want Helmsman to connect kubectl to your cluster for you. When using Helmsman in CI pipeline, these details are required to connect to your cluster everytime the pipeline is executed. +The following options can be skipped if your kubectl context is already created and you don't want Helmsman to connect kubectl to your cluster for you. When using Helmsman in CI pipeline, these details are required to connect to your cluster every time the pipeline is executed. - username : the username to be used for kubectl credentials. - password : an environment variable name (starting with `$`) where your password is stored. Get the password from your k8s admin or consult k8s docs on how to get/set it. - clusterURI : the URI for your cluster API or the name of an environment variable (starting with `$`) containing the URI. - serviceAccount: the name of the service account to use to initiate helm. This should have enough permissions to allow Helm to work and should exist already in the cluster. More details can be found in [helm's RBAC guide](https://github.com/kubernetes/helm/blob/master/docs/rbac.md) - storageBackend : by default Helm stores release information in configMaps, using secrets is for storage is recommended for security. Setting this flag to `secret` will deploy/upgrade Tiller with the `--storage=secret`. Other values will be skipped and configMaps will be used. +- slackWebhook : a [Slack](slack.com) Webhook URL to receive Helmsman notifications. This can be passed directly or in an environment variable. -> If you use `storageBackend` with a Tiller that has been previously deployed with configMaps as storage backend, you need to migrate your release information from the configMap to the new secret on your own. +> If you use `storageBackend` with a Tiller that has been previously deployed with configMaps as storage backend, you need to migrate your release information from the configMap to the new secret on your own. Helm does not support this yet. Example: @@ -99,6 +100,7 @@ kubeContext = "minikube" ## clusterURI= "$K8S_URI" # serviceAccount = "my-service-account" # storageBackend = "secret" +# slackWebhook = $MY_SLACK_WEBHOOK ``` ```yaml @@ -110,6 +112,7 @@ settings: ##clusterURI: "$K8S_URI" #serviceAccount: "my-service-account" #storageBackend: "secret" + #slackWebhook : "$MY_SLACK_WEBHOOK" ``` ## Namespaces @@ -122,9 +125,15 @@ If a namespaces does not already exist, Helmsman will create it. Options: - protected : defines if a namespace is protected (true or false). Default false. - installTiller: defines if Tiller should be deployed in this namespace or not. Default is false. Any chart desired to be deployed into a namespace with a Tiller deployed, will be deployed using that Tiller and not the one in kube-system. -> Tiller will always be deployed into `kube-system`, even if you set installTiller for kube-system to false. +> Tiller will ALWAYS be deployed into `kube-system`, even if you set installTiller for kube-system to false. + +- tillerServiceAccount: defines what service account to use when deploying Tiller. If this is not set, the following options are considered: + + 1. If the `serviceAccount` defined in the `settings` section exists in the namespace you want to deploy Tiller in, it will be used, else + 2. Helmsman creates the service account in that namespace and binds it to a role. If the namespace is kube-system, the service account is bound to `cluster-admin` clusterrole. Otherwise, a new role called `helmsman-tiller` is created in that namespace and only gives access to that namespace. + + > If `installTiller` is not defined or set to false, this flag is ignored. -- tillerServiceAccount: defines what service account to use when deploying Tiller. If not set, the `serviceAccount` defined in the `settings` section will be used. If that is also not defined, the namespace `default` service account will be used. If `installTiller` is not defined or set to false, this flag is ignored. - The following options are `ALL` needed for deploying Tiller with TLS enabled. If they are not all defined, they will be ignored and Tiller will be deployed without TLS. All of these options can be provided as either: a valid local file path, a valid GCS or S3 bucket URI or an environment variable containing a file path or bucket URI. - caCert: the CA certificate. - tillerCert: the SSL certificate for Tiller. @@ -132,7 +141,7 @@ Options: - clientCert: the SSL certificate for the Helm client. - clientKey: the SSL certificate private key for the Helm client. -> For the defintion of what a protected namespace means, check the [protection guide](how_to/protect_namespaces_and_releases.md) +> For the definition of what a protected namespace means, check the [protection guide](how_to/protect_namespaces_and_releases.md) Example: @@ -212,20 +221,29 @@ Synopsis: defines the releases (instances of Helm charts) you would like to mana Releases must have unique names which are defined under `apps`. Example: in `[apps.jenkins]`, the release name will be `jenkins` and it should be unique in your cluster. Options: -- name : the Helm release name. Releases must have unique names within a cluster. -- description : a release metadata for human readers. + +**Required** - namespace : the namespace where the release should be deployed. The namespace should map to one of the ones defined in [namespaces](#namespaces). -- enabled : describes the required state of the release (true for enabled, false for disabled). Once a release is deployed, you can change it to false if you want to delete this app release [empty = flase]. +- enabled : describes the required state of the release (true for enabled, false for disabled). Once a release is deployed, you can change it to false if you want to delete this release [default is false]. - chart : the chart name. It should contain the repo name as well. Example: repoName/chartName. Changing the chart name means delete and reinstall this release using the new Chart. - version : the chart version. + +**Optional** +- tillerNamespace : which Tiller to use for deploying this release. This is available starting from v1.4.0-rc The decision on which Tiller to use for deploying a release follows the following criteria: + 1. If `tillerNamespace`is explicitly defined, it is used. + 2. If `tillerNamespace`is not defined and the namespace in which the release will be deployed has a Tiller installed by Helmsman (i.e. has `installTiller set to true` in the [Namespaces](#namespaces) section), Tiller in that namespace is used. + 3. If none of the above, the shared Tiller in `kube-system` is used. + +- name : the Helm release name. Releases must have unique names within a Helm Tiller. If not set, the release name will be taken from the app identifier in your desired state file. e.g, for ` apps.jenkins ` the name release name will be `jenkins`. +- description : a release metadata for human readers. - valuesFile : a valid path to custom Helm values.yaml file. File extension must be `yaml`. Cannot be used with valuesFiles together. Leaving it empty uses the default chart values. - valuesFiles : array of valid paths to custom Helm values.yaml file. File extension must be `yaml`. Cannot be used with valuesFile together. Leaving it empty uses the default chart values. -- purge : defines whether to use the Helm purge flag wgen deleting the release. (true/false) -- test : defines whether to run the chart tests whenever the release is installed/upgraded/rolledback. +- purge : defines whether to use the Helm purge flag when deleting the release. (true/false) +- test : defines whether to run the chart tests whenever the release is installed. - protected : defines if the release should be protected against changes. Namespace-level protection has higher priority than this flag. Check the [protection guide](how_to/protect_namespaces_and_releases.md) for more details. -- wait : defines whether helmsman should block execution until all k8s resources are in a ready state. Default is false. +- wait : defines whether Helmsman should block execution until all k8s resources are in a ready state. Default is false. - priority : defines the priority of applying operations on this release. Only negative values allowed and the lower the value, the higher the priority. Default priority is 0. Apps with equal priorities will be applied in the order they were added in your state file (DSF). -- [apps..set] : is used to override certain values from values.yaml with values from environment variables (or ,starting from v1.3.0-rc, directly provided in the Desired State File). This is particularily useful for passing secrets to charts. If the an environment variable with the same name as the provided value exists, the environment variable value will be used, otherwise, the provided value will be used as is. +- [apps..set] : is used to override certain values from values.yaml with values from environment variables (or ,starting from v1.3.0-rc, directly provided in the Desired State File). This is particularly useful for passing secrets to charts. If the an environment variable with the same name as the provided value exists, the environment variable value will be used, otherwise, the provided value will be used as is. Example: diff --git a/example.toml b/example.toml index aede317d..a43a022d 100644 --- a/example.toml +++ b/example.toml @@ -88,3 +88,5 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" purge = false # will only be considered when there is a delete operation test = false # run the tests when this release is installed for the first time only priority= -2 + +# See https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md#apps for more apps options \ No newline at end of file diff --git a/example.yaml b/example.yaml index 6ff78cdb..5d08e79f 100644 --- a/example.yaml +++ b/example.yaml @@ -1,4 +1,4 @@ -# version: v1.2.0-rc +# version: v1.4.0-rc # metadata -- add as many key/value pairs as you want metadata: org: "example.com" @@ -55,31 +55,36 @@ apps: # jenkins will be deployed using the Tiller in the staging namespace jenkins: - name: "jenkins" # should be unique across all apps - description: "jenkins" namespace: "staging" # maps to the namespace as defined in namespaces above enabled: true # change to false if you want to delete this app release empty: flase: chart: "stable/jenkins" # changing the chart name means delete and recreate this chart version: "0.14.3" # chart version + ### Optional values below + name: "jenkins" # should be unique across all apps + description: "jenkins" valuesFile: "" # leaving it empty uses the default chart values purge: false # will only be considered when there is a delete operation test: false # run the tests when this release is installed for the first time only protected: true priority: -3 wait: true - #set: # values to override values from values.yaml with values from env vars-- useful for passing secrets to charts - #AdminPassword: "$JENKINS_PASSWORD" # $JENKINS_PASSWORD must exist in the environment - + #tillerNamespace: "kube-system" # which Tiller to use to deploy this release + set: # values to override values from values.yaml with values from env vars-- useful for passing secrets to charts + AdminPassword: "$JENKINS_PASSWORD" # $JENKINS_PASSWORD must exist in the environment + AdminUser: "admin" # artifactory will be deployed using the Tiller in the kube-system namespace artifactory: - name: "artifactory" # should be unique across all apps - description: "artifactory" namespace: "production" # maps to the namespace as defined in namespaces above enabled: true # change to false if you want to delete this app release empty: flase: chart: "stable/artifactory" # changing the chart name means delete and recreate this chart version: "7.0.6" # chart version + ### Optional values below + name: "artifactory" # should be unique across all apps + description: "artifactory" valuesFile: "" # leaving it empty uses the default chart values purge: false # will only be considered when there is a delete operation test: false # run the tests when this release is installed for the first time only priority: -2 + +# See https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md#apps for more apps options \ No newline at end of file From 7336f3533dbd41f969313bd78aff04aff3c5ff23 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Wed, 1 Aug 2018 16:48:42 +0200 Subject: [PATCH 0191/1127] updating install links --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 3b985f8f..4879cad8 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ --- -version: v1.3.0-rc +version: v1.4.0-rc --- ![helmsman-logo](docs/images/helmsman.png) @@ -31,7 +31,7 @@ To show debugging details: - **Built for CD**: Helmsman can be used as a docker image or a binary. - **Applications as code**: describe your desired applications and manage them from a single version-controlled declarative file. - **Suitable for Multitenant Clusters**: deploy Tiller in different namespaces with service accounts and TLS. -- **Easy to use**: deep knowledge of Helm CLI and Kubectl is NOT manadatory to use Helmsman. +- **Easy to use**: deep knowledge of Helm CLI and Kubectl is NOT mandatory to use Helmsman. - **Plan, View, apply**: you can run Helmsman to generate and view a plan with/without executing it. - **Portable**: Helmsman can be used to manage charts deployments on any k8s cluster. - **Protect Namespaces/Releases**: you can define certain namespaces/releases to be protected against accidental human mistakes. @@ -46,9 +46,9 @@ To show debugging details: Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. ``` # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v1.3.0-rc/helmsman_1.3.0-rc_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.4.0-rc/helmsman_1.4.0-rc_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v1.3.0-rc/helmsman_1.3.0-rc_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.4.0-rc/helmsman_1.4.0-rc_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` @@ -56,7 +56,7 @@ mv helmsman /usr/local/bin/helmsman ## As a docker image Check the images on [dockerhub](https://hub.docker.com/r/praqma/helmsman/tags/) -# Documentaion +# Documentation Documentation and How-Tos can be found [here](https://github.com/Praqma/helmsman/blob/master/docs/). Helmsman lets you: @@ -85,4 +85,4 @@ Helmsman can be used in three different settings: # Contributing -Pull requests, feeback/feature requests are welcome. Please check our [contribution guide](CONTRIBUTION.md). +Pull requests, feedback/feature requests are welcome. Please check our [contribution guide](CONTRIBUTION.md). From 4bd1b3111ed986430c9402569f8484ee188cf93a Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Wed, 1 Aug 2018 16:50:28 +0200 Subject: [PATCH 0192/1127] fixing typos --- docs/best_practice.md | 4 ++-- docs/how_to/move_charts_across_namespaces.md | 2 +- .../how_to/protect_namespaces_and_releases.md | 6 +++--- .../run_helmsman_with_hosted_cluster.md | 4 ++-- docs/how_to/test_charts.md | 4 ++-- docs/how_to/use_the_priority_key.md | 20 +++++++++---------- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/docs/best_practice.md b/docs/best_practice.md index f9c122a5..5a933d07 100644 --- a/docs/best_practice.md +++ b/docs/best_practice.md @@ -8,7 +8,7 @@ When using Helmsman, we recommend the following best practices: - Add useful metadata in your desired state files (DSFs) so that others (who have access to them) can make understand what your DSF is for. We recommend the following metadata: organization, maintainer (name and email), and description/purpose. -- Use environment variabels to pass K8S connection secrets (password, certificates paths on the local system or AWS/GCS bucket urls and the API URI). This keeps all sensitive information out of your version controlled source code. +- Use environment variables to pass K8S connection secrets (password, certificates paths on the local system or AWS/GCS bucket urls and the API URI). This keeps all sensitive information out of your version controlled source code. - Define certain namespaces (e.g, production) as protected namespaces (supported in v1.0.0+) and deploy your production-ready releases there. @@ -16,5 +16,5 @@ When using Helmsman, we recommend the following best practices: - Don't maintain the same release in multiple DSFs. -- While the decison on how many DSFs to use and what each can containt is up to you and dependes on your case, we recommend coming up with your own rule for that and following it. +- While the decision on how many DSFs to use and what each can contain is up to you and depends on your case, we recommend coming up with your own rule for that and following it. diff --git a/docs/how_to/move_charts_across_namespaces.md b/docs/how_to/move_charts_across_namespaces.md index 3a0cf858..6a6a272e 100644 --- a/docs/how_to/move_charts_across_namespaces.md +++ b/docs/how_to/move_charts_across_namespaces.md @@ -109,7 +109,7 @@ Helmsman will delete the jenkins release from the `staging` namespace and instal ## Note on Persistent Volumes -Helmsman does not automatically move PVCs across namespaces. You have to follow the steps below to retain your data when moving an app to a different namesapce. +Helmsman does not automatically move PVCs across namespaces. You have to follow the steps below to retain your data when moving an app to a different namespace. Persistent Volumes (PV) are accessed through Persistent Volume Claims (PVC). But **PVCs are namespaced object** which means moving an application from one namespace to another will result in a new PVC created in the new namespace. The old PV -which possibly contains your application data- will still be mounted to the old PVC (the one in the old namespace) even if you have deleted your application helm release. diff --git a/docs/how_to/protect_namespaces_and_releases.md b/docs/how_to/protect_namespaces_and_releases.md index 4b66097d..34c74366 100644 --- a/docs/how_to/protect_namespaces_and_releases.md +++ b/docs/how_to/protect_namespaces_and_releases.md @@ -8,7 +8,7 @@ Since helmsman is used with version controlled code and is often configured to b Helmsman provides the `plan` flag which helps you see the actions that it will take based on your desired state file before actually doing them. We recommend to use a `plan-hold-approve` pipeline when using helmsman with production clusters. -As of version v1.0.0, helmsman provides a fine-grained mechansim to protect releases/namespaces from accidental desired state file changes. +As of version v1.0.0, helmsman provides a fine-grained mechanism to protect releases/namespaces from accidental desired state file changes. ## Protection definition @@ -70,11 +70,11 @@ apps: protected: true # defining this release to be protected. ``` -> All releases in a protected namespace are automatically protected. Namespace protection has higher priority than the relase-level protection. +> All releases in a protected namespace are automatically protected. Namespace protection has higher priority than the release-level protection. ## Important Notes - You can combine both types of protection in your desired state file. The namespace-level protection always has a higher priority. - Removing the protection from a namespace means all releases deployed in that namespace are no longer protected. - We recommend using namespace-level protection for production namespace(s) and release-level protection for releases deployed in other namespaces. -- Release/namespace protection is only applied on single desired state files. It is your responsibility to make sure that multiple desired state files (if used) do not conflict with each other (e.g, one defines a particular namespace as defined and another defines it unprotected.) If you use multiple desired state files with the same cluster, please refer to [deploymemt strategies](../deplyment_strategies.md) and [best practice](../best_practice.md) documentation. \ No newline at end of file +- Release/namespace protection is only applied on single desired state files. It is your responsibility to make sure that multiple desired state files (if used) do not conflict with each other (e.g, one defines a particular namespace as defined and another defines it unprotected.) If you use multiple desired state files with the same cluster, please refer to [deployment strategies](../deployment_strategies.md) and [best practice](../best_practice.md) documentation. \ No newline at end of file diff --git a/docs/how_to/run_helmsman_with_hosted_cluster.md b/docs/how_to/run_helmsman_with_hosted_cluster.md index 047155e6..c2a6f9cd 100644 --- a/docs/how_to/run_helmsman_with_hosted_cluster.md +++ b/docs/how_to/run_helmsman_with_hosted_cluster.md @@ -7,7 +7,7 @@ You can manage Helm charts deployment on a hosted K8S cluster in the cloud or on **IMPORTANT**: Helmsman expects certain environment variables to be available depending on where your cluster and connection certificates are hosted. Certificates can be used from S3/GCS buckets or local file system. ## AWS -If you use s3 buckets for storing certificates or for hosting private helm repos, Helmsman needs valid AWS access keys to be able to retrieve private charts or certificates from your s3 buckets. It expects the keys to be in the following environemnt variables: +If you use s3 buckets for storing certificates or for hosting private helm repos, Helmsman needs valid AWS access keys to be able to retrieve private charts or certificates from your s3 buckets. It expects the keys to be in the following environment variables: - AWS_ACCESS_KEY_ID - AWS_SECRET_ACCESS_KEY @@ -21,7 +21,7 @@ If you use GCS buckets for storing certificates or for hosting private helm repo check [here](https://www.terraform.io/docs/providers/google/index.html#authentication-json-file) for getting the required authentication file. -## Additional environemnt variables +## Additional environment variables The K8S user password is expected in an environment variable which you can give any name you want and define it in your desired state file. Additionally, you can optionally use environment variables to provide certificate paths and clusterURI. diff --git a/docs/how_to/test_charts.md b/docs/how_to/test_charts.md index 3c12d0c2..03a4ac92 100644 --- a/docs/how_to/test_charts.md +++ b/docs/how_to/test_charts.md @@ -4,7 +4,7 @@ version: v1.3.0-rc # test charts -You can specifiy that you would like a chart to be tested whenever it is installed for the first time using the `test` key as follows: +You can specify that you would like a chart to be tested whenever it is installed for the first time using the `test` key as follows: ```toml ... @@ -19,7 +19,7 @@ You can specifiy that you would like a chart to be tested whenever it is install version = "0.9.1" valuesFile = "" purge = false - test = true # setting this to true, means you want the charts tests to be run on this release when it is intalled. + test = true # setting this to true, means you want the charts tests to be run on this release when it is installed. ... diff --git a/docs/how_to/use_the_priority_key.md b/docs/how_to/use_the_priority_key.md index b1ce82c6..5792f6a5 100644 --- a/docs/how_to/use_the_priority_key.md +++ b/docs/how_to/use_the_priority_key.md @@ -4,9 +4,9 @@ version: v1.3.0-rc # Using the priority key for Apps -The `priority` flag in Apps definition allows you to define the order at which apps operations will be applied. This is useful if you have dependecies between your apps/services. +The `priority` flag in Apps definition allows you to define the order at which apps operations will be applied. This is useful if you have dependencies between your apps/services. -Priority is an optinal flag and has a default value of 0 (zero). If set, it can only use a negative value. The lower the value, the higher the priority. +Priority is an optional flag and has a default value of 0 (zero). If set, it can only use a negative value. The lower the value, the higher the priority. ## Example @@ -35,8 +35,8 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" [apps.jenkins] name = "jenkins" # should be unique across all apps description = "jenkins" - namespace = "staging" # maps to the namespace as defined in environmetns above - enabled = true # change to false if you want to delete this app release [empty = flase] + namespace = "staging" # maps to the namespace as defined in environments above + enabled = true # change to false if you want to delete this app release [empty = false] chart = "stable/jenkins" # changing the chart name means delete and recreate this chart version = "0.14.3" # chart version valuesFile = "" # leaving it empty uses the default chart values @@ -45,8 +45,8 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" [apps.jenkins1] name = "jenkins1" # should be unique across all apps description = "jenkins" - namespace = "staging" # maps to the namespace as defined in environmetns above - enabled = true # change to false if you want to delete this app release [empty = flase] + namespace = "staging" # maps to the namespace as defined in environments above + enabled = true # change to false if you want to delete this app release [empty = false] chart = "stable/jenkins" # changing the chart name means delete and recreate this chart version = "0.14.3" # chart version valuesFile = "" # leaving it empty uses the default chart values @@ -55,8 +55,8 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" [apps.jenkins2] name = "jenkins2" # should be unique across all apps description = "jenkins" - namespace = "production" # maps to the namespace as defined in environmetns above - enabled = true # change to false if you want to delete this app release [empty = flase] + namespace = "production" # maps to the namespace as defined in environments above + enabled = true # change to false if you want to delete this app release [empty = false] chart = "stable/jenkins" # changing the chart name means delete and recreate this chart version = "0.14.3" # chart version valuesFile = "" # leaving it empty uses the default chart values @@ -65,8 +65,8 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" [apps.artifactory] name = "artifactory" # should be unique across all apps description = "artifactory" - namespace = "staging" # maps to the namespace as defined in environmetns above - enabled = true # change to false if you want to delete this app release [empty = flase] + namespace = "staging" # maps to the namespace as defined in environments above + enabled = true # change to false if you want to delete this app release [empty = false] chart = "stable/artifactory" # changing the chart name means delete and recreate this chart version = "7.0.6" # chart version valuesFile = "" # leaving it empty uses the default chart values From 52fd3f97cd5ca5ea92f0792b34ea21e18daed80a Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Wed, 1 Aug 2018 16:51:03 +0200 Subject: [PATCH 0193/1127] adding v1.4.0-rc migration guide --- docs/migrating_to_v1.4.0-rc.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 docs/migrating_to_v1.4.0-rc.md diff --git a/docs/migrating_to_v1.4.0-rc.md b/docs/migrating_to_v1.4.0-rc.md new file mode 100644 index 00000000..46ded3fe --- /dev/null +++ b/docs/migrating_to_v1.4.0-rc.md @@ -0,0 +1,8 @@ +# Migrating to Helmsman v1.4.0-rc + +This document highlights the main changes between Helmsman v1.4.0-rc and the older versions. While the changes are still backward-compatible, the behavior and the internals have changed. The list below highlights those changes: + +- Helmsman v1.4.0-rc tracks the releases it manages by applying specific labels to their Helm state (stored in Helm's configured backend storage). For smooth transition when upgrading to v1.4.0-rc, you should run `helmsman -f --apply-labels` once. This will label all releases from your desired state as with a `MANAGED-BY=Helmsman` label. The `--apply-labels`is safe to run multiple times. + +- After each run, Helmsman v1.4.0-rc looks for, and deletes any releases with the `MANAGED-BY=Helmsman` label which are no longer existing in your desired state. This means that **deleting/commenting out an app from your desired state file will result in its deletion**. + From b8c07a6875505be5595339c9f954bf281ed13b3b Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Tue, 7 Aug 2018 14:00:35 +0200 Subject: [PATCH 0194/1127] fixing tests --- plan_test.go | 3 ++- release_test.go | 46 +++++----------------------------------------- 2 files changed, 7 insertions(+), 42 deletions(-) diff --git a/plan_test.go b/plan_test.go index 9c8dc727..cf8038c2 100644 --- a/plan_test.go +++ b/plan_test.go @@ -62,7 +62,8 @@ func Test_plan_addCommand(t *testing.T) { Decisions: tt.fields.Decisions, Created: tt.fields.Created, } - p.addCommand(tt.args.c, 0) + r := &release{} + p.addCommand(tt.args.c, 0, r) if got := len(p.Commands); got != 1 { t.Errorf("addCommand(): got %v, want 1", got) } diff --git a/release_test.go b/release_test.go index 21e4a6e9..cca936a9 100644 --- a/release_test.go +++ b/release_test.go @@ -96,7 +96,7 @@ func Test_validateRelease(t *testing.T) { s: st, }, want: false, - want1: "release name can't be empty and must be unique.", + want1: "release name must be unique within a given Tiller.", }, { name: "test case 5", args: args{ @@ -113,8 +113,8 @@ func Test_validateRelease(t *testing.T) { }, s: st, }, - want: false, - want1: "release name can't be empty and must be unique.", + want: true, + want1: "", }, { name: "test case 6", args: args{ @@ -262,10 +262,10 @@ func Test_validateRelease(t *testing.T) { want1: "", }, } - names := make(map[string]bool) + names := make(map[string]map[string]bool) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, got1 := validateRelease(tt.args.r, names, tt.args.s) + got, got1 := validateRelease("testApp", tt.args.r, names, tt.args.s) if got != tt.want { t.Errorf("validateRelease() got = %v, want %v", got, tt.want) } @@ -275,39 +275,3 @@ func Test_validateRelease(t *testing.T) { }) } } - -// func Test_release_print(t *testing.T) { -// type fields struct { -// Name string -// Description string -// Namespace string -// Enabled bool -// Chart string -// Version string -// ValuesFile string -// Purge bool -// Test bool -// } -// tests := []struct { -// name string -// fields fields -// }{ -// // TODO: Add test cases. -// } -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// r := release{ -// Name: tt.fields.Name, -// Description: tt.fields.Description, -// Namespace: tt.fields.Namespace, -// Enabled: tt.fields.Enabled, -// Chart: tt.fields.Chart, -// Version: tt.fields.Version, -// ValuesFile: tt.fields.ValuesFile, -// Purge: tt.fields.Purge, -// Test: tt.fields.Test, -// } -// r.print() -// }) -// } -// } From 5e6bd3f52271e88ec59e03683b4404dd16534fc4 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Tue, 7 Aug 2018 14:01:59 +0200 Subject: [PATCH 0195/1127] removing redundant logs --- kube_helpers.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/kube_helpers.go b/kube_helpers.go index 223c9636..35794edf 100644 --- a/kube_helpers.go +++ b/kube_helpers.go @@ -8,7 +8,6 @@ import ( // validateServiceAccount checks if k8s service account exists in a given namespace // if the provided namespace is empty, it checks in the "default" namespace func validateServiceAccount(sa string, namespace string) (bool, string) { - log.Println("INFO: validating if service account [" + sa + "] exists in namespace [" + namespace + "]") if namespace == "" { namespace = "default" } @@ -17,7 +16,7 @@ func validateServiceAccount(sa string, namespace string) (bool, string) { cmd := command{ Cmd: "bash", Args: []string{"-c", "kubectl get serviceaccount " + sa + ns}, - Description: "validating that serviceaccount [ " + sa + " ] exists in namespace [ " + namespace + " ].", + Description: "validating if serviceaccount [ " + sa + " ] exists in namespace [ " + namespace + " ].", } if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { @@ -194,7 +193,6 @@ func setKubeContext(context string) bool { // createServiceAccount creates a service account in a given namespace and associates it with a cluster-admin role func createServiceAccount(saName string, namespace string) (bool, string) { - log.Println("INFO: creating service account [ " + saName + " ] in namespace [ " + namespace + " ]") cmd := command{ Cmd: "bash", Args: []string{"-c", "kubectl create serviceaccount -n " + namespace + " " + saName}, @@ -225,12 +223,10 @@ func createRoleBinding(role string, saName string, namespace string) (bool, stri bindingOption = "--clusterrole=" + role } - log.Println("INFO: creating " + resource + " for service account [ " + saName + " ] in namespace [ " + namespace + " ] with role: " + role) - cmd := command{ Cmd: "bash", Args: []string{"-c", "kubectl create " + resource + " " + saName + "-binding " + bindingOption + " --serviceaccount " + namespace + ":" + saName + " -n " + namespace}, - Description: "creating " + resource + " for [ " + saName + " ] in namespace [ " + namespace + " ]", + Description: "creating " + resource + " for service account [ " + saName + " ] in namespace [ " + namespace + " ] with role: " + role, } exitCode, err := cmd.exec(debug, verbose) @@ -252,8 +248,6 @@ func createRole(namespace string) (bool, string) { } replaceStringInFile(resource, "temp-modified-role.yaml", map[string]string{"<>": namespace}) - log.Println("INFO: creating role [helmsman-tiller] in namespace [ " + namespace + " ] ") - cmd := command{ Cmd: "bash", Args: []string{"-c", "kubectl apply -f temp-modified-role.yaml "}, From 7dc28140a6592f3d319facd76c02c25dac91ded5 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Tue, 7 Aug 2018 14:04:30 +0200 Subject: [PATCH 0196/1127] fixing slack message format --- utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils.go b/utils.go index 3d169175..9dba11ad 100644 --- a/utils.go +++ b/utils.go @@ -305,7 +305,7 @@ func notifySlack(content string, url string, failure bool, executing bool) bool "pretext": "` + pretext + `", "title": "` + content + `", - "footer": "Helmsman "` + version + `", + "footer": "Helmsman ` + version + `", "ts": ` + strconv.FormatInt(t.Unix(), 10) + ` } ] From 73b8284f00e68a1c9af8ca33746da76e0ed4adc3 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Tue, 7 Aug 2018 14:08:59 +0200 Subject: [PATCH 0197/1127] updating release notes for v1.4.0-rc --- release-notes.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/release-notes.md b/release-notes.md index 3541d466..5272875c 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,4 +1,16 @@ -# v1.3.0-rc +# v1.4.0-rc -- Support for using YAML for desired state files. -- Support for passing values directly through the `set` flag in the desired state file. \ No newline at end of file +> If you are already using an older version of Helmsman, please read the changes below carefully and follow the upgrade guide [here](docs/migrating_to_v1.4.0-rc.md) + +- Slack notifications for Helmsman plan and execution results. Issue #49 +- RBAC Improvements: + - Validation for service accounts to be used for deploying Tillers. Issue #47 + - Support creating RBAC service accounts for configuring Tiller(s) if they don't exist. Issue 48# +- Improvements for Multi-tenancy: + - Adding `tillerNamespace` option for releases to select which Tiller should manage a release. Issue #32 + - Allowing releases with the same name to be deployed with different Tillers. Issue #50 + - Tracking Helmsman managed releases with special Helmsman labels. +- Adding `--apply-labels` flag to label releases defined in the desired state file with Helmsman's labels. +- Making the name option for Apps optional and using the app name from the (toml/yaml) desired state as a release name when this option is not set. +- Changing Helmsman behavior when removing/commenting out a release in the Apps section of the desired state. Removing/commenting out a release in the desired state will result in **deleting** the release if it's labeled as `managed-by Helmsman`. + \ No newline at end of file From 9fff41da217afa5b35e154507b686eca34c22e85 Mon Sep 17 00:00:00 2001 From: Javier Domingo Cansino Date: Sat, 11 Aug 2018 07:44:17 +0100 Subject: [PATCH 0198/1127] Helmsman is now packaged in archlinux --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 4879cad8..166de2cf 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,9 @@ mv helmsman /usr/local/bin/helmsman ## As a docker image Check the images on [dockerhub](https://hub.docker.com/r/praqma/helmsman/tags/) +## As a package +Helmsman has been packaged in Archlinux under `helmsman-bin` for the latest binary release, and `helmsman-git` for master. + # Documentation Documentation and How-Tos can be found [here](https://github.com/Praqma/helmsman/blob/master/docs/). From 69fc617ab5ef62cb44ae1c4b5a9b9731a8ecaaed Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Wed, 15 Aug 2018 19:20:07 +0200 Subject: [PATCH 0199/1127] supporting optional Tiller deployment in kube-system #53 --- helm_helpers.go | 33 +++++++++------------------------ main.go | 4 +++- state.go | 4 ++-- utils.go | 6 +++--- 4 files changed, 17 insertions(+), 30 deletions(-) diff --git a/helm_helpers.go b/helm_helpers.go index 4e742b44..acf6de4e 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -23,9 +23,12 @@ type releaseState struct { // getAllReleases fetches a list of all releases in a k8s cluster func getAllReleases() string { - result := getTillerReleases("kube-system") + result := "" + if _, ok := s.Namespaces["kube-system"]; !ok { + result = result + getTillerReleases("kube-system") + } for ns, v := range s.Namespaces { - if v.InstallTiller && ns != "kube-system" { + if v.InstallTiller { result = result + getTillerReleases(ns) } } @@ -171,26 +174,6 @@ func getReleaseChartVersion(rs releaseState) string { return string(runes[strings.LastIndexByte(chart, '-')+1 : len(chart)]) } -// DEPRECATED -// getReleaseStatus returns the output of Helm status command for a release. -// if the release does not exist, it returns an empty string without breaking the program execution. -func getReleaseStatus(releaseName string) string { - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm status " + releaseName + getNSTLSFlags("kube-system")}, - Description: "inspecting the status of release: " + releaseName, - } - - exitCode, result := cmd.exec(debug, verbose) - if exitCode == 0 { - return result - } - - log.Fatal("ERROR: while checking release [ " + releaseName + " ] status: " + result) - - return "" -} - // getNSTLSFlags returns TLS flags for a given namespace if it's deployed with TLS func getNSTLSFlags(ns string) string { tls := "" @@ -365,8 +348,10 @@ func initHelm() (bool, string) { } if v, ok := s.Namespaces["kube-system"]; ok { - if ok, err := deployTiller("kube-system", v.TillerServiceAccount, defaultSA); !ok { - return false, err + if v.InstallTiller { + if ok, err := deployTiller("kube-system", v.TillerServiceAccount, defaultSA); !ok { + return false, err + } } } else { if ok, err := deployTiller("kube-system", "", defaultSA); !ok { diff --git a/main.go b/main.go index 573dba43..63d55781 100644 --- a/main.go +++ b/main.go @@ -42,7 +42,9 @@ func main() { } } - waitForTiller("kube-system") + if _, ok := s.Namespaces["kube-system"]; !ok { + waitForTiller("kube-system") + } if verbose { logVersions() diff --git a/state.go b/state.go index 7626286a..cc66e024 100644 --- a/state.go +++ b/state.go @@ -104,8 +104,8 @@ func (s state) validate() (bool, string) { } for k, v := range s.Namespaces { - if !v.InstallTiller && k != "kube-system" { - log.Println("INFO: namespace validation -- Tiller is not desired to be deployed in namespace [ " + k + " ].") + if !v.InstallTiller { + log.Println("INFO: namespace validation -- Tiller is NOT desired to be deployed in namespace [ " + k + " ].") } else { if tillerTLSEnabled(k) { // validating the TLS certs and keys for Tiller diff --git a/utils.go b/utils.go index 9dba11ad..aceb1702 100644 --- a/utils.go +++ b/utils.go @@ -182,15 +182,15 @@ func logVersions() { cmd = command{ Cmd: "bash", - Args: []string{"-c", "helm version"}, - Description: "Helm version: ", + Args: []string{"-c", "helm version -c"}, + Description: "Helm client version: ", } exitCode, result = cmd.exec(debug, false) if exitCode != 0 { log.Fatal("ERROR: while checking helm version: " + result) } - log.Println("VERBOSE: helm version: \n" + result + "\n") + log.Println("VERBOSE: Helm client version: \n" + result + "\n") } // envVarExists checks if an environment variable is set or not and returns it. From 475f96c291188147f5b622b017d3c0a59db8898a Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Wed, 15 Aug 2018 19:21:05 +0200 Subject: [PATCH 0200/1127] fixes #58 --- release.go | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/release.go b/release.go index b82d5634..122ed1b6 100644 --- a/release.go +++ b/release.go @@ -32,24 +32,36 @@ func validateRelease(appLabel string, r *release, names map[string]map[string]bo _, err := os.Stat(r.ValuesFile) if r.Name == "" { r.Name = appLabel - } else if r.TillerNamespace != "" && r.TillerNamespace != "kube-system" { + } + if r.TillerNamespace != "" { if v, ok := s.Namespaces[r.TillerNamespace]; !ok { return false, "tillerNamespace specified, but the namespace specified does not exist!" } else if !v.InstallTiller { return false, "tillerNamespace specified, but that namespace does not have installTiller set to true." } - } else if names[r.Name][getDesiredTillerNamespace(r)] { + } else if getDesiredTillerNamespace(r) == "kube-system" { + if v, ok := s.Namespaces["kube-system"]; ok && !v.InstallTiller { + return false, "app is desired to be deployed using Tiller from [[ kube-system ]] but kube-system is not desired to have a Tiller. You can use another Tiller with the 'tillerNamespace' option or deploy Tiller in kube-system. " + } + } + if names[r.Name][getDesiredTillerNamespace(r)] { return false, "release name must be unique within a given Tiller." - } else if nsOverride == "" && r.Namespace == "" { + } + + if nsOverride == "" && r.Namespace == "" { return false, "release targeted namespace can't be empty." } else if nsOverride == "" && r.Namespace != "" && !checkNamespaceDefined(r.Namespace, s) { return false, "release " + r.Name + " is using namespace [ " + r.Namespace + " ] which is not defined in the Namespaces section of your desired state file." + " Release [ " + r.Name + " ] can't be installed in that Namespace until its defined." - } else if r.Chart == "" || !strings.ContainsAny(r.Chart, "/") { + } + if r.Chart == "" || !strings.ContainsAny(r.Chart, "/") { return false, "chart can't be empty and must be of the format: repo/chart." - } else if r.Version == "" { + } + if r.Version == "" { return false, "version can't be empty." - } else if r.ValuesFile != "" && (!isOfType(r.ValuesFile, ".yaml") || err != nil) { + } + + if r.ValuesFile != "" && (!isOfType(r.ValuesFile, ".yaml") || err != nil) { return false, "valuesFile must be a valid file path for a yaml file, Or can be left empty." } else if r.ValuesFile != "" && len(r.ValuesFiles) > 0 { return false, "valuesFile and valuesFiles should not be used together." @@ -59,7 +71,9 @@ func validateRelease(appLabel string, r *release, names map[string]map[string]bo return false, "the value for valueFile '" + filePath + "' must be a valid file path for a yaml file." } } - } else if r.Priority != 0 && r.Priority > 0 { + } + + if r.Priority != 0 && r.Priority > 0 { return false, "priority can only be 0 or negative value, positive values are not allowed." } From e8deec97eedc895c45f6afea6f18606ea4c64bf1 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Mon, 20 Aug 2018 10:04:15 +0200 Subject: [PATCH 0201/1127] providing a cmd option to keep untracked releases. --- helm_helpers.go | 22 ++++++++++++---------- init.go | 1 + main.go | 5 ++++- utils.go | 19 ++++++++++--------- 4 files changed, 27 insertions(+), 20 deletions(-) diff --git a/helm_helpers.go b/helm_helpers.go index acf6de4e..087077b3 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -379,18 +379,20 @@ func cleanUntrackedReleases() { toDelete := make(map[string]map[string]bool) log.Println("INFO: checking if any Helmsman managed releases are no longer tracked by your desired state ...") for ns, releases := range getHelmsmanReleases() { - for r := range releases { - tracked := false - for _, app := range s.Apps { - if app.Name == r && getDesiredTillerNamespace(app) == ns { - tracked = true + if v, ok := s.Namespaces[ns]; ok && v.InstallTiller { + for r := range releases { + tracked := false + for _, app := range s.Apps { + if app.Name == r && getDesiredTillerNamespace(app) == ns { + tracked = true + } } - } - if !tracked { - if _, ok := toDelete[ns]; !ok { - toDelete[ns] = make(map[string]bool) + if !tracked { + if _, ok := toDelete[ns]; !ok { + toDelete[ns] = make(map[string]bool) + } + toDelete[ns][r] = true } - toDelete[ns][r] = true } } } diff --git a/init.go b/init.go index 5b799fb1..0709a7a0 100644 --- a/init.go +++ b/init.go @@ -30,6 +30,7 @@ func init() { flag.StringVar(&nsOverride, "ns-override", "", "override defined namespaces with this one") flag.BoolVar(&skipValidation, "skip-validation", false, "skip desired state validation") flag.BoolVar(&applyLabels, "apply-labels", false, "apply Helmsman labels to Helm state for all defined apps.") + flag.BoolVar(&keepUntrackedReleases, "keep-untracked-releases", false, "keep releases that are managed by Helmsman and are no longer tracked in your desired state.") flag.Parse() diff --git a/main.go b/main.go index 63d55781..bf21361f 100644 --- a/main.go +++ b/main.go @@ -16,6 +16,7 @@ var nsOverride string var checkCleanup bool var skipValidation bool var applyLabels bool +var keepUntrackedReleases bool var version = "v1.4.0-rc" func main() { @@ -67,7 +68,9 @@ func main() { log.Println("INFO: checking what I need to do for your charts ... ") p := makePlan(&s) - cleanUntrackedReleases() + if !keepUntrackedReleases { + cleanUntrackedReleases() + } p.sortPlan() p.printPlan() diff --git a/utils.go b/utils.go index aceb1702..f652425b 100644 --- a/utils.go +++ b/utils.go @@ -154,15 +154,16 @@ func printHelp() { fmt.Println("Usage: helmsman [options]") fmt.Println() fmt.Println("Options:") - fmt.Println("-f specifies the desired state TOML file.") - fmt.Println("--debug prints basic logs during execution.") - fmt.Println("--apply generates and applies an action plan.") - fmt.Println("--verbose prints more verbose logs during execution.") - fmt.Println("--ns-override overrides defined namespaces with a provided one.") - fmt.Println("--skip-validation generates and applies an action plan.") - fmt.Println("--apply-labels applies Helmsman labels to Helm state for all defined apps.") - fmt.Println("--help prints Helmsman help.") - fmt.Println("-v prints Helmsman version.") + fmt.Println("-f specifies the desired state TOML file.") + fmt.Println("--debug prints basic logs during execution.") + fmt.Println("--apply generates and applies an action plan.") + fmt.Println("--verbose prints more verbose logs during execution.") + fmt.Println("--ns-override overrides defined namespaces with a provided one.") + fmt.Println("--skip-validation generates and applies an action plan.") + fmt.Println("--apply-labels applies Helmsman labels to Helm state for all defined apps.") + fmt.Println("--keep-untracked-releases keep releases that are managed by Helmsman and are no longer tracked in your desired state.") + fmt.Println("--help prints Helmsman help.") + fmt.Println("-v prints Helmsman version.") } // logVersions prints the versions of kubectl and helm to the logs From b22610d2cfb85eb24ca7953b01dadd1ba239c4b8 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Mon, 20 Aug 2018 11:16:18 +0200 Subject: [PATCH 0202/1127] allowing apps to be deployed to kube-system if it is not defined in namespaces section --- release.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release.go b/release.go index 122ed1b6..dd862a9c 100644 --- a/release.go +++ b/release.go @@ -50,7 +50,7 @@ func validateRelease(appLabel string, r *release, names map[string]map[string]bo if nsOverride == "" && r.Namespace == "" { return false, "release targeted namespace can't be empty." - } else if nsOverride == "" && r.Namespace != "" && !checkNamespaceDefined(r.Namespace, s) { + } else if nsOverride == "" && r.Namespace != "" && r.Namespace != "kube-system" && !checkNamespaceDefined(r.Namespace, s) { return false, "release " + r.Name + " is using namespace [ " + r.Namespace + " ] which is not defined in the Namespaces section of your desired state file." + " Release [ " + r.Name + " ] can't be installed in that Namespace until its defined." } From e765676c1f5e8c201dd620e6bce75b850a4976b3 Mon Sep 17 00:00:00 2001 From: John Norwood Date: Thu, 23 Aug 2018 19:08:50 +0000 Subject: [PATCH 0203/1127] Adds the ability to specify multiple dsf files with repeated -f arguments --- CONTRIBUTION.md | 3 +- dockerfile/dockerfile | 2 ++ docs/how_to/merge_desired_state_files.md | 44 ++++++++++++++++++++++++ init.go | 21 +++++++---- main.go | 13 ++++++- test_files/dockerfile | 5 +-- utils.go | 5 ++- 7 files changed, 79 insertions(+), 14 deletions(-) create mode 100644 docs/how_to/merge_desired_state_files.md diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md index 3c99751b..5932a678 100644 --- a/CONTRIBUTION.md +++ b/CONTRIBUTION.md @@ -11,6 +11,7 @@ git clone https://github.com/Praqma/helmsman.git go get github.com/BurntSushi/toml go get github.com/Praqma/helmsman/gcs go get github.com/Praqma/helmsman/aws +go get github.com/imdario/mergo TAG=$(git describe --abbrev=0 --tags)-$(date +"%s") go build -ldflags '-X main.version='$TAG' -extldflags "-static"' ``` @@ -21,7 +22,7 @@ Please make sure you state the purpose of the pull request and that the code you ## Contribution to documentation -Contribution to the documentation can be done via pull requests or by openeing an issue. +Contribution to the documentation can be done via pull requests or by opening an issue. ## Reporting issues/featuer requests diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile index 1bb6f2df..c63f8485 100644 --- a/dockerfile/dockerfile +++ b/dockerfile/dockerfile @@ -5,6 +5,8 @@ RUN go get gopkg.in/yaml.v2 RUN git clone https://github.com/Praqma/helmsman.git RUN go get github.com/Praqma/helmsman/gcs RUN go get github.com/Praqma/helmsman/aws +RUN go get github.com/imdario/mergo + # build a statically linked binary so that it works on stripped linux images such as alpine/busybox. RUN cd helmsman \ && LastTag=$(git describe --abbrev=0 --tags) \ diff --git a/docs/how_to/merge_desired_state_files.md b/docs/how_to/merge_desired_state_files.md new file mode 100644 index 00000000..015b14c6 --- /dev/null +++ b/docs/how_to/merge_desired_state_files.md @@ -0,0 +1,44 @@ +--- +version: v1.5.0-rc +--- + +# supply multiple desired state files + +Starting from v1.5.0-rc, Helmsman allows you to pass the `-f` flag multiple times to specify multiple desired state files +that should be merged. This allows us to do things like specify our non-environment-specific config in a `common.toml` file +and environment specific info in a `nonprod.toml` or `prod.toml` file. This process uses [this library](https://github.com/imdario/mergo) +to do the merging, and is subject to the limitations described there. + +For example: + +* common.toml: +```toml +[metadata] +org = "Organization Name" +maintainer = "project-owners@example.com" +description = "Project charts" + +[settings] +serviceAccount = "tiller" +storageBackend = "secret" +... +``` + +* nonprod.toml: +```toml +[settings] +kubeContext = "cluster-nonprod" + +[apps] + [apps.external-dns] + valuesFiles = ["./external-dns/values.yaml", "./external-dns/nonprod.yaml"] + + [apps.cert-issuer] + valuesFile = "./cert-issuer/nonprod.yaml" +... +``` + +One can then run the following to use the merged config of the above files, with later files override values of earlier ones: +```bash +$ helmsman -f common.toml -f nonprod.toml ... +``` diff --git a/init.go b/init.go index 0709a7a0..e67759ee 100644 --- a/init.go +++ b/init.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "github.com/imdario/mergo" ) const ( @@ -21,7 +22,7 @@ const ( // It checks if Helm and Kubectl exist and configures: the connection to the k8s cluster, helm repos, namespaces, etc. func init() { //parsing command line flags - flag.StringVar(&file, "f", "example.toml", "desired state file name") + flag.Var(&files, "f", "desired state file name(s), may be supplied more than once to merge state files") flag.BoolVar(&apply, "apply", false, "apply the plan directly") flag.BoolVar(&debug, "debug", false, "show the execution logs") flag.BoolVar(&help, "help", false, "show Helmsman help") @@ -57,11 +58,18 @@ func init() { } // read the TOML/YAML desired state file - result, msg := fromFile(file, &s) - if result { - log.Printf(msg) - } else { - log.Fatal(msg) + var fileState state + for _, f := range files { + result, msg := fromFile(f, &fileState) + if result { + log.Printf(msg) + } else { + log.Fatal(msg) + } + + if err := mergo.Merge(&s, &fileState); err != nil { + log.Fatalf("Failed to merge desired state file %s", f) + } } if !skipValidation { @@ -75,7 +83,6 @@ func init() { if applyLabels { for _, r := range s.Apps { - labelResource(r) } } diff --git a/main.go b/main.go index bf21361f..335e81a2 100644 --- a/main.go +++ b/main.go @@ -5,9 +5,20 @@ import ( "os" ) +// Allow parsing of multiple string command line options into an array of strings +type stringArray []string +func (i *stringArray) String() string { + return "my string representation" +} + +func (i *stringArray) Set(value string) error { + *i = append(*i, value) + return nil +} + var s state var debug bool -var file string +var files stringArray var apply bool var help bool var v bool diff --git a/test_files/dockerfile b/test_files/dockerfile index eea05ba7..7637b4e9 100644 --- a/test_files/dockerfile +++ b/test_files/dockerfile @@ -18,6 +18,7 @@ RUN apk add --update --no-cache ca-certificates git \ && rm -rf /tmp/linux-amd64 RUN go get github.com/BurntSushi/toml \ - && go get github.com/goreleaser/goreleaser + && go get github.com/goreleaser/goreleaser \ + && go get github.com/imdario/mergo -WORKDIR src/helmsman \ No newline at end of file +WORKDIR src/helmsman diff --git a/utils.go b/utils.go index f652425b..fbaf779e 100644 --- a/utils.go +++ b/utils.go @@ -38,12 +38,11 @@ func printNamespacesMap(m map[string]namespace) { // fromTOML reads a toml file and decodes it to a state type. // It uses the BurntSuchi TOML parser which throws an error if the TOML file is not valid. func fromTOML(file string, s *state) (bool, string) { - if _, err := toml.DecodeFile(file, s); err != nil { return false, err.Error() } - return true, "INFO: Parsed TOML [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps." + return true, "INFO: Parsed TOML [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps." } // toTOML encodes a state type into a TOML file. @@ -154,7 +153,7 @@ func printHelp() { fmt.Println("Usage: helmsman [options]") fmt.Println() fmt.Println("Options:") - fmt.Println("-f specifies the desired state TOML file.") + fmt.Println("-f desired state file name(s), may be supplied more than once to merge state files.") fmt.Println("--debug prints basic logs during execution.") fmt.Println("--apply generates and applies an action plan.") fmt.Println("--verbose prints more verbose logs during execution.") From 95e5b7c83573ffa7e29f0aaae3c3e42be36193ab Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sun, 26 Aug 2018 22:15:46 +0100 Subject: [PATCH 0204/1127] use helm's json output support versions of helm that don't include AppVersion --- bindata.go | 3 ++- helm_helpers.go | 64 ++++++++++++++++++++++++++++--------------------- 2 files changed, 39 insertions(+), 28 deletions(-) diff --git a/bindata.go b/bindata.go index 6369278b..a837d919 100644 --- a/bindata.go +++ b/bindata.go @@ -13,6 +13,7 @@ import ( "strings" "time" ) + type asset struct { bytes []byte info os.FileInfo @@ -163,6 +164,7 @@ type bintree struct { Func func() (*asset, error) Children map[string]*bintree } + var _bintree = &bintree{nil, map[string]*bintree{ "data": &bintree{nil, map[string]*bintree{ "role.yaml": &bintree{dataRoleYaml, map[string]*bintree{}}, @@ -215,4 +217,3 @@ func _filePath(dir, name string) string { cannonicalName := strings.Replace(name, "\\", "/", -1) return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) } - diff --git a/helm_helpers.go b/helm_helpers.go index 087077b3..a499e471 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "log" "strconv" "strings" @@ -21,15 +22,30 @@ type releaseState struct { TillerNamespace string } +type tillerReleases struct { + Next string `json:"Next"` + Releases []struct { + Name string `json:"Name"` + Revision int `json:"Revision"` + Updated string `json:"Updated"` + Status string `json:"Status"` + Chart string `json:"Chart"` + AppVersion string `json:"AppVersion,omitempty"` + Namespace string `json:"Namespace"` + TillerNamespace string `json:",omitempty"` + } `json:"Releases"` +} + // getAllReleases fetches a list of all releases in a k8s cluster -func getAllReleases() string { - result := "" +func getAllReleases() tillerReleases { + // result := make(map[string]interface{}) + var result tillerReleases if _, ok := s.Namespaces["kube-system"]; !ok { - result = result + getTillerReleases("kube-system") + result.Releases = append(result.Releases, getTillerReleases("kube-system").Releases...) } for ns, v := range s.Namespaces { if v.InstallTiller { - result = result + getTillerReleases(ns) + result.Releases = append(result.Releases, getTillerReleases(ns).Releases...) } } @@ -37,10 +53,10 @@ func getAllReleases() string { } // getTillerReleases gets releases deployed with a given Tiller (in a given namespace) -func getTillerReleases(tillerNS string) string { +func getTillerReleases(tillerNS string) tillerReleases { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm list --all --tiller-namespace " + tillerNS + getNSTLSFlags(tillerNS)}, + Args: []string{"-c", "helm list --all --output json --tiller-namespace " + tillerNS + getNSTLSFlags(tillerNS)}, Description: "listing all existing releases in namespace [ " + tillerNS + " ]...", } @@ -48,43 +64,37 @@ func getTillerReleases(tillerNS string) string { if exitCode != 0 { log.Fatal("ERROR: failed to list all releases in namespace [ " + tillerNS + " ]: " + result) } + var out tillerReleases + json.Unmarshal([]byte(result), &out) // appending tiller-namespace to each release found - lines := strings.Split(result, "\n") - for i, l := range lines { - if l != "" && !strings.HasPrefix(strings.TrimSpace(l), "NAME") && !strings.HasSuffix(strings.TrimSpace(l), "NAMESPACE") { - lines[i] = strings.TrimSuffix(l, "\n") + " " + tillerNS - } + for i := 0; i < len(out.Releases); i++ { + out.Releases[i].TillerNamespace = tillerNS } - return strings.Join(lines, "\n") + + return out } // buildState builds the currentState map contianing information about all releases existing in a k8s cluster func buildState() { log.Println("INFO: mapping the current helm state ...") currentState = make(map[string]releaseState) - lines := strings.Split(getAllReleases(), "\n") + rel := getAllReleases() - for i := 0; i < len(lines); i++ { + for i := 0; i < len(rel.Releases); i++ { // skipping the header from helm output - if lines[i] == "" || (strings.HasPrefix(strings.TrimSpace(lines[i]), "NAME") && strings.HasSuffix(strings.TrimSpace(lines[i]), "NAMESPACE")) { - continue - } - r, _ := strconv.Atoi(strings.Fields(lines[i])[1]) - t := strings.Fields(lines[i])[2] + " " + strings.Fields(lines[i])[3] + " " + strings.Fields(lines[i])[4] + " " + - strings.Fields(lines[i])[5] + " " + strings.Fields(lines[i])[6] - time, err := time.Parse("Mon Jan _2 15:04:05 2006", t) + time, err := time.Parse("Mon Jan _2 15:04:05 2006", rel.Releases[i].Updated) if err != nil { log.Fatal("ERROR: while converting release time: " + err.Error()) } - currentState[strings.Fields(lines[i])[0]+"-"+strings.Fields(lines[i])[10]] = releaseState{ - Revision: r, + currentState[rel.Releases[i].Name+"-"+rel.Releases[i].TillerNamespace] = releaseState{ + Revision: rel.Releases[i].Revision, Updated: time, - Status: strings.Fields(lines[i])[7], - Chart: strings.Fields(lines[i])[8], - Namespace: strings.Fields(lines[i])[9], - TillerNamespace: strings.Fields(lines[i])[10], + Status: rel.Releases[i].Status, + Chart: rel.Releases[i].Chart, + Namespace: rel.Releases[i].Namespace, + TillerNamespace: rel.Releases[i].TillerNamespace, } } } From bf3d343ce22aec0d1b6fde4ab6dfbeb08096157a Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Wed, 29 Aug 2018 20:30:14 +0200 Subject: [PATCH 0205/1127] #55 adding more override options --- decision_maker.go | 25 +++++++++++++++++++++---- docs/desired_state_specification.md | 2 ++ release.go | 6 +++++- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index 4cdf5520..56098974 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -1,6 +1,7 @@ package main import ( + "strconv" "strings" ) @@ -98,7 +99,7 @@ func installRelease(r *release) { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm install " + r.Chart + " -n " + r.Name + " --namespace " + r.Namespace + getValuesFiles(r) + " --version " + r.Version + getSetValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r)}, + Args: []string{"-c", "helm install " + r.Chart + " -n " + r.Name + " --namespace " + r.Namespace + getValuesFiles(r) + " --version " + r.Version + getSetValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r)}, Description: "installing release [ " + r.Name + " ] in namespace [[ " + r.Namespace + " ]] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } outcome.addCommand(cmd, r.Priority, r) @@ -119,7 +120,7 @@ func rollbackRelease(r *release, rs releaseState) { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm rollback " + r.Name + " " + getReleaseRevision(rs) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r)}, + Args: []string{"-c", "helm rollback " + r.Name + " " + getReleaseRevision(rs) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r)}, Description: "rolling back release [ " + r.Name + " ] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } outcome.addCommand(cmd, r.Priority, r) @@ -198,7 +199,7 @@ func inspectUpgradeScenario(r *release, rs releaseState) { func upgradeRelease(r *release) { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + r.Version + " --force " + getSetValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r)}, + Args: []string{"-c", "helm upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + r.Version + " --force " + getSetValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r)}, Description: "upgrading release [ " + r.Name + " ] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } @@ -218,7 +219,7 @@ func reInstallRelease(r *release, rs releaseState) { installCmd := command{ Cmd: "bash", - Args: []string{"-c", "helm install " + r.Chart + " --version " + r.Version + " -n " + r.Name + " --namespace " + r.Namespace + getValuesFiles(r) + getSetValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r)}, + Args: []string{"-c", "helm install " + r.Chart + " --version " + r.Version + " -n " + r.Name + " --namespace " + r.Namespace + getValuesFiles(r) + getSetValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r)}, Description: "installing release [ " + r.Name + " ] in namespace [[ " + r.Namespace + " ]] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } outcome.addCommand(installCmd, r.Priority, r) @@ -240,6 +241,22 @@ func extractChartName(releaseChart string) string { } +// getNoHooks returns the no-hooks flag for install/upgrade commands +func getNoHooks(r *release) string { + if r.NoHooks { + return " --no-hooks " + } + return "" +} + +// getTimeout returns the timeout flag for install/upgrade commands +func getTimeout(r *release) string { + if r.Timeout != 0 { + return " --timeout " + strconv.Itoa(r.Timeout) + } + return "" +} + // getValuesFiles return partial install/upgrade release command to substitute the -f flag in Helm. func getValuesFiles(r *release) string { if r.ValuesFile != "" { diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index c29468a1..10008be1 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -242,6 +242,8 @@ Options: - test : defines whether to run the chart tests whenever the release is installed. - protected : defines if the release should be protected against changes. Namespace-level protection has higher priority than this flag. Check the [protection guide](how_to/protect_namespaces_and_releases.md) for more details. - wait : defines whether Helmsman should block execution until all k8s resources are in a ready state. Default is false. +- timeout : helm timeout in seconds. Default 300 seconds. +- noHooks : helm noHooks option. If true, it will disable pre/post upgrade hooks. Default is false. - priority : defines the priority of applying operations on this release. Only negative values allowed and the lower the value, the higher the priority. Default priority is 0. Apps with equal priorities will be applied in the order they were added in your state file (DSF). - [apps..set] : is used to override certain values from values.yaml with values from environment variables (or ,starting from v1.3.0-rc, directly provided in the Desired State File). This is particularly useful for passing secrets to charts. If the an environment variable with the same name as the provided value exists, the environment variable value will be used, otherwise, the provided value will be used as is. diff --git a/release.go b/release.go index dd862a9c..584edf02 100644 --- a/release.go +++ b/release.go @@ -24,6 +24,8 @@ type release struct { Priority int TillerNamespace string Set map[string]string + NoHooks bool + Timeout int } // validateRelease validates if a release inside a desired state meets the specifications or not. @@ -122,7 +124,9 @@ func (r release) print() { fmt.Println("\tprotected : ", r.Protected) fmt.Println("\twait : ", r.Wait) fmt.Println("\tpriority : ", r.Priority) - fmt.Println("\ttillerNamespace : ", r.TillerNamespace) + fmt.Println("\ttiller namespace : ", r.TillerNamespace) + fmt.Println("\tno-hooks : ", r.NoHooks) + fmt.Println("\ttimeout : ", r.Timeout) fmt.Println("\tvalues to override from env:") printMap(r.Set) fmt.Println("------------------- ") From baedd6e770d8996c2714a67c95da5bcdfb5f2cd5 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Wed, 29 Aug 2018 20:32:04 +0200 Subject: [PATCH 0206/1127] adding a check if state files are provided before running validation. --- init.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/init.go b/init.go index e67759ee..73986879 100644 --- a/init.go +++ b/init.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "github.com/imdario/mergo" ) @@ -74,8 +75,10 @@ func init() { if !skipValidation { // validate the desired state content - if result, msg := s.validate(); !result { // syntax validation - log.Fatal(msg) + if len(files) > 0 { + if result, msg := s.validate(); !result { // syntax validation + log.Fatal(msg) + } } } else { log.Println("INFO: desired state validation is skipped.") From acb108b68729e8b02f85f5948751ce308d353597 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Wed, 29 Aug 2018 20:22:48 +0100 Subject: [PATCH 0207/1127] Update config.yml This should fix the failing CI job. --- .circleci/config.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c1479234..2ba95baa 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -14,6 +14,7 @@ jobs: echo "fetching dependencies ..." go get github.com/Praqma/helmsman/gcs go get github.com/Praqma/helmsman/aws + go get github.com/imdario/mergo echo "building ..." TAG=$(git describe --abbrev=0 --tags)-$(date +"%d%m%y") go build -ldflags '-X main.version='$TAG' -extldflags "-static"' @@ -81,4 +82,4 @@ workflows: branches: only: master tags: - only: /^v.*/ \ No newline at end of file + only: /^v.*/ From 3df10ec650b6deae49ec0093f504f704e724a7f0 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Thu, 30 Aug 2018 10:05:38 +0200 Subject: [PATCH 0208/1127] updating versions extracting --- helm_helpers.go | 15 +++++++++++++++ init.go | 4 ++++ kube_helpers.go | 15 +++++++++++++++ main.go | 5 +---- utils.go | 25 ++----------------------- 5 files changed, 37 insertions(+), 27 deletions(-) diff --git a/helm_helpers.go b/helm_helpers.go index a499e471..95bd8c64 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -36,6 +36,21 @@ type tillerReleases struct { } `json:"Releases"` } +// getHelmClientVersion returns Helm client Version +func getHelmClientVersion() string { + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm version --client --short"}, + Description: "checking Helm version ", + } + + exitCode, result := cmd.exec(debug, false) + if exitCode != 0 { + logError("ERROR: while checking helm version: " + result) + } + return result +} + // getAllReleases fetches a list of all releases in a k8s cluster func getAllReleases() tillerReleases { // result := make(map[string]interface{}) diff --git a/init.go b/init.go index 73986879..fe7f5100 100644 --- a/init.go +++ b/init.go @@ -48,6 +48,10 @@ func init() { os.Exit(0) } + if verbose { + logVersions() + } + //fmt.Println("Helmsman version: " + version) if !toolExists("helm") { diff --git a/kube_helpers.go b/kube_helpers.go index 35794edf..65accdb8 100644 --- a/kube_helpers.go +++ b/kube_helpers.go @@ -331,3 +331,18 @@ func getHelmsmanReleases() map[string]map[string]bool { return releases } + +// getKubectlClientVersion returns kubectl client version +func getKubectlClientVersion() string { + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "kubectl version --client --short"}, + Description: "checking kubectl version ", + } + + exitCode, result := cmd.exec(debug, false) + if exitCode != 0 { + logError("ERROR: while checking kubectl version: " + result) + } + return result +} diff --git a/main.go b/main.go index 335e81a2..04da1608 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,7 @@ import ( // Allow parsing of multiple string command line options into an array of strings type stringArray []string + func (i *stringArray) String() string { return "my string representation" } @@ -58,10 +59,6 @@ func main() { waitForTiller("kube-system") } - if verbose { - logVersions() - } - // add repos -- fails if they are not valid if r, msg := addHelmRepos(s.HelmRepos); !r { logError(msg) diff --git a/utils.go b/utils.go index fbaf779e..0e55d50a 100644 --- a/utils.go +++ b/utils.go @@ -167,30 +167,9 @@ func printHelp() { // logVersions prints the versions of kubectl and helm to the logs func logVersions() { - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "kubectl version"}, - Description: "Kubectl version: ", - } - - exitCode, result := cmd.exec(debug, false) - if exitCode != 0 { - log.Fatal("ERROR: while checking kubectl version: " + result) - } - - log.Println("VERBOSE: kubectl version: \n " + result + "\n") - cmd = command{ - Cmd: "bash", - Args: []string{"-c", "helm version -c"}, - Description: "Helm client version: ", - } - - exitCode, result = cmd.exec(debug, false) - if exitCode != 0 { - log.Fatal("ERROR: while checking helm version: " + result) - } - log.Println("VERBOSE: Helm client version: \n" + result + "\n") + log.Println("VERBOSE: kubectl version: " + getKubectlClientVersion()) + log.Println("VERBOSE: Helm version: " + getHelmClientVersion()) } // envVarExists checks if an environment variable is set or not and returns it. From 10c3f5f09bcd5adede118a7212320c41bbd0c4c8 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Thu, 30 Aug 2018 10:04:18 +0100 Subject: [PATCH 0209/1127] Update config.yml missed a few spots, maybe adding a `Makefile` could simplify this process. --- .circleci/config.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2ba95baa..7a290a41 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -30,6 +30,7 @@ jobs: echo "fetching dependencies ..." go get github.com/Praqma/helmsman/gcs go get github.com/Praqma/helmsman/aws + go get github.com/imdario/mergo echo "running tests ..." go test -v release: @@ -46,6 +47,7 @@ jobs: echo "fetching dependencies ..." go get github.com/Praqma/helmsman/gcs go get github.com/Praqma/helmsman/aws + go get github.com/imdario/mergo echo "releasing ..." goreleaser --release-notes release-notes.md else From 8046cde22aa5a7b5512be267ae98f538cbc4b919 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Thu, 30 Aug 2018 13:21:57 +0200 Subject: [PATCH 0210/1127] #64 adding version checks to support backwards compatability for output formats. --- helm_helpers.go | 39 ++++++++++++++++++++++++++++++++++++--- init.go | 18 ++++++++++-------- main.go | 5 +++-- utils.go | 9 ++++----- 4 files changed, 53 insertions(+), 18 deletions(-) diff --git a/helm_helpers.go b/helm_helpers.go index 95bd8c64..e5748956 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -8,6 +8,7 @@ import ( "time" "github.com/Praqma/helmsman/gcs" + version "github.com/hashicorp/go-version" ) var currentState map[string]releaseState @@ -69,18 +70,50 @@ func getAllReleases() tillerReleases { // getTillerReleases gets releases deployed with a given Tiller (in a given namespace) func getTillerReleases(tillerNS string) tillerReleases { + v1, _ := version.NewVersion(helmVersion) + jsonConstraint, _ := version.NewConstraint(">=2.10.0-rc.1") + var outputFormat string + if jsonConstraint.Check(v1) { + outputFormat = "--output json" + } + cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm list --all --output json --tiller-namespace " + tillerNS + getNSTLSFlags(tillerNS)}, + Args: []string{"-c", "helm list --all " + outputFormat + " --tiller-namespace " + tillerNS + getNSTLSFlags(tillerNS)}, Description: "listing all existing releases in namespace [ " + tillerNS + " ]...", } exitCode, result := cmd.exec(debug, verbose) if exitCode != 0 { - log.Fatal("ERROR: failed to list all releases in namespace [ " + tillerNS + " ]: " + result) + logError("ERROR: failed to list all releases in namespace [ " + tillerNS + " ]: " + result) } var out tillerReleases - json.Unmarshal([]byte(result), &out) + if jsonConstraint.Check(v1) { + json.Unmarshal([]byte(result), &out) + } else { + lines := strings.Split(result, "\n") + index := 0 + for i, l := range lines { + if l == "" || (strings.HasPrefix(strings.TrimSpace(l), "NAME") && strings.HasSuffix(strings.TrimSpace(l), "NAMESPACE")) { + continue + } else { + r, _ := strconv.Atoi(strings.Fields(lines[i])[1]) + t := strings.Fields(lines[i])[2] + " " + strings.Fields(lines[i])[3] + " " + strings.Fields(lines[i])[4] + " " + + strings.Fields(lines[i])[5] + " " + strings.Fields(lines[i])[6] + + out.Releases[index].Revision = r + out.Releases[index].Updated = t + out.Releases[index].Status = strings.Fields(lines[i])[7] + out.Releases[index].Chart = strings.Fields(lines[i])[8] + out.Releases[index].Namespace = strings.Fields(lines[i])[9] + out.Releases[index].Name = strings.Fields(lines[i])[0] + //out.Releases[index].AppVersion = "" + //out.Releases[index].TillerNamespace = tillerNS + index++ + } + } + + } // appending tiller-namespace to each release found for i := 0; i < len(out.Releases); i++ { diff --git a/init.go b/init.go index fe7f5100..869474f8 100644 --- a/init.go +++ b/init.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "strings" "github.com/imdario/mergo" ) @@ -36,10 +37,13 @@ func init() { flag.Parse() - fmt.Println(banner + "version: " + version + "\n" + slogan) + fmt.Println(banner + "version: " + appVersion + "\n" + slogan) + + helmVersion = strings.TrimSpace(strings.SplitN(getHelmClientVersion(), ": ", 2)[1]) + kubectlVersion = strings.TrimSpace(strings.SplitN(getKubectlClientVersion(), ": ", 2)[1]) if v { - fmt.Println("Helmsman version: " + version) + fmt.Println("Helmsman version: " + appVersion) os.Exit(0) } @@ -52,14 +56,12 @@ func init() { logVersions() } - //fmt.Println("Helmsman version: " + version) - if !toolExists("helm") { - log.Fatal("ERROR: helm is not installed/configured correctly. Aborting!") + logError("ERROR: helm is not installed/configured correctly. Aborting!") } if !toolExists("kubectl") { - log.Fatal("ERROR: kubectl is not installed/configured correctly. Aborting!") + logError("ERROR: kubectl is not installed/configured correctly. Aborting!") } // read the TOML/YAML desired state file @@ -69,11 +71,11 @@ func init() { if result { log.Printf(msg) } else { - log.Fatal(msg) + logError(msg) } if err := mergo.Merge(&s, &fileState); err != nil { - log.Fatalf("Failed to merge desired state file %s", f) + logError("Failed to merge desired state file" + f) } } diff --git a/main.go b/main.go index 04da1608..af42016f 100644 --- a/main.go +++ b/main.go @@ -29,10 +29,11 @@ var checkCleanup bool var skipValidation bool var applyLabels bool var keepUntrackedReleases bool -var version = "v1.4.0-rc" +var appVersion = "v1.4.0-rc" +var helmVersion string +var kubectlVersion string func main() { - // set the kubecontext to be used Or create it if it does not exist if !setKubeContext(s.Settings["kubeContext"]) { if r, msg := createContext(); !r { diff --git a/utils.go b/utils.go index 0e55d50a..1f65dabe 100644 --- a/utils.go +++ b/utils.go @@ -148,7 +148,7 @@ func readFile(filepath string) string { // printHelp prints Helmsman commands func printHelp() { - fmt.Println("Helmsman version: " + version) + fmt.Println("Helmsman version: " + appVersion) fmt.Println("Helmsman is a Helm Charts as Code tool which allows you to automate the deployment/management of your Helm charts.") fmt.Println("Usage: helmsman [options]") fmt.Println() @@ -167,9 +167,8 @@ func printHelp() { // logVersions prints the versions of kubectl and helm to the logs func logVersions() { - - log.Println("VERBOSE: kubectl version: " + getKubectlClientVersion()) - log.Println("VERBOSE: Helm version: " + getHelmClientVersion()) + log.Println("VERBOSE: kubectl client version: " + kubectlVersion) + log.Println("VERBOSE: Helm client version: " + helmVersion) } // envVarExists checks if an environment variable is set or not and returns it. @@ -284,7 +283,7 @@ func notifySlack(content string, url string, failure bool, executing bool) bool "pretext": "` + pretext + `", "title": "` + content + `", - "footer": "Helmsman ` + version + `", + "footer": "Helmsman ` + appVersion + `", "ts": ` + strconv.FormatInt(t.Unix(), 10) + ` } ] From 348c31eb0ca99d21febb3ddbe300c71c2ef96722 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Thu, 30 Aug 2018 13:37:55 +0200 Subject: [PATCH 0211/1127] fixing #59 to support relative paths for values files --- decision_maker.go | 5 ++++- decision_maker_test.go | 10 ++++++---- init.go | 11 +++++++++++ main.go | 2 ++ release.go | 8 ++++---- release_test.go | 6 +++--- 6 files changed, 30 insertions(+), 12 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index 56098974..8abdd8c3 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -260,8 +260,11 @@ func getTimeout(r *release) string { // getValuesFiles return partial install/upgrade release command to substitute the -f flag in Helm. func getValuesFiles(r *release) string { if r.ValuesFile != "" { - return " -f " + r.ValuesFile + return " -f " + pwd + "/" + relativeDir + "/" + r.ValuesFile } else if len(r.ValuesFiles) > 0 { + for i := 0; i < len(r.ValuesFiles); i++ { + r.ValuesFiles[i] = pwd + "/" + relativeDir + "/" + r.ValuesFiles[i] + } return " -f " + strings.Join(r.ValuesFiles, " -f ") } return "" diff --git a/decision_maker_test.go b/decision_maker_test.go index def018b0..0f2bb723 100644 --- a/decision_maker_test.go +++ b/decision_maker_test.go @@ -1,6 +1,8 @@ package main -import "testing" +import ( + "testing" +) func Test_getValuesFiles(t *testing.T) { type args struct { @@ -27,7 +29,7 @@ func Test_getValuesFiles(t *testing.T) { }, //s: st, }, - want: " -f test_files/values.yaml", + want: " -f " + pwd + "/" + relativeDir + "/test_files/values.yaml", }, { name: "test case 2", @@ -45,7 +47,7 @@ func Test_getValuesFiles(t *testing.T) { }, //s: st, }, - want: " -f test_files/values.yaml", + want: " -f " + pwd + "/" + relativeDir + "/test_files/values.yaml", }, { name: "test case 1", @@ -63,7 +65,7 @@ func Test_getValuesFiles(t *testing.T) { }, //s: st, }, - want: " -f test_files/values.yaml -f test_files/values2.yaml", + want: " -f " + pwd + "/" + relativeDir + "/test_files/values.yaml -f " + pwd + "/" + relativeDir + "/test_files/values2.yaml", }, } for _, tt := range tests { diff --git a/init.go b/init.go index 869474f8..a03f23ec 100644 --- a/init.go +++ b/init.go @@ -42,6 +42,17 @@ func init() { helmVersion = strings.TrimSpace(strings.SplitN(getHelmClientVersion(), ": ", 2)[1]) kubectlVersion = strings.TrimSpace(strings.SplitN(getKubectlClientVersion(), ": ", 2)[1]) + // initializing pwd and relative directory for DSF(s) and values files + pwd, _ = os.Getwd() + lastSlashIndex := -1 + if len(files) > 0 { + lastSlashIndex = strings.LastIndex(files[0], "/") + } + relativeDir = "." + if lastSlashIndex != -1 { + relativeDir = files[0][:lastSlashIndex] + } + if v { fmt.Println("Helmsman version: " + appVersion) os.Exit(0) diff --git a/main.go b/main.go index af42016f..e1835035 100644 --- a/main.go +++ b/main.go @@ -32,6 +32,8 @@ var keepUntrackedReleases bool var appVersion = "v1.4.0-rc" var helmVersion string var kubectlVersion string +var pwd string +var relativeDir string func main() { // set the kubecontext to be used Or create it if it does not exist diff --git a/release.go b/release.go index 584edf02..16544193 100644 --- a/release.go +++ b/release.go @@ -31,7 +31,7 @@ type release struct { // validateRelease validates if a release inside a desired state meets the specifications or not. // check the full specification @ https://github.com/Praqma/helmsman/docs/desired_state_spec.md func validateRelease(appLabel string, r *release, names map[string]map[string]bool, s state) (bool, string) { - _, err := os.Stat(r.ValuesFile) + _, err := os.Stat(pwd + "/" + relativeDir + "/" + r.ValuesFile) if r.Name == "" { r.Name = appLabel } @@ -64,13 +64,13 @@ func validateRelease(appLabel string, r *release, names map[string]map[string]bo } if r.ValuesFile != "" && (!isOfType(r.ValuesFile, ".yaml") || err != nil) { - return false, "valuesFile must be a valid file path for a yaml file, Or can be left empty." + return false, "valuesFile must be a valid relative (from your first dsf file) file path for a yaml file, Or can be left empty." } else if r.ValuesFile != "" && len(r.ValuesFiles) > 0 { return false, "valuesFile and valuesFiles should not be used together." } else if len(r.ValuesFiles) > 0 { for _, filePath := range r.ValuesFiles { - if _, pathErr := os.Stat(filePath); !isOfType(filePath, ".yaml") || pathErr != nil { - return false, "the value for valueFile '" + filePath + "' must be a valid file path for a yaml file." + if _, pathErr := os.Stat(pwd + "/" + relativeDir + "/" + filePath); !isOfType(filePath, ".yaml") || pathErr != nil { + return false, "the value for valueFile '" + filePath + "' must be a valid relative (from your first dsf file) file path for a yaml file." } } } diff --git a/release_test.go b/release_test.go index cca936a9..6239ed57 100644 --- a/release_test.go +++ b/release_test.go @@ -60,7 +60,7 @@ func Test_validateRelease(t *testing.T) { s: st, }, want: false, - want1: "valuesFile must be a valid file path for a yaml file, Or can be left empty.", + want1: "valuesFile must be a valid relative (from your first dsf file) file path for a yaml file, Or can be left empty.", }, { name: "test case 3", args: args{ @@ -78,7 +78,7 @@ func Test_validateRelease(t *testing.T) { s: st, }, want: false, - want1: "valuesFile must be a valid file path for a yaml file, Or can be left empty.", + want1: "valuesFile must be a valid relative (from your first dsf file) file path for a yaml file, Or can be left empty.", }, { name: "test case 4", args: args{ @@ -241,7 +241,7 @@ func Test_validateRelease(t *testing.T) { s: st, }, want: false, - want1: "the value for valueFile 'xyz.yaml' must be a valid file path for a yaml file.", + want1: "the value for valueFile 'xyz.yaml' must be a valid relative (from your first dsf file) file path for a yaml file.", }, { name: "test case 13", args: args{ From 6d863a903862913d5df28423172e3955e9341d01 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Thu, 30 Aug 2018 14:28:47 +0200 Subject: [PATCH 0212/1127] #64 fixing building helm state from string output for older helm versions --- helm_helpers.go | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/helm_helpers.go b/helm_helpers.go index e5748956..dd77836c 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -23,18 +23,20 @@ type releaseState struct { TillerNamespace string } +type releaseInfo struct { + Name string `json:"Name"` + Revision int `json:"Revision"` + Updated string `json:"Updated"` + Status string `json:"Status"` + Chart string `json:"Chart"` + AppVersion string `json:"AppVersion,omitempty"` + Namespace string `json:"Namespace"` + TillerNamespace string `json:",omitempty"` +} + type tillerReleases struct { - Next string `json:"Next"` - Releases []struct { - Name string `json:"Name"` - Revision int `json:"Revision"` - Updated string `json:"Updated"` - Status string `json:"Status"` - Chart string `json:"Chart"` - AppVersion string `json:"AppVersion,omitempty"` - Namespace string `json:"Namespace"` - TillerNamespace string `json:",omitempty"` - } `json:"Releases"` + Next string `json:"Next"` + Releases []releaseInfo `json:"Releases"` } // getHelmClientVersion returns Helm client Version @@ -92,24 +94,16 @@ func getTillerReleases(tillerNS string) tillerReleases { json.Unmarshal([]byte(result), &out) } else { lines := strings.Split(result, "\n") - index := 0 for i, l := range lines { if l == "" || (strings.HasPrefix(strings.TrimSpace(l), "NAME") && strings.HasSuffix(strings.TrimSpace(l), "NAMESPACE")) { continue } else { + r, _ := strconv.Atoi(strings.Fields(lines[i])[1]) t := strings.Fields(lines[i])[2] + " " + strings.Fields(lines[i])[3] + " " + strings.Fields(lines[i])[4] + " " + strings.Fields(lines[i])[5] + " " + strings.Fields(lines[i])[6] - out.Releases[index].Revision = r - out.Releases[index].Updated = t - out.Releases[index].Status = strings.Fields(lines[i])[7] - out.Releases[index].Chart = strings.Fields(lines[i])[8] - out.Releases[index].Namespace = strings.Fields(lines[i])[9] - out.Releases[index].Name = strings.Fields(lines[i])[0] - //out.Releases[index].AppVersion = "" - //out.Releases[index].TillerNamespace = tillerNS - index++ + out.Releases = append(out.Releases, releaseInfo{Name: strings.Fields(lines[i])[0], Revision: r, Updated: t, Status: strings.Fields(lines[i])[7], Chart: strings.Fields(lines[i])[8], Namespace: strings.Fields(lines[i])[9], AppVersion: "", TillerNamespace: ""}) } } From 24091fc61404830ef96aef8d30f39ef157ba8354 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Thu, 30 Aug 2018 14:33:37 +0200 Subject: [PATCH 0213/1127] updating go dependencies in circleci config --- .circleci/config.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7a290a41..aadacb8a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -15,6 +15,7 @@ jobs: go get github.com/Praqma/helmsman/gcs go get github.com/Praqma/helmsman/aws go get github.com/imdario/mergo + go get github.com/hashicorp/go-version echo "building ..." TAG=$(git describe --abbrev=0 --tags)-$(date +"%d%m%y") go build -ldflags '-X main.version='$TAG' -extldflags "-static"' @@ -31,6 +32,7 @@ jobs: go get github.com/Praqma/helmsman/gcs go get github.com/Praqma/helmsman/aws go get github.com/imdario/mergo + go get github.com/hashicorp/go-version echo "running tests ..." go test -v release: @@ -48,6 +50,7 @@ jobs: go get github.com/Praqma/helmsman/gcs go get github.com/Praqma/helmsman/aws go get github.com/imdario/mergo + go get github.com/hashicorp/go-version echo "releasing ..." goreleaser --release-notes release-notes.md else From 356576cdf388ea1258885b40c616c36cd20e3a4b Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Sun, 2 Sep 2018 11:51:36 +0200 Subject: [PATCH 0214/1127] fixes #68 --- helm_helpers.go | 43 +------------------------------------------ 1 file changed, 1 insertion(+), 42 deletions(-) diff --git a/helm_helpers.go b/helm_helpers.go index dd77836c..dfe3ea74 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -81,7 +81,7 @@ func getTillerReleases(tillerNS string) tillerReleases { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm list --all " + outputFormat + " --tiller-namespace " + tillerNS + getNSTLSFlags(tillerNS)}, + Args: []string{"-c", "helm list --all --max 0 " + outputFormat + " --tiller-namespace " + tillerNS + getNSTLSFlags(tillerNS)}, Description: "listing all existing releases in namespace [ " + tillerNS + " ]...", } @@ -141,47 +141,6 @@ func buildState() { } } -// Deprecated: listReleases lists releases in a given namespace and with a given status -func listReleases(namespace string, scope string) string { - var options string - if scope == "all" { - options = "--all -q" - } else if scope == "deleted" { - options = "--deleted -q" - } else if scope == "deployed" && namespace != "" { - options = "--deployed -q --namespace " + namespace - } else if scope == "deployed" && namespace == "" { - options = "--deployed -q " - } else if scope == "failed" { - options = "--failed -q" - } else { - options = "--all -q" - log.Println("INFO: scope " + scope + " is not valid, using [ all ] instead!") - } - - ns := namespace - tls := "" - if namespace == "" { - ns = "all" - tls = getNSTLSFlags("kube-system") - } else { - tls = getNSTLSFlags(namespace) - } - - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm list " + options + tls}, - Description: "listing the existing releases in namespace [ " + ns + " ] with status [ " + scope + " ]", - } - - exitCode, result := cmd.exec(debug, verbose) - if exitCode != 0 { - log.Fatal("ERROR: failed to list " + scope + " releases in " + ns + " namespace(s): " + result) - } - - return result -} - // helmRealseExists checks if a Helm release is/was deployed in a k8s cluster. // It searches the Current State for releases. // The key format for releases uniqueness is: From 17d72caa8c498d34ef326b3ee465594870eade58 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Sun, 2 Sep 2018 11:57:38 +0200 Subject: [PATCH 0215/1127] adding go-version dependency --- dockerfile/dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile index c63f8485..fee50301 100644 --- a/dockerfile/dockerfile +++ b/dockerfile/dockerfile @@ -6,6 +6,7 @@ RUN git clone https://github.com/Praqma/helmsman.git RUN go get github.com/Praqma/helmsman/gcs RUN go get github.com/Praqma/helmsman/aws RUN go get github.com/imdario/mergo +RUN go get github.com/hashicorp/go-version # build a statically linked binary so that it works on stripped linux images such as alpine/busybox. RUN cd helmsman \ From 2743d26ee2c9bb1c64db2bdc023927fa9f81df9c Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Sun, 2 Sep 2018 13:59:16 +0200 Subject: [PATCH 0216/1127] updating docs for v1.5.0 --- README.md | 9 ++++---- docs/best_practice.md | 2 +- docs/deplyment_strategies.md | 10 ++++---- docs/desired_state_specification.md | 22 +++++++++++------- docs/how_to/define_namespaces.md | 22 +++++++++++++++++- docs/how_to/helmsman_on_windows10.md | 4 +++- docs/how_to/manipulate_apps.md | 13 ++++++----- docs/how_to/merge_desired_state_files.md | 4 ++-- docs/how_to/move_charts_across_namespaces.md | 4 ++-- docs/how_to/multitenant_clusters_guide.md | 13 +++++++---- .../how_to/protect_namespaces_and_releases.md | 2 +- docs/how_to/run_helmsman_in_ci.md | 6 +++-- .../send_slack_notifications_from_helmsman.md | 23 +++++++++++++++++++ docs/migrating_to_v1.4.0-rc.md | 4 ++-- example.toml | 2 +- example.yaml | 2 +- helm_helpers.go | 2 +- 17 files changed, 102 insertions(+), 42 deletions(-) create mode 100644 docs/how_to/send_slack_notifications_from_helmsman.md diff --git a/README.md b/README.md index 166de2cf..94cb0c1a 100644 --- a/README.md +++ b/README.md @@ -21,10 +21,10 @@ To plan without executing: ``` $ helmsman -f example.toml ``` To plan and execute the plan: -``` $ helmsman -apply -f example.toml ``` +``` $ helmsman --apply -f example.toml ``` To show debugging details: -``` $ helmsman -debug -apply -f example.toml ``` +``` $ helmsman --debug --apply -f example.toml ``` # Features @@ -46,9 +46,9 @@ To show debugging details: Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. ``` # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v1.4.0-rc/helmsman_1.4.0-rc_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.5.0/helmsman_1.5.0_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v1.4.0-rc/helmsman_1.4.0-rc_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.5.0/helmsman_1.5.0_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` @@ -67,6 +67,7 @@ Helmsman lets you: - [install/delete/upgrade/rollback your helm charts from code](https://github.com/Praqma/helmsman/blob/master/docs/how_to/manipulate_apps.md). - [work safely in a multitenant cluster](https://github.com/Praqma/helmsman/blob/master/docs/how_to/multitenant_clusters_guide.md). - [pass secrets/user input to helm charts from environment variables](https://github.com/Praqma/helmsman/blob/master/docs/how_to/pass_secrets_from_env_variables.md). +- [send Slack notifications from Helmsman](https://github.com/Praqma/helmsman/blob/master/docs/how_to/send_slack_notifications_from_helmsman.md) - [test releases when they are first installed](https://github.com/Praqma/helmsman/blob/master/docs/how_to/test_charts.md). - [use public and private helm charts](https://github.com/Praqma/helmsman/blob/master/docs/how_to/use_private_helm_charts.md). - [use locally developed helm charts (the tar archives)](https://github.com/Praqma/helmsman/blob/master/docs/how_to/use_local_charts.md). diff --git a/docs/best_practice.md b/docs/best_practice.md index 5a933d07..de7314d8 100644 --- a/docs/best_practice.md +++ b/docs/best_practice.md @@ -16,5 +16,5 @@ When using Helmsman, we recommend the following best practices: - Don't maintain the same release in multiple DSFs. -- While the decision on how many DSFs to use and what each can contain is up to you and depends on your case, we recommend coming up with your own rule for that and following it. +- While the decision on how many DSFs to use and what each can contain is up to you and depends on your case, we recommend coming up with your own rule for how to split them. diff --git a/docs/deplyment_strategies.md b/docs/deplyment_strategies.md index 65a17d49..28214154 100644 --- a/docs/deplyment_strategies.md +++ b/docs/deplyment_strategies.md @@ -4,7 +4,7 @@ version: v1.1.0 # Deployment Strategies -This document describes the different startegies to use Helmsman for maintaining your helm charts deployment to k8s clusters. +This document describes the different strategies to use Helmsman for maintaining your helm charts deployment to k8s clusters. ## Deploying 3rd party charts (apps) in a production cluster @@ -122,7 +122,7 @@ If you use multiple clusters for multiple purposes, you need at least one Helmsm ## Deploying your dev charts -If you are developing your own applications/services and packaging them in helm charts. It makes sense to automtically deploy these charts to a staging namespace or a dev cluster on every source code commit. +If you are developing your own applications/services and packaging them in helm charts. It makes sense to automatically deploy these charts to a staging namespace or a dev cluster on every source code commit. Often, you would have multiple apps developed in separate source code repositories but you would like to test their deployment in the same cluster/namespace. In that case, Helmsman can be used [as part of your CI pipeline](how_to/run_helmsman_in_ci.md) as described in the diagram below: @@ -136,10 +136,10 @@ If you need supporting applications (charts) for your application (e.g, reverse ## Notes on using multiple Helmsman desired state files with the same cluster -Helmsman works with a single desired state file at a time and does not maintain a state anywhere. i.e. it does not have any context awareness about other desired state files used with the same cluster. For this reason, it is the user's responsibility to make sure that: +Helmsman works with a single desired state file at a time (starting from v1.5.0, you can pass multiple desired state files which get merged at runtime. See the [docs](how_to/merge_desired_state_files.md)) and does not maintain a state anywhere. i.e. it does not have any context awareness about other desired state files used with the same cluster. For this reason, it is the user's responsibility to make sure that: -- no releases have the same name in different desired state files pointing to the same cluster. If such conflict exists, Helmsman will not raise any errors but that release would be subject to unexpected behaviour. +- no releases have the same name in different desired state files pointing to the same cluster. If such conflict exists, Helmsman will not raise any errors but that release would be subject to unexpected behavior. - protected namespaces are defined protected in all the desired state files. Otherwise, namespace protection can be accidentally compromised if the same release name is used across multiple desired state files. -Also please refere to the [best parctice](best_practice.md) document. +Also please refer to the [best practice](best_practice.md) document. diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 10008be1..67c0a81f 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -1,5 +1,5 @@ --- -version: v1.4.0-rc +version: v1.5.0 --- # Helmsman desired state specification @@ -8,7 +8,7 @@ This document describes the specification for how to write your Helm charts desi - [Metadata](#metadata) [Optional] -- metadata for any human reader of the desired state file. - [Certificates](#certificates) [Optional] -- only needed when you want Helmsman to connect kubectl to your cluster for you. -- [Settings](#settings) -- data about your k8s cluster. +- [Settings](#settings) -- data about your k8s cluster and how to deploy Helm on it if needed. - [Namespaces](#namespaces) -- defines the namespaces where you want your Helm charts to be deployed. - [Helm Repos](#helm-repos) -- defines the repos where you want to get Helm charts from. - [Apps](#apps) -- defines the applications/charts you want to manage in your cluster. @@ -119,13 +119,14 @@ settings: Optional : No. -Synopsis: defines the namespaces to be used/created in your k8s cluster and wether they are protected or not. It also defines if Tiller should be deployed in these namespaces and with what configurations (TLS and service account). You can add as many namespaces as you like. -If a namespaces does not already exist, Helmsman will create it. +Synopsis: defines the namespaces to be used/created in your k8s cluster and whether they are protected or not. It also defines if Tiller should be deployed in these namespaces and with what configurations (TLS and service account). You can add as many namespaces as you like. +If a namespace does not already exist, Helmsman will create it. Options: - protected : defines if a namespace is protected (true or false). Default false. -- installTiller: defines if Tiller should be deployed in this namespace or not. Default is false. Any chart desired to be deployed into a namespace with a Tiller deployed, will be deployed using that Tiller and not the one in kube-system. -> Tiller will ALWAYS be deployed into `kube-system`, even if you set installTiller for kube-system to false. +> For the definition of what a protected namespace means, check the [protection guide](how_to/protect_namespaces_and_releases.md) +- installTiller: defines if Tiller should be deployed in this namespace or not. Default is false. Any chart desired to be deployed into a namespace with a Tiller deployed, will be deployed using that Tiller and not the one in kube-system unless you use the `TillerNamespace` option (see the [Apps](#apps) section below) to use another Tiller. +> By default Tiller will be deployed into `kube-system` even if you don't define kube-system in the namespaces section. To prevent deploying Tiller into `kube-system, add kube-system in your namespaces section and set its installTiller to false. - tillerServiceAccount: defines what service account to use when deploying Tiller. If this is not set, the following options are considered: @@ -141,12 +142,13 @@ Options: - clientCert: the SSL certificate for the Helm client. - clientKey: the SSL certificate private key for the Helm client. -> For the definition of what a protected namespace means, check the [protection guide](how_to/protect_namespaces_and_releases.md) - Example: ```toml [namespaces] +# to prevent deploying Tiller into kube-system, use the two lines below +# [namespaces.kube-system] +# installTiller = false # this line can be omitted since installTiller defaults to false [namespaces.staging] [namespaces.dev] protected = false @@ -163,6 +165,9 @@ clientKey = "s3://mybucket/mydir/helm.key.pem" ```yaml namespaces: + # to prevent deploying Tiller into kube-system, use the two lines below + # kube-system: + # installTiller: false # this line can be omitted since installTiller defaults to false staging: dev: protected: false @@ -238,6 +243,7 @@ Options: - description : a release metadata for human readers. - valuesFile : a valid path to custom Helm values.yaml file. File extension must be `yaml`. Cannot be used with valuesFiles together. Leaving it empty uses the default chart values. - valuesFiles : array of valid paths to custom Helm values.yaml file. File extension must be `yaml`. Cannot be used with valuesFile together. Leaving it empty uses the default chart values. +> The values file(s) path is relative from the location of the (first) desired state file you pass in your Helmsman command. - purge : defines whether to use the Helm purge flag when deleting the release. (true/false) - test : defines whether to run the chart tests whenever the release is installed. - protected : defines if the release should be protected against changes. Namespace-level protection has higher priority than this flag. Check the [protection guide](how_to/protect_namespaces_and_releases.md) for more details. diff --git a/docs/how_to/define_namespaces.md b/docs/how_to/define_namespaces.md index f4eb2bb9..a8271e8b 100644 --- a/docs/how_to/define_namespaces.md +++ b/docs/how_to/define_namespaces.md @@ -1,5 +1,5 @@ --- -version: v1.3.0-rc +version: v1.5.0 --- # define namespaces @@ -29,8 +29,12 @@ namespaces: >For details on protecting a namespace, please check the [namespace/release protection guide](protect_namespaces_and_releases.md) +## Deploying Tiller into namespaces + As of `v1.2.0-rc`, you can instruct Helmsman to deploy Tiller into specific namespaces (with or without TLS). +> By default Tiller will be deployed into `kube-system` even if you don't define kube-system in the namespaces section. To prevent deploying Tiller into `kube-system, see the subsection below. + ```toml [namespaces] [namespaces.production] @@ -57,6 +61,22 @@ namespaces: clientKey: "s3://mybucket/mydir/helm.key.pem" ``` +### Preventing Tiller deployment in kube-system + +By default Tiller will be deployed into `kube-system` even if you don't define kube-system in the namespaces section. To prevent this, simply add `kube-system` into your namespaces section. Since `installTiller` for namespaces is by default false, Helmsman will not deploy Tiller in `kube-system`. + +```toml +[namespaces] +[namespaces.kube-system] +# installTiller = false # this line is not needed since the default is false, but can be added for human readability. +``` +```yaml +namespaces: + kube-system: + #installTiller: false # this line is not needed since the default is false, but can be added for human readability. +``` + +## Deploying releases with specific Tillers You can then tell Helmsman to deploy specific releases in a specific namespace: ```toml diff --git a/docs/how_to/helmsman_on_windows10.md b/docs/how_to/helmsman_on_windows10.md index 086dd70a..51490be6 100644 --- a/docs/how_to/helmsman_on_windows10.md +++ b/docs/how_to/helmsman_on_windows10.md @@ -2,9 +2,11 @@ version: v1.1.0 --- +> This guide has not been thoroughly tested. + # Using Helmsman from a docker image on Windows 10 -If you have Windows 10 with Docker installed, you might be able to run Helmsman in a linux container on Windows. +If you have Windows 10 with Docker installed, you **might** be able to run Helmsman in a linux container on Windows. 1. Switch to the Linux containers from the docker tray icon. 2. Configure your local kubectl on Windows to connect to your cluster. diff --git a/docs/how_to/manipulate_apps.md b/docs/how_to/manipulate_apps.md index b36445fb..bd3c3acb 100644 --- a/docs/how_to/manipulate_apps.md +++ b/docs/how_to/manipulate_apps.md @@ -1,5 +1,5 @@ --- -version: v1.3.0-rc +version: v1.5.0 --- # install releases @@ -8,7 +8,7 @@ You can run helmsman with the [example.toml](https://github.com/Praqma/helmsman/ ``` -$ helmsman -apply -f example.toml +$ helmsman --apply -f example.toml 2017/11/19 18:17:57 Parsed [[ example.toml ]] successfully and found [ 2 ] apps. 2017/11/19 18:17:59 WARN: I could not create namespace [staging ]. It already exists. I am skipping this. 2017/11/19 18:17:59 WARN: I could not create namespace [default ]. It already exists. I am skipping this. @@ -35,10 +35,10 @@ You can then change your desire, for example to disable the Jenkins release that Then run Helmsman again and it will detect that you want to delete Jenkins: -> Note: deleting the jenkins app entry in example.toml WILL NOT resultin deleting the jenkins release. It simply means that Helmsman is no longer responsible for managing it. +> Note: As of v1.4.0-rc, deleting the jenkins app entry in the desired state file WILL result in deleting the jenkins release. To prevent this, use the `--keep-untracked-releases` flag with your Helmsman command. ``` -$ helmsman -apply -f example.toml +$ helmsman --apply -f example.toml 2017/11/19 18:28:27 Parsed [[ example.toml ]] successfully and found [ 2 ] apps. 2017/11/19 18:28:29 WARN: I could not create namespace [staging ]. It already exists. I am skipping this. 2017/11/19 18:28:29 WARN: I could not create namespace [default ]. It already exists. I am skipping this. @@ -98,10 +98,11 @@ apps: # rollback releases +> Rollbacks in helm versions 2.8.2 and higher may not work due to a [bug](https://github.com/helm/helm/issues/3722). Similarly, if you change `enabled` back to `true`, it will figure out that you would like to roll it back. ``` -$ helmsman -apply -f example.toml +$ helmsman --apply -f example.toml 2017/11/19 18:30:41 Parsed [[ example.toml ]] successfully and found [ 2 ] apps. 2017/11/19 18:30:42 WARN: I could not create namespace [staging ]. It already exists. I am skipping this. 2017/11/19 18:30:43 WARN: I could not create namespace [default ]. It already exists. I am skipping this. @@ -116,7 +117,7 @@ DECISION: release [ artifactory ] is desired to be upgraded. Planing this for yo # upgrade releases -Everytime you run Helmsman, (unless the release is [protected or deployed in a protected namespace](protect_namespaces_and_releases.md)) it will upgrade existing deployed releases to the version you specified in the desired state file. It also applies the `values.yaml` file you specify with each install/upgrade. This means that when you don't change anything for a specific release, Helmsman would upgrade with the `values.yaml` file you provide (just in case it is a new file or you changed something there.) +Every time you run Helmsman, (unless the release is [protected or deployed in a protected namespace](protect_namespaces_and_releases.md)) it will upgrade existing deployed releases to the version you specified in the desired state file. It also applies the `values.yaml` file you specify with each install/upgrade. This means that when you don't change anything for a specific release, Helmsman would upgrade with the `values.yaml` file you provide (just in case it is a new file or you changed something there.) If you change the chart, the existing release will be deleted and a new one with the same name will be created using the new chart. diff --git a/docs/how_to/merge_desired_state_files.md b/docs/how_to/merge_desired_state_files.md index 015b14c6..b6305d7e 100644 --- a/docs/how_to/merge_desired_state_files.md +++ b/docs/how_to/merge_desired_state_files.md @@ -1,10 +1,10 @@ --- -version: v1.5.0-rc +version: v1.5.0 --- # supply multiple desired state files -Starting from v1.5.0-rc, Helmsman allows you to pass the `-f` flag multiple times to specify multiple desired state files +Starting from v1.5.0, Helmsman allows you to pass the `-f` flag multiple times to specify multiple desired state files that should be merged. This allows us to do things like specify our non-environment-specific config in a `common.toml` file and environment specific info in a `nonprod.toml` or `prod.toml` file. This process uses [this library](https://github.com/imdario/mergo) to do the merging, and is subject to the limitations described there. diff --git a/docs/how_to/move_charts_across_namespaces.md b/docs/how_to/move_charts_across_namespaces.md index 6a6a272e..ea3329c9 100644 --- a/docs/how_to/move_charts_across_namespaces.md +++ b/docs/how_to/move_charts_across_namespaces.md @@ -111,7 +111,7 @@ Helmsman will delete the jenkins release from the `staging` namespace and instal Helmsman does not automatically move PVCs across namespaces. You have to follow the steps below to retain your data when moving an app to a different namespace. -Persistent Volumes (PV) are accessed through Persistent Volume Claims (PVC). But **PVCs are namespaced object** which means moving an application from one namespace to another will result in a new PVC created in the new namespace. The old PV -which possibly contains your application data- will still be mounted to the old PVC (the one in the old namespace) even if you have deleted your application helm release. +Persistent Volumes (PV) are accessed through Persistent Volume Claims (PVC). But **PVCs are namespaced objects** which means moving an application from one namespace to another will result in a new PVC created in the new namespace. The old PV -which possibly contains your application data- will still be mounted to the old PVC (the one in the old namespace) even if you have deleted your application helm release. Now, the newly created PVC (in the new namespace) will not be able to mount to the old PV and instead it will mount to any other available one or (in the case of dynamic provisioning) will provision a new PV. This means the application in the new namespace does not have the old data. Don't panic, the old PV is still there and contains your old data. @@ -131,7 +131,7 @@ kubectl delete pvc --namespace ``` Since, we changed the Reclaim Policy to Retain, the PV will stay around (with all your data). -3. The PV is now the **Released** state but not yet available for mounting. +3. The PV is now in the **Released** state but not yet available for mounting. ``` kubectl get pv diff --git a/docs/how_to/multitenant_clusters_guide.md b/docs/how_to/multitenant_clusters_guide.md index 7a206cc2..f0b2aae5 100644 --- a/docs/how_to/multitenant_clusters_guide.md +++ b/docs/how_to/multitenant_clusters_guide.md @@ -1,5 +1,5 @@ --- -version: v1.3.0 +version: v1.5.0 --- # Multitenant Clusters Guide @@ -42,6 +42,8 @@ namespaces: ``` +By default Tiller will be deployed into `kube-system` even if you don't define kube-system in the namespaces section. To prevent deploying Tiller into `kube-system, you need to explicitly add `kube-system` in your defined namespaces. See the [namespaces guide](define_namespaces.md#preventing_tiller_deployment_in_kube-system) for an example. + ## Deploying Tiller with a service account For K8S clusters with RBAC enabled, you will need to initialize Helm with a service account. Check [Helm's RBAC guide](https://github.com/kubernetes/helm/blob/master/docs/rbac.md). @@ -95,12 +97,15 @@ namespaces: tillerServiceAccount: "dev2-sa" ``` -> Currently, Helmsman does not create the service accounts and expects them to be available in the namespace before hand. This should be fixed in upcoming releases and you can track it in [this issue](https://github.com/Praqma/helmsman/issues/48) -> If you don't specify `tillerServiceAccount` option for a namespace, it will try to use the service account you defined in your settings section (`default-tiller-sa` in the example above) +If `tillerServiceAccount` is not defined, the following options are considered: + + 1. If the `serviceAccount` defined in the `settings` section exists in the namespace you want to deploy Tiller in, it will be used, else + 2. Helmsman creates the service account in that namespace and binds it to a role. If the namespace is kube-system, the service account is bound to `cluster-admin` clusterrole. Otherwise, a new role called `helmsman-tiller` is created in that namespace and only gives access to that namespace. + In the example above, namespaces `staging, developer1 & developer2` will have Tiller deployed with different service accounts. -The `production` namespace ,however, will be deployed using the `default-tiller-sa` service account defined in the `settings` section. If this one is not defined, the production namespace Tiller will be deployed with k8s default service account. +The `production` namespace ,however, will be deployed using the `default-tiller-sa` service account defined in the `settings` section -assuming it exists in the production namespace-. If this one is not defined, Helmsman creates a new service account and binds it to a new role that only gives access to the `production` namespace. ## Deploying Tiller with TLS enabled diff --git a/docs/how_to/protect_namespaces_and_releases.md b/docs/how_to/protect_namespaces_and_releases.md index 34c74366..b6be3dd4 100644 --- a/docs/how_to/protect_namespaces_and_releases.md +++ b/docs/how_to/protect_namespaces_and_releases.md @@ -77,4 +77,4 @@ apps: - You can combine both types of protection in your desired state file. The namespace-level protection always has a higher priority. - Removing the protection from a namespace means all releases deployed in that namespace are no longer protected. - We recommend using namespace-level protection for production namespace(s) and release-level protection for releases deployed in other namespaces. -- Release/namespace protection is only applied on single desired state files. It is your responsibility to make sure that multiple desired state files (if used) do not conflict with each other (e.g, one defines a particular namespace as defined and another defines it unprotected.) If you use multiple desired state files with the same cluster, please refer to [deployment strategies](../deployment_strategies.md) and [best practice](../best_practice.md) documentation. \ No newline at end of file +- Release/namespace protection is only applied on single desired state files. It is your responsibility to make sure that multiple desired state files (if used) do not conflict with each other (e.g, one defines a particular namespace as protected and another defines it unprotected.) If you use multiple desired state files with the same cluster, please refer to [deployment strategies](../deployment_strategies.md) and [best practice](../best_practice.md) documentation. \ No newline at end of file diff --git a/docs/how_to/run_helmsman_in_ci.md b/docs/how_to/run_helmsman_in_ci.md index fc1e6bfc..f1396bf1 100644 --- a/docs/how_to/run_helmsman_in_ci.md +++ b/docs/how_to/run_helmsman_in_ci.md @@ -13,12 +13,12 @@ jobs: deploy-apps: docker: - - image: praqma/helmsman:v1.2.0-rc + - image: praqma/helmsman:v1.5.0 steps: - checkout - run: name: Deploy Helm Packages using helmsman - command: helmsman -debug -apply -f helmsman-deployments.toml + command: helmsman --debug --apply -f helmsman-deployments.toml workflows: @@ -28,4 +28,6 @@ workflows: - deploy-apps ``` +> IMPORTANT: If your CI build logs are publicly readable, don't use the `--verbose` flag as logs any secrets being passed from env vars to the helm charts. + The `helmsman-deployments.toml` is your desired state file which will version controlled in your git repo. \ No newline at end of file diff --git a/docs/how_to/send_slack_notifications_from_helmsman.md b/docs/how_to/send_slack_notifications_from_helmsman.md new file mode 100644 index 00000000..cecff253 --- /dev/null +++ b/docs/how_to/send_slack_notifications_from_helmsman.md @@ -0,0 +1,23 @@ +--- +version: v1.5.0 +--- + +# Slack notifications from Helmsman + +Starting from v1.4.0-rc, Helmsman can send slack notifications to a channel of your choice. To enable the notifications, simply add a `slack webhook` in the `settings` section of your desired state file. The webhook URL can be passed directly or from an environment variable. + +```toml +[settings] +... +slackWebhook = $MY_SLACK_WEBHOOK +``` + +```yaml +settings: + ... + #slackWebhook : "$MY_SLACK_WEBHOOK" +``` + +## Getting a Slack Webhook URL + +Follow the [slack guide](https://api.slack.com/incoming-webhooks) for generating a webhook URL. \ No newline at end of file diff --git a/docs/migrating_to_v1.4.0-rc.md b/docs/migrating_to_v1.4.0-rc.md index 46ded3fe..90ca9aa5 100644 --- a/docs/migrating_to_v1.4.0-rc.md +++ b/docs/migrating_to_v1.4.0-rc.md @@ -1,8 +1,8 @@ -# Migrating to Helmsman v1.4.0-rc +# Migrating to Helmsman v1.4.0-rc or higher This document highlights the main changes between Helmsman v1.4.0-rc and the older versions. While the changes are still backward-compatible, the behavior and the internals have changed. The list below highlights those changes: - Helmsman v1.4.0-rc tracks the releases it manages by applying specific labels to their Helm state (stored in Helm's configured backend storage). For smooth transition when upgrading to v1.4.0-rc, you should run `helmsman -f --apply-labels` once. This will label all releases from your desired state as with a `MANAGED-BY=Helmsman` label. The `--apply-labels`is safe to run multiple times. -- After each run, Helmsman v1.4.0-rc looks for, and deletes any releases with the `MANAGED-BY=Helmsman` label which are no longer existing in your desired state. This means that **deleting/commenting out an app from your desired state file will result in its deletion**. +- After each run, Helmsman v1.4.0-rc looks for, and deletes any releases with the `MANAGED-BY=Helmsman` label which are no longer existing in your desired state. This means that **deleting/commenting out an app from your desired state file will result in its deletion**. You can disable this cleanup by adding the flag `--keep-untracked-releases` to your Helmsman commands. diff --git a/example.toml b/example.toml index a43a022d..952f7f23 100644 --- a/example.toml +++ b/example.toml @@ -1,4 +1,4 @@ -# version: v1.4.0-rc +# version: v1.5.0 # metadata -- add as many key/value pairs as you want [metadata] org = "example.com" diff --git a/example.yaml b/example.yaml index 5d08e79f..2a3846c4 100644 --- a/example.yaml +++ b/example.yaml @@ -1,4 +1,4 @@ -# version: v1.4.0-rc +# version: v1.5.0 # metadata -- add as many key/value pairs as you want metadata: org: "example.com" diff --git a/helm_helpers.go b/helm_helpers.go index dfe3ea74..eafc7ae0 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -281,7 +281,7 @@ func addHelmRepos(repos map[string]string) (bool, string) { // deployTiller deploys Helm's Tiller in a specific namespace with a serviceAccount // If serviceAccount is not provided (empty string), the defaultServiceAccount is used. -// If no defaultServiceAccount is provided, Tiller is deployed with the namespace default service account +// If no defaultServiceAccount is provided, A service account is created and Tiller is deployed with the new service account // If no namespace is provided, Tiller is deployed to kube-system func deployTiller(namespace string, serviceAccount string, defaultServiceAccount string) (bool, string) { log.Println("INFO: deploying Tiller in namespace [ " + namespace + " ].") From 6893dca710677629a0390fbef35a12f9dbca781b Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Sun, 2 Sep 2018 13:59:37 +0200 Subject: [PATCH 0217/1127] bumping version and updating release notes --- main.go | 2 +- release-notes.md | 22 +++++++++------------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/main.go b/main.go index e1835035..f1cef000 100644 --- a/main.go +++ b/main.go @@ -29,7 +29,7 @@ var checkCleanup bool var skipValidation bool var applyLabels bool var keepUntrackedReleases bool -var appVersion = "v1.4.0-rc" +var appVersion = "v1.5.0" var helmVersion string var kubectlVersion string var pwd string diff --git a/release-notes.md b/release-notes.md index 5272875c..8ff3061d 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,16 +1,12 @@ -# v1.4.0-rc +# v1.5.0 -> If you are already using an older version of Helmsman, please read the changes below carefully and follow the upgrade guide [here](docs/migrating_to_v1.4.0-rc.md) +> If you are already using an older version of Helmsman than v1.4.0-rc, please read the changes below carefully and follow the upgrade guide [here](docs/migrating_to_v1.4.0-rc.md) + +- Adding `--keep-untracked-releases` option to prevent cleaning up untracked release that are managed by Helmsman. +- Restricting the untracked releases clean up (when enabled) to the ones only in the defined namespace in the desired state file. +- Support using multiple desired state files which are merged at runtime. Issue #62. Thanks to @norwoodj +- Support using the `json` output of newer versions of helm. Fixes #61. Thanks to @luisdavim +- Fix relative paths for values files. Issue #59 +- Adding `timeout` & `no-hooks` as additional release deployment options. Issue #55 -- Slack notifications for Helmsman plan and execution results. Issue #49 -- RBAC Improvements: - - Validation for service accounts to be used for deploying Tillers. Issue #47 - - Support creating RBAC service accounts for configuring Tiller(s) if they don't exist. Issue 48# -- Improvements for Multi-tenancy: - - Adding `tillerNamespace` option for releases to select which Tiller should manage a release. Issue #32 - - Allowing releases with the same name to be deployed with different Tillers. Issue #50 - - Tracking Helmsman managed releases with special Helmsman labels. -- Adding `--apply-labels` flag to label releases defined in the desired state file with Helmsman's labels. -- Making the name option for Apps optional and using the app name from the (toml/yaml) desired state as a release name when this option is not set. -- Changing Helmsman behavior when removing/commenting out a release in the Apps section of the desired state. Removing/commenting out a release in the desired state will result in **deleting** the release if it's labeled as `managed-by Helmsman`. \ No newline at end of file From 195300e4db8fe57be06af9b3bc3da4d11822f9de Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Sun, 2 Sep 2018 14:36:03 +0200 Subject: [PATCH 0218/1127] [ci skip] updating readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 94cb0c1a..80d899ae 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ --- -version: v1.4.0-rc +version: v1.5.0 --- ![helmsman-logo](docs/images/helmsman.png) From 7204870c28fa953b435d292e7659f8d23fc5fa49 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Mon, 3 Sep 2018 12:51:13 +0100 Subject: [PATCH 0219/1127] Adding new option to reverse the priorities on delete --- decision_maker.go | 11 ++- docs/deplyment_strategies.md | 59 ++++++------ docs/desired_state_specification.md | 91 ++++++++++--------- .../run_helmsman_with_hosted_cluster.md | 72 +++++++-------- docs/how_to/use_private_helm_charts.md | 10 +- docs/how_to/use_the_priority_key.md | 32 +++---- example.toml | 85 ++++++++--------- example.yaml | 9 +- 8 files changed, 190 insertions(+), 179 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index 8abdd8c3..d0a3fa54 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -7,9 +7,11 @@ import ( var outcome plan var releases string +var settings map[string]string // makePlan creates a plan of the actions needed to make the desired state come true. func makePlan(s *state) *plan { + settings = s.Settings outcome = createPlan() buildState() @@ -149,13 +151,18 @@ func deleteRelease(r *release, rs releaseState) { purgeDesc = "and purged!" } + priority := r.Priority + if settings["reverseDelete"] == "yes" { + priority = priority * -1 + } + cmd := command{ Cmd: "bash", Args: []string{"-c", "helm delete " + p + " " + r.Name + getCurrentTillerNamespaceFlag(rs) + getTLSFlags(r)}, Description: "deleting release [ " + r.Name + " ] from namespace [[ " + r.Namespace + " ]] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } - outcome.addCommand(cmd, r.Priority, r) - logDecision("DECISION: release [ "+r.Name+" ] is desired to be deleted "+purgeDesc+". Planing this for you!", r.Priority) + outcome.addCommand(cmd, priority, r) + logDecision("DECISION: release [ "+r.Name+" ] is desired to be deleted "+purgeDesc+". Planing this for you!", priority) } // inspectUpgradeScenario evaluates if a release should be upgraded. diff --git a/docs/deplyment_strategies.md b/docs/deplyment_strategies.md index 28214154..b2c3f275 100644 --- a/docs/deplyment_strategies.md +++ b/docs/deplyment_strategies.md @@ -2,66 +2,65 @@ version: v1.1.0 --- -# Deployment Strategies +# Deployment Strategies This document describes the different strategies to use Helmsman for maintaining your helm charts deployment to k8s clusters. ## Deploying 3rd party charts (apps) in a production cluster -Suppose you are deploying 3rd party charts (e.g. Jenkins, Jira ... etc.) in your cluster. These applications can be deployed with Helmsman using a single desired state file. The desired state tells helmsman to deploy these apps into certain namespaces in a production cluster. +Suppose you are deploying 3rd party charts (e.g. Jenkins, Jira ... etc.) in your cluster. These applications can be deployed with Helmsman using a single desired state file. The desired state tells helmsman to deploy these apps into certain namespaces in a production cluster. -You can test 3rd party charts in designated namespaces (e.g, staging) within the same production cluster. This also can be defined in the same desired state file. Below is an example of a desired state file for deploying 3rd party apps in production and staging namespaces: +You can test 3rd party charts in designated namespaces (e.g, staging) within the same production cluster. This also can be defined in the same desired state file. Below is an example of a desired state file for deploying 3rd party apps in production and staging namespaces: ```toml [metadata] -org = "example" + org = "example" # using a minikube cluster [settings] -kubeContext = "minikube" + kubeContext = "minikube" [namespaces] [namespaces.staging] - protected = false + protected = false [namespaces.production] - protected = true + protected = true [helmRepos] -stable = "https://kubernetes-charts.storage.googleapis.com" -incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" + stable = "https://kubernetes-charts.storage.googleapis.com" + incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" [apps] - [apps.jenkins] + [apps.jenkins] name = "jenkins-prod" # should be unique across all apps description = "production jenkins" - namespace = "production" - enabled = true - chart = "stable/jenkins" + namespace = "production" + enabled = true + chart = "stable/jenkins" version = "0.9.1" # chart version valuesFiles = [ "../my-jenkins-common-values.yaml", "../my-jenkins-production-values.yaml" ] - [apps.artifactory] + [apps.artifactory] name = "artifactory-prod" # should be unique across all apps description = "production artifactory" - namespace = "production" - enabled = true - chart = "stable/artifactory" + namespace = "production" + enabled = true + chart = "stable/artifactory" version = "6.2.0" # chart version - valuesFile = "../my-artificatory-production-values.yaml" - + valuesFile = "../my-artificatory-production-values.yaml" - # the jenkins release below is being tested in the staging namespace - [apps.jenkins-test] + + # the jenkins release below is being tested in the staging namespace + [apps.jenkins-test] name = "jenkins-test" # should be unique across all apps description = "test release of jenkins, testing xyz feature" - namespace = "staging" - enabled = true - chart = "stable/jenkins" + namespace = "staging" + enabled = true + chart = "stable/jenkins" version = "0.9.1" # chart version valuesFiles = [ "../my-jenkins-common-values.yaml", "../my-jenkins-testing-values.yaml" ] - ``` ```yaml @@ -117,7 +116,7 @@ You can split the desired state file into multiple files if your deployment pipe ## Working with multiple clusters -If you use multiple clusters for multiple purposes, you need at least one Helmsman desired state file for each cluster. +If you use multiple clusters for multiple purposes, you need at least one Helmsman desired state file for each cluster. ## Deploying your dev charts @@ -126,13 +125,13 @@ If you are developing your own applications/services and packaging them in helm Often, you would have multiple apps developed in separate source code repositories but you would like to test their deployment in the same cluster/namespace. In that case, Helmsman can be used [as part of your CI pipeline](how_to/run_helmsman_in_ci.md) as described in the diagram below: -> as of v1.1.0 , you can use the `ns-override`flag to force helmsman to deploy/move all apps into a given namespace. For example, you could use this flag in a CI job that gets triggered on commits to the dev branch to deploy all apps into the `staging` namespace. +> as of v1.1.0 , you can use the `ns-override`flag to force helmsman to deploy/move all apps into a given namespace. For example, you could use this flag in a CI job that gets triggered on commits to the dev branch to deploy all apps into the `staging` namespace. ![multi-DSF](images/multi-DSF.png) -Each repository will have a Helmsman desired state file (DSF). But it is important to consider the notes below on using multiple desired state files with one cluster. +Each repository will have a Helmsman desired state file (DSF). But it is important to consider the notes below on using multiple desired state files with one cluster. -If you need supporting applications (charts) for your application (e.g, reverse proxies, DB, k8s dashborad etc.), you can describe the desired state for these in a separate file which can live in another repository. Adding such file in the pipeline where you create your cluster from code makes total "DevOps" sense. +If you need supporting applications (charts) for your application (e.g, reverse proxies, DB, k8s dashborad etc.), you can describe the desired state for these in a separate file which can live in another repository. Adding such file in the pipeline where you create your cluster from code makes total "DevOps" sense. ## Notes on using multiple Helmsman desired state files with the same cluster @@ -142,4 +141,4 @@ Helmsman works with a single desired state file at a time (starting from v1.5.0, - protected namespaces are defined protected in all the desired state files. Otherwise, namespace protection can be accidentally compromised if the same release name is used across multiple desired state files. -Also please refer to the [best practice](best_practice.md) document. +Also please refer to the [best practice](best_practice.md) document. diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 67c0a81f..24473d88 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -8,7 +8,7 @@ This document describes the specification for how to write your Helm charts desi - [Metadata](#metadata) [Optional] -- metadata for any human reader of the desired state file. - [Certificates](#certificates) [Optional] -- only needed when you want Helmsman to connect kubectl to your cluster for you. -- [Settings](#settings) -- data about your k8s cluster and how to deploy Helm on it if needed. +- [Settings](#settings) -- data about your k8s cluster and how to deploy Helm on it if needed. - [Namespaces](#namespaces) -- defines the namespaces where you want your Helm charts to be deployed. - [Helm Repos](#helm-repos) -- defines the repos where you want to get Helm charts from. - [Apps](#apps) -- defines the applications/charts you want to manage in your cluster. @@ -19,10 +19,10 @@ Optional : Yes. Synopsis: Metadata is used for the human reader of the desired state file. While it is optional, we recommend having a maintainer and scope/cluster metadata. -Options: +Options: - you can define any key/value pairs. -Example: +Example: ```toml [metadata] @@ -42,8 +42,8 @@ Optional : Yes, only needed if you want Helmsman to connect kubectl to your clus Synopsis: defines where to find the certificates needed for connecting kubectl to a k8s cluster. If connection settings (username/password/clusterAPI) are provided in the Settings section below, then you need AT LEAST to provide caCrt and caKey. You can optionally provide a client certificate (caClient) depending on your cluster connection setup. -Options: -- caCrt : a valid S3/GCS bucket or local relative file path to a certificate file. +Options: +- caCrt : a valid S3/GCS bucket or local relative file path to a certificate file. - caKey : a valid S3/GCS bucket or local relative file path to a client key file. - caClient: a valid S3/GCS bucket or local relative file path to a client certificate file. @@ -51,12 +51,12 @@ Options: > bucket format is: ://bucket-name/dir1/dir2/.../file.extension -Example: +Example: ```toml [certificates] -caCrt = "s3://myS3bucket/mydir/ca.crt" -caKey = "gs://myGCSbucket/ca.key" +caCrt = "s3://myS3bucket/mydir/ca.crt" +caKey = "gs://myGCSbucket/ca.key" caClient ="../path/to/my/local/client-certificate.crt" #caClient = "$CA_CLIENT" ``` @@ -75,32 +75,34 @@ Optional : No. Synopsis: provides settings for connecting to your k8s cluster and configuring Helm's Tiller in the cluster. -Options: +Options: - kubeContext : this is always required and defines what context to use in kubectl. Helmsman will try connect to this context first, if it does not exist, it will try to create it (i.e. connect to a k8s cluster) using the options below. The following options can be skipped if your kubectl context is already created and you don't want Helmsman to connect kubectl to your cluster for you. When using Helmsman in CI pipeline, these details are required to connect to your cluster every time the pipeline is executed. - username : the username to be used for kubectl credentials. -- password : an environment variable name (starting with `$`) where your password is stored. Get the password from your k8s admin or consult k8s docs on how to get/set it. +- password : an environment variable name (starting with `$`) where your password is stored. Get the password from your k8s admin or consult k8s docs on how to get/set it. - clusterURI : the URI for your cluster API or the name of an environment variable (starting with `$`) containing the URI. -- serviceAccount: the name of the service account to use to initiate helm. This should have enough permissions to allow Helm to work and should exist already in the cluster. More details can be found in [helm's RBAC guide](https://github.com/kubernetes/helm/blob/master/docs/rbac.md) +- serviceAccount: the name of the service account to use to initiate helm. This should have enough permissions to allow Helm to work and should exist already in the cluster. More details can be found in [helm's RBAC guide](https://github.com/kubernetes/helm/blob/master/docs/rbac.md) - storageBackend : by default Helm stores release information in configMaps, using secrets is for storage is recommended for security. Setting this flag to `secret` will deploy/upgrade Tiller with the `--storage=secret`. Other values will be skipped and configMaps will be used. -- slackWebhook : a [Slack](slack.com) Webhook URL to receive Helmsman notifications. This can be passed directly or in an environment variable. +- slackWebhook : a [Slack](slack.com) Webhook URL to receive Helmsman notifications. This can be passed directly or in an environment variable. +- reverseDelete : if set to `"yes"` it will reverse the priority order whilst deleting. > If you use `storageBackend` with a Tiller that has been previously deployed with configMaps as storage backend, you need to migrate your release information from the configMap to the new secret on your own. Helm does not support this yet. -Example: +Example: ```toml [settings] -kubeContext = "minikube" +kubeContext = "minikube" # username = "admin" -# password = "$K8S_PASSWORD" -# clusterURI = "https://192.168.99.100:8443" +# password = "$K8S_PASSWORD" +# clusterURI = "https://192.168.99.100:8443" ## clusterURI= "$K8S_URI" # serviceAccount = "my-service-account" # storageBackend = "secret" # slackWebhook = $MY_SLACK_WEBHOOK +# reverseDelete = "no" ``` ```yaml @@ -112,7 +114,8 @@ settings: ##clusterURI: "$K8S_URI" #serviceAccount: "my-service-account" #storageBackend: "secret" - #slackWebhook : "$MY_SLACK_WEBHOOK" + #slackWebhook: "$MY_SLACK_WEBHOOK" + #reverseDelete: "no" ``` ## Namespaces @@ -122,7 +125,7 @@ Optional : No. Synopsis: defines the namespaces to be used/created in your k8s cluster and whether they are protected or not. It also defines if Tiller should be deployed in these namespaces and with what configurations (TLS and service account). You can add as many namespaces as you like. If a namespace does not already exist, Helmsman will create it. -Options: +Options: - protected : defines if a namespace is protected (true or false). Default false. > For the definition of what a protected namespace means, check the [protection guide](how_to/protect_namespaces_and_releases.md) - installTiller: defines if Tiller should be deployed in this namespace or not. Default is false. Any chart desired to be deployed into a namespace with a Tiller deployed, will be deployed using that Tiller and not the one in kube-system unless you use the `TillerNamespace` option (see the [Apps](#apps) section below) to use another Tiller. @@ -132,7 +135,7 @@ Options: 1. If the `serviceAccount` defined in the `settings` section exists in the namespace you want to deploy Tiller in, it will be used, else 2. Helmsman creates the service account in that namespace and binds it to a role. If the namespace is kube-system, the service account is bound to `cluster-admin` clusterrole. Otherwise, a new role called `helmsman-tiller` is created in that namespace and only gives access to that namespace. - + > If `installTiller` is not defined or set to false, this flag is ignored. - The following options are `ALL` needed for deploying Tiller with TLS enabled. If they are not all defined, they will be ignored and Tiller will be deployed without TLS. All of these options can be provided as either: a valid local file path, a valid GCS or S3 bucket URI or an environment variable containing a file path or bucket URI. @@ -142,7 +145,7 @@ Options: - clientCert: the SSL certificate for the Helm client. - clientKey: the SSL certificate private key for the Helm client. -Example: +Example: ```toml [namespaces] @@ -166,7 +169,7 @@ clientKey = "s3://mybucket/mydir/helm.key.pem" ```yaml namespaces: # to prevent deploying Tiller into kube-system, use the two lines below - # kube-system: + # kube-system: # installTiller: false # this line can be omitted since installTiller defaults to false staging: dev: @@ -186,20 +189,20 @@ namespaces: Optional : No. -Synopsis: defines the Helm repos where your charts can be found. You can add as many repos as you like. Public repos can be added without any additional setup. Private repos require authentication. +Synopsis: defines the Helm repos where your charts can be found. You can add as many repos as you like. Public repos can be added without any additional setup. Private repos require authentication. -> AS of version v0.2.0, both AWS S3 and Google GCS buckets can be used for private repos (using the [Helm S3](https://github.com/hypnoglow/helm-s3) and [Helm GCS](https://github.com/nouney/helm-gcs) plugins). +> AS of version v0.2.0, both AWS S3 and Google GCS buckets can be used for private repos (using the [Helm S3](https://github.com/hypnoglow/helm-s3) and [Helm GCS](https://github.com/nouney/helm-gcs) plugins). Authenticating to private helm repos: - **For S3 repos**: you need to have valid AWS access keys in your environment variables. See [here](https://github.com/hypnoglow/helm-s3#note-on-aws-authentication) for more details. - **For GCS repos**: check [here](https://www.terraform.io/docs/providers/google/index.html#authentication-json-file) for getting the required authentication file. Once you have the file, you have two options, either: - set `GOOGLE_APPLICATION_CREDENTIALS` environment variable to contain the absolute path to your Google cloud credentials.json file. - - Or, set `GCLOUD_CREDENTIALS` environment variable to contain the content of the credentials.json file. + - Or, set `GCLOUD_CREDENTIALS` environment variable to contain the content of the credentials.json file. -Options: +Options: - you can define any key/value pairs where key is the repo name and value is a valid URI for the repo. -Example: +Example: ```toml [helmRepos] @@ -221,20 +224,20 @@ helmRepos: Optional : Yes. -Synopsis: defines the releases (instances of Helm charts) you would like to manage in your k8s cluster. +Synopsis: defines the releases (instances of Helm charts) you would like to manage in your k8s cluster. -Releases must have unique names which are defined under `apps`. Example: in `[apps.jenkins]`, the release name will be `jenkins` and it should be unique in your cluster. +Releases must have unique names which are defined under `apps`. Example: in `[apps.jenkins]`, the release name will be `jenkins` and it should be unique in your cluster. -Options: +Options: **Required** -- namespace : the namespace where the release should be deployed. The namespace should map to one of the ones defined in [namespaces](#namespaces). +- namespace : the namespace where the release should be deployed. The namespace should map to one of the ones defined in [namespaces](#namespaces). - enabled : describes the required state of the release (true for enabled, false for disabled). Once a release is deployed, you can change it to false if you want to delete this release [default is false]. - chart : the chart name. It should contain the repo name as well. Example: repoName/chartName. Changing the chart name means delete and reinstall this release using the new Chart. - version : the chart version. **Optional** -- tillerNamespace : which Tiller to use for deploying this release. This is available starting from v1.4.0-rc The decision on which Tiller to use for deploying a release follows the following criteria: +- tillerNamespace : which Tiller to use for deploying this release. This is available starting from v1.4.0-rc The decision on which Tiller to use for deploying a release follows the following criteria: 1. If `tillerNamespace`is explicitly defined, it is used. 2. If `tillerNamespace`is not defined and the namespace in which the release will be deployed has a Tiller installed by Helmsman (i.e. has `installTiller set to true` in the [Namespaces](#namespaces) section), Tiller in that namespace is used. 3. If none of the above, the shared Tiller in `kube-system` is used. @@ -253,30 +256,29 @@ Options: - priority : defines the priority of applying operations on this release. Only negative values allowed and the lower the value, the higher the priority. Default priority is 0. Apps with equal priorities will be applied in the order they were added in your state file (DSF). - [apps..set] : is used to override certain values from values.yaml with values from environment variables (or ,starting from v1.3.0-rc, directly provided in the Desired State File). This is particularly useful for passing secrets to charts. If the an environment variable with the same name as the provided value exists, the environment variable value will be used, otherwise, the provided value will be used as is. -Example: +Example: > Whitespace does not matter in TOML files. You could use whatever indentation style you prefer for readability. ```toml [apps] - [apps.jenkins] - name = "jenkins" + [apps.jenkins] + name = "jenkins" description = "jenkins" - namespace = "staging" - enabled = true - chart = "stable/jenkins" + namespace = "staging" + enabled = true + chart = "stable/jenkins" version = "0.9.0" - valuesFile = "" - purge = false - test = true + valuesFile = "" + purge = false + test = true protected = false wait = true priority = -3 - [apps.jenkins.set] - secret1="$SECRET_ENV_VAR1" - secret2="SECRET_ENV_VAR2" # works with/without $ at the beginning - + [apps.jenkins.set] + secret1="$SECRET_ENV_VAR1" + secret2="SECRET_ENV_VAR2" # works with/without $ at the beginning ``` ```yaml @@ -297,5 +299,4 @@ apps: set: secret1: "$SECRET_ENV_VAR1" secret2: "$SECRET_ENV_VAR2" - ``` diff --git a/docs/how_to/run_helmsman_with_hosted_cluster.md b/docs/how_to/run_helmsman_with_hosted_cluster.md index c2a6f9cd..1adaf200 100644 --- a/docs/how_to/run_helmsman_with_hosted_cluster.md +++ b/docs/how_to/run_helmsman_with_hosted_cluster.md @@ -2,9 +2,9 @@ version: v1.3.0-rc --- -You can manage Helm charts deployment on a hosted K8S cluster in the cloud or on-prem. You need to include the required information to connect to the cluster in your state file. +You can manage Helm charts deployment on a hosted K8S cluster in the cloud or on-prem. You need to include the required information to connect to the cluster in your state file. -**IMPORTANT**: Helmsman expects certain environment variables to be available depending on where your cluster and connection certificates are hosted. Certificates can be used from S3/GCS buckets or local file system. +**IMPORTANT**: Helmsman expects certain environment variables to be available depending on where your cluster and connection certificates are hosted. Certificates can be used from S3/GCS buckets or local file system. ## AWS If you use s3 buckets for storing certificates or for hosting private helm repos, Helmsman needs valid AWS access keys to be able to retrieve private charts or certificates from your s3 buckets. It expects the keys to be in the following environment variables: @@ -14,10 +14,10 @@ If you use s3 buckets for storing certificates or for hosting private helm repos - AWS_DEFAULT_REGION ## GCS -If you use GCS buckets for storing certificates or for hosting private helm repos, Helmsman needs valid Google Cloud credentials to authenticate reading requests from private buckets. This can be provided in one of two ways: +If you use GCS buckets for storing certificates or for hosting private helm repos, Helmsman needs valid Google Cloud credentials to authenticate reading requests from private buckets. This can be provided in one of two ways: - set `GOOGLE_APPLICATION_CREDENTIALS` environment variable to contain the absolute path to your Google cloud credentials.json file. -- Or, set `GCLOUD_CREDENTIALS` environment variable to contain the content of the credentials.json file. +- Or, set `GCLOUD_CREDENTIALS` environment variable to contain the content of the credentials.json file. check [here](https://www.terraform.io/docs/providers/google/index.html#authentication-json-file) for getting the required authentication file. @@ -30,54 +30,54 @@ Below is an example state file: ```toml [metadata] -org = "orgX" -maintainer = "k8s-admin" + org = "orgX" + maintainer = "k8s-admin" # Certificates are used to connect to the cluster. Currently, they can only be retrieved from s3 buckets. [certificates] -caCrt = "s3://your-bucket/ca.crt" # s3 bucket -caKey = "$K8S_CLIENT_KEY" # relative file path -caClient = "gs://your-GCS-bucket/caClient.crt" # GCS bucket + caCrt = "s3://your-bucket/ca.crt" # s3 bucket + caKey = "$K8S_CLIENT_KEY" # relative file path + caClient = "gs://your-GCS-bucket/caClient.crt" # GCS bucket [settings] -kubeContext = "mycontext" -username = "<>" -password = "$K8S_PASSWORD" # the name of an environment variable containing the k8s password -clusterURI = "$K8S_URI" # cluster API + kubeContext = "mycontext" + username = "<>" + password = "$K8S_PASSWORD" # the name of an environment variable containing the k8s password + clusterURI = "$K8S_URI" # cluster API [namespaces] [namespaces.staging] [helmRepos] -stable = "https://kubernetes-charts.storage.googleapis.com" -incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" -myrepo = "s3://my-private-repo/charts" -myGCSrepo = "gs://my-GCS-repo/charts" + stable = "https://kubernetes-charts.storage.googleapis.com" + incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" + myrepo = "s3://my-private-repo/charts" + myGCSrepo = "gs://my-GCS-repo/charts" [apps] - [apps.jenkins] - name = "jenkins" + [apps.jenkins] + name = "jenkins" description = "jenkins" - namespace = "staging" - enabled = true - chart = "stable/jenkins" - version = "0.9.1" - valuesFile = "" - purge = false - test = false + namespace = "staging" + enabled = true + chart = "stable/jenkins" + version = "0.9.1" + valuesFile = "" + purge = false + test = false - [apps.artifactory] - name = "artifactory" + [apps.artifactory] + name = "artifactory" description = "artifactory" - namespace = "staging" - enabled = true - chart = "stable/artifactory" - version = "6.2.0" - valuesFile = "" - purge = false - test = false + namespace = "staging" + enabled = true + chart = "stable/artifactory" + version = "6.2.0" + valuesFile = "" + purge = false + test = false ``` ```yaml @@ -143,4 +143,4 @@ The above example requires the following environment variables to be set: - K8S_URI (used in the file) -For secure Helm configurations to fit for multi-tenant clusters, check the [multitenancy guide](multitenant_clusters_guide.md). \ No newline at end of file +For secure Helm configurations to fit for multi-tenant clusters, check the [multitenancy guide](multitenant_clusters_guide.md). diff --git a/docs/how_to/use_private_helm_charts.md b/docs/how_to/use_private_helm_charts.md index 4ff2e0bd..761d0556 100644 --- a/docs/how_to/use_private_helm_charts.md +++ b/docs/how_to/use_private_helm_charts.md @@ -4,11 +4,11 @@ version: v1.3.0-rc # use private helm charts -Helmsman allows you to use private charts from private repos. Currently only repos hosted in S3 or GCS buckets are supported for private repos. +Helmsman allows you to use private charts from private repos. Currently only repos hosted in S3 or GCS buckets are supported for private repos. Other hosting options might be supported in the future. Please open an issue if you require supporting other options. -define your private repo: +define your private repo: ```toml ... @@ -20,7 +20,7 @@ myPrivateRepo = s3://this-is-a-private-repo/charts ... -``` +``` ```yaml ... @@ -49,6 +49,6 @@ Helmsman uses the [helm s3](https://github.com/hypnoglow/helm-s3) plugin to work If you are using GCS private repos, you need to provide one of the following env variables: - `GOOGLE_APPLICATION_CREDENTIALS` environment variable to contain the absolute path to your Google cloud credentials.json file. -- Or, `GCLOUD_CREDENTIALS` environment variable to contain the content of the credentials.json file. +- Or, `GCLOUD_CREDENTIALS` environment variable to contain the content of the credentials.json file. -Helmsman uses the [helm GCS](https://github.com/nouney/helm-gcs) plugin to work with GCS helm repos. \ No newline at end of file +Helmsman uses the [helm GCS](https://github.com/nouney/helm-gcs) plugin to work with GCS helm repos. diff --git a/docs/how_to/use_the_priority_key.md b/docs/how_to/use_the_priority_key.md index 5792f6a5..d1332e13 100644 --- a/docs/how_to/use_the_priority_key.md +++ b/docs/how_to/use_the_priority_key.md @@ -4,35 +4,35 @@ version: v1.3.0-rc # Using the priority key for Apps -The `priority` flag in Apps definition allows you to define the order at which apps operations will be applied. This is useful if you have dependencies between your apps/services. +The `priority` flag in Apps definition allows you to define the order at which apps operations will be applied. This is useful if you have dependencies between your apps/services. -Priority is an optional flag and has a default value of 0 (zero). If set, it can only use a negative value. The lower the value, the higher the priority. +Priority is an optional flag and has a default value of 0 (zero). If set, it can only use a negative value. The lower the value, the higher the priority. + +If you want your apps te de deleted in the reverse order as they where created, you can also use the optional `Settings` flag `reverseDelete`, to acheive this, set it to `"yes"` ## Example ```toml [metadata] -org = "example.com" -description = "example Desired State File for demo purposes." - + org = "example.com" + description = "example Desired State File for demo purposes." [settings] -kubeContext = "minikube" + kubeContext = "minikube" + reverseDelete = "no" # Optional flat to reverse the priorities when deletting [namespaces] [namespaces.staging] - protected = false + protected = false [namespaces.production] - prtoected = true + prtoected = true [helmRepos] stable = "https://kubernetes-charts.storage.googleapis.com" incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" - [apps] - - [apps.jenkins] + [apps.jenkins] name = "jenkins" # should be unique across all apps description = "jenkins" namespace = "staging" # maps to the namespace as defined in environments above @@ -42,7 +42,7 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" valuesFile = "" # leaving it empty uses the default chart values priority= -2 - [apps.jenkins1] + [apps.jenkins1] name = "jenkins1" # should be unique across all apps description = "jenkins" namespace = "staging" # maps to the namespace as defined in environments above @@ -50,9 +50,9 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" chart = "stable/jenkins" # changing the chart name means delete and recreate this chart version = "0.14.3" # chart version valuesFile = "" # leaving it empty uses the default chart values - - [apps.jenkins2] + + [apps.jenkins2] name = "jenkins2" # should be unique across all apps description = "jenkins" namespace = "production" # maps to the namespace as defined in environments above @@ -62,7 +62,7 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" valuesFile = "" # leaving it empty uses the default chart values priority= -3 - [apps.artifactory] + [apps.artifactory] name = "artifactory" # should be unique across all apps description = "artifactory" namespace = "staging" # maps to the namespace as defined in environments above @@ -81,4 +81,4 @@ DECISION: release [ jenkins ] is not present in the current k8s context. Will in DECISION: release [ artifactory ] is not present in the current k8s context. Will install it in namespace [[ staging ]] -- priority: -2 DECISION: release [ jenkins1 ] is not present in the current k8s context. Will install it in namespace [[ staging ]] -- priority: 0 -``` \ No newline at end of file +``` diff --git a/example.toml b/example.toml index 952f7f23..45e4982c 100644 --- a/example.toml +++ b/example.toml @@ -1,63 +1,66 @@ # version: v1.5.0 # metadata -- add as many key/value pairs as you want [metadata] -org = "example.com" -maintainer = "k8s-admin (me@example.com)" -description = "example Desired State File for demo purposes." + org = "example.com" + maintainer = "k8s-admin (me@example.com)" + description = "example Desired State File for demo purposes." + # paths to the certificate for connecting to the cluster -# You can skip this if you use Helmsman on a machine with kubectl already connected to your k8s cluster. +# You can skip this if you use Helmsman on a machine with kubectl already connected to your k8s cluster. # you have to use exact key names here : 'caCrt' for certificate and 'caKey' for the key and caClient for the client certificate [certificates] -#caClient = "gs://mybucket/client.crt" # GCS bucket path -#caCrt = "s3://mybucket/ca.crt" # S3 bucket path -#caKey = "../ca.key" # valid local file relative path +# caClient = "gs://mybucket/client.crt" # GCS bucket path +# caCrt = "s3://mybucket/ca.crt" # S3 bucket path +# caKey = "../ca.key" # valid local file relative path + [settings] -kubeContext = "minikube" # will try connect to this context first, if it does not exist, it will be created using the details below -#username = "admin" -#password = "$K8S_PASSWORD" # the name of an environment variable containing the k8s password -#clusterURI = "$K8S_URI" # the name of an environment variable containing the cluster API -##clusterURI = "https://192.168.99.100:8443" # equivalent to the above -serviceAccount = "tiller" # k8s serviceaccount. If it does not exist, it will be created. -storageBackend = "secret" # default is configMap -#slackWebhook = "$slack" # or "your slack webhook url" + kubeContext = "minikube" # will try connect to this context first, if it does not exist, it will be created using the details below +# username = "admin" +# password = "$K8S_PASSWORD" # the name of an environment variable containing the k8s password +# clusterURI = "$K8S_URI" # the name of an environment variable containing the cluster API +# #clusterURI = "https://192.168.99.100:8443" # equivalent to the above + serviceAccount = "tiller" # k8s serviceaccount. If it does not exist, it will be created. + storageBackend = "secret" # default is configMap +# slackWebhook = "$slack" # or "your slack webhook url" +# reverseDelete = "no" # reverse the priorities on delete + # define your environments and their k8s namespaces -# syntax: -# [namespaces.] -- whitespace before this entry does not matter, use whatever indentation style you like - # protected = -- default to false +# syntax: +# [namespaces.] -- whitespace before this entry does not matter, use whatever indentation style you like +# protected = -- default to false [namespaces] [namespaces.production] - protected = true + protected = true [namespaces.staging] - protected = false - installTiller = true - # tillerServiceAccount = "tiller-staging" # should already exist in the staging namespace - # caCert = "secrets/ca.cert.pem" # or an env var, e.g. "$CA_CERT_PATH" - # tillerCert = "secrets/tiller.cert.pem" # or S3 bucket s3://mybucket/tiller.crt - # tillerKey = "secrets/tiller.key.pem" # or GCS bucket gs://mybucket/tiller.key - # clientCert = "secrets/helm.cert.pem" - # clientKey = "secrets/helm.key.pem" + protected = false + installTiller = true +# tillerServiceAccount = "tiller-staging" # should already exist in the staging namespace +# caCert = "secrets/ca.cert.pem" # or an env var, e.g. "$CA_CERT_PATH" +# tillerCert = "secrets/tiller.cert.pem" # or S3 bucket s3://mybucket/tiller.crt +# tillerKey = "secrets/tiller.key.pem" # or GCS bucket gs://mybucket/tiller.key +# clientCert = "secrets/helm.cert.pem" +# clientKey = "secrets/helm.key.pem" # define any private/public helm charts repos you would like to get charts from # syntax: repo_name = "repo_url" # only private repos hosted in s3 buckets are now supported [helmRepos] -stable = "https://kubernetes-charts.storage.googleapis.com" -incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" -#myS3repo = "s3://my-S3-private-repo/charts" -#myGCSrepo = "gs://my-GCS-private-repo/charts" + stable = "https://kubernetes-charts.storage.googleapis.com" + incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" +# myS3repo = "s3://my-S3-private-repo/charts" +# myGCSrepo = "gs://my-GCS-private-repo/charts" + # define the desired state of your applications helm charts # each contains the following: - - [apps] - # jenkins will be deployed using the Tiller in the staging namespace - [apps.jenkins] + # jenkins will be deployed using the Tiller in the staging namespace + [apps.jenkins] namespace = "staging" # maps to the namespace as defined in namespaces above enabled = true # change to false if you want to delete this app release [default = false] chart = "stable/jenkins" # changing the chart name means delete and recreate this release @@ -71,13 +74,13 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" protected = true priority= -3 wait = true - [apps.jenkins.set] # values to override values from values.yaml with values from env vars or directly entered-- useful for passing secrets to charts - AdminPassword="$JENKINS_PASSWORD" # $JENKINS_PASSWORD must exist in the environment - AdminUser="admin" + [apps.jenkins.set] # values to override values from values.yaml with values from env vars or directly entered-- useful for passing secrets to charts + AdminPassword="$JENKINS_PASSWORD" # $JENKINS_PASSWORD must exist in the environment + AdminUser="admin" - # artifactory will be deployed using the Tiller in the kube-system namespace - [apps.artifactory] + # artifactory will be deployed using the Tiller in the kube-system namespace + [apps.artifactory] namespace = "production" # maps to the namespace as defined in namespaces above enabled = true # change to false if you want to delete this app release [default = flase] chart = "stable/artifactory" # changing the chart name means delete and recreate this release @@ -89,4 +92,4 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" test = false # run the tests when this release is installed for the first time only priority= -2 -# See https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md#apps for more apps options \ No newline at end of file +# See https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md#apps for more apps options diff --git a/example.yaml b/example.yaml index 2a3846c4..8bfcb1b2 100644 --- a/example.yaml +++ b/example.yaml @@ -6,11 +6,11 @@ metadata: description: "example Desired State File for demo purposes." # paths to the certificate for connecting to the cluster -# You can skip this if you use Helmsman on a machine with kubectl already connected to your k8s cluster. +# You can skip this if you use Helmsman on a machine with kubectl already connected to your k8s cluster. # you have to use exact key names here : 'caCrt' for certificate and 'caKey' for the key and caClient for the client certificate certificates: #caClient: "gs://mybucket/client.crt" # GCS bucket path - #caCrt: "s3://mybucket/ca.crt" # S3 bucket path + #caCrt: "s3://mybucket/ca.crt" # S3 bucket path #caKey: "../ca.key" # valid local file relative path settings: @@ -22,6 +22,7 @@ settings: #serviceAccount: "foo" # k8s serviceaccount must be already defined, validation error will be thrown otherwise storageBackend: "secret" # default is configMap #slackWebhook: "$slack" # or your slack webhook url + #reverseDelete: "no" # reverse the priorities on delete # define your environments and their k8s namespaces namespaces: @@ -31,7 +32,7 @@ namespaces: protected: false installTiller: true #tillerServiceAccount: "tiller-staging" # should already exist in the staging namespace - #caCert: "secrets/ca.cert.pem" # or an env var, e.g. "$CA_CERT_PATH" + #caCert: "secrets/ca.cert.pem" # or an env var, e.g. "$CA_CERT_PATH" #tillerCert: "secrets/tiller.cert.pem" # or S3 bucket s3://mybucket/tiller.crt #tillerKey: "secrets/tiller.key.pem" # or GCS bucket gs://mybucket/tiller.key #clientCert: "secrets/helm.cert.pem" @@ -87,4 +88,4 @@ apps: test: false # run the tests when this release is installed for the first time only priority: -2 -# See https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md#apps for more apps options \ No newline at end of file +# See https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md#apps for more apps options From b85e34b3b965f67b22e9863bb29e1cd9888f7525 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sun, 2 Sep 2018 22:00:21 +0100 Subject: [PATCH 0220/1127] adding a Makefile --- .circleci/config.yml | 54 ++++++++++++++------------------------ .gitignore | 4 ++- CONTRIBUTION.md | 9 ++----- Makefile | 60 +++++++++++++++++++++++++++++++++++++++++++ dockerfile/dockerfile | 24 ++++++++--------- test_files/dockerfile | 8 +++--- 6 files changed, 97 insertions(+), 62 deletions(-) create mode 100644 Makefile diff --git a/.circleci/config.yml b/.circleci/config.yml index aadacb8a..616949db 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -11,53 +11,37 @@ jobs: - run: name: Build helmsman command: | - echo "fetching dependencies ..." - go get github.com/Praqma/helmsman/gcs - go get github.com/Praqma/helmsman/aws - go get github.com/imdario/mergo - go get github.com/hashicorp/go-version echo "building ..." - TAG=$(git describe --abbrev=0 --tags)-$(date +"%d%m%y") - go build -ldflags '-X main.version='$TAG' -extldflags "-static"' - + make build + test: - docker: + docker: - image: praqma/helmsman-test steps: - - checkout + - checkout - run: - name: Unit test helmsman + name: Unit test helmsman command: | - echo "fetching dependencies ..." - go get github.com/Praqma/helmsman/gcs - go get github.com/Praqma/helmsman/aws - go get github.com/imdario/mergo - go get github.com/hashicorp/go-version echo "running tests ..." - go test -v - release: - docker: + make test + release: + docker: - image: praqma/helmsman-test steps: - - checkout - - run: + - checkout + - run: name: Release helmsman command: | TAG_SHA=$(git rev-parse $(git describe --abbrev=0 --tags)) LAST_COMMIT=$(git rev-parse HEAD) - if [ "${TAG_SHA}" == "${LAST_COMMIT}" ]; then - echo "fetching dependencies ..." - go get github.com/Praqma/helmsman/gcs - go get github.com/Praqma/helmsman/aws - go get github.com/imdario/mergo - go get github.com/hashicorp/go-version + if [ "${TAG_SHA}" == "${LAST_COMMIT}" ]; then echo "releasing ..." - goreleaser --release-notes release-notes.md - else + make release + else echo "No release is needed yet." exit 0 - fi - + fi + # - setup_remote_docker # - run: # name: build docker images and push them to dockerhub @@ -71,7 +55,7 @@ jobs: # docker build -t praqma/helmsman:$TAG-helm-v2.7.2 --build-arg HELM_VERSION=v2.7.2 dockerfile/. # docker push praqma/helmsman:$TAG-helm-v2.7.2 - + workflows: version: 2 build-test-push-release: @@ -79,12 +63,12 @@ workflows: - build - test: requires: - - build + - build - release: requires: - test filters: branches: - only: master + only: master tags: - only: /^v.*/ + only: /^v.*/ diff --git a/.gitignore b/.gitignore index 05eb9627..619a16e7 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,7 @@ *.key *.crt /dist +/vendor/ *.world -*.world1 \ No newline at end of file +*.world1 +helmsman diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md index 5932a678..33b6a50f 100644 --- a/CONTRIBUTION.md +++ b/CONTRIBUTION.md @@ -1,6 +1,6 @@ # Contribution Guide -Pull requests, feeback/feature requests are all welcome. This guide will be updated overtime. +Pull requests, feeback/feature requests are all welcome. This guide will be updated overtime. ## Build helmsman from source @@ -8,12 +8,7 @@ To build helmsman from source, you need go:1.9+. Follow the steps below: ``` git clone https://github.com/Praqma/helmsman.git -go get github.com/BurntSushi/toml -go get github.com/Praqma/helmsman/gcs -go get github.com/Praqma/helmsman/aws -go get github.com/imdario/mergo -TAG=$(git describe --abbrev=0 --tags)-$(date +"%s") -go build -ldflags '-X main.version='$TAG' -extldflags "-static"' +make build ``` ## Submitting pull requests diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..a454ca28 --- /dev/null +++ b/Makefile @@ -0,0 +1,60 @@ +.DEFAULT_GOAL := help + +PKGS := $(shell go list ./... | grep -v /vendor/) +TAG = $(shell git describe --tags --abbrev=0 HEAD) +LAST = $(shell git describe --tags --abbrev=0 HEAD^) +BODY = "`git log ${LAST}..HEAD --oneline --decorate` `printf '\n\#\#\# [Build Info](${BUILD_URL})'`" +DATE = $(shell date +'%d%m%y') + +ifneq ($(OS),Windows_NT) + # Before we start test that we have the mandatory executables available + EXECUTABLES = go + OK := $(foreach exec,$(EXECUTABLES),\ + $(if $(shell which $(exec)),some string,$(error "No $(exec) in PATH, please install $(exec)"))) +endif + +help: + @echo "Available options:" + @grep -E '^[/1-9a-zA-Z._%-]+:.*?## .*$$' $(MAKEFILE_LIST) \ + | sort \ + | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-45s\033[0m %s\n", $$1, $$2}' +.PHONY: help + +clean: ## Remove build artifacts + @git clean -fdX +.PHONY: clean + +fmt: ## Reformat package sources + @go fmt +.PHONY: fmt + +dependencies: ## Ensure all the necessary dependencies + @go get -t -d -v ./... +.PHONY: dependencies + +build: dependencies ## Build the package + @go build -ldflags '-X main.version='${TAG}-${DATE}' -extldflags "-static"' + +generate: + @go generate #${PKGS} +.PHONY: generate + +check: + @go vet #${PKGS} +.PHONY: check + +test: dependencies ## Run unit tests + @go test -v -cover -race -p=1 #${PKGS} +.PHONY: test + +cross: dependencies ## Create binaries for all OSs + @env CGO_ENABLED=0 gox -os '!freebsd !netbsd' -arch '!arm' -output "dist/{{.Dir}}_{{.OS}}_{{.Arch}}" -ldflags '-X main.Version=${TAG}-${DATE}' +.PHONY: cross + +release: dependencies ## Generate a new release + goreleaser --release-notes release-notes.md + +tools: ## Get extra tools used by this makefile + @go get -u github.com/mitchellh/gox + @go get -u github.com/goreleaser/goreleaser +.PHONY: tools diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile index fee50301..4e4a07d4 100644 --- a/dockerfile/dockerfile +++ b/dockerfile/dockerfile @@ -1,12 +1,7 @@ FROM golang:1.9 as builder WORKDIR /go/src/ -RUN go get github.com/BurntSushi/toml -RUN go get gopkg.in/yaml.v2 -RUN git clone https://github.com/Praqma/helmsman.git -RUN go get github.com/Praqma/helmsman/gcs -RUN go get github.com/Praqma/helmsman/aws -RUN go get github.com/imdario/mergo -RUN go get github.com/hashicorp/go-version +RUN git clone https://github.com/Praqma/helmsman.git +RUN apk --no-cache add make # build a statically linked binary so that it works on stripped linux images such as alpine/busybox. RUN cd helmsman \ @@ -15,18 +10,19 @@ RUN cd helmsman \ && LT_SHA=$(git rev-parse ${LastTag}^{}) \ && LC_SHA=$(git rev-parse HEAD) \ && if [ ${LT_SHA} != ${LC_SHA} ]; then TAG=latest-$(date +"%d%m%y"); fi \ - && CGO_ENABLED=0 GOOS=linux go install -a -ldflags '-X main.version='$TAG' -extldflags "-static"' . + && make dependencies \ + && CGO_ENABLED=0 GOOS=linux go install -a -ldflags '-X main.version='$TAG' -extldflags "-static"' . FROM alpine:3.6 as kube ENV KUBE_LATEST_VERSION="v1.10.0" -RUN apk add --update ca-certificates -RUN apk add --update -t deps curl -RUN curl -L https://storage.googleapis.com/kubernetes-release/release/${KUBE_LATEST_VERSION}/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl -RUN chmod +x /usr/local/bin/kubectl +RUN apk add --update ca-certificates +RUN apk add --update -t deps curl +RUN curl -L https://storage.googleapis.com/kubernetes-release/release/${KUBE_LATEST_VERSION}/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl +RUN chmod +x /usr/local/bin/kubectl # The image to keep -FROM alpine:3.7 +FROM alpine:3.7 RUN apk add --update --no-cache ca-certificates git ARG HELM_VERSION=v2.8.1 @@ -47,4 +43,4 @@ COPY --from=builder /go/bin/helmsman /bin/helmsman WORKDIR /tmp # ENTRYPOINT ["/bin/helmsman"] - + diff --git a/test_files/dockerfile b/test_files/dockerfile index 7637b4e9..3befa16d 100644 --- a/test_files/dockerfile +++ b/test_files/dockerfile @@ -8,7 +8,7 @@ ENV HELM_VERSION v2.7.0 RUN apk add --update ca-certificates \ && apk add --update -t deps curl \ && curl -L https://storage.googleapis.com/kubernetes-release/release/${KUBE_LATEST_VERSION}/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl \ - && chmod +x /usr/local/bin/kubectl + && chmod +x /usr/local/bin/kubectl RUN apk add --update --no-cache ca-certificates git \ && apk add --update -t deps curl tar gzip make bash \ @@ -16,9 +16,7 @@ RUN apk add --update --no-cache ca-certificates git \ && mv /tmp/linux-amd64/helm /usr/local/bin/helm \ && chmod +x /usr/local/bin/helm \ && rm -rf /tmp/linux-amd64 - -RUN go get github.com/BurntSushi/toml \ - && go get github.com/goreleaser/goreleaser \ - && go get github.com/imdario/mergo + +RUN go get github.com/goreleaser/goreleaser WORKDIR src/helmsman From ff95d57fe14142fd501f8f52b9ad6c1139d62294 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Mon, 3 Sep 2018 15:40:46 +0100 Subject: [PATCH 0221/1127] fix typos --- docs/how_to/manipulate_apps.md | 58 +++++++++++----------- docs/how_to/override_defined_namespaces.md | 16 +++--- example.toml | 2 +- example.yaml | 4 +- helm_helpers.go | 2 +- 5 files changed, 41 insertions(+), 41 deletions(-) diff --git a/docs/how_to/manipulate_apps.md b/docs/how_to/manipulate_apps.md index bd3c3acb..b931dbab 100644 --- a/docs/how_to/manipulate_apps.md +++ b/docs/how_to/manipulate_apps.md @@ -2,34 +2,34 @@ version: v1.5.0 --- -# install releases +# install releases You can run helmsman with the [example.toml](https://github.com/Praqma/helmsman/blob/master/example.toml) or [example.yaml](https://github.com/Praqma/helmsman/blob/master/example.yaml) file. -``` +``` -$ helmsman --apply -f example.toml +$ helmsman --apply -f example.toml 2017/11/19 18:17:57 Parsed [[ example.toml ]] successfully and found [ 2 ] apps. 2017/11/19 18:17:59 WARN: I could not create namespace [staging ]. It already exists. I am skipping this. 2017/11/19 18:17:59 WARN: I could not create namespace [default ]. It already exists. I am skipping this. -2017/11/19 18:18:02 INFO: Executing the following plan ... +2017/11/19 18:18:02 INFO: Executing the following plan ... --------------- -Ok, I have generated a plan for you at: 2017-11-19 18:17:59.347859706 +0100 CET m=+2.255430021 +Ok, I have generated a plan for you at: 2017-11-19 18:17:59.347859706 +0100 CET m=+2.255430021 DECISION: release [ jenkins ] is not present in the current k8s context. Will install it in namespace [[ staging ]] DECISION: release [ artifactory ] is not present in the current k8s context. Will install it in namespace [[ staging ]] 2017/11/19 18:18:02 INFO: attempting: -- installing release [ jenkins ] in namespace [[ staging ]] 2017/11/19 18:18:05 INFO: attempting: -- installing release [ artifactory ] in namespace [[ staging ]] -``` +``` ``` $ helm list --namespace staging NAME REVISION UPDATED STATUS CHART NAMESPACE -artifactory 1 Sun Nov 19 18:18:06 2017 DEPLOYED artifactory-6.2.0 staging -jenkins 1 Sun Nov 19 18:18:03 2017 DEPLOYED jenkins-0.9.1 staging +artifactory 1 Sun Nov 19 18:18:06 2017 DEPLOYED artifactory-6.2.0 staging +jenkins 1 Sun Nov 19 18:18:03 2017 DEPLOYED jenkins-0.9.1 staging ``` -# delete releases +# delete releases You can then change your desire, for example to disable the Jenkins release that was created above by setting `enabled = false` : @@ -38,13 +38,13 @@ Then run Helmsman again and it will detect that you want to delete Jenkins: > Note: As of v1.4.0-rc, deleting the jenkins app entry in the desired state file WILL result in deleting the jenkins release. To prevent this, use the `--keep-untracked-releases` flag with your Helmsman command. ``` -$ helmsman --apply -f example.toml +$ helmsman --apply -f example.toml 2017/11/19 18:28:27 Parsed [[ example.toml ]] successfully and found [ 2 ] apps. 2017/11/19 18:28:29 WARN: I could not create namespace [staging ]. It already exists. I am skipping this. 2017/11/19 18:28:29 WARN: I could not create namespace [default ]. It already exists. I am skipping this. -2017/11/19 18:29:01 INFO: Executing the following plan ... +2017/11/19 18:29:01 INFO: Executing the following plan ... --------------- -Ok, I have generated a plan for you at: 2017-11-19 18:28:29.437061909 +0100 CET m=+1.987623555 +Ok, I have generated a plan for you at: 2017-11-19 18:28:29.437061909 +0100 CET m=+1.987623555 DECISION: release [ jenkins ] is desired to be deleted . Planing this for you! DECISION: release [ artifactory ] is desired to be upgraded. Planing this for you! 2017/11/19 18:29:01 INFO: attempting: -- deleting release [ jenkins ] @@ -54,7 +54,7 @@ DECISION: release [ artifactory ] is desired to be upgraded. Planing this for yo ``` $ helm list --namespace staging NAME REVISION UPDATED STATUS CHART NAMESPACE -artifactory 2 Sun Nov 19 18:29:11 2017 DEPLOYED artifactory-6.2.0 staging +artifactory 2 Sun Nov 19 18:29:11 2017 DEPLOYED artifactory-6.2.0 staging ``` If you would like the release to be deleted along with its history, you can use the `purge` flag in your desired state file as follows: @@ -66,18 +66,18 @@ If you would like the release to be deleted along with its history, you can use [apps] [apps.jenkins] - name = "jenkins" + name = "jenkins" description = "jenkins" - namespace = "staging" - enabled = false # this tells helmsman to delete it - chart = "stable/jenkins" - version = "0.9.1" - valuesFile = "" - purge = true # this means purge delete this release whenever it is required to be deleted - test = flase + namespace = "staging" + enabled = false # this tells helmsman to delete it + chart = "stable/jenkins" + version = "0.9.1" + valuesFile = "" + purge = true # this means purge delete this release whenever it is required to be deleted + test = false ... -``` +``` ```yaml ... @@ -91,24 +91,24 @@ apps: version: "0.9.1" valuesFile: "" purge: true # this means purge delete this release whenever it is required to be deleted - test: flase + test: false ... ``` -# rollback releases +# rollback releases -> Rollbacks in helm versions 2.8.2 and higher may not work due to a [bug](https://github.com/helm/helm/issues/3722). -Similarly, if you change `enabled` back to `true`, it will figure out that you would like to roll it back. +> Rollbacks in helm versions 2.8.2 and higher may not work due to a [bug](https://github.com/helm/helm/issues/3722). +Similarly, if you change `enabled` back to `true`, it will figure out that you would like to roll it back. ``` -$ helmsman --apply -f example.toml +$ helmsman --apply -f example.toml 2017/11/19 18:30:41 Parsed [[ example.toml ]] successfully and found [ 2 ] apps. 2017/11/19 18:30:42 WARN: I could not create namespace [staging ]. It already exists. I am skipping this. 2017/11/19 18:30:43 WARN: I could not create namespace [default ]. It already exists. I am skipping this. -2017/11/19 18:30:49 INFO: Executing the following plan ... +2017/11/19 18:30:49 INFO: Executing the following plan ... --------------- -Ok, I have generated a plan for you at: 2017-11-19 18:30:43.108693039 +0100 CET m=+1.978435517 +Ok, I have generated a plan for you at: 2017-11-19 18:30:43.108693039 +0100 CET m=+1.978435517 DECISION: release [ jenkins ] is currently deleted and is desired to be rolledback to namespace [[ staging ]] . No problem! DECISION: release [ artifactory ] is desired to be upgraded. Planing this for you! 2017/11/19 18:30:49 INFO: attempting: -- rolling back release [ jenkins ] diff --git a/docs/how_to/override_defined_namespaces.md b/docs/how_to/override_defined_namespaces.md index c85426d7..c3f3b918 100644 --- a/docs/how_to/override_defined_namespaces.md +++ b/docs/how_to/override_defined_namespaces.md @@ -4,21 +4,21 @@ version: v1.3.0-rc # Override defined namespaces from command line -If you use different release branches for your releasing/managing your applications in your k8s clusters, then you might want to use the same desired state but with different namespaces on each branch. Instead of duplicating the DSF in multiple branches and adjusting it, you can use the `--ns-override` command line flag when running helmsman. +If you use different release branches for your releasing/managing your applications in your k8s clusters, then you might want to use the same desired state but with different namespaces on each branch. Instead of duplicating the DSF in multiple branches and adjusting it, you can use the `--ns-override` command line flag when running helmsman. -This flag overrides all namespaces defined in your DSF with the single one you pass from command line. +This flag overrides all namespaces defined in your DSF with the single one you pass from command line. # Example dsf.toml -```toml +```toml [metadata] org = "example.com" description = "example Desired State File for demo purposes." [settings] -kubeContext = "minikube" +kubeContext = "minikube" [namespaces] [namespaces.staging] @@ -37,7 +37,7 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" name = "jenkins" # should be unique across all apps description = "jenkins" namespace = "production" # maps to the namespace as defined in environmetns above - enabled = true # change to false if you want to delete this app release [empty = flase] + enabled = true # change to false if you want to delete this app release [empty = false] chart = "stable/jenkins" # changing the chart name means delete and recreate this chart version = "0.14.3" # chart version valuesFile = "" # leaving it empty uses the default chart values @@ -46,7 +46,7 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" name = "artifactory" # should be unique across all apps description = "artifactory" namespace = "staging" # maps to the namespace as defined in environmetns above - enabled = true # change to false if you want to delete this app release [empty = flase] + enabled = true # change to false if you want to delete this app release [empty = false] chart = "stable/artifactory" # changing the chart name means delete and recreate this chart version = "7.0.6" # chart version valuesFile = "" # leaving it empty uses the default chart values @@ -103,7 +103,7 @@ helmsman -f dsf.toml --debug --ns-override testing This will override the `staging` and `production` namespaces defined in `dsf.toml` : ``` -2018/03/31 17:38:12 INFO: Plan generated at: Sat Mar 31 2018 17:37:57 +2018/03/31 17:38:12 INFO: Plan generated at: Sat Mar 31 2018 17:37:57 DECISION: release [ jenkins ] is not present in the current k8s context. Will install it in namespace [[ testing ]] -- priority: 0 DECISION: release [ artifactory ] is not present in the current k8s context. Will install it in namespace [[ testing ]] -- priority: 0 -``` \ No newline at end of file +``` diff --git a/example.toml b/example.toml index 45e4982c..1e1b9142 100644 --- a/example.toml +++ b/example.toml @@ -82,7 +82,7 @@ # artifactory will be deployed using the Tiller in the kube-system namespace [apps.artifactory] namespace = "production" # maps to the namespace as defined in namespaces above - enabled = true # change to false if you want to delete this app release [default = flase] + enabled = true # change to false if you want to delete this app release [default = false] chart = "stable/artifactory" # changing the chart name means delete and recreate this release version = "7.0.6" # chart version ### Optional values below diff --git a/example.yaml b/example.yaml index 8bfcb1b2..7f68858d 100644 --- a/example.yaml +++ b/example.yaml @@ -57,7 +57,7 @@ apps: # jenkins will be deployed using the Tiller in the staging namespace jenkins: namespace: "staging" # maps to the namespace as defined in namespaces above - enabled: true # change to false if you want to delete this app release empty: flase: + enabled: true # change to false if you want to delete this app release empty: false: chart: "stable/jenkins" # changing the chart name means delete and recreate this chart version: "0.14.3" # chart version ### Optional values below @@ -77,7 +77,7 @@ apps: # artifactory will be deployed using the Tiller in the kube-system namespace artifactory: namespace: "production" # maps to the namespace as defined in namespaces above - enabled: true # change to false if you want to delete this app release empty: flase: + enabled: true # change to false if you want to delete this app release empty: false: chart: "stable/artifactory" # changing the chart name means delete and recreate this chart version: "7.0.6" # chart version ### Optional values below diff --git a/helm_helpers.go b/helm_helpers.go index eafc7ae0..8a9f62e2 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -117,7 +117,7 @@ func getTillerReleases(tillerNS string) tillerReleases { return out } -// buildState builds the currentState map contianing information about all releases existing in a k8s cluster +// buildState builds the currentState map containing information about all releases existing in a k8s cluster func buildState() { log.Println("INFO: mapping the current helm state ...") currentState = make(map[string]releaseState) From f0a57f2512e02703aa88aee870fc5b7f86189af1 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 4 Sep 2018 17:57:54 +0100 Subject: [PATCH 0222/1127] fix: fatal: No names found, cannot describe anything. --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index a454ca28..d679ca4f 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ .DEFAULT_GOAL := help PKGS := $(shell go list ./... | grep -v /vendor/) -TAG = $(shell git describe --tags --abbrev=0 HEAD) -LAST = $(shell git describe --tags --abbrev=0 HEAD^) +TAG = $(shell git describe --always --tags --abbrev=0 HEAD) +LAST = $(shell git describe --always --tags --abbrev=0 HEAD^) BODY = "`git log ${LAST}..HEAD --oneline --decorate` `printf '\n\#\#\# [Build Info](${BUILD_URL})'`" DATE = $(shell date +'%d%m%y') @@ -33,7 +33,7 @@ dependencies: ## Ensure all the necessary dependencies .PHONY: dependencies build: dependencies ## Build the package - @go build -ldflags '-X main.version='${TAG}-${DATE}' -extldflags "-static"' + @go build -ldflags '-X main.version="${TAG}-${DATE}" -extldflags "-static"' generate: @go generate #${PKGS} From e7bf42a5298ffdb2ff6536cf17705e3c10eb38fa Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 4 Sep 2018 18:10:49 +0100 Subject: [PATCH 0223/1127] fixes make test (-race is supported only on linux/amd64, freebsd/amd64, darwin/amd64 and windows/amd64. no Alpine) --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index d679ca4f..20393aa9 100644 --- a/Makefile +++ b/Makefile @@ -44,7 +44,7 @@ check: .PHONY: check test: dependencies ## Run unit tests - @go test -v -cover -race -p=1 #${PKGS} + @go test -v -cover -p=1 #${PKGS} .PHONY: test cross: dependencies ## Create binaries for all OSs From e5b0b61a3679b6a689bc54f2c839a38af1ae4ef5 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 4 Sep 2018 19:36:53 +0100 Subject: [PATCH 0224/1127] convert Settings into struct --- decision_maker.go | 4 +- docs/desired_state_specification.md | 8 +- docs/how_to/use_the_priority_key.md | 4 +- example.toml | 2 +- example.yaml | 2 +- helm_helpers.go | 7 +- kube_helpers.go | 28 +++---- main.go | 2 +- plan.go | 8 +- release_test.go | 2 +- state.go | 47 +++++++---- state_test.go | 126 ++++++++++++++-------------- utils.go | 4 +- 13 files changed, 128 insertions(+), 116 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index d0a3fa54..9ff6b11f 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -7,7 +7,7 @@ import ( var outcome plan var releases string -var settings map[string]string +var settings config // makePlan creates a plan of the actions needed to make the desired state come true. func makePlan(s *state) *plan { @@ -152,7 +152,7 @@ func deleteRelease(r *release, rs releaseState) { } priority := r.Priority - if settings["reverseDelete"] == "yes" { + if settings.ReverseDelete == true { priority = priority * -1 } diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 24473d88..41e1fe6a 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -86,7 +86,7 @@ The following options can be skipped if your kubectl context is already created - serviceAccount: the name of the service account to use to initiate helm. This should have enough permissions to allow Helm to work and should exist already in the cluster. More details can be found in [helm's RBAC guide](https://github.com/kubernetes/helm/blob/master/docs/rbac.md) - storageBackend : by default Helm stores release information in configMaps, using secrets is for storage is recommended for security. Setting this flag to `secret` will deploy/upgrade Tiller with the `--storage=secret`. Other values will be skipped and configMaps will be used. - slackWebhook : a [Slack](slack.com) Webhook URL to receive Helmsman notifications. This can be passed directly or in an environment variable. -- reverseDelete : if set to `"yes"` it will reverse the priority order whilst deleting. +- reverseDelete : if set to `true` it will reverse the priority order whilst deleting. > If you use `storageBackend` with a Tiller that has been previously deployed with configMaps as storage backend, you need to migrate your release information from the configMap to the new secret on your own. Helm does not support this yet. @@ -102,12 +102,12 @@ kubeContext = "minikube" # serviceAccount = "my-service-account" # storageBackend = "secret" # slackWebhook = $MY_SLACK_WEBHOOK -# reverseDelete = "no" +# reverseDelete = false ``` ```yaml settings: - kubeContext = "minikube" + kubeContext: "minikube" #username: "admin" #password: "$K8S_PASSWORD" #clusterURI: "https://192.168.99.100:8443" @@ -115,7 +115,7 @@ settings: #serviceAccount: "my-service-account" #storageBackend: "secret" #slackWebhook: "$MY_SLACK_WEBHOOK" - #reverseDelete: "no" + #reverseDelete: false ``` ## Namespaces diff --git a/docs/how_to/use_the_priority_key.md b/docs/how_to/use_the_priority_key.md index d1332e13..a32d848e 100644 --- a/docs/how_to/use_the_priority_key.md +++ b/docs/how_to/use_the_priority_key.md @@ -8,7 +8,7 @@ The `priority` flag in Apps definition allows you to define the order at which a Priority is an optional flag and has a default value of 0 (zero). If set, it can only use a negative value. The lower the value, the higher the priority. -If you want your apps te de deleted in the reverse order as they where created, you can also use the optional `Settings` flag `reverseDelete`, to acheive this, set it to `"yes"` +If you want your apps te de deleted in the reverse order as they where created, you can also use the optional `Settings` flag `reverseDelete`, to acheive this, set it to `true` ## Example @@ -19,7 +19,7 @@ If you want your apps te de deleted in the reverse order as they where created, [settings] kubeContext = "minikube" - reverseDelete = "no" # Optional flat to reverse the priorities when deletting + reverseDelete = false # Optional flag to reverse the priorities when deletting [namespaces] [namespaces.staging] diff --git a/example.toml b/example.toml index 1e1b9142..a9648028 100644 --- a/example.toml +++ b/example.toml @@ -24,7 +24,7 @@ serviceAccount = "tiller" # k8s serviceaccount. If it does not exist, it will be created. storageBackend = "secret" # default is configMap # slackWebhook = "$slack" # or "your slack webhook url" -# reverseDelete = "no" # reverse the priorities on delete +# reverseDelete = false # reverse the priorities on delete # define your environments and their k8s namespaces diff --git a/example.yaml b/example.yaml index 7f68858d..360ed1a8 100644 --- a/example.yaml +++ b/example.yaml @@ -22,7 +22,7 @@ settings: #serviceAccount: "foo" # k8s serviceaccount must be already defined, validation error will be thrown otherwise storageBackend: "secret" # default is configMap #slackWebhook: "$slack" # or your slack webhook url - #reverseDelete: "no" # reverse the priorities on delete + #reverseDelete: false # reverse the priorities on delete # define your environments and their k8s namespaces namespaces: diff --git a/helm_helpers.go b/helm_helpers.go index 8a9f62e2..574d1c4e 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -335,7 +335,7 @@ func deployTiller(namespace string, serviceAccount string, defaultServiceAccount } storageBackend := "" - if v, ok := s.Settings["storageBackend"]; ok && v == "secret" { + if s.Settings.StorageBackend == "secret" { storageBackend = " --override 'spec.template.spec.containers[0].command'='{/tiller,--storage=secret}'" } cmd := command{ @@ -353,10 +353,7 @@ func deployTiller(namespace string, serviceAccount string, defaultServiceAccount // initHelm initializes helm on a k8s cluster and deploys Tiller in one or more namespaces func initHelm() (bool, string) { - defaultSA := "" - if value, ok := s.Settings["serviceAccount"]; ok { - defaultSA = value - } + defaultSA := s.Settings.ServiceAccount if v, ok := s.Namespaces["kube-system"]; ok { if v.InstallTiller { diff --git a/kube_helpers.go b/kube_helpers.go index 65accdb8..29c47124 100644 --- a/kube_helpers.go +++ b/kube_helpers.go @@ -90,11 +90,11 @@ func createNamespace(ns string) { // It returns true if successful, false otherwise func createContext() (bool, string) { - if s.Settings["password"] == "" || s.Settings["username"] == "" || s.Settings["clusterURI"] == "" { - return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ] " + + if s.Settings.Password == "" || s.Settings.Username == "" || s.Settings.ClusterURI == "" { + return false, "ERROR: failed to create context [ " + s.Settings.KubeContext + " ] " + "as you did not specify enough information in the Settings section of your desired state file." } else if s.Certificates == nil || s.Certificates["caCrt"] == "" || s.Certificates["caKey"] == "" { - return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ] " + + return false, "ERROR: failed to create context [ " + s.Settings.KubeContext + " ] " + "as you did not provide Certifications to use in your desired state file." } @@ -128,8 +128,8 @@ func createContext() (bool, string) { } // connecting to the cluster - setCredentialsCmd := "kubectl config set-credentials " + s.Settings["username"] + " --username=" + s.Settings["username"] + - " --password=" + s.Settings["password"] + " --client-key=" + caKey + setCredentialsCmd := "kubectl config set-credentials " + s.Settings.Username + " --username=" + s.Settings.Username + + " --password=" + s.Settings.Password + " --client-key=" + caKey if caClient != "" { setCredentialsCmd = setCredentialsCmd + " --client-certificate=" + caClient } @@ -140,32 +140,32 @@ func createContext() (bool, string) { } if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { - return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ]: " + err + return false, "ERROR: failed to create context [ " + s.Settings.KubeContext + " ]: " + err } cmd = command{ Cmd: "bash", - Args: []string{"-c", "kubectl config set-cluster " + s.Settings["kubeContext"] + " --server=" + s.Settings["clusterURI"] + + Args: []string{"-c", "kubectl config set-cluster " + s.Settings.KubeContext + " --server=" + s.Settings.ClusterURI + " --certificate-authority=" + caCrt}, Description: "creating kubectl context - setting cluster.", } if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { - return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ]: " + err + return false, "ERROR: failed to create context [ " + s.Settings.KubeContext + " ]: " + err } cmd = command{ Cmd: "bash", - Args: []string{"-c", "kubectl config set-context " + s.Settings["kubeContext"] + " --cluster=" + s.Settings["kubeContext"] + - " --user=" + s.Settings["username"]}, + Args: []string{"-c", "kubectl config set-context " + s.Settings.KubeContext + " --cluster=" + s.Settings.KubeContext + + " --user=" + s.Settings.Username}, Description: "creating kubectl context - setting context.", } if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { - return false, "ERROR: failed to create context [ " + s.Settings["kubeContext"] + " ]: " + err + return false, "ERROR: failed to create context [ " + s.Settings.KubeContext + " ]: " + err } - if setKubeContext(s.Settings["kubeContext"]) { + if setKubeContext(s.Settings.KubeContext) { return true, "" } @@ -272,7 +272,7 @@ func labelResource(r *release) { log.Println("INFO: applying Helmsman lables to [ " + r.Name + " ] in namespace [ " + r.Namespace + " ] ") storageBackend := "configmap" - if v, ok := s.Settings["storageBackend"]; ok && v == "secret" { + if s.Settings.StorageBackend == "secret" { storageBackend = "secret" } @@ -297,7 +297,7 @@ func getHelmsmanReleases() map[string]map[string]bool { releases := make(map[string]map[string]bool) storageBackend := "configmap" - if v, ok := s.Settings["storageBackend"]; ok && v == "secret" { + if s.Settings.StorageBackend == "secret" { storageBackend = "secret" } diff --git a/main.go b/main.go index f1cef000..44794a86 100644 --- a/main.go +++ b/main.go @@ -37,7 +37,7 @@ var relativeDir string func main() { // set the kubecontext to be used Or create it if it does not exist - if !setKubeContext(s.Settings["kubeContext"]) { + if !setKubeContext(s.Settings.KubeContext) { if r, msg := createContext(); !r { logError(msg) } diff --git a/plan.go b/plan.go index b3e77e22..59a922b5 100644 --- a/plan.go +++ b/plan.go @@ -72,8 +72,8 @@ func (p plan) execPlan() { if cmd.targetRelease != nil { labelResource(cmd.targetRelease) } - if _, err := url.ParseRequestURI(s.Settings["slackWebhook"]); err == nil { - notifySlack(cmd.Command.Description+" ... SUCCESS!", s.Settings["slackWebhook"], false, true) + if _, err := url.ParseRequestURI(s.Settings.SlackWebhook); err == nil { + notifySlack(cmd.Command.Description+" ... SUCCESS!", s.Settings.SlackWebhook, false, true) } } } @@ -98,13 +98,13 @@ func (p plan) printPlan() { // sendPlanToSlack sends the description of plan commands to slack if a webhook is provided. func (p plan) sendPlanToSlack() { - if _, err := url.ParseRequestURI(s.Settings["slackWebhook"]); err == nil { + if _, err := url.ParseRequestURI(s.Settings.SlackWebhook); err == nil { str := "" for _, c := range p.Commands { str = str + c.Command.Description + "\n" } - notifySlack(strings.TrimRight(str, "\n"), s.Settings["slackWebhook"], false, false) + notifySlack(strings.TrimRight(str, "\n"), s.Settings.SlackWebhook, false, false) } } diff --git a/release_test.go b/release_test.go index 6239ed57..14889160 100644 --- a/release_test.go +++ b/release_test.go @@ -9,7 +9,7 @@ func Test_validateRelease(t *testing.T) { st := state{ Metadata: make(map[string]string), Certificates: make(map[string]string), - Settings: make(map[string]string), + Settings: (config{}), Namespaces: map[string]namespace{"namespace": namespace{false, false, "", "", "", "", "", ""}}, HelmRepos: make(map[string]string), Apps: make(map[string]*release), diff --git a/state.go b/state.go index cc66e024..6a3bcf18 100644 --- a/state.go +++ b/state.go @@ -20,11 +20,23 @@ type namespace struct { ClientKey string `yaml:"clientKey"` } +// config type represents the settings fields +type config struct { + KubeContext string `yaml:"kubeContext"` + Username string `yaml:"username"` + Password string `yaml:"password"` + ClusterURI string `yaml:"clusterURI"` + ServiceAccount string `yaml:"serviceAccount"` + StorageBackend string `yaml:"storageBackend"` + SlackWebhook string `yaml:"slackWebhook"` + ReverseDelete bool `yaml:"reverseDelete"` +} + // state type represents the desired state of applications on a k8s cluster. type state struct { Metadata map[string]string `yaml:"metadata"` Certificates map[string]string `yaml:"certificates"` - Settings map[string]string `yaml:"settings"` + Settings config `yaml:"settings"` Namespaces map[string]namespace `yaml:"namespaces"` HelmRepos map[string]string `yaml:"helmRepos"` Apps map[string]*release `yaml:"apps"` @@ -35,43 +47,46 @@ type state struct { func (s state) validate() (bool, string) { // settings - if s.Settings == nil || len(s.Settings) == 0 { + if s.Settings == (config{}) { return false, "ERROR: settings validation failed -- no settings table provided in state file." - } else if value, ok := s.Settings["kubeContext"]; !ok || value == "" { + } else if s.Settings.KubeContext == "" { return false, "ERROR: settings validation failed -- you have not provided a " + "kubeContext to use. Can't work without it. Sorry!" - } else if value, ok = s.Settings["clusterURI"]; ok { + } else if s.Settings.ClusterURI != "" { - s.Settings["clusterURI"] = subsituteEnv(value) - if _, err := url.ParseRequestURI(s.Settings["clusterURI"]); err != nil { + s.Settings.ClusterURI = subsituteEnv(s.Settings.ClusterURI) + if _, err := url.ParseRequestURI(s.Settings.ClusterURI); err != nil { return false, "ERROR: settings validation failed -- clusterURI must have a valid URL set in an env variable or passed directly. Either the env var is missing/empty or the URL is invalid." } - if _, ok = s.Settings["username"]; !ok { + if s.Settings.Username == "" { return false, "ERROR: settings validation failed -- username must be provided if clusterURI is defined." } - if value, ok = s.Settings["password"]; ok { - s.Settings["password"] = subsituteEnv(value) + if s.Settings.Password != "" { + s.Settings.Password = subsituteEnv(s.Settings.Password) } else { return false, "ERROR: settings validation failed -- password must be provided if clusterURI is defined." } - if s.Settings["password"] == "" { + if s.Settings.Password == "" { return false, "ERROR: settings validation failed -- password should be set as an env variable. It is currently missing or empty. " } } // slack webhook validation (if provided) - if value, ok := s.Settings["slackWebhook"]; ok { - s.Settings["slackWebhook"] = subsituteEnv(value) - if _, err := url.ParseRequestURI(s.Settings["slackWebhook"]); err != nil { + if s.Settings.SlackWebhook != "" { + s.Settings.SlackWebhook = subsituteEnv(s.Settings.SlackWebhook) + if _, err := url.ParseRequestURI(s.Settings.SlackWebhook); err != nil { return false, "ERROR: settings validation failed -- slackWebhook must be a valid URL." } } // certificates if s.Certificates != nil && len(s.Certificates) != 0 { - _, ok1 := s.Settings["clusterURI"] + ok1 := false + if s.Settings.ClusterURI != "" { + ok1 = true + } _, ok2 := s.Certificates["caCrt"] _, ok3 := s.Certificates["caKey"] if ok1 && (!ok2 || !ok3) { @@ -90,7 +105,7 @@ func (s state) validate() (bool, string) { } } else { - if _, ok := s.Settings["clusterURI"]; ok { + if s.Settings.ClusterURI != "" { return false, "ERROR: certifications validation failed -- You want me to connect to your cluster for you " + "but have not given me the cert/key to do so. Please add [caCrt] and [caKey] under Certifications. You might also need to provide [clientCrt]." } @@ -206,7 +221,7 @@ func (s state) print() { printMap(s.Certificates) fmt.Println("\nSettings: ") fmt.Println("--------- ") - printMap(s.Settings) + fmt.Printf("%+v\n", s.Settings) fmt.Println("\nNamespaces: ") fmt.Println("------------- ") printNamespacesMap(s.Namespaces) diff --git a/state_test.go b/state_test.go index 4c7259a0..0eec6051 100644 --- a/state_test.go +++ b/state_test.go @@ -9,7 +9,7 @@ func Test_state_validate(t *testing.T) { type fields struct { Metadata map[string]string Certificates map[string]string - Settings map[string]string + Settings config Namespaces map[string]namespace HelmRepos map[string]string Apps map[string]*release @@ -27,11 +27,11 @@ func Test_state_validate(t *testing.T) { "caCrt": "s3://some-bucket/12345.crt", "caKey": "s3://some-bucket/12345.key", }, - Settings: map[string]string{ - "kubeContext": "minikube", - "username": "admin", - "password": "$K8S_PASSWORD", - "clusterURI": "https://192.168.99.100:8443", + Settings: config{ + KubeContext: "minikube", + Username: "admin", + Password: "$K8S_PASSWORD", + ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ "staging": namespace{false, false, "", "", "", "", "", ""}, @@ -51,7 +51,7 @@ func Test_state_validate(t *testing.T) { "caCrt": "s3://some-bucket/12345.crt", "caKey": "s3://some-bucket/12345.key", }, - Settings: nil, + Settings: config{}, Namespaces: map[string]namespace{ "staging": namespace{false, false, "", "", "", "", "", ""}, }, @@ -70,11 +70,11 @@ func Test_state_validate(t *testing.T) { "caCrt": "s3://some-bucket/12345.crt", "caKey": "s3://some-bucket/12345.key", }, - Settings: map[string]string{ - "kubeContext": "", - "username": "admin", - "password": "$K8S_PASSWORD", - "clusterURI": "https://192.168.99.100:8443", + Settings: config{ + KubeContext: "", + Username: "admin", + Password: "$K8S_PASSWORD", + ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ "staging": namespace{false, false, "", "", "", "", "", ""}, @@ -94,8 +94,8 @@ func Test_state_validate(t *testing.T) { "caCrt": "s3://some-bucket/12345.crt", "caKey": "s3://some-bucket/12345.key", }, - Settings: map[string]string{ - "kubeContext": "minikube", + Settings: config{ + KubeContext: "minikube", }, Namespaces: map[string]namespace{ "staging": namespace{false, false, "", "", "", "", "", ""}, @@ -115,11 +115,11 @@ func Test_state_validate(t *testing.T) { "caCrt": "s3://some-bucket/12345.crt", "caKey": "s3://some-bucket/12345.key", }, - Settings: map[string]string{ - "kubeContext": "minikube", - "username": "admin", - "password": "K8S_PASSWORD", - "clusterURI": "https://192.168.99.100:8443", + Settings: config{ + KubeContext: "minikube", + Username: "admin", + Password: "K8S_PASSWORD", + ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ "staging": namespace{false, false, "", "", "", "", "", ""}, @@ -139,11 +139,11 @@ func Test_state_validate(t *testing.T) { "caCrt": "s3://some-bucket/12345.crt", "caKey": "s3://some-bucket/12345.key", }, - Settings: map[string]string{ - "kubeContext": "minikube", - "username": "admin", - "password": "K8S_PASSWORD", - "clusterURI": "$URI", // unset env + Settings: config{ + KubeContext: "minikube", + Username: "admin", + Password: "K8S_PASSWORD", + ClusterURI: "$URI", // unset env }, Namespaces: map[string]namespace{ "staging": namespace{false, false, "", "", "", "", "", ""}, @@ -163,11 +163,11 @@ func Test_state_validate(t *testing.T) { "caCrt": "s3://some-bucket/12345.crt", "caKey": "s3://some-bucket/12345.key", }, - Settings: map[string]string{ - "kubeContext": "minikube", - "username": "admin", - "password": "K8S_PASSWORD", - "clusterURI": "$SET_URI", // set env var + Settings: config{ + KubeContext: "minikube", + Username: "admin", + Password: "K8S_PASSWORD", + ClusterURI: "$SET_URI", // set env var }, Namespaces: map[string]namespace{ "staging": namespace{false, false, "", "", "", "", "", ""}, @@ -187,11 +187,11 @@ func Test_state_validate(t *testing.T) { "caCrt": "s3://some-bucket/12345.crt", "caKey": "s3://some-bucket/12345.key", }, - Settings: map[string]string{ - "kubeContext": "minikube", - "username": "admin", - "password": "K8S_PASSWORD", - "clusterURI": "https//192.168.99.100:8443", // invalid url + Settings: config{ + KubeContext: "minikube", + Username: "admin", + Password: "K8S_PASSWORD", + ClusterURI: "https//192.168.99.100:8443", // invalid url }, Namespaces: map[string]namespace{ "staging": namespace{false, false, "", "", "", "", "", ""}, @@ -210,11 +210,11 @@ func Test_state_validate(t *testing.T) { Certificates: map[string]string{ "caCrt": "s3://some-bucket/12345.crt", }, - Settings: map[string]string{ - "kubeContext": "minikube", - "username": "admin", - "password": "$K8S_PASSWORD", - "clusterURI": "https://192.168.99.100:8443", + Settings: config{ + KubeContext: "minikube", + Username: "admin", + Password: "$K8S_PASSWORD", + ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ "staging": namespace{false, false, "", "", "", "", "", ""}, @@ -231,11 +231,11 @@ func Test_state_validate(t *testing.T) { fields: fields{ Metadata: make(map[string]string), Certificates: nil, - Settings: map[string]string{ - "kubeContext": "minikube", - "username": "admin", - "password": "$K8S_PASSWORD", - "clusterURI": "https://192.168.99.100:8443", + Settings: config{ + KubeContext: "minikube", + Username: "admin", + Password: "$K8S_PASSWORD", + ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ "staging": namespace{false, false, "", "", "", "", "", ""}, @@ -255,11 +255,11 @@ func Test_state_validate(t *testing.T) { "caCrt": "s3://some-bucket/12345.crt", "caKey": "http://someurl.com/", }, - Settings: map[string]string{ - "kubeContext": "minikube", - "username": "admin", - "password": "$K8S_PASSWORD", - "clusterURI": "https://192.168.99.100:8443", + Settings: config{ + KubeContext: "minikube", + Username: "admin", + Password: "$K8S_PASSWORD", + ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ "staging": namespace{false, false, "", "", "", "", "", ""}, @@ -276,8 +276,8 @@ func Test_state_validate(t *testing.T) { fields: fields{ Metadata: make(map[string]string), Certificates: nil, - Settings: map[string]string{ - "kubeContext": "minikube", + Settings: config{ + KubeContext: "minikube", }, Namespaces: map[string]namespace{ "staging": namespace{false, false, "", "", "", "", "", ""}, @@ -294,8 +294,8 @@ func Test_state_validate(t *testing.T) { fields: fields{ Metadata: make(map[string]string), Certificates: nil, - Settings: map[string]string{ - "kubeContext": "minikube", + Settings: config{ + KubeContext: "minikube", }, Namespaces: nil, HelmRepos: map[string]string{ @@ -310,8 +310,8 @@ func Test_state_validate(t *testing.T) { fields: fields{ Metadata: make(map[string]string), Certificates: nil, - Settings: map[string]string{ - "kubeContext": "minikube", + Settings: config{ + KubeContext: "minikube", }, Namespaces: map[string]namespace{}, HelmRepos: map[string]string{ @@ -326,8 +326,8 @@ func Test_state_validate(t *testing.T) { fields: fields{ Metadata: make(map[string]string), Certificates: nil, - Settings: map[string]string{ - "kubeContext": "minikube", + Settings: config{ + KubeContext: "minikube", }, Namespaces: map[string]namespace{ "staging": namespace{false, false, "", "", "", "", "", ""}, @@ -341,8 +341,8 @@ func Test_state_validate(t *testing.T) { fields: fields{ Metadata: make(map[string]string), Certificates: nil, - Settings: map[string]string{ - "kubeContext": "minikube", + Settings: config{ + KubeContext: "minikube", }, Namespaces: map[string]namespace{ "staging": namespace{false, false, "", "", "", "", "", ""}, @@ -356,8 +356,8 @@ func Test_state_validate(t *testing.T) { fields: fields{ Metadata: make(map[string]string), Certificates: nil, - Settings: map[string]string{ - "kubeContext": "minikube", + Settings: config{ + KubeContext: "minikube", }, Namespaces: map[string]namespace{ "staging": namespace{false, false, "", "", "", "", "", ""}, @@ -374,8 +374,8 @@ func Test_state_validate(t *testing.T) { fields: fields{ Metadata: make(map[string]string), Certificates: nil, - Settings: map[string]string{ - "kubeContext": "minikube", + Settings: config{ + KubeContext: "minikube", }, Namespaces: map[string]namespace{ "staging": namespace{false, false, "", "", "", "", "", ""}, diff --git a/utils.go b/utils.go index 1f65dabe..5bfced56 100644 --- a/utils.go +++ b/utils.go @@ -306,8 +306,8 @@ func notifySlack(content string, url string, failure bool, executing bool) bool // logError sends a notification on slack if a webhook URL is provided and logs the error before terminating. func logError(msg string) { - if _, err := url.ParseRequestURI(s.Settings["slackWebhook"]); err == nil { - notifySlack(msg, s.Settings["slackWebhook"], true, apply) + if _, err := url.ParseRequestURI(s.Settings.SlackWebhook); err == nil { + notifySlack(msg, s.Settings.SlackWebhook, true, apply) } log.Fatal(msg) } From c2c03bc8b92efc73144f9aeacca01106cd8805c0 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Sun, 9 Sep 2018 14:09:41 +0200 Subject: [PATCH 0225/1127] adding useTiller option #71 --- decision_maker.go | 2 +- helm_helpers.go | 6 +++--- main.go | 4 ++-- release.go | 10 +++++----- state.go | 20 +++++++++++++------- 5 files changed, 24 insertions(+), 18 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index 9ff6b11f..5a895bef 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -342,7 +342,7 @@ func getDesiredTillerNamespaceFlag(r *release) string { func getDesiredTillerNamespace(r *release) string { if r.TillerNamespace != "" { return r.TillerNamespace - } else if v, ok := s.Namespaces[r.Namespace]; ok && v.InstallTiller { + } else if ns, ok := s.Namespaces[r.Namespace]; ok && (ns.InstallTiller || ns.UseTiller) { return r.Namespace } diff --git a/helm_helpers.go b/helm_helpers.go index 574d1c4e..5abaa432 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -355,9 +355,9 @@ func initHelm() (bool, string) { defaultSA := s.Settings.ServiceAccount - if v, ok := s.Namespaces["kube-system"]; ok { - if v.InstallTiller { - if ok, err := deployTiller("kube-system", v.TillerServiceAccount, defaultSA); !ok { + if ns, ok := s.Namespaces["kube-system"]; ok { + if ns.InstallTiller { + if ok, err := deployTiller("kube-system", ns.TillerServiceAccount, defaultSA); !ok { return false, err } } diff --git a/main.go b/main.go index 44794a86..97d74f01 100644 --- a/main.go +++ b/main.go @@ -52,8 +52,8 @@ func main() { } // check if helm Tiller is ready - for k, v := range s.Namespaces { - if v.InstallTiller { + for k, ns := range s.Namespaces { + if ns.InstallTiller || ns.UseTiller { waitForTiller(k) } } diff --git a/release.go b/release.go index 16544193..0b9ccf6d 100644 --- a/release.go +++ b/release.go @@ -36,14 +36,14 @@ func validateRelease(appLabel string, r *release, names map[string]map[string]bo r.Name = appLabel } if r.TillerNamespace != "" { - if v, ok := s.Namespaces[r.TillerNamespace]; !ok { + if ns, ok := s.Namespaces[r.TillerNamespace]; !ok { return false, "tillerNamespace specified, but the namespace specified does not exist!" - } else if !v.InstallTiller { - return false, "tillerNamespace specified, but that namespace does not have installTiller set to true." + } else if !ns.InstallTiller && !ns.UseTiller { + return false, "tillerNamespace specified, but that namespace does not have neither installTiller nor useTiller set to true." } } else if getDesiredTillerNamespace(r) == "kube-system" { - if v, ok := s.Namespaces["kube-system"]; ok && !v.InstallTiller { - return false, "app is desired to be deployed using Tiller from [[ kube-system ]] but kube-system is not desired to have a Tiller. You can use another Tiller with the 'tillerNamespace' option or deploy Tiller in kube-system. " + if ns, ok := s.Namespaces["kube-system"]; ok && !ns.InstallTiller && !ns.UseTiller { + return false, "app is desired to be deployed using Tiller from [[ kube-system ]] but kube-system is not desired to have a Tiller installed nor use an existing Tiller. You can use another Tiller with the 'tillerNamespace' option or deploy Tiller in kube-system. " } } if names[r.Name][getDesiredTillerNamespace(r)] { diff --git a/state.go b/state.go index 6a3bcf18..6a348f6e 100644 --- a/state.go +++ b/state.go @@ -12,6 +12,7 @@ import ( type namespace struct { Protected bool `yaml:"protected"` InstallTiller bool `yaml:"installTiller"` + UseTiller bool `yaml:"useTiller"` TillerServiceAccount string `yaml:"tillerServiceAccount"` CaCert string `yaml:"caCert"` TillerCert string `yaml:"tillerCert"` @@ -118,19 +119,24 @@ func (s state) validate() (bool, string) { "to work with!" } - for k, v := range s.Namespaces { - if !v.InstallTiller { + for k, ns := range s.Namespaces { + if ns.InstallTiller && ns.UseTiller { + return false, "ERROR: namespaces validation failed -- installTiller and useTiller can't be used together for namespace [ " + k + " ]" + } + if !ns.InstallTiller { log.Println("INFO: namespace validation -- Tiller is NOT desired to be deployed in namespace [ " + k + " ].") + } else if !ns.UseTiller { + log.Println("INFO: namespace validation -- a pre-installed Tiller is desired to be used in namespace [ " + k + " ].") } else { if tillerTLSEnabled(k) { // validating the TLS certs and keys for Tiller // if they are valid, their values (if they are env vars) are substituted var ok1, ok2, ok3, ok4, ok5 bool - ok1, v.CaCert = isValidCert(v.CaCert) - ok2, v.ClientCert = isValidCert(v.ClientCert) - ok3, v.ClientKey = isValidCert(v.ClientKey) - ok4, v.TillerCert = isValidCert(v.TillerCert) - ok5, v.TillerKey = isValidCert(v.TillerKey) + ok1, ns.CaCert = isValidCert(ns.CaCert) + ok2, ns.ClientCert = isValidCert(ns.ClientCert) + ok3, ns.ClientKey = isValidCert(ns.ClientKey) + ok4, ns.TillerCert = isValidCert(ns.TillerCert) + ok5, ns.TillerKey = isValidCert(ns.TillerKey) if !ok1 || !ok2 || !ok3 || !ok4 || !ok5 { return false, "ERROR: namespaces validation failed -- some certs/keys are not valid for Tiller TLS in namespace [ " + k + " ]." } From b85b4cb24d106833df6a1db72e36aff9d2322d19 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Sun, 9 Sep 2018 14:19:57 +0200 Subject: [PATCH 0226/1127] fixing tests --- release_test.go | 2 +- state_test.go | 32 ++++++++++++++++---------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/release_test.go b/release_test.go index 14889160..ee761871 100644 --- a/release_test.go +++ b/release_test.go @@ -10,7 +10,7 @@ func Test_validateRelease(t *testing.T) { Metadata: make(map[string]string), Certificates: make(map[string]string), Settings: (config{}), - Namespaces: map[string]namespace{"namespace": namespace{false, false, "", "", "", "", "", ""}}, + Namespaces: map[string]namespace{"namespace": namespace{false, false, false, "", "", "", "", "", ""}}, HelmRepos: make(map[string]string), Apps: make(map[string]*release), } diff --git a/state_test.go b/state_test.go index 0eec6051..b90016bb 100644 --- a/state_test.go +++ b/state_test.go @@ -34,7 +34,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, "", "", "", "", "", ""}, + "staging": namespace{false, false, false, "", "", "", "", "", ""}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -53,7 +53,7 @@ func Test_state_validate(t *testing.T) { }, Settings: config{}, Namespaces: map[string]namespace{ - "staging": namespace{false, false, "", "", "", "", "", ""}, + "staging": namespace{false, false, false, "", "", "", "", "", ""}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -77,7 +77,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, "", "", "", "", "", ""}, + "staging": namespace{false, false, false, "", "", "", "", "", ""}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -98,7 +98,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, "", "", "", "", "", ""}, + "staging": namespace{false, false, false, "", "", "", "", "", ""}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -122,7 +122,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, "", "", "", "", "", ""}, + "staging": namespace{false, false, false, "", "", "", "", "", ""}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -146,7 +146,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "$URI", // unset env }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, "", "", "", "", "", ""}, + "staging": namespace{false, false, false, "", "", "", "", "", ""}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -170,7 +170,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "$SET_URI", // set env var }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, "", "", "", "", "", ""}, + "staging": namespace{false, false, false, "", "", "", "", "", ""}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -194,7 +194,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https//192.168.99.100:8443", // invalid url }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, "", "", "", "", "", ""}, + "staging": namespace{false, false, false, "", "", "", "", "", ""}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -217,7 +217,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, "", "", "", "", "", ""}, + "staging": namespace{false, false, false, "", "", "", "", "", ""}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -238,7 +238,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, "", "", "", "", "", ""}, + "staging": namespace{false, false, false, "", "", "", "", "", ""}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -262,7 +262,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, "", "", "", "", "", ""}, + "staging": namespace{false, false, false, "", "", "", "", "", ""}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -280,7 +280,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, "", "", "", "", "", ""}, + "staging": namespace{false, false, false, "", "", "", "", "", ""}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -330,7 +330,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, "", "", "", "", "", ""}, + "staging": namespace{false, false, false, "", "", "", "", "", ""}, }, HelmRepos: nil, Apps: make(map[string]*release), @@ -345,7 +345,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, "", "", "", "", "", ""}, + "staging": namespace{false, false, false, "", "", "", "", "", ""}, }, HelmRepos: map[string]string{}, Apps: make(map[string]*release), @@ -360,7 +360,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, "", "", "", "", "", ""}, + "staging": namespace{false, false, false, "", "", "", "", "", ""}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -378,7 +378,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, "", "", "", "", "", ""}, + "staging": namespace{false, false, false, "", "", "", "", "", ""}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", From 5bea4307680794d3936cdc5371b00d84efc47ab5 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Sun, 9 Sep 2018 14:36:24 +0200 Subject: [PATCH 0227/1127] fix useTiller validation --- state.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/state.go b/state.go index 6a348f6e..a10e419b 100644 --- a/state.go +++ b/state.go @@ -123,10 +123,10 @@ func (s state) validate() (bool, string) { if ns.InstallTiller && ns.UseTiller { return false, "ERROR: namespaces validation failed -- installTiller and useTiller can't be used together for namespace [ " + k + " ]" } - if !ns.InstallTiller { - log.Println("INFO: namespace validation -- Tiller is NOT desired to be deployed in namespace [ " + k + " ].") - } else if !ns.UseTiller { + if ns.UseTiller { log.Println("INFO: namespace validation -- a pre-installed Tiller is desired to be used in namespace [ " + k + " ].") + } else if !ns.InstallTiller { + log.Println("INFO: namespace validation -- Tiller is NOT desired to be deployed in namespace [ " + k + " ].") } else { if tillerTLSEnabled(k) { // validating the TLS certs and keys for Tiller From 8823ddbcd57c2273b3bc66999700c86715002e15 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sun, 9 Sep 2018 13:32:46 +0100 Subject: [PATCH 0228/1127] adding helm diff to the decision maker --- Makefile | 2 +- decision_maker.go | 32 +++++++++++++++++++++++++++++--- init.go | 26 ++++++++++++++++++++++++-- kube_helpers.go | 2 +- utils.go | 1 - 5 files changed, 55 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 20393aa9..f57fae90 100644 --- a/Makefile +++ b/Makefile @@ -48,7 +48,7 @@ test: dependencies ## Run unit tests .PHONY: test cross: dependencies ## Create binaries for all OSs - @env CGO_ENABLED=0 gox -os '!freebsd !netbsd' -arch '!arm' -output "dist/{{.Dir}}_{{.OS}}_{{.Arch}}" -ldflags '-X main.Version=${TAG}-${DATE}' + @env CGO_ENABLED=0 gox -os '!freebsd !netbsd' -arch '!arm' -output "dist/{{.Dir}}_{{.OS}}_{{.Arch}}" -ldflags '-X main.Version=${TAG}-${DATE}' .PHONY: cross release: dependencies ## Generate a new release diff --git a/decision_maker.go b/decision_maker.go index 5a895bef..ea45a11d 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "strconv" "strings" ) @@ -187,9 +188,14 @@ func inspectUpgradeScenario(r *release, rs releaseState) { r.Namespace+" ]]", r.Priority) } else { - upgradeRelease(r) - logDecision("DECISION: release [ "+r.Name+" ] is desired to be enabled and is currently enabled."+ - "I will upgrade it in case you changed your values.yaml!", r.Priority) + if diff := diffRelease(r); diff != "" { + upgradeRelease(r) + logDecision("DECISION: release [ "+r.Name+" ] is desired to be enabled and is currently enabled. "+ + "I will upgrade it!", r.Priority) + } else { + logDecision("DECISION: release [ "+r.Name+" ] is desired to be enabled and is currently enabled. "+ + "Nothing to do here!", r.Priority) + } } } else { reInstallRelease(r, rs) @@ -202,6 +208,26 @@ func inspectUpgradeScenario(r *release, rs releaseState) { } } +// diffRelease diffs an existing release with the specified values.yaml +func diffRelease(r *release) string { + exitCode := 0 + msg := "" + + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm diff upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + r.Version + " " + getSetValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r)}, + Description: "upgrading release [ " + r.Name + " ] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", + } + + if exitCode, msg = cmd.exec(debug, verbose); exitCode != 0 { + logError("Command returned with exit code: " + string(exitCode) + ". And error message: " + msg) + } else { + fmt.Println(msg) + } + + return msg +} + // upgradeRelease upgrades an existing release with the specified values.yaml func upgradeRelease(r *release) { cmd := command{ diff --git a/init.go b/init.go index a03f23ec..df911d62 100644 --- a/init.go +++ b/init.go @@ -67,12 +67,16 @@ func init() { logVersions() } + if !toolExists("kubectl") { + logError("ERROR: kubectl is not installed/configured correctly. Aborting!") + } + if !toolExists("helm") { logError("ERROR: helm is not installed/configured correctly. Aborting!") } - if !toolExists("kubectl") { - logError("ERROR: kubectl is not installed/configured correctly. Aborting!") + if !helmPluginExists("diff") { + logError("ERROR: helm diff plugin is not installed/configured correctly. Aborting!") } // read the TOML/YAML desired state file @@ -126,3 +130,21 @@ func toolExists(tool string) bool { return true } + +// helmPluginExists returns true if the plugin is present in the environment and false otherwise. +// It takes as input the plugin's name to check if it is recognizable or not. e.g. diff +func helmPluginExists(plugin string) bool { + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm plugin list"}, + Description: "validating that " + plugin + " is installed.", + } + + exitCode, result := cmd.exec(debug, false) + + if exitCode != 0 { + return false + } + + return strings.Contains(result, plugin) +} diff --git a/kube_helpers.go b/kube_helpers.go index 29c47124..4d4bd1ab 100644 --- a/kube_helpers.go +++ b/kube_helpers.go @@ -269,7 +269,7 @@ func createRole(namespace string) (bool, string) { // labelResource applies Helmsman specific labels to Helm's state resources (secrets/configmaps) func labelResource(r *release) { if r.Enabled { - log.Println("INFO: applying Helmsman lables to [ " + r.Name + " ] in namespace [ " + r.Namespace + " ] ") + log.Println("INFO: applying Helmsman labels to [ " + r.Name + " ] in namespace [ " + r.Namespace + " ] ") storageBackend := "configmap" if s.Settings.StorageBackend == "secret" { diff --git a/utils.go b/utils.go index 5bfced56..f9082ee9 100644 --- a/utils.go +++ b/utils.go @@ -282,7 +282,6 @@ func notifySlack(content string, url string, failure bool, executing bool) bool "color": "` + color + `" , "pretext": "` + pretext + `", "title": "` + content + `", - "footer": "Helmsman ` + appVersion + `", "ts": ` + strconv.FormatInt(t.Unix(), 10) + ` } From 1329f297c854673404cffc1f9e27df073a71deb6 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sun, 9 Sep 2018 14:01:44 +0100 Subject: [PATCH 0229/1127] add helm-diff to dockerfiles --- dockerfile/dockerfile | 1 + test_files/dockerfile | 2 ++ 2 files changed, 3 insertions(+) diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile index 4e4a07d4..fd6b2028 100644 --- a/dockerfile/dockerfile +++ b/dockerfile/dockerfile @@ -36,6 +36,7 @@ RUN apk --no-cache update \ && mkdir -p ~/.helm/plugins \ && helm plugin install https://github.com/hypnoglow/helm-s3.git \ && helm plugin install https://github.com/nouney/helm-gcs \ + && helm plugin install https://github.com/databus23/helm-diff \ && rm -rf /tmp/linux-amd64 COPY --from=kube /usr/local/bin/kubectl /bin/kubectl diff --git a/test_files/dockerfile b/test_files/dockerfile index 3befa16d..d227cd2c 100644 --- a/test_files/dockerfile +++ b/test_files/dockerfile @@ -15,6 +15,8 @@ RUN apk add --update --no-cache ca-certificates git \ && curl -L http://storage.googleapis.com/kubernetes-helm/helm-${HELM_VERSION}-linux-amd64.tar.gz | tar zxv -C /tmp \ && mv /tmp/linux-amd64/helm /usr/local/bin/helm \ && chmod +x /usr/local/bin/helm \ + && mkdir -p ~/.helm/plugins \ + && helm plugin install https://github.com/databus23/helm-diff \ && rm -rf /tmp/linux-amd64 RUN go get github.com/goreleaser/goreleaser From ce0c238b591eed2afef203f76f2e19a0415eac50 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Sun, 9 Sep 2018 15:43:01 +0200 Subject: [PATCH 0230/1127] updating dockerfile builder base image --- dockerfile/dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile index fd6b2028..982659f5 100644 --- a/dockerfile/dockerfile +++ b/dockerfile/dockerfile @@ -1,7 +1,7 @@ -FROM golang:1.9 as builder +FROM golang:1.10-alpine3.7 as builder WORKDIR /go/src/ +RUN apk --no-cache add make git RUN git clone https://github.com/Praqma/helmsman.git -RUN apk --no-cache add make # build a statically linked binary so that it works on stripped linux images such as alpine/busybox. RUN cd helmsman \ From 4ff15dacebd4533bb5440f8baddbbdab639d6bfd Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Sun, 9 Sep 2018 15:59:59 +0200 Subject: [PATCH 0231/1127] updating circleci config --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 616949db..df3b18d3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -23,6 +23,7 @@ jobs: name: Unit test helmsman command: | echo "running tests ..." + helm plugin install https://github.com/databus23/helm-diff make test release: docker: From c4f4b81e654908c3a0a2626e710a9e045d3f330a Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Mon, 10 Sep 2018 09:58:50 +0100 Subject: [PATCH 0232/1127] don't run as root --- test_files/dockerfile | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/test_files/dockerfile b/test_files/dockerfile index d227cd2c..f7f41e03 100644 --- a/test_files/dockerfile +++ b/test_files/dockerfile @@ -2,23 +2,26 @@ # It can be pulled from praqma/helmsman-test FROM golang:1.10-alpine3.7 as builder -ENV KUBE_LATEST_VERSION v1.8.2 +ENV KUBE_VERSION v1.8.2 ENV HELM_VERSION v2.7.0 -RUN apk add --update ca-certificates \ - && apk add --update -t deps curl \ - && curl -L https://storage.googleapis.com/kubernetes-release/release/${KUBE_LATEST_VERSION}/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl \ - && chmod +x /usr/local/bin/kubectl - RUN apk add --update --no-cache ca-certificates git \ && apk add --update -t deps curl tar gzip make bash \ + && curl -L https://storage.googleapis.com/kubernetes-release/release/${KUBE_VERSION}/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl \ + && chmod +x /usr/local/bin/kubectl \ && curl -L http://storage.googleapis.com/kubernetes-helm/helm-${HELM_VERSION}-linux-amd64.tar.gz | tar zxv -C /tmp \ && mv /tmp/linux-amd64/helm /usr/local/bin/helm \ - && chmod +x /usr/local/bin/helm \ - && mkdir -p ~/.helm/plugins \ - && helm plugin install https://github.com/databus23/helm-diff \ - && rm -rf /tmp/linux-amd64 - -RUN go get github.com/goreleaser/goreleaser + && rm -rf /tmp/linux-amd64 \ + && chmod +x /usr/local/bin/helm WORKDIR src/helmsman + +RUN adduser -D -g '' helmsman && \ + chown -R helmsman /go + +USER helmsman + +RUN mkdir -p ~/.helm/plugins \ + && helm plugin install https://github.com/databus23/helm-diff + +RUN go get github.com/goreleaser/goreleaser From 08319361568c5a8fbb288f6319fbe293c252d127 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Mon, 10 Sep 2018 12:49:43 +0200 Subject: [PATCH 0233/1127] updating circleci config --- .circleci/config.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index df3b18d3..616949db 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -23,7 +23,6 @@ jobs: name: Unit test helmsman command: | echo "running tests ..." - helm plugin install https://github.com/databus23/helm-diff make test release: docker: From 52169d134c861fe59d52cd1d8f019e62fa3213ab Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Sun, 16 Sep 2018 14:09:34 +0200 Subject: [PATCH 0234/1127] fixing tls certs not downloaded when useTiller is used #71 --- helm_helpers.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/helm_helpers.go b/helm_helpers.go index 5abaa432..37519952 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -325,12 +325,10 @@ func deployTiller(namespace string, serviceAccount string, defaultServiceAccount tls := "" if tillerTLSEnabled(namespace) { - tillerCert := downloadFile(s.Namespaces[namespace].TillerCert, namespace+"-tiller.cert") - tillerKey := downloadFile(s.Namespaces[namespace].TillerKey, namespace+"-tiller.key") - caCert := downloadFile(s.Namespaces[namespace].CaCert, namespace+"-ca.cert") - // client cert and key - downloadFile(s.Namespaces[namespace].ClientCert, namespace+"-client.cert") - downloadFile(s.Namespaces[namespace].ClientKey, namespace+"-client.key") + tillerCert := namespace + "-tiller.cert" + tillerKey := namespace + "-tiller.key" + caCert := namespace + "-ca.cert" + tls = " --tiller-tls --tiller-tls-cert " + tillerCert + " --tiller-tls-key " + tillerKey + " --tiller-tls-verify --tls-ca-cert " + caCert } @@ -373,6 +371,14 @@ func initHelm() (bool, string) { return false, err } } + if tillerTLSEnabled(k) { + downloadFile(s.Namespaces[k].TillerCert, k+"-tiller.cert") + downloadFile(s.Namespaces[k].TillerKey, k+"-tiller.key") + downloadFile(s.Namespaces[k].CaCert, k+"-ca.cert") + // client cert and key + downloadFile(s.Namespaces[k].ClientCert, k+"-client.cert") + downloadFile(s.Namespaces[k].ClientKey, k+"-client.key") + } } return true, "" From 2bb53e6b080803b89941070836f8d706dc9bd98d Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Sun, 16 Sep 2018 15:05:41 +0200 Subject: [PATCH 0235/1127] fixing helm diff installation and adding helm secrets in dockerfile --- dockerfile/dockerfile | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile index 982659f5..74203ad5 100644 --- a/dockerfile/dockerfile +++ b/dockerfile/dockerfile @@ -14,7 +14,7 @@ RUN cd helmsman \ && CGO_ENABLED=0 GOOS=linux go install -a -ldflags '-X main.version='$TAG' -extldflags "-static"' . FROM alpine:3.6 as kube -ENV KUBE_LATEST_VERSION="v1.10.0" +ENV KUBE_LATEST_VERSION="v1.11.3" RUN apk add --update ca-certificates RUN apk add --update -t deps curl @@ -33,11 +33,19 @@ RUN apk --no-cache update \ && curl -L http://storage.googleapis.com/kubernetes-helm/helm-${HELM_VERSION}-linux-amd64.tar.gz | tar zxv -C /tmp \ && mv /tmp/linux-amd64/helm /usr/local/bin/helm \ && chmod +x /usr/local/bin/helm \ - && mkdir -p ~/.helm/plugins \ - && helm plugin install https://github.com/hypnoglow/helm-s3.git \ + && rm -rf /tmp/linux-amd64 \ + && mkdir -p ~/.helm/plugins + +RUN adduser -D -g '' helmsman \ + && chown -R helmsman ~/.helm + +#USER helmsman + +RUN helm plugin install https://github.com/hypnoglow/helm-s3.git \ && helm plugin install https://github.com/nouney/helm-gcs \ && helm plugin install https://github.com/databus23/helm-diff \ - && rm -rf /tmp/linux-amd64 + && helm plugin install https://github.com/futuresimple/helm-secrets \ + && rm -r /tmp/helm-diff /tmp/helm-diff.tgz COPY --from=kube /usr/local/bin/kubectl /bin/kubectl COPY --from=builder /go/bin/helmsman /bin/helmsman From 9577aa63d548ff84d0ee6705e1e798f7fb9aa155 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Fri, 14 Sep 2018 19:00:08 +0100 Subject: [PATCH 0236/1127] adding support for the helm-secrets plugin --- decision_maker.go | 26 ++++++++++++++++++++++++-- helm_helpers.go | 17 +++++++++++++++++ main.go | 12 ++++++++++++ release.go | 14 ++++++++++++++ 4 files changed, 67 insertions(+), 2 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index ea45a11d..0ee71d85 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -292,13 +292,35 @@ func getTimeout(r *release) string { // getValuesFiles return partial install/upgrade release command to substitute the -f flag in Helm. func getValuesFiles(r *release) string { + var fileList []string + if r.ValuesFile != "" { - return " -f " + pwd + "/" + relativeDir + "/" + r.ValuesFile + fileList = append(fileList, pwd+"/"+relativeDir+"/"+r.ValuesFile) } else if len(r.ValuesFiles) > 0 { for i := 0; i < len(r.ValuesFiles); i++ { r.ValuesFiles[i] = pwd + "/" + relativeDir + "/" + r.ValuesFiles[i] } - return " -f " + strings.Join(r.ValuesFiles, " -f ") + fileList = append(fileList, r.ValuesFiles...) + } + + if r.SecretFile != "" { + if ok := decryptSecret(r.SecretFile); !ok { + logError("Failed to decrypt secret file" + r.SecretFile) + } + fileList = append(fileList, pwd+"/"+relativeDir+"/"+r.SecretFile+".dec") + } else if len(r.SecretFiles) > 0 { + for i := 0; i < len(r.SecretFiles); i++ { + r.SecretFiles[i] = pwd + "/" + relativeDir + "/" + r.SecretFiles[i] + if ok := decryptSecret(r.SecretFiles[i]); !ok { + logError("Failed to decrypt secret file" + r.SecretFiles[i]) + } + r.SecretFiles[i] = r.SecretFiles[i] + ".dec" + } + fileList = append(fileList, r.SecretFiles...) + } + + if len(fileList) > 0 { + return " -f " + strings.Join(fileList, " -f ") } return "" } diff --git a/helm_helpers.go b/helm_helpers.go index 37519952..50f9a478 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -439,3 +439,20 @@ func deleteUntrackedRelease(release string, tillerNamespace string) { outcome.addCommand(cmd, -800, nil) } + +// decrypt a helm secret file +func decryptSecret(name string) bool { + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm secrets dec " + name}, + Description: "Decrypting " + name, + } + + exitCode, _ := cmd.exec(debug, false) + + if exitCode != 0 { + return false + } + + return true +} diff --git a/main.go b/main.go index 97d74f01..adc75e25 100644 --- a/main.go +++ b/main.go @@ -100,6 +100,7 @@ func main() { // cleanup deletes the k8s certificates and keys files // It also deletes any Tiller TLS certs and keys +// and secret files func cleanup() { if _, err := os.Stat("ca.crt"); err == nil { deleteFile("ca.crt") @@ -130,4 +131,15 @@ func cleanup() { deleteFile(k + "-client.key") } } + + for _, app := range s.Apps { + if _, err := os.Stat(app.SecretFile + ".dec"); err == nil { + deleteFile(app.SecretFile + ".dec") + } + for _, secret := range app.SecretFiles { + if _, err := os.Stat(secret + ".dec"); err == nil { + deleteFile(secret + ".dec") + } + } + } } diff --git a/release.go b/release.go index 0b9ccf6d..8dfadb19 100644 --- a/release.go +++ b/release.go @@ -17,6 +17,8 @@ type release struct { Version string ValuesFile string `yaml:"valuesFile"` ValuesFiles []string `yaml:"valuesFiles"` + SecretFile string `yaml:"secretFile"` + SecretFiles []string `yaml:"secretFiles"` Purge bool Test bool Protected bool @@ -75,6 +77,18 @@ func validateRelease(appLabel string, r *release, names map[string]map[string]bo } } + if r.SecretFile != "" && (!isOfType(r.SecretFile, ".yaml") || err != nil) { + return false, "secretFile must be a valid relative (from your first dsf file) file path for a yaml file, Or can be left empty." + } else if r.SecretFile != "" && len(r.SecretFiles) > 0 { + return false, "secretFile and secretFiles should not be used together." + } else if len(r.SecretFiles) > 0 { + for _, filePath := range r.SecretFiles { + if _, pathErr := os.Stat(pwd + "/" + relativeDir + "/" + filePath); !isOfType(filePath, ".yaml") || pathErr != nil { + return false, "the value for valueFile '" + filePath + "' must be a valid relative (from your first dsf file) file path for a yaml file." + } + } + } + if r.Priority != 0 && r.Priority > 0 { return false, "priority can only be 0 or negative value, positive values are not allowed." } From e828831d217a18eb3d50ff4f273c304de932c36f Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Fri, 14 Sep 2018 14:19:15 +0100 Subject: [PATCH 0237/1127] adding godotenv to load environment variables from files --- init.go | 29 ++++++++++++++++++++++++++++- main.go | 2 ++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/init.go b/init.go index df911d62..a5f1a887 100644 --- a/init.go +++ b/init.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/imdario/mergo" + "github.com/joho/godotenv" ) const ( @@ -25,11 +26,13 @@ const ( func init() { //parsing command line flags flag.Var(&files, "f", "desired state file name(s), may be supplied more than once to merge state files") + flag.Var(&envFiles, "e", "file(s) to load environment variables from (default .env), may be supplied more than once") flag.BoolVar(&apply, "apply", false, "apply the plan directly") flag.BoolVar(&debug, "debug", false, "show the execution logs") flag.BoolVar(&help, "help", false, "show Helmsman help") flag.BoolVar(&v, "v", false, "show the version") flag.BoolVar(&verbose, "verbose", false, "show verbose execution logs") + flag.BoolVar(&noBanner, "no-banner", false, "don't show the banner") flag.StringVar(&nsOverride, "ns-override", "", "override defined namespaces with this one") flag.BoolVar(&skipValidation, "skip-validation", false, "skip desired state validation") flag.BoolVar(&applyLabels, "apply-labels", false, "apply Helmsman labels to Helm state for all defined apps.") @@ -37,7 +40,9 @@ func init() { flag.Parse() - fmt.Println(banner + "version: " + appVersion + "\n" + slogan) + if !noBanner { + fmt.Println(banner + "version: " + appVersion + "\n" + slogan) + } helmVersion = strings.TrimSpace(strings.SplitN(getHelmClientVersion(), ": ", 2)[1]) kubectlVersion = strings.TrimSpace(strings.SplitN(getKubectlClientVersion(), ": ", 2)[1]) @@ -79,6 +84,28 @@ func init() { logError("ERROR: helm diff plugin is not installed/configured correctly. Aborting!") } + // read the env file + if len(envFiles) == 0 { + if _, err := os.Stat(".env"); err == nil { + err = godotenv.Load() + if err != nil { + logError("Error loading .env file") + } + } + } + + for _, e := range envFiles { + err := godotenv.Load(e) + if err != nil { + logError("Error loading " + e + " env file") + } + } + + // print all env variables + // for _, pair := range os.Environ() { + // fmt.Println(pair) + // } + // read the TOML/YAML desired state file var fileState state for _, f := range files { diff --git a/main.go b/main.go index 97d74f01..54eda81b 100644 --- a/main.go +++ b/main.go @@ -20,10 +20,12 @@ func (i *stringArray) Set(value string) error { var s state var debug bool var files stringArray +var envFiles stringArray var apply bool var help bool var v bool var verbose bool +var noBanner bool var nsOverride string var checkCleanup bool var skipValidation bool From 93c33e6c22f6158fc62b0e0f98268601c2110e34 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Fri, 14 Sep 2018 14:32:26 +0100 Subject: [PATCH 0238/1127] updated the docs --- .../how_to/pass_secrets_from_env_variables.md | 49 +++++++++++++------ 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/docs/how_to/pass_secrets_from_env_variables.md b/docs/how_to/pass_secrets_from_env_variables.md index 757e0b9a..01083c9c 100644 --- a/docs/how_to/pass_secrets_from_env_variables.md +++ b/docs/how_to/pass_secrets_from_env_variables.md @@ -6,29 +6,29 @@ version: v1.3.0-rc Starting from v0.1.3, Helmsman allows you to pass secrets and other user input to helm charts from environment variables as follows: -```toml -... +```toml +# ... [apps] [apps.jira] - name = "jira" + name = "jira" description = "jira" - namespace = "staging" - enabled = true - chart = "myrepo/jira" + namespace = "staging" + enabled = true + chart = "myrepo/jira" version = "0.1.5" - valuesFile = "applications/jira-values.yaml" - purge = false - test = true + valuesFile = "applications/jira-values.yaml" + purge = false + test = true [apps.jira.set] # the format is [apps.<>.set] db_username= "$JIRA_DB_USERNAME" # pass any number of key/value pairs where the key is the input expected by the helm charts and the value is an env variable name starting with $ db_password= "$JIRA_DB_PASSWORD" -... + # ... -``` +``` ```yaml -... +# ... apps: jira: @@ -44,8 +44,29 @@ apps: set: db_username: "$JIRA_DB_USERNAME" # pass any number of key/value pairs where the key is the input expected by the helm charts and the value is an env variable name starting with $ db_password: "$JIRA_DB_PASSWORD" -... +# ... + +``` + +These input variables will be passed to the chart when it is deployed/upgraded using helm's `--set <>=<>` + +You can also keep these environment variables in files, by default `helmsman` will load variables from a `.env` file but you can also specify files by using the `-e` option: +```bash +helmsman -e myVars ``` -These input variables will be passed to the chart when it is deployed/upgraded using helm's `--set <>=<>` \ No newline at end of file +Below are some examples of valid env files + +```bash +# I am a comment and that is OK +SOME_VAR=someval +FOO=BAR # comments at line end are OK too +export BAR=BAZ +``` +Or you can do YAML(ish) style + +```yaml +FOO: bar +BAR: baz +``` From 50a806e9d8410b4275210d58e4605cfbb1ae6a09 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sun, 16 Sep 2018 19:01:31 +0100 Subject: [PATCH 0239/1127] updated the docs --- decision_maker.go | 6 ++++++ docs/desired_state_specification.md | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/decision_maker.go b/decision_maker.go index 0ee71d85..1fec439f 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -304,11 +304,17 @@ func getValuesFiles(r *release) string { } if r.SecretFile != "" { + if !helmPluginExists("secrets") { + logError("ERROR: helm secrets plugin is not installed/configured correctly. Aborting!") + } if ok := decryptSecret(r.SecretFile); !ok { logError("Failed to decrypt secret file" + r.SecretFile) } fileList = append(fileList, pwd+"/"+relativeDir+"/"+r.SecretFile+".dec") } else if len(r.SecretFiles) > 0 { + if !helmPluginExists("secrets") { + logError("ERROR: helm secrets plugin is not installed/configured correctly. Aborting!") + } for i := 0; i < len(r.SecretFiles); i++ { r.SecretFiles[i] = pwd + "/" + relativeDir + "/" + r.SecretFiles[i] if ok := decryptSecret(r.SecretFiles[i]); !ok { diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 41e1fe6a..3b0bbc57 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -247,6 +247,10 @@ Options: - valuesFile : a valid path to custom Helm values.yaml file. File extension must be `yaml`. Cannot be used with valuesFiles together. Leaving it empty uses the default chart values. - valuesFiles : array of valid paths to custom Helm values.yaml file. File extension must be `yaml`. Cannot be used with valuesFile together. Leaving it empty uses the default chart values. > The values file(s) path is relative from the location of the (first) desired state file you pass in your Helmsman command. +- secretsFile : a valid path to custom Helm secrets.yaml file. File extension must be `yaml`. Cannot be used with secretsFiles together. Leaving it empty uses the default chart secrets. +- secretsFiles : array of valid paths to custom Helm secrets.yaml file. File extension must be `yaml`. Cannot be used with secretsFile together. Leaving it empty uses the default chart secrets. +> The secrets file(s) path is relative from the location of the (first) desired state file you pass in your Helmsman command. +> To use the secrets files you must have the helm-secrets plugin - purge : defines whether to use the Helm purge flag when deleting the release. (true/false) - test : defines whether to run the chart tests whenever the release is installed. - protected : defines if the release should be protected against changes. Namespace-level protection has higher priority than this flag. Check the [protection guide](how_to/protect_namespaces_and_releases.md) for more details. From 73c8c32af73e95a1744d606f54ad3ad76d0815ec Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sun, 16 Sep 2018 20:37:31 +0100 Subject: [PATCH 0240/1127] consolidate env var substitution --- decision_maker.go | 2 +- .../how_to/pass_secrets_from_env_variables.md | 2 +- release.go | 2 ++ state.go | 18 ++++---------- utils.go | 24 ++++++------------- 5 files changed, 15 insertions(+), 33 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index ea45a11d..51c1880b 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -307,7 +307,7 @@ func getValuesFiles(r *release) string { func getSetValues(r *release) string { result := "" for k, v := range r.Set { - _, value := envVarExists(v) + value := substituteEnv(v) result = result + " --set " + k + "=\"" + strings.Replace(value, ",", "\\,", -1) + "\"" } return result diff --git a/docs/how_to/pass_secrets_from_env_variables.md b/docs/how_to/pass_secrets_from_env_variables.md index 01083c9c..e1d7456d 100644 --- a/docs/how_to/pass_secrets_from_env_variables.md +++ b/docs/how_to/pass_secrets_from_env_variables.md @@ -23,7 +23,7 @@ Starting from v0.1.3, Helmsman allows you to pass secrets and other user input t [apps.jira.set] # the format is [apps.<>.set] db_username= "$JIRA_DB_USERNAME" # pass any number of key/value pairs where the key is the input expected by the helm charts and the value is an env variable name starting with $ db_password= "$JIRA_DB_PASSWORD" - # ... +# ... ``` diff --git a/release.go b/release.go index 0b9ccf6d..ecad8c40 100644 --- a/release.go +++ b/release.go @@ -61,6 +61,8 @@ func validateRelease(appLabel string, r *release, names map[string]map[string]bo } if r.Version == "" { return false, "version can't be empty." + } else { + r.Version = substituteEnv(r.Version) } if r.ValuesFile != "" && (!isOfType(r.ValuesFile, ".yaml") || err != nil) { diff --git a/state.go b/state.go index a10e419b..f2afc60c 100644 --- a/state.go +++ b/state.go @@ -55,7 +55,7 @@ func (s state) validate() (bool, string) { "kubeContext to use. Can't work without it. Sorry!" } else if s.Settings.ClusterURI != "" { - s.Settings.ClusterURI = subsituteEnv(s.Settings.ClusterURI) + s.Settings.ClusterURI = substituteEnv(s.Settings.ClusterURI) if _, err := url.ParseRequestURI(s.Settings.ClusterURI); err != nil { return false, "ERROR: settings validation failed -- clusterURI must have a valid URL set in an env variable or passed directly. Either the env var is missing/empty or the URL is invalid." } @@ -64,7 +64,7 @@ func (s state) validate() (bool, string) { return false, "ERROR: settings validation failed -- username must be provided if clusterURI is defined." } if s.Settings.Password != "" { - s.Settings.Password = subsituteEnv(s.Settings.Password) + s.Settings.Password = substituteEnv(s.Settings.Password) } else { return false, "ERROR: settings validation failed -- password must be provided if clusterURI is defined." } @@ -76,7 +76,7 @@ func (s state) validate() (bool, string) { // slack webhook validation (if provided) if s.Settings.SlackWebhook != "" { - s.Settings.SlackWebhook = subsituteEnv(s.Settings.SlackWebhook) + s.Settings.SlackWebhook = substituteEnv(s.Settings.SlackWebhook) if _, err := url.ParseRequestURI(s.Settings.SlackWebhook); err != nil { return false, "ERROR: settings validation failed -- slackWebhook must be a valid URL." } @@ -184,19 +184,9 @@ func (s state) validate() (bool, string) { return true, "" } -// subsituteEnv checks if a string is an env variable (contains '$'), then it returns its value -// if the env variable is empty or unset, an empty string is returned -// if the string does not contain '$', it is returned as is. -func subsituteEnv(name string) string { - if strings.Contains(name, "$") { - return os.ExpandEnv(name) - } - return name -} - // isValidCert checks if a certificate/key path/URI is valid func isValidCert(value string) (bool, string) { - tmp := subsituteEnv(value) + tmp := substituteEnv(value) _, err1 := url.ParseRequestURI(tmp) _, err2 := os.Stat(tmp) if err2 != nil && (err1 != nil || (!strings.HasPrefix(tmp, "s3://") && !strings.HasPrefix(tmp, "gs://"))) { diff --git a/utils.go b/utils.go index f9082ee9..f387b977 100644 --- a/utils.go +++ b/utils.go @@ -171,24 +171,14 @@ func logVersions() { log.Println("VERBOSE: Helm client version: " + helmVersion) } -// envVarExists checks if an environment variable is set or not and returns it. -// empty string is returned for unset env vars -// it accepts env var with/without '$' at the beginning -// if an env var 'v' does not exist, 'v' is returned as the value -func envVarExists(v string) (bool, string) { - - if strings.HasPrefix(v, "$") { - v = strings.SplitAfter(v, "$")[1] +// substituteEnv checks if a string has an env variable (contains '$'), then it returns its value +// if the env variable is empty or unset, an empty string is returned +// if the string does not contain '$', it is returned as is. +func substituteEnv(name string) string { + if strings.Contains(name, "$") { + return os.ExpandEnv(name) } - - value, ok := os.LookupEnv(v) - - // return the value as is if no env var with that key is set. - if !ok { - return ok, v - } - - return ok, value + return name } // sliceContains checks if a string slice contains a given string From a09accec30c829304afd42aeb166a9efc81cd3a6 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Sun, 16 Sep 2018 22:25:29 +0200 Subject: [PATCH 0241/1127] adding dry-run option #60 #77 --- aws/aws.go | 7 ++++--- command.go | 4 ++-- decision_maker.go | 20 ++++++++++++++------ gcs/gcs.go | 13 +++++++------ helm_helpers.go | 4 ++-- init.go | 7 ++++++- main.go | 3 ++- plan.go | 16 ++++++++++++---- utils.go | 30 ++++++++++++++++-------------- 9 files changed, 65 insertions(+), 39 deletions(-) diff --git a/aws/aws.go b/aws/aws.go index d9ba165d..52614f77 100644 --- a/aws/aws.go +++ b/aws/aws.go @@ -8,6 +8,7 @@ import ( "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/s3" "github.com/aws/aws-sdk-go/service/s3/s3manager" + "github.com/logrusorgru/aurora" ) func checkCredentialsEnvVar() bool { @@ -38,14 +39,14 @@ func ReadFile(bucketName string, filename string, outFile string) { sess, err := session.NewSession() if err != nil { - log.Fatal("ERROR: Can't create AWS session: " + err.Error()) + log.Fatal(aurora.Bold(aurora.Red("ERROR: Can't create AWS session: " + err.Error()))) } // create S3 download manger downloader := s3manager.NewDownloader(sess) file, err := os.Create(outFile) if err != nil { - log.Fatal("ERROR: Failed to open file " + outFile + ": " + err.Error()) + log.Fatal(aurora.Bold(aurora.Red("ERROR: Failed to open file " + outFile + ": " + err.Error()))) } defer file.Close() @@ -56,7 +57,7 @@ func ReadFile(bucketName string, filename string, outFile string) { Key: aws.String(filename), }) if err != nil { - log.Fatal("ERROR: Failed to download file " + filename + " from S3: " + err.Error()) + log.Fatal(aurora.Bold(aurora.Red("ERROR: Failed to download file " + filename + " from S3: " + err.Error()))) } log.Println("INFO: Successfully downloaded " + filename + " from S3 as " + outFile) diff --git a/command.go b/command.go index a44e4dff..099246f1 100644 --- a/command.go +++ b/command.go @@ -44,7 +44,7 @@ func (c command) exec(debug bool, verbose bool) (int, string) { cmd.Stderr = &stderr if err := cmd.Start(); err != nil { - log.Fatalf("ERROR: cmd.Start: %v", err) + logError("ERROR: cmd.Start: " + err.Error()) } if err := cmd.Wait(); err != nil { @@ -53,7 +53,7 @@ func (c command) exec(debug bool, verbose bool) (int, string) { return status.ExitStatus(), stderr.String() } } else { - log.Fatalf("ERROR: cmd.Wait: %v", err) + logError("ERROR: cmd.Wait: " + err.Error()) } } diff --git a/decision_maker.go b/decision_maker.go index ea45a11d..ad846bc8 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -102,7 +102,7 @@ func installRelease(r *release) { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm install " + r.Chart + " -n " + r.Name + " --namespace " + r.Namespace + getValuesFiles(r) + " --version " + r.Version + getSetValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r)}, + Args: []string{"-c", "helm install " + r.Chart + " -n " + r.Name + " --namespace " + r.Namespace + getValuesFiles(r) + " --version " + r.Version + getSetValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r) + getDryRunFlags()}, Description: "installing release [ " + r.Name + " ] in namespace [[ " + r.Namespace + " ]] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } outcome.addCommand(cmd, r.Priority, r) @@ -123,7 +123,7 @@ func rollbackRelease(r *release, rs releaseState) { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm rollback " + r.Name + " " + getReleaseRevision(rs) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r)}, + Args: []string{"-c", "helm rollback " + r.Name + " " + getReleaseRevision(rs) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r) + getDryRunFlags()}, Description: "rolling back release [ " + r.Name + " ] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } outcome.addCommand(cmd, r.Priority, r) @@ -159,7 +159,7 @@ func deleteRelease(r *release, rs releaseState) { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm delete " + p + " " + r.Name + getCurrentTillerNamespaceFlag(rs) + getTLSFlags(r)}, + Args: []string{"-c", "helm delete " + p + " " + r.Name + getCurrentTillerNamespaceFlag(rs) + getTLSFlags(r) + getDryRunFlags()}, Description: "deleting release [ " + r.Name + " ] from namespace [[ " + r.Namespace + " ]] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } outcome.addCommand(cmd, priority, r) @@ -232,7 +232,7 @@ func diffRelease(r *release) string { func upgradeRelease(r *release) { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + r.Version + " --force " + getSetValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r)}, + Args: []string{"-c", "helm upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + r.Version + " --force " + getSetValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r) + getDryRunFlags()}, Description: "upgrading release [ " + r.Name + " ] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } @@ -245,14 +245,14 @@ func reInstallRelease(r *release, rs releaseState) { delCmd := command{ Cmd: "bash", - Args: []string{"-c", "helm delete --purge " + r.Name + getCurrentTillerNamespaceFlag(rs) + getTLSFlags(r)}, + Args: []string{"-c", "helm delete --purge " + r.Name + getCurrentTillerNamespaceFlag(rs) + getTLSFlags(r) + getDryRunFlags()}, Description: "deleting release [ " + r.Name + " ] from namespace [[ " + r.Namespace + " ]] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } outcome.addCommand(delCmd, r.Priority, r) installCmd := command{ Cmd: "bash", - Args: []string{"-c", "helm install " + r.Chart + " --version " + r.Version + " -n " + r.Name + " --namespace " + r.Namespace + getValuesFiles(r) + getSetValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r)}, + Args: []string{"-c", "helm install " + r.Chart + " --version " + r.Version + " -n " + r.Name + " --namespace " + r.Namespace + getValuesFiles(r) + getSetValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r) + getDryRunFlags()}, Description: "installing release [ " + r.Name + " ] in namespace [[ " + r.Namespace + " ]] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } outcome.addCommand(installCmd, r.Priority, r) @@ -407,3 +407,11 @@ func getTLSFlags(r *release) string { return tls } + +// getDryRunFlags returns dry-run flag +func getDryRunFlags() string { + if dryRun { + return " --dry-run --debug " + } + return "" +} diff --git a/gcs/gcs.go b/gcs/gcs.go index 2cb607e6..2917465a 100644 --- a/gcs/gcs.go +++ b/gcs/gcs.go @@ -8,6 +8,7 @@ import ( // Imports the Google Cloud Storage client package. "cloud.google.com/go/storage" + "github.com/logrusorgru/aurora" "golang.org/x/net/context" ) @@ -27,7 +28,7 @@ func Auth() bool { err := ioutil.WriteFile(credFile, d, 0644) if err != nil { - log.Fatal("ERROR: Cannot create credentials file: ", err) + log.Fatal(aurora.Bold(aurora.Red("ERROR: Cannot create credentials file: " + err.Error()))) } os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", credFile) @@ -39,13 +40,13 @@ func Auth() bool { // ReadFile reads a file from storage bucket and saves it in a desired location. func ReadFile(bucketName string, filename string, outFile string) { if !Auth() { - log.Fatal("Failed to find the GCLOUD_CREDENTIALS env var. Please make sure it is set in the environment.") + log.Fatal(aurora.Bold(aurora.Red("ERROR: Failed to find the GCLOUD_CREDENTIALS env var. Please make sure it is set in the environment."))) } ctx := context.Background() client, err := storage.NewClient(ctx) if err != nil { - log.Fatalf("Failed to configure Storage bucket: %v", err) + log.Fatal(aurora.Bold(aurora.Red("ERROR: Failed to configure Storage bucket: " + err.Error()))) } storageBucket := client.Bucket(bucketName) @@ -55,7 +56,7 @@ func ReadFile(bucketName string, filename string, outFile string) { // Read the object. r, err := obj.NewReader(ctx) if err != nil { - log.Fatalf("Failed to create object reader: %v", err) + log.Fatal(aurora.Bold(aurora.Red("ERROR: Failed to create object reader: " + err.Error()))) } defer r.Close() @@ -63,14 +64,14 @@ func ReadFile(bucketName string, filename string, outFile string) { var writers []io.Writer file, err := os.Create(outFile) if err != nil { - log.Fatalf("Failed to create an output file: %v", err) + log.Fatal(aurora.Bold(aurora.Red("ERROR: Failed to create an output file: " + err.Error()))) } writers = append(writers, file) defer file.Close() dest := io.MultiWriter(writers...) if _, err := io.Copy(dest, r); err != nil { - log.Fatalf("Failed to read object content: %v", err) + log.Fatal(aurora.Bold(aurora.Red("ERROR: Failed to read object content: " + err.Error()))) } log.Println("INFO: Successfully downloaded " + filename + " from GCS as " + outFile) } diff --git a/helm_helpers.go b/helm_helpers.go index 37519952..0cdb92e9 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -127,7 +127,7 @@ func buildState() { // skipping the header from helm output time, err := time.Parse("Mon Jan _2 15:04:05 2006", rel.Releases[i].Updated) if err != nil { - log.Fatal("ERROR: while converting release time: " + err.Error()) + logError("ERROR: while converting release time: " + err.Error()) } currentState[rel.Releases[i].Name+"-"+rel.Releases[i].TillerNamespace] = releaseState{ @@ -237,7 +237,7 @@ func waitForTiller(namespace string) { time.Sleep(5 * time.Second) exitCode, err = cmd.exec(debug, verbose) } else { - log.Fatal("ERROR: while waiting for helm Tiller to be ready in namespace [ " + namespace + " ] : " + err) + logError("ERROR: while waiting for helm Tiller to be ready in namespace [ " + namespace + " ] : " + err) } attempt = attempt + 1 } diff --git a/init.go b/init.go index df911d62..e4dcd9cf 100644 --- a/init.go +++ b/init.go @@ -27,6 +27,7 @@ func init() { flag.Var(&files, "f", "desired state file name(s), may be supplied more than once to merge state files") flag.BoolVar(&apply, "apply", false, "apply the plan directly") flag.BoolVar(&debug, "debug", false, "show the execution logs") + flag.BoolVar(&dryRun, "dry-run", false, "apply the dry-run option for helm commands.") flag.BoolVar(&help, "help", false, "show Helmsman help") flag.BoolVar(&v, "v", false, "show the version") flag.BoolVar(&verbose, "verbose", false, "show verbose execution logs") @@ -39,6 +40,10 @@ func init() { fmt.Println(banner + "version: " + appVersion + "\n" + slogan) + if dryRun && apply { + logError("ERROR: --apply and --dry-run can't be used together.") + } + helmVersion = strings.TrimSpace(strings.SplitN(getHelmClientVersion(), ": ", 2)[1]) kubectlVersion = strings.TrimSpace(strings.SplitN(getKubectlClientVersion(), ": ", 2)[1]) @@ -98,7 +103,7 @@ func init() { // validate the desired state content if len(files) > 0 { if result, msg := s.validate(); !result { // syntax validation - log.Fatal(msg) + logError(msg) } } } else { diff --git a/main.go b/main.go index 97d74f01..662a2db5 100644 --- a/main.go +++ b/main.go @@ -34,6 +34,7 @@ var helmVersion string var kubectlVersion string var pwd string var relativeDir string +var dryRun bool func main() { // set the kubecontext to be used Or create it if it does not exist @@ -87,7 +88,7 @@ func main() { p.printPlan() p.sendPlanToSlack() - if apply { + if apply || dryRun { p.execPlan() } diff --git a/plan.go b/plan.go index 59a922b5..3f2287f8 100644 --- a/plan.go +++ b/plan.go @@ -8,6 +8,8 @@ import ( "strconv" "strings" "time" + + "github.com/logrusorgru/aurora" ) // orderedDecision type representing a Decision and it's priority weight @@ -64,12 +66,18 @@ func (p *plan) addDecision(decision string, priority int) { // execPlan executes the commands (actions) which were added to the plan. func (p plan) execPlan() { p.sortPlan() - log.Println("INFO: Executing the plan ... ") + if len(p.Commands) > 0 { + log.Println("INFO: Executing the plan ... ") + } else { + log.Println("INFO: Nothing to execute ... ") + } + for _, cmd := range p.Commands { if exitCode, msg := cmd.Command.exec(debug, verbose); exitCode != 0 { logError("Command returned with exit code: " + string(exitCode) + ". And error message: " + msg) } else { - if cmd.targetRelease != nil { + log.Println(aurora.Cyan(msg)) + if cmd.targetRelease != nil && !dryRun { labelResource(cmd.targetRelease) } if _, err := url.ParseRequestURI(s.Settings.SlackWebhook); err == nil { @@ -90,9 +98,9 @@ func (p plan) printPlanCmds() { // printPlan prints the decisions made in a plan. func (p plan) printPlan() { fmt.Println("----------------------") - log.Printf("INFO: Plan generated at: %s \n", p.Created.Format("Mon Jan _2 2006 15:04:05")) + log.Println(aurora.Bold(aurora.Green("INFO: Plan generated at: " + p.Created.Format("Mon Jan _2 2006 15:04:05")))) for _, decision := range p.Decisions { - fmt.Println(decision.Description + " -- priority: " + strconv.Itoa(decision.Priority)) + fmt.Println(aurora.Green(decision.Description + " -- priority: " + strconv.Itoa(decision.Priority))) } } diff --git a/utils.go b/utils.go index f9082ee9..6bdd9add 100644 --- a/utils.go +++ b/utils.go @@ -19,6 +19,7 @@ import ( "github.com/BurntSushi/toml" "github.com/Praqma/helmsman/aws" "github.com/Praqma/helmsman/gcs" + "github.com/logrusorgru/aurora" ) // printMap prints to the console any map of string keys and values. @@ -56,16 +57,16 @@ func toTOML(file string, s *state) { ) if err := toml.NewEncoder(&buff).Encode(s); err != nil { - log.Fatal(err) + logError(err.Error()) os.Exit(1) } newFile, err = os.Create(file) if err != nil { - log.Fatal(err) + logError(err.Error()) } bytesWritten, err := newFile.Write(buff.Bytes()) if err != nil { - log.Fatal(err) + logError(err.Error()) } log.Printf("Wrote %d bytes.\n", bytesWritten) newFile.Close() @@ -94,16 +95,16 @@ func toYAML(file string, s *state) { ) if err := yaml.NewEncoder(&buff).Encode(s); err != nil { - log.Fatal(err) + logError(err.Error()) os.Exit(1) } newFile, err = os.Create(file) if err != nil { - log.Fatal(err) + logError(err.Error()) } bytesWritten, err := newFile.Write(buff.Bytes()) if err != nil { - log.Fatal(err) + logError(err.Error()) } log.Printf("Wrote %d bytes.\n", bytesWritten) newFile.Close() @@ -126,7 +127,7 @@ func toFile(file string, s *state) { } else if isOfType(file, ".yaml") { fromYAML(file, s) } else { - log.Fatal("State file does not have toml/yaml extension.") + logError("ERROR: State file does not have toml/yaml extension.") } } @@ -141,7 +142,7 @@ func isOfType(filename string, filetype string) bool { func readFile(filepath string) string { data, err := ioutil.ReadFile(filepath) if err != nil { - log.Fatal("ERROR: failed to read [ " + filepath + " ] file content: " + err.Error()) + logError("ERROR: failed to read [ " + filepath + " ] file content: " + err.Error()) } return string(data) } @@ -155,6 +156,7 @@ func printHelp() { fmt.Println("Options:") fmt.Println("-f desired state file name(s), may be supplied more than once to merge state files.") fmt.Println("--debug prints basic logs during execution.") + fmt.Println("--dry-run apply the dry-run option for helm commands.") fmt.Println("--apply generates and applies an action plan.") fmt.Println("--verbose prints more verbose logs during execution.") fmt.Println("--ns-override overrides defined namespaces with a provided one.") @@ -226,19 +228,19 @@ func downloadFile(path string, outfile string) string { func copyFile(source string, destination string) { from, err := os.Open(source) if err != nil { - log.Fatal("ERROR: while copying " + source + " to " + destination + " : " + err.Error()) + logError("ERROR: while copying " + source + " to " + destination + " : " + err.Error()) } defer from.Close() to, err := os.OpenFile(destination, os.O_RDWR|os.O_CREATE, 0666) if err != nil { - log.Fatal("ERROR: while copying " + source + " to " + destination + " : " + err.Error()) + logError("ERROR: while copying " + source + " to " + destination + " : " + err.Error()) } defer to.Close() _, err = io.Copy(to, from) if err != nil { - log.Fatal("ERROR: while copying " + source + " to " + destination + " : " + err.Error()) + logError("ERROR: while copying " + source + " to " + destination + " : " + err.Error()) } } @@ -246,7 +248,7 @@ func copyFile(source string, destination string) { func deleteFile(path string) { log.Println("INFO: cleaning up ... deleting " + path) if err := os.Remove(path); err != nil { - log.Fatal("ERROR: could not delete file: " + path) + logError("ERROR: could not delete file: " + path) } } @@ -293,7 +295,7 @@ func notifySlack(content string, url string, failure bool, executing bool) bool client := &http.Client{} resp, err := client.Do(req) if err != nil { - log.Fatal("ERROR: while sending notifications to slack" + err.Error()) + logError("ERROR: while sending notifications to slack" + err.Error()) } defer resp.Body.Close() @@ -308,7 +310,7 @@ func logError(msg string) { if _, err := url.ParseRequestURI(s.Settings.SlackWebhook); err == nil { notifySlack(msg, s.Settings.SlackWebhook, true, apply) } - log.Fatal(msg) + log.Fatal(aurora.Bold(aurora.Red(msg))) } // getBucketElements returns a map containing the bucket name and the file path inside the bucket From b8a2d2ddd08c74b0e340b16023ee5b11d5b077fd Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sun, 16 Sep 2018 21:50:07 +0100 Subject: [PATCH 0242/1127] cleanup the dockerfile --- dockerfile/dockerfile | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile index 74203ad5..08ad79a3 100644 --- a/dockerfile/dockerfile +++ b/dockerfile/dockerfile @@ -1,5 +1,7 @@ FROM golang:1.10-alpine3.7 as builder + WORKDIR /go/src/ + RUN apk --no-cache add make git RUN git clone https://github.com/Praqma/helmsman.git @@ -13,43 +15,36 @@ RUN cd helmsman \ && make dependencies \ && CGO_ENABLED=0 GOOS=linux go install -a -ldflags '-X main.version='$TAG' -extldflags "-static"' . -FROM alpine:3.6 as kube -ENV KUBE_LATEST_VERSION="v1.11.3" - -RUN apk add --update ca-certificates -RUN apk add --update -t deps curl -RUN curl -L https://storage.googleapis.com/kubernetes-release/release/${KUBE_LATEST_VERSION}/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl -RUN chmod +x /usr/local/bin/kubectl # The image to keep FROM alpine:3.7 RUN apk add --update --no-cache ca-certificates git ARG HELM_VERSION=v2.8.1 +ARG KUBE_VERSION="v1.11.3" RUN apk --no-cache update \ && rm -rf /var/cache/apk/* \ && apk add --update -t deps curl tar gzip make bash \ + && curl -L https://storage.googleapis.com/kubernetes-release/release/${KUBE_VERSION}/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl \ + && chmod +x /usr/local/bin/kubectl \ && curl -L http://storage.googleapis.com/kubernetes-helm/helm-${HELM_VERSION}-linux-amd64.tar.gz | tar zxv -C /tmp \ && mv /tmp/linux-amd64/helm /usr/local/bin/helm \ && chmod +x /usr/local/bin/helm \ - && rm -rf /tmp/linux-amd64 \ - && mkdir -p ~/.helm/plugins + && rm -rf /tmp/linux-amd64 -RUN adduser -D -g '' helmsman \ - && chown -R helmsman ~/.helm +COPY --from=builder /go/bin/helmsman /bin/helmsman +#RUN adduser -D -g '' helmsman #USER helmsman -RUN helm plugin install https://github.com/hypnoglow/helm-s3.git \ +RUN mkdir -p ~/.helm/plugins \ + && helm plugin install https://github.com/hypnoglow/helm-s3.git \ && helm plugin install https://github.com/nouney/helm-gcs \ && helm plugin install https://github.com/databus23/helm-diff \ && helm plugin install https://github.com/futuresimple/helm-secrets \ && rm -r /tmp/helm-diff /tmp/helm-diff.tgz -COPY --from=kube /usr/local/bin/kubectl /bin/kubectl -COPY --from=builder /go/bin/helmsman /bin/helmsman - WORKDIR /tmp # ENTRYPOINT ["/bin/helmsman"] From 05afad113edff2cb98d4b0f8965a981bf96b35d4 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Tue, 18 Sep 2018 11:22:36 +0200 Subject: [PATCH 0243/1127] bumping version and updating some docs --- README.md | 2 +- docs/how_to/define_namespaces.md | 20 ++++++++++++++++++- .../how_to/pass_secrets_from_env_variables.md | 2 +- main.go | 2 +- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 80d899ae..279659d7 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ --- -version: v1.5.0 +version: v1.6.0 --- ![helmsman-logo](docs/images/helmsman.png) diff --git a/docs/how_to/define_namespaces.md b/docs/how_to/define_namespaces.md index a8271e8b..3618dc14 100644 --- a/docs/how_to/define_namespaces.md +++ b/docs/how_to/define_namespaces.md @@ -1,5 +1,5 @@ --- -version: v1.5.0 +version: v1.6.0 --- # define namespaces @@ -29,6 +29,24 @@ namespaces: >For details on protecting a namespace, please check the [namespace/release protection guide](protect_namespaces_and_releases.md) +## Using your existing Tillers (available from v1.6.0) + +If you would like to use custom configuration when deploying your Tiller, you can do that before using Helmsman and then use the `useTiller` option in your namespace definition. + +This will allow Helmsman to use your existing Tiller as it is. Note that you can't set both `useTiller` and `installTiller` to true at the same time. + +```toml +[namespaces] +[namespaces.production] + useTiller = true +``` + +```yaml +namespaces: + production: + useTiller: true +``` + ## Deploying Tiller into namespaces As of `v1.2.0-rc`, you can instruct Helmsman to deploy Tiller into specific namespaces (with or without TLS). diff --git a/docs/how_to/pass_secrets_from_env_variables.md b/docs/how_to/pass_secrets_from_env_variables.md index e1d7456d..c2c361e5 100644 --- a/docs/how_to/pass_secrets_from_env_variables.md +++ b/docs/how_to/pass_secrets_from_env_variables.md @@ -1,5 +1,5 @@ --- -version: v1.3.0-rc +version: v1.6.0 --- # pass secrets from env. variables: diff --git a/main.go b/main.go index 40c30e39..6fddd128 100644 --- a/main.go +++ b/main.go @@ -31,7 +31,7 @@ var checkCleanup bool var skipValidation bool var applyLabels bool var keepUntrackedReleases bool -var appVersion = "v1.5.0" +var appVersion = "v1.6.0" var helmVersion string var kubectlVersion string var pwd string From 3dc06cd319e25af1b830badcc02b563851aaa878 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Tue, 18 Sep 2018 11:36:35 +0200 Subject: [PATCH 0244/1127] updating help ouptut with new options --- utils.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/utils.go b/utils.go index 9bd60bcc..82c058f6 100644 --- a/utils.go +++ b/utils.go @@ -155,6 +155,7 @@ func printHelp() { fmt.Println() fmt.Println("Options:") fmt.Println("-f desired state file name(s), may be supplied more than once to merge state files.") + fmt.Println("-e file(s) to load environment variables from (default .env), may be supplied more than once") fmt.Println("--debug prints basic logs during execution.") fmt.Println("--dry-run apply the dry-run option for helm commands.") fmt.Println("--apply generates and applies an action plan.") @@ -164,6 +165,7 @@ func printHelp() { fmt.Println("--apply-labels applies Helmsman labels to Helm state for all defined apps.") fmt.Println("--keep-untracked-releases keep releases that are managed by Helmsman and are no longer tracked in your desired state.") fmt.Println("--help prints Helmsman help.") + fmt.Println("--no-banner don't show the banner") fmt.Println("-v prints Helmsman version.") } From 50fe703af7ee958819325727ddd5345f7aec3d04 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Tue, 18 Sep 2018 11:36:54 +0200 Subject: [PATCH 0245/1127] updating release notes for v1.6.0 --- release-notes.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/release-notes.md b/release-notes.md index 8ff3061d..2abf54c0 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,12 +1,12 @@ -# v1.5.0 +# v1.6.0 > If you are already using an older version of Helmsman than v1.4.0-rc, please read the changes below carefully and follow the upgrade guide [here](docs/migrating_to_v1.4.0-rc.md) -- Adding `--keep-untracked-releases` option to prevent cleaning up untracked release that are managed by Helmsman. -- Restricting the untracked releases clean up (when enabled) to the ones only in the defined namespace in the desired state file. -- Support using multiple desired state files which are merged at runtime. Issue #62. Thanks to @norwoodj -- Support using the `json` output of newer versions of helm. Fixes #61. Thanks to @luisdavim -- Fix relative paths for values files. Issue #59 -- Adding `timeout` & `no-hooks` as additional release deployment options. Issue #55 +- Adding support for helm secrets plugin (thanks to @luisdavim). issue #54 +- Adding support for using helm diff to determine when upgrades are needed and show the diff. +- Allowing env vars to be loaded from files using Godotenv (thanks to @luisdavim) +- Adding `--dry-run` option in Helmsman to perform a helm dry-run operation. Issues #77 #60 +- Adding `useTiller` option in namespaces definitions to use existing Tillers. Issue #71 +- Other minor code improvements and color coded output. \ No newline at end of file From 3e78d9ad663b8f35e7ff287e6ae10f05252d0b2e Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Wed, 19 Sep 2018 12:38:23 +0200 Subject: [PATCH 0246/1127] [ci skip] updating version in install command --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 279659d7..1f3685c6 100644 --- a/README.md +++ b/README.md @@ -46,9 +46,9 @@ To show debugging details: Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. ``` # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v1.5.0/helmsman_1.5.0_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.6.0/helmsman_1.6.0_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v1.5.0/helmsman_1.5.0_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.6.0/helmsman_1.6.0_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` From 42b3a3939a82e9b5f38303037680a334966baccc Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Fri, 21 Sep 2018 09:25:48 +0100 Subject: [PATCH 0247/1127] adding a flag to disable the color output --- aws/aws.go | 6 +++--- decision_maker.go | 7 ++++++- gcs/gcs.go | 12 ++++++------ init.go | 13 +++++++++++++ main.go | 2 ++ plan.go | 8 +++----- utils.go | 5 +++-- 7 files changed, 36 insertions(+), 17 deletions(-) diff --git a/aws/aws.go b/aws/aws.go index 52614f77..935b2c68 100644 --- a/aws/aws.go +++ b/aws/aws.go @@ -39,14 +39,14 @@ func ReadFile(bucketName string, filename string, outFile string) { sess, err := session.NewSession() if err != nil { - log.Fatal(aurora.Bold(aurora.Red("ERROR: Can't create AWS session: " + err.Error()))) + log.Fatal(style.Bold(style.Red("ERROR: Can't create AWS session: " + err.Error()))) } // create S3 download manger downloader := s3manager.NewDownloader(sess) file, err := os.Create(outFile) if err != nil { - log.Fatal(aurora.Bold(aurora.Red("ERROR: Failed to open file " + outFile + ": " + err.Error()))) + log.Fatal(style.Bold(style.Red("ERROR: Failed to open file " + outFile + ": " + err.Error()))) } defer file.Close() @@ -57,7 +57,7 @@ func ReadFile(bucketName string, filename string, outFile string) { Key: aws.String(filename), }) if err != nil { - log.Fatal(aurora.Bold(aurora.Red("ERROR: Failed to download file " + filename + " from S3: " + err.Error()))) + log.Fatal(style.Bold(style.Red("ERROR: Failed to download file " + filename + " from S3: " + err.Error()))) } log.Println("INFO: Successfully downloaded " + filename + " from S3 as " + outFile) diff --git a/decision_maker.go b/decision_maker.go index 0ea9060d..0b7bd87b 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -212,13 +212,18 @@ func inspectUpgradeScenario(r *release, rs releaseState) { func diffRelease(r *release) string { exitCode := 0 msg := "" + colorFlag := "" + if noColors { + colorFlag = "--no-color " + } cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm diff upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + r.Version + " " + getSetValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r)}, + Args: []string{"-c", "helm diff " + colorFlag + "upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + r.Version + " " + getSetValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r)}, Description: "upgrading release [ " + r.Name + " ] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } + fmt.Println(cmd) if exitCode, msg = cmd.exec(debug, verbose); exitCode != 0 { logError("Command returned with exit code: " + string(exitCode) + ". And error message: " + msg) } else { diff --git a/gcs/gcs.go b/gcs/gcs.go index 2917465a..e5cf6707 100644 --- a/gcs/gcs.go +++ b/gcs/gcs.go @@ -28,7 +28,7 @@ func Auth() bool { err := ioutil.WriteFile(credFile, d, 0644) if err != nil { - log.Fatal(aurora.Bold(aurora.Red("ERROR: Cannot create credentials file: " + err.Error()))) + log.Fatal(style.Bold(style.Red("ERROR: Cannot create credentials file: " + err.Error()))) } os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", credFile) @@ -40,13 +40,13 @@ func Auth() bool { // ReadFile reads a file from storage bucket and saves it in a desired location. func ReadFile(bucketName string, filename string, outFile string) { if !Auth() { - log.Fatal(aurora.Bold(aurora.Red("ERROR: Failed to find the GCLOUD_CREDENTIALS env var. Please make sure it is set in the environment."))) + log.Fatal(style.Bold(style.Red("ERROR: Failed to find the GCLOUD_CREDENTIALS env var. Please make sure it is set in the environment."))) } ctx := context.Background() client, err := storage.NewClient(ctx) if err != nil { - log.Fatal(aurora.Bold(aurora.Red("ERROR: Failed to configure Storage bucket: " + err.Error()))) + log.Fatal(style.Bold(style.Red("ERROR: Failed to configure Storage bucket: " + err.Error()))) } storageBucket := client.Bucket(bucketName) @@ -56,7 +56,7 @@ func ReadFile(bucketName string, filename string, outFile string) { // Read the object. r, err := obj.NewReader(ctx) if err != nil { - log.Fatal(aurora.Bold(aurora.Red("ERROR: Failed to create object reader: " + err.Error()))) + log.Fatal(style.Bold(style.Red("ERROR: Failed to create object reader: " + err.Error()))) } defer r.Close() @@ -64,14 +64,14 @@ func ReadFile(bucketName string, filename string, outFile string) { var writers []io.Writer file, err := os.Create(outFile) if err != nil { - log.Fatal(aurora.Bold(aurora.Red("ERROR: Failed to create an output file: " + err.Error()))) + log.Fatal(style.Bold(style.Red("ERROR: Failed to create an output file: " + err.Error()))) } writers = append(writers, file) defer file.Close() dest := io.MultiWriter(writers...) if _, err := io.Copy(dest, r); err != nil { - log.Fatal(aurora.Bold(aurora.Red("ERROR: Failed to read object content: " + err.Error()))) + log.Fatal(style.Bold(style.Red("ERROR: Failed to read object content: " + err.Error()))) } log.Println("INFO: Successfully downloaded " + filename + " from GCS as " + outFile) } diff --git a/init.go b/init.go index d372d865..a1ec2830 100644 --- a/init.go +++ b/init.go @@ -9,8 +9,12 @@ import ( "github.com/imdario/mergo" "github.com/joho/godotenv" + "github.com/logrusorgru/aurora" ) +// colorizer +var style aurora.Aurora + const ( banner = " _ _ \n" + "| | | | \n" + @@ -34,6 +38,8 @@ func init() { flag.BoolVar(&v, "v", false, "show the version") flag.BoolVar(&verbose, "verbose", false, "show verbose execution logs") flag.BoolVar(&noBanner, "no-banner", false, "don't show the banner") + flag.BoolVar(&noColors, "no-color", false, "don't use colors") + flag.BoolVar(&noFancy, "no-fancy", false, "don't display the banner and don't use colors") flag.StringVar(&nsOverride, "ns-override", "", "override defined namespaces with this one") flag.BoolVar(&skipValidation, "skip-validation", false, "skip desired state validation") flag.BoolVar(&applyLabels, "apply-labels", false, "apply Helmsman labels to Helm state for all defined apps.") @@ -41,6 +47,13 @@ func init() { flag.Parse() + if noFancy { + noColors = true + noBanner = true + } + + style = aurora.NewAurora(!noColors) + if !noBanner { fmt.Println(banner + "version: " + appVersion + "\n" + slogan) } diff --git a/main.go b/main.go index 6fddd128..879bfc74 100644 --- a/main.go +++ b/main.go @@ -26,6 +26,8 @@ var help bool var v bool var verbose bool var noBanner bool +var noColors bool +var noFancy bool var nsOverride string var checkCleanup bool var skipValidation bool diff --git a/plan.go b/plan.go index 3f2287f8..e27d24e5 100644 --- a/plan.go +++ b/plan.go @@ -8,8 +8,6 @@ import ( "strconv" "strings" "time" - - "github.com/logrusorgru/aurora" ) // orderedDecision type representing a Decision and it's priority weight @@ -76,7 +74,7 @@ func (p plan) execPlan() { if exitCode, msg := cmd.Command.exec(debug, verbose); exitCode != 0 { logError("Command returned with exit code: " + string(exitCode) + ". And error message: " + msg) } else { - log.Println(aurora.Cyan(msg)) + log.Println(style.Cyan(msg)) if cmd.targetRelease != nil && !dryRun { labelResource(cmd.targetRelease) } @@ -98,9 +96,9 @@ func (p plan) printPlanCmds() { // printPlan prints the decisions made in a plan. func (p plan) printPlan() { fmt.Println("----------------------") - log.Println(aurora.Bold(aurora.Green("INFO: Plan generated at: " + p.Created.Format("Mon Jan _2 2006 15:04:05")))) + log.Println(style.Bold(style.Green("INFO: Plan generated at: " + p.Created.Format("Mon Jan _2 2006 15:04:05")))) for _, decision := range p.Decisions { - fmt.Println(aurora.Green(decision.Description + " -- priority: " + strconv.Itoa(decision.Priority))) + fmt.Println(style.Green(decision.Description + " -- priority: " + strconv.Itoa(decision.Priority))) } } diff --git a/utils.go b/utils.go index 82c058f6..9a0e2caa 100644 --- a/utils.go +++ b/utils.go @@ -19,7 +19,6 @@ import ( "github.com/BurntSushi/toml" "github.com/Praqma/helmsman/aws" "github.com/Praqma/helmsman/gcs" - "github.com/logrusorgru/aurora" ) // printMap prints to the console any map of string keys and values. @@ -166,6 +165,8 @@ func printHelp() { fmt.Println("--keep-untracked-releases keep releases that are managed by Helmsman and are no longer tracked in your desired state.") fmt.Println("--help prints Helmsman help.") fmt.Println("--no-banner don't show the banner") + fmt.Println("--no-color don't use colors") + fmt.Println("--no-fancy don't show the banner and don't use colors") fmt.Println("-v prints Helmsman version.") } @@ -302,7 +303,7 @@ func logError(msg string) { if _, err := url.ParseRequestURI(s.Settings.SlackWebhook); err == nil { notifySlack(msg, s.Settings.SlackWebhook, true, apply) } - log.Fatal(aurora.Bold(aurora.Red(msg))) + log.Fatal(style.Bold(style.Red(msg))) } // getBucketElements returns a map containing the bucket name and the file path inside the bucket From 604e975bfd6f91de498526d4bfab5d697a100970 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Fri, 21 Sep 2018 09:26:54 +0100 Subject: [PATCH 0248/1127] cleanup trailing spaces --- README.md | 12 +++---- docs/best_practice.md | 8 ++--- docs/how_to/define_namespaces.md | 26 +++++++------- docs/how_to/move_charts_across_namespaces.md | 32 ++++++++--------- docs/how_to/multiple_value_files.md | 6 ++-- docs/how_to/multitenant_clusters_guide.md | 16 ++++----- .../how_to/protect_namespaces_and_releases.md | 30 ++++++++-------- docs/how_to/run_helmsman_in_ci.md | 6 ++-- docs/how_to/run_helmsman_with_minikube.md | 34 +++++++++---------- .../send_slack_notifications_from_helmsman.md | 6 ++-- docs/how_to/test_charts.md | 16 ++++----- docs/migrating_to_v1.4.0-rc.md | 2 +- docs/why_helmsman.md | 14 ++++---- release-notes.md | 7 ++-- test_files/invalid_example.toml | 4 +-- test_files/invalid_example.yaml | 4 +-- 16 files changed, 111 insertions(+), 112 deletions(-) diff --git a/README.md b/README.md index 1f3685c6..59784980 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Alternatively YAML declaration is also acceptable [example yaml file](https://gi The desired state file (DSF) follows the [desired state specification](https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md). -Helmsman sees what you desire, validates that your desire makes sense (e.g. that the charts you desire are available in the repos you defined), compares it with the current state of Helm and figures out what to do to make your desire come true. +Helmsman sees what you desire, validates that your desire makes sense (e.g. that the charts you desire are available in the repos you defined), compares it with the current state of Helm and figures out what to do to make your desire come true. To plan without executing: ``` $ helmsman -f example.toml ``` @@ -28,15 +28,15 @@ To show debugging details: # Features -- **Built for CD**: Helmsman can be used as a docker image or a binary. +- **Built for CD**: Helmsman can be used as a docker image or a binary. - **Applications as code**: describe your desired applications and manage them from a single version-controlled declarative file. - **Suitable for Multitenant Clusters**: deploy Tiller in different namespaces with service accounts and TLS. -- **Easy to use**: deep knowledge of Helm CLI and Kubectl is NOT mandatory to use Helmsman. -- **Plan, View, apply**: you can run Helmsman to generate and view a plan with/without executing it. +- **Easy to use**: deep knowledge of Helm CLI and Kubectl is NOT mandatory to use Helmsman. +- **Plan, View, apply**: you can run Helmsman to generate and view a plan with/without executing it. - **Portable**: Helmsman can be used to manage charts deployments on any k8s cluster. - **Protect Namespaces/Releases**: you can define certain namespaces/releases to be protected against accidental human mistakes. - **Define the order of managing releases**: you can define the priorities at which releases are managed by helmsman (useful for dependencies). -- **Idempotency**: As long your desired state file does not change, you can execute Helmsman several times and get the same result. +- **Idempotency**: As long your desired state file does not change, you can execute Helmsman several times and get the same result. - **Continue from failures**: In the case of partial deployment due to a specific chart deployment failure, fix your helm chart and execute Helmsman again without needing to rollback the partial successes first. # Install @@ -78,7 +78,7 @@ Helmsman lets you: - [Override the defined namespaces to deploy all releases in a specific namespace](https://github.com/Praqma/helmsman/blob/master/docs/how_to/override_defined_namespaces.md) -## Usage +## Usage Helmsman can be used in three different settings: diff --git a/docs/best_practice.md b/docs/best_practice.md index de7314d8..940580fb 100644 --- a/docs/best_practice.md +++ b/docs/best_practice.md @@ -2,7 +2,7 @@ version: v1.0.0 --- -# Best Practice +# Best Practice When using Helmsman, we recommend the following best practices: @@ -10,11 +10,11 @@ When using Helmsman, we recommend the following best practices: - Use environment variables to pass K8S connection secrets (password, certificates paths on the local system or AWS/GCS bucket urls and the API URI). This keeps all sensitive information out of your version controlled source code. -- Define certain namespaces (e.g, production) as protected namespaces (supported in v1.0.0+) and deploy your production-ready releases there. +- Define certain namespaces (e.g, production) as protected namespaces (supported in v1.0.0+) and deploy your production-ready releases there. -- If you use multiple desired state files (DSF) with the same cluster, make sure your namespace protection definitions are identical across all DSFs. +- If you use multiple desired state files (DSF) with the same cluster, make sure your namespace protection definitions are identical across all DSFs. -- Don't maintain the same release in multiple DSFs. +- Don't maintain the same release in multiple DSFs. - While the decision on how many DSFs to use and what each can contain is up to you and depends on your case, we recommend coming up with your own rule for how to split them. diff --git a/docs/how_to/define_namespaces.md b/docs/how_to/define_namespaces.md index 3618dc14..b5ca87cb 100644 --- a/docs/how_to/define_namespaces.md +++ b/docs/how_to/define_namespaces.md @@ -31,11 +31,11 @@ namespaces: ## Using your existing Tillers (available from v1.6.0) -If you would like to use custom configuration when deploying your Tiller, you can do that before using Helmsman and then use the `useTiller` option in your namespace definition. +If you would like to use custom configuration when deploying your Tiller, you can do that before using Helmsman and then use the `useTiller` option in your namespace definition. This will allow Helmsman to use your existing Tiller as it is. Note that you can't set both `useTiller` and `installTiller` to true at the same time. -```toml +```toml [namespaces] [namespaces.production] useTiller = true @@ -47,7 +47,7 @@ namespaces: useTiller: true ``` -## Deploying Tiller into namespaces +## Deploying Tiller into namespaces As of `v1.2.0-rc`, you can instruct Helmsman to deploy Tiller into specific namespaces (with or without TLS). @@ -79,7 +79,7 @@ namespaces: clientKey: "s3://mybucket/mydir/helm.key.pem" ``` -### Preventing Tiller deployment in kube-system +### Preventing Tiller deployment in kube-system By default Tiller will be deployed into `kube-system` even if you don't define kube-system in the namespaces section. To prevent this, simply add `kube-system` into your namespaces section. Since `installTiller` for namespaces is by default false, Helmsman will not deploy Tiller in `kube-system`. @@ -102,19 +102,19 @@ You can then tell Helmsman to deploy specific releases in a specific namespace: [apps] [apps.jenkins] - name = "jenkins" + name = "jenkins" description = "jenkins" namespace = "production" # pointing to the namespace defined above - enabled = true - chart = "stable/jenkins" - version = "0.9.1" - valuesFile = "" - purge = false - test = true + enabled = true + chart = "stable/jenkins" + version = "0.9.1" + valuesFile = "" + purge = false + test = true ... -``` +``` ```yaml ... @@ -134,5 +134,5 @@ apps: ``` -In the above example, `Jenkins` will be deployed in the production namespace using the Tiller deployed in the production namespace. If the production namespace was not configured to have Tiller deployed there, Jenkins will be deployed using the Tiller in `kube-system`. +In the above example, `Jenkins` will be deployed in the production namespace using the Tiller deployed in the production namespace. If the production namespace was not configured to have Tiller deployed there, Jenkins will be deployed using the Tiller in `kube-system`. diff --git a/docs/how_to/move_charts_across_namespaces.md b/docs/how_to/move_charts_across_namespaces.md index ea3329c9..ba04d3e6 100644 --- a/docs/how_to/move_charts_across_namespaces.md +++ b/docs/how_to/move_charts_across_namespaces.md @@ -19,19 +19,19 @@ If you have a workflow for testing a release first in the `staging` namespace th [apps] [apps.jenkins] - name = "jenkins" + name = "jenkins" description = "jenkins" namespace = "staging" # this is where it is deployed - enabled = true - chart = "stable/jenkins" - version = "0.9.1" - valuesFile = "" - purge = false - test = true + enabled = true + chart = "stable/jenkins" + version = "0.9.1" + valuesFile = "" + purge = false + test = true ... -``` +``` ```yaml ... @@ -68,19 +68,19 @@ Then if you change the namespace key for jenkins: [apps] [apps.jenkins] - name = "jenkins" + name = "jenkins" description = "jenkins" namespace = "production" # we want to move it to production - enabled = true - chart = "stable/jenkins" - version = "0.9.1" - valuesFile = "" - purge = false - test = true + enabled = true + chart = "stable/jenkins" + version = "0.9.1" + valuesFile = "" + purge = false + test = true ... -``` +``` ```yaml ... diff --git a/docs/how_to/multiple_value_files.md b/docs/how_to/multiple_value_files.md index 689e8f4e..f866eee5 100644 --- a/docs/how_to/multiple_value_files.md +++ b/docs/how_to/multiple_value_files.md @@ -26,9 +26,9 @@ You can include multiple yaml value files to separate configuration for differen [apps.jenkins-test] name = "jenkins-test" # should be unique across all apps description = "test release of jenkins, testing xyz feature" - namespace = "staging" - enabled = true - chart = "stable/jenkins" + namespace = "staging" + enabled = true + chart = "stable/jenkins" version = "0.9.1" # chart version valuesFiles = [ "../my-jenkins-common-values.yaml", diff --git a/docs/how_to/multitenant_clusters_guide.md b/docs/how_to/multitenant_clusters_guide.md index f0b2aae5..ee78b670 100644 --- a/docs/how_to/multitenant_clusters_guide.md +++ b/docs/how_to/multitenant_clusters_guide.md @@ -4,7 +4,7 @@ version: v1.5.0 # Multitenant Clusters Guide -This guide helps you use Helmsman to secure your Helm deployment with service accounts and TLS. +This guide helps you use Helmsman to secure your Helm deployment with service accounts and TLS. >Checkout Helm's [security guide](https://github.com/kubernetes/helm/blob/master/docs/securing_installation.md) @@ -42,13 +42,13 @@ namespaces: ``` -By default Tiller will be deployed into `kube-system` even if you don't define kube-system in the namespaces section. To prevent deploying Tiller into `kube-system, you need to explicitly add `kube-system` in your defined namespaces. See the [namespaces guide](define_namespaces.md#preventing_tiller_deployment_in_kube-system) for an example. +By default Tiller will be deployed into `kube-system` even if you don't define kube-system in the namespaces section. To prevent deploying Tiller into `kube-system, you need to explicitly add `kube-system` in your defined namespaces. See the [namespaces guide](define_namespaces.md#preventing_tiller_deployment_in_kube-system) for an example. -## Deploying Tiller with a service account +## Deploying Tiller with a service account For K8S clusters with RBAC enabled, you will need to initialize Helm with a service account. Check [Helm's RBAC guide](https://github.com/kubernetes/helm/blob/master/docs/rbac.md). -Helmsman lets you deploy each of the Tillers with a different k8s service account Or with a default service account of your choice. +Helmsman lets you deploy each of the Tillers with a different k8s service account Or with a default service account of your choice. ```toml @@ -63,7 +63,7 @@ serviceAccount = "default-tiller-sa" [namespaces.production] installTiller = true - + [namespaces.developer1] installTiller = true tillerServiceAccount = "dev1-sa" @@ -104,8 +104,8 @@ If `tillerServiceAccount` is not defined, the following options are considered: 2. Helmsman creates the service account in that namespace and binds it to a role. If the namespace is kube-system, the service account is bound to `cluster-admin` clusterrole. Otherwise, a new role called `helmsman-tiller` is created in that namespace and only gives access to that namespace. -In the example above, namespaces `staging, developer1 & developer2` will have Tiller deployed with different service accounts. -The `production` namespace ,however, will be deployed using the `default-tiller-sa` service account defined in the `settings` section -assuming it exists in the production namespace-. If this one is not defined, Helmsman creates a new service account and binds it to a new role that only gives access to the `production` namespace. +In the example above, namespaces `staging, developer1 & developer2` will have Tiller deployed with different service accounts. +The `production` namespace ,however, will be deployed using the `default-tiller-sa` service account defined in the `settings` section -assuming it exists in the production namespace-. If this one is not defined, Helmsman creates a new service account and binds it to a new role that only gives access to the `production` namespace. ## Deploying Tiller with TLS enabled @@ -115,7 +115,7 @@ In a multitenant setting, it is also recommended to deploy Tiller with TLS enabl [namespaces] [namespaces.kube-system] - installTiller = false # has no effect. Tiller is always deployed in kube-system + installTiller = false # has no effect. Tiller is always deployed in kube-system caCert = "secrets/kube-system/ca.cert.pem" tillerCert = "secrets/kube-system/tiller.cert.pem" tillerKey = "$TILLER_KEY" # where TILLER_KEY=secrets/kube-system/tiller.key.pem diff --git a/docs/how_to/protect_namespaces_and_releases.md b/docs/how_to/protect_namespaces_and_releases.md index b6be3dd4..f9ef22fe 100644 --- a/docs/how_to/protect_namespaces_and_releases.md +++ b/docs/how_to/protect_namespaces_and_releases.md @@ -2,15 +2,15 @@ version: v1.3.0-rc --- -# Namespace and Release Protection +# Namespace and Release Protection -Since helmsman is used with version controlled code and is often configured to be triggered as part of a CI pipeline, accidental mistakes could happen by the user (e.g, disabling a production application and taking out of service as a result of a mistaken change in the desired state file). +Since helmsman is used with version controlled code and is often configured to be triggered as part of a CI pipeline, accidental mistakes could happen by the user (e.g, disabling a production application and taking out of service as a result of a mistaken change in the desired state file). -Helmsman provides the `plan` flag which helps you see the actions that it will take based on your desired state file before actually doing them. We recommend to use a `plan-hold-approve` pipeline when using helmsman with production clusters. +Helmsman provides the `plan` flag which helps you see the actions that it will take based on your desired state file before actually doing them. We recommend to use a `plan-hold-approve` pipeline when using helmsman with production clusters. -As of version v1.0.0, helmsman provides a fine-grained mechanism to protect releases/namespaces from accidental desired state file changes. +As of version v1.0.0, helmsman provides a fine-grained mechanism to protect releases/namespaces from accidental desired state file changes. -## Protection definition +## Protection definition - When a release (application) is protected, it CANNOT: - deleted @@ -20,14 +20,14 @@ As of version v1.0.0, helmsman provides a fine-grained mechanism to protect rele - A release CAN be moved into protection from a non-protected state. - If a protected release need to be updated/changed or even deleted, this is possible, but the protection has to be removed first (i.e. remove the namespace/release from the protected state). This explained further below. -> A release is an instance (installation) of an application which has been packaged as a helm chart. +> A release is an instance (installation) of an application which has been packaged as a helm chart. ## Protection mechanism Protection is supported in two forms: - **Namespace-level Protection**: is defined at the namespace level. A namespace can be declaratively defined to be protected in the desired state file as in the example below: -```toml +```toml [namespaces] [namespaces.staging] protected = false @@ -42,15 +42,15 @@ Protection is supported in two forms: [apps] [apps.jenkins] - name = "jenkins" + name = "jenkins" description = "jenkins" - namespace = "staging" - enabled = true - chart = "stable/jenkins" - version = "0.9.1" - valuesFile = "" - purge = false - test = false + namespace = "staging" + enabled = true + chart = "stable/jenkins" + version = "0.9.1" + valuesFile = "" + purge = false + test = false protected = true # defining this release to be protected. ``` diff --git a/docs/how_to/run_helmsman_in_ci.md b/docs/how_to/run_helmsman_in_ci.md index f1396bf1..8464191b 100644 --- a/docs/how_to/run_helmsman_in_ci.md +++ b/docs/how_to/run_helmsman_in_ci.md @@ -4,13 +4,13 @@ version: v1.3.0-rc # Run Helmsman in CI -You can run Helmsman as a job in your CI system using the [helmsman docker image](https://hub.docker.com/r/praqma/helmsman/). +You can run Helmsman as a job in your CI system using the [helmsman docker image](https://hub.docker.com/r/praqma/helmsman/). The following example is a `config.yml` file for CircleCI but can be replicated for other CI systems. ``` version: 2 jobs: - + deploy-apps: docker: - image: praqma/helmsman:v1.5.0 @@ -26,7 +26,7 @@ workflows: build: jobs: - deploy-apps -``` +``` > IMPORTANT: If your CI build logs are publicly readable, don't use the `--verbose` flag as logs any secrets being passed from env vars to the helm charts. diff --git a/docs/how_to/run_helmsman_with_minikube.md b/docs/how_to/run_helmsman_with_minikube.md index adf5741f..56bb3e0e 100644 --- a/docs/how_to/run_helmsman_with_minikube.md +++ b/docs/how_to/run_helmsman_with_minikube.md @@ -11,7 +11,7 @@ org = "orgX" maintainer = "k8s-admin" [settings] -kubeContext = "minikube" +kubeContext = "minikube" [namespaces] [namespaces.staging] @@ -22,27 +22,27 @@ stable = "https://kubernetes-charts.storage.googleapis.com" [apps] [apps.jenkins] - name = "jenkins" + name = "jenkins" description = "jenkins" - namespace = "staging" - enabled = true - chart = "stable/jenkins" - version = "0.9.1" - valuesFile = "" - purge = false - test = false + namespace = "staging" + enabled = true + chart = "stable/jenkins" + version = "0.9.1" + valuesFile = "" + purge = false + test = false [apps.artifactory] - name = "artifactory" + name = "artifactory" description = "artifactory" - namespace = "staging" - enabled = true - chart = "stable/artifactory" - version = "6.2.0" - valuesFile = "" - purge = false - test = false + namespace = "staging" + enabled = true + chart = "stable/artifactory" + version = "6.2.0" + valuesFile = "" + purge = false + test = false ``` ```yaml diff --git a/docs/how_to/send_slack_notifications_from_helmsman.md b/docs/how_to/send_slack_notifications_from_helmsman.md index cecff253..d3cc5eef 100644 --- a/docs/how_to/send_slack_notifications_from_helmsman.md +++ b/docs/how_to/send_slack_notifications_from_helmsman.md @@ -6,17 +6,17 @@ version: v1.5.0 Starting from v1.4.0-rc, Helmsman can send slack notifications to a channel of your choice. To enable the notifications, simply add a `slack webhook` in the `settings` section of your desired state file. The webhook URL can be passed directly or from an environment variable. -```toml +```toml [settings] ... -slackWebhook = $MY_SLACK_WEBHOOK +slackWebhook = $MY_SLACK_WEBHOOK ``` ```yaml settings: ... #slackWebhook : "$MY_SLACK_WEBHOOK" -``` +``` ## Getting a Slack Webhook URL diff --git a/docs/how_to/test_charts.md b/docs/how_to/test_charts.md index 03a4ac92..88dc8b57 100644 --- a/docs/how_to/test_charts.md +++ b/docs/how_to/test_charts.md @@ -11,15 +11,15 @@ You can specify that you would like a chart to be tested whenever it is installe [apps] [apps.jenkins] - name = "jenkins" + name = "jenkins" description = "jenkins" - namespace = "staging" - enabled = true - chart = "stable/jenkins" - version = "0.9.1" - valuesFile = "" - purge = false - test = true # setting this to true, means you want the charts tests to be run on this release when it is installed. + namespace = "staging" + enabled = true + chart = "stable/jenkins" + version = "0.9.1" + valuesFile = "" + purge = false + test = true # setting this to true, means you want the charts tests to be run on this release when it is installed. ... diff --git a/docs/migrating_to_v1.4.0-rc.md b/docs/migrating_to_v1.4.0-rc.md index 90ca9aa5..07c63ae0 100644 --- a/docs/migrating_to_v1.4.0-rc.md +++ b/docs/migrating_to_v1.4.0-rc.md @@ -4,5 +4,5 @@ This document highlights the main changes between Helmsman v1.4.0-rc and the old - Helmsman v1.4.0-rc tracks the releases it manages by applying specific labels to their Helm state (stored in Helm's configured backend storage). For smooth transition when upgrading to v1.4.0-rc, you should run `helmsman -f --apply-labels` once. This will label all releases from your desired state as with a `MANAGED-BY=Helmsman` label. The `--apply-labels`is safe to run multiple times. -- After each run, Helmsman v1.4.0-rc looks for, and deletes any releases with the `MANAGED-BY=Helmsman` label which are no longer existing in your desired state. This means that **deleting/commenting out an app from your desired state file will result in its deletion**. You can disable this cleanup by adding the flag `--keep-untracked-releases` to your Helmsman commands. +- After each run, Helmsman v1.4.0-rc looks for, and deletes any releases with the `MANAGED-BY=Helmsman` label which are no longer existing in your desired state. This means that **deleting/commenting out an app from your desired state file will result in its deletion**. You can disable this cleanup by adding the flag `--keep-untracked-releases` to your Helmsman commands. diff --git a/docs/why_helmsman.md b/docs/why_helmsman.md index 0f21d4df..b4141e28 100644 --- a/docs/why_helmsman.md +++ b/docs/why_helmsman.md @@ -4,33 +4,33 @@ version: v0.1.2 # Why Helmsman? -This document describes the reasoning and need behind the inception of Helmsman. +This document describes the reasoning and need behind the inception of Helmsman. ## Before Helm -Helmsman was created with continous deployment in mind. -When we started using k8s, we deployed applications on our cluster directly from k8s manifest files. Initially, we had a custom shell script added to our CI system to deploy the k8s resources on the cluster. That script could only create the k8s resources from the manifest files. Soon we needed to have a more flexible way to dynamically create/delete those resources. We structured our git repo and used custom file names (adding enabled or disabled into file names) and updated the shell script accordingly. It did not take long before we realized that this does not scale and is difficult to maintain. +Helmsman was created with continous deployment in mind. +When we started using k8s, we deployed applications on our cluster directly from k8s manifest files. Initially, we had a custom shell script added to our CI system to deploy the k8s resources on the cluster. That script could only create the k8s resources from the manifest files. Soon we needed to have a more flexible way to dynamically create/delete those resources. We structured our git repo and used custom file names (adding enabled or disabled into file names) and updated the shell script accordingly. It did not take long before we realized that this does not scale and is difficult to maintain. ![CI-pipeline-before-helm](images/CI-pipeline-before-helm.jpg) ## Helm to the rescue? -While looking for solutions for managing the growing number of k8s manifest files from a CI pipeline, we came to know about Helm and quickly releaized its potential. By creating Helm charts, we packaged related k8s manifests together into a single entity "a chart". This reduced the amount of files the CI script has to deal with. However, all the CI shell script could do is package a chart and install/upgrade it in our k8s cluster whenever a new commit is done into the chart's files in git. +While looking for solutions for managing the growing number of k8s manifest files from a CI pipeline, we came to know about Helm and quickly releaized its potential. By creating Helm charts, we packaged related k8s manifests together into a single entity "a chart". This reduced the amount of files the CI script has to deal with. However, all the CI shell script could do is package a chart and install/upgrade it in our k8s cluster whenever a new commit is done into the chart's files in git. ![CI-pipeline-after-helm](images/CI-pipeline-after-helm.jpg) But there were a couple of issues here: 1. Helm has more to it than package and install. Operations such as rollback, running chart tests etc. are only doable from the Helm's CLI client. -2. You have to keep updating your CI script everytime you add a chart to k8s. +2. You have to keep updating your CI script everytime you add a chart to k8s. 3. What if you want to do the same on another cluster? you will have to replicate your CI pipeline and possibly change your CI script accordingly. -We have also decided to split the Helm charts development from the git repositories where they are used. This is simply to let us develop the charts independently from the projects where we used them and to allow us to reuse them in different projects. +We have also decided to split the Helm charts development from the git repositories where they are used. This is simply to let us develop the charts independently from the projects where we used them and to allow us to reuse them in different projects. With all this in mind, we needed a flexible and dynamic solution that can let us deploy and manage Helm charts into multiple k8s cluster independently and with minimum human intervention. Such solution should be generic enough to be reusable for many different projects/cluster. And this is where Helmsman was born! ## The Helmsman way -In English, [Helmsman](https://www.merriam-webster.com/dictionary/helmsman) is the person at the helm (in a ship). In k8s and Helm context, Helmsman holds the Helm and maintains your Helm charts' lifecycle in your k8s cluster(s). Helmsman gets its directions to navigate from a [declarative file](desired_state_specification.md) maintained by the user (k8s admin). +In English, [Helmsman](https://www.merriam-webster.com/dictionary/helmsman) is the person at the helm (in a ship). In k8s and Helm context, Helmsman holds the Helm and maintains your Helm charts' lifecycle in your k8s cluster(s). Helmsman gets its directions to navigate from a [declarative file](desired_state_specification.md) maintained by the user (k8s admin). > Although knowledge about Helm and K8S is highly beneficial, such knowledge is NOT required to use Helmsman. diff --git a/release-notes.md b/release-notes.md index 2abf54c0..7508ef43 100644 --- a/release-notes.md +++ b/release-notes.md @@ -3,10 +3,9 @@ > If you are already using an older version of Helmsman than v1.4.0-rc, please read the changes below carefully and follow the upgrade guide [here](docs/migrating_to_v1.4.0-rc.md) - Adding support for helm secrets plugin (thanks to @luisdavim). issue #54 -- Adding support for using helm diff to determine when upgrades are needed and show the diff. +- Adding support for using helm diff to determine when upgrades are needed and show the diff. - Allowing env vars to be loaded from files using Godotenv (thanks to @luisdavim) -- Adding `--dry-run` option in Helmsman to perform a helm dry-run operation. Issues #77 #60 +- Adding `--dry-run` option in Helmsman to perform a helm dry-run operation. Issues #77 #60 - Adding `useTiller` option in namespaces definitions to use existing Tillers. Issue #71 -- Other minor code improvements and color coded output. +- Other minor code improvements and color coded output. - \ No newline at end of file diff --git a/test_files/invalid_example.toml b/test_files/invalid_example.toml index f2b71d1b..eb63cdb8 100644 --- a/test_files/invalid_example.toml +++ b/test_files/invalid_example.toml @@ -6,10 +6,10 @@ maintainer = "k8s-admin" [certificates] [settings] -kubeContext = +kubeContext = [namespaces] -staging = "staging" +staging = "staging" production = "default" [helmRepos] diff --git a/test_files/invalid_example.yaml b/test_files/invalid_example.yaml index b0d964de..410fc558 100644 --- a/test_files/invalid_example.yaml +++ b/test_files/invalid_example.yaml @@ -6,10 +6,10 @@ metadata: certificates: settings: - kubeContext: + kubeContext: namespaces: - staging: "staging" + staging: "staging" production: "default" helmRepos: From 1bce1b0afb367d38f97ad1b53cec080b58d2ec9b Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Fri, 21 Sep 2018 10:01:13 +0100 Subject: [PATCH 0249/1127] remove redundant printHelp function and help flags --- decision_maker.go | 1 - init.go | 18 +++++++++++------- main.go | 1 - utils.go | 24 ------------------------ 4 files changed, 11 insertions(+), 33 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index 0b7bd87b..7ff9d2d3 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -223,7 +223,6 @@ func diffRelease(r *release) string { Description: "upgrading release [ " + r.Name + " ] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } - fmt.Println(cmd) if exitCode, msg = cmd.exec(debug, verbose); exitCode != 0 { logError("Command returned with exit code: " + string(exitCode) + ". And error message: " + msg) } else { diff --git a/init.go b/init.go index a1ec2830..e3401db3 100644 --- a/init.go +++ b/init.go @@ -25,6 +25,15 @@ const ( slogan = "A Helm-Charts-as-Code tool.\n\n" ) +func printUsage() { + fmt.Println(banner + "\n") + fmt.Println("Helmsman version: " + appVersion) + fmt.Println("Helmsman is a Helm Charts as Code tool which allows you to automate the deployment/management of your Helm charts.") + fmt.Println() + fmt.Println("Usage: helmsman [options]") + flag.PrintDefaults() +} + // init is executed after all package vars are initialized [before the main() func in this case]. // It checks if Helm and Kubectl exist and configures: the connection to the k8s cluster, helm repos, namespaces, etc. func init() { @@ -34,7 +43,6 @@ func init() { flag.BoolVar(&apply, "apply", false, "apply the plan directly") flag.BoolVar(&debug, "debug", false, "show the execution logs") flag.BoolVar(&dryRun, "dry-run", false, "apply the dry-run option for helm commands.") - flag.BoolVar(&help, "help", false, "show Helmsman help") flag.BoolVar(&v, "v", false, "show the version") flag.BoolVar(&verbose, "verbose", false, "show verbose execution logs") flag.BoolVar(&noBanner, "no-banner", false, "don't show the banner") @@ -45,6 +53,7 @@ func init() { flag.BoolVar(&applyLabels, "apply-labels", false, "apply Helmsman labels to Helm state for all defined apps.") flag.BoolVar(&keepUntrackedReleases, "keep-untracked-releases", false, "keep releases that are managed by Helmsman and are no longer tracked in your desired state.") + flag.Usage = printUsage flag.Parse() if noFancy { @@ -55,7 +64,7 @@ func init() { style = aurora.NewAurora(!noColors) if !noBanner { - fmt.Println(banner + "version: " + appVersion + "\n" + slogan) + fmt.Println(banner + " version: " + appVersion + "\n" + slogan) } if dryRun && apply { @@ -81,11 +90,6 @@ func init() { os.Exit(0) } - if help { - printHelp() - os.Exit(0) - } - if verbose { logVersions() } diff --git a/main.go b/main.go index 879bfc74..635efb8c 100644 --- a/main.go +++ b/main.go @@ -22,7 +22,6 @@ var debug bool var files stringArray var envFiles stringArray var apply bool -var help bool var v bool var verbose bool var noBanner bool diff --git a/utils.go b/utils.go index 9a0e2caa..a644dfc4 100644 --- a/utils.go +++ b/utils.go @@ -146,30 +146,6 @@ func readFile(filepath string) string { return string(data) } -// printHelp prints Helmsman commands -func printHelp() { - fmt.Println("Helmsman version: " + appVersion) - fmt.Println("Helmsman is a Helm Charts as Code tool which allows you to automate the deployment/management of your Helm charts.") - fmt.Println("Usage: helmsman [options]") - fmt.Println() - fmt.Println("Options:") - fmt.Println("-f desired state file name(s), may be supplied more than once to merge state files.") - fmt.Println("-e file(s) to load environment variables from (default .env), may be supplied more than once") - fmt.Println("--debug prints basic logs during execution.") - fmt.Println("--dry-run apply the dry-run option for helm commands.") - fmt.Println("--apply generates and applies an action plan.") - fmt.Println("--verbose prints more verbose logs during execution.") - fmt.Println("--ns-override overrides defined namespaces with a provided one.") - fmt.Println("--skip-validation generates and applies an action plan.") - fmt.Println("--apply-labels applies Helmsman labels to Helm state for all defined apps.") - fmt.Println("--keep-untracked-releases keep releases that are managed by Helmsman and are no longer tracked in your desired state.") - fmt.Println("--help prints Helmsman help.") - fmt.Println("--no-banner don't show the banner") - fmt.Println("--no-color don't use colors") - fmt.Println("--no-fancy don't show the banner and don't use colors") - fmt.Println("-v prints Helmsman version.") -} - // logVersions prints the versions of kubectl and helm to the logs func logVersions() { log.Println("VERBOSE: kubectl client version: " + kubectlVersion) From 57f79ec1d5856a210e5abab87d8f3cd66a957dc5 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Thu, 20 Sep 2018 11:57:19 +0200 Subject: [PATCH 0250/1127] fixing not including releases from existing Tillers in helm state when using useTiller --- helm_helpers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helm_helpers.go b/helm_helpers.go index bfa105f7..7fe649ca 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -62,7 +62,7 @@ func getAllReleases() tillerReleases { result.Releases = append(result.Releases, getTillerReleases("kube-system").Releases...) } for ns, v := range s.Namespaces { - if v.InstallTiller { + if v.InstallTiller || v.UseTiller { result.Releases = append(result.Releases, getTillerReleases(ns).Releases...) } } From 23b4788b800220d6c4306e27e086b5fced4849b2 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Thu, 20 Sep 2018 11:58:23 +0200 Subject: [PATCH 0251/1127] fixing expected message from kubectl --- kube_helpers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kube_helpers.go b/kube_helpers.go index 4d4bd1ab..a0aaf2bf 100644 --- a/kube_helpers.go +++ b/kube_helpers.go @@ -314,7 +314,7 @@ func getHelmsmanReleases() map[string]map[string]bool { } lines := strings.Split(output, "\n") - if strings.ToUpper("No resources found") == strings.ToUpper(strings.TrimSpace(output)) { + if strings.ToUpper("No resources found.") == strings.ToUpper(strings.TrimSpace(output)) { return releases } for i := 0; i < len(lines); i++ { From 88d6c3f48bfa24b8e76ef9b0707fc5347cf180e7 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Tue, 25 Sep 2018 16:28:37 +0200 Subject: [PATCH 0252/1127] removing unnecessary else --- release.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/release.go b/release.go index ec916539..6e7c3ef6 100644 --- a/release.go +++ b/release.go @@ -63,9 +63,8 @@ func validateRelease(appLabel string, r *release, names map[string]map[string]bo } if r.Version == "" { return false, "version can't be empty." - } else { - r.Version = substituteEnv(r.Version) } + r.Version = substituteEnv(r.Version) if r.ValuesFile != "" && (!isOfType(r.ValuesFile, ".yaml") || err != nil) { return false, "valuesFile must be a valid relative (from your first dsf file) file path for a yaml file, Or can be left empty." From 87f5e16fea8e4d4852063532aa78dbae1d53b67b Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Tue, 25 Sep 2018 16:45:17 +0200 Subject: [PATCH 0253/1127] fixnig noColors --- aws/aws.go | 8 ++++++-- gcs/gcs.go | 6 +++++- utils.go | 4 ++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/aws/aws.go b/aws/aws.go index 935b2c68..ce2a9a93 100644 --- a/aws/aws.go +++ b/aws/aws.go @@ -11,6 +11,9 @@ import ( "github.com/logrusorgru/aurora" ) +// colorizer +var style aurora.Aurora + func checkCredentialsEnvVar() bool { if os.Getenv("AWS_ACCESS_KEY_ID") == "" || os.Getenv("AWS_SECRET_ACCESS_KEY") == "" { @@ -29,10 +32,11 @@ func checkCredentialsEnvVar() bool { } // ReadFile reads a file from S3 bucket and saves it in a desired location. -func ReadFile(bucketName string, filename string, outFile string) { +func ReadFile(bucketName string, filename string, outFile string, noColors bool) { + style = aurora.NewAurora(!noColors) // Checking env vars are set to configure AWS if !checkCredentialsEnvVar() { - log.Fatal("Failed to find the AWS env vars needed to configure AWS. Please make sure they are set in the environment.") + log.Fatal(style.Bold(style.Red("ERROR: Failed to find the AWS env vars needed to configure AWS. Please make sure they are set in the environment."))) } // Create Session -- use config (credentials + region) from env vars or aws profile diff --git a/gcs/gcs.go b/gcs/gcs.go index e5cf6707..d0badabf 100644 --- a/gcs/gcs.go +++ b/gcs/gcs.go @@ -12,6 +12,9 @@ import ( "golang.org/x/net/context" ) +// colorizer +var style aurora.Aurora + // Auth checks for GCLOUD_CREDENTIALS in the environment // returns true if they exist and creates a json credentials file and sets the GOOGLE_APPLICATION_CREDENTIALS env var // returns false if credentials are not found @@ -38,7 +41,8 @@ func Auth() bool { } // ReadFile reads a file from storage bucket and saves it in a desired location. -func ReadFile(bucketName string, filename string, outFile string) { +func ReadFile(bucketName string, filename string, outFile string, noColors bool) { + style = aurora.NewAurora(!noColors) if !Auth() { log.Fatal(style.Bold(style.Red("ERROR: Failed to find the GCLOUD_CREDENTIALS env var. Please make sure it is set in the environment."))) } diff --git a/utils.go b/utils.go index a644dfc4..42af5dcb 100644 --- a/utils.go +++ b/utils.go @@ -178,12 +178,12 @@ func downloadFile(path string, outfile string) string { if strings.HasPrefix(path, "s3") { tmp := getBucketElements(path) - aws.ReadFile(tmp["bucketName"], tmp["filePath"], outfile) + aws.ReadFile(tmp["bucketName"], tmp["filePath"], outfile, noColors) } else if strings.HasPrefix(path, "gs") { tmp := getBucketElements(path) - gcs.ReadFile(tmp["bucketName"], tmp["filePath"], outfile) + gcs.ReadFile(tmp["bucketName"], tmp["filePath"], outfile, noColors) } else { From a4b7e3695fcfe3b134fd5ed783bb109a80cb35a6 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Tue, 25 Sep 2018 18:50:33 +0200 Subject: [PATCH 0254/1127] limiting untracked releases cleanup for defined namespaces only. fixes #83 --- helm_helpers.go | 24 +++++++++++------------ kube_helpers.go | 52 ++++++++++++++++++++++++++++++------------------- 2 files changed, 43 insertions(+), 33 deletions(-) diff --git a/helm_helpers.go b/helm_helpers.go index 7fe649ca..c9301ea9 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -384,7 +384,7 @@ func initHelm() (bool, string) { return true, "" } -// cleanUntrackedReleases checks for any releases that are managed by Helmsman and is no longer tracked by the desired state +// cleanUntrackedReleases checks for any releases that are managed by Helmsman and are no longer tracked by the desired state // It compares the currently deployed releases with "MANAGED-BY=HELMSMAN" labels with Apps defined in the desired state // For all untracked releases found, a decision is made to delete them and is added to the Helmsman plan // NOTE: Untracked releases don't benefit from either namespace or application protection. @@ -393,20 +393,18 @@ func cleanUntrackedReleases() { toDelete := make(map[string]map[string]bool) log.Println("INFO: checking if any Helmsman managed releases are no longer tracked by your desired state ...") for ns, releases := range getHelmsmanReleases() { - if v, ok := s.Namespaces[ns]; ok && v.InstallTiller { - for r := range releases { - tracked := false - for _, app := range s.Apps { - if app.Name == r && getDesiredTillerNamespace(app) == ns { - tracked = true - } + for r := range releases { + tracked := false + for _, app := range s.Apps { + if app.Name == r && getDesiredTillerNamespace(app) == ns { + tracked = true } - if !tracked { - if _, ok := toDelete[ns]; !ok { - toDelete[ns] = make(map[string]bool) - } - toDelete[ns][r] = true + } + if !tracked { + if _, ok := toDelete[ns]; !ok { + toDelete[ns] = make(map[string]bool) } + toDelete[ns][r] = true } } } diff --git a/kube_helpers.go b/kube_helpers.go index a0aaf2bf..3e8a0f5f 100644 --- a/kube_helpers.go +++ b/kube_helpers.go @@ -294,6 +294,7 @@ func labelResource(r *release) { // The releases are categorized by the namespaces in which their Tiller is running // The returned map format is: map[:map[:true]] func getHelmsmanReleases() map[string]map[string]bool { + var lines []string releases := make(map[string]map[string]bool) storageBackend := "configmap" @@ -301,31 +302,42 @@ func getHelmsmanReleases() map[string]map[string]bool { storageBackend = "secret" } - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "kubectl get " + storageBackend + " --all-namespaces -l MANAGED-BY=HELMSMAN"}, - Description: "getting helm releases which are managed by Helmsman.", + namespaces := make([]string, len(s.Namespaces)) + i := 0 + for s := range s.Namespaces { + namespaces[i] = s + i++ + } + if v, ok := s.Namespaces["kube-system"]; !ok || (ok && (v.UseTiller || v.InstallTiller)) { + namespaces = append(namespaces, "kube-system") } - exitCode, output := cmd.exec(debug, verbose) + for _, ns := range namespaces { + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "kubectl get " + storageBackend + " -n " + ns + " -l MANAGED-BY=HELMSMAN"}, + Description: "getting helm releases which are managed by Helmsman in namespace [[ " + ns + " ]].", + } - if exitCode != 0 { - logError(output) - } + exitCode, output := cmd.exec(debug, verbose) - lines := strings.Split(output, "\n") - if strings.ToUpper("No resources found.") == strings.ToUpper(strings.TrimSpace(output)) { - return releases - } - for i := 0; i < len(lines); i++ { - if lines[i] == "" || (strings.HasPrefix(strings.TrimSpace(lines[i]), "NAMESPACE") && strings.HasSuffix(strings.TrimSpace(lines[i]), "AGE")) { - continue - } else { - fields := strings.Fields(lines[i]) - if _, ok := releases[fields[0]]; !ok { - releases[fields[0]] = make(map[string]bool) + if exitCode != 0 { + logError(output) + } + if strings.ToUpper("No resources found.") != strings.ToUpper(strings.TrimSpace(output)) { + lines = strings.Split(output, "\n") + } + + for i := 0; i < len(lines); i++ { + if lines[i] == "" || strings.HasSuffix(strings.TrimSpace(lines[i]), "AGE") { + continue + } else { + fields := strings.Fields(lines[i]) + if _, ok := releases[fields[0]]; !ok { + releases[ns] = make(map[string]bool) + } + releases[ns][fields[0][0:strings.LastIndex(fields[0], ".v")]] = true } - releases[fields[0]][fields[1][0:strings.LastIndex(fields[1], ".v")]] = true } } From e0dad612884655a49b0f34edc6993ed7c030ce4e Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Tue, 25 Sep 2018 19:06:37 +0200 Subject: [PATCH 0255/1127] limiting checks for untracked releases to namespaces installing or using Tiller. fixes #83 --- kube_helpers.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/kube_helpers.go b/kube_helpers.go index 3e8a0f5f..c8da9031 100644 --- a/kube_helpers.go +++ b/kube_helpers.go @@ -304,10 +304,13 @@ func getHelmsmanReleases() map[string]map[string]bool { namespaces := make([]string, len(s.Namespaces)) i := 0 - for s := range s.Namespaces { - namespaces[i] = s - i++ + for s, v := range s.Namespaces { + if v.InstallTiller || v.UseTiller { + namespaces[i] = s + i++ + } } + namespaces = namespaces[0:i] if v, ok := s.Namespaces["kube-system"]; !ok || (ok && (v.UseTiller || v.InstallTiller)) { namespaces = append(namespaces, "kube-system") } From 7b935f080a08359dbda5ecd977ef804358d754b8 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Tue, 25 Sep 2018 19:21:01 +0200 Subject: [PATCH 0256/1127] add a check for no desired state files provided --- init.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/init.go b/init.go index e3401db3..b48cd63c 100644 --- a/init.go +++ b/init.go @@ -74,6 +74,15 @@ func init() { helmVersion = strings.TrimSpace(strings.SplitN(getHelmClientVersion(), ": ", 2)[1]) kubectlVersion = strings.TrimSpace(strings.SplitN(getKubectlClientVersion(), ": ", 2)[1]) + if verbose { + logVersions() + } + + if len(files) == 0 { + log.Println("INFO: No desired state files provided.") + os.Exit(0) + } + // initializing pwd and relative directory for DSF(s) and values files pwd, _ = os.Getwd() lastSlashIndex := -1 @@ -90,10 +99,6 @@ func init() { os.Exit(0) } - if verbose { - logVersions() - } - if !toolExists("kubectl") { logError("ERROR: kubectl is not installed/configured correctly. Aborting!") } From 22f12b27e9ce5ce09f1ac7883a8604447c5bad6b Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Tue, 25 Sep 2018 19:21:21 +0200 Subject: [PATCH 0257/1127] bumping version to 1.6.1 --- README.md | 6 +++--- main.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 59784980..763975ee 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ --- -version: v1.6.0 +version: v1.6.1 --- ![helmsman-logo](docs/images/helmsman.png) @@ -46,9 +46,9 @@ To show debugging details: Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. ``` # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v1.6.0/helmsman_1.6.0_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.6.1/helmsman_1.6.1_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v1.6.0/helmsman_1.6.0_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.6.1/helmsman_1.6.1_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/main.go b/main.go index 635efb8c..043694ac 100644 --- a/main.go +++ b/main.go @@ -32,7 +32,7 @@ var checkCleanup bool var skipValidation bool var applyLabels bool var keepUntrackedReleases bool -var appVersion = "v1.6.0" +var appVersion = "v1.6.1" var helmVersion string var kubectlVersion string var pwd string From 400e53de2de04234f027c2b7aec6b419101fc316 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Tue, 25 Sep 2018 19:22:00 +0200 Subject: [PATCH 0258/1127] updating release notes for v1.6.1 --- release-notes.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/release-notes.md b/release-notes.md index 7508ef43..df6fc6a7 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,11 +1,8 @@ -# v1.6.0 +# v1.6.1 > If you are already using an older version of Helmsman than v1.4.0-rc, please read the changes below carefully and follow the upgrade guide [here](docs/migrating_to_v1.4.0-rc.md) -- Adding support for helm secrets plugin (thanks to @luisdavim). issue #54 -- Adding support for using helm diff to determine when upgrades are needed and show the diff. -- Allowing env vars to be loaded from files using Godotenv (thanks to @luisdavim) -- Adding `--dry-run` option in Helmsman to perform a helm dry-run operation. Issues #77 #60 -- Adding `useTiller` option in namespaces definitions to use existing Tillers. Issue #71 -- Other minor code improvements and color coded output. +- Fixing cluster-wide access problem when checking for untracked releases in restricted clusters. Issue #83. +- Fixing not including releases from existing Tillers in helm state when using useTiller. +- Adding `--no-fancy` option to disable colored output. From 434053f65a74fa440ba2aa49788eade5f2f1b8c5 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Tue, 25 Sep 2018 19:31:08 +0200 Subject: [PATCH 0259/1127] fixing test command --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f57fae90..e03b6a1a 100644 --- a/Makefile +++ b/Makefile @@ -44,7 +44,7 @@ check: .PHONY: check test: dependencies ## Run unit tests - @go test -v -cover -p=1 #${PKGS} + @go test -v -cover -p=1 -args -f example.toml #${PKGS} .PHONY: test cross: dependencies ## Create binaries for all OSs From 9fb62c71ae8a7010b109b32bca5b3ff7dc56113a Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Wed, 26 Sep 2018 11:46:53 +0200 Subject: [PATCH 0260/1127] updating docs --- decision_maker.go | 18 ++++-- docs/desired_state_specification.md | 86 ++++++++++++++++------------- example.toml | 6 +- example.yaml | 3 + release.go | 37 ++++++++----- 5 files changed, 92 insertions(+), 58 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index 7ff9d2d3..0eb45ad5 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -102,7 +102,7 @@ func installRelease(r *release) { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm install " + r.Chart + " -n " + r.Name + " --namespace " + r.Namespace + getValuesFiles(r) + " --version " + r.Version + getSetValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r) + getDryRunFlags()}, + Args: []string{"-c", "helm install " + r.Chart + " -n " + r.Name + " --namespace " + r.Namespace + getValuesFiles(r) + " --version " + r.Version + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r) + getDryRunFlags()}, Description: "installing release [ " + r.Name + " ] in namespace [[ " + r.Namespace + " ]] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } outcome.addCommand(cmd, r.Priority, r) @@ -219,7 +219,7 @@ func diffRelease(r *release) string { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm diff " + colorFlag + "upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + r.Version + " " + getSetValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r)}, + Args: []string{"-c", "helm diff " + colorFlag + "upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + r.Version + " " + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r)}, Description: "upgrading release [ " + r.Name + " ] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } @@ -236,7 +236,7 @@ func diffRelease(r *release) string { func upgradeRelease(r *release) { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + r.Version + " --force " + getSetValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r) + getDryRunFlags()}, + Args: []string{"-c", "helm upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + r.Version + " --force " + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r) + getDryRunFlags()}, Description: "upgrading release [ " + r.Name + " ] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } @@ -256,7 +256,7 @@ func reInstallRelease(r *release, rs releaseState) { installCmd := command{ Cmd: "bash", - Args: []string{"-c", "helm install " + r.Chart + " --version " + r.Version + " -n " + r.Name + " --namespace " + r.Namespace + getValuesFiles(r) + getSetValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r) + getDryRunFlags()}, + Args: []string{"-c", "helm install " + r.Chart + " --version " + r.Version + " -n " + r.Name + " --namespace " + r.Namespace + getValuesFiles(r) + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r) + getDryRunFlags()}, Description: "installing release [ " + r.Name + " ] in namespace [[ " + r.Namespace + " ]] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } outcome.addCommand(installCmd, r.Priority, r) @@ -345,6 +345,16 @@ func getSetValues(r *release) string { return result } +// getSetStringValues returns --set-string params to be used with helm install/upgrade commands +func getSetStringValues(r *release) string { + result := "" + for k, v := range r.SetString { + value := substituteEnv(v) + result = result + " --set-string " + k + "=\"" + strings.Replace(value, ",", "\\,", -1) + "\"" + } + return result +} + // getWait returns a partial helm command containing the helm wait flag (--wait) if the wait flag for the release was set to true // Otherwise, retruns an empty string func getWait(r *release) string { diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 3b0bbc57..1d415000 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -1,5 +1,5 @@ --- -version: v1.5.0 +version: v1.6.2 --- # Helmsman desired state specification @@ -43,9 +43,9 @@ Optional : Yes, only needed if you want Helmsman to connect kubectl to your clus Synopsis: defines where to find the certificates needed for connecting kubectl to a k8s cluster. If connection settings (username/password/clusterAPI) are provided in the Settings section below, then you need AT LEAST to provide caCrt and caKey. You can optionally provide a client certificate (caClient) depending on your cluster connection setup. Options: -- caCrt : a valid S3/GCS bucket or local relative file path to a certificate file. -- caKey : a valid S3/GCS bucket or local relative file path to a client key file. -- caClient: a valid S3/GCS bucket or local relative file path to a client certificate file. +- **caCrt** : a valid S3/GCS bucket or local relative file path to a certificate file. +- **caKey** : a valid S3/GCS bucket or local relative file path to a client key file. +- **caClient**: a valid S3/GCS bucket or local relative file path to a client certificate file. > You can use environment variables to pass the values of the options above. The environment variable name should start with $ @@ -80,13 +80,13 @@ Options: The following options can be skipped if your kubectl context is already created and you don't want Helmsman to connect kubectl to your cluster for you. When using Helmsman in CI pipeline, these details are required to connect to your cluster every time the pipeline is executed. -- username : the username to be used for kubectl credentials. -- password : an environment variable name (starting with `$`) where your password is stored. Get the password from your k8s admin or consult k8s docs on how to get/set it. -- clusterURI : the URI for your cluster API or the name of an environment variable (starting with `$`) containing the URI. -- serviceAccount: the name of the service account to use to initiate helm. This should have enough permissions to allow Helm to work and should exist already in the cluster. More details can be found in [helm's RBAC guide](https://github.com/kubernetes/helm/blob/master/docs/rbac.md) -- storageBackend : by default Helm stores release information in configMaps, using secrets is for storage is recommended for security. Setting this flag to `secret` will deploy/upgrade Tiller with the `--storage=secret`. Other values will be skipped and configMaps will be used. -- slackWebhook : a [Slack](slack.com) Webhook URL to receive Helmsman notifications. This can be passed directly or in an environment variable. -- reverseDelete : if set to `true` it will reverse the priority order whilst deleting. +- **username** : the username to be used for kubectl credentials. +- **password** : an environment variable name (starting with `$`) where your password is stored. Get the password from your k8s admin or consult k8s docs on how to get/set it. +- **clusterURI** : the URI for your cluster API or the name of an environment variable (starting with `$`) containing the URI. +- **serviceAccount**: the name of the service account to use to initiate helm. This should have enough permissions to allow Helm to work and should exist already in the cluster. More details can be found in [helm's RBAC guide](https://github.com/kubernetes/helm/blob/master/docs/rbac.md) +- **storageBackend** : by default Helm stores release information in configMaps, using secrets is for storage is recommended for security. Setting this flag to `secret` will deploy/upgrade Tiller with the `--storage=secret`. Other values will be skipped and configMaps will be used. +- **slackWebhook** : a [Slack](slack.com) Webhook URL to receive Helmsman notifications. This can be passed directly or in an environment variable. +- **reverseDelete** : if set to `true` it will reverse the priority order whilst deleting. > If you use `storageBackend` with a Tiller that has been previously deployed with configMaps as storage backend, you need to migrate your release information from the configMap to the new secret on your own. Helm does not support this yet. @@ -126,12 +126,13 @@ Synopsis: defines the namespaces to be used/created in your k8s cluster and whet If a namespace does not already exist, Helmsman will create it. Options: -- protected : defines if a namespace is protected (true or false). Default false. +- **protected** : defines if a namespace is protected (true or false). Default false. > For the definition of what a protected namespace means, check the [protection guide](how_to/protect_namespaces_and_releases.md) -- installTiller: defines if Tiller should be deployed in this namespace or not. Default is false. Any chart desired to be deployed into a namespace with a Tiller deployed, will be deployed using that Tiller and not the one in kube-system unless you use the `TillerNamespace` option (see the [Apps](#apps) section below) to use another Tiller. +- **installTiller**: defines if Tiller should be deployed in this namespace or not. Default is false. Any chart desired to be deployed into a namespace with a Tiller deployed, will be deployed using that Tiller and not the one in kube-system unless you use the `TillerNamespace` option (see the [Apps](#apps) section below) to use another Tiller. > By default Tiller will be deployed into `kube-system` even if you don't define kube-system in the namespaces section. To prevent deploying Tiller into `kube-system, add kube-system in your namespaces section and set its installTiller to false. +-**useTiller**: defines that you would like to use an existing Tiller from that namespace. Can't be set together with `installTiller` -- tillerServiceAccount: defines what service account to use when deploying Tiller. If this is not set, the following options are considered: +- **tillerServiceAccount**: defines what service account to use when deploying Tiller. If this is not set, the following options are considered: 1. If the `serviceAccount` defined in the `settings` section exists in the namespace you want to deploy Tiller in, it will be used, else 2. Helmsman creates the service account in that namespace and binds it to a role. If the namespace is kube-system, the service account is bound to `cluster-admin` clusterrole. Otherwise, a new role called `helmsman-tiller` is created in that namespace and only gives access to that namespace. @@ -139,11 +140,11 @@ Options: > If `installTiller` is not defined or set to false, this flag is ignored. - The following options are `ALL` needed for deploying Tiller with TLS enabled. If they are not all defined, they will be ignored and Tiller will be deployed without TLS. All of these options can be provided as either: a valid local file path, a valid GCS or S3 bucket URI or an environment variable containing a file path or bucket URI. - - caCert: the CA certificate. - - tillerCert: the SSL certificate for Tiller. - - tillerKey: the SSL certificate private key for Tiller. - - clientCert: the SSL certificate for the Helm client. - - clientKey: the SSL certificate private key for the Helm client. + - **caCert**: the CA certificate. + - **tillerCert**: the SSL certificate for Tiller. + - **tillerKey**: the SSL certificate private key for Tiller. + - **clientCert**: the SSL certificate for the Helm client. + - **clientKey**: the SSL certificate private key for the Helm client. Example: @@ -154,6 +155,7 @@ Example: # installTiller = false # this line can be omitted since installTiller defaults to false [namespaces.staging] [namespaces.dev] +useTiller = true # use a Tiller which has been deployed in dev namespace protected = false [namespaces.production] protected = true @@ -174,6 +176,7 @@ namespaces: staging: dev: protected: false + useTiller: true # use a Tiller which has been deployed in dev namespace production: protected: true installTiller: true @@ -226,39 +229,40 @@ Optional : Yes. Synopsis: defines the releases (instances of Helm charts) you would like to manage in your k8s cluster. -Releases must have unique names which are defined under `apps`. Example: in `[apps.jenkins]`, the release name will be `jenkins` and it should be unique in your cluster. +Releases must have unique names which are defined under `apps`. Example: in `[apps.jenkins]`, the release name will be `jenkins` and it should be unique within the Tiller which manages it . Options: **Required** -- namespace : the namespace where the release should be deployed. The namespace should map to one of the ones defined in [namespaces](#namespaces). -- enabled : describes the required state of the release (true for enabled, false for disabled). Once a release is deployed, you can change it to false if you want to delete this release [default is false]. -- chart : the chart name. It should contain the repo name as well. Example: repoName/chartName. Changing the chart name means delete and reinstall this release using the new Chart. -- version : the chart version. +- **namespace** : the namespace where the release should be deployed. The namespace should map to one of the ones defined in [namespaces](#namespaces). +- **enabled** : describes the required state of the release (true for enabled, false for disabled). Once a release is deployed, you can change it to false if you want to delete this release [default is false]. +- **chart** : the chart name. It should contain the repo name as well. Example: repoName/chartName. Changing the chart name means delete and reinstall this release using the new Chart. +- **version** : the chart version. **Optional** -- tillerNamespace : which Tiller to use for deploying this release. This is available starting from v1.4.0-rc The decision on which Tiller to use for deploying a release follows the following criteria: +- **tillerNamespace** : which Tiller to use for deploying this release. This is available starting from v1.4.0-rc The decision on which Tiller to use for deploying a release follows the following criteria: 1. If `tillerNamespace`is explicitly defined, it is used. 2. If `tillerNamespace`is not defined and the namespace in which the release will be deployed has a Tiller installed by Helmsman (i.e. has `installTiller set to true` in the [Namespaces](#namespaces) section), Tiller in that namespace is used. 3. If none of the above, the shared Tiller in `kube-system` is used. -- name : the Helm release name. Releases must have unique names within a Helm Tiller. If not set, the release name will be taken from the app identifier in your desired state file. e.g, for ` apps.jenkins ` the name release name will be `jenkins`. -- description : a release metadata for human readers. -- valuesFile : a valid path to custom Helm values.yaml file. File extension must be `yaml`. Cannot be used with valuesFiles together. Leaving it empty uses the default chart values. -- valuesFiles : array of valid paths to custom Helm values.yaml file. File extension must be `yaml`. Cannot be used with valuesFile together. Leaving it empty uses the default chart values. +- **name** : the Helm release name. Releases must have unique names within a Helm Tiller. If not set, the release name will be taken from the app identifier in your desired state file. e.g, for ` apps.jenkins ` the release name will be `jenkins`. +- **description** : a release metadata for human readers. +- **valuesFile** : a valid path to custom Helm values.yaml file. File extension must be `yaml`. Cannot be used with valuesFiles together. Leaving it empty uses the default chart values. +- **valuesFiles** : array of valid paths to custom Helm values.yaml file. File extension must be `yaml`. Cannot be used with valuesFile together. Leaving it empty uses the default chart values. > The values file(s) path is relative from the location of the (first) desired state file you pass in your Helmsman command. -- secretsFile : a valid path to custom Helm secrets.yaml file. File extension must be `yaml`. Cannot be used with secretsFiles together. Leaving it empty uses the default chart secrets. -- secretsFiles : array of valid paths to custom Helm secrets.yaml file. File extension must be `yaml`. Cannot be used with secretsFile together. Leaving it empty uses the default chart secrets. +- **secretsFile** : a valid path to custom Helm secrets.yaml file. File extension must be `yaml`. Cannot be used with secretsFiles together. Leaving it empty uses the default chart secrets. +- **secretsFiles** : array of valid paths to custom Helm secrets.yaml file. File extension must be `yaml`. Cannot be used with secretsFile together. Leaving it empty uses the default chart secrets. > The secrets file(s) path is relative from the location of the (first) desired state file you pass in your Helmsman command. > To use the secrets files you must have the helm-secrets plugin -- purge : defines whether to use the Helm purge flag when deleting the release. (true/false) -- test : defines whether to run the chart tests whenever the release is installed. -- protected : defines if the release should be protected against changes. Namespace-level protection has higher priority than this flag. Check the [protection guide](how_to/protect_namespaces_and_releases.md) for more details. -- wait : defines whether Helmsman should block execution until all k8s resources are in a ready state. Default is false. -- timeout : helm timeout in seconds. Default 300 seconds. -- noHooks : helm noHooks option. If true, it will disable pre/post upgrade hooks. Default is false. -- priority : defines the priority of applying operations on this release. Only negative values allowed and the lower the value, the higher the priority. Default priority is 0. Apps with equal priorities will be applied in the order they were added in your state file (DSF). -- [apps..set] : is used to override certain values from values.yaml with values from environment variables (or ,starting from v1.3.0-rc, directly provided in the Desired State File). This is particularly useful for passing secrets to charts. If the an environment variable with the same name as the provided value exists, the environment variable value will be used, otherwise, the provided value will be used as is. +- **purge** : defines whether to use the Helm purge flag when deleting the release. (true/false) +- **test** : defines whether to run the chart tests whenever the release is installed. +- **protected** : defines if the release should be protected against changes. Namespace-level protection has higher priority than this flag. Check the [protection guide](how_to/protect_namespaces_and_releases.md) for more details. +- **wait** : defines whether Helmsman should block execution until all k8s resources are in a ready state. Default is false. +- **timeout** : helm timeout in seconds. Default 300 seconds. +- **noHooks** : helm noHooks option. If true, it will disable pre/post upgrade hooks. Default is false. +- **priority** : defines the priority of applying operations on this release. Only negative values allowed and the lower the value, the higher the priority. Default priority is 0. Apps with equal priorities will be applied in the order they were added in your state file (DSF). +- **set** : is used to override certain values from values.yaml with values from environment variables (or ,starting from v1.3.0-rc, directly provided in the Desired State File). This is particularly useful for passing secrets to charts. If the an environment variable with the same name as the provided value exists, the environment variable value will be used, otherwise, the provided value will be used as is. The TOML stanza for this is `[apps..set]` +- **setString** : is used to override String values from values.yaml or chart's defaults. This uses the `--set-string` flag in helm which is available only in helm >v2.9.0. This option is useful for image tags and the like. The TOML stanza for this is `[apps..setString]` Example: @@ -283,6 +287,8 @@ Example: [apps.jenkins.set] secret1="$SECRET_ENV_VAR1" secret2="SECRET_ENV_VAR2" # works with/without $ at the beginning + [apps.jenkins.setString] + longInt = "1234567890" ``` ```yaml @@ -303,4 +309,6 @@ apps: set: secret1: "$SECRET_ENV_VAR1" secret2: "$SECRET_ENV_VAR2" + setString: + longInt: "1234567890" ``` diff --git a/example.toml b/example.toml index a9648028..2055bc5d 100644 --- a/example.toml +++ b/example.toml @@ -1,4 +1,4 @@ -# version: v1.5.0 +# version: v1.6.2 # metadata -- add as many key/value pairs as you want [metadata] org = "example.com" @@ -74,8 +74,10 @@ protected = true priority= -3 wait = true - [apps.jenkins.set] # values to override values from values.yaml with values from env vars or directly entered-- useful for passing secrets to charts + [apps.jenkins.setString] # values to override values from values.yaml with values from env vars or directly entered-- useful for passing secrets to charts AdminPassword="$JENKINS_PASSWORD" # $JENKINS_PASSWORD must exist in the environment + MyLongIntVar="1234567890" + [apps.jenkins.set] AdminUser="admin" diff --git a/example.yaml b/example.yaml index 360ed1a8..afe85013 100644 --- a/example.yaml +++ b/example.yaml @@ -73,6 +73,9 @@ apps: set: # values to override values from values.yaml with values from env vars-- useful for passing secrets to charts AdminPassword: "$JENKINS_PASSWORD" # $JENKINS_PASSWORD must exist in the environment AdminUser: "admin" + setString: + MyLongIntVar: "1234567890" + # artifactory will be deployed using the Tiller in the kube-system namespace artifactory: diff --git a/release.go b/release.go index 6e7c3ef6..f8803226 100644 --- a/release.go +++ b/release.go @@ -5,28 +5,31 @@ import ( "log" "os" "strings" + + version "github.com/hashicorp/go-version" ) // release type representing Helm releases which are described in the desired state type release struct { - Name string - Description string - Namespace string - Enabled bool - Chart string - Version string + Name string `yaml:"name"` + Description string `yaml:"description"` + Namespace string `yaml:"namespace"` + Enabled bool `yaml:"enabled"` + Chart string `yaml:"chart"` + Version string `yaml:"version"` ValuesFile string `yaml:"valuesFile"` ValuesFiles []string `yaml:"valuesFiles"` SecretFile string `yaml:"secretFile"` SecretFiles []string `yaml:"secretFiles"` - Purge bool - Test bool - Protected bool - Wait bool - Priority int - TillerNamespace string + Purge bool `yaml:"purge"` + Test bool `yaml:"test"` + Protected bool `yaml:"protected"` + Wait bool `yaml:"wait"` + Priority int `yaml:"priority"` + TillerNamespace string `yaml:"tillerNamespace"` Set map[string]string - NoHooks bool + SetString map[string]string `yaml:"setString"` + NoHooks bool `yaml:"noHooks"` Timeout int } @@ -105,6 +108,14 @@ func validateRelease(appLabel string, r *release, names map[string]map[string]bo names[r.Name]["kube-system"] = true } + if len(r.SetString) > 0 { + v1, _ := version.NewVersion(helmVersion) + setStringConstraint, _ := version.NewConstraint(">=2.9.0") + if !setStringConstraint.Check(v1) { + return false, "you are using setString in your desired state, but your helm client does not support it. You need helm v2.9.0 or above for this feature." + } + } + return true, "" } From c0b82735c6d13a1252fd554d08e07db1c6d58845 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Wed, 26 Sep 2018 11:47:08 +0200 Subject: [PATCH 0261/1127] bumping version --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 043694ac..89ecd606 100644 --- a/main.go +++ b/main.go @@ -32,7 +32,7 @@ var checkCleanup bool var skipValidation bool var applyLabels bool var keepUntrackedReleases bool -var appVersion = "v1.6.1" +var appVersion = "v1.6.2" var helmVersion string var kubectlVersion string var pwd string From 18443fc19722f0097f286a135b98228e2859be7d Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Wed, 26 Sep 2018 12:24:05 +0200 Subject: [PATCH 0262/1127] bumping helm version in the test dockerfile --- test_files/dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_files/dockerfile b/test_files/dockerfile index f7f41e03..4b6a0216 100644 --- a/test_files/dockerfile +++ b/test_files/dockerfile @@ -3,7 +3,7 @@ FROM golang:1.10-alpine3.7 as builder ENV KUBE_VERSION v1.8.2 -ENV HELM_VERSION v2.7.0 +ENV HELM_VERSION v2.10.0 RUN apk add --update --no-cache ca-certificates git \ && apk add --update -t deps curl tar gzip make bash \ From 9df9a351c7ec321db7c93d75c992394b4e2efe13 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Wed, 26 Sep 2018 12:46:55 +0200 Subject: [PATCH 0263/1127] adding destroy support. #88 --- decision_maker.go | 6 ++++++ init.go | 10 +++++----- main.go | 6 +++++- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index 0eb45ad5..8105dcde 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -26,6 +26,12 @@ func makePlan(s *state) *plan { // decide makes a decision about what commands (actions) need to be executed // to make a release section of the desired state come true. func decide(r *release, s *state) { + if destroy { + if ok, rs := helmReleaseExists(r, ""); ok { + deleteRelease(r, rs) + return + } + } // check for deletion if !r.Enabled { diff --git a/init.go b/init.go index b48cd63c..d697ed22 100644 --- a/init.go +++ b/init.go @@ -43,6 +43,7 @@ func init() { flag.BoolVar(&apply, "apply", false, "apply the plan directly") flag.BoolVar(&debug, "debug", false, "show the execution logs") flag.BoolVar(&dryRun, "dry-run", false, "apply the dry-run option for helm commands.") + flag.BoolVar(&destroy, "destroy", false, "delete all deployed releases. Purge delete is used if the purge option is set to true for the releases.") flag.BoolVar(&v, "v", false, "show the version") flag.BoolVar(&verbose, "verbose", false, "show verbose execution logs") flag.BoolVar(&noBanner, "no-banner", false, "don't show the banner") @@ -71,6 +72,10 @@ func init() { logError("ERROR: --apply and --dry-run can't be used together.") } + if destroy && apply { + logError("ERROR: --destroy and --apply can't be used together.") + } + helmVersion = strings.TrimSpace(strings.SplitN(getHelmClientVersion(), ": ", 2)[1]) kubectlVersion = strings.TrimSpace(strings.SplitN(getKubectlClientVersion(), ": ", 2)[1]) @@ -128,11 +133,6 @@ func init() { } } - // print all env variables - // for _, pair := range os.Environ() { - // fmt.Println(pair) - // } - // read the TOML/YAML desired state file var fileState state for _, f := range files { diff --git a/main.go b/main.go index 89ecd606..a53eacc5 100644 --- a/main.go +++ b/main.go @@ -38,6 +38,7 @@ var kubectlVersion string var pwd string var relativeDir string var dryRun bool +var destroy bool func main() { // set the kubecontext to be used Or create it if it does not exist @@ -81,6 +82,9 @@ func main() { } log.Println("INFO: checking what I need to do for your charts ... ") + if destroy { + log.Println("WARN: --destroy is enabled. Your releases will be deleted!") + } p := makePlan(&s) if !keepUntrackedReleases { @@ -91,7 +95,7 @@ func main() { p.printPlan() p.sendPlanToSlack() - if apply || dryRun { + if apply || dryRun || destroy { p.execPlan() } From e8148f4c961c2e58dc3ccedbe75058b030515c61 Mon Sep 17 00:00:00 2001 From: Exequiel Pierotto Date: Fri, 28 Sep 2018 10:14:14 +0100 Subject: [PATCH 0264/1127] Adding some quotes --- decision_maker.go | 2 +- helm_helpers.go | 4 ++-- kube_helpers.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index 8105dcde..24478b84 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -225,7 +225,7 @@ func diffRelease(r *release) string { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm diff " + colorFlag + "upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + r.Version + " " + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r)}, + Args: []string{"-c", "helm diff " + colorFlag + "upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + strconv.Quote(r.Version) + " " + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r)}, Description: "upgrading release [ " + r.Name + " ] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } diff --git a/helm_helpers.go b/helm_helpers.go index c9301ea9..76a4f805 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -203,7 +203,7 @@ func validateReleaseCharts(apps map[string]*release) (bool, string) { for app, r := range apps { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm search " + r.Chart + " --version " + r.Version + " -l"}, + Args: []string{"-c", "helm search " + r.Chart + " --version " + strconv.Quote(r.Version) + " -l"}, Description: "validating if chart " + r.Chart + "-" + r.Version + " is available in the defined repos.", } @@ -256,7 +256,7 @@ func addHelmRepos(repos map[string]string) (bool, string) { } cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm repo add " + repoName + " " + url}, + Args: []string{"-c", "helm repo add " + repoName + " " + strconv.Quote(url)}, Description: "adding repo " + repoName, } diff --git a/kube_helpers.go b/kube_helpers.go index c8da9031..d47aebc5 100644 --- a/kube_helpers.go +++ b/kube_helpers.go @@ -81,7 +81,7 @@ func createNamespace(ns string) { } if exitCode, _ := cmd.exec(debug, verbose); exitCode != 0 { - log.Println("WARN: I could not create namespace [" + + log.Println("WARN: I could not create namespace [ " + ns + " ]. It already exists. I am skipping this.") } } From 686169774670f37d38786bb0ac63df702df0cf5d Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Fri, 28 Sep 2018 12:46:19 +0200 Subject: [PATCH 0265/1127] fixing values files absolute paths when using valuesFiles. fixes #93 --- decision_maker.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index 8105dcde..575d4586 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -307,10 +307,11 @@ func getValuesFiles(r *release) string { if r.ValuesFile != "" { fileList = append(fileList, pwd+"/"+relativeDir+"/"+r.ValuesFile) } else if len(r.ValuesFiles) > 0 { + tempValuesFiles := make([]string, len(r.ValuesFiles)) for i := 0; i < len(r.ValuesFiles); i++ { - r.ValuesFiles[i] = pwd + "/" + relativeDir + "/" + r.ValuesFiles[i] + tempValuesFiles[i] = pwd + "/" + relativeDir + "/" + r.ValuesFiles[i] } - fileList = append(fileList, r.ValuesFiles...) + fileList = append(fileList, tempValuesFiles...) } if r.SecretFile != "" { From 83d4264653d14a350f8c982431686380a75adc50 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Fri, 28 Sep 2018 13:15:28 +0200 Subject: [PATCH 0266/1127] adding godep files #91 --- Gopkg.lock | 356 +++++++++++++++++++++++++++++++++++++++++++++++++++++ Gopkg.toml | 70 +++++++++++ 2 files changed, 426 insertions(+) create mode 100644 Gopkg.lock create mode 100644 Gopkg.toml diff --git a/Gopkg.lock b/Gopkg.lock new file mode 100644 index 00000000..460f312a --- /dev/null +++ b/Gopkg.lock @@ -0,0 +1,356 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + digest = "1:9854327327a05877e8c528f6830dcadd05d149f5be7c6405ac499c0202e9d43b" + name = "cloud.google.com/go" + packages = [ + "compute/metadata", + "iam", + "internal", + "internal/optional", + "internal/trace", + "internal/version", + "storage", + ] + pruneopts = "UT" + revision = "97efc2c9ffd9fe8ef47f7f3203dc60bbca547374" + version = "v0.28.0" + +[[projects]] + digest = "1:dc27d9777febe9e63ab33a2cd15e31ce8d9463932d2472cc7097dc662dedb5ab" + name = "contrib.go.opencensus.io/exporter/stackdriver" + packages = ["propagation"] + pruneopts = "UT" + revision = "2b93072101d466aa4120b3c23c2e1b08af01541c" + version = "v0.6.0" + +[[projects]] + digest = "1:9f3b30d9f8e0d7040f729b82dcbc8f0dead820a133b3147ce355fc451f32d761" + name = "github.com/BurntSushi/toml" + packages = ["."] + pruneopts = "UT" + revision = "3012a1dbe2e4bd1391d42b32f0577cb7bbc7f005" + version = "v0.3.1" + +[[projects]] + digest = "1:a3634c13a25998109db4c6298c5b36b79012eafa7fe23cb6d5ab2a552a4f07cb" + name = "github.com/Praqma/helmsman" + packages = [ + "aws", + "gcs", + ] + pruneopts = "UT" + revision = "f1dab5cebb9bc8a3cb93e74ef3de1fa4e1b24d70" + version = "v1.6.1" + +[[projects]] + digest = "1:d76c131361e2535e8031b250f331dfd4617bfcb6564cf04c01a7371de0e4c202" + name = "github.com/aws/aws-sdk-go" + packages = [ + "aws", + "aws/awserr", + "aws/awsutil", + "aws/client", + "aws/client/metadata", + "aws/corehandlers", + "aws/credentials", + "aws/credentials/ec2rolecreds", + "aws/credentials/endpointcreds", + "aws/credentials/stscreds", + "aws/csm", + "aws/defaults", + "aws/ec2metadata", + "aws/endpoints", + "aws/request", + "aws/session", + "aws/signer/v4", + "internal/sdkio", + "internal/sdkrand", + "internal/sdkuri", + "internal/shareddefaults", + "private/protocol", + "private/protocol/eventstream", + "private/protocol/eventstream/eventstreamapi", + "private/protocol/query", + "private/protocol/query/queryutil", + "private/protocol/rest", + "private/protocol/restxml", + "private/protocol/xml/xmlutil", + "service/s3", + "service/s3/s3iface", + "service/s3/s3manager", + "service/sts", + ] + pruneopts = "UT" + revision = "85d9dfd77e6d694e83c3ac054141cb5e81eecc7f" + version = "v1.15.43" + +[[projects]] + digest = "1:5abd6a22805b1919f6a6bca0ae58b13cef1f3412812f38569978f43ef02743d4" + name = "github.com/go-ini/ini" + packages = ["."] + pruneopts = "UT" + revision = "5cf292cae48347c2490ac1a58fe36735fb78df7e" + version = "v1.38.2" + +[[projects]] + digest = "1:5d1b5a25486fc7d4e133646d834f6fca7ba1cef9903d40e7aa786c41b89e9e91" + name = "github.com/golang/protobuf" + packages = [ + "proto", + "protoc-gen-go/descriptor", + "ptypes", + "ptypes/any", + "ptypes/duration", + "ptypes/timestamp", + ] + pruneopts = "UT" + revision = "aa810b61a9c79d51363740d207bb46cf8e620ed5" + version = "v1.2.0" + +[[projects]] + digest = "1:e145e9710a10bc114a6d3e2738aadf8de146adaa031854ffdf7bbfe15da85e63" + name = "github.com/googleapis/gax-go" + packages = ["."] + pruneopts = "UT" + revision = "317e0006254c44a0ac427cc52a0e083ff0b9622f" + version = "v2.0.0" + +[[projects]] + digest = "1:77395dd3847dac9c45118c668f5dab85aedf0163dc3b38aea6578c5cf0d502f9" + name = "github.com/hashicorp/go-version" + packages = ["."] + pruneopts = "UT" + revision = "b5a281d3160aa11950a6182bd9a9dc2cb1e02d50" + version = "v1.0.0" + +[[projects]] + digest = "1:8eb1de8112c9924d59bf1d3e5c26f5eaa2bfc2a5fcbb92dc1c2e4546d695f277" + name = "github.com/imdario/mergo" + packages = ["."] + pruneopts = "UT" + revision = "9f23e2d6bd2a77f959b2bf6acdbefd708a83a4a4" + version = "v0.3.6" + +[[projects]] + digest = "1:e22af8c7518e1eab6f2eab2b7d7558927f816262586cd6ed9f349c97a6c285c4" + name = "github.com/jmespath/go-jmespath" + packages = ["."] + pruneopts = "UT" + revision = "0b12d6b5" + +[[projects]] + digest = "1:ecd9aa82687cf31d1585d4ac61d0ba180e42e8a6182b85bd785fcca8dfeefc1b" + name = "github.com/joho/godotenv" + packages = ["."] + pruneopts = "UT" + revision = "23d116af351c84513e1946b527c88823e476be13" + version = "v1.3.0" + +[[projects]] + branch = "master" + digest = "1:7616dd8d9ddca4d4d8aa0e3793f66015c8c8bf9a3f2387be6be59347f43a75c0" + name = "github.com/logrusorgru/aurora" + packages = ["."] + pruneopts = "UT" + revision = "d694e6f975a9109e2b063829d563a7c153c4b53c" + +[[projects]] + digest = "1:ac5cb21cbe4f095b6e5f1ae5102a85dfd598d39b5ad0d64df3d41ee046586f30" + name = "go.opencensus.io" + packages = [ + ".", + "internal", + "internal/tagencoding", + "plugin/ochttp", + "plugin/ochttp/propagation/b3", + "stats", + "stats/internal", + "stats/view", + "tag", + "trace", + "trace/internal", + "trace/propagation", + "trace/tracestate", + ] + pruneopts = "UT" + revision = "79993219becaa7e29e3b60cb67f5b8e82dee11d6" + version = "v0.17.0" + +[[projects]] + branch = "master" + digest = "1:1ae047ded1ddcbe0eca8b0772e3ff2c10e354db4c42c65b96d0386883e63904d" + name = "golang.org/x/net" + packages = [ + "context", + "context/ctxhttp", + "http/httpguts", + "http2", + "http2/hpack", + "idna", + "internal/timeseries", + "trace", + ] + pruneopts = "UT" + revision = "4dfa2610cdf3b287375bbba5b8f2a14d3b01d8de" + +[[projects]] + branch = "master" + digest = "1:f645667d687fc8bf228865a2c5455824ef05bad08841e673673ef2bb89ac5b90" + name = "golang.org/x/oauth2" + packages = [ + ".", + "google", + "internal", + "jws", + "jwt", + ] + pruneopts = "UT" + revision = "d2e6202438beef2727060aa7cabdd924d92ebfd9" + +[[projects]] + branch = "master" + digest = "1:d40e62a7b8e72fdc18e22ceddd3e6025e1d96f228b9a286abf3d3bd96d7d7a51" + name = "golang.org/x/sys" + packages = ["unix"] + pruneopts = "UT" + revision = "c2ed4eda69e7f62900806e4cd6e45f0429f859fa" + +[[projects]] + digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18" + name = "golang.org/x/text" + packages = [ + "collate", + "collate/build", + "internal/colltab", + "internal/gen", + "internal/tag", + "internal/triegen", + "internal/ucd", + "language", + "secure/bidirule", + "transform", + "unicode/bidi", + "unicode/cldr", + "unicode/norm", + "unicode/rangetable", + ] + pruneopts = "UT" + revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" + version = "v0.3.0" + +[[projects]] + branch = "master" + digest = "1:77e161fa978e667f956c28c800dde3cd5cf6f183b2e2b392b5b3ccb266d6d474" + name = "google.golang.org/api" + packages = [ + "gensupport", + "googleapi", + "googleapi/internal/uritemplates", + "googleapi/transport", + "internal", + "iterator", + "option", + "storage/v1", + "transport/http", + ] + pruneopts = "UT" + revision = "76f31e4d17c2f1190154e8fab72d772db9a59a5d" + +[[projects]] + digest = "1:193950893ea275f89ed92e5da11ed8fa1436872f755a9ea5d4afa83dc9d9c3a8" + name = "google.golang.org/appengine" + packages = [ + ".", + "internal", + "internal/app_identity", + "internal/base", + "internal/datastore", + "internal/log", + "internal/modules", + "internal/remote_api", + "internal/urlfetch", + "urlfetch", + ] + pruneopts = "UT" + revision = "ae0ab99deb4dc413a2b4bd6c8bdd0eb67f1e4d06" + version = "v1.2.0" + +[[projects]] + branch = "master" + digest = "1:8fae81abb5d7dbeee199c4fff5bbd130b832d126f0cbd99b0a6fdf3f95a8890c" + name = "google.golang.org/genproto" + packages = [ + "googleapis/api/annotations", + "googleapis/iam/v1", + "googleapis/rpc/code", + "googleapis/rpc/status", + ] + pruneopts = "UT" + revision = "0e822944c569bf5c9afd034adaa56208bd2906ac" + +[[projects]] + digest = "1:ab8e92d746fb5c4c18846b0879842ac8e53b3d352449423d0924a11f1020ae1b" + name = "google.golang.org/grpc" + packages = [ + ".", + "balancer", + "balancer/base", + "balancer/roundrobin", + "codes", + "connectivity", + "credentials", + "encoding", + "encoding/proto", + "grpclog", + "internal", + "internal/backoff", + "internal/channelz", + "internal/envconfig", + "internal/grpcrand", + "internal/transport", + "keepalive", + "metadata", + "naming", + "peer", + "resolver", + "resolver/dns", + "resolver/passthrough", + "stats", + "status", + "tap", + ] + pruneopts = "UT" + revision = "8dea3dc473e90c8179e519d91302d0597c0ca1d1" + version = "v1.15.0" + +[[projects]] + digest = "1:342378ac4dcb378a5448dd723f0784ae519383532f5e70ade24132c4c8693202" + name = "gopkg.in/yaml.v2" + packages = ["."] + pruneopts = "UT" + revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183" + version = "v2.2.1" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + input-imports = [ + "cloud.google.com/go/storage", + "github.com/BurntSushi/toml", + "github.com/Praqma/helmsman/aws", + "github.com/Praqma/helmsman/gcs", + "github.com/aws/aws-sdk-go/aws", + "github.com/aws/aws-sdk-go/aws/session", + "github.com/aws/aws-sdk-go/service/s3", + "github.com/aws/aws-sdk-go/service/s3/s3manager", + "github.com/hashicorp/go-version", + "github.com/imdario/mergo", + "github.com/joho/godotenv", + "github.com/logrusorgru/aurora", + "golang.org/x/net/context", + "gopkg.in/yaml.v2", + ] + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml new file mode 100644 index 00000000..f8f2fc7c --- /dev/null +++ b/Gopkg.toml @@ -0,0 +1,70 @@ +# Gopkg.toml example +# +# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" +# +# [prune] +# non-go = false +# go-tests = true +# unused-packages = true + + +[[constraint]] + name = "cloud.google.com/go" + version = "0.28.0" + +[[constraint]] + name = "github.com/BurntSushi/toml" + version = "0.3.1" + +[[constraint]] + name = "github.com/Praqma/helmsman" + version = "1.6.1" + +[[constraint]] + name = "github.com/aws/aws-sdk-go" + version = "1.15.43" + +[[constraint]] + name = "github.com/hashicorp/go-version" + version = "1.0.0" + +[[constraint]] + name = "github.com/imdario/mergo" + version = "0.3.6" + +[[constraint]] + name = "github.com/joho/godotenv" + version = "1.3.0" + +[[constraint]] + branch = "master" + name = "github.com/logrusorgru/aurora" + +[[constraint]] + branch = "master" + name = "golang.org/x/net" + +[[constraint]] + name = "gopkg.in/yaml.v2" + version = "2.2.1" + +[prune] + go-tests = true + unused-packages = true From a2c6d9554550ddec96db2b2e986e8d5382473836 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Fri, 28 Sep 2018 13:28:46 +0200 Subject: [PATCH 0267/1127] bumping version --- README.md | 9 ++++++--- release-notes.md | 10 ++++++---- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 763975ee..787f702d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ --- -version: v1.6.1 +version: v1.6.2 --- ![helmsman-logo](docs/images/helmsman.png) @@ -26,6 +26,9 @@ To plan and execute the plan: To show debugging details: ``` $ helmsman --debug --apply -f example.toml ``` +To run a dry-run: +``` $ helmsman --debug --dry-run -f example.toml ``` + # Features - **Built for CD**: Helmsman can be used as a docker image or a binary. @@ -46,9 +49,9 @@ To show debugging details: Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. ``` # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v1.6.1/helmsman_1.6.1_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.6.2/helmsman_1.6.2_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v1.6.1/helmsman_1.6.1_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.6.2/helmsman_1.6.2_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/release-notes.md b/release-notes.md index df6fc6a7..f2e20735 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,8 +1,10 @@ -# v1.6.1 +# v1.6.2 > If you are already using an older version of Helmsman than v1.4.0-rc, please read the changes below carefully and follow the upgrade guide [here](docs/migrating_to_v1.4.0-rc.md) -- Fixing cluster-wide access problem when checking for untracked releases in restricted clusters. Issue #83. -- Fixing not including releases from existing Tillers in helm state when using useTiller. -- Adding `--no-fancy` option to disable colored output. +- Fixing wrong absolute paths for values files when using `valuesFiles`. Issue #93 +- Adding `--destroy` option. Issue #88 +- Supporting setString in the desired state spec. Issue #84 +- Fixing adding helm repos which contain special characters. Issue #94 (thanks to @epierotto) + From e5aa632fa25497c8542212c3ee04cf4cdf7c073b Mon Sep 17 00:00:00 2001 From: Bob Henkel Date: Sun, 30 Sep 2018 10:22:09 -0500 Subject: [PATCH 0268/1127] Minor spelling fix --- docs/how_to/use_the_priority_key.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how_to/use_the_priority_key.md b/docs/how_to/use_the_priority_key.md index a32d848e..eeb70d88 100644 --- a/docs/how_to/use_the_priority_key.md +++ b/docs/how_to/use_the_priority_key.md @@ -8,7 +8,7 @@ The `priority` flag in Apps definition allows you to define the order at which a Priority is an optional flag and has a default value of 0 (zero). If set, it can only use a negative value. The lower the value, the higher the priority. -If you want your apps te de deleted in the reverse order as they where created, you can also use the optional `Settings` flag `reverseDelete`, to acheive this, set it to `true` +If you want your apps to be deleted in the reverse order as they where created, you can also use the optional `Settings` flag `reverseDelete`, to acheive this, set it to `true` ## Example From e68d53f9ae90912b109328ad347058dd4916f88a Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Mon, 1 Oct 2018 13:31:09 +0200 Subject: [PATCH 0269/1127] adding working directory in circleci config. #96 --- .circleci/config.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 616949db..929f0399 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,6 +4,7 @@ version: 2 jobs: build: + working_directory: "/go/src/helmsman" docker: - image: praqma/helmsman-test steps: @@ -15,6 +16,7 @@ jobs: make build test: + working_directory: "/go/src/helmsman" docker: - image: praqma/helmsman-test steps: @@ -25,6 +27,7 @@ jobs: echo "running tests ..." make test release: + working_directory: "/go/src/helmsman" docker: - image: praqma/helmsman-test steps: From abbcfd68accd581537cfbddcae4baf8e6cc898ab Mon Sep 17 00:00:00 2001 From: Robert James Hernandez Date: Mon, 1 Oct 2018 22:23:25 -0700 Subject: [PATCH 0270/1127] Adding prereqs for helmsman --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 787f702d..15d7c9a3 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,13 @@ To run a dry-run: ## From binary +Please make sure the following are installed prior to using `helmsman`: + +- [kubectl](https://github.com/kubernetes/kubectl) +- [helm](https://github.com/helm/helm) +- [helm-diff](https://github.com/databus23/helm-diff) (`helmsman` >= 1.6.0) + + Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. ``` # on Linux From 3ddf1d36bfdaf7e7dab7cdf287bd1e20a5bf7c3d Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Mon, 8 Oct 2018 20:39:41 +0200 Subject: [PATCH 0271/1127] fixing Tiller tls certs not being pulled before Tiller is deployed. --- helm_helpers.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/helm_helpers.go b/helm_helpers.go index 76a4f805..1abc4b53 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -366,11 +366,6 @@ func initHelm() (bool, string) { } for k, ns := range s.Namespaces { - if ns.InstallTiller && k != "kube-system" { - if ok, err := deployTiller(k, ns.TillerServiceAccount, defaultSA); !ok { - return false, err - } - } if tillerTLSEnabled(k) { downloadFile(s.Namespaces[k].TillerCert, k+"-tiller.cert") downloadFile(s.Namespaces[k].TillerKey, k+"-tiller.key") @@ -379,6 +374,11 @@ func initHelm() (bool, string) { downloadFile(s.Namespaces[k].ClientCert, k+"-client.cert") downloadFile(s.Namespaces[k].ClientKey, k+"-client.key") } + if ns.InstallTiller && k != "kube-system" { + if ok, err := deployTiller(k, ns.TillerServiceAccount, defaultSA); !ok { + return false, err + } + } } return true, "" From e50ab0f7c607ecf676babbbcaef20d1debb1cd9b Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Mon, 8 Oct 2018 20:59:44 +0200 Subject: [PATCH 0272/1127] bumping helm version in dockerfile to v2.10.0 --- dockerfile/dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile index 08ad79a3..4e39206e 100644 --- a/dockerfile/dockerfile +++ b/dockerfile/dockerfile @@ -20,7 +20,7 @@ RUN cd helmsman \ FROM alpine:3.7 RUN apk add --update --no-cache ca-certificates git -ARG HELM_VERSION=v2.8.1 +ARG HELM_VERSION=v2.10.0 ARG KUBE_VERSION="v1.11.3" RUN apk --no-cache update \ From 3246b86d747f6108d2ffb8d32c95a67adb743d48 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Fri, 28 Sep 2018 17:15:43 +0100 Subject: [PATCH 0273/1127] updating the makefile to use dep to ensure the necessary dependencies --- .circleci/config.yml | 1 + Makefile | 61 ++++++++++++++++++++++++++++++++++--------- test_files/dockerfile | 3 ++- 3 files changed, 52 insertions(+), 13 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 929f0399..7d8bb285 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -13,6 +13,7 @@ jobs: name: Build helmsman command: | echo "building ..." + make tools make build test: diff --git a/Makefile b/Makefile index e03b6a1a..711f1e25 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,33 @@ .DEFAULT_GOAL := help -PKGS := $(shell go list ./... | grep -v /vendor/) -TAG = $(shell git describe --always --tags --abbrev=0 HEAD) -LAST = $(shell git describe --always --tags --abbrev=0 HEAD^) -BODY = "`git log ${LAST}..HEAD --oneline --decorate` `printf '\n\#\#\# [Build Info](${BUILD_URL})'`" -DATE = $(shell date +'%d%m%y') +PKGS := $(shell go list ./... | grep -v /vendor/) +TAG := $(shell git describe --always --tags --abbrev=0 HEAD) +LAST := $(shell git describe --always --tags --abbrev=0 HEAD^) +BODY := "`git log ${LAST}..HEAD --oneline --decorate` `printf '\n\#\#\# [Build Info](${BUILD_URL})'`" +DATE := $(shell date +'%d%m%y') + +# Ensure we have an unambiguous GOPATH. +GOPATH := $(shell go env GOPATH) + +ifneq "$(or $(findstring :,$(GOPATH)),$(findstring ;,$(GOPATH)))" "" + $(error GOPATHs with multiple entries are not supported) +endif + +GOPATH := $(realpath $(GOPATH)) +ifeq ($(strip $(GOPATH)),) + $(error GOPATH is not set and could not be automatically determined) +endif + +SRCDIR := $(GOPATH)/src/ + +ifeq ($(filter $(GOPATH)%,$(CURDIR)),) + GOPATH := $(shell mktemp -d "/tmp/dep.XXXXXXXX") + SRCDIR := $(GOPATH)/src/ + SHELL := if test -d $(SRCDIR)helmsman/; then cd $(SRCDIR)helmsman/; fi; sh +endif + +# $(info SHELL set to "$(SHELL)") +# $(info GOPATH set to $(GOPATH)) ifneq ($(OS),Windows_NT) # Before we start test that we have the mandatory executables available @@ -32,29 +55,43 @@ dependencies: ## Ensure all the necessary dependencies @go get -t -d -v ./... .PHONY: dependencies -build: dependencies ## Build the package +$(SRCDIR): + @mkdir -p $(SRCDIR) + @ln -s $(CURDIR) $(SRCDIR) + +dep: $(SRCDIR) ## Ensure vendors with dep + @dep ensure +.PHONY: dep + +dep-update: $(SRCDIR) ## Ensure vendors with dep + @dep ensure --update +.PHONY: dep-update + +build: dep ## Build the package @go build -ldflags '-X main.version="${TAG}-${DATE}" -extldflags "-static"' generate: @go generate #${PKGS} .PHONY: generate -check: +check: dep + @dep check @go vet #${PKGS} .PHONY: check -test: dependencies ## Run unit tests - @go test -v -cover -p=1 -args -f example.toml #${PKGS} +test: dep ## Run unit tests + @go test -v -cover -p=1 -args -f example.toml .PHONY: test -cross: dependencies ## Create binaries for all OSs +cross: dep ## Create binaries for all OSs @env CGO_ENABLED=0 gox -os '!freebsd !netbsd' -arch '!arm' -output "dist/{{.Dir}}_{{.OS}}_{{.Arch}}" -ldflags '-X main.Version=${TAG}-${DATE}' .PHONY: cross -release: dependencies ## Generate a new release - goreleaser --release-notes release-notes.md +release: dep ## Generate a new release + @goreleaser --release-notes release-notes.md tools: ## Get extra tools used by this makefile + @go get -u github.com/golang/dep/cmd/dep @go get -u github.com/mitchellh/gox @go get -u github.com/goreleaser/goreleaser .PHONY: tools diff --git a/test_files/dockerfile b/test_files/dockerfile index 4b6a0216..cb1475dd 100644 --- a/test_files/dockerfile +++ b/test_files/dockerfile @@ -24,4 +24,5 @@ USER helmsman RUN mkdir -p ~/.helm/plugins \ && helm plugin install https://github.com/databus23/helm-diff -RUN go get github.com/goreleaser/goreleaser +RUN go get github.com/goreleaser/goreleaser && \ + go get github.com/golang/dep/cmd/dep From 4a59be6e1985ca69fae839e47883f0ac359ddc5b Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sat, 29 Sep 2018 01:24:03 +0100 Subject: [PATCH 0274/1127] no need for make tools --- .circleci/config.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7d8bb285..929f0399 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -13,7 +13,6 @@ jobs: name: Build helmsman command: | echo "building ..." - make tools make build test: From bb17a5f857ed24a62604944b8636b1eb5d544e83 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sat, 29 Sep 2018 09:48:36 +0100 Subject: [PATCH 0275/1127] not so fancy --- Makefile | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index 711f1e25..da71dfd6 100644 --- a/Makefile +++ b/Makefile @@ -23,12 +23,8 @@ SRCDIR := $(GOPATH)/src/ ifeq ($(filter $(GOPATH)%,$(CURDIR)),) GOPATH := $(shell mktemp -d "/tmp/dep.XXXXXXXX") SRCDIR := $(GOPATH)/src/ - SHELL := if test -d $(SRCDIR)helmsman/; then cd $(SRCDIR)helmsman/; fi; sh endif -# $(info SHELL set to "$(SHELL)") -# $(info GOPATH set to $(GOPATH)) - ifneq ($(OS),Windows_NT) # Before we start test that we have the mandatory executables available EXECUTABLES = go @@ -60,35 +56,42 @@ $(SRCDIR): @ln -s $(CURDIR) $(SRCDIR) dep: $(SRCDIR) ## Ensure vendors with dep - @dep ensure + @cd $(SRCDIR)helmsman && \ + dep ensure .PHONY: dep dep-update: $(SRCDIR) ## Ensure vendors with dep - @dep ensure --update + @cd $(SRCDIR)helmsman && \ + dep ensure --update .PHONY: dep-update build: dep ## Build the package - @go build -ldflags '-X main.version="${TAG}-${DATE}" -extldflags "-static"' + @cd $(SRCDIR)helmsman && \ + go build -ldflags '-X main.version="${TAG}-${DATE}" -extldflags "-static"' generate: @go generate #${PKGS} .PHONY: generate -check: dep - @dep check - @go vet #${PKGS} +check: $(SRCDIR) + @cd $(SRCDIR)helmsman && \ + dep check && \ + go vet #${PKGS} .PHONY: check test: dep ## Run unit tests - @go test -v -cover -p=1 -args -f example.toml + @cd $(SRCDIR)helmsman && \ + go test -v -cover -p=1 -args -f example.toml .PHONY: test cross: dep ## Create binaries for all OSs - @env CGO_ENABLED=0 gox -os '!freebsd !netbsd' -arch '!arm' -output "dist/{{.Dir}}_{{.OS}}_{{.Arch}}" -ldflags '-X main.Version=${TAG}-${DATE}' + @cd $(SRCDIR)helmsman && \ + env CGO_ENABLED=0 gox -os '!freebsd !netbsd' -arch '!arm' -output "dist/{{.Dir}}_{{.OS}}_{{.Arch}}" -ldflags '-X main.Version=${TAG}-${DATE}' .PHONY: cross release: dep ## Generate a new release - @goreleaser --release-notes release-notes.md + @cd $(SRCDIR)helmsman && \ + goreleaser --release-notes release-notes.md tools: ## Get extra tools used by this makefile @go get -u github.com/golang/dep/cmd/dep From 852820ad92f2fd7a9966cb2e1fa9757545f7b15e Mon Sep 17 00:00:00 2001 From: Steve Ruble Date: Tue, 16 Oct 2018 08:38:54 -0400 Subject: [PATCH 0276/1127] Don't pass --wait to helm diff The diff plugin errors if you pass the --wait flag, and I don't think --wait could mean anything in this context, so I removed it from the invocation. I also updated the description of the command run by diffRelease so that it reflects what the command is doing. --- decision_maker.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index f5d9019a..c573b35a 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -225,8 +225,8 @@ func diffRelease(r *release) string { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm diff " + colorFlag + "upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + strconv.Quote(r.Version) + " " + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r)}, - Description: "upgrading release [ " + r.Name + " ] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", + Args: []string{"-c", "helm diff " + colorFlag + "upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + strconv.Quote(r.Version) + " " + getSetValues(r) + getSetStringValues(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r)}, + Description: "diffing release [ " + r.Name + " ] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } if exitCode, msg = cmd.exec(debug, verbose); exitCode != 0 { From fa12a777d7fcf82e8fd531d91fc37f49dba839e0 Mon Sep 17 00:00:00 2001 From: Chris Kleeschulte Date: Tue, 16 Oct 2018 15:21:27 -0400 Subject: [PATCH 0277/1127] Fixes chart version that are valid semantic version strings. --- helm_helpers.go | 11 +++++- helm_helpers_test.go | 94 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 helm_helpers_test.go diff --git a/helm_helpers.go b/helm_helpers.go index 1abc4b53..bcbbd2a4 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -3,6 +3,7 @@ package main import ( "encoding/json" "log" + "regexp" "strconv" "strings" "time" @@ -179,10 +180,16 @@ func getReleaseChartName(rs releaseState) string { // getReleaseChartVersion extracts and returns the Helm chart version from the chart info in a release state. // example: chart in release state is returns "jenkins-0.9.0" and this functions will extract "0.9.0" from it. +// It should also handle semver-valid pre-release/meta information, example: in: jenkins-0.9.0-1, out: 0.9.0-1 +// in the event of an error, an empty string is returned. func getReleaseChartVersion(rs releaseState) string { chart := rs.Chart - runes := []rune(chart) - return string(runes[strings.LastIndexByte(chart, '-')+1 : len(chart)]) + re := regexp.MustCompile("-([0-9]+\\.[0-9]+\\.[0-9]+.*)") + matches := re.FindStringSubmatch(chart) + if len(matches) > 1 { + return matches[1] + } + return "" } // getNSTLSFlags returns TLS flags for a given namespace if it's deployed with TLS diff --git a/helm_helpers_test.go b/helm_helpers_test.go new file mode 100644 index 00000000..80b19f92 --- /dev/null +++ b/helm_helpers_test.go @@ -0,0 +1,94 @@ +package main + +import ( + "testing" + "time" +) + +func Test_getReleaseChartVersion(t *testing.T) { + // version string = the first semver-valid string after the last hypen in the chart string. + + type args struct { + r releaseState + } + tests := []struct { + name string + args args + want string + }{ + { + name: "test case 1: there is a pre-release version", + args: args{ + r: releaseState{ + Revision: 0, + Updated: time.Now(), + Status: "", + Chart: "elasticsearch-1.3.0-1", + Namespace: "", + TillerNamespace: "", + }, + }, + want: "1.3.0-1", + }, { + name: "test case 2: normal case", + args: args{ + r: releaseState{ + Revision: 0, + Updated: time.Now(), + Status: "", + Chart: "elasticsearch-1.3.0", + Namespace: "", + TillerNamespace: "", + }, + }, + want: "1.3.0", + }, { + name: "test case 3: there is a hypen in the name", + args: args{ + r: releaseState{ + Revision: 0, + Updated: time.Now(), + Status: "", + Chart: "elastic-search-1.3.0", + Namespace: "", + TillerNamespace: "", + }, + }, + want: "1.3.0", + }, { + name: "test case 4: there is meta information", + args: args{ + r: releaseState{ + Revision: 0, + Updated: time.Now(), + Status: "", + Chart: "elastic-search-1.3.0+meta.info", + Namespace: "", + TillerNamespace: "", + }, + }, + want: "1.3.0+meta.info", + }, { + name: "test case 5: an invalid string", + args: args{ + r: releaseState{ + Revision: 0, + Updated: time.Now(), + Status: "", + Chart: "foo", + Namespace: "", + TillerNamespace: "", + }, + }, + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Log(tt.want) + if got := getReleaseChartVersion(tt.args.r); got != tt.want { + t.Errorf("getReleaseChartName() = %v, want %v", got, tt.want) + } + }) + } +} From ad3d615ff8edd195e6eb4a2591f6e4e4f842ceb8 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Mon, 22 Oct 2018 11:04:46 +0200 Subject: [PATCH 0278/1127] adding --show-diff option. #106 --- decision_maker.go | 6 ++++-- init.go | 1 + main.go | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index c573b35a..f188a588 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -196,7 +196,7 @@ func inspectUpgradeScenario(r *release, rs releaseState) { } else { if diff := diffRelease(r); diff != "" { upgradeRelease(r) - logDecision("DECISION: release [ "+r.Name+" ] is desired to be enabled and is currently enabled. "+ + logDecision("DECISION: release [ "+r.Name+" ] is currently enabled and have some changed parameters. "+ "I will upgrade it!", r.Priority) } else { logDecision("DECISION: release [ "+r.Name+" ] is desired to be enabled and is currently enabled. "+ @@ -232,7 +232,9 @@ func diffRelease(r *release) string { if exitCode, msg = cmd.exec(debug, verbose); exitCode != 0 { logError("Command returned with exit code: " + string(exitCode) + ". And error message: " + msg) } else { - fmt.Println(msg) + if verbose || showDiff { + fmt.Println(msg) + } } return msg diff --git a/init.go b/init.go index d697ed22..fc29dff6 100644 --- a/init.go +++ b/init.go @@ -53,6 +53,7 @@ func init() { flag.BoolVar(&skipValidation, "skip-validation", false, "skip desired state validation") flag.BoolVar(&applyLabels, "apply-labels", false, "apply Helmsman labels to Helm state for all defined apps.") flag.BoolVar(&keepUntrackedReleases, "keep-untracked-releases", false, "keep releases that are managed by Helmsman and are no longer tracked in your desired state.") + flag.BoolVar(&showDiff, "show-diff", false, "show helm diff results. Can expose sensitive information.") flag.Usage = printUsage flag.Parse() diff --git a/main.go b/main.go index a53eacc5..fefc60a0 100644 --- a/main.go +++ b/main.go @@ -39,6 +39,7 @@ var pwd string var relativeDir string var dryRun bool var destroy bool +var showDiff bool func main() { // set the kubecontext to be used Or create it if it does not exist From be99e31386a0551e643afc16a7a297b101a638c4 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Mon, 22 Oct 2018 11:05:33 +0200 Subject: [PATCH 0279/1127] updating example.toml --- example.toml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/example.toml b/example.toml index 2055bc5d..23886836 100644 --- a/example.toml +++ b/example.toml @@ -21,8 +21,8 @@ # password = "$K8S_PASSWORD" # the name of an environment variable containing the k8s password # clusterURI = "$K8S_URI" # the name of an environment variable containing the cluster API # #clusterURI = "https://192.168.99.100:8443" # equivalent to the above - serviceAccount = "tiller" # k8s serviceaccount. If it does not exist, it will be created. - storageBackend = "secret" # default is configMap + # serviceAccount = "tiller" # k8s serviceaccount. If it does not exist, it will be created. + # storageBackend = "secret" # default is configMap # slackWebhook = "$slack" # or "your slack webhook url" # reverseDelete = false # reverse the priorities on delete @@ -74,9 +74,9 @@ protected = true priority= -3 wait = true - [apps.jenkins.setString] # values to override values from values.yaml with values from env vars or directly entered-- useful for passing secrets to charts - AdminPassword="$JENKINS_PASSWORD" # $JENKINS_PASSWORD must exist in the environment - MyLongIntVar="1234567890" + # [apps.jenkins.setString] # values to override values from values.yaml with values from env vars or directly entered-- useful for passing secrets to charts + # AdminPassword="$JENKINS_PASSWORD" # $JENKINS_PASSWORD must exist in the environment + # MyLongIntVar="1234567890" [apps.jenkins.set] AdminUser="admin" From 95bb2f3a19c4c973789109a2c93c0c0a33587ef6 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Mon, 22 Oct 2018 11:07:00 +0200 Subject: [PATCH 0280/1127] bumping version --- README.md | 6 +++--- main.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 15d7c9a3..29b09ccb 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ --- -version: v1.6.2 +version: v1.6.3 --- ![helmsman-logo](docs/images/helmsman.png) @@ -56,9 +56,9 @@ Please make sure the following are installed prior to using `helmsman`: Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. ``` # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v1.6.2/helmsman_1.6.2_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.6.3/helmsman_1.6.3_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v1.6.2/helmsman_1.6.2_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.6.3/helmsman_1.6.3_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/main.go b/main.go index fefc60a0..600e2b75 100644 --- a/main.go +++ b/main.go @@ -32,7 +32,7 @@ var checkCleanup bool var skipValidation bool var applyLabels bool var keepUntrackedReleases bool -var appVersion = "v1.6.2" +var appVersion = "v1.6.3" var helmVersion string var kubectlVersion string var pwd string From c94c48cee9730c15a09209a1ba7055b476e7a0df Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Mon, 22 Oct 2018 11:13:34 +0200 Subject: [PATCH 0281/1127] updating release notes for v1.6.3 [ci skip] --- release-notes.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/release-notes.md b/release-notes.md index f2e20735..cde14024 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,10 +1,6 @@ -# v1.6.2 +# v1.6.3 > If you are already using an older version of Helmsman than v1.4.0-rc, please read the changes below carefully and follow the upgrade guide [here](docs/migrating_to_v1.4.0-rc.md) -- Fixing wrong absolute paths for values files when using `valuesFiles`. Issue #93 -- Adding `--destroy` option. Issue #88 -- Supporting setString in the desired state spec. Issue #84 -- Fixing adding helm repos which contain special characters. Issue #94 (thanks to @epierotto) - +- Minor bug fixes and improvements. #104 #106 #85 From 4795ad4d8240c73214c14ab94eedb803e201f79c Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Mon, 22 Oct 2018 16:46:53 +0200 Subject: [PATCH 0282/1127] fixing secretsFile names #105 --- decision_maker.go | 22 +++++++++++----------- main.go | 6 +++--- release.go | 20 ++++++++++---------- release_test.go | 2 +- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index f188a588..dea264ab 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -316,26 +316,26 @@ func getValuesFiles(r *release) string { fileList = append(fileList, tempValuesFiles...) } - if r.SecretFile != "" { + if r.SecretsFile != "" { if !helmPluginExists("secrets") { logError("ERROR: helm secrets plugin is not installed/configured correctly. Aborting!") } - if ok := decryptSecret(r.SecretFile); !ok { - logError("Failed to decrypt secret file" + r.SecretFile) + if ok := decryptSecret(r.SecretsFile); !ok { + logError("Failed to decrypt secret file" + r.SecretsFile) } - fileList = append(fileList, pwd+"/"+relativeDir+"/"+r.SecretFile+".dec") - } else if len(r.SecretFiles) > 0 { + fileList = append(fileList, pwd+"/"+relativeDir+"/"+r.SecretsFile+".dec") + } else if len(r.SecretsFiles) > 0 { if !helmPluginExists("secrets") { logError("ERROR: helm secrets plugin is not installed/configured correctly. Aborting!") } - for i := 0; i < len(r.SecretFiles); i++ { - r.SecretFiles[i] = pwd + "/" + relativeDir + "/" + r.SecretFiles[i] - if ok := decryptSecret(r.SecretFiles[i]); !ok { - logError("Failed to decrypt secret file" + r.SecretFiles[i]) + for i := 0; i < len(r.SecretsFiles); i++ { + r.SecretsFiles[i] = pwd + "/" + relativeDir + "/" + r.SecretsFiles[i] + if ok := decryptSecret(r.SecretsFiles[i]); !ok { + logError("Failed to decrypt secret file" + r.SecretsFiles[i]) } - r.SecretFiles[i] = r.SecretFiles[i] + ".dec" + r.SecretsFiles[i] = r.SecretsFiles[i] + ".dec" } - fileList = append(fileList, r.SecretFiles...) + fileList = append(fileList, r.SecretsFiles...) } if len(fileList) > 0 { diff --git a/main.go b/main.go index 600e2b75..b04ea584 100644 --- a/main.go +++ b/main.go @@ -142,10 +142,10 @@ func cleanup() { } for _, app := range s.Apps { - if _, err := os.Stat(app.SecretFile + ".dec"); err == nil { - deleteFile(app.SecretFile + ".dec") + if _, err := os.Stat(app.SecretsFile + ".dec"); err == nil { + deleteFile(app.SecretsFile + ".dec") } - for _, secret := range app.SecretFiles { + for _, secret := range app.SecretsFiles { if _, err := os.Stat(secret + ".dec"); err == nil { deleteFile(secret + ".dec") } diff --git a/release.go b/release.go index f8803226..2c4ba948 100644 --- a/release.go +++ b/release.go @@ -19,8 +19,8 @@ type release struct { Version string `yaml:"version"` ValuesFile string `yaml:"valuesFile"` ValuesFiles []string `yaml:"valuesFiles"` - SecretFile string `yaml:"secretFile"` - SecretFiles []string `yaml:"secretFiles"` + SecretsFile string `yaml:"secretsFile"` + SecretsFiles []string `yaml:"secretsFiles"` Purge bool `yaml:"purge"` Test bool `yaml:"test"` Protected bool `yaml:"protected"` @@ -76,19 +76,19 @@ func validateRelease(appLabel string, r *release, names map[string]map[string]bo } else if len(r.ValuesFiles) > 0 { for _, filePath := range r.ValuesFiles { if _, pathErr := os.Stat(pwd + "/" + relativeDir + "/" + filePath); !isOfType(filePath, ".yaml") || pathErr != nil { - return false, "the value for valueFile '" + filePath + "' must be a valid relative (from your first dsf file) file path for a yaml file." + return false, "the value for valuesFile '" + filePath + "' must be a valid relative (from your first dsf file) file path for a yaml file." } } } - if r.SecretFile != "" && (!isOfType(r.SecretFile, ".yaml") || err != nil) { - return false, "secretFile must be a valid relative (from your first dsf file) file path for a yaml file, Or can be left empty." - } else if r.SecretFile != "" && len(r.SecretFiles) > 0 { - return false, "secretFile and secretFiles should not be used together." - } else if len(r.SecretFiles) > 0 { - for _, filePath := range r.SecretFiles { + if r.SecretsFile != "" && (!isOfType(r.SecretsFile, ".yaml") || err != nil) { + return false, "secretsFile must be a valid relative (from your first dsf file) file path for a yaml file, Or can be left empty." + } else if r.SecretsFile != "" && len(r.SecretsFiles) > 0 { + return false, "secretsFile and secretsFiles should not be used together." + } else if len(r.SecretsFiles) > 0 { + for _, filePath := range r.SecretsFiles { if _, pathErr := os.Stat(pwd + "/" + relativeDir + "/" + filePath); !isOfType(filePath, ".yaml") || pathErr != nil { - return false, "the value for valueFile '" + filePath + "' must be a valid relative (from your first dsf file) file path for a yaml file." + return false, "the value for secretsFile '" + filePath + "' must be a valid relative (from your first dsf file) file path for a yaml file." } } } diff --git a/release_test.go b/release_test.go index ee761871..99770a2e 100644 --- a/release_test.go +++ b/release_test.go @@ -241,7 +241,7 @@ func Test_validateRelease(t *testing.T) { s: st, }, want: false, - want1: "the value for valueFile 'xyz.yaml' must be a valid relative (from your first dsf file) file path for a yaml file.", + want1: "the value for valuesFile 'xyz.yaml' must be a valid relative (from your first dsf file) file path for a yaml file.", }, { name: "test case 13", args: args{ From b87e009ca626083595640d0c6fd998a0da7ce069 Mon Sep 17 00:00:00 2001 From: SteveRuble Date: Wed, 24 Oct 2018 09:46:01 -0400 Subject: [PATCH 0283/1127] resolve values and secrets relative to dsf --- decision_maker.go | 12 ++++-------- decision_maker_test.go | 6 +++--- init.go | 11 ----------- main.go | 2 -- release.go | 17 +++++++++-------- release_test.go | 8 ++++---- utils.go | 27 +++++++++++++++++++++++++-- 7 files changed, 45 insertions(+), 38 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index dea264ab..4924d442 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -307,13 +307,9 @@ func getValuesFiles(r *release) string { var fileList []string if r.ValuesFile != "" { - fileList = append(fileList, pwd+"/"+relativeDir+"/"+r.ValuesFile) + fileList = append(fileList,r.ValuesFile) } else if len(r.ValuesFiles) > 0 { - tempValuesFiles := make([]string, len(r.ValuesFiles)) - for i := 0; i < len(r.ValuesFiles); i++ { - tempValuesFiles[i] = pwd + "/" + relativeDir + "/" + r.ValuesFiles[i] - } - fileList = append(fileList, tempValuesFiles...) + fileList = append(fileList, r.ValuesFiles...) } if r.SecretsFile != "" { @@ -323,13 +319,13 @@ func getValuesFiles(r *release) string { if ok := decryptSecret(r.SecretsFile); !ok { logError("Failed to decrypt secret file" + r.SecretsFile) } - fileList = append(fileList, pwd+"/"+relativeDir+"/"+r.SecretsFile+".dec") + fileList = append(fileList, r.SecretsFile+".dec") } else if len(r.SecretsFiles) > 0 { if !helmPluginExists("secrets") { logError("ERROR: helm secrets plugin is not installed/configured correctly. Aborting!") } for i := 0; i < len(r.SecretsFiles); i++ { - r.SecretsFiles[i] = pwd + "/" + relativeDir + "/" + r.SecretsFiles[i] + r.SecretsFiles[i] = r.SecretsFiles[i] if ok := decryptSecret(r.SecretsFiles[i]); !ok { logError("Failed to decrypt secret file" + r.SecretsFiles[i]) } diff --git a/decision_maker_test.go b/decision_maker_test.go index 0f2bb723..03aa6292 100644 --- a/decision_maker_test.go +++ b/decision_maker_test.go @@ -29,7 +29,7 @@ func Test_getValuesFiles(t *testing.T) { }, //s: st, }, - want: " -f " + pwd + "/" + relativeDir + "/test_files/values.yaml", + want: " -f test_files/values.yaml", }, { name: "test case 2", @@ -47,7 +47,7 @@ func Test_getValuesFiles(t *testing.T) { }, //s: st, }, - want: " -f " + pwd + "/" + relativeDir + "/test_files/values.yaml", + want: " -f test_files/values.yaml", }, { name: "test case 1", @@ -65,7 +65,7 @@ func Test_getValuesFiles(t *testing.T) { }, //s: st, }, - want: " -f " + pwd + "/" + relativeDir + "/test_files/values.yaml -f " + pwd + "/" + relativeDir + "/test_files/values2.yaml", + want: " -f test_files/values.yaml -f test_files/values2.yaml", }, } for _, tt := range tests { diff --git a/init.go b/init.go index fc29dff6..483de17a 100644 --- a/init.go +++ b/init.go @@ -89,17 +89,6 @@ func init() { os.Exit(0) } - // initializing pwd and relative directory for DSF(s) and values files - pwd, _ = os.Getwd() - lastSlashIndex := -1 - if len(files) > 0 { - lastSlashIndex = strings.LastIndex(files[0], "/") - } - relativeDir = "." - if lastSlashIndex != -1 { - relativeDir = files[0][:lastSlashIndex] - } - if v { fmt.Println("Helmsman version: " + appVersion) os.Exit(0) diff --git a/main.go b/main.go index b04ea584..81b59c32 100644 --- a/main.go +++ b/main.go @@ -35,8 +35,6 @@ var keepUntrackedReleases bool var appVersion = "v1.6.3" var helmVersion string var kubectlVersion string -var pwd string -var relativeDir string var dryRun bool var destroy bool var showDiff bool diff --git a/release.go b/release.go index 2c4ba948..9b4396e6 100644 --- a/release.go +++ b/release.go @@ -36,7 +36,6 @@ type release struct { // validateRelease validates if a release inside a desired state meets the specifications or not. // check the full specification @ https://github.com/Praqma/helmsman/docs/desired_state_spec.md func validateRelease(appLabel string, r *release, names map[string]map[string]bool, s state) (bool, string) { - _, err := os.Stat(pwd + "/" + relativeDir + "/" + r.ValuesFile) if r.Name == "" { r.Name = appLabel } @@ -69,26 +68,28 @@ func validateRelease(appLabel string, r *release, names map[string]map[string]bo } r.Version = substituteEnv(r.Version) + _, err := os.Stat(r.ValuesFile) if r.ValuesFile != "" && (!isOfType(r.ValuesFile, ".yaml") || err != nil) { - return false, "valuesFile must be a valid relative (from your first dsf file) file path for a yaml file, Or can be left empty." + return false, fmt.Sprintf("valuesFile must be a valid relative (from dsf file) file path for a yaml file, or can be left empty (provided path resolved to %q).", r.ValuesFile) } else if r.ValuesFile != "" && len(r.ValuesFiles) > 0 { return false, "valuesFile and valuesFiles should not be used together." } else if len(r.ValuesFiles) > 0 { - for _, filePath := range r.ValuesFiles { - if _, pathErr := os.Stat(pwd + "/" + relativeDir + "/" + filePath); !isOfType(filePath, ".yaml") || pathErr != nil { - return false, "the value for valuesFile '" + filePath + "' must be a valid relative (from your first dsf file) file path for a yaml file." + for i, filePath := range r.ValuesFiles { + if _, pathErr := os.Stat(filePath); !isOfType(filePath, ".yaml") || pathErr != nil { + return false, fmt.Sprintf("valuesFiles must be valid relative (from dsf file) file paths for a yaml file; path at index %d provided path resolved to %q.", i, filePath) } } } + _, err = os.Stat(r.SecretsFile) if r.SecretsFile != "" && (!isOfType(r.SecretsFile, ".yaml") || err != nil) { - return false, "secretsFile must be a valid relative (from your first dsf file) file path for a yaml file, Or can be left empty." + return false, fmt.Sprintf("secretsFile must be a valid relative (from dsf file) file path for a yaml file, or can be left empty (provided path resolved to %q).", r.SecretsFile) } else if r.SecretsFile != "" && len(r.SecretsFiles) > 0 { return false, "secretsFile and secretsFiles should not be used together." } else if len(r.SecretsFiles) > 0 { for _, filePath := range r.SecretsFiles { - if _, pathErr := os.Stat(pwd + "/" + relativeDir + "/" + filePath); !isOfType(filePath, ".yaml") || pathErr != nil { - return false, "the value for secretsFile '" + filePath + "' must be a valid relative (from your first dsf file) file path for a yaml file." + if i, pathErr := os.Stat(filePath); !isOfType(filePath, ".yaml") || pathErr != nil { + return false, fmt.Sprintf("secretsFiles must be valid relative (from dsf file) file paths for a yaml file; path at index %d provided path resolved to %q.", i, filePath) } } } diff --git a/release_test.go b/release_test.go index 99770a2e..6399218d 100644 --- a/release_test.go +++ b/release_test.go @@ -60,7 +60,7 @@ func Test_validateRelease(t *testing.T) { s: st, }, want: false, - want1: "valuesFile must be a valid relative (from your first dsf file) file path for a yaml file, Or can be left empty.", + want1: "valuesFile must be a valid relative (from dsf file) file path for a yaml file, or can be left empty (provided path resolved to \"xyz.yaml\").", }, { name: "test case 3", args: args{ @@ -78,7 +78,7 @@ func Test_validateRelease(t *testing.T) { s: st, }, want: false, - want1: "valuesFile must be a valid relative (from your first dsf file) file path for a yaml file, Or can be left empty.", + want1: "valuesFile must be a valid relative (from dsf file) file path for a yaml file, or can be left empty (provided path resolved to \"test_files/values.xml\").", }, { name: "test case 4", args: args{ @@ -241,7 +241,7 @@ func Test_validateRelease(t *testing.T) { s: st, }, want: false, - want1: "the value for valuesFile 'xyz.yaml' must be a valid relative (from your first dsf file) file path for a yaml file.", + want1: "valuesFiles must be valid relative (from dsf file) file paths for a yaml file; path at index 0 provided path resolved to \"xyz.yaml\".", }, { name: "test case 13", args: args{ @@ -252,7 +252,7 @@ func Test_validateRelease(t *testing.T) { Enabled: true, Chart: "repo/chartX", Version: "1.0", - ValuesFiles: []string{"test_files/values.yaml", "test_files/values2.yaml"}, + ValuesFiles: []string{"./test_files/values.yaml", "./test_files/values2.yaml"}, Purge: true, Test: true, }, diff --git a/utils.go b/utils.go index 42af5dcb..cb7ec240 100644 --- a/utils.go +++ b/utils.go @@ -3,6 +3,7 @@ package main import ( "bytes" "fmt" + "gopkg.in/yaml.v2" "io" "io/ioutil" "log" @@ -14,8 +15,6 @@ import ( "strings" "time" - "gopkg.in/yaml.v2" - "github.com/BurntSushi/toml" "github.com/Praqma/helmsman/aws" "github.com/Praqma/helmsman/gcs" @@ -42,6 +41,8 @@ func fromTOML(file string, s *state) (bool, string) { return false, err.Error() } + resolvePaths(file, s) + return true, "INFO: Parsed TOML [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps." } @@ -81,6 +82,9 @@ func fromYAML(file string, s *state) (bool, string) { if err = yaml.Unmarshal(yamlFile, s); err != nil { return false, err.Error() } + + resolvePaths(file, s) + return true, "INFO: Parsed YAML [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps." } @@ -130,6 +134,25 @@ func toFile(file string, s *state) { } } +func resolvePaths(relativeToFile string, s *state) { + dir := filepath.Dir(relativeToFile) + for k, v := range s.Apps { + if v.ValuesFile != "" { + v.ValuesFile, _ = filepath.Abs(filepath.Join(dir, v.ValuesFile)) + } + if v.SecretsFile != "" { + v.SecretsFile, _ = filepath.Abs(filepath.Join(dir, v.SecretsFile)) + } + for i, f := range v.ValuesFiles { + v.ValuesFiles[i], _ = filepath.Abs(filepath.Join(dir, f)) + } + for i, f := range v.SecretsFiles { + v.SecretsFiles[i], _ = filepath.Abs(filepath.Join(dir, f)) + } + s.Apps[k] = v + } +} + // isOfType checks if the file extension of a filename/path is the same as "filetype". // isisOfType is case insensitive. filetype should contain the "." e.g. ".yaml" func isOfType(filename string, filetype string) bool { From 0e7b8e41e4824914638bde8489fd5907ae169513 Mon Sep 17 00:00:00 2001 From: SteveRuble Date: Wed, 24 Oct 2018 09:53:20 -0400 Subject: [PATCH 0284/1127] ensure relative path is forgiving --- release_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release_test.go b/release_test.go index 6399218d..f477e3d1 100644 --- a/release_test.go +++ b/release_test.go @@ -252,7 +252,7 @@ func Test_validateRelease(t *testing.T) { Enabled: true, Chart: "repo/chartX", Version: "1.0", - ValuesFiles: []string{"./test_files/values.yaml", "./test_files/values2.yaml"}, + ValuesFiles: []string{"./test_files/values.yaml", "test_files/values2.yaml"}, Purge: true, Test: true, }, From 7218d0cc0856461c80c214aec95611aec76120f7 Mon Sep 17 00:00:00 2001 From: SteveRuble Date: Wed, 24 Oct 2018 10:05:54 -0400 Subject: [PATCH 0285/1127] do not delete untracked releases on dry-run --- helm_helpers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helm_helpers.go b/helm_helpers.go index bcbbd2a4..fdebd7aa 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -438,7 +438,7 @@ func deleteUntrackedRelease(release string, tillerNamespace string) { } cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm delete --purge " + release + " --tiller-namespace " + tillerNamespace + tls}, + Args: []string{"-c", "helm delete --purge " + release + " --tiller-namespace " + tillerNamespace + tls + getDryRunFlags()}, Description: "deleting untracked release [ " + release + " ] from Tiller in namespace [[ " + tillerNamespace + " ]]", } From 7398a0f48d1a6abbc6b39ce614ff33cbfbd65967 Mon Sep 17 00:00:00 2001 From: Bekir Dogan Date: Thu, 25 Oct 2018 12:27:06 +0100 Subject: [PATCH 0286/1127] allow labeling namespaces --- docs/desired_state_specification.md | 5 +++++ example.toml | 2 ++ example.yaml | 2 ++ kube_helpers.go | 21 +++++++++++++++++-- release_test.go | 2 +- state.go | 19 +++++++++-------- state_test.go | 32 ++++++++++++++--------------- 7 files changed, 55 insertions(+), 28 deletions(-) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 1d415000..c4e031ac 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -131,6 +131,7 @@ Options: - **installTiller**: defines if Tiller should be deployed in this namespace or not. Default is false. Any chart desired to be deployed into a namespace with a Tiller deployed, will be deployed using that Tiller and not the one in kube-system unless you use the `TillerNamespace` option (see the [Apps](#apps) section below) to use another Tiller. > By default Tiller will be deployed into `kube-system` even if you don't define kube-system in the namespaces section. To prevent deploying Tiller into `kube-system, add kube-system in your namespaces section and set its installTiller to false. -**useTiller**: defines that you would like to use an existing Tiller from that namespace. Can't be set together with `installTiller` +- **labels** : defines labels to be added to the namespace, doesn't remove existing labels but updates them if the label key exists with any other different value. You can define any key/value pairs. Default is empty. - **tillerServiceAccount**: defines what service account to use when deploying Tiller. If this is not set, the following options are considered: @@ -166,6 +167,8 @@ tillerCert = "secrets/tiller.cert.pem" tillerKey = "$TILLER_KEY" # where TILLER_KEY=secrets/tiller.key.pem clientCert = "gs://mybucket/mydir/helm.cert.pem" clientKey = "s3://mybucket/mydir/helm.key.pem" +[namespaces.production.labels] +env = "prod" ``` ```yaml @@ -186,6 +189,8 @@ namespaces: tillerKey: "$TILLER_KEY" # where TILLER_KEY=secrets/tiller.key.pem clientCert: "gs://mybucket/mydir/helm.cert.pem" clientKey: "s3://mybucket/mydir/helm.key.pem" + labels: + env: "prod" ``` ## Helm Repos diff --git a/example.toml b/example.toml index 23886836..86a1c5f8 100644 --- a/example.toml +++ b/example.toml @@ -43,6 +43,8 @@ # tillerKey = "secrets/tiller.key.pem" # or GCS bucket gs://mybucket/tiller.key # clientCert = "secrets/helm.cert.pem" # clientKey = "secrets/helm.key.pem" + [namespaces.staging.labels] + env = "staging" # define any private/public helm charts repos you would like to get charts from diff --git a/example.yaml b/example.yaml index afe85013..f5b30fde 100644 --- a/example.yaml +++ b/example.yaml @@ -37,6 +37,8 @@ namespaces: #tillerKey: "secrets/tiller.key.pem" # or GCS bucket gs://mybucket/tiller.key #clientCert: "secrets/helm.cert.pem" #clientKey: "secrets/helm.key.pem" + labels: + env: "staging" # define any private/public helm charts repos you would like to get charts from diff --git a/kube_helpers.go b/kube_helpers.go index d47aebc5..5028217d 100644 --- a/kube_helpers.go +++ b/kube_helpers.go @@ -55,8 +55,9 @@ func createRBAC(sa string, namespace string, sharedTiller bool) (bool, string) { // If --ns-override flag is used, it only creates the provided namespace in that flag func addNamespaces(namespaces map[string]namespace) { if nsOverride == "" { - for ns := range namespaces { - createNamespace(ns) + for nsName, ns := range namespaces { + createNamespace(nsName) + labelNamespace(nsName, ns.Labels) } } else { createNamespace(nsOverride) @@ -86,6 +87,22 @@ func createNamespace(ns string) { } } +// labelNamespace labels a namespace with provided labels +func labelNamespace(ns string, labels map[string]string) { + for k, v := range labels { + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "kubectl label --overwrite namespace/" + ns + " " + k + "=" + v}, + Description: "labeling namespace " + ns, + } + + if exitCode, _ := cmd.exec(debug, verbose); exitCode != 0 { + log.Println("WARN: I could not label namespace [ " + ns + " with " + k + "=" + v + + " ]. It already exists. I am skipping this.") + } + } +} + // createContext creates a context -connecting to a k8s cluster- in kubectl config. // It returns true if successful, false otherwise func createContext() (bool, string) { diff --git a/release_test.go b/release_test.go index 99770a2e..c1ed518a 100644 --- a/release_test.go +++ b/release_test.go @@ -10,7 +10,7 @@ func Test_validateRelease(t *testing.T) { Metadata: make(map[string]string), Certificates: make(map[string]string), Settings: (config{}), - Namespaces: map[string]namespace{"namespace": namespace{false, false, false, "", "", "", "", "", ""}}, + Namespaces: map[string]namespace{"namespace": namespace{false, false, false, "", "", "", "", "", "", make(map[string]string)}}, HelmRepos: make(map[string]string), Apps: make(map[string]*release), } diff --git a/state.go b/state.go index f2afc60c..b63855a9 100644 --- a/state.go +++ b/state.go @@ -10,15 +10,16 @@ import ( // namespace type represents the fields of a namespace type namespace struct { - Protected bool `yaml:"protected"` - InstallTiller bool `yaml:"installTiller"` - UseTiller bool `yaml:"useTiller"` - TillerServiceAccount string `yaml:"tillerServiceAccount"` - CaCert string `yaml:"caCert"` - TillerCert string `yaml:"tillerCert"` - TillerKey string `yaml:"tillerKey"` - ClientCert string `yaml:"clientCert"` - ClientKey string `yaml:"clientKey"` + Protected bool `yaml:"protected"` + InstallTiller bool `yaml:"installTiller"` + UseTiller bool `yaml:"useTiller"` + TillerServiceAccount string `yaml:"tillerServiceAccount"` + CaCert string `yaml:"caCert"` + TillerCert string `yaml:"tillerCert"` + TillerKey string `yaml:"tillerKey"` + ClientCert string `yaml:"clientCert"` + ClientKey string `yaml:"clientKey"` + Labels map[string]string `yaml:"labels"` } // config type represents the settings fields diff --git a/state_test.go b/state_test.go index b90016bb..47795e80 100644 --- a/state_test.go +++ b/state_test.go @@ -34,7 +34,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", ""}, + "staging": namespace{false, false, false, "", "", "", "", "", "", make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -53,7 +53,7 @@ func Test_state_validate(t *testing.T) { }, Settings: config{}, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", ""}, + "staging": namespace{false, false, false, "", "", "", "", "", "", make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -77,7 +77,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", ""}, + "staging": namespace{false, false, false, "", "", "", "", "", "", make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -98,7 +98,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", ""}, + "staging": namespace{false, false, false, "", "", "", "", "", "", make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -122,7 +122,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", ""}, + "staging": namespace{false, false, false, "", "", "", "", "", "", make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -146,7 +146,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "$URI", // unset env }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", ""}, + "staging": namespace{false, false, false, "", "", "", "", "", "", make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -170,7 +170,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "$SET_URI", // set env var }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", ""}, + "staging": namespace{false, false, false, "", "", "", "", "", "", make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -194,7 +194,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https//192.168.99.100:8443", // invalid url }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", ""}, + "staging": namespace{false, false, false, "", "", "", "", "", "", make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -217,7 +217,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", ""}, + "staging": namespace{false, false, false, "", "", "", "", "", "", make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -238,7 +238,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", ""}, + "staging": namespace{false, false, false, "", "", "", "", "", "", make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -262,7 +262,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", ""}, + "staging": namespace{false, false, false, "", "", "", "", "", "", make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -280,7 +280,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", ""}, + "staging": namespace{false, false, false, "", "", "", "", "", "", make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -330,7 +330,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", ""}, + "staging": namespace{false, false, false, "", "", "", "", "", "", make(map[string]string)}, }, HelmRepos: nil, Apps: make(map[string]*release), @@ -345,7 +345,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", ""}, + "staging": namespace{false, false, false, "", "", "", "", "", "", make(map[string]string)}, }, HelmRepos: map[string]string{}, Apps: make(map[string]*release), @@ -360,7 +360,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", ""}, + "staging": namespace{false, false, false, "", "", "", "", "", "", make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -378,7 +378,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", ""}, + "staging": namespace{false, false, false, "", "", "", "", "", "", make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", From e9ef2dc07aa30148311b938dd682654c5875312d Mon Sep 17 00:00:00 2001 From: Chris Kleeschulte Date: Thu, 25 Oct 2018 13:21:41 -0400 Subject: [PATCH 0287/1127] Fixed one more place where we need to single quote a version. --- decision_maker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/decision_maker.go b/decision_maker.go index dea264ab..7310e432 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -244,7 +244,7 @@ func diffRelease(r *release) string { func upgradeRelease(r *release) { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + r.Version + " --force " + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r) + getDryRunFlags()}, + Args: []string{"-c", "helm upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + strconv.Quote(r.Version) + " --force " + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r) + getDryRunFlags()}, Description: "upgrading release [ " + r.Name + " ] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } From 22f6c34ba398c9097b82be8b397fdbcc9077e242 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Fri, 12 Oct 2018 11:25:51 +0100 Subject: [PATCH 0288/1127] adding flag to disable namespace creation --- init.go | 1 + main.go | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/init.go b/init.go index 483de17a..daf2bad3 100644 --- a/init.go +++ b/init.go @@ -49,6 +49,7 @@ func init() { flag.BoolVar(&noBanner, "no-banner", false, "don't show the banner") flag.BoolVar(&noColors, "no-color", false, "don't use colors") flag.BoolVar(&noFancy, "no-fancy", false, "don't display the banner and don't use colors") + flag.BoolVar(&noNs, "no-ns", false, "don't create namespaces") flag.StringVar(&nsOverride, "ns-override", "", "override defined namespaces with this one") flag.BoolVar(&skipValidation, "skip-validation", false, "skip desired state validation") flag.BoolVar(&applyLabels, "apply-labels", false, "apply Helmsman labels to Helm state for all defined apps.") diff --git a/main.go b/main.go index 81b59c32..da3f5237 100644 --- a/main.go +++ b/main.go @@ -27,6 +27,7 @@ var verbose bool var noBanner bool var noColors bool var noFancy bool +var noNs bool var nsOverride string var checkCleanup bool var skipValidation bool @@ -49,7 +50,9 @@ func main() { } // add/validate namespaces - addNamespaces(s.Namespaces) + if !noNs { + addNamespaces(s.Namespaces) + } if r, msg := initHelm(); !r { logError(msg) From aeb46c4f6056ae966571fd860001d5693cc5661f Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Mon, 22 Oct 2018 11:57:46 +0100 Subject: [PATCH 0289/1127] no changes without apply --- main.go | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/main.go b/main.go index da3f5237..cd57b915 100644 --- a/main.go +++ b/main.go @@ -49,29 +49,31 @@ func main() { checkCleanup = true } - // add/validate namespaces - if !noNs { - addNamespaces(s.Namespaces) - } + if apply { + // add/validate namespaces + if !noNs { + addNamespaces(s.Namespaces) + } - if r, msg := initHelm(); !r { - logError(msg) - } + if r, msg := initHelm(); !r { + logError(msg) + } - // check if helm Tiller is ready - for k, ns := range s.Namespaces { - if ns.InstallTiller || ns.UseTiller { - waitForTiller(k) + // check if helm Tiller is ready + for k, ns := range s.Namespaces { + if ns.InstallTiller || ns.UseTiller { + waitForTiller(k) + } } - } - if _, ok := s.Namespaces["kube-system"]; !ok { - waitForTiller("kube-system") - } + if _, ok := s.Namespaces["kube-system"]; !ok { + waitForTiller("kube-system") + } - // add repos -- fails if they are not valid - if r, msg := addHelmRepos(s.HelmRepos); !r { - logError(msg) + // add repos -- fails if they are not valid + if r, msg := addHelmRepos(s.HelmRepos); !r { + logError(msg) + } } if !skipValidation { From 7b78fac8762df77b861155a5aa8ea37bbea65f93 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 23 Oct 2018 15:37:22 +0100 Subject: [PATCH 0290/1127] repos are added client side, it should be fine to add them without apply --- main.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/main.go b/main.go index cd57b915..fb573fed 100644 --- a/main.go +++ b/main.go @@ -69,11 +69,11 @@ func main() { if _, ok := s.Namespaces["kube-system"]; !ok { waitForTiller("kube-system") } + } - // add repos -- fails if they are not valid - if r, msg := addHelmRepos(s.HelmRepos); !r { - logError(msg) - } + // add repos -- fails if they are not valid + if r, msg := addHelmRepos(s.HelmRepos); !r { + logError(msg) } if !skipValidation { From 04760b18e60aebaf4da08bc0732a2969c124bef1 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Fri, 26 Oct 2018 09:47:01 +0100 Subject: [PATCH 0291/1127] don't make any changes without --apply --- helm_helpers.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/helm_helpers.go b/helm_helpers.go index fdebd7aa..173ff182 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -88,6 +88,11 @@ func getTillerReleases(tillerNS string) tillerReleases { exitCode, result := cmd.exec(debug, verbose) if exitCode != 0 { + if !apply { + log.Println("INFO: " + strings.Replace(result, "Error: ", "", 1)) + return tillerReleases{} + } + logError("ERROR: failed to list all releases in namespace [ " + tillerNS + " ]: " + result) } var out tillerReleases From 86e2db9e03a0a2e645126d2f9cd4d34c3fd1347c Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Sat, 27 Oct 2018 13:47:02 +0200 Subject: [PATCH 0292/1127] adding openssh to the docker image. fixes #113 --- dockerfile/dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile index 4e39206e..be6d048b 100644 --- a/dockerfile/dockerfile +++ b/dockerfile/dockerfile @@ -18,7 +18,7 @@ RUN cd helmsman \ # The image to keep FROM alpine:3.7 -RUN apk add --update --no-cache ca-certificates git +RUN apk add --update --no-cache ca-certificates git openssh ARG HELM_VERSION=v2.10.0 ARG KUBE_VERSION="v1.11.3" From fdb86d9680dc4f2b9fa76d470bab8fd64cedc26e Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Sat, 27 Oct 2018 14:07:57 +0200 Subject: [PATCH 0293/1127] bumping version to v1.7.0 --- README.md | 12 +++++++----- docs/desired_state_specification.md | 2 +- main.go | 2 +- release-notes.md | 8 ++++++-- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 29b09ccb..332a6d4b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ --- -version: v1.6.3 +version: v1.7.0 --- ![helmsman-logo](docs/images/helmsman.png) @@ -46,19 +46,21 @@ To run a dry-run: ## From binary -Please make sure the following are installed prior to using `helmsman`: +Please make sure the following are installed prior to using `helmsman` as a binary (the docker image contains all of them): - [kubectl](https://github.com/kubernetes/kubectl) -- [helm](https://github.com/helm/helm) +- [helm](https://github.com/helm/helm) (for `helmsman` >= 1.6.0, use helm >= 2.10.0. this is due to a dependency bug #87 ) - [helm-diff](https://github.com/databus23/helm-diff) (`helmsman` >= 1.6.0) +If you use private helm repos, you will need either `helm-gcs` or `helm-s3` plugin. See the [docs](https://github.com/Praqma/helmsman/blob/master/docs/how_to/use_private_helm_charts.md) for details. + Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. ``` # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v1.6.3/helmsman_1.6.3_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.7.0/helmsman_1.7.0_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v1.6.3/helmsman_1.6.3_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.7.0/helmsman_1.7.0_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index c4e031ac..223aec64 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -1,5 +1,5 @@ --- -version: v1.6.2 +version: v1.7.0 --- # Helmsman desired state specification diff --git a/main.go b/main.go index fb573fed..4c28bd47 100644 --- a/main.go +++ b/main.go @@ -33,7 +33,7 @@ var checkCleanup bool var skipValidation bool var applyLabels bool var keepUntrackedReleases bool -var appVersion = "v1.6.3" +var appVersion = "v1.7.0" var helmVersion string var kubectlVersion string var dryRun bool diff --git a/release-notes.md b/release-notes.md index cde14024..1440b42d 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,6 +1,10 @@ -# v1.6.3 +# v1.7.0 > If you are already using an older version of Helmsman than v1.4.0-rc, please read the changes below carefully and follow the upgrade guide [here](docs/migrating_to_v1.4.0-rc.md) -- Minor bug fixes and improvements. #104 #106 #85 +- Resources (Tillers, namespaces, service accounts) will not be created without the `--apply` flag. Fixes #65 and #100. +- adding `--no-ns` option to prevent helmsman from creating namespaces. This allows users to create namespaces using their own custom charts. Fixes #71 and #100. +- Adding namespace labels. PR #111 +- Fixing a bug that deletes untracked releases when running with `--dry-run`. PR #110 +- Improving values and secrets file pahts resolution relative to dsf(s). PR #109 From fd27676897b71d1609f9fcc730c7029b7980ffd9 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Thu, 1 Nov 2018 15:51:12 +0100 Subject: [PATCH 0294/1127] updating docs for valuefile path resolution [ci skip] #115 --- docs/desired_state_specification.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 223aec64..f0a73c82 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -254,10 +254,10 @@ Options: - **description** : a release metadata for human readers. - **valuesFile** : a valid path to custom Helm values.yaml file. File extension must be `yaml`. Cannot be used with valuesFiles together. Leaving it empty uses the default chart values. - **valuesFiles** : array of valid paths to custom Helm values.yaml file. File extension must be `yaml`. Cannot be used with valuesFile together. Leaving it empty uses the default chart values. -> The values file(s) path is relative from the location of the (first) desired state file you pass in your Helmsman command. +> The values file(s) path is resolved when the DSF yaml/toml file is loaded, relative to the path that the dsf was loaded from. - **secretsFile** : a valid path to custom Helm secrets.yaml file. File extension must be `yaml`. Cannot be used with secretsFiles together. Leaving it empty uses the default chart secrets. - **secretsFiles** : array of valid paths to custom Helm secrets.yaml file. File extension must be `yaml`. Cannot be used with secretsFile together. Leaving it empty uses the default chart secrets. -> The secrets file(s) path is relative from the location of the (first) desired state file you pass in your Helmsman command. +> The secrets file(s) path is resolved when the DSF yaml/toml file is loaded, relative to the path that the dsf was loaded from. > To use the secrets files you must have the helm-secrets plugin - **purge** : defines whether to use the Helm purge flag when deleting the release. (true/false) - **test** : defines whether to run the chart tests whenever the release is installed. From c7d3262c8c5d1f82fa50e8a88d551ea1bbc70fae Mon Sep 17 00:00:00 2001 From: Danny Martini Date: Fri, 2 Nov 2018 15:30:12 +0100 Subject: [PATCH 0295/1127] delete untracked releases fix #117 --- kube_helpers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kube_helpers.go b/kube_helpers.go index 5028217d..cb9d869e 100644 --- a/kube_helpers.go +++ b/kube_helpers.go @@ -353,7 +353,7 @@ func getHelmsmanReleases() map[string]map[string]bool { continue } else { fields := strings.Fields(lines[i]) - if _, ok := releases[fields[0]]; !ok { + if _, ok := releases[ns]; !ok { releases[ns] = make(map[string]bool) } releases[ns][fields[0][0:strings.LastIndex(fields[0], ".v")]] = true From bf68931d60028c02802bab23edc255f951b30ab9 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sun, 4 Nov 2018 14:42:46 +0000 Subject: [PATCH 0296/1127] Override existing values when merging DSFs --- init.go | 22 +++++++++++++++++++++- release.go | 38 +++++++++++++++++++------------------- state.go | 6 +++--- utils.go | 4 ++-- 4 files changed, 45 insertions(+), 25 deletions(-) diff --git a/init.go b/init.go index daf2bad3..5ee6026c 100644 --- a/init.go +++ b/init.go @@ -134,11 +134,31 @@ func init() { logError(msg) } - if err := mergo.Merge(&s, &fileState); err != nil { + // Merge Apps that already existed in the state + for appName, app := range fileState.Apps { + if _, ok := s.Apps[appName]; ok { + if err := mergo.Merge(s.Apps[appName], app, mergo.WithAppendSlice, mergo.WithOverride); err != nil { + logError("Failed to merge " + appName + " from desired state file" + f) + } + } + } + + // Merge the remaining Apps + if err := mergo.Merge(&s.Apps, &fileState.Apps); err != nil { + logError("Failed to merge desired state file" + f) + } + // All the apps are already merged, make fileState.Apps empty to avoid conflicts in the final merge + fileState.Apps = make(map[string]*release) + + if err := mergo.Merge(&s, &fileState, mergo.WithAppendSlice, mergo.WithOverride); err != nil { logError("Failed to merge desired state file" + f) } } + if debug { + s.print() + } + if !skipValidation { // validate the desired state content if len(files) > 0 { diff --git a/release.go b/release.go index 9b4396e6..9f29f330 100644 --- a/release.go +++ b/release.go @@ -11,26 +11,26 @@ import ( // release type representing Helm releases which are described in the desired state type release struct { - Name string `yaml:"name"` - Description string `yaml:"description"` - Namespace string `yaml:"namespace"` - Enabled bool `yaml:"enabled"` - Chart string `yaml:"chart"` - Version string `yaml:"version"` - ValuesFile string `yaml:"valuesFile"` - ValuesFiles []string `yaml:"valuesFiles"` - SecretsFile string `yaml:"secretsFile"` - SecretsFiles []string `yaml:"secretsFiles"` - Purge bool `yaml:"purge"` - Test bool `yaml:"test"` - Protected bool `yaml:"protected"` - Wait bool `yaml:"wait"` - Priority int `yaml:"priority"` - TillerNamespace string `yaml:"tillerNamespace"` - Set map[string]string + Name string `yaml:"name"` + Description string `yaml:"description"` + Namespace string `yaml:"namespace"` + Enabled bool `yaml:"enabled"` + Chart string `yaml:"chart"` + Version string `yaml:"version"` + ValuesFile string `yaml:"valuesFile"` + ValuesFiles []string `yaml:"valuesFiles"` + SecretsFile string `yaml:"secretsFile"` + SecretsFiles []string `yaml:"secretsFiles"` + Purge bool `yaml:"purge"` + Test bool `yaml:"test"` + Protected bool `yaml:"protected"` + Wait bool `yaml:"wait"` + Priority int `yaml:"priority"` + TillerNamespace string `yaml:"tillerNamespace"` + Set map[string]string `yaml:"set"` SetString map[string]string `yaml:"setString"` NoHooks bool `yaml:"noHooks"` - Timeout int + Timeout int `yaml:"timeout"` } // validateRelease validates if a release inside a desired state meets the specifications or not. @@ -155,6 +155,6 @@ func (r release) print() { fmt.Println("\tno-hooks : ", r.NoHooks) fmt.Println("\ttimeout : ", r.Timeout) fmt.Println("\tvalues to override from env:") - printMap(r.Set) + printMap(r.Set, 2) fmt.Println("------------------- ") } diff --git a/state.go b/state.go index b63855a9..5fa00bef 100644 --- a/state.go +++ b/state.go @@ -212,10 +212,10 @@ func (s state) print() { fmt.Println("\nMetadata: ") fmt.Println("--------- ") - printMap(s.Metadata) + printMap(s.Metadata, 0) fmt.Println("\nCertificates: ") fmt.Println("--------- ") - printMap(s.Certificates) + printMap(s.Certificates, 0) fmt.Println("\nSettings: ") fmt.Println("--------- ") fmt.Printf("%+v\n", s.Settings) @@ -224,7 +224,7 @@ func (s state) print() { printNamespacesMap(s.Namespaces) fmt.Println("\nRepositories: ") fmt.Println("------------- ") - printMap(s.HelmRepos) + printMap(s.HelmRepos, 0) fmt.Println("\nApplications: ") fmt.Println("--------------- ") for _, r := range s.Apps { diff --git a/utils.go b/utils.go index cb7ec240..6fbae666 100644 --- a/utils.go +++ b/utils.go @@ -21,9 +21,9 @@ import ( ) // printMap prints to the console any map of string keys and values. -func printMap(m map[string]string) { +func printMap(m map[string]string, indent int) { for key, value := range m { - fmt.Println(key, " : ", value) + fmt.Println(strings.Repeat("\t", indent)+key, " : ", value) } } From a59af23c053a56fa1af70aeafad3ce45b0245044 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Onur=20Yalaz=C4=B1?= Date: Mon, 5 Nov 2018 17:31:45 +0000 Subject: [PATCH 0297/1127] Breaking: Expanding envs on the whole dfs file. This patch brings a global dfs file environment variable expansion. For escaping "$" characters one needs to use "$$". This change may impact some users with dfs files including "$" strings. --- decision_maker.go | 8 +++----- release.go | 2 +- state.go | 15 +++++---------- utils.go | 18 +++++++++++++++--- 4 files changed, 24 insertions(+), 19 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index d62cd1dd..85e08f0f 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -307,7 +307,7 @@ func getValuesFiles(r *release) string { var fileList []string if r.ValuesFile != "" { - fileList = append(fileList,r.ValuesFile) + fileList = append(fileList, r.ValuesFile) } else if len(r.ValuesFiles) > 0 { fileList = append(fileList, r.ValuesFiles...) } @@ -344,8 +344,7 @@ func getValuesFiles(r *release) string { func getSetValues(r *release) string { result := "" for k, v := range r.Set { - value := substituteEnv(v) - result = result + " --set " + k + "=\"" + strings.Replace(value, ",", "\\,", -1) + "\"" + result = result + " --set " + k + "=\"" + strings.Replace(v, ",", "\\,", -1) + "\"" } return result } @@ -354,8 +353,7 @@ func getSetValues(r *release) string { func getSetStringValues(r *release) string { result := "" for k, v := range r.SetString { - value := substituteEnv(v) - result = result + " --set-string " + k + "=\"" + strings.Replace(value, ",", "\\,", -1) + "\"" + result = result + " --set-string " + k + "=\"" + strings.Replace(v, ",", "\\,", -1) + "\"" } return result } diff --git a/release.go b/release.go index 9f29f330..cd9613f1 100644 --- a/release.go +++ b/release.go @@ -66,7 +66,7 @@ func validateRelease(appLabel string, r *release, names map[string]map[string]bo if r.Version == "" { return false, "version can't be empty." } - r.Version = substituteEnv(r.Version) + r.Version = r.Version _, err := os.Stat(r.ValuesFile) if r.ValuesFile != "" && (!isOfType(r.ValuesFile, ".yaml") || err != nil) { diff --git a/state.go b/state.go index 5fa00bef..d72b62da 100644 --- a/state.go +++ b/state.go @@ -56,7 +56,6 @@ func (s state) validate() (bool, string) { "kubeContext to use. Can't work without it. Sorry!" } else if s.Settings.ClusterURI != "" { - s.Settings.ClusterURI = substituteEnv(s.Settings.ClusterURI) if _, err := url.ParseRequestURI(s.Settings.ClusterURI); err != nil { return false, "ERROR: settings validation failed -- clusterURI must have a valid URL set in an env variable or passed directly. Either the env var is missing/empty or the URL is invalid." } @@ -64,9 +63,7 @@ func (s state) validate() (bool, string) { if s.Settings.Username == "" { return false, "ERROR: settings validation failed -- username must be provided if clusterURI is defined." } - if s.Settings.Password != "" { - s.Settings.Password = substituteEnv(s.Settings.Password) - } else { + if s.Settings.Password == "" { return false, "ERROR: settings validation failed -- password must be provided if clusterURI is defined." } @@ -77,7 +74,6 @@ func (s state) validate() (bool, string) { // slack webhook validation (if provided) if s.Settings.SlackWebhook != "" { - s.Settings.SlackWebhook = substituteEnv(s.Settings.SlackWebhook) if _, err := url.ParseRequestURI(s.Settings.SlackWebhook); err != nil { return false, "ERROR: settings validation failed -- slackWebhook must be a valid URL." } @@ -187,13 +183,12 @@ func (s state) validate() (bool, string) { // isValidCert checks if a certificate/key path/URI is valid func isValidCert(value string) (bool, string) { - tmp := substituteEnv(value) - _, err1 := url.ParseRequestURI(tmp) - _, err2 := os.Stat(tmp) - if err2 != nil && (err1 != nil || (!strings.HasPrefix(tmp, "s3://") && !strings.HasPrefix(tmp, "gs://"))) { + _, err1 := url.ParseRequestURI(value) + _, err2 := os.Stat(value) + if err2 != nil && (err1 != nil || (!strings.HasPrefix(value, "s3://") && !strings.HasPrefix(value, "gs://"))) { return false, "" } - return true, tmp + return true, value } // tillerTLSEnabled checks if Tiller is desired to be deployed with TLS enabled for a given namespace diff --git a/utils.go b/utils.go index 6fbae666..b561d831 100644 --- a/utils.go +++ b/utils.go @@ -37,7 +37,12 @@ func printNamespacesMap(m map[string]namespace) { // fromTOML reads a toml file and decodes it to a state type. // It uses the BurntSuchi TOML parser which throws an error if the TOML file is not valid. func fromTOML(file string, s *state) (bool, string) { - if _, err := toml.DecodeFile(file, s); err != nil { + rawTomlFile, err := ioutil.ReadFile(file) + if err != nil { + return false, err.Error() + } + tomlFile := substituteEnv(string(rawTomlFile)) + if _, err := toml.Decode(tomlFile, s); err != nil { return false, err.Error() } @@ -75,10 +80,11 @@ func toTOML(file string, s *state) { // fromYAML reads a yaml file and decodes it to a state type. // parser which throws an error if the YAML file is not valid. func fromYAML(file string, s *state) (bool, string) { - yamlFile, err := ioutil.ReadFile(file) + rawYamlFile, err := ioutil.ReadFile(file) if err != nil { return false, err.Error() } + yamlFile := []byte(substituteEnv(string(rawYamlFile))) if err = yaml.Unmarshal(yamlFile, s); err != nil { return false, err.Error() } @@ -175,12 +181,18 @@ func logVersions() { log.Println("VERBOSE: Helm client version: " + helmVersion) } +// add $$ escaping for $ strings +func expandEnv(s string) string { + os.Setenv("HELMSMAN_DOLLAR", "$") + return os.ExpandEnv(strings.Replace(s, "$$", "${HELMSMAN_DOLLAR}", -1)) +} + // substituteEnv checks if a string has an env variable (contains '$'), then it returns its value // if the env variable is empty or unset, an empty string is returned // if the string does not contain '$', it is returned as is. func substituteEnv(name string) string { if strings.Contains(name, "$") { - return os.ExpandEnv(name) + return expandEnv(name) } return name } From c6ae4ae833696ef17d1393c2187509bf948609c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Onur=20Yalaz=C4=B1?= Date: Tue, 6 Nov 2018 15:41:42 +0000 Subject: [PATCH 0298/1127] Added full dsf environment file expansion documentation. --- docs/desired_state_specification.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index f0a73c82..59702ff0 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -13,6 +13,9 @@ This document describes the specification for how to write your Helm charts desi - [Helm Repos](#helm-repos) -- defines the repos where you want to get Helm charts from. - [Apps](#apps) -- defines the applications/charts you want to manage in your cluster. + +> You can use environment variables in the desired state files. The environment variable name should start with "$", or encapsulated in "$(", ")". "$" characters can be escaped like "$$". + ## Metadata Optional : Yes. @@ -47,7 +50,6 @@ Options: - **caKey** : a valid S3/GCS bucket or local relative file path to a client key file. - **caClient**: a valid S3/GCS bucket or local relative file path to a client certificate file. -> You can use environment variables to pass the values of the options above. The environment variable name should start with $ > bucket format is: ://bucket-name/dir1/dir2/.../file.extension From 0619feb13c77f1d428f24bc8529dc404e5ad70bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Onur=20Yalaz=C4=B1?= Date: Tue, 6 Nov 2018 15:42:35 +0000 Subject: [PATCH 0299/1127] Added test cases for dsf environment var expansion --- example.toml | 4 +- example.yaml | 4 +- state_test.go | 46 ++++---------- utils_test.go | 171 +++++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 185 insertions(+), 40 deletions(-) diff --git a/example.toml b/example.toml index 86a1c5f8..533a2f30 100644 --- a/example.toml +++ b/example.toml @@ -1,7 +1,7 @@ # version: v1.6.2 # metadata -- add as many key/value pairs as you want [metadata] - org = "example.com" + org = "example.com/${ORG_PATH}/" maintainer = "k8s-admin (me@example.com)" description = "example Desired State File for demo purposes." @@ -19,7 +19,7 @@ kubeContext = "minikube" # will try connect to this context first, if it does not exist, it will be created using the details below # username = "admin" # password = "$K8S_PASSWORD" # the name of an environment variable containing the k8s password -# clusterURI = "$K8S_URI" # the name of an environment variable containing the cluster API + clusterURI = "${SET_URI}" # the name of an environment variable containing the cluster API # #clusterURI = "https://192.168.99.100:8443" # equivalent to the above # serviceAccount = "tiller" # k8s serviceaccount. If it does not exist, it will be created. # storageBackend = "secret" # default is configMap diff --git a/example.yaml b/example.yaml index f5b30fde..aa76d870 100644 --- a/example.yaml +++ b/example.yaml @@ -1,7 +1,7 @@ # version: v1.5.0 # metadata -- add as many key/value pairs as you want metadata: - org: "example.com" + org: "example.com/$ORG_PATH/" maintainer: "k8s-admin (me@example.com)" description: "example Desired State File for demo purposes." @@ -17,7 +17,7 @@ settings: kubeContext: "minikube" # will try connect to this context first, if it does not exist, it will be created using the details below #username: "admin" #password: "$K8S_PASSWORD" # the name of an environment variable containing the k8s password - #clusterURI: "$K8S_URI" # the name of an environment variable containing the cluster API + clusterURI: "$SET_URI" # the name of an environment variable containing the cluster API #clusterURI: "https://192.168.99.100:8443" # equivalent to the above #serviceAccount: "foo" # k8s serviceaccount must be already defined, validation error will be thrown otherwise storageBackend: "secret" # default is configMap diff --git a/state_test.go b/state_test.go index 47795e80..a1f0c246 100644 --- a/state_test.go +++ b/state_test.go @@ -156,31 +156,7 @@ func Test_state_validate(t *testing.T) { }, want: false, }, { - name: "test case 7 -- settings/clusterURI-env-var", - fields: fields{ - Metadata: make(map[string]string), - Certificates: map[string]string{ - "caCrt": "s3://some-bucket/12345.crt", - "caKey": "s3://some-bucket/12345.key", - }, - Settings: config{ - KubeContext: "minikube", - Username: "admin", - Password: "K8S_PASSWORD", - ClusterURI: "$SET_URI", // set env var - }, - Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", make(map[string]string)}, - }, - HelmRepos: map[string]string{ - "stable": "https://kubernetes-charts.storage.googleapis.com", - "myrepo": "s3://my-repo/charts", - }, - Apps: make(map[string]*release), - }, - want: true, - }, { - name: "test case 8 -- settings/clusterURI-invalid", + name: "test case 7 -- settings/clusterURI-invalid", fields: fields{ Metadata: make(map[string]string), Certificates: map[string]string{ @@ -204,7 +180,7 @@ func Test_state_validate(t *testing.T) { }, want: false, }, { - name: "test case 9 -- certifications/missing key", + name: "test case 8 -- certifications/missing key", fields: fields{ Metadata: make(map[string]string), Certificates: map[string]string{ @@ -227,7 +203,7 @@ func Test_state_validate(t *testing.T) { }, want: false, }, { - name: "test case 10 -- certifications/nil_value", + name: "test case 9 -- certifications/nil_value", fields: fields{ Metadata: make(map[string]string), Certificates: nil, @@ -248,7 +224,7 @@ func Test_state_validate(t *testing.T) { }, want: false, }, { - name: "test case 11 -- certifications/invalid_s3", + name: "test case 10 -- certifications/invalid_s3", fields: fields{ Metadata: make(map[string]string), Certificates: map[string]string{ @@ -272,7 +248,7 @@ func Test_state_validate(t *testing.T) { }, want: false, }, { - name: "test case 12 -- certifications/nil_value_pass", + name: "test case 11 -- certifications/nil_value_pass", fields: fields{ Metadata: make(map[string]string), Certificates: nil, @@ -290,7 +266,7 @@ func Test_state_validate(t *testing.T) { }, want: true, }, { - name: "test case 13 -- namespaces/nil_value", + name: "test case 12 -- namespaces/nil_value", fields: fields{ Metadata: make(map[string]string), Certificates: nil, @@ -306,7 +282,7 @@ func Test_state_validate(t *testing.T) { }, want: false, }, { - name: "test case 14 -- namespaces/empty", + name: "test case 13 -- namespaces/empty", fields: fields{ Metadata: make(map[string]string), Certificates: nil, @@ -322,7 +298,7 @@ func Test_state_validate(t *testing.T) { }, want: false, }, { - name: "test case 15 -- helmRepos/nil_value", + name: "test case 14 -- helmRepos/nil_value", fields: fields{ Metadata: make(map[string]string), Certificates: nil, @@ -337,7 +313,7 @@ func Test_state_validate(t *testing.T) { }, want: false, }, { - name: "test case 16 -- helmRepos/empty", + name: "test case 15 -- helmRepos/empty", fields: fields{ Metadata: make(map[string]string), Certificates: nil, @@ -352,7 +328,7 @@ func Test_state_validate(t *testing.T) { }, want: false, }, { - name: "test case 17 -- helmRepos/empty_repo_value", + name: "test case 16 -- helmRepos/empty_repo_value", fields: fields{ Metadata: make(map[string]string), Certificates: nil, @@ -370,7 +346,7 @@ func Test_state_validate(t *testing.T) { }, want: false, }, { - name: "test case 18 -- helmRepos/invalid_repo_value", + name: "test case 17 -- helmRepos/invalid_repo_value", fields: fields{ Metadata: make(map[string]string), Certificates: nil, diff --git a/utils_test.go b/utils_test.go index 390df6b2..18117253 100644 --- a/utils_test.go +++ b/utils_test.go @@ -1,6 +1,10 @@ package main -import "testing" +import ( + "os" + "reflect" + "testing" +) // func Test_printMap(t *testing.T) { // type args struct { @@ -53,6 +57,88 @@ func Test_fromTOML(t *testing.T) { }) } } +func Test_fromTOML_Expand(t *testing.T) { + type args struct { + file string + s *state + } + tests := []struct { + name string + args args + section string + field string + want string + }{ + { + name: "test case 1 -- valid TOML expand ClusterURI", + args: args{ + file: "example.toml", + s: new(state), + }, + section: "Settings", + field: "ClusterURI", + want: "https://192.168.99.100:8443", + }, + { + name: "test case 2 -- valid TOML expand org", + args: args{ + file: "example.toml", + s: new(state), + }, + section: "Metadata", + field: "org", + want: "example.com/sample/", + }, + } + os.Setenv("SET_URI", "https://192.168.99.100:8443") + os.Setenv("ORG_PATH", "sample") + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err, msg := fromTOML(tt.args.file, tt.args.s) + if !err { + t.Errorf("fromToml(), got: %v", msg) + } + + tomlVal := reflect.ValueOf(tt.args.s).Elem() + tomlType := reflect.TypeOf(tt.args.s) + + if tomlType.Kind() != reflect.Struct { + + section := tomlVal.FieldByName(tt.section) + sectionType := reflect.TypeOf(section) + + if section.IsValid() && section.Kind() == reflect.Struct { + field := section.FieldByName(tt.field) + if sectionType.Kind() == reflect.String { + if field.String() != tt.want { + t.Errorf("fromToml().section.field = %v, got: %v", tt.want, field.String()) + } + } + } else if section.IsValid() && section.Kind() == reflect.Map { + found := false + value := "" + for _, key := range section.MapKeys() { + if key.String() == tt.field { + found = true + value = section.MapIndex(key).String() + } + } + if !found { + t.Errorf("fromToml().section.field = '%v' not found", tt.field) + } else if value != tt.want { + t.Errorf("fromToml().section.field = %v, got: %v", tt.want, value) + } + + } else { + t.Errorf("fromToml().section = struct, got: %v", sectionType.Kind()) + } + + } else { + t.Errorf("fromToml() = struct, got: %v", tomlType.Kind()) + } + }) + } +} // func Test_toTOML(t *testing.T) { // type args struct { @@ -107,6 +193,89 @@ func Test_fromYAML(t *testing.T) { } } +func Test_fromYAML_Expand(t *testing.T) { + type args struct { + file string + s *state + } + tests := []struct { + name string + args args + section string + field string + want string + }{ + { + name: "test case 1 -- valid YAML expand ClusterURI", + args: args{ + file: "example.yaml", + s: new(state), + }, + section: "Settings", + field: "ClusterURI", + want: "https://192.168.99.100:8443", + }, + { + name: "test case 2 -- valid YAML expand org", + args: args{ + file: "example.yaml", + s: new(state), + }, + section: "Metadata", + field: "org", + want: "example.com/sample/", + }, + } + os.Setenv("SET_URI", "https://192.168.99.100:8443") + os.Setenv("ORG_PATH", "sample") + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err, msg := fromYAML(tt.args.file, tt.args.s) + if !err { + t.Errorf("fromYaml(), got: %v", msg) + } + + yamlVal := reflect.ValueOf(tt.args.s).Elem() + yamlType := reflect.TypeOf(tt.args.s) + + if yamlType.Kind() != reflect.Struct { + + section := yamlVal.FieldByName(tt.section) + sectionType := reflect.TypeOf(section) + + if section.IsValid() && section.Kind() == reflect.Struct { + field := section.FieldByName(tt.field) + if sectionType.Kind() == reflect.String { + if field.String() != tt.want { + t.Errorf("fromYaml().section.field = %v, got: %v", tt.want, field.String()) + } + } + } else if section.IsValid() && section.Kind() == reflect.Map { + found := false + value := "" + for _, key := range section.MapKeys() { + if key.String() == tt.field { + found = true + value = section.MapIndex(key).String() + } + } + if !found { + t.Errorf("fromYaml().section.field = '%v' not found", tt.field) + } else if value != tt.want { + t.Errorf("fromYaml().section.field = %v, got: %v", tt.want, value) + } + + } else { + t.Errorf("fromYaml().section = struct, got: %v", sectionType.Kind()) + } + + } else { + t.Errorf("fromYaml() = struct, got: %v", yamlType.Kind()) + } + }) + } +} + // func Test_toYAML(t *testing.T) { // type args struct { // file string From 3eddf02ea4f4823377c946bcbb19d0a00d78bbba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Onur=20Yalaz=C4=B1?= Date: Tue, 6 Nov 2018 15:54:44 +0000 Subject: [PATCH 0300/1127] removed expandEnv method --- utils.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/utils.go b/utils.go index b561d831..5306272f 100644 --- a/utils.go +++ b/utils.go @@ -181,18 +181,14 @@ func logVersions() { log.Println("VERBOSE: Helm client version: " + helmVersion) } -// add $$ escaping for $ strings -func expandEnv(s string) string { - os.Setenv("HELMSMAN_DOLLAR", "$") - return os.ExpandEnv(strings.Replace(s, "$$", "${HELMSMAN_DOLLAR}", -1)) -} - // substituteEnv checks if a string has an env variable (contains '$'), then it returns its value // if the env variable is empty or unset, an empty string is returned // if the string does not contain '$', it is returned as is. func substituteEnv(name string) string { if strings.Contains(name, "$") { - return expandEnv(name) + // add $$ escaping for $ strings + os.Setenv("HELMSMAN_DOLLAR", "$") + return os.ExpandEnv(strings.Replace(name, "$$", "${HELMSMAN_DOLLAR}", -1)) } return name } From 2205591de82efd851d1f628d8c59a865aa9d77be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Onur=20Yalaz=C4=B1?= Date: Tue, 6 Nov 2018 15:59:01 +0000 Subject: [PATCH 0301/1127] Fixed environment var encapsulation doc --- docs/desired_state_specification.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 59702ff0..471d75e1 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -14,7 +14,7 @@ This document describes the specification for how to write your Helm charts desi - [Apps](#apps) -- defines the applications/charts you want to manage in your cluster. -> You can use environment variables in the desired state files. The environment variable name should start with "$", or encapsulated in "$(", ")". "$" characters can be escaped like "$$". +> You can use environment variables in the desired state files. The environment variable name should start with "$", or encapsulated in "${", "}". "$" characters can be escaped like "$$". ## Metadata From 24624241d3e2db5b8d628995455838c32ddce833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Onur=20Yalaz=C4=B1?= Date: Tue, 13 Nov 2018 13:16:33 +0000 Subject: [PATCH 0302/1127] Removed redundant line --- release.go | 1 - 1 file changed, 1 deletion(-) diff --git a/release.go b/release.go index cd9613f1..457e85db 100644 --- a/release.go +++ b/release.go @@ -66,7 +66,6 @@ func validateRelease(appLabel string, r *release, names map[string]map[string]bo if r.Version == "" { return false, "version can't be empty." } - r.Version = r.Version _, err := os.Stat(r.ValuesFile) if r.ValuesFile != "" && (!isOfType(r.ValuesFile, ".yaml") || err != nil) { From 062890e973f36231b1d5cccdb28510ca8c567a62 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Tue, 13 Nov 2018 14:30:03 +0100 Subject: [PATCH 0303/1127] adding file path resoultion for certificates --- utils.go | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/utils.go b/utils.go index 6fbae666..f99a3185 100644 --- a/utils.go +++ b/utils.go @@ -3,7 +3,6 @@ package main import ( "bytes" "fmt" - "gopkg.in/yaml.v2" "io" "io/ioutil" "log" @@ -15,6 +14,8 @@ import ( "strings" "time" + "gopkg.in/yaml.v2" + "github.com/BurntSushi/toml" "github.com/Praqma/helmsman/aws" "github.com/Praqma/helmsman/gcs" @@ -151,6 +152,35 @@ func resolvePaths(relativeToFile string, s *state) { } s.Apps[k] = v } + //resolving paths for k8s certificate files + for k, v := range s.Certificates { + if _, err := url.ParseRequestURI(v); err != nil { + v, _ = filepath.Abs(filepath.Join(dir, v)) + } + s.Certificates[k] = v + } + // resolving paths for helm certificate files + for k, v := range s.Namespaces { + if tillerTLSEnabled(k) { + if _, err := url.ParseRequestURI(v.CaCert); err != nil { + v.CaCert, _ = filepath.Abs(filepath.Join(dir, v.CaCert)) + } + if _, err := url.ParseRequestURI(v.ClientCert); err != nil { + v.ClientCert, _ = filepath.Abs(filepath.Join(dir, v.ClientCert)) + } + if _, err := url.ParseRequestURI(v.ClientKey); err != nil { + v.ClientKey, _ = filepath.Abs(filepath.Join(dir, v.ClientKey)) + } + if _, err := url.ParseRequestURI(v.TillerCert); err != nil { + v.TillerCert, _ = filepath.Abs(filepath.Join(dir, v.TillerCert)) + } + if _, err := url.ParseRequestURI(v.TillerKey); err != nil { + v.TillerKey, _ = filepath.Abs(filepath.Join(dir, v.TillerKey)) + } + } + s.Namespaces[k] = v + } + } // isOfType checks if the file extension of a filename/path is the same as "filetype". From b29395e4b1dda0283b0239bb9d64b6a560987241 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Fri, 16 Nov 2018 09:10:42 +0000 Subject: [PATCH 0304/1127] fixes #123 --- docs/desired_state_specification.md | 14 +++++++------- example.toml | 4 ++-- example.yaml | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 471d75e1..5224602d 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -256,14 +256,14 @@ Options: - **description** : a release metadata for human readers. - **valuesFile** : a valid path to custom Helm values.yaml file. File extension must be `yaml`. Cannot be used with valuesFiles together. Leaving it empty uses the default chart values. - **valuesFiles** : array of valid paths to custom Helm values.yaml file. File extension must be `yaml`. Cannot be used with valuesFile together. Leaving it empty uses the default chart values. -> The values file(s) path is resolved when the DSF yaml/toml file is loaded, relative to the path that the dsf was loaded from. +> The values file(s) path is resolved when the DSF yaml/toml file is loaded, relative to the path that the dsf was loaded from. - **secretsFile** : a valid path to custom Helm secrets.yaml file. File extension must be `yaml`. Cannot be used with secretsFiles together. Leaving it empty uses the default chart secrets. - **secretsFiles** : array of valid paths to custom Helm secrets.yaml file. File extension must be `yaml`. Cannot be used with secretsFile together. Leaving it empty uses the default chart secrets. > The secrets file(s) path is resolved when the DSF yaml/toml file is loaded, relative to the path that the dsf was loaded from. > To use the secrets files you must have the helm-secrets plugin -- **purge** : defines whether to use the Helm purge flag when deleting the release. (true/false) -- **test** : defines whether to run the chart tests whenever the release is installed. -- **protected** : defines if the release should be protected against changes. Namespace-level protection has higher priority than this flag. Check the [protection guide](how_to/protect_namespaces_and_releases.md) for more details. +- **purge** : defines whether to use the Helm purge flag when deleting the release. Default is false. +- **test** : defines whether to run the chart tests whenever the release is installed. Default is false. +- **protected** : defines if the release should be protected against changes. Namespace-level protection has higher priority than this flag. Check the [protection guide](how_to/protect_namespaces_and_releases.md) for more details. Default is false. - **wait** : defines whether Helmsman should block execution until all k8s resources are in a ready state. Default is false. - **timeout** : helm timeout in seconds. Default 300 seconds. - **noHooks** : helm noHooks option. If true, it will disable pre/post upgrade hooks. Default is false. @@ -295,7 +295,7 @@ Example: secret1="$SECRET_ENV_VAR1" secret2="SECRET_ENV_VAR2" # works with/without $ at the beginning [apps.jenkins.setString] - longInt = "1234567890" + longInt = "1234567890" ``` ```yaml @@ -316,6 +316,6 @@ apps: set: secret1: "$SECRET_ENV_VAR1" secret2: "$SECRET_ENV_VAR2" - setString: - longInt: "1234567890" + setString: + longInt: "1234567890" ``` diff --git a/example.toml b/example.toml index 533a2f30..e497f817 100644 --- a/example.toml +++ b/example.toml @@ -78,8 +78,8 @@ wait = true # [apps.jenkins.setString] # values to override values from values.yaml with values from env vars or directly entered-- useful for passing secrets to charts # AdminPassword="$JENKINS_PASSWORD" # $JENKINS_PASSWORD must exist in the environment - # MyLongIntVar="1234567890" - [apps.jenkins.set] + # MyLongIntVar="1234567890" + [apps.jenkins.set] AdminUser="admin" diff --git a/example.yaml b/example.yaml index aa76d870..166967ff 100644 --- a/example.yaml +++ b/example.yaml @@ -75,8 +75,8 @@ apps: set: # values to override values from values.yaml with values from env vars-- useful for passing secrets to charts AdminPassword: "$JENKINS_PASSWORD" # $JENKINS_PASSWORD must exist in the environment AdminUser: "admin" - setString: - MyLongIntVar: "1234567890" + setString: + MyLongIntVar: "1234567890" # artifactory will be deployed using the Tiller in the kube-system namespace From 28d7985028c9810d74eb9b5e67e65b768e98a88b Mon Sep 17 00:00:00 2001 From: Shaun Mansell Date: Mon, 19 Nov 2018 15:09:07 +1100 Subject: [PATCH 0305/1127] Update helm diff arguments to remove timeout as it's not supported by the helm-diff plugin --- decision_maker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/decision_maker.go b/decision_maker.go index 85e08f0f..1fa8c2b1 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -225,7 +225,7 @@ func diffRelease(r *release) string { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm diff " + colorFlag + "upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + strconv.Quote(r.Version) + " " + getSetValues(r) + getSetStringValues(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r)}, + Args: []string{"-c", "helm diff " + colorFlag + "upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + strconv.Quote(r.Version) + " " + getSetValues(r) + getSetStringValues(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getNoHooks(r)}, Description: "diffing release [ " + r.Name + " ] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } From fdf325f034bb7261c35c145308ca16b6af575e39 Mon Sep 17 00:00:00 2001 From: hatemosphere Date: Tue, 20 Nov 2018 12:34:51 +0200 Subject: [PATCH 0306/1127] Adding ability to suppress secrets in helm diff output --- decision_maker.go | 6 +++++- init.go | 1 + main.go | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/decision_maker.go b/decision_maker.go index 1fa8c2b1..e424c06d 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -219,13 +219,17 @@ func diffRelease(r *release) string { exitCode := 0 msg := "" colorFlag := "" + suppressDiffSecretsFlag := "" if noColors { colorFlag = "--no-color " } + if suppressDiffSecrets { + suppressDiffSecretsFlag = "--suppress-secrets " + } cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm diff " + colorFlag + "upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + strconv.Quote(r.Version) + " " + getSetValues(r) + getSetStringValues(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getNoHooks(r)}, + Args: []string{"-c", "helm diff " + colorFlag + suppressDiffSecretsFlag + "upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + strconv.Quote(r.Version) + " " + getSetValues(r) + getSetStringValues(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r)}, Description: "diffing release [ " + r.Name + " ] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } diff --git a/init.go b/init.go index 5ee6026c..0a08a8b3 100644 --- a/init.go +++ b/init.go @@ -55,6 +55,7 @@ func init() { flag.BoolVar(&applyLabels, "apply-labels", false, "apply Helmsman labels to Helm state for all defined apps.") flag.BoolVar(&keepUntrackedReleases, "keep-untracked-releases", false, "keep releases that are managed by Helmsman and are no longer tracked in your desired state.") flag.BoolVar(&showDiff, "show-diff", false, "show helm diff results. Can expose sensitive information.") + flag.BoolVar(&suppressDiffSecrets, "suppress-diff-secrets", false, "don't show secrets in helm diff output.") flag.Usage = printUsage flag.Parse() diff --git a/main.go b/main.go index 4c28bd47..635aa511 100644 --- a/main.go +++ b/main.go @@ -39,6 +39,7 @@ var kubectlVersion string var dryRun bool var destroy bool var showDiff bool +var suppressDiffSecrets bool func main() { // set the kubecontext to be used Or create it if it does not exist From f059b4d476ec7570387ebc706baa27d1113e92f2 Mon Sep 17 00:00:00 2001 From: hatemosphere Date: Wed, 21 Nov 2018 13:56:21 +0200 Subject: [PATCH 0307/1127] Changing Unmarshal to UnmarshalStrict to error on unknown YAML fields in spec --- utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils.go b/utils.go index 7b973852..5476db63 100644 --- a/utils.go +++ b/utils.go @@ -86,7 +86,7 @@ func fromYAML(file string, s *state) (bool, string) { return false, err.Error() } yamlFile := []byte(substituteEnv(string(rawYamlFile))) - if err = yaml.Unmarshal(yamlFile, s); err != nil { + if err = yaml.UnmarshalStrict(yamlFile, s); err != nil { return false, err.Error() } From 48f2432705b42ed62d88682394e2e5905583b1e5 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Mon, 26 Nov 2018 13:47:35 +0100 Subject: [PATCH 0308/1127] bumping version and release notes --- README.md | 6 +++--- docs/desired_state_specification.md | 2 +- main.go | 2 +- release-notes.md | 11 +++++------ 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 332a6d4b..5ff046d6 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ --- -version: v1.7.0 +version: v1.7.1 --- ![helmsman-logo](docs/images/helmsman.png) @@ -58,9 +58,9 @@ If you use private helm repos, you will need either `helm-gcs` or `helm-s3` plug Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. ``` # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v1.7.0/helmsman_1.7.0_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.7.1/helmsman_1.7.1_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v1.7.0/helmsman_1.7.0_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.7.1/helmsman_1.7.1_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 5224602d..723fc761 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -1,5 +1,5 @@ --- -version: v1.7.0 +version: v1.7.1 --- # Helmsman desired state specification diff --git a/main.go b/main.go index 635aa511..4af43fa3 100644 --- a/main.go +++ b/main.go @@ -33,7 +33,7 @@ var checkCleanup bool var skipValidation bool var applyLabels bool var keepUntrackedReleases bool -var appVersion = "v1.7.0" +var appVersion = "v1.7.1" var helmVersion string var kubectlVersion string var dryRun bool diff --git a/release-notes.md b/release-notes.md index 1440b42d..6b89bfb9 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,10 +1,9 @@ -# v1.7.0 +# v1.7.1 > If you are already using an older version of Helmsman than v1.4.0-rc, please read the changes below carefully and follow the upgrade guide [here](docs/migrating_to_v1.4.0-rc.md) -- Resources (Tillers, namespaces, service accounts) will not be created without the `--apply` flag. Fixes #65 and #100. -- adding `--no-ns` option to prevent helmsman from creating namespaces. This allows users to create namespaces using their own custom charts. Fixes #71 and #100. -- Adding namespace labels. PR #111 -- Fixing a bug that deletes untracked releases when running with `--dry-run`. PR #110 -- Improving values and secrets file pahts resolution relative to dsf(s). PR #109 +- Improving env variable expansion across DSFs. PR #122. This change requires `$` signs in strings inside DSFs to be escaped as `$$`. If not escaped, they will be treated as variables. +- Adding `suppress-diff-secrets` for suppressing helm diff secrets. PR #132 +- Support overriding existing values when merging DSFs. PR #119 +- Fixing minor issues. PRs #131 #128 #130 #131 #118 From 21a17005757531c07efe307e818ff65de75a7ec7 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 27 Nov 2018 16:04:20 +0000 Subject: [PATCH 0309/1127] fixes #133 --- helm_helpers.go | 2 +- helm_helpers_test.go | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/helm_helpers.go b/helm_helpers.go index 173ff182..ea43d29f 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -189,7 +189,7 @@ func getReleaseChartName(rs releaseState) string { // in the event of an error, an empty string is returned. func getReleaseChartVersion(rs releaseState) string { chart := rs.Chart - re := regexp.MustCompile("-([0-9]+\\.[0-9]+\\.[0-9]+.*)") + re := regexp.MustCompile("-(v?[0-9]+\\.[0-9]+\\.[0-9]+.*)") matches := re.FindStringSubmatch(chart) if len(matches) > 1 { return matches[1] diff --git a/helm_helpers_test.go b/helm_helpers_test.go index 80b19f92..028ab455 100644 --- a/helm_helpers_test.go +++ b/helm_helpers_test.go @@ -81,6 +81,19 @@ func Test_getReleaseChartVersion(t *testing.T) { }, }, want: "", + }, { + name: "test case 6: version includes v", + args: args{ + r: releaseState{ + Revision: 0, + Updated: time.Now(), + Status: "", + Chart: "cert-manager-v0.5.2", + Namespace: "", + TillerNamespace: "", + }, + }, + want: "v0.5.2", }, } for _, tt := range tests { From 881bc31234c978c9b2eaf0e1c58bf424553df1d2 Mon Sep 17 00:00:00 2001 From: Artem Yarmoliuk Date: Thu, 29 Nov 2018 14:39:12 +0200 Subject: [PATCH 0310/1127] Supress empty diff --- decision_maker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/decision_maker.go b/decision_maker.go index e424c06d..917ba5a3 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -236,7 +236,7 @@ func diffRelease(r *release) string { if exitCode, msg = cmd.exec(debug, verbose); exitCode != 0 { logError("Command returned with exit code: " + string(exitCode) + ". And error message: " + msg) } else { - if verbose || showDiff { + if (verbose || showDiff) && msg != "" { fmt.Println(msg) } } From dceb3de4480b0f19f659f899dd66e10ad8bb56e6 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Thu, 6 Dec 2018 10:41:44 +0000 Subject: [PATCH 0311/1127] fixes #135 --- utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils.go b/utils.go index 5476db63..0199858c 100644 --- a/utils.go +++ b/utils.go @@ -135,7 +135,7 @@ func toFile(file string, s *state) { if isOfType(file, ".toml") { toTOML(file, s) } else if isOfType(file, ".yaml") { - fromYAML(file, s) + toYAML(file, s) } else { logError("ERROR: State file does not have toml/yaml extension.") } From 8d5445022f728f5cbbf0e981c06d3aee685ffe1c Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Mon, 10 Dec 2018 10:32:59 +0000 Subject: [PATCH 0312/1127] fixes #139 --- helm_helpers.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/helm_helpers.go b/helm_helpers.go index ea43d29f..29fe138e 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -365,18 +365,6 @@ func initHelm() (bool, string) { defaultSA := s.Settings.ServiceAccount - if ns, ok := s.Namespaces["kube-system"]; ok { - if ns.InstallTiller { - if ok, err := deployTiller("kube-system", ns.TillerServiceAccount, defaultSA); !ok { - return false, err - } - } - } else { - if ok, err := deployTiller("kube-system", "", defaultSA); !ok { - return false, err - } - } - for k, ns := range s.Namespaces { if tillerTLSEnabled(k) { downloadFile(s.Namespaces[k].TillerCert, k+"-tiller.cert") @@ -393,6 +381,18 @@ func initHelm() (bool, string) { } } + if ns, ok := s.Namespaces["kube-system"]; ok { + if ns.InstallTiller { + if ok, err := deployTiller("kube-system", ns.TillerServiceAccount, defaultSA); !ok { + return false, err + } + } + } else { + if ok, err := deployTiller("kube-system", "", defaultSA); !ok { + return false, err + } + } + return true, "" } From 74da8fb4c44c81097f2509181731001886947a6f Mon Sep 17 00:00:00 2001 From: Erik Aaron Hansen Date: Tue, 11 Dec 2018 12:53:27 +0100 Subject: [PATCH 0313/1127] Update protect_namespaces_and_releases.md Fixed typo --- docs/how_to/protect_namespaces_and_releases.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/how_to/protect_namespaces_and_releases.md b/docs/how_to/protect_namespaces_and_releases.md index f9ef22fe..44124138 100644 --- a/docs/how_to/protect_namespaces_and_releases.md +++ b/docs/how_to/protect_namespaces_and_releases.md @@ -32,7 +32,7 @@ Protection is supported in two forms: [namespaces.staging] protected = false [namespaces.production] - prtoected = true + protected = true ``` @@ -77,4 +77,4 @@ apps: - You can combine both types of protection in your desired state file. The namespace-level protection always has a higher priority. - Removing the protection from a namespace means all releases deployed in that namespace are no longer protected. - We recommend using namespace-level protection for production namespace(s) and release-level protection for releases deployed in other namespaces. -- Release/namespace protection is only applied on single desired state files. It is your responsibility to make sure that multiple desired state files (if used) do not conflict with each other (e.g, one defines a particular namespace as protected and another defines it unprotected.) If you use multiple desired state files with the same cluster, please refer to [deployment strategies](../deployment_strategies.md) and [best practice](../best_practice.md) documentation. \ No newline at end of file +- Release/namespace protection is only applied on single desired state files. It is your responsibility to make sure that multiple desired state files (if used) do not conflict with each other (e.g, one defines a particular namespace as protected and another defines it unprotected.) If you use multiple desired state files with the same cluster, please refer to [deployment strategies](../deployment_strategies.md) and [best practice](../best_practice.md) documentation. From 3c3a5b034616e0b13726d050a7506766441e8a71 Mon Sep 17 00:00:00 2001 From: SteveRuble Date: Thu, 13 Dec 2018 11:11:11 -0500 Subject: [PATCH 0314/1127] feat: Support local charts on file system This adds support for charts that are on the file system rather than in a repo. It works by checking whether a chart has a repo that's listed in the repos section of the DSF. If it doesn't, the chart name is treated as a file path instead. If the chart is not present on the file system, or the chart on the file system is a different version than specified in the DSF, validation fails. --- decision_maker.go | 13 +++++++--- docs/how_to/use_local_charts.md | 15 +++++++++-- helm_helpers.go | 45 ++++++++++++++++++++++++++------- release.go | 2 +- utils.go | 19 +++++++++++++- 5 files changed, 78 insertions(+), 16 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index 917ba5a3..4e0255be 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "regexp" "strconv" "strings" ) @@ -268,7 +269,7 @@ func reInstallRelease(r *release, rs releaseState) { installCmd := command{ Cmd: "bash", - Args: []string{"-c", "helm install " + r.Chart + " --version " + r.Version + " -n " + r.Name + " --namespace " + r.Namespace + getValuesFiles(r) + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r) + getDryRunFlags()}, + Args: []string{"-c", "helm install " + r.Chart+ " --version " + r.Version + " -n " + r.Name + " --namespace " + r.Namespace + getValuesFiles(r) + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r) + getDryRunFlags()}, Description: "installing release [ " + r.Name + " ] in namespace [[ " + r.Namespace + " ]] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } outcome.addCommand(installCmd, r.Priority, r) @@ -283,13 +284,19 @@ func logDecision(decision string, priority int) { } // extractChartName extracts the Helm chart name from full chart name in the desired state. -// example: it extracts "chartY" from "repoX/chartY" +// example: it extracts "chartY" from "repoX/chartY" and "chartZ" from "c:\charts\chartZ" func extractChartName(releaseChart string) string { - return strings.TrimSpace(strings.Split(releaseChart, "/")[1]) + m := chartNameExtractor.FindStringSubmatch(releaseChart) + if len(m) == 2 { + return m[1] + } + return "" } +var chartNameExtractor = regexp.MustCompile(`[\\/]([^\\/]+)$`) + // getNoHooks returns the no-hooks flag for install/upgrade commands func getNoHooks(r *release) string { if r.NoHooks { diff --git a/docs/how_to/use_local_charts.md b/docs/how_to/use_local_charts.md index cc9c0574..487ba4c6 100644 --- a/docs/how_to/use_local_charts.md +++ b/docs/how_to/use_local_charts.md @@ -4,7 +4,11 @@ version: v1.3.0-rc # use local helm charts -You can use your locally developed charts. But first, you have to serve them on localhost using helm's `serve` option. +You can use your locally developed charts. + +## Served by Helm + +You can serve them on localhost using helm's `serve` option. ```toml ... @@ -28,4 +32,11 @@ helmRepos: ... -``` \ No newline at end of file +``` + +## From file system + +If you use a file path (relative to the DSF, or absolute) for the ```chart``` attribute +helmsman will try to resolve that chart from the local file system. The chart on the +local file system must have a version matching the version specified in the DSF. + diff --git a/helm_helpers.go b/helm_helpers.go index 29fe138e..71d96c46 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -3,13 +3,14 @@ package main import ( "encoding/json" "log" + "path/filepath" "regexp" "strconv" "strings" "time" "github.com/Praqma/helmsman/gcs" - version "github.com/hashicorp/go-version" + "github.com/hashicorp/go-version" ) var currentState map[string]releaseState @@ -211,18 +212,44 @@ func getNSTLSFlags(ns string) string { // Valid charts are the ones that can be found in the defined repos. // This function uses Helm search to verify if the chart can be found or not. func validateReleaseCharts(apps map[string]*release) (bool, string) { + versionExtractor := regexp.MustCompile(`version:\s?(.*)`) for app, r := range apps { - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm search " + r.Chart + " --version " + strconv.Quote(r.Version) + " -l"}, - Description: "validating if chart " + r.Chart + "-" + r.Version + " is available in the defined repos.", - } - if exitCode, result := cmd.exec(debug, verbose); exitCode != 0 || strings.Contains(result, "No results found") { - return false, "ERROR: chart " + r.Chart + "-" + r.Version + " is specified for " + - "app [" + app + "] but is not found in the defined repos." + isLocal := filepath.IsAbs(r.Chart) + if isLocal { + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm inspect chart " + r.Chart}, + Description: "validating if chart at " + r.Chart + " is available.", + } + + if exitCode, output := cmd.exec(debug, verbose); exitCode != 0 { + maybeRepo := filepath.Base(filepath.Dir(r.Chart)) + return false, "ERROR: chart at " + r.Chart + " for app [" + app + "] could not be found. Did you mean to add a repo named '" + maybeRepo +"'?" + } else { + matches := versionExtractor.FindStringSubmatch(output) + if len(matches) == 2 { + version := matches[1] + if r.Version != version { + return false, "ERROR: chart " + r.Chart + " with version " + r.Version + " is specified for " + + "app [" + app + "] but the chart found at that path has version " + version + " which does not match." + } + } + } + } else { + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm search " + r.Chart + " --version " + strconv.Quote(r.Version) + " -l"}, + Description: "validating if chart " + r.Chart + "-" + r.Version + " is available in the defined repos.", + } + + if exitCode, result := cmd.exec(debug, verbose); exitCode != 0 || strings.Contains(result, "No results found") { + return false, "ERROR: chart " + r.Chart + "-" + r.Version + " is specified for " + + "app [" + app + "] but is not found in the defined repos." + } } + } return true, "" } diff --git a/release.go b/release.go index 457e85db..f7eb7616 100644 --- a/release.go +++ b/release.go @@ -6,7 +6,7 @@ import ( "os" "strings" - version "github.com/hashicorp/go-version" + "github.com/hashicorp/go-version" ) // release type representing Helm releases which are described in the desired state diff --git a/utils.go b/utils.go index 0199858c..210b4858 100644 --- a/utils.go +++ b/utils.go @@ -143,6 +143,7 @@ func toFile(file string, s *state) { func resolvePaths(relativeToFile string, s *state) { dir := filepath.Dir(relativeToFile) + for k, v := range s.Apps { if v.ValuesFile != "" { v.ValuesFile, _ = filepath.Abs(filepath.Join(dir, v.ValuesFile)) @@ -156,9 +157,25 @@ func resolvePaths(relativeToFile string, s *state) { for i, f := range v.SecretsFiles { v.SecretsFiles[i], _ = filepath.Abs(filepath.Join(dir, f)) } + + if v.Chart != "" { + var repoOrDir = filepath.Dir(v.Chart) + _, isRepo := s.HelmRepos[repoOrDir] + if !isRepo { + // if there is no repo for the chart, we assume it's intended to be a local path + + // support env vars in path + v.Chart = os.ExpandEnv(v.Chart) + // respect absolute paths to charts but resolve relative paths + if !filepath.IsAbs(v.Chart) { + v.Chart, _ = filepath.Abs(filepath.Join(dir, v.Chart)) + } + } + } + s.Apps[k] = v } - //resolving paths for k8s certificate files + // resolving paths for k8s certificate files for k, v := range s.Certificates { if _, err := url.ParseRequestURI(v); err != nil { v, _ = filepath.Abs(filepath.Join(dir, v)) From 40b099b239e6239ad94d428f9bbfc98f4de153f1 Mon Sep 17 00:00:00 2001 From: Artem Yarmoliuk Date: Fri, 14 Dec 2018 19:50:52 +0200 Subject: [PATCH 0315/1127] Add colors to decision outputs --- decision_maker.go | 40 ++++++++++++++++++++-------------------- helm_helpers.go | 2 +- plan.go | 25 +++++++++++++++++++++++-- plan_test.go | 2 +- 4 files changed, 45 insertions(+), 24 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index 917ba5a3..9d0fc345 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -43,10 +43,10 @@ func decide(r *release, s *state) { } else { logDecision("DECISION: release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ - "you remove its protection.", r.Priority) + "you remove its protection.", r.Priority, noop) } } else { - logDecision("DECISION: release [ "+r.Name+" ] is set to be disabled but is not yet deployed. Skipping.", r.Priority) + logDecision("DECISION: release [ "+r.Name+" ] is set to be disabled but is not yet deployed. Skipping.", r.Priority, noop) } } else { // check for install/upgrade/rollback @@ -56,7 +56,7 @@ func decide(r *release, s *state) { } else { logDecision("DECISION: release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ - "you remove its protection.", r.Priority) + "you remove its protection.", r.Priority, noop) } } else if ok, rs := helmReleaseExists(r, "deleted"); ok { @@ -66,19 +66,19 @@ func decide(r *release, s *state) { } else { logDecision("DECISION: release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ - "you remove its protection.", r.Priority) + "you remove its protection.", r.Priority, noop) } } else if ok, rs := helmReleaseExists(r, "failed"); ok { if !isProtected(r, rs) { - logDecision("DECISION: release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is in FAILED state. I will upgrade it for you. Hope it gets fixed!", r.Priority) + logDecision("DECISION: release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is in FAILED state. I will upgrade it for you. Hope it gets fixed!", r.Priority, change) upgradeRelease(r) } else { logDecision("DECISION: release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ - "you remove its protection.", r.Priority) + "you remove its protection.", r.Priority, noop) } } else { @@ -99,7 +99,7 @@ func testRelease(r *release) { Description: "running tests for release [ " + r.Name + " ]", } outcome.addCommand(cmd, r.Priority, r) - logDecision("DECISION: release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is required to be tested when installed. Got it!", r.Priority) + logDecision("DECISION: release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is required to be tested when installed. Got it!", r.Priority, noop) } @@ -113,7 +113,7 @@ func installRelease(r *release) { } outcome.addCommand(cmd, r.Priority, r) logDecision("DECISION: release [ "+r.Name+" ] is not installed. Will install it in namespace [[ "+ - r.Namespace+" ]] using Tiller in [ "+getDesiredTillerNamespace(r)+" ]", r.Priority) + r.Namespace+" ]] using Tiller in [ "+getDesiredTillerNamespace(r)+" ]", r.Priority, create) if r.Test { testRelease(r) @@ -135,16 +135,16 @@ func rollbackRelease(r *release, rs releaseState) { outcome.addCommand(cmd, r.Priority, r) upgradeRelease(r) // this is to reflect any changes in values file(s) logDecision("DECISION: release [ "+r.Name+" ] is currently deleted and is desired to be rolledback to "+ - "namespace [[ "+r.Namespace+" ]] . It will also be upgraded in case values have changed.", r.Priority) + "namespace [[ "+r.Namespace+" ]] . It will also be upgraded in case values have changed.", r.Priority, change) } else { reInstallRelease(r, rs) logDecision("DECISION: release [ "+r.Name+" ] is deleted BUT from namespace [[ "+rs.Namespace+ - " ]]. Will purge delete it from there and install it in namespace [[ "+r.Namespace+" ]]", r.Priority) + " ]]. Will purge delete it from there and install it in namespace [[ "+r.Namespace+" ]]", r.Priority, change) logDecision("WARNING: rolling back release [ "+r.Name+" ] from [[ "+rs.Namespace+" ]] to [[ "+r.Namespace+ " ]] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md"+ - " for details if this release uses PV and PVC.", r.Priority) + " for details if this release uses PV and PVC.", r.Priority, change) } } @@ -169,7 +169,7 @@ func deleteRelease(r *release, rs releaseState) { Description: "deleting release [ " + r.Name + " ] from namespace [[ " + r.Namespace + " ]] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } outcome.addCommand(cmd, priority, r) - logDecision("DECISION: release [ "+r.Name+" ] is desired to be deleted "+purgeDesc+". Planing this for you!", priority) + logDecision("DECISION: release [ "+r.Name+" ] is desired to be deleted "+purgeDesc+". Planing this for you!", priority, delete) } // inspectUpgradeScenario evaluates if a release should be upgraded. @@ -185,32 +185,32 @@ func inspectUpgradeScenario(r *release, rs releaseState) { if extractChartName(r.Chart) == getReleaseChartName(rs) && r.Version != getReleaseChartVersion(rs) { // upgrade upgradeRelease(r) - logDecision("DECISION: release [ "+r.Name+" ] is desired to be upgraded. Planing this for you!", r.Priority) + logDecision("DECISION: release [ "+r.Name+" ] is desired to be upgraded. Planing this for you!", r.Priority, change) } else if extractChartName(r.Chart) != getReleaseChartName(rs) { reInstallRelease(r, rs) logDecision("DECISION: release [ "+r.Name+" ] is desired to use a new Chart [ "+r.Chart+ " ]. I am planning a purge delete of the current release and will install it with the new chart in namespace [[ "+ - r.Namespace+" ]]", r.Priority) + r.Namespace+" ]]", r.Priority, change) } else { if diff := diffRelease(r); diff != "" { upgradeRelease(r) logDecision("DECISION: release [ "+r.Name+" ] is currently enabled and have some changed parameters. "+ - "I will upgrade it!", r.Priority) + "I will upgrade it!", r.Priority, change) } else { logDecision("DECISION: release [ "+r.Name+" ] is desired to be enabled and is currently enabled. "+ - "Nothing to do here!", r.Priority) + "Nothing to do here!", r.Priority, noop) } } } else { reInstallRelease(r, rs) logDecision("DECISION: release [ "+r.Name+" ] is desired to be enabled in a new namespace [[ "+r.Namespace+ " ]]. I am planning a purge delete of the current release from namespace [[ "+rs.Namespace+" ]] "+ - "and will install it for you in namespace [[ "+r.Namespace+" ]]", r.Priority) + "and will install it for you in namespace [[ "+r.Namespace+" ]]", r.Priority, change) logDecision("WARNING: moving release [ "+r.Name+" ] from [[ "+rs.Namespace+" ]] to [[ "+r.Namespace+ " ]] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md"+ - " for details if this release uses PV and PVC.", r.Priority) + " for details if this release uses PV and PVC.", r.Priority, change) } } @@ -276,9 +276,9 @@ func reInstallRelease(r *release, rs releaseState) { // logDecision adds the decisions made to the plan. // Depending on the debug flag being set or not, it will either log the the decision to output or not. -func logDecision(decision string, priority int) { +func logDecision(decision string, priority int, decisionType decisionType) { - outcome.addDecision(decision, priority) + outcome.addDecision(decision, priority, decisionType) } diff --git a/helm_helpers.go b/helm_helpers.go index 29fe138e..fc79928b 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -426,7 +426,7 @@ func cleanUntrackedReleases() { } else { for ns, releases := range toDelete { for r := range releases { - logDecision("DECISION: untracked release found: release [ "+r+" ] from Tiller in namespace [ "+ns+" ]. It will be deleted.", -800) + logDecision("DECISION: untracked release found: release [ "+r+" ] from Tiller in namespace [ "+ns+" ]. It will be deleted.", -800, delete) deleteUntrackedRelease(r, ns) } } diff --git a/plan.go b/plan.go index e27d24e5..7714d35e 100644 --- a/plan.go +++ b/plan.go @@ -8,12 +8,32 @@ import ( "strconv" "strings" "time" + + "github.com/logrusorgru/aurora" +) + +// decisionType type representing type of Decision for console output +type decisionType int + +const ( + create decisionType = iota + 1 + change + delete + noop ) +var decisionColor = map[decisionType]aurora.Color{ + create: aurora.BlueFg, + change: aurora.BrownFg, + delete: aurora.RedFg, + noop: aurora.GreenFg, +} + // orderedDecision type representing a Decision and it's priority weight type orderedDecision struct { Description string Priority int + Type decisionType } // orderedCommand type representing a Command and it's priority weight and the targeted release from the desired state @@ -53,10 +73,11 @@ func (p *plan) addCommand(cmd command, priority int, r *release) { } // addDecision adds a decision type to the plan -func (p *plan) addDecision(decision string, priority int) { +func (p *plan) addDecision(decision string, priority int, decisionType decisionType) { od := orderedDecision{ Description: decision, Priority: priority, + Type: decisionType, } p.Decisions = append(p.Decisions, od) } @@ -98,7 +119,7 @@ func (p plan) printPlan() { fmt.Println("----------------------") log.Println(style.Bold(style.Green("INFO: Plan generated at: " + p.Created.Format("Mon Jan _2 2006 15:04:05")))) for _, decision := range p.Decisions { - fmt.Println(style.Green(decision.Description + " -- priority: " + strconv.Itoa(decision.Priority))) + fmt.Println(style.Colorize(decision.Description+" -- priority: "+strconv.Itoa(decision.Priority), decisionColor[decision.Type])) } } diff --git a/plan_test.go b/plan_test.go index cf8038c2..bd3114d4 100644 --- a/plan_test.go +++ b/plan_test.go @@ -104,7 +104,7 @@ func Test_plan_addDecision(t *testing.T) { Decisions: tt.fields.Decisions, Created: tt.fields.Created, } - p.addDecision(tt.args.decision, 0) + p.addDecision(tt.args.decision, 0, noop) if got := len(p.Decisions); got != 1 { t.Errorf("addDecision(): got %v, want 1", got) } From 82f29bbd476d981321131181f4a13f1761dea73f Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Mon, 17 Dec 2018 11:43:53 +0100 Subject: [PATCH 0316/1127] allowing yaml and yml files. fixes #141 --- release.go | 8 ++++---- utils.go | 17 +++++++++++------ utils_test.go | 27 +++++++++++++++++---------- 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/release.go b/release.go index f7eb7616..5ec93172 100644 --- a/release.go +++ b/release.go @@ -68,26 +68,26 @@ func validateRelease(appLabel string, r *release, names map[string]map[string]bo } _, err := os.Stat(r.ValuesFile) - if r.ValuesFile != "" && (!isOfType(r.ValuesFile, ".yaml") || err != nil) { + if r.ValuesFile != "" && (!isOfType(r.ValuesFile, []string{".yaml", ".yml"}) || err != nil) { return false, fmt.Sprintf("valuesFile must be a valid relative (from dsf file) file path for a yaml file, or can be left empty (provided path resolved to %q).", r.ValuesFile) } else if r.ValuesFile != "" && len(r.ValuesFiles) > 0 { return false, "valuesFile and valuesFiles should not be used together." } else if len(r.ValuesFiles) > 0 { for i, filePath := range r.ValuesFiles { - if _, pathErr := os.Stat(filePath); !isOfType(filePath, ".yaml") || pathErr != nil { + if _, pathErr := os.Stat(filePath); !isOfType(filePath, []string{".yaml", ".yml"}) || pathErr != nil { return false, fmt.Sprintf("valuesFiles must be valid relative (from dsf file) file paths for a yaml file; path at index %d provided path resolved to %q.", i, filePath) } } } _, err = os.Stat(r.SecretsFile) - if r.SecretsFile != "" && (!isOfType(r.SecretsFile, ".yaml") || err != nil) { + if r.SecretsFile != "" && (!isOfType(r.SecretsFile, []string{".yaml", ".yml"}) || err != nil) { return false, fmt.Sprintf("secretsFile must be a valid relative (from dsf file) file path for a yaml file, or can be left empty (provided path resolved to %q).", r.SecretsFile) } else if r.SecretsFile != "" && len(r.SecretsFiles) > 0 { return false, "secretsFile and secretsFiles should not be used together." } else if len(r.SecretsFiles) > 0 { for _, filePath := range r.SecretsFiles { - if i, pathErr := os.Stat(filePath); !isOfType(filePath, ".yaml") || pathErr != nil { + if i, pathErr := os.Stat(filePath); !isOfType(filePath, []string{".yaml", ".yml"}) || pathErr != nil { return false, fmt.Sprintf("secretsFiles must be valid relative (from dsf file) file paths for a yaml file; path at index %d provided path resolved to %q.", i, filePath) } } diff --git a/utils.go b/utils.go index 210b4858..5d6a4c11 100644 --- a/utils.go +++ b/utils.go @@ -122,9 +122,9 @@ func toYAML(file string, s *state) { // invokes either yaml or toml parser considering file extension func fromFile(file string, s *state) (bool, string) { - if isOfType(file, ".toml") { + if isOfType(file, []string{".toml"}) { return fromTOML(file, s) - } else if isOfType(file, ".yaml") { + } else if isOfType(file, []string{".yaml", ".yml"}) { return fromYAML(file, s) } else { return false, "State file does not have toml/yaml extension." @@ -132,9 +132,9 @@ func fromFile(file string, s *state) (bool, string) { } func toFile(file string, s *state) { - if isOfType(file, ".toml") { + if isOfType(file, []string{".toml"}) { toTOML(file, s) - } else if isOfType(file, ".yaml") { + } else if isOfType(file, []string{".yaml", ".yml"}) { toYAML(file, s) } else { logError("ERROR: State file does not have toml/yaml extension.") @@ -208,8 +208,13 @@ func resolvePaths(relativeToFile string, s *state) { // isOfType checks if the file extension of a filename/path is the same as "filetype". // isisOfType is case insensitive. filetype should contain the "." e.g. ".yaml" -func isOfType(filename string, filetype string) bool { - return filepath.Ext(strings.ToLower(filename)) == strings.ToLower(filetype) +func isOfType(filename string, filetypes []string) bool { + lowerMap := make(map[string]struct{}) + for _, v := range filetypes { + lowerMap[strings.ToLower(v)] = struct{}{} + } + _, result := lowerMap[filepath.Ext(strings.ToLower(filename))] + return result } // readFile returns the content of a file as a string. diff --git a/utils_test.go b/utils_test.go index 18117253..94404742 100644 --- a/utils_test.go +++ b/utils_test.go @@ -296,8 +296,8 @@ func Test_fromYAML_Expand(t *testing.T) { func Test_isOfType(t *testing.T) { type args struct { - filename string - filetype string + filename string + filetypes []string } tests := []struct { name string @@ -307,29 +307,36 @@ func Test_isOfType(t *testing.T) { { name: "test case 1 -- valid xml check", args: args{ - filename: "name.xml", - filetype: ".xml", + filename: "name.xml", + filetypes: []string{".xml"}, }, want: true, }, { name: "test case 2 -- valid yaml check", args: args{ - filename: "another_name.yaml", - filetype: ".yaml", + filename: "another_name.yaml", + filetypes: []string{".yaml", ".yml"}, }, want: true, }, { - name: "test case 3 -- invalid yaml check", + name: "test case 3 -- valid (short) yaml check", args: args{ - filename: "name.xml", - filetype: ".yaml", + filename: "another_name.yml", + filetypes: []string{".yaml", ".yml"}, + }, + want: true, + }, { + name: "test case 4 -- invalid yaml check", + args: args{ + filename: "name.xml", + filetypes: []string{".yaml", ".yml"}, }, want: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := isOfType(tt.args.filename, tt.args.filetype); got != tt.want { + if got := isOfType(tt.args.filename, tt.args.filetypes); got != tt.want { t.Errorf("isOfType() = %v, want %v", got, tt.want) } }) From 4b9e6beab179431b410092d325ba703089630ef7 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Mon, 17 Dec 2018 12:06:55 +0100 Subject: [PATCH 0317/1127] releasing v1.7.2 --- README.md | 6 +++--- docs/desired_state_specification.md | 2 +- main.go | 2 +- release-notes.md | 8 +++----- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 5ff046d6..a4c91c20 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ --- -version: v1.7.1 +version: v1.7.2 --- ![helmsman-logo](docs/images/helmsman.png) @@ -58,9 +58,9 @@ If you use private helm repos, you will need either `helm-gcs` or `helm-s3` plug Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. ``` # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v1.7.1/helmsman_1.7.1_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.7.2/helmsman_1.7.2_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v1.7.1/helmsman_1.7.1_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.7.2/helmsman_1.7.2_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 723fc761..5ad61b0b 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -1,5 +1,5 @@ --- -version: v1.7.1 +version: v1.7.2 --- # Helmsman desired state specification diff --git a/main.go b/main.go index 4af43fa3..ada9e7da 100644 --- a/main.go +++ b/main.go @@ -33,7 +33,7 @@ var checkCleanup bool var skipValidation bool var applyLabels bool var keepUntrackedReleases bool -var appVersion = "v1.7.1" +var appVersion = "v1.7.2" var helmVersion string var kubectlVersion string var dryRun bool diff --git a/release-notes.md b/release-notes.md index 6b89bfb9..5c5323e2 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,9 +1,7 @@ -# v1.7.1 +# v1.7.2 > If you are already using an older version of Helmsman than v1.4.0-rc, please read the changes below carefully and follow the upgrade guide [here](docs/migrating_to_v1.4.0-rc.md) -- Improving env variable expansion across DSFs. PR #122. This change requires `$` signs in strings inside DSFs to be escaped as `$$`. If not escaped, they will be treated as variables. -- Adding `suppress-diff-secrets` for suppressing helm diff secrets. PR #132 -- Support overriding existing values when merging DSFs. PR #119 -- Fixing minor issues. PRs #131 #128 #130 #131 #118 +- Several Minor fixes and improvements: PRs #145 #142 #140 #138 #136 #134 +- Support specifying local charts in the DSFs. PR #144 (thanks to @SteveRuble) From 5703b4bc4d198b721f796a6100f2c8d52931aa2e Mon Sep 17 00:00:00 2001 From: Lachlan Cooper Date: Tue, 18 Dec 2018 10:05:50 +1100 Subject: [PATCH 0318/1127] Fix spelling issues in docs --- docs/{deplyment_strategies.md => deployment_strategies.md} | 4 ++-- docs/how_to/override_defined_namespaces.md | 4 ++-- docs/how_to/use_the_priority_key.md | 6 +++--- docs/why_helmsman.md | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) rename docs/{deplyment_strategies.md => deployment_strategies.md} (97%) diff --git a/docs/deplyment_strategies.md b/docs/deployment_strategies.md similarity index 97% rename from docs/deplyment_strategies.md rename to docs/deployment_strategies.md index b2c3f275..8cfd8805 100644 --- a/docs/deplyment_strategies.md +++ b/docs/deployment_strategies.md @@ -98,7 +98,7 @@ apps: enabled: true chart: "stable/artifactory" version: "6.2.0" # chart version - valuesFile: "../my-artificatory-production-values.yaml" + valuesFile: "../my-artifactory-production-values.yaml" # the jenkins release below is being tested in the staging namespace jenkins-test: @@ -131,7 +131,7 @@ Often, you would have multiple apps developed in separate source code repositori Each repository will have a Helmsman desired state file (DSF). But it is important to consider the notes below on using multiple desired state files with one cluster. -If you need supporting applications (charts) for your application (e.g, reverse proxies, DB, k8s dashborad etc.), you can describe the desired state for these in a separate file which can live in another repository. Adding such file in the pipeline where you create your cluster from code makes total "DevOps" sense. +If you need supporting applications (charts) for your application (e.g, reverse proxies, DB, k8s dashboard etc.), you can describe the desired state for these in a separate file which can live in another repository. Adding such file in the pipeline where you create your cluster from code makes total "DevOps" sense. ## Notes on using multiple Helmsman desired state files with the same cluster diff --git a/docs/how_to/override_defined_namespaces.md b/docs/how_to/override_defined_namespaces.md index c3f3b918..1f82989e 100644 --- a/docs/how_to/override_defined_namespaces.md +++ b/docs/how_to/override_defined_namespaces.md @@ -36,7 +36,7 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" [apps.jenkins] name = "jenkins" # should be unique across all apps description = "jenkins" - namespace = "production" # maps to the namespace as defined in environmetns above + namespace = "production" # maps to the namespace as defined in environments above enabled = true # change to false if you want to delete this app release [empty = false] chart = "stable/jenkins" # changing the chart name means delete and recreate this chart version = "0.14.3" # chart version @@ -45,7 +45,7 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" [apps.artifactory] name = "artifactory" # should be unique across all apps description = "artifactory" - namespace = "staging" # maps to the namespace as defined in environmetns above + namespace = "staging" # maps to the namespace as defined in environments above enabled = true # change to false if you want to delete this app release [empty = false] chart = "stable/artifactory" # changing the chart name means delete and recreate this chart version = "7.0.6" # chart version diff --git a/docs/how_to/use_the_priority_key.md b/docs/how_to/use_the_priority_key.md index eeb70d88..60d78167 100644 --- a/docs/how_to/use_the_priority_key.md +++ b/docs/how_to/use_the_priority_key.md @@ -8,7 +8,7 @@ The `priority` flag in Apps definition allows you to define the order at which a Priority is an optional flag and has a default value of 0 (zero). If set, it can only use a negative value. The lower the value, the higher the priority. -If you want your apps to be deleted in the reverse order as they where created, you can also use the optional `Settings` flag `reverseDelete`, to acheive this, set it to `true` +If you want your apps to be deleted in the reverse order as they where created, you can also use the optional `Settings` flag `reverseDelete`, to achieve this, set it to `true` ## Example @@ -19,13 +19,13 @@ If you want your apps to be deleted in the reverse order as they where created, [settings] kubeContext = "minikube" - reverseDelete = false # Optional flag to reverse the priorities when deletting + reverseDelete = false # Optional flag to reverse the priorities when deleting [namespaces] [namespaces.staging] protected = false [namespaces.production] - prtoected = true + protected = true [helmRepos] stable = "https://kubernetes-charts.storage.googleapis.com" diff --git a/docs/why_helmsman.md b/docs/why_helmsman.md index b4141e28..7b189f22 100644 --- a/docs/why_helmsman.md +++ b/docs/why_helmsman.md @@ -8,20 +8,20 @@ This document describes the reasoning and need behind the inception of Helmsman. ## Before Helm -Helmsman was created with continous deployment in mind. +Helmsman was created with continuous deployment in mind. When we started using k8s, we deployed applications on our cluster directly from k8s manifest files. Initially, we had a custom shell script added to our CI system to deploy the k8s resources on the cluster. That script could only create the k8s resources from the manifest files. Soon we needed to have a more flexible way to dynamically create/delete those resources. We structured our git repo and used custom file names (adding enabled or disabled into file names) and updated the shell script accordingly. It did not take long before we realized that this does not scale and is difficult to maintain. ![CI-pipeline-before-helm](images/CI-pipeline-before-helm.jpg) ## Helm to the rescue? -While looking for solutions for managing the growing number of k8s manifest files from a CI pipeline, we came to know about Helm and quickly releaized its potential. By creating Helm charts, we packaged related k8s manifests together into a single entity "a chart". This reduced the amount of files the CI script has to deal with. However, all the CI shell script could do is package a chart and install/upgrade it in our k8s cluster whenever a new commit is done into the chart's files in git. +While looking for solutions for managing the growing number of k8s manifest files from a CI pipeline, we came to know about Helm and quickly realized its potential. By creating Helm charts, we packaged related k8s manifests together into a single entity "a chart". This reduced the amount of files the CI script has to deal with. However, all the CI shell script could do is package a chart and install/upgrade it in our k8s cluster whenever a new commit is done into the chart's files in git. ![CI-pipeline-after-helm](images/CI-pipeline-after-helm.jpg) But there were a couple of issues here: 1. Helm has more to it than package and install. Operations such as rollback, running chart tests etc. are only doable from the Helm's CLI client. -2. You have to keep updating your CI script everytime you add a chart to k8s. +2. You have to keep updating your CI script every time you add a chart to k8s. 3. What if you want to do the same on another cluster? you will have to replicate your CI pipeline and possibly change your CI script accordingly. We have also decided to split the Helm charts development from the git repositories where they are used. This is simply to let us develop the charts independently from the projects where we used them and to allow us to reuse them in different projects. From 59f930883fedda98a586be8f0445e78d97a8ae7d Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Wed, 19 Dec 2018 12:10:40 +0100 Subject: [PATCH 0319/1127] updating docs to fix#129 --- README.md | 9 +++++- docs/cmd_reference.md | 46 ++++++++++++++++++++++++++++++ docs/how_to/delete_all_releases.md | 14 +++++++++ 3 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 docs/cmd_reference.md create mode 100644 docs/how_to/delete_all_releases.md diff --git a/README.md b/README.md index a4c91c20..363f71a4 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,14 @@ Helmsman has been packaged in Archlinux under `helmsman-bin` for the latest bina # Documentation -Documentation and How-Tos can be found [here](https://github.com/Praqma/helmsman/blob/master/docs/). +- [Documentation and How-Tos](https://github.com/Praqma/helmsman/blob/master/docs/). + +- [Desired state specification](https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md). + +- [CMD reference](https://github.com/Praqma/helmsman/blob/master/docs/cmd_reference.md) + + + Helmsman lets you: - [install/delete/upgrade/rollback your helm charts from code](https://github.com/Praqma/helmsman/blob/master/docs/how_to/manipulate_apps.md). diff --git a/docs/cmd_reference.md b/docs/cmd_reference.md new file mode 100644 index 00000000..23c381f0 --- /dev/null +++ b/docs/cmd_reference.md @@ -0,0 +1,46 @@ +--- +version: v1.7.2 +--- + +# CMD reference + +This is the list of the available CMD options in Helmsman: + +> you can find the CMD options for the version you are using by typing: `helmsman -h` or `helmsman --help` + + --apply + apply the plan directly. + --apply-labels + apply Helmsman labels to Helm state for all defined apps. + --debug + show the execution logs. + --destroy + delete all deployed releases. Purge delete is used if the purge option is set to true for the releases. + --dry-run + apply the dry-run option for helm commands. + -e value + file(s) to load environment variables from (default .env), may be supplied more than once. + -f value + desired state file name(s), may be supplied more than once to merge state files. + --keep-untracked-releases + keep releases that are managed by Helmsman and are no longer tracked in your desired state. + --no-banner + don't show the banner. + --no-color + don't use colors. + --no-fancy + don't display the banner and don't use colors. + --no-ns + don't create namespaces. + --ns-override string + override defined namespaces with this one. + --show-diff + show helm diff results. Can expose sensitive information. + --skip-validation + skip desired state validation. + --suppress-diff-secrets + don't show secrets in helm diff output. + -v + show the version. + --verbose + show verbose execution logs. \ No newline at end of file diff --git a/docs/how_to/delete_all_releases.md b/docs/how_to/delete_all_releases.md new file mode 100644 index 00000000..90c8c215 --- /dev/null +++ b/docs/how_to/delete_all_releases.md @@ -0,0 +1,14 @@ +--- +version: v1.6.2 +--- + +# delete all deployed releases + +Helmsman allows you to delete all the helm releases that were deployed by Helmsman from a given desired state. + +The `--destroy` flag will remove all deployed releases from a given desired state file (DSF). Note that this does not currently delete the namespaces nor the Kubernetes contexts created. + +The deletion of releases will respect the `purge` options in the desired state file. i.e. only if `purge` is true for release A, then the destruction of A will be a purge delete + +This was originally requested in issue [#88](https://github.com/Praqma/helmsman/issues/88). + From 39f07b6b0f7ad075e78ed070d97a779e7c1d022b Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Wed, 19 Dec 2018 12:15:20 +0100 Subject: [PATCH 0320/1127] fix markdown formatting --- docs/cmd_reference.md | 56 +++++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/docs/cmd_reference.md b/docs/cmd_reference.md index 23c381f0..484246f4 100644 --- a/docs/cmd_reference.md +++ b/docs/cmd_reference.md @@ -8,39 +8,55 @@ This is the list of the available CMD options in Helmsman: > you can find the CMD options for the version you are using by typing: `helmsman -h` or `helmsman --help` - --apply + `--apply` apply the plan directly. - --apply-labels + + `--apply-labels` apply Helmsman labels to Helm state for all defined apps. - --debug + + `--debug` show the execution logs. - --destroy + + `--destroy` delete all deployed releases. Purge delete is used if the purge option is set to true for the releases. - --dry-run + + `--dry-run` apply the dry-run option for helm commands. - -e value + + `-e value` file(s) to load environment variables from (default .env), may be supplied more than once. - -f value + + `-f value` desired state file name(s), may be supplied more than once to merge state files. - --keep-untracked-releases + + `--keep-untracked-releases` keep releases that are managed by Helmsman and are no longer tracked in your desired state. - --no-banner + + `--no-banner` don't show the banner. - --no-color + + `--no-color` don't use colors. - --no-fancy + + `--no-fancy` don't display the banner and don't use colors. - --no-ns + + `--no-ns` don't create namespaces. - --ns-override string + + `--ns-override string` override defined namespaces with this one. - --show-diff + + `--show-diff` show helm diff results. Can expose sensitive information. - --skip-validation + + `--skip-validation` skip desired state validation. - --suppress-diff-secrets + + `--suppress-diff-secrets` don't show secrets in helm diff output. - -v - show the version. - --verbose - show verbose execution logs. \ No newline at end of file + + `-v` show the version. + + `--verbose` + show verbose execution logs. From 517410c16365f6c3dd4862e76367149ce4ffb87c Mon Sep 17 00:00:00 2001 From: rothandrew Date: Fri, 28 Dec 2018 18:45:40 -0500 Subject: [PATCH 0321/1127] fix(logging): Fix typo "planing" to "planning" in log statements --- decision_maker.go | 4 ++-- docs/how_to/manipulate_apps.md | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index e2560d0b..cf7f65f7 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -170,7 +170,7 @@ func deleteRelease(r *release, rs releaseState) { Description: "deleting release [ " + r.Name + " ] from namespace [[ " + r.Namespace + " ]] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } outcome.addCommand(cmd, priority, r) - logDecision("DECISION: release [ "+r.Name+" ] is desired to be deleted "+purgeDesc+". Planing this for you!", priority, delete) + logDecision("DECISION: release [ "+r.Name+" ] is desired to be deleted "+purgeDesc+". Planning this for you!", priority, delete) } // inspectUpgradeScenario evaluates if a release should be upgraded. @@ -186,7 +186,7 @@ func inspectUpgradeScenario(r *release, rs releaseState) { if extractChartName(r.Chart) == getReleaseChartName(rs) && r.Version != getReleaseChartVersion(rs) { // upgrade upgradeRelease(r) - logDecision("DECISION: release [ "+r.Name+" ] is desired to be upgraded. Planing this for you!", r.Priority, change) + logDecision("DECISION: release [ "+r.Name+" ] is desired to be upgraded. Planning this for you!", r.Priority, change) } else if extractChartName(r.Chart) != getReleaseChartName(rs) { reInstallRelease(r, rs) diff --git a/docs/how_to/manipulate_apps.md b/docs/how_to/manipulate_apps.md index b931dbab..e16b07b7 100644 --- a/docs/how_to/manipulate_apps.md +++ b/docs/how_to/manipulate_apps.md @@ -45,8 +45,8 @@ $ helmsman --apply -f example.toml 2017/11/19 18:29:01 INFO: Executing the following plan ... --------------- Ok, I have generated a plan for you at: 2017-11-19 18:28:29.437061909 +0100 CET m=+1.987623555 -DECISION: release [ jenkins ] is desired to be deleted . Planing this for you! -DECISION: release [ artifactory ] is desired to be upgraded. Planing this for you! +DECISION: release [ jenkins ] is desired to be deleted . Planning this for you! +DECISION: release [ artifactory ] is desired to be upgraded. Planning this for you! 2017/11/19 18:29:01 INFO: attempting: -- deleting release [ jenkins ] 2017/11/19 18:29:11 INFO: attempting: -- upgrading release [ artifactory ] ``` @@ -110,7 +110,7 @@ $ helmsman --apply -f example.toml --------------- Ok, I have generated a plan for you at: 2017-11-19 18:30:43.108693039 +0100 CET m=+1.978435517 DECISION: release [ jenkins ] is currently deleted and is desired to be rolledback to namespace [[ staging ]] . No problem! -DECISION: release [ artifactory ] is desired to be upgraded. Planing this for you! +DECISION: release [ artifactory ] is desired to be upgraded. Planning this for you! 2017/11/19 18:30:49 INFO: attempting: -- rolling back release [ jenkins ] 2017/11/19 18:30:50 INFO: attempting: -- upgrading release [ artifactory ] ``` From e8a062ada723c97de00cd130e2c6aea38363fb8e Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Mon, 31 Dec 2018 10:02:40 +0000 Subject: [PATCH 0322/1127] sync dockerfiles, fix #157, fix #156 --- dockerfile/dockerfile | 16 +++++++++------- test_files/dockerfile | 19 +++++++++++++------ 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile index be6d048b..3a0e4b82 100644 --- a/dockerfile/dockerfile +++ b/dockerfile/dockerfile @@ -1,3 +1,8 @@ +# This is a docker image for helmsman + +ARG KUBE_VERSION=v1.11.3 +ARG HELM_VERSION=v2.11.0 + FROM golang:1.10-alpine3.7 as builder WORKDIR /go/src/ @@ -18,20 +23,17 @@ RUN cd helmsman \ # The image to keep FROM alpine:3.7 -RUN apk add --update --no-cache ca-certificates git openssh - -ARG HELM_VERSION=v2.10.0 -ARG KUBE_VERSION="v1.11.3" RUN apk --no-cache update \ - && rm -rf /var/cache/apk/* \ + && apk add --update --no-cache ca-certificates git openssh \ && apk add --update -t deps curl tar gzip make bash \ + && rm -rf /var/cache/apk/* \ && curl -L https://storage.googleapis.com/kubernetes-release/release/${KUBE_VERSION}/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl \ && chmod +x /usr/local/bin/kubectl \ && curl -L http://storage.googleapis.com/kubernetes-helm/helm-${HELM_VERSION}-linux-amd64.tar.gz | tar zxv -C /tmp \ && mv /tmp/linux-amd64/helm /usr/local/bin/helm \ - && chmod +x /usr/local/bin/helm \ - && rm -rf /tmp/linux-amd64 + && rm -rf /tmp/linux-amd64 \ + && chmod +x /usr/local/bin/helm COPY --from=builder /go/bin/helmsman /bin/helmsman diff --git a/test_files/dockerfile b/test_files/dockerfile index cb1475dd..b2200982 100644 --- a/test_files/dockerfile +++ b/test_files/dockerfile @@ -1,12 +1,15 @@ # This is a docker image for the helmsman test container # It can be pulled from praqma/helmsman-test -FROM golang:1.10-alpine3.7 as builder -ENV KUBE_VERSION v1.8.2 -ENV HELM_VERSION v2.10.0 +ARG KUBE_VERSION=v1.11.3 +ARG HELM_VERSION=v2.11.0 + +FROM golang:1.10-alpine3.7 as builder -RUN apk add --update --no-cache ca-certificates git \ +RUN apk --no-cache update \ + && apk add --update --no-cache ca-certificates git \ && apk add --update -t deps curl tar gzip make bash \ + && rm -rf /var/cache/apk/* \ && curl -L https://storage.googleapis.com/kubernetes-release/release/${KUBE_VERSION}/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl \ && chmod +x /usr/local/bin/kubectl \ && curl -L http://storage.googleapis.com/kubernetes-helm/helm-${HELM_VERSION}-linux-amd64.tar.gz | tar zxv -C /tmp \ @@ -21,8 +24,12 @@ RUN adduser -D -g '' helmsman && \ USER helmsman -RUN mkdir -p ~/.helm/plugins \ - && helm plugin install https://github.com/databus23/helm-diff +RUN mkdir -p ~/.helm/plugins \ + && helm plugin install https://github.com/hypnoglow/helm-s3.git \ + && helm plugin install https://github.com/nouney/helm-gcs \ + && helm plugin install https://github.com/databus23/helm-diff \ + && helm plugin install https://github.com/futuresimple/helm-secrets \ + && rm -r /tmp/helm-diff /tmp/helm-diff.tgz RUN go get github.com/goreleaser/goreleaser && \ go get github.com/golang/dep/cmd/dep From 61b20fdcad1b9fbfc7207f7ab3d0287c9d0b1c66 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Mon, 31 Dec 2018 11:26:52 +0000 Subject: [PATCH 0323/1127] make settings.kubeContext optional and respect current context in active kubeconfig file --- kube_helpers.go | 23 +++++++++++++++++++++++ state.go | 5 ++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/kube_helpers.go b/kube_helpers.go index cb9d869e..9057cc74 100644 --- a/kube_helpers.go +++ b/kube_helpers.go @@ -192,6 +192,10 @@ func createContext() (bool, string) { // setKubeContext sets your kubectl context to the one specified in the desired state file. // It returns false if it fails to set the context. This means the context does not exist. func setKubeContext(context string) bool { + if context == "" { + return getKubeContext() + } + cmd := command{ Cmd: "bash", Args: []string{"-c", "kubectl config use-context " + context}, @@ -208,6 +212,25 @@ func setKubeContext(context string) bool { return true } +// getKubeContext gets your kubectl context. +// It returns false if no context is set. +func getKubeContext() bool { + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "kubectl config current-context"}, + Description: "getting kubectl context", + } + + exitCode, result := cmd.exec(debug, verbose) + + if exitCode != 0 || result == "" { + log.Println("INFO: Kubectl context is not set") + return false + } + + return true +} + // createServiceAccount creates a service account in a given namespace and associates it with a cluster-admin role func createServiceAccount(saName string, namespace string) (bool, string) { cmd := command{ diff --git a/state.go b/state.go index d72b62da..784c791b 100644 --- a/state.go +++ b/state.go @@ -51,7 +51,7 @@ func (s state) validate() (bool, string) { // settings if s.Settings == (config{}) { return false, "ERROR: settings validation failed -- no settings table provided in state file." - } else if s.Settings.KubeContext == "" { + } else if s.Settings.KubeContext == "" && !getKubeContext() { return false, "ERROR: settings validation failed -- you have not provided a " + "kubeContext to use. Can't work without it. Sorry!" } else if s.Settings.ClusterURI != "" { @@ -60,6 +60,9 @@ func (s state) validate() (bool, string) { return false, "ERROR: settings validation failed -- clusterURI must have a valid URL set in an env variable or passed directly. Either the env var is missing/empty or the URL is invalid." } + if s.Settings.KubeContext == "" { + return false, "ERROR: settings validation failed -- KubeContext must be provided if clusterURI is defined." + } if s.Settings.Username == "" { return false, "ERROR: settings validation failed -- username must be provided if clusterURI is defined." } From 7655d07f41f2379c06c4ac33e6667d5a3dbd7f52 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Mon, 31 Dec 2018 12:09:41 +0000 Subject: [PATCH 0324/1127] Bonus: addind a --kubeconfig option --- init.go | 5 +++++ main.go | 1 + 2 files changed, 6 insertions(+) diff --git a/init.go b/init.go index 0a08a8b3..eb4df1ae 100644 --- a/init.go +++ b/init.go @@ -40,6 +40,7 @@ func init() { //parsing command line flags flag.Var(&files, "f", "desired state file name(s), may be supplied more than once to merge state files") flag.Var(&envFiles, "e", "file(s) to load environment variables from (default .env), may be supplied more than once") + flag.StringVar(&kubeconfig, "kubeconfig", "", "path to the kubeconfig file to use for CLI requests") flag.BoolVar(&apply, "apply", false, "apply the plan directly") flag.BoolVar(&debug, "debug", false, "show the execution logs") flag.BoolVar(&dryRun, "dry-run", false, "apply the dry-run option for helm commands.") @@ -96,6 +97,10 @@ func init() { os.Exit(0) } + if kubeconfig != "" { + os.Setenv("KUBECONFIG", kubeconfig) + } + if !toolExists("kubectl") { logError("ERROR: kubectl is not installed/configured correctly. Aborting!") } diff --git a/main.go b/main.go index ada9e7da..3e438535 100644 --- a/main.go +++ b/main.go @@ -21,6 +21,7 @@ var s state var debug bool var files stringArray var envFiles stringArray +var kubeconfig string var apply bool var v bool var verbose bool From 03989ee336f5e5055ab4dbc78755aef05a5c31d6 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Wed, 2 Jan 2019 13:52:30 +0000 Subject: [PATCH 0325/1127] fixes #160 --- data/role.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/role.yaml b/data/role.yaml index 59ad77ab..6b8a7ea0 100644 --- a/data/role.yaml +++ b/data/role.yaml @@ -4,6 +4,6 @@ metadata: name: helmsman-tiller namespace: <> rules: -- apiGroups: ["", "extensions", "apps"] +- apiGroups: ["", "batch", "extensions", "apps"] resources: ["*"] - verbs: ["*"] \ No newline at end of file + verbs: ["*"] From ff783120eadbe17c3488b6e2401dfc00a0007509 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Wed, 2 Jan 2019 13:35:03 +0000 Subject: [PATCH 0326/1127] allow setting helm flags in the DSF --- decision_maker.go | 16 +++++++++++++--- docs/desired_state_specification.md | 3 ++- helm_helpers.go | 2 +- release.go | 1 + 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index e2560d0b..8aabeecd 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -109,7 +109,7 @@ func installRelease(r *release) { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm install " + r.Chart + " -n " + r.Name + " --namespace " + r.Namespace + getValuesFiles(r) + " --version " + r.Version + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r) + getDryRunFlags()}, + Args: []string{"-c", "helm upgrade --install " + r.Name + " " + r.Chart + " --version " + strconv.Quote(r.Version) + " --namespace " + r.Namespace + getValuesFiles(r) + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getHelmFlags(r)}, Description: "installing release [ " + r.Name + " ] in namespace [[ " + r.Namespace + " ]] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } outcome.addCommand(cmd, r.Priority, r) @@ -249,7 +249,7 @@ func diffRelease(r *release) string { func upgradeRelease(r *release) { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + strconv.Quote(r.Version) + " --force " + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r) + getDryRunFlags()}, + Args: []string{"-c", "helm upgrade " + r.Name + " " + r.Chart + " --version " + strconv.Quote(r.Version) + getValuesFiles(r) + " --force " + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getHelmFlags(r)}, Description: "upgrading release [ " + r.Name + " ] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } @@ -269,7 +269,7 @@ func reInstallRelease(r *release, rs releaseState) { installCmd := command{ Cmd: "bash", - Args: []string{"-c", "helm install " + r.Chart+ " --version " + r.Version + " -n " + r.Name + " --namespace " + r.Namespace + getValuesFiles(r) + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r) + getDryRunFlags()}, + Args: []string{"-c", "helm upgrade --install " + r.Name + " " + r.Chart + " --version " + strconv.Quote(r.Version) + " --namespace " + r.Namespace + getValuesFiles(r) + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getHelmFlags(r)}, Description: "installing release [ " + r.Name + " ] in namespace [[ " + r.Namespace + " ]] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } outcome.addCommand(installCmd, r.Priority, r) @@ -471,3 +471,13 @@ func getDryRunFlags() string { } return "" } + +// getHelmFlags returns helm flags +func getHelmFlags(r *release) string { + var flags string + + for _, flag := range r.helmFlags { + flags = flags + " " + flag + } + return getNoHooks(r) + getTimeout(r) + getDryRunFlags() + flags +} diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 5ad61b0b..f0a06c29 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -269,7 +269,8 @@ Options: - **noHooks** : helm noHooks option. If true, it will disable pre/post upgrade hooks. Default is false. - **priority** : defines the priority of applying operations on this release. Only negative values allowed and the lower the value, the higher the priority. Default priority is 0. Apps with equal priorities will be applied in the order they were added in your state file (DSF). - **set** : is used to override certain values from values.yaml with values from environment variables (or ,starting from v1.3.0-rc, directly provided in the Desired State File). This is particularly useful for passing secrets to charts. If the an environment variable with the same name as the provided value exists, the environment variable value will be used, otherwise, the provided value will be used as is. The TOML stanza for this is `[apps..set]` -- **setString** : is used to override String values from values.yaml or chart's defaults. This uses the `--set-string` flag in helm which is available only in helm >v2.9.0. This option is useful for image tags and the like. The TOML stanza for this is `[apps..setString]` +- **setString** : is used to override String values from values.yaml or chart's defaults. This uses the `--set-string` flag in helm which is available only in helm >v2.9.0. This option is useful for image tags and the like. The TOML stanza for this is `[apps..setString]` +- **helmFlags** : array of `helm` flags, is used to pass flags to helm install/upgrade commands Example: diff --git a/helm_helpers.go b/helm_helpers.go index 3a6f7f6c..f375b67b 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -226,7 +226,7 @@ func validateReleaseCharts(apps map[string]*release) (bool, string) { if exitCode, output := cmd.exec(debug, verbose); exitCode != 0 { maybeRepo := filepath.Base(filepath.Dir(r.Chart)) - return false, "ERROR: chart at " + r.Chart + " for app [" + app + "] could not be found. Did you mean to add a repo named '" + maybeRepo +"'?" + return false, "ERROR: chart at " + r.Chart + " for app [" + app + "] could not be found. Did you mean to add a repo named '" + maybeRepo + "'?" } else { matches := versionExtractor.FindStringSubmatch(output) if len(matches) == 2 { diff --git a/release.go b/release.go index 5ec93172..e6586725 100644 --- a/release.go +++ b/release.go @@ -29,6 +29,7 @@ type release struct { TillerNamespace string `yaml:"tillerNamespace"` Set map[string]string `yaml:"set"` SetString map[string]string `yaml:"setString"` + helmFlags []string `yaml:"helmFlags"` NoHooks bool `yaml:"noHooks"` Timeout int `yaml:"timeout"` } From eb332f0ce85f563ea0dbf9c45484cbd727683bfa Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Fri, 4 Jan 2019 13:43:18 +0000 Subject: [PATCH 0327/1127] Adding a way to define a LimitRange per namespace --- README.md | 21 ++++---- command.go | 3 +- docs/desired_state_specification.md | 15 ++++++ docs/how_to/define_namespaces.md | 78 ++++++++++++++++++++++++++--- kube_helpers.go | 47 +++++++++++++++++ namespace.go | 60 ++++++++++++++++++++++ release.go | 9 ---- release_test.go | 2 +- state.go | 14 ------ state_test.go | 30 +++++------ utils.go | 15 ++++++ 11 files changed, 237 insertions(+), 57 deletions(-) create mode 100644 namespace.go diff --git a/README.md b/README.md index 363f71a4..690a812c 100644 --- a/README.md +++ b/README.md @@ -83,16 +83,17 @@ Helmsman has been packaged in Archlinux under `helmsman-bin` for the latest bina Helmsman lets you: -- [install/delete/upgrade/rollback your helm charts from code](https://github.com/Praqma/helmsman/blob/master/docs/how_to/manipulate_apps.md). -- [work safely in a multitenant cluster](https://github.com/Praqma/helmsman/blob/master/docs/how_to/multitenant_clusters_guide.md). -- [pass secrets/user input to helm charts from environment variables](https://github.com/Praqma/helmsman/blob/master/docs/how_to/pass_secrets_from_env_variables.md). -- [send Slack notifications from Helmsman](https://github.com/Praqma/helmsman/blob/master/docs/how_to/send_slack_notifications_from_helmsman.md) -- [test releases when they are first installed](https://github.com/Praqma/helmsman/blob/master/docs/how_to/test_charts.md). -- [use public and private helm charts](https://github.com/Praqma/helmsman/blob/master/docs/how_to/use_private_helm_charts.md). -- [use locally developed helm charts (the tar archives)](https://github.com/Praqma/helmsman/blob/master/docs/how_to/use_local_charts.md). -- [define namespaces to be used in your cluster](https://github.com/Praqma/helmsman/blob/master/docs/how_to/define_namespaces.md). -- [move charts across namespaces](https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md). -- [protect namespaces/releases against accidental changes](https://github.com/Praqma/helmsman/blob/master/docs/how_to/protect_namespaces_and_releases.md) +- [Install/delete/upgrade/rollback your helm charts from code](https://github.com/Praqma/helmsman/blob/master/docs/how_to/manipulate_apps.md). +- [Work safely in a multitenant cluster](https://github.com/Praqma/helmsman/blob/master/docs/how_to/multitenant_clusters_guide.md). +- [Pass secrets/user input to helm charts from environment variables](https://github.com/Praqma/helmsman/blob/master/docs/how_to/pass_secrets_from_env_variables.md). +- [Send Slack notifications from Helmsman](https://github.com/Praqma/helmsman/blob/master/docs/how_to/send_slack_notifications_from_helmsman.md). +- [Test releases when they are first installed](https://github.com/Praqma/helmsman/blob/master/docs/how_to/test_charts.md). +- [Use public and private helm charts](https://github.com/Praqma/helmsman/blob/master/docs/how_to/use_private_helm_charts.md). +- [Use locally developed helm charts (the tar archives)](https://github.com/Praqma/helmsman/blob/master/docs/how_to/use_local_charts.md). +- [Define namespaces to be used in your cluster](https://github.com/Praqma/helmsman/blob/master/docs/how_to/define_namespaces.md). +- [Define resource limits per namespace](https://github.com/Praqma/helmsman/blob/master/docs/how_to/define_namespaces.md). +- [Move charts across namespaces](https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md). +- [Protect namespaces/releases against accidental changes](https://github.com/Praqma/helmsman/blob/master/docs/how_to/protect_namespaces_and_releases.md) - [Define priorities at which releases are deployed/managed](https://github.com/Praqma/helmsman/blob/master/docs/how_to/use_the_priority_key.md) - [Override the defined namespaces to deploy all releases in a specific namespace](https://github.com/Praqma/helmsman/blob/master/docs/how_to/override_defined_namespaces.md) diff --git a/command.go b/command.go index 099246f1..cf690249 100644 --- a/command.go +++ b/command.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os/exec" + "strings" "syscall" ) @@ -35,7 +36,7 @@ func (c command) exec(debug bool, verbose bool) (int, string) { log.Println("INFO: " + c.Description) } if verbose { - log.Println("VERBOSE: " + c.Args[1]) + log.Println("VERBOSE: " + strings.Join(c.Args[1:], " ")) } cmd := exec.Command(c.Cmd, c.Args...) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 5ad61b0b..2df11804 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -134,6 +134,7 @@ Options: > By default Tiller will be deployed into `kube-system` even if you don't define kube-system in the namespaces section. To prevent deploying Tiller into `kube-system, add kube-system in your namespaces section and set its installTiller to false. -**useTiller**: defines that you would like to use an existing Tiller from that namespace. Can't be set together with `installTiller` - **labels** : defines labels to be added to the namespace, doesn't remove existing labels but updates them if the label key exists with any other different value. You can define any key/value pairs. Default is empty. +- **limits** : defines a [LimitRange](https://kubernetes.io/docs/tasks/administer-cluster/manage-resources/memory-default-namespace/) to be configured on the namespace - **tillerServiceAccount**: defines what service account to use when deploying Tiller. If this is not set, the following options are considered: @@ -171,6 +172,13 @@ clientCert = "gs://mybucket/mydir/helm.cert.pem" clientKey = "s3://mybucket/mydir/helm.key.pem" [namespaces.production.labels] env = "prod" +[namespaces.production.limits] +[namespaces.production.limits.default] +cpu = "300m" +memory = "200Mi" +[namespaces.production.limits.defaultRequest] +cpu = "200m" +memory = "100Mi" ``` ```yaml @@ -191,6 +199,13 @@ namespaces: tillerKey: "$TILLER_KEY" # where TILLER_KEY=secrets/tiller.key.pem clientCert: "gs://mybucket/mydir/helm.cert.pem" clientKey: "s3://mybucket/mydir/helm.key.pem" + limits: + default: + cpu: "300m" + memory: "200Mi" + defaultRequest: + cpu: "200m" + memory: "100Mi" labels: env: "prod" ``` diff --git a/docs/how_to/define_namespaces.md b/docs/how_to/define_namespaces.md index b5ca87cb..3f938576 100644 --- a/docs/how_to/define_namespaces.md +++ b/docs/how_to/define_namespaces.md @@ -7,14 +7,14 @@ version: v1.6.0 You can define namespaces to be used in your cluster. If they don't exist, Helmsman will create them for you. ```toml -... +#... [namespaces] [namespaces.staging] [namespaces.production] protected = true # default is false -... +#... ``` ```yaml @@ -24,7 +24,6 @@ namespaces: production: protected: true # default is false - ``` >For details on protecting a namespace, please check the [namespace/release protection guide](protect_namespaces_and_releases.md) @@ -98,7 +97,7 @@ namespaces: You can then tell Helmsman to deploy specific releases in a specific namespace: ```toml -... +#... [apps] [apps.jenkins] @@ -112,12 +111,12 @@ You can then tell Helmsman to deploy specific releases in a specific namespace: purge = false test = true -... +#... ``` ```yaml -... +#... apps: jenkins: name: "jenkins" @@ -130,9 +129,74 @@ apps: purge: false test: true -... +#... ``` In the above example, `Jenkins` will be deployed in the production namespace using the Tiller deployed in the production namespace. If the production namespace was not configured to have Tiller deployed there, Jenkins will be deployed using the Tiller in `kube-system`. +## Setting limit ranges + +As of `v1.7.3-rc`, you can instruct Helmsman to deploy `LimitRange`s into specific namespaces by setting the limits in the namespace specification. + +Example: + +```toml +[namespaces] +# to prevent deploying Tiller into kube-system, use the two lines below +# [namespaces.kube-system] +# installTiller = false # this line can be omitted since installTiller defaults to false +[namespaces.staging] +[namespaces.dev] +useTiller = true # use a Tiller which has been deployed in dev namespace +protected = false +[namespaces.production] +protected = true +installTiller = true +tillerServiceAccount = "tiller-production" +caCert = "secrets/ca.cert.pem" +tillerCert = "secrets/tiller.cert.pem" +tillerKey = "$TILLER_KEY" # where TILLER_KEY=secrets/tiller.key.pem +clientCert = "gs://mybucket/mydir/helm.cert.pem" +clientKey = "s3://mybucket/mydir/helm.key.pem" +[namespaces.production.labels] +env = "prod" +[namespaces.production.limits] +[namespaces.production.limits.default] +cpu = "300m" +memory = "200Mi" +[namespaces.production.limits.defaultRequest] +cpu = "200m" +memory = "100Mi" +``` + +```yaml +namespaces: + # to prevent deploying Tiller into kube-system, use the two lines below + # kube-system: + # installTiller: false # this line can be omitted since installTiller defaults to false + staging: + dev: + protected: false + useTiller: true # use a Tiller which has been deployed in dev namespace + production: + protected: true + installTiller: true + tillerServiceAccount: "tiller-production" + caCert: "secrets/ca.cert.pem" + tillerCert: "secrets/tiller.cert.pem" + tillerKey: "$TILLER_KEY" # where TILLER_KEY=secrets/tiller.key.pem + clientCert: "gs://mybucket/mydir/helm.cert.pem" + clientKey: "s3://mybucket/mydir/helm.key.pem" + limits: + default: + cpu: "300m" + memory: "200Mi" + defaultRequest: + cpu: "200m" + memory: "100Mi" + labels: + env: "prod" +``` + +You can read more about the `LimitRange` specification [here](https://docs.openshift.com/enterprise/3.2/dev_guide/compute_resources.html#dev-viewing-limit-ranges). diff --git a/kube_helpers.go b/kube_helpers.go index cb9d869e..2b5fa18c 100644 --- a/kube_helpers.go +++ b/kube_helpers.go @@ -1,8 +1,11 @@ package main import ( + "io/ioutil" "log" "strings" + + "gopkg.in/yaml.v2" ) // validateServiceAccount checks if k8s service account exists in a given namespace @@ -58,6 +61,7 @@ func addNamespaces(namespaces map[string]namespace) { for nsName, ns := range namespaces { createNamespace(nsName) labelNamespace(nsName, ns.Labels) + setLimits(nsName, ns.Limits) } } else { createNamespace(nsOverride) @@ -103,6 +107,49 @@ func labelNamespace(ns string, labels map[string]string) { } } +// setLimits creates a LimitRange resource in the provided Namespace +func setLimits(ns string, lims limits) { + + if lims == (limits{}) { + return + } + + definition := `--- +apiVersion: v1 +kind: LimitRange +metadata: + name: limit-range +spec: + limits: + - type: Container +` + d, err := yaml.Marshal(&lims) + if err != nil { + logError(err.Error()) + } + + definition = definition + Indent(string(d), strings.Repeat(" ", 4)) + + if err := ioutil.WriteFile("temp-LimitRange.yaml", []byte(definition), 0666); err != nil { + logError(err.Error()) + } + + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "kubectl apply -f temp-LimitRange.yaml -n " + ns}, + Description: "creating LimitRange in namespace [ " + ns + " ]", + } + + exitCode, e := cmd.exec(debug, verbose) + + if exitCode != 0 { + logError("ERROR: failed to create LimitRange in namespace [ " + ns + " ]: " + e) + } + + deleteFile("temp-LimitRange.yaml") + +} + // createContext creates a context -connecting to a k8s cluster- in kubectl config. // It returns true if successful, false otherwise func createContext() (bool, string) { diff --git a/namespace.go b/namespace.go new file mode 100644 index 00000000..9b80094a --- /dev/null +++ b/namespace.go @@ -0,0 +1,60 @@ +package main + +import ( + "fmt" +) + +// resources type +type resources struct { + Cpu string `yaml:"cpu,omitempty"` + Memory string `yaml:"memory,omitempty"` +} + +// limits type +type limits struct { + Max resources `yaml:"max,omitempty"` + Min resources `yaml:"min,omitempty"` + Default resources `yaml:"default,omitempty"` + DefaultRequest resources `yaml:"defaultRequest,omitempty"` +} + +// namespace type represents the fields of a namespace +type namespace struct { + Protected bool `yaml:"protected"` + InstallTiller bool `yaml:"installTiller"` + UseTiller bool `yaml:"useTiller"` + TillerServiceAccount string `yaml:"tillerServiceAccount"` + CaCert string `yaml:"caCert"` + TillerCert string `yaml:"tillerCert"` + TillerKey string `yaml:"tillerKey"` + ClientCert string `yaml:"clientCert"` + ClientKey string `yaml:"clientKey"` + Limits limits `yaml:"limits,omitempty"` + Labels map[string]string `yaml:"labels"` +} + +// checkNamespaceDefined checks if a given namespace is defined in the namespaces section of the desired state file +func checkNamespaceDefined(ns string, s state) bool { + _, ok := s.Namespaces[ns] + if !ok { + return false + } + return true +} + +// print prints the namespace +func (n namespace) print() { + fmt.Println("") + fmt.Println("\tprotected : ", n.Protected) + fmt.Println("\tinstallTiller : ", n.InstallTiller) + fmt.Println("\tuseTiller : ", n.UseTiller) + fmt.Println("\ttillerServiceAccount : ", n.TillerServiceAccount) + fmt.Println("\tcaCert : ", n.CaCert) + fmt.Println("\ttillerCert : ", n.TillerCert) + fmt.Println("\ttillerKey : ", n.TillerKey) + fmt.Println("\tclientCert : ", n.ClientCert) + fmt.Println("\tclientKey : ", n.ClientKey) + fmt.Println("\tlabels : ") + printMap(n.Labels, 2) + fmt.Println("------------------- ") +} diff --git a/release.go b/release.go index 5ec93172..7c618d5b 100644 --- a/release.go +++ b/release.go @@ -119,15 +119,6 @@ func validateRelease(appLabel string, r *release, names map[string]map[string]bo return true, "" } -// checkNamespaceDefined checks if a given namespace is defined in the namespaces section of the desired state file -func checkNamespaceDefined(ns string, s state) bool { - _, ok := s.Namespaces[ns] - if !ok { - return false - } - return true -} - // overrideNamespace overrides a release defined namespace with a new given one func overrideNamespace(r *release, newNs string) { log.Println("INFO: overriding namespace for app: " + r.Name) diff --git a/release_test.go b/release_test.go index 4fc832de..7a278478 100644 --- a/release_test.go +++ b/release_test.go @@ -10,7 +10,7 @@ func Test_validateRelease(t *testing.T) { Metadata: make(map[string]string), Certificates: make(map[string]string), Settings: (config{}), - Namespaces: map[string]namespace{"namespace": namespace{false, false, false, "", "", "", "", "", "", make(map[string]string)}}, + Namespaces: map[string]namespace{"namespace": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}}, HelmRepos: make(map[string]string), Apps: make(map[string]*release), } diff --git a/state.go b/state.go index d72b62da..480fca5e 100644 --- a/state.go +++ b/state.go @@ -8,20 +8,6 @@ import ( "strings" ) -// namespace type represents the fields of a namespace -type namespace struct { - Protected bool `yaml:"protected"` - InstallTiller bool `yaml:"installTiller"` - UseTiller bool `yaml:"useTiller"` - TillerServiceAccount string `yaml:"tillerServiceAccount"` - CaCert string `yaml:"caCert"` - TillerCert string `yaml:"tillerCert"` - TillerKey string `yaml:"tillerKey"` - ClientCert string `yaml:"clientCert"` - ClientKey string `yaml:"clientKey"` - Labels map[string]string `yaml:"labels"` -} - // config type represents the settings fields type config struct { KubeContext string `yaml:"kubeContext"` diff --git a/state_test.go b/state_test.go index a1f0c246..cfa63599 100644 --- a/state_test.go +++ b/state_test.go @@ -34,7 +34,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -53,7 +53,7 @@ func Test_state_validate(t *testing.T) { }, Settings: config{}, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -77,7 +77,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -98,7 +98,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -122,7 +122,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -146,7 +146,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "$URI", // unset env }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -170,7 +170,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https//192.168.99.100:8443", // invalid url }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -193,7 +193,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -214,7 +214,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -238,7 +238,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -256,7 +256,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -306,7 +306,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, }, HelmRepos: nil, Apps: make(map[string]*release), @@ -321,7 +321,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, }, HelmRepos: map[string]string{}, Apps: make(map[string]*release), @@ -336,7 +336,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -354,7 +354,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", diff --git a/utils.go b/utils.go index 5d6a4c11..22ff0715 100644 --- a/utils.go +++ b/utils.go @@ -389,3 +389,18 @@ func replaceStringInFile(input []byte, outfile string, replacements map[string]s logError(err.Error()) } } + +// Indent inserts prefix at the beginning of each non-empty line of s. The +// end-of-line marker is NL. +func Indent(s, prefix string) string { + var res []byte + bol := true + for _, c := range []byte(s) { + if bol && c != '\n' { + res = append(res, []byte(prefix)...) + } + res = append(res, c) + bol = c == '\n' + } + return string(res) +} From 5d0020c4862701772f16392cef701850e869539a Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Mon, 7 Jan 2019 09:10:42 +0000 Subject: [PATCH 0328/1127] Allow json values - fixes #148 --- release.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/release.go b/release.go index 5ec93172..227926fc 100644 --- a/release.go +++ b/release.go @@ -68,26 +68,26 @@ func validateRelease(appLabel string, r *release, names map[string]map[string]bo } _, err := os.Stat(r.ValuesFile) - if r.ValuesFile != "" && (!isOfType(r.ValuesFile, []string{".yaml", ".yml"}) || err != nil) { + if r.ValuesFile != "" && (!isOfType(r.ValuesFile, []string{".yaml", ".yml", ".json"}) || err != nil) { return false, fmt.Sprintf("valuesFile must be a valid relative (from dsf file) file path for a yaml file, or can be left empty (provided path resolved to %q).", r.ValuesFile) } else if r.ValuesFile != "" && len(r.ValuesFiles) > 0 { return false, "valuesFile and valuesFiles should not be used together." } else if len(r.ValuesFiles) > 0 { for i, filePath := range r.ValuesFiles { - if _, pathErr := os.Stat(filePath); !isOfType(filePath, []string{".yaml", ".yml"}) || pathErr != nil { + if _, pathErr := os.Stat(filePath); !isOfType(filePath, []string{".yaml", ".yml", ".json"}) || pathErr != nil { return false, fmt.Sprintf("valuesFiles must be valid relative (from dsf file) file paths for a yaml file; path at index %d provided path resolved to %q.", i, filePath) } } } _, err = os.Stat(r.SecretsFile) - if r.SecretsFile != "" && (!isOfType(r.SecretsFile, []string{".yaml", ".yml"}) || err != nil) { + if r.SecretsFile != "" && (!isOfType(r.SecretsFile, []string{".yaml", ".yml", ".json"}) || err != nil) { return false, fmt.Sprintf("secretsFile must be a valid relative (from dsf file) file path for a yaml file, or can be left empty (provided path resolved to %q).", r.SecretsFile) } else if r.SecretsFile != "" && len(r.SecretsFiles) > 0 { return false, "secretsFile and secretsFiles should not be used together." } else if len(r.SecretsFiles) > 0 { for _, filePath := range r.SecretsFiles { - if i, pathErr := os.Stat(filePath); !isOfType(filePath, []string{".yaml", ".yml"}) || pathErr != nil { + if i, pathErr := os.Stat(filePath); !isOfType(filePath, []string{".yaml", ".yml", ".json"}) || pathErr != nil { return false, fmt.Sprintf("secretsFiles must be valid relative (from dsf file) file paths for a yaml file; path at index %d provided path resolved to %q.", i, filePath) } } From 9fa63eb96a59e8f861b69fad99bef8da2e4769f1 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Mon, 7 Jan 2019 09:14:42 +0000 Subject: [PATCH 0329/1127] updated the examples --- example.toml | 7 +++++++ example.yaml | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/example.toml b/example.toml index e497f817..f1cf896b 100644 --- a/example.toml +++ b/example.toml @@ -34,6 +34,13 @@ [namespaces] [namespaces.production] protected = true + [namespaces.production.limits] + [namespaces.production.limits.default] + cpu = "300m" + memory = "200Mi" + [namespaces.production.limits.defaultRequest] + cpu = "200m" + memory = "100Mi" [namespaces.staging] protected = false installTiller = true diff --git a/example.yaml b/example.yaml index 166967ff..7bef084c 100644 --- a/example.yaml +++ b/example.yaml @@ -28,6 +28,13 @@ settings: namespaces: production: protected: true + limits: + default: + cpu: "300m" + memory: "200Mi" + defaultRequest: + cpu: "200m" + memory: "100Mi" staging: protected: false installTiller: true From c943d5021d3b4dd2e1bcac91fd3d0068d316fc35 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 8 Jan 2019 11:21:33 +0000 Subject: [PATCH 0330/1127] maintenance: update dependencies --- Gopkg.lock | 107 ++++++++++++++++++++++++++--------------------------- 1 file changed, 52 insertions(+), 55 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 460f312a..831983a8 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -17,14 +17,6 @@ revision = "97efc2c9ffd9fe8ef47f7f3203dc60bbca547374" version = "v0.28.0" -[[projects]] - digest = "1:dc27d9777febe9e63ab33a2cd15e31ce8d9463932d2472cc7097dc662dedb5ab" - name = "contrib.go.opencensus.io/exporter/stackdriver" - packages = ["propagation"] - pruneopts = "UT" - revision = "2b93072101d466aa4120b3c23c2e1b08af01541c" - version = "v0.6.0" - [[projects]] digest = "1:9f3b30d9f8e0d7040f729b82dcbc8f0dead820a133b3147ce355fc451f32d761" name = "github.com/BurntSushi/toml" @@ -41,11 +33,11 @@ "gcs", ] pruneopts = "UT" - revision = "f1dab5cebb9bc8a3cb93e74ef3de1fa4e1b24d70" - version = "v1.6.1" + revision = "cf49770b6512bc718855133097f55a2f444600a5" + version = "v1.7.2" [[projects]] - digest = "1:d76c131361e2535e8031b250f331dfd4617bfcb6564cf04c01a7371de0e4c202" + digest = "1:e2a8eda4c51943dfe50caa35e6f4318cc66c659ffcbe633e0aaa0e2ec03aa5bb" name = "github.com/aws/aws-sdk-go" packages = [ "aws", @@ -57,6 +49,7 @@ "aws/credentials", "aws/credentials/ec2rolecreds", "aws/credentials/endpointcreds", + "aws/credentials/processcreds", "aws/credentials/stscreds", "aws/csm", "aws/defaults", @@ -65,6 +58,8 @@ "aws/request", "aws/session", "aws/signer/v4", + "internal/ini", + "internal/s3err", "internal/sdkio", "internal/sdkrand", "internal/sdkuri", @@ -83,16 +78,8 @@ "service/sts", ] pruneopts = "UT" - revision = "85d9dfd77e6d694e83c3ac054141cb5e81eecc7f" - version = "v1.15.43" - -[[projects]] - digest = "1:5abd6a22805b1919f6a6bca0ae58b13cef1f3412812f38569978f43ef02743d4" - name = "github.com/go-ini/ini" - packages = ["."] - pruneopts = "UT" - revision = "5cf292cae48347c2490ac1a58fe36735fb78df7e" - version = "v1.38.2" + revision = "62936e15518acb527a1a9cb4a39d96d94d0fd9a2" + version = "v1.16.15" [[projects]] digest = "1:5d1b5a25486fc7d4e133646d834f6fca7ba1cef9903d40e7aa786c41b89e9e91" @@ -110,20 +97,23 @@ version = "v1.2.0" [[projects]] - digest = "1:e145e9710a10bc114a6d3e2738aadf8de146adaa031854ffdf7bbfe15da85e63" + digest = "1:cd9864c6366515827a759931746738ede6079faa08df9c584596370d6add135c" name = "github.com/googleapis/gax-go" - packages = ["."] + packages = [ + ".", + "v2", + ] pruneopts = "UT" - revision = "317e0006254c44a0ac427cc52a0e083ff0b9622f" - version = "v2.0.0" + revision = "c8a15bac9b9fe955bd9f900272f9a306465d28cf" + version = "v2.0.3" [[projects]] - digest = "1:77395dd3847dac9c45118c668f5dab85aedf0163dc3b38aea6578c5cf0d502f9" + digest = "1:950caca7dfcf796419232ba996c9c3539d09f26af27ba848c4508e604c13efbb" name = "github.com/hashicorp/go-version" packages = ["."] pruneopts = "UT" - revision = "b5a281d3160aa11950a6182bd9a9dc2cb1e02d50" - version = "v1.0.0" + revision = "d40cf49b3a77bba84a7afdbd7f1dc295d114efb1" + version = "v1.1.0" [[projects]] digest = "1:8eb1de8112c9924d59bf1d3e5c26f5eaa2bfc2a5fcbb92dc1c2e4546d695f277" @@ -134,11 +124,11 @@ version = "v0.3.6" [[projects]] - digest = "1:e22af8c7518e1eab6f2eab2b7d7558927f816262586cd6ed9f349c97a6c285c4" + digest = "1:bb81097a5b62634f3e9fec1014657855610c82d19b9a40c17612e32651e35dca" name = "github.com/jmespath/go-jmespath" packages = ["."] pruneopts = "UT" - revision = "0b12d6b5" + revision = "c2b33e84" [[projects]] digest = "1:ecd9aa82687cf31d1585d4ac61d0ba180e42e8a6182b85bd785fcca8dfeefc1b" @@ -150,17 +140,18 @@ [[projects]] branch = "master" - digest = "1:7616dd8d9ddca4d4d8aa0e3793f66015c8c8bf9a3f2387be6be59347f43a75c0" + digest = "1:be40f095cd741773905744f16c1f7a21fd9226ccd0529f019deb7da6d667f71c" name = "github.com/logrusorgru/aurora" packages = ["."] pruneopts = "UT" - revision = "d694e6f975a9109e2b063829d563a7c153c4b53c" + revision = "a7b3b318ed4e1ae5b80602b08627267303c68572" [[projects]] - digest = "1:ac5cb21cbe4f095b6e5f1ae5102a85dfd598d39b5ad0d64df3d41ee046586f30" + digest = "1:3b5a3bc35810830ded5e26ef9516e933083a2380d8e57371fdfde3c70d7c6952" name = "go.opencensus.io" packages = [ ".", + "exemplar", "internal", "internal/tagencoding", "plugin/ochttp", @@ -175,12 +166,12 @@ "trace/tracestate", ] pruneopts = "UT" - revision = "79993219becaa7e29e3b60cb67f5b8e82dee11d6" - version = "v0.17.0" + revision = "b7bf3cdb64150a8c8c53b769fdeb2ba581bd4d4b" + version = "v0.18.0" [[projects]] branch = "master" - digest = "1:1ae047ded1ddcbe0eca8b0772e3ff2c10e354db4c42c65b96d0386883e63904d" + digest = "1:8ecb828bb550a8c6b7d75b8261a42c369461311616ebe5451966d067f5f993bf" name = "golang.org/x/net" packages = [ "context", @@ -193,11 +184,11 @@ "trace", ] pruneopts = "UT" - revision = "4dfa2610cdf3b287375bbba5b8f2a14d3b01d8de" + revision = "45ffb0cd1ba084b73e26dee67e667e1be5acce83" [[projects]] branch = "master" - digest = "1:f645667d687fc8bf228865a2c5455824ef05bad08841e673673ef2bb89ac5b90" + digest = "1:23443edff0740e348959763085df98400dcfd85528d7771bb0ce9f5f2754ff4a" name = "golang.org/x/oauth2" packages = [ ".", @@ -207,15 +198,15 @@ "jwt", ] pruneopts = "UT" - revision = "d2e6202438beef2727060aa7cabdd924d92ebfd9" + revision = "d668ce993890a79bda886613ee587a69dd5da7a6" [[projects]] branch = "master" - digest = "1:d40e62a7b8e72fdc18e22ceddd3e6025e1d96f228b9a286abf3d3bd96d7d7a51" + digest = "1:f106a08663d0031b576f61ab81ffd80124da17096eab36aceb25b1b0d847da9a" name = "golang.org/x/sys" packages = ["unix"] pruneopts = "UT" - revision = "c2ed4eda69e7f62900806e4cd6e45f0429f859fa" + revision = "7fbe1cd0fcc20051e1fcb87fbabec4a1bacaaeba" [[projects]] digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18" @@ -241,8 +232,7 @@ version = "v0.3.0" [[projects]] - branch = "master" - digest = "1:77e161fa978e667f956c28c800dde3cd5cf6f183b2e2b392b5b3ccb266d6d474" + digest = "1:768c35ec83dd17029060ea581d6ca9fdcaef473ec87e93e4bb750949035f6070" name = "google.golang.org/api" packages = [ "gensupport", @@ -254,12 +244,14 @@ "option", "storage/v1", "transport/http", + "transport/http/internal/propagation", ] pruneopts = "UT" - revision = "76f31e4d17c2f1190154e8fab72d772db9a59a5d" + revision = "19e022d8cf43ce81f046bae8cc18c5397cc7732f" + version = "v0.1.0" [[projects]] - digest = "1:193950893ea275f89ed92e5da11ed8fa1436872f755a9ea5d4afa83dc9d9c3a8" + digest = "1:fa026a5c59bd2df343ec4a3538e6288dcf4e2ec5281d743ae82c120affe6926a" name = "google.golang.org/appengine" packages = [ ".", @@ -274,12 +266,12 @@ "urlfetch", ] pruneopts = "UT" - revision = "ae0ab99deb4dc413a2b4bd6c8bdd0eb67f1e4d06" - version = "v1.2.0" + revision = "e9657d882bb81064595ca3b56cbe2546bbabf7b1" + version = "v1.4.0" [[projects]] branch = "master" - digest = "1:8fae81abb5d7dbeee199c4fff5bbd130b832d126f0cbd99b0a6fdf3f95a8890c" + digest = "1:a7d48ca460ca1b4f6ccd8c95502443afa05df88aee84de7dbeb667a8754e8fa6" name = "google.golang.org/genproto" packages = [ "googleapis/api/annotations", @@ -288,27 +280,32 @@ "googleapis/rpc/status", ] pruneopts = "UT" - revision = "0e822944c569bf5c9afd034adaa56208bd2906ac" + revision = "bd9b4fb69e2ffd37621a6caa54dcbead29b546f2" [[projects]] - digest = "1:ab8e92d746fb5c4c18846b0879842ac8e53b3d352449423d0924a11f1020ae1b" + digest = "1:9edd250a3c46675d0679d87540b30c9ed253b19bd1fd1af08f4f5fb3c79fc487" name = "google.golang.org/grpc" packages = [ ".", "balancer", "balancer/base", "balancer/roundrobin", + "binarylog/grpc_binarylog_v1", "codes", "connectivity", "credentials", + "credentials/internal", "encoding", "encoding/proto", "grpclog", "internal", "internal/backoff", + "internal/binarylog", "internal/channelz", "internal/envconfig", "internal/grpcrand", + "internal/grpcsync", + "internal/syscall", "internal/transport", "keepalive", "metadata", @@ -322,16 +319,16 @@ "tap", ] pruneopts = "UT" - revision = "8dea3dc473e90c8179e519d91302d0597c0ca1d1" - version = "v1.15.0" + revision = "df014850f6dee74ba2fc94874043a9f3f75fbfd8" + version = "v1.17.0" [[projects]] - digest = "1:342378ac4dcb378a5448dd723f0784ae519383532f5e70ade24132c4c8693202" + digest = "1:4d2e5a73dc1500038e504a8d78b986630e3626dc027bc030ba5c75da257cdb96" name = "gopkg.in/yaml.v2" packages = ["."] pruneopts = "UT" - revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183" - version = "v2.2.1" + revision = "51d6538a90f86fe93ac480b35f37b2be17fef232" + version = "v2.2.2" [solve-meta] analyzer-name = "dep" From 7ce0f7ed5146308230514789a316bf536f9b3450 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 8 Jan 2019 11:29:12 +0000 Subject: [PATCH 0331/1127] maintenance: update dependencies --- Gopkg.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gopkg.toml b/Gopkg.toml index f8f2fc7c..5616a255 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -35,7 +35,7 @@ [[constraint]] name = "github.com/Praqma/helmsman" - version = "1.6.1" + version = "1.7.2" [[constraint]] name = "github.com/aws/aws-sdk-go" @@ -43,7 +43,7 @@ [[constraint]] name = "github.com/hashicorp/go-version" - version = "1.0.0" + version = "1.1.0" [[constraint]] name = "github.com/imdario/mergo" @@ -63,7 +63,7 @@ [[constraint]] name = "gopkg.in/yaml.v2" - version = "2.2.1" + version = "2.2.2" [prune] go-tests = true From dcba0f7e0fa7a7a165aa8b42f7aa475d70cc23fa Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 8 Jan 2019 11:38:29 +0000 Subject: [PATCH 0332/1127] update the docs --- docs/cmd_reference.md | 37 ++++++++++++++++++--------------- docs/how_to/use_local_charts.md | 4 ++-- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/docs/cmd_reference.md b/docs/cmd_reference.md index 484246f4..5c395691 100644 --- a/docs/cmd_reference.md +++ b/docs/cmd_reference.md @@ -10,53 +10,56 @@ This is the list of the available CMD options in Helmsman: `--apply` apply the plan directly. - + `--apply-labels` apply Helmsman labels to Helm state for all defined apps. - + `--debug` show the execution logs. - + `--destroy` delete all deployed releases. Purge delete is used if the purge option is set to true for the releases. - + `--dry-run` apply the dry-run option for helm commands. - + `-e value` file(s) to load environment variables from (default .env), may be supplied more than once. - + `-f value` desired state file name(s), may be supplied more than once to merge state files. - + `--keep-untracked-releases` keep releases that are managed by Helmsman and are no longer tracked in your desired state. - + `--no-banner` don't show the banner. - + `--no-color` don't use colors. - + `--no-fancy` don't display the banner and don't use colors. - + `--no-ns` don't create namespaces. - + `--ns-override string` override defined namespaces with this one. - + `--show-diff` show helm diff results. Can expose sensitive information. - + `--skip-validation` skip desired state validation. - + `--suppress-diff-secrets` don't show secrets in helm diff output. - + `-v` show the version. - + `--verbose` show verbose execution logs. + + `--kubeconfig` + path to the kubeconfig file to use for CLI requests diff --git a/docs/how_to/use_local_charts.md b/docs/how_to/use_local_charts.md index 487ba4c6..463739a1 100644 --- a/docs/how_to/use_local_charts.md +++ b/docs/how_to/use_local_charts.md @@ -4,7 +4,7 @@ version: v1.3.0-rc # use local helm charts -You can use your locally developed charts. +You can use your locally developed charts. ## Served by Helm @@ -37,6 +37,6 @@ helmRepos: ## From file system If you use a file path (relative to the DSF, or absolute) for the ```chart``` attribute -helmsman will try to resolve that chart from the local file system. The chart on the +helmsman will try to resolve that chart from the local file system. The chart on the local file system must have a version matching the version specified in the DSF. From dcfc4bf0587cac7116ac06e367f27eddd146d37e Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Thu, 10 Jan 2019 15:25:09 +0100 Subject: [PATCH 0333/1127] releasing v1.7.3-rc --- README.md | 6 +++--- docs/desired_state_specification.md | 2 +- release-notes.md | 15 ++++++++++++--- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 690a812c..49955466 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ --- -version: v1.7.2 +version: v1.7.3-rc --- ![helmsman-logo](docs/images/helmsman.png) @@ -58,9 +58,9 @@ If you use private helm repos, you will need either `helm-gcs` or `helm-s3` plug Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. ``` # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v1.7.2/helmsman_1.7.2_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.7.3-rc/helmsman_1.7.3-rc_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v1.7.2/helmsman_1.7.2_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.7.3-rc/helmsman_1.7.3-rc_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 86f80bfd..e08ab86c 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -1,5 +1,5 @@ --- -version: v1.7.2 +version: v1.7.3-rc --- # Helmsman desired state specification diff --git a/release-notes.md b/release-notes.md index 5c5323e2..58f13048 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,7 +1,16 @@ -# v1.7.2 +# v1.7.3-rc > If you are already using an older version of Helmsman than v1.4.0-rc, please read the changes below carefully and follow the upgrade guide [here](docs/migrating_to_v1.4.0-rc.md) -- Several Minor fixes and improvements: PRs #145 #142 #140 #138 #136 #134 -- Support specifying local charts in the DSFs. PR #144 (thanks to @SteveRuble) +# Fixes: +- fixing docker images helm verions with and updating dependencies. Issues: #157 #156. PR: #158 #165 +- adding `batch` to the RBAC API groups. Issue: #160. PR: #162 + +#New features: + +- allow `json` files to be used as values files. PR #164 +- adding `LimitRange` to the namespaces definitions. PR #163 +- allow using current kubecontext without specifying it and adding `--kubeconfig` flag to pass a kube config file. PR #159 +- allow users to pass additional helm flags when defining apps in the desired state files. PR #161 +- improved visibility of decisions output.PR #146 \ No newline at end of file From b1020e0f051d0e75a9ad626a4eb71e6d6cfa0dcd Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Thu, 10 Jan 2019 21:44:54 +0100 Subject: [PATCH 0334/1127] fixing dockerfiles --- dockerfile/dockerfile | 11 ++++++----- test_files/dockerfile | 14 ++++++-------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile index 3a0e4b82..ca3acd76 100644 --- a/dockerfile/dockerfile +++ b/dockerfile/dockerfile @@ -1,7 +1,5 @@ # This is a docker image for helmsman -ARG KUBE_VERSION=v1.11.3 -ARG HELM_VERSION=v2.11.0 FROM golang:1.10-alpine3.7 as builder @@ -24,6 +22,12 @@ RUN cd helmsman \ # The image to keep FROM alpine:3.7 +ARG KUBE_VERSION +ARG HELM_VERSION + +ENV KUBE_VERSION ${KUBE_VERSION:-v1.11.3} +ENV HELM_VERSION ${HELM_VERSION:-v2.11.0} + RUN apk --no-cache update \ && apk add --update --no-cache ca-certificates git openssh \ && apk add --update -t deps curl tar gzip make bash \ @@ -37,9 +41,6 @@ RUN apk --no-cache update \ COPY --from=builder /go/bin/helmsman /bin/helmsman -#RUN adduser -D -g '' helmsman -#USER helmsman - RUN mkdir -p ~/.helm/plugins \ && helm plugin install https://github.com/hypnoglow/helm-s3.git \ && helm plugin install https://github.com/nouney/helm-gcs \ diff --git a/test_files/dockerfile b/test_files/dockerfile index b2200982..4e60c84e 100644 --- a/test_files/dockerfile +++ b/test_files/dockerfile @@ -1,10 +1,13 @@ # This is a docker image for the helmsman test container # It can be pulled from praqma/helmsman-test -ARG KUBE_VERSION=v1.11.3 -ARG HELM_VERSION=v2.11.0 +ARG KUBE_VERSION +ARG HELM_VERSION -FROM golang:1.10-alpine3.7 as builder +FROM golang:1.10-alpine3.7 + +ENV KUBE_VERSION ${KUBE_VERSION:-v1.11.3} +ENV HELM_VERSION ${HELM_VERSION:-v2.11.0} RUN apk --no-cache update \ && apk add --update --no-cache ca-certificates git \ @@ -19,11 +22,6 @@ RUN apk --no-cache update \ WORKDIR src/helmsman -RUN adduser -D -g '' helmsman && \ - chown -R helmsman /go - -USER helmsman - RUN mkdir -p ~/.helm/plugins \ && helm plugin install https://github.com/hypnoglow/helm-s3.git \ && helm plugin install https://github.com/nouney/helm-gcs \ From 5e7845e25571022327f75e64b49a3a28c65cf4c6 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Mon, 14 Jan 2019 09:34:45 +0100 Subject: [PATCH 0335/1127] fixes #170 --- docs/how_to/multitenant_clusters_guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how_to/multitenant_clusters_guide.md b/docs/how_to/multitenant_clusters_guide.md index ee78b670..28ffb23a 100644 --- a/docs/how_to/multitenant_clusters_guide.md +++ b/docs/how_to/multitenant_clusters_guide.md @@ -115,7 +115,7 @@ In a multitenant setting, it is also recommended to deploy Tiller with TLS enabl [namespaces] [namespaces.kube-system] - installTiller = false # has no effect. Tiller is always deployed in kube-system + installTiller = true caCert = "secrets/kube-system/ca.cert.pem" tillerCert = "secrets/kube-system/tiller.cert.pem" tillerKey = "$TILLER_KEY" # where TILLER_KEY=secrets/kube-system/tiller.key.pem From 0cbb2d9c781f03b8f1b9ac32a15b4668c8eba73d Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Mon, 14 Jan 2019 09:45:17 +0100 Subject: [PATCH 0336/1127] updating app version --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 3e438535..44c23ce2 100644 --- a/main.go +++ b/main.go @@ -34,7 +34,7 @@ var checkCleanup bool var skipValidation bool var applyLabels bool var keepUntrackedReleases bool -var appVersion = "v1.7.2" +var appVersion = "v1.7.3-rc" var helmVersion string var kubectlVersion string var dryRun bool From 3fadd5e16ef8ff41910aca6ec4eaac66c45d8781 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Mon, 14 Jan 2019 10:21:23 +0100 Subject: [PATCH 0337/1127] allowing helm initialization when using --dry-run #169 --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 44c23ce2..16f68dff 100644 --- a/main.go +++ b/main.go @@ -51,7 +51,7 @@ func main() { checkCleanup = true } - if apply { + if apply || dryRun { // add/validate namespaces if !noNs { addNamespaces(s.Namespaces) From bdf8241a1ee673e363baa3fd661cb9e76244bc2f Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Tue, 22 Jan 2019 13:05:13 +0100 Subject: [PATCH 0338/1127] adding validation for apps Set env vars. Fixes #149 --- release.go | 10 ++++++++++ release_test.go | 19 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/release.go b/release.go index 32fdf908..67a1f843 100644 --- a/release.go +++ b/release.go @@ -117,6 +117,16 @@ func validateRelease(appLabel string, r *release, names map[string]map[string]bo } } + // add $$ escaping for $ strings + os.Setenv("HELMSMAN_DOLLAR", "$") + for k, v := range r.Set { + if strings.Contains(v, "$") { + if os.ExpandEnv(strings.Replace(v, "$$", "${HELMSMAN_DOLLAR}", -1)) == "" { + return false, "env var [ " + v + " ] is not set, but is wanted to be passed for [ " + k + " ] in [[ " + r.Name + " ]]" + } + } + } + return true, "" } diff --git a/release_test.go b/release_test.go index 7a278478..d025b544 100644 --- a/release_test.go +++ b/release_test.go @@ -260,6 +260,25 @@ func Test_validateRelease(t *testing.T) { }, want: true, want1: "", + }, { + name: "test case 14", + args: args{ + r: &release{ + Name: "release14", + Description: "", + Namespace: "namespace", + Enabled: true, + Chart: "repo/chartX", + Version: "1.0", + ValuesFiles: []string{"./test_files/values.yaml", "test_files/values2.yaml"}, + Purge: true, + Test: true, + Set: map[string]string{"some_var": "$SOME_VAR"}, + }, + s: st, + }, + want: false, + want1: "env var [ $SOME_VAR ] is not set, but is wanted to be passed for [ some_var ] in [[ release14 ]]", }, } names := make(map[string]map[string]bool) From e07c9131dd4d27bd256b02e9768ef0c8d3ffdb44 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Tue, 22 Jan 2019 13:16:46 +0100 Subject: [PATCH 0339/1127] clean Tiller certs upon exit when not creating a context. Fixes #168 --- helm_helpers.go | 1 + main.go | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/helm_helpers.go b/helm_helpers.go index f375b67b..e93eb17e 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -364,6 +364,7 @@ func deployTiller(namespace string, serviceAccount string, defaultServiceAccount tls := "" if tillerTLSEnabled(namespace) { + shouldCleanup = true // needed to activate cleaning up the certs upon exit tillerCert := namespace + "-tiller.cert" tillerKey := namespace + "-tiller.key" caCert := namespace + "-ca.cert" diff --git a/main.go b/main.go index 16f68dff..992e6b62 100644 --- a/main.go +++ b/main.go @@ -30,7 +30,7 @@ var noColors bool var noFancy bool var noNs bool var nsOverride string -var checkCleanup bool +var shouldCleanup bool var skipValidation bool var applyLabels bool var keepUntrackedReleases bool @@ -48,7 +48,7 @@ func main() { if r, msg := createContext(); !r { logError(msg) } - checkCleanup = true + shouldCleanup = true } if apply || dryRun { @@ -105,7 +105,7 @@ func main() { p.execPlan() } - if checkCleanup { + if shouldCleanup { cleanup() } From 214c9ab1ed1535903a0f61f3d33909471e26ffc0 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Tue, 22 Jan 2019 13:29:49 +0100 Subject: [PATCH 0340/1127] fix #171 --- decision_maker.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index 44f15e91..663a4f94 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -336,11 +336,14 @@ func getValuesFiles(r *release) string { logError("ERROR: helm secrets plugin is not installed/configured correctly. Aborting!") } for i := 0; i < len(r.SecretsFiles); i++ { - r.SecretsFiles[i] = r.SecretsFiles[i] if ok := decryptSecret(r.SecretsFiles[i]); !ok { logError("Failed to decrypt secret file" + r.SecretsFiles[i]) } - r.SecretsFiles[i] = r.SecretsFiles[i] + ".dec" + // if .dec extension is added before to the secret filename, don't add it again. + // This happens at upgrade time (where diff and upgrade both call this function) + if !isOfType(r.SecretsFiles[i], []string{".dec"}) { + r.SecretsFiles[i] = r.SecretsFiles[i] + ".dec" + } } fileList = append(fileList, r.SecretsFiles...) } From c6e51b4bb54c8e723d5f37e72821745c63b3aefa Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Tue, 22 Jan 2019 13:56:51 +0100 Subject: [PATCH 0341/1127] allowing helm initialization when using --destroy. fixes #166 --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 16f68dff..794ad26a 100644 --- a/main.go +++ b/main.go @@ -51,7 +51,7 @@ func main() { checkCleanup = true } - if apply || dryRun { + if apply || dryRun || destroy { // add/validate namespaces if !noNs { addNamespaces(s.Namespaces) From 990c6339166388a63674e83f206c787a67f6edda Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Tue, 22 Jan 2019 14:34:39 +0100 Subject: [PATCH 0342/1127] updating deps --- Gopkg.lock | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 831983a8..89903cb5 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -37,7 +37,7 @@ version = "v1.7.2" [[projects]] - digest = "1:e2a8eda4c51943dfe50caa35e6f4318cc66c659ffcbe633e0aaa0e2ec03aa5bb" + digest = "1:c78f02a2c6a138255ce52eefe18b62fe4e89815e4289819582c643ce51cdbf84" name = "github.com/aws/aws-sdk-go" packages = [ "aws", @@ -78,8 +78,8 @@ "service/sts", ] pruneopts = "UT" - revision = "62936e15518acb527a1a9cb4a39d96d94d0fd9a2" - version = "v1.16.15" + revision = "c1afd6c3340feedd0a2c1cd929dcb291d4bd09ba" + version = "v1.16.23" [[projects]] digest = "1:5d1b5a25486fc7d4e133646d834f6fca7ba1cef9903d40e7aa786c41b89e9e91" @@ -171,7 +171,7 @@ [[projects]] branch = "master" - digest = "1:8ecb828bb550a8c6b7d75b8261a42c369461311616ebe5451966d067f5f993bf" + digest = "1:9d2f08c64693fbe7177b5980f80c35672c80f12be79bb3bc86948b934d70e4ee" name = "golang.org/x/net" packages = [ "context", @@ -184,11 +184,11 @@ "trace", ] pruneopts = "UT" - revision = "45ffb0cd1ba084b73e26dee67e667e1be5acce83" + revision = "ed066c81e75eba56dd9bd2139ade88125b855585" [[projects]] branch = "master" - digest = "1:23443edff0740e348959763085df98400dcfd85528d7771bb0ce9f5f2754ff4a" + digest = "1:511a6232760c10dcb1ebf1ab83ef0291e2baf801f203ca6314759c5458b73a6a" name = "golang.org/x/oauth2" packages = [ ".", @@ -198,15 +198,15 @@ "jwt", ] pruneopts = "UT" - revision = "d668ce993890a79bda886613ee587a69dd5da7a6" + revision = "5dab4167f31cbd76b407f1486c86b40748bc5073" [[projects]] branch = "master" - digest = "1:f106a08663d0031b576f61ab81ffd80124da17096eab36aceb25b1b0d847da9a" + digest = "1:64ec1e1de5cec5e1579df2ff7ac10b20e3bf1cb4393846f631fb204dd9cb44f6" name = "golang.org/x/sys" packages = ["unix"] pruneopts = "UT" - revision = "7fbe1cd0fcc20051e1fcb87fbabec4a1bacaaeba" + revision = "054c452bb702e465e95ce8e7a3d9a6cf0cd1188d" [[projects]] digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18" @@ -280,10 +280,10 @@ "googleapis/rpc/status", ] pruneopts = "UT" - revision = "bd9b4fb69e2ffd37621a6caa54dcbead29b546f2" + revision = "db91494dd46c1fdcbbde05e5ff5eb56df8f7d79a" [[projects]] - digest = "1:9edd250a3c46675d0679d87540b30c9ed253b19bd1fd1af08f4f5fb3c79fc487" + digest = "1:9ab5a33d8cb5c120602a34d2e985ce17956a4e8c2edce7e6961568f95e40c09a" name = "google.golang.org/grpc" packages = [ ".", @@ -319,8 +319,8 @@ "tap", ] pruneopts = "UT" - revision = "df014850f6dee74ba2fc94874043a9f3f75fbfd8" - version = "v1.17.0" + revision = "a02b0774206b209466313a0b525d2c738fe407eb" + version = "v1.18.0" [[projects]] digest = "1:4d2e5a73dc1500038e504a8d78b986630e3626dc027bc030ba5c75da257cdb96" From ff185484321e4f6be3535917b905533818ce67ce Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Tue, 22 Jan 2019 14:41:34 +0100 Subject: [PATCH 0343/1127] releasing v1.7.3 --- README.md | 6 +++--- docs/cmd_reference.md | 2 +- docs/desired_state_specification.md | 2 +- main.go | 2 +- release-notes.md | 3 ++- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 49955466..e9ba0ac9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ --- -version: v1.7.3-rc +version: v1.7.3 --- ![helmsman-logo](docs/images/helmsman.png) @@ -58,9 +58,9 @@ If you use private helm repos, you will need either `helm-gcs` or `helm-s3` plug Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. ``` # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v1.7.3-rc/helmsman_1.7.3-rc_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.7.3/helmsman_1.7.3_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v1.7.3-rc/helmsman_1.7.3-rc_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.7.3/helmsman_1.7.3_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/docs/cmd_reference.md b/docs/cmd_reference.md index 5c395691..e674f248 100644 --- a/docs/cmd_reference.md +++ b/docs/cmd_reference.md @@ -1,5 +1,5 @@ --- -version: v1.7.2 +version: v1.7.3 --- # CMD reference diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index e08ab86c..14342244 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -1,5 +1,5 @@ --- -version: v1.7.3-rc +version: v1.7.3 --- # Helmsman desired state specification diff --git a/main.go b/main.go index b73257f9..69df88c0 100644 --- a/main.go +++ b/main.go @@ -34,7 +34,7 @@ var shouldCleanup bool var skipValidation bool var applyLabels bool var keepUntrackedReleases bool -var appVersion = "v1.7.3-rc" +var appVersion = "v1.7.3" var helmVersion string var kubectlVersion string var dryRun bool diff --git a/release-notes.md b/release-notes.md index 58f13048..a21ffa95 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,9 +1,10 @@ -# v1.7.3-rc +# v1.7.3 > If you are already using an older version of Helmsman than v1.4.0-rc, please read the changes below carefully and follow the upgrade guide [here](docs/migrating_to_v1.4.0-rc.md) # Fixes: +- Addition fixes to 1.7.3-rc: PRs #175 #176 #177 #178 - fixing docker images helm verions with and updating dependencies. Issues: #157 #156. PR: #158 #165 - adding `batch` to the RBAC API groups. Issue: #160. PR: #162 From 5ce25b03ed713540be5c224718310d1294f07b2d Mon Sep 17 00:00:00 2001 From: Greg Sidelinger Date: Wed, 30 Jan 2019 00:03:29 -0500 Subject: [PATCH 0344/1127] Adding support to specify the tiller role. --- docs/desired_state_specification.md | 3 ++- example.toml | 1 + example.yaml | 1 + helm_helpers.go | 16 ++++++---------- kube_helpers.go | 18 ++++++++++++++---- namespace.go | 2 ++ 6 files changed, 26 insertions(+), 15 deletions(-) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 14342244..d3f00934 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -132,6 +132,7 @@ Options: > For the definition of what a protected namespace means, check the [protection guide](how_to/protect_namespaces_and_releases.md) - **installTiller**: defines if Tiller should be deployed in this namespace or not. Default is false. Any chart desired to be deployed into a namespace with a Tiller deployed, will be deployed using that Tiller and not the one in kube-system unless you use the `TillerNamespace` option (see the [Apps](#apps) section below) to use another Tiller. > By default Tiller will be deployed into `kube-system` even if you don't define kube-system in the namespaces section. To prevent deploying Tiller into `kube-system, add kube-system in your namespaces section and set its installTiller to false. +-**tillerRole**: specify the role to use. If 'cluster-admin' a clusterrolebinding will be used else a rolebinding will be used. -**useTiller**: defines that you would like to use an existing Tiller from that namespace. Can't be set together with `installTiller` - **labels** : defines labels to be added to the namespace, doesn't remove existing labels but updates them if the label key exists with any other different value. You can define any key/value pairs. Default is empty. - **limits** : defines a [LimitRange](https://kubernetes.io/docs/tasks/administer-cluster/manage-resources/memory-default-namespace/) to be configured on the namespace @@ -139,7 +140,7 @@ Options: - **tillerServiceAccount**: defines what service account to use when deploying Tiller. If this is not set, the following options are considered: 1. If the `serviceAccount` defined in the `settings` section exists in the namespace you want to deploy Tiller in, it will be used, else - 2. Helmsman creates the service account in that namespace and binds it to a role. If the namespace is kube-system, the service account is bound to `cluster-admin` clusterrole. Otherwise, a new role called `helmsman-tiller` is created in that namespace and only gives access to that namespace. + 2. Helmsman creates the service account in that namespace and binds it to a role. If the namespace is kube-system, the service account is bound to `cluster-admin` clusterrole. Otherwise, a new role called `helmsman-tiller` is created in that namespace and only gives access to that namespace. The role can be overriden with the tillerRole option. > If `installTiller` is not defined or set to false, this flag is ignored. diff --git a/example.toml b/example.toml index f1cf896b..87e28bdc 100644 --- a/example.toml +++ b/example.toml @@ -45,6 +45,7 @@ protected = false installTiller = true # tillerServiceAccount = "tiller-staging" # should already exist in the staging namespace +# tillerRole = "cluster-admin" # Give tiller full access to the cluster # caCert = "secrets/ca.cert.pem" # or an env var, e.g. "$CA_CERT_PATH" # tillerCert = "secrets/tiller.cert.pem" # or S3 bucket s3://mybucket/tiller.crt # tillerKey = "secrets/tiller.key.pem" # or GCS bucket gs://mybucket/tiller.key diff --git a/example.yaml b/example.yaml index 7bef084c..31f0c66b 100644 --- a/example.yaml +++ b/example.yaml @@ -39,6 +39,7 @@ namespaces: protected: false installTiller: true #tillerServiceAccount: "tiller-staging" # should already exist in the staging namespace + #tillerRole: "cluster-admin" # Give tiller full access to the cluster #caCert: "secrets/ca.cert.pem" # or an env var, e.g. "$CA_CERT_PATH" #tillerCert: "secrets/tiller.cert.pem" # or S3 bucket s3://mybucket/tiller.crt #tillerKey: "secrets/tiller.key.pem" # or GCS bucket gs://mybucket/tiller.key diff --git a/helm_helpers.go b/helm_helpers.go index e93eb17e..ae84a850 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -322,19 +322,15 @@ func addHelmRepos(repos map[string]string) (bool, string) { // If serviceAccount is not provided (empty string), the defaultServiceAccount is used. // If no defaultServiceAccount is provided, A service account is created and Tiller is deployed with the new service account // If no namespace is provided, Tiller is deployed to kube-system -func deployTiller(namespace string, serviceAccount string, defaultServiceAccount string) (bool, string) { +func deployTiller(namespace string, serviceAccount string, defaultServiceAccount string, role string) (bool, string) { log.Println("INFO: deploying Tiller in namespace [ " + namespace + " ].") sa := "" - sharedTiller := false - if namespace == "kube-system" && serviceAccount == "" { - sharedTiller = true - } if serviceAccount != "" { if ok, err := validateServiceAccount(serviceAccount, namespace); !ok { if strings.Contains(err, "NotFound") || strings.Contains(err, "not found") { log.Println("INFO: service account [ " + serviceAccount + " ] does not exist in namespace [ " + namespace + " ] .. attempting to create it ... ") - if _, rbacErr := createRBAC(serviceAccount, namespace, sharedTiller); rbacErr != "" { + if _, rbacErr := createRBAC(serviceAccount, namespace, role); rbacErr != "" { return false, rbacErr } } else { @@ -347,7 +343,7 @@ func deployTiller(namespace string, serviceAccount string, defaultServiceAccount if strings.Contains(err, "NotFound") || strings.Contains(err, "not found") { log.Println("INFO: service account [ " + defaultServiceAccount + " ] does not exist in namespace [ " + namespace + " ] .. attempting to create it ... ") - if _, rbacErr := createRBAC(defaultServiceAccount, namespace, sharedTiller); rbacErr != "" { + if _, rbacErr := createRBAC(defaultServiceAccount, namespace, role); rbacErr != "" { return false, rbacErr } } else { @@ -403,7 +399,7 @@ func initHelm() (bool, string) { downloadFile(s.Namespaces[k].ClientKey, k+"-client.key") } if ns.InstallTiller && k != "kube-system" { - if ok, err := deployTiller(k, ns.TillerServiceAccount, defaultSA); !ok { + if ok, err := deployTiller(k, ns.TillerServiceAccount, defaultSA, ns.TillerRole); !ok { return false, err } } @@ -411,12 +407,12 @@ func initHelm() (bool, string) { if ns, ok := s.Namespaces["kube-system"]; ok { if ns.InstallTiller { - if ok, err := deployTiller("kube-system", ns.TillerServiceAccount, defaultSA); !ok { + if ok, err := deployTiller("kube-system", ns.TillerServiceAccount, defaultSA, ns.TillerRole); !ok { return false, err } } } else { - if ok, err := deployTiller("kube-system", "", defaultSA); !ok { + if ok, err := deployTiller("kube-system", "", defaultSA, ns.TillerRole); !ok { return false, err } } diff --git a/kube_helpers.go b/kube_helpers.go index 22a32642..3b34a04a 100644 --- a/kube_helpers.go +++ b/kube_helpers.go @@ -31,18 +31,18 @@ func validateServiceAccount(sa string, namespace string) (bool, string) { // createRBAC creates a k8s service account and bind it to a (Cluster)Role // If sharedTiller is true , it binds the service account to cluster-admin role. Otherwise, // It binds it to a new role called "helmsman-tiller" -func createRBAC(sa string, namespace string, sharedTiller bool) (bool, string) { +func createRBAC(sa string, namespace string, role string) (bool, string) { var ok bool var err string if ok, err = createServiceAccount(sa, namespace); ok { - if sharedTiller { - if ok, err = createRoleBinding("cluster-admin", sa, namespace); ok { + if role == "cluster-admin" { + if ok, err = createRoleBinding(role, sa, namespace); ok { return true, "" } return false, err } if ok, err = createRole(namespace); ok { - if ok, err = createRoleBinding("helmsman-tiller", sa, namespace); ok { + if ok, err = createRoleBinding(role, sa, namespace); ok { return true, "" } return false, err @@ -300,16 +300,26 @@ func createServiceAccount(saName string, namespace string) (bool, string) { func createRoleBinding(role string, saName string, namespace string) (bool, string) { clusterRole := false resource := "rolebinding" + if role == "" { + if namespace == "kube-system"{ + role = "cluster-admin" + } else { + role = "helmsman-tiller" + } + } + if role == "cluster-admin" { clusterRole = true resource = "clusterrolebinding" } + bindingOption := "--role=" + role if clusterRole { bindingOption = "--clusterrole=" + role } + log.Println("INFO: creating " + resource + " for service account [ " + saName + " ] in namespace [ " + namespace + " ] with role: " + role + ".") cmd := command{ Cmd: "bash", Args: []string{"-c", "kubectl create " + resource + " " + saName + "-binding " + bindingOption + " --serviceaccount " + namespace + ":" + saName + " -n " + namespace}, diff --git a/namespace.go b/namespace.go index 9b80094a..02f93f48 100644 --- a/namespace.go +++ b/namespace.go @@ -24,6 +24,7 @@ type namespace struct { InstallTiller bool `yaml:"installTiller"` UseTiller bool `yaml:"useTiller"` TillerServiceAccount string `yaml:"tillerServiceAccount"` + TillerRole string `yaml:"tillerRole"` CaCert string `yaml:"caCert"` TillerCert string `yaml:"tillerCert"` TillerKey string `yaml:"tillerKey"` @@ -49,6 +50,7 @@ func (n namespace) print() { fmt.Println("\tinstallTiller : ", n.InstallTiller) fmt.Println("\tuseTiller : ", n.UseTiller) fmt.Println("\ttillerServiceAccount : ", n.TillerServiceAccount) + fmt.Println("\ttillerRole: ", n.TillerRole) fmt.Println("\tcaCert : ", n.CaCert) fmt.Println("\ttillerCert : ", n.TillerCert) fmt.Println("\ttillerKey : ", n.TillerKey) From ede416d1ee9e33e237a13f4cbf24b5323d14d6f9 Mon Sep 17 00:00:00 2001 From: Greg Sidelinger Date: Thu, 31 Jan 2019 00:00:24 -0500 Subject: [PATCH 0345/1127] Updating test suite for tillerRole namespace attribute --- release_test.go | 2 +- state_test.go | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/release_test.go b/release_test.go index d025b544..7e60df6b 100644 --- a/release_test.go +++ b/release_test.go @@ -10,7 +10,7 @@ func Test_validateRelease(t *testing.T) { Metadata: make(map[string]string), Certificates: make(map[string]string), Settings: (config{}), - Namespaces: map[string]namespace{"namespace": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}}, + Namespaces: map[string]namespace{"namespace": namespace{false, false, false, "", "", "", "", "", "", "", (limits{}), make(map[string]string)}}, HelmRepos: make(map[string]string), Apps: make(map[string]*release), } diff --git a/state_test.go b/state_test.go index cfa63599..824297bf 100644 --- a/state_test.go +++ b/state_test.go @@ -34,7 +34,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", "", (limits{}), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -53,7 +53,7 @@ func Test_state_validate(t *testing.T) { }, Settings: config{}, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", "", (limits{}), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -77,7 +77,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", "", (limits{}), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -98,7 +98,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", "", (limits{}), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -122,7 +122,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", "", (limits{}), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -146,7 +146,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "$URI", // unset env }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", "", (limits{}), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -170,7 +170,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https//192.168.99.100:8443", // invalid url }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", "", (limits{}), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -193,7 +193,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", "", (limits{}), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -214,7 +214,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", "", (limits{}), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -238,7 +238,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", "", (limits{}), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -256,7 +256,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", "", (limits{}), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -306,7 +306,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", "", (limits{}), make(map[string]string)}, }, HelmRepos: nil, Apps: make(map[string]*release), @@ -321,7 +321,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", "", (limits{}), make(map[string]string)}, }, HelmRepos: map[string]string{}, Apps: make(map[string]*release), @@ -336,7 +336,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", "", (limits{}), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -354,7 +354,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", "", (limits{}), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", From 6e605ae8b94a51a7c219771ca1a22ec36298e9ba Mon Sep 17 00:00:00 2001 From: May Pongpitpitak Date: Sun, 3 Feb 2019 13:51:19 -0800 Subject: [PATCH 0346/1127] Add annotation functionality to the namespace This should allow us to specify annotations for the namespaces as well, just like labels. --- kube_helpers.go | 18 ++++++++++++++++++ namespace.go | 1 + release_test.go | 2 +- state_test.go | 30 +++++++++++++++--------------- 4 files changed, 35 insertions(+), 16 deletions(-) diff --git a/kube_helpers.go b/kube_helpers.go index 22a32642..ef878f57 100644 --- a/kube_helpers.go +++ b/kube_helpers.go @@ -61,6 +61,7 @@ func addNamespaces(namespaces map[string]namespace) { for nsName, ns := range namespaces { createNamespace(nsName) labelNamespace(nsName, ns.Labels) + annotateNamespace(nsName, ns.Annotations) setLimits(nsName, ns.Limits) } } else { @@ -107,6 +108,23 @@ func labelNamespace(ns string, labels map[string]string) { } } +// annotateNamespace annotates a namespace with provided annotations +func annotateNamespace(ns string, labels map[string]string) { + for k, v := range labels { + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "kubectl annotate --overwrite namespace/" + ns + " " + k + "=" + v}, + Description: "annotating namespace " + ns, + } + + if exitCode, _ := cmd.exec(debug, verbose); exitCode != 0 { + log.Println("WARN: I could not annotate namespace [ " + ns + " with " + k + "=" + v + + " ]. It already exists. I am skipping this.") + } + } +} + + // setLimits creates a LimitRange resource in the provided Namespace func setLimits(ns string, lims limits) { diff --git a/namespace.go b/namespace.go index 9b80094a..dcd7bc19 100644 --- a/namespace.go +++ b/namespace.go @@ -31,6 +31,7 @@ type namespace struct { ClientKey string `yaml:"clientKey"` Limits limits `yaml:"limits,omitempty"` Labels map[string]string `yaml:"labels"` + Annotations map[string]string `yaml:"annotations"` } // checkNamespaceDefined checks if a given namespace is defined in the namespaces section of the desired state file diff --git a/release_test.go b/release_test.go index d025b544..ec234094 100644 --- a/release_test.go +++ b/release_test.go @@ -10,7 +10,7 @@ func Test_validateRelease(t *testing.T) { Metadata: make(map[string]string), Certificates: make(map[string]string), Settings: (config{}), - Namespaces: map[string]namespace{"namespace": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}}, + Namespaces: map[string]namespace{"namespace": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}}, HelmRepos: make(map[string]string), Apps: make(map[string]*release), } diff --git a/state_test.go b/state_test.go index cfa63599..79abb885 100644 --- a/state_test.go +++ b/state_test.go @@ -34,7 +34,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -53,7 +53,7 @@ func Test_state_validate(t *testing.T) { }, Settings: config{}, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -77,7 +77,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -98,7 +98,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -122,7 +122,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -146,7 +146,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "$URI", // unset env }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -170,7 +170,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https//192.168.99.100:8443", // invalid url }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -193,7 +193,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -214,7 +214,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -238,7 +238,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -256,7 +256,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -306,7 +306,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: nil, Apps: make(map[string]*release), @@ -321,7 +321,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{}, Apps: make(map[string]*release), @@ -336,7 +336,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -354,7 +354,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", From c07727c92025cba3abff6f8e415f2e6f2464aa64 Mon Sep 17 00:00:00 2001 From: May Pongpitpitak Date: Wed, 6 Feb 2019 09:20:48 -0800 Subject: [PATCH 0347/1127] Update docs to show the new annotation option --- docs/desired_state_specification.md | 5 +++++ docs/how_to/define_namespaces.md | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 14342244..e6af1973 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -134,6 +134,7 @@ Options: > By default Tiller will be deployed into `kube-system` even if you don't define kube-system in the namespaces section. To prevent deploying Tiller into `kube-system, add kube-system in your namespaces section and set its installTiller to false. -**useTiller**: defines that you would like to use an existing Tiller from that namespace. Can't be set together with `installTiller` - **labels** : defines labels to be added to the namespace, doesn't remove existing labels but updates them if the label key exists with any other different value. You can define any key/value pairs. Default is empty. +- **annotations** : defines annotations to be added to the namespace. It behaves the same way as the labels option. - **limits** : defines a [LimitRange](https://kubernetes.io/docs/tasks/administer-cluster/manage-resources/memory-default-namespace/) to be configured on the namespace - **tillerServiceAccount**: defines what service account to use when deploying Tiller. If this is not set, the following options are considered: @@ -172,6 +173,8 @@ clientCert = "gs://mybucket/mydir/helm.cert.pem" clientKey = "s3://mybucket/mydir/helm.key.pem" [namespaces.production.labels] env = "prod" +[namespaces.production.annotations] +iam.amazonaws.com/role = "dynamodb-reader" [namespaces.production.limits] [namespaces.production.limits.default] cpu = "300m" @@ -208,6 +211,8 @@ namespaces: memory: "100Mi" labels: env: "prod" + annotations: + iam.amazonaws.com/role: "dynamodb-reader" ``` ## Helm Repos diff --git a/docs/how_to/define_namespaces.md b/docs/how_to/define_namespaces.md index 3f938576..f13067f8 100644 --- a/docs/how_to/define_namespaces.md +++ b/docs/how_to/define_namespaces.md @@ -161,6 +161,8 @@ clientCert = "gs://mybucket/mydir/helm.cert.pem" clientKey = "s3://mybucket/mydir/helm.key.pem" [namespaces.production.labels] env = "prod" +[namespaces.production.annotations] +iam.amazonaws.com/role = "dynamodb-reader" [namespaces.production.limits] [namespaces.production.limits.default] cpu = "300m" @@ -197,6 +199,8 @@ namespaces: memory: "100Mi" labels: env: "prod" + annotations: + iam.amazonaws.com/role: "dynamodb-reader" ``` You can read more about the `LimitRange` specification [here](https://docs.openshift.com/enterprise/3.2/dev_guide/compute_resources.html#dev-viewing-limit-ranges). From 758f42f79f6d36f58f4d9869262d0d5c97a90b07 Mon Sep 17 00:00:00 2001 From: Per Abich Date: Thu, 7 Feb 2019 10:29:15 +0100 Subject: [PATCH 0348/1127] #186: Updated the tiller role so that it can create RBAC objects in its namespace --- data/role.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/role.yaml b/data/role.yaml index 6b8a7ea0..7595d603 100644 --- a/data/role.yaml +++ b/data/role.yaml @@ -4,6 +4,6 @@ metadata: name: helmsman-tiller namespace: <> rules: -- apiGroups: ["", "batch", "extensions", "apps"] +- apiGroups: ["", "batch", "extensions", "apps", "rbac.authorization.k8s.io"] resources: ["*"] verbs: ["*"] From 3352f0f3a932adb6d7210459f78176883e745086 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sat, 9 Feb 2019 09:52:52 +0000 Subject: [PATCH 0349/1127] Fixes 181 --- decision_maker.go | 2 +- docs/desired_state_specification.md | 2 +- release.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index 663a4f94..7411b4ab 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -479,7 +479,7 @@ func getDryRunFlags() string { func getHelmFlags(r *release) string { var flags string - for _, flag := range r.helmFlags { + for _, flag := range r.HelmFlags { flags = flags + " " + flag } return getNoHooks(r) + getTimeout(r) + getDryRunFlags() + flags diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 14342244..1e276296 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -285,7 +285,7 @@ Options: - **priority** : defines the priority of applying operations on this release. Only negative values allowed and the lower the value, the higher the priority. Default priority is 0. Apps with equal priorities will be applied in the order they were added in your state file (DSF). - **set** : is used to override certain values from values.yaml with values from environment variables (or ,starting from v1.3.0-rc, directly provided in the Desired State File). This is particularly useful for passing secrets to charts. If the an environment variable with the same name as the provided value exists, the environment variable value will be used, otherwise, the provided value will be used as is. The TOML stanza for this is `[apps..set]` - **setString** : is used to override String values from values.yaml or chart's defaults. This uses the `--set-string` flag in helm which is available only in helm >v2.9.0. This option is useful for image tags and the like. The TOML stanza for this is `[apps..setString]` -- **helmFlags** : array of `helm` flags, is used to pass flags to helm install/upgrade commands +- **HelmFlags** : array of `helm` flags, is used to pass flags to helm install/upgrade commands Example: diff --git a/release.go b/release.go index 67a1f843..6f5630ca 100644 --- a/release.go +++ b/release.go @@ -29,7 +29,7 @@ type release struct { TillerNamespace string `yaml:"tillerNamespace"` Set map[string]string `yaml:"set"` SetString map[string]string `yaml:"setString"` - helmFlags []string `yaml:"helmFlags"` + HelmFlags []string `yaml:"helmFlags"` NoHooks bool `yaml:"noHooks"` Timeout int `yaml:"timeout"` } From 3fd2bf1b685e08145e39493646fe1837bd598f22 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sat, 9 Feb 2019 10:04:44 +0000 Subject: [PATCH 0350/1127] Update desired_state_specification.md --- docs/desired_state_specification.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 1e276296..14342244 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -285,7 +285,7 @@ Options: - **priority** : defines the priority of applying operations on this release. Only negative values allowed and the lower the value, the higher the priority. Default priority is 0. Apps with equal priorities will be applied in the order they were added in your state file (DSF). - **set** : is used to override certain values from values.yaml with values from environment variables (or ,starting from v1.3.0-rc, directly provided in the Desired State File). This is particularly useful for passing secrets to charts. If the an environment variable with the same name as the provided value exists, the environment variable value will be used, otherwise, the provided value will be used as is. The TOML stanza for this is `[apps..set]` - **setString** : is used to override String values from values.yaml or chart's defaults. This uses the `--set-string` flag in helm which is available only in helm >v2.9.0. This option is useful for image tags and the like. The TOML stanza for this is `[apps..setString]` -- **HelmFlags** : array of `helm` flags, is used to pass flags to helm install/upgrade commands +- **helmFlags** : array of `helm` flags, is used to pass flags to helm install/upgrade commands Example: From a17b4aa72bfec629ecf360c726b848daf66c257a Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Wed, 13 Feb 2019 10:45:54 +0100 Subject: [PATCH 0351/1127] make kube-system default binding to cluster-admin clusterrole --- bindata.go | 42 ++++++++++++++++++++--------- data/role.yaml | 4 ++- docs/desired_state_specification.md | 4 +-- kube_helpers.go | 19 ++++++------- 4 files changed, 43 insertions(+), 26 deletions(-) diff --git a/bindata.go b/bindata.go index a837d919..6d744bc3 100644 --- a/bindata.go +++ b/bindata.go @@ -6,7 +6,10 @@ package main import ( + "bytes" + "compress/gzip" "fmt" + "io" "io/ioutil" "os" "path/filepath" @@ -14,6 +17,26 @@ import ( "time" ) +func bindataRead(data []byte, name string) ([]byte, error) { + gz, err := gzip.NewReader(bytes.NewBuffer(data)) + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + + var buf bytes.Buffer + _, err = io.Copy(&buf, gz) + clErr := gz.Close() + + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + if clErr != nil { + return nil, err + } + + return buf.Bytes(), nil +} + type asset struct { bytes []byte info os.FileInfo @@ -45,18 +68,13 @@ func (fi bindataFileInfo) Sys() interface{} { return nil } -var _dataRoleYaml = []byte(`kind: Role -apiVersion: rbac.authorization.k8s.io/v1beta1 -metadata: - name: helmsman-tiller - namespace: <> -rules: -- apiGroups: ["", "extensions", "apps"] - resources: ["*"] - verbs: ["*"]`) +var _dataRoleYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x74\x8e\xc1\x4a\x03\x31\x10\x86\xef\x79\x8a\x61\x8f\xe2\xae\xf4\x26\xa1\x14\xaa\x2e\x7a\xb0\x1e\xaa\x08\x22\x1e\x26\xe9\x40\x43\xd3\x4c\x98\x49\x8a\xf8\xf4\x92\x20\x7b\xf3\x34\xf3\xfd\x3f\x33\x7c\xa7\x90\x0e\x16\xf6\x1c\xc9\x60\x0e\xef\x24\x1a\x38\x59\x10\x87\x7e\xc2\x5a\x8e\x2c\xe1\x07\x4b\xe0\x34\x9d\x6e\x75\x0a\x7c\x73\x59\x39\x2a\xb8\x32\x67\x2a\x78\xc0\x82\xd6\x00\x24\x3c\x93\x85\xf5\x5a\x38\xd2\xd8\x60\xb3\xf9\x4b\x35\xa3\xef\xd5\x02\xbd\x8a\xe8\x28\x6a\x3b\x05\xb8\xdf\xcf\xdb\xb7\xf9\x61\xbc\xfb\xb0\xf0\x34\x3f\xef\x5e\x77\xdb\x17\x23\x35\x92\x5a\x33\x02\xe6\xf0\x28\x5c\xb3\x5a\xf8\x1c\x86\x6b\x18\x1c\x16\x7f\x6c\x0b\x7d\x17\x4a\xcd\x56\x1b\x61\xce\x7d\xfe\x2b\x3e\x7c\x19\x00\x21\xe5\x2a\x9e\xfa\xb7\xab\x1e\x5d\x48\xdc\x82\xbf\x01\x00\x00\xff\xff\xfa\x4b\x3c\xe3\x0e\x01\x00\x00") func dataRoleYamlBytes() ([]byte, error) { - return _dataRoleYaml, nil + return bindataRead( + _dataRoleYaml, + "data/role.yaml", + ) } func dataRoleYaml() (*asset, error) { @@ -65,7 +83,7 @@ func dataRoleYaml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "data/role.yaml", size: 198, mode: os.FileMode(420), modTime: time.Unix(1531758991, 0)} + info := bindataFileInfo{name: "data/role.yaml", size: 270, mode: os.FileMode(420), modTime: time.Unix(1550050901, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -164,7 +182,6 @@ type bintree struct { Func func() (*asset, error) Children map[string]*bintree } - var _bintree = &bintree{nil, map[string]*bintree{ "data": &bintree{nil, map[string]*bintree{ "role.yaml": &bintree{dataRoleYaml, map[string]*bintree{}}, @@ -217,3 +234,4 @@ func _filePath(dir, name string) string { cannonicalName := strings.Replace(name, "\\", "/", -1) return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) } + diff --git a/data/role.yaml b/data/role.yaml index 7595d603..29c24b37 100644 --- a/data/role.yaml +++ b/data/role.yaml @@ -1,8 +1,10 @@ kind: Role apiVersion: rbac.authorization.k8s.io/v1beta1 metadata: - name: helmsman-tiller + name: <> namespace: <> + labels: + CREATED-BY: HELMSMAN rules: - apiGroups: ["", "batch", "extensions", "apps", "rbac.authorization.k8s.io"] resources: ["*"] diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index d65fd08e..b2922478 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -132,7 +132,7 @@ Options: > For the definition of what a protected namespace means, check the [protection guide](how_to/protect_namespaces_and_releases.md) - **installTiller**: defines if Tiller should be deployed in this namespace or not. Default is false. Any chart desired to be deployed into a namespace with a Tiller deployed, will be deployed using that Tiller and not the one in kube-system unless you use the `TillerNamespace` option (see the [Apps](#apps) section below) to use another Tiller. > By default Tiller will be deployed into `kube-system` even if you don't define kube-system in the namespaces section. To prevent deploying Tiller into `kube-system, add kube-system in your namespaces section and set its installTiller to false. --**tillerRole**: specify the role to use. If 'cluster-admin' a clusterrolebinding will be used else a rolebinding will be used. +-**tillerRole**: specify the role to use. If 'cluster-admin' a clusterrolebinding will be used else a role with a single namespace scope will be created and bound with a rolebinding. -**useTiller**: defines that you would like to use an existing Tiller from that namespace. Can't be set together with `installTiller` - **labels** : defines labels to be added to the namespace, doesn't remove existing labels but updates them if the label key exists with any other different value. You can define any key/value pairs. Default is empty. - **annotations** : defines annotations to be added to the namespace. It behaves the same way as the labels option. @@ -141,7 +141,7 @@ Options: - **tillerServiceAccount**: defines what service account to use when deploying Tiller. If this is not set, the following options are considered: 1. If the `serviceAccount` defined in the `settings` section exists in the namespace you want to deploy Tiller in, it will be used, else - 2. Helmsman creates the service account in that namespace and binds it to a role. If the namespace is kube-system, the service account is bound to `cluster-admin` clusterrole. Otherwise, a new role called `helmsman-tiller` is created in that namespace and only gives access to that namespace. The role can be overriden with the tillerRole option. + 2. Helmsman creates the service account in that namespace and binds it to a (cluster)role. If the namespace is kube-system and `tillerRole` is unset or is set to cluster-admin, the service account is bound to `cluster-admin` clusterrole. Otherwise, if you specified a `tillerRole`, a new role with that name is created and bound to the service account with rolebinding. If `tillerRole` is unset (for namespaces other than kube-system), the role is called `helmsman-tiller` and is created in the specified namespace to only gives access to that namespace. The custom role is created from a [yaml template](../data/role.yaml). > If `installTiller` is not defined or set to false, this flag is ignored. diff --git a/kube_helpers.go b/kube_helpers.go index 87f7c7a9..5062ff64 100644 --- a/kube_helpers.go +++ b/kube_helpers.go @@ -29,19 +29,19 @@ func validateServiceAccount(sa string, namespace string) (bool, string) { } // createRBAC creates a k8s service account and bind it to a (Cluster)Role -// If sharedTiller is true , it binds the service account to cluster-admin role. Otherwise, +// role can be "cluster-admin" or any other custom name // It binds it to a new role called "helmsman-tiller" func createRBAC(sa string, namespace string, role string) (bool, string) { var ok bool var err string if ok, err = createServiceAccount(sa, namespace); ok { - if role == "cluster-admin" { + if role == "cluster-admin" || (role == "" && namespace == "kube-system") { if ok, err = createRoleBinding(role, sa, namespace); ok { return true, "" } return false, err } - if ok, err = createRole(namespace); ok { + if ok, err = createRole(namespace, role); ok { if ok, err = createRoleBinding(role, sa, namespace); ok { return true, "" } @@ -124,7 +124,6 @@ func annotateNamespace(ns string, labels map[string]string) { } } - // setLimits creates a LimitRange resource in the provided Namespace func setLimits(ns string, lims limits) { @@ -319,7 +318,7 @@ func createRoleBinding(role string, saName string, namespace string) (bool, stri clusterRole := false resource := "rolebinding" if role == "" { - if namespace == "kube-system"{ + if namespace == "kube-system" { role = "cluster-admin" } else { role = "helmsman-tiller" @@ -331,7 +330,6 @@ func createRoleBinding(role string, saName string, namespace string) (bool, stri resource = "clusterrolebinding" } - bindingOption := "--role=" + role if clusterRole { bindingOption = "--clusterrole=" + role @@ -353,26 +351,25 @@ func createRoleBinding(role string, saName string, namespace string) (bool, stri return true, "" } -// createRole creates a k8s Role in a given namespace -func createRole(namespace string) (bool, string) { +// createRole creates a k8s Role in a given namespace from a template +func createRole(namespace string, role string) (bool, string) { // load static resource resource, e := Asset("data/role.yaml") if e != nil { logError(e.Error()) } - replaceStringInFile(resource, "temp-modified-role.yaml", map[string]string{"<>": namespace}) + replaceStringInFile(resource, "temp-modified-role.yaml", map[string]string{"<>": namespace, "<>": role}) cmd := command{ Cmd: "bash", Args: []string{"-c", "kubectl apply -f temp-modified-role.yaml "}, - Description: "creating role [helmsman-tiller] in namespace [ " + namespace + " ]", + Description: "creating role [" + role + "] in namespace [ " + namespace + " ]", } exitCode, err := cmd.exec(debug, verbose) if exitCode != 0 { - //logError("ERROR: failed to create Tiller role in namespace [ " + namespace + " ]: " + err) return false, err } From 23799da413ed96418bb14c4f1a472ed591349b7e Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Thu, 14 Feb 2019 13:03:19 +0100 Subject: [PATCH 0352/1127] fix #185 and update TLS validation --- decision_maker.go | 9 ++++-- helm_helpers.go | 13 +++++--- state.go | 54 +++++++++++++++++++------------- state_test.go | 80 ++++++++++++++++++++++++++++++++++++++++++++--- utils.go | 2 +- 5 files changed, 124 insertions(+), 34 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index 7411b4ab..51f98f06 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -447,18 +447,21 @@ func getCurrentTillerNamespaceFlag(rs releaseState) string { // otherwise, it will be the certs/keys for the kube-system namespace. func getTLSFlags(r *release) string { tls := "" + ns := s.Namespaces[r.TillerNamespace] if r.TillerNamespace != "" { - if tillerTLSEnabled(r.TillerNamespace) { + if tillerTLSEnabled(ns) { tls = " --tls --tls-ca-cert " + r.TillerNamespace + "-ca.cert --tls-cert " + r.TillerNamespace + "-client.cert --tls-key " + r.TillerNamespace + "-client.key " } } else if s.Namespaces[r.Namespace].InstallTiller { - if tillerTLSEnabled(r.Namespace) { + ns := s.Namespaces[r.Namespace] + if tillerTLSEnabled(ns) { tls = " --tls --tls-ca-cert " + r.Namespace + "-ca.cert --tls-cert " + r.Namespace + "-client.cert --tls-key " + r.Namespace + "-client.key " } } else { - if tillerTLSEnabled("kube-system") { + ns := s.Namespaces["kube-system"] + if tillerTLSEnabled(ns) { tls = " --tls --tls-ca-cert kube-system-ca.cert --tls-cert kube-system-client.cert --tls-key kube-system-client.key " } diff --git a/helm_helpers.go b/helm_helpers.go index ae84a850..db78165f 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -199,11 +199,12 @@ func getReleaseChartVersion(rs releaseState) string { } // getNSTLSFlags returns TLS flags for a given namespace if it's deployed with TLS -func getNSTLSFlags(ns string) string { +func getNSTLSFlags(namespace string) string { tls := "" + ns := s.Namespaces[namespace] if tillerTLSEnabled(ns) { - tls = " --tls --tls-ca-cert " + ns + "-ca.cert --tls-cert " + ns + "-client.cert --tls-key " + ns + "-client.key " + tls = " --tls --tls-ca-cert " + namespace + "-ca.cert --tls-cert " + namespace + "-client.cert --tls-key " + namespace + "-client.key " } return tls } @@ -359,7 +360,8 @@ func deployTiller(namespace string, serviceAccount string, defaultServiceAccount tillerNameSpace := " --tiller-namespace " + namespace tls := "" - if tillerTLSEnabled(namespace) { + ns := s.Namespaces[namespace] + if tillerTLSEnabled(ns) { shouldCleanup = true // needed to activate cleaning up the certs upon exit tillerCert := namespace + "-tiller.cert" tillerKey := namespace + "-tiller.key" @@ -390,7 +392,7 @@ func initHelm() (bool, string) { defaultSA := s.Settings.ServiceAccount for k, ns := range s.Namespaces { - if tillerTLSEnabled(k) { + if tillerTLSEnabled(ns) { downloadFile(s.Namespaces[k].TillerCert, k+"-tiller.cert") downloadFile(s.Namespaces[k].TillerKey, k+"-tiller.key") downloadFile(s.Namespaces[k].CaCert, k+"-ca.cert") @@ -461,7 +463,8 @@ func cleanUntrackedReleases() { func deleteUntrackedRelease(release string, tillerNamespace string) { tls := "" - if tillerTLSEnabled(tillerNamespace) { + ns := s.Namespaces[tillerNamespace] + if tillerTLSEnabled(ns) { tls = " --tls --tls-ca-cert " + tillerNamespace + "-ca.cert --tls-cert " + tillerNamespace + "-client.cert --tls-key " + tillerNamespace + "-client.key " } diff --git a/state.go b/state.go index 4d411b10..9a72cb5e 100644 --- a/state.go +++ b/state.go @@ -113,22 +113,28 @@ func (s state) validate() (bool, string) { log.Println("INFO: namespace validation -- a pre-installed Tiller is desired to be used in namespace [ " + k + " ].") } else if !ns.InstallTiller { log.Println("INFO: namespace validation -- Tiller is NOT desired to be deployed in namespace [ " + k + " ].") - } else { - if tillerTLSEnabled(k) { - // validating the TLS certs and keys for Tiller - // if they are valid, their values (if they are env vars) are substituted - var ok1, ok2, ok3, ok4, ok5 bool - ok1, ns.CaCert = isValidCert(ns.CaCert) - ok2, ns.ClientCert = isValidCert(ns.ClientCert) - ok3, ns.ClientKey = isValidCert(ns.ClientKey) - ok4, ns.TillerCert = isValidCert(ns.TillerCert) - ok5, ns.TillerKey = isValidCert(ns.TillerKey) + } + + if ns.UseTiller || ns.InstallTiller { + // validating the TLS certs and keys for Tiller + // if they are valid, their values (if they are env vars) are substituted + var ok1, ok2, ok3, ok4, ok5 bool + ok1, ns.CaCert = isValidCert(ns.CaCert) + ok2, ns.ClientCert = isValidCert(ns.ClientCert) + ok3, ns.ClientKey = isValidCert(ns.ClientKey) + ok4, ns.TillerCert = isValidCert(ns.TillerCert) + ok5, ns.TillerKey = isValidCert(ns.TillerKey) + + if ns.InstallTiller { if !ok1 || !ok2 || !ok3 || !ok4 || !ok5 { - return false, "ERROR: namespaces validation failed -- some certs/keys are not valid for Tiller TLS in namespace [ " + k + " ]." + log.Println("INFO: namespace validation -- Either no or invalid certs/keys provided for DEPLOYING Tiller with TLS in namespace [ " + k + " ].") + } + log.Println("INFO: namespace validation -- Tiller is desired to be DEPLOYED with TLS in namespace [ " + k + " ]. ") + } else if ns.UseTiller { + if !ok1 || !ok2 || !ok3 { + log.Println("INFO: namespace validation -- Either no or invalid certs/keys provided for USING Tiller with TLS in namespace [ " + k + " ].") } - log.Println("INFO: namespace validation -- Tiller is desired to be deployed with TLS in namespace [ " + k + " ]. ") - } else { - log.Println("INFO: namespace validation -- Tiller is desired to be deployed WITHOUT TLS in namespace [ " + k + " ]. ") + log.Println("INFO: namespace validation -- Tiller is desired to be USED with TLS in namespace [ " + k + " ]. ") } } } @@ -180,13 +186,19 @@ func isValidCert(value string) (bool, string) { return true, value } -// tillerTLSEnabled checks if Tiller is desired to be deployed with TLS enabled for a given namespace -// TLS is considered desired ONLY if all certs and keys for both Tiller and the Helm client are defined. -func tillerTLSEnabled(namespace string) bool { - - ns := s.Namespaces[namespace] - if ns.CaCert != "" && ns.TillerCert != "" && ns.TillerKey != "" && ns.ClientCert != "" && ns.ClientKey != "" { - return true +// tillerTLSEnabled checks if Tiller is desired to be deployed with TLS enabled for a given namespace or +// if helmsman is supposed to use an existing Tiller which is secured with TLS. +// For deploying Tiller, TLS is considered desired ONLY if all certs and keys for both Tiller and the Helm client are provided. +// For using an existing Tiller, TLS is considered desired ONLY if "CaCert" & "ClientCert" & "ClientKey" are provided. +func tillerTLSEnabled(ns namespace) bool { + if ns.UseTiller { + if ns.CaCert != "" && ns.ClientCert != "" && ns.ClientKey != "" { + return true + } + } else if ns.InstallTiller { + if ns.CaCert != "" && ns.TillerCert != "" && ns.TillerKey != "" && ns.ClientCert != "" && ns.ClientKey != "" { + return true + } } return false } diff --git a/state_test.go b/state_test.go index b9187dee..3a298a5b 100644 --- a/state_test.go +++ b/state_test.go @@ -298,7 +298,79 @@ func Test_state_validate(t *testing.T) { }, want: false, }, { - name: "test case 14 -- helmRepos/nil_value", + name: "test case 14 -- namespaces/use and install tiller", + fields: fields{ + Metadata: make(map[string]string), + Certificates: nil, + Settings: config{ + KubeContext: "minikube", + }, + Namespaces: map[string]namespace{ + "staging": namespace{false, true, true, "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + }, + HelmRepos: map[string]string{ + "stable": "https://kubernetes-charts.storage.googleapis.com", + "myrepo": "s3://my-repo/charts", + }, + Apps: make(map[string]*release), + }, + want: false, + }, { + name: "test case 15 -- namespaces/use tiller with tls-valid", + fields: fields{ + Metadata: make(map[string]string), + Certificates: nil, + Settings: config{ + KubeContext: "minikube", + }, + Namespaces: map[string]namespace{ + "staging": namespace{false, false, true, "", "", "s3://some-bucket/12345.crt", "", "", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", (limits{}), make(map[string]string), make(map[string]string)}, + }, + HelmRepos: map[string]string{ + "stable": "https://kubernetes-charts.storage.googleapis.com", + "myrepo": "s3://my-repo/charts", + }, + Apps: make(map[string]*release), + }, + want: true, + }, { + name: "test case 16 -- namespaces/use tiller with tls-not enough certs", + fields: fields{ + Metadata: make(map[string]string), + Certificates: nil, + Settings: config{ + KubeContext: "minikube", + }, + Namespaces: map[string]namespace{ + "staging": namespace{false, false, true, "", "", "", "", "", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", (limits{}), make(map[string]string), make(map[string]string)}, + }, + HelmRepos: map[string]string{ + "stable": "https://kubernetes-charts.storage.googleapis.com", + "myrepo": "s3://my-repo/charts", + }, + Apps: make(map[string]*release), + }, + want: true, + }, { + name: "test case 17 -- namespaces/deploy tiller with tls- valid", + fields: fields{ + Metadata: make(map[string]string), + Certificates: nil, + Settings: config{ + KubeContext: "minikube", + }, + Namespaces: map[string]namespace{ + "staging": namespace{false, true, false, "", "", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", (limits{}), make(map[string]string), make(map[string]string)}, + }, + HelmRepos: map[string]string{ + "stable": "https://kubernetes-charts.storage.googleapis.com", + "myrepo": "s3://my-repo/charts", + }, + Apps: make(map[string]*release), + }, + want: true, + }, { + name: "test case 18 -- helmRepos/nil_value", fields: fields{ Metadata: make(map[string]string), Certificates: nil, @@ -313,7 +385,7 @@ func Test_state_validate(t *testing.T) { }, want: false, }, { - name: "test case 15 -- helmRepos/empty", + name: "test case 19 -- helmRepos/empty", fields: fields{ Metadata: make(map[string]string), Certificates: nil, @@ -328,7 +400,7 @@ func Test_state_validate(t *testing.T) { }, want: false, }, { - name: "test case 16 -- helmRepos/empty_repo_value", + name: "test case 20 -- helmRepos/empty_repo_value", fields: fields{ Metadata: make(map[string]string), Certificates: nil, @@ -346,7 +418,7 @@ func Test_state_validate(t *testing.T) { }, want: false, }, { - name: "test case 17 -- helmRepos/invalid_repo_value", + name: "test case 21 -- helmRepos/invalid_repo_value", fields: fields{ Metadata: make(map[string]string), Certificates: nil, diff --git a/utils.go b/utils.go index 22ff0715..bb14b673 100644 --- a/utils.go +++ b/utils.go @@ -184,7 +184,7 @@ func resolvePaths(relativeToFile string, s *state) { } // resolving paths for helm certificate files for k, v := range s.Namespaces { - if tillerTLSEnabled(k) { + if tillerTLSEnabled(v) { if _, err := url.ParseRequestURI(v.CaCert); err != nil { v.CaCert, _ = filepath.Abs(filepath.Join(dir, v.CaCert)) } From d02dbc1ccfff8c0127dc21b990e4d3c43412b8a0 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Thu, 14 Feb 2019 13:03:39 +0100 Subject: [PATCH 0353/1127] updating deps --- Gopkg.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 89903cb5..a396763b 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -116,12 +116,12 @@ version = "v1.1.0" [[projects]] - digest = "1:8eb1de8112c9924d59bf1d3e5c26f5eaa2bfc2a5fcbb92dc1c2e4546d695f277" + digest = "1:a0cefd27d12712af4b5018dc7046f245e1e3b5760e2e848c30b171b570708f9b" name = "github.com/imdario/mergo" packages = ["."] pruneopts = "UT" - revision = "9f23e2d6bd2a77f959b2bf6acdbefd708a83a4a4" - version = "v0.3.6" + revision = "7c29201646fa3de8506f701213473dd407f19646" + version = "v0.3.7" [[projects]] digest = "1:bb81097a5b62634f3e9fec1014657855610c82d19b9a40c17612e32651e35dca" From b4a072ec4a0fc3c5cfa0f1d88c1e11662d301c07 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Thu, 14 Feb 2019 14:16:04 +0100 Subject: [PATCH 0354/1127] fixes #174 --- decision_maker.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index 51f98f06..ee4a0b42 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -28,10 +28,13 @@ func makePlan(s *state) *plan { // to make a release section of the desired state come true. func decide(r *release, s *state) { if destroy { - if ok, rs := helmReleaseExists(r, ""); ok { + if ok, rs := helmReleaseExists(r, "DEPLOYED"); ok { + deleteRelease(r, rs) + } + if ok, rs := helmReleaseExists(r, "FAILED"); ok { deleteRelease(r, rs) - return } + return } // check for deletion From 4d4ed2e47c5266e0c92d989960daca95bf631210 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Thu, 14 Feb 2019 14:22:45 +0100 Subject: [PATCH 0355/1127] fixing role naming with default tiller service account --- kube_helpers.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/kube_helpers.go b/kube_helpers.go index 5062ff64..a747135a 100644 --- a/kube_helpers.go +++ b/kube_helpers.go @@ -34,6 +34,13 @@ func validateServiceAccount(sa string, namespace string) (bool, string) { func createRBAC(sa string, namespace string, role string) (bool, string) { var ok bool var err string + if role == "" { + if namespace == "kube-system" { + role = "cluster-admin" + } else { + role = "helmsman-tiller" + } + } if ok, err = createServiceAccount(sa, namespace); ok { if role == "cluster-admin" || (role == "" && namespace == "kube-system") { if ok, err = createRoleBinding(role, sa, namespace); ok { @@ -317,13 +324,6 @@ func createServiceAccount(saName string, namespace string) (bool, string) { func createRoleBinding(role string, saName string, namespace string) (bool, string) { clusterRole := false resource := "rolebinding" - if role == "" { - if namespace == "kube-system" { - role = "cluster-admin" - } else { - role = "helmsman-tiller" - } - } if role == "cluster-admin" { clusterRole = true From 40fa182084814abad90b4ea40d92d5034c1460cf Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Thu, 14 Feb 2019 14:23:33 +0100 Subject: [PATCH 0356/1127] fixing tiller TLS validation logs --- state.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/state.go b/state.go index 9a72cb5e..f436998c 100644 --- a/state.go +++ b/state.go @@ -128,13 +128,15 @@ func (s state) validate() (bool, string) { if ns.InstallTiller { if !ok1 || !ok2 || !ok3 || !ok4 || !ok5 { log.Println("INFO: namespace validation -- Either no or invalid certs/keys provided for DEPLOYING Tiller with TLS in namespace [ " + k + " ].") + } else { + log.Println("INFO: namespace validation -- Tiller is desired to be DEPLOYED with TLS in namespace [ " + k + " ]. ") } - log.Println("INFO: namespace validation -- Tiller is desired to be DEPLOYED with TLS in namespace [ " + k + " ]. ") } else if ns.UseTiller { if !ok1 || !ok2 || !ok3 { log.Println("INFO: namespace validation -- Either no or invalid certs/keys provided for USING Tiller with TLS in namespace [ " + k + " ].") + } else { + log.Println("INFO: namespace validation -- Tiller is desired to be USED with TLS in namespace [ " + k + " ]. ") } - log.Println("INFO: namespace validation -- Tiller is desired to be USED with TLS in namespace [ " + k + " ]. ") } } } From 4789a757fd50cba9169814cfb2fabafaa4d71c89 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Thu, 14 Feb 2019 15:00:00 +0100 Subject: [PATCH 0357/1127] parametrizing helm diff version --- dockerfile/dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile index ca3acd76..a0f2410a 100644 --- a/dockerfile/dockerfile +++ b/dockerfile/dockerfile @@ -27,6 +27,7 @@ ARG HELM_VERSION ENV KUBE_VERSION ${KUBE_VERSION:-v1.11.3} ENV HELM_VERSION ${HELM_VERSION:-v2.11.0} +ENV HELM_DIFF_VERSION ${HELM_DIFF_VERSION:-v2.11.0+3} RUN apk --no-cache update \ && apk add --update --no-cache ca-certificates git openssh \ @@ -44,7 +45,7 @@ COPY --from=builder /go/bin/helmsman /bin/helmsman RUN mkdir -p ~/.helm/plugins \ && helm plugin install https://github.com/hypnoglow/helm-s3.git \ && helm plugin install https://github.com/nouney/helm-gcs \ - && helm plugin install https://github.com/databus23/helm-diff \ + && helm plugin install https://github.com/databus23/helm-diff --version ${HELM_DIFF_VERSION} \ && helm plugin install https://github.com/futuresimple/helm-secrets \ && rm -r /tmp/helm-diff /tmp/helm-diff.tgz From 6cec07a13804e26e81f28777df7bc22e3e85dee4 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Thu, 14 Feb 2019 15:10:20 +0100 Subject: [PATCH 0358/1127] releasing v1.7.4 --- README.md | 6 +++--- docs/desired_state_specification.md | 2 +- main.go | 2 +- release-notes.md | 15 ++++----------- 4 files changed, 9 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index e9ba0ac9..5c5a5d9f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ --- -version: v1.7.3 +version: v1.7.4 --- ![helmsman-logo](docs/images/helmsman.png) @@ -58,9 +58,9 @@ If you use private helm repos, you will need either `helm-gcs` or `helm-s3` plug Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. ``` # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v1.7.3/helmsman_1.7.3_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.7.4/helmsman_1.7.4_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v1.7.3/helmsman_1.7.3_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.7.4/helmsman_1.7.4_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index b2922478..8e11e44c 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -1,5 +1,5 @@ --- -version: v1.7.3 +version: v1.7.4 --- # Helmsman desired state specification diff --git a/main.go b/main.go index 69df88c0..339d030e 100644 --- a/main.go +++ b/main.go @@ -34,7 +34,7 @@ var shouldCleanup bool var skipValidation bool var applyLabels bool var keepUntrackedReleases bool -var appVersion = "v1.7.3" +var appVersion = "v1.7.4" var helmVersion string var kubectlVersion string var dryRun bool diff --git a/release-notes.md b/release-notes.md index a21ffa95..be86a30c 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,17 +1,10 @@ -# v1.7.3 +# v1.7.4 > If you are already using an older version of Helmsman than v1.4.0-rc, please read the changes below carefully and follow the upgrade guide [here](docs/migrating_to_v1.4.0-rc.md) # Fixes: - -- Addition fixes to 1.7.3-rc: PRs #175 #176 #177 #178 -- fixing docker images helm verions with and updating dependencies. Issues: #157 #156. PR: #158 #165 -- adding `batch` to the RBAC API groups. Issue: #160. PR: #162 +- Bug fixes for: #174 #185 #181 #125 #186 #New features: - -- allow `json` files to be used as values files. PR #164 -- adding `LimitRange` to the namespaces definitions. PR #163 -- allow using current kubecontext without specifying it and adding `--kubeconfig` flag to pass a kube config file. PR #159 -- allow users to pass additional helm flags when defining apps in the desired state files. PR #161 -- improved visibility of decisions output.PR #146 \ No newline at end of file +- Adding annotation functionality for namespaces (PR #184) +- Adding support to specify Tiller(s) role. \ No newline at end of file From 459517fc8dbabd64658a66b850691630f3fcc647 Mon Sep 17 00:00:00 2001 From: Peter Turi Date: Thu, 21 Feb 2019 17:21:30 +0100 Subject: [PATCH 0359/1127] Add support for externally configured helm repos This patch adds support for externally configured helm repositories, which is a handy tool if you are using Artifcatory as helm repository server. --- docs/desired_state_specification.md | 22 ++++++++++++++++++++++ helm_helpers.go | 1 + state.go | 13 +++++++------ utils.go | 10 ++++++++++ 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 8e11e44c..b3064de9 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -251,6 +251,28 @@ helmRepos: myGCSrepo: "gs://my-GCS-private-repo/charts" ``` +## Preconfigured Helm Repos + +Optional : Yes. + +Synopsis: defines the list of helm repositories that the helmsman will consider already preconfigured and thus will not try to overwrite it's configuration. + +The primary use-case is if you have some helm repositories that require HTTP basic authentication and you don't want to store the password into the helmsman.yaml. In this case you can execute the following sequence to have those repositories configured: + +Set up the helmsman configuration: + +```toml +preconfiguredHelmRepos = [ "myrepo1", "myrepo2" ] +``` + +```yaml +preconfiguredHelmRepos: +- myrepo1 +- myrepo2 +``` + +> In this case you will manually need to execute `helm repo add myrepo1 --username= --password=` + ## Apps Optional : Yes. diff --git a/helm_helpers.go b/helm_helpers.go index db78165f..ce090028 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -294,6 +294,7 @@ func addHelmRepos(repos map[string]string) (bool, string) { if strings.HasPrefix(url, "gs://") { gcs.Auth() } + cmd := command{ Cmd: "bash", Args: []string{"-c", "helm repo add " + repoName + " " + strconv.Quote(url)}, diff --git a/state.go b/state.go index f436998c..f19d9b4d 100644 --- a/state.go +++ b/state.go @@ -22,12 +22,13 @@ type config struct { // state type represents the desired state of applications on a k8s cluster. type state struct { - Metadata map[string]string `yaml:"metadata"` - Certificates map[string]string `yaml:"certificates"` - Settings config `yaml:"settings"` - Namespaces map[string]namespace `yaml:"namespaces"` - HelmRepos map[string]string `yaml:"helmRepos"` - Apps map[string]*release `yaml:"apps"` + Metadata map[string]string `yaml:"metadata"` + Certificates map[string]string `yaml:"certificates"` + Settings config `yaml:"settings"` + Namespaces map[string]namespace `yaml:"namespaces"` + HelmRepos map[string]string `yaml:"helmRepos"` + PreconfiguredHelmRepos []string `yaml:"preconfiguredHelmRepos"` + Apps map[string]*release `yaml:"apps"` } // validate validates that the values specified in the desired state are valid according to the desired state spec. diff --git a/utils.go b/utils.go index bb14b673..3c13ca83 100644 --- a/utils.go +++ b/utils.go @@ -141,6 +141,15 @@ func toFile(file string, s *state) { } } +func stringInSlice(a string, list []string) bool { + for _, b := range list { + if b == a { + return true + } + } + return false +} + func resolvePaths(relativeToFile string, s *state) { dir := filepath.Dir(relativeToFile) @@ -161,6 +170,7 @@ func resolvePaths(relativeToFile string, s *state) { if v.Chart != "" { var repoOrDir = filepath.Dir(v.Chart) _, isRepo := s.HelmRepos[repoOrDir] + isRepo = isRepo || stringInSlice(repoOrDir, s.PreconfiguredHelmRepos) if !isRepo { // if there is no repo for the chart, we assume it's intended to be a local path From 933e0b20ba1d3eabd955dfa1de1eb5ee37654f34 Mon Sep 17 00:00:00 2001 From: Matthew Coleman Date: Mon, 25 Feb 2019 22:58:54 -0500 Subject: [PATCH 0360/1127] Fix documentation formatting --- docs/desired_state_specification.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 8e11e44c..d1b7dfa5 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -78,7 +78,7 @@ Optional : No. Synopsis: provides settings for connecting to your k8s cluster and configuring Helm's Tiller in the cluster. Options: -- kubeContext : this is always required and defines what context to use in kubectl. Helmsman will try connect to this context first, if it does not exist, it will try to create it (i.e. connect to a k8s cluster) using the options below. +- **kubeContext** : this is always required and defines what context to use in kubectl. Helmsman will try connect to this context first, if it does not exist, it will try to create it (i.e. connect to a k8s cluster) using the options below. The following options can be skipped if your kubectl context is already created and you don't want Helmsman to connect kubectl to your cluster for you. When using Helmsman in CI pipeline, these details are required to connect to your cluster every time the pipeline is executed. @@ -132,8 +132,8 @@ Options: > For the definition of what a protected namespace means, check the [protection guide](how_to/protect_namespaces_and_releases.md) - **installTiller**: defines if Tiller should be deployed in this namespace or not. Default is false. Any chart desired to be deployed into a namespace with a Tiller deployed, will be deployed using that Tiller and not the one in kube-system unless you use the `TillerNamespace` option (see the [Apps](#apps) section below) to use another Tiller. > By default Tiller will be deployed into `kube-system` even if you don't define kube-system in the namespaces section. To prevent deploying Tiller into `kube-system, add kube-system in your namespaces section and set its installTiller to false. --**tillerRole**: specify the role to use. If 'cluster-admin' a clusterrolebinding will be used else a role with a single namespace scope will be created and bound with a rolebinding. --**useTiller**: defines that you would like to use an existing Tiller from that namespace. Can't be set together with `installTiller` +- **tillerRole**: specify the role to use. If 'cluster-admin' a clusterrolebinding will be used else a role with a single namespace scope will be created and bound with a rolebinding. +- **useTiller**: defines that you would like to use an existing Tiller from that namespace. Can't be set together with `installTiller` - **labels** : defines labels to be added to the namespace, doesn't remove existing labels but updates them if the label key exists with any other different value. You can define any key/value pairs. Default is empty. - **annotations** : defines annotations to be added to the namespace. It behaves the same way as the labels option. - **limits** : defines a [LimitRange](https://kubernetes.io/docs/tasks/administer-cluster/manage-resources/memory-default-namespace/) to be configured on the namespace From b4370b2f076477ff0c965fbd42d4481c6c355722 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Tue, 26 Feb 2019 16:35:51 +0100 Subject: [PATCH 0361/1127] allowing cluster connection with bearer token. Fixes #167 #197 #203 --- kube_helpers.go | 42 +++++++++++++++++++------- main.go | 4 +++ state.go | 78 ++++++++++++++++++++++++++----------------------- state_test.go | 4 +-- utils.go | 8 ++++- 5 files changed, 85 insertions(+), 51 deletions(-) diff --git a/kube_helpers.go b/kube_helpers.go index a747135a..daa63153 100644 --- a/kube_helpers.go +++ b/kube_helpers.go @@ -177,13 +177,20 @@ spec: // createContext creates a context -connecting to a k8s cluster- in kubectl config. // It returns true if successful, false otherwise func createContext() (bool, string) { - - if s.Settings.Password == "" || s.Settings.Username == "" || s.Settings.ClusterURI == "" { - return false, "ERROR: failed to create context [ " + s.Settings.KubeContext + " ] " + - "as you did not specify enough information in the Settings section of your desired state file." - } else if s.Certificates == nil || s.Certificates["caCrt"] == "" || s.Certificates["caKey"] == "" { - return false, "ERROR: failed to create context [ " + s.Settings.KubeContext + " ] " + - "as you did not provide Certifications to use in your desired state file." + if s.Settings.BearerToken && s.Settings.BearerTokenPath == "" { + log.Println("INFO: creating kube context with bearer token from K8S service account.") + s.Settings.BearerTokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token" + } else if s.Settings.BearerToken && s.Settings.BearerTokenPath != "" { + log.Println("INFO: creating kube context with bearer token from " + s.Settings.BearerTokenPath) + } else if s.Settings.Password == "" || s.Settings.Username == "" || s.Settings.ClusterURI == "" { + return false, "ERROR: missing information to create context [ " + s.Settings.KubeContext + " ] " + + "you are either missing PASSWORD, USERNAME or CLUSTERURI in the Settings section of your desired state file." + } else if !s.Settings.BearerToken && (s.Certificates == nil || s.Certificates["caCrt"] == "" || s.Certificates["caKey"] == "") { + return false, "ERROR: missing information to create context [ " + s.Settings.KubeContext + " ] " + + "you are either missing caCrt or caKey or both in the Certifications section of your desired state file." + } else if s.Settings.BearerToken && (s.Certificates == nil || s.Certificates["caCrt"] == "") { + return false, "ERROR: missing information to create context [ " + s.Settings.KubeContext + " ] " + + "caCrt is missing in the Certifications section of your desired state file." } // set certs locations (relative filepath, GCS bucket, AWS bucket) @@ -215,11 +222,24 @@ func createContext() (bool, string) { } + // bearer token + tokenPath := "bearer.token" + if s.Settings.BearerToken && s.Settings.BearerTokenPath != "" { + downloadFile(s.Settings.BearerTokenPath, tokenPath) + } + // connecting to the cluster - setCredentialsCmd := "kubectl config set-credentials " + s.Settings.Username + " --username=" + s.Settings.Username + - " --password=" + s.Settings.Password + " --client-key=" + caKey - if caClient != "" { - setCredentialsCmd = setCredentialsCmd + " --client-certificate=" + caClient + setCredentialsCmd := "" + if s.Settings.BearerToken { + token := readFile(tokenPath) + setCredentialsCmd = "kubectl config set-credentials " + s.Settings.Username + " --username=" + s.Settings.Username + + " --token=" + token + } else { + setCredentialsCmd = "kubectl config set-credentials " + s.Settings.Username + " --username=" + s.Settings.Username + + " --password=" + s.Settings.Password + " --client-key=" + caKey + if caClient != "" { + setCredentialsCmd = setCredentialsCmd + " --client-certificate=" + caClient + } } cmd := command{ Cmd: "bash", diff --git a/main.go b/main.go index 339d030e..7f78d364 100644 --- a/main.go +++ b/main.go @@ -128,6 +128,10 @@ func cleanup() { deleteFile("client.crt") } + if _, err := os.Stat("bearer.token"); err == nil { + deleteFile("bearer.token") + } + for k := range s.Namespaces { if _, err := os.Stat(k + "-tiller.cert"); err == nil { deleteFile(k + "-tiller.cert") diff --git a/state.go b/state.go index f436998c..4f8ca512 100644 --- a/state.go +++ b/state.go @@ -10,14 +10,16 @@ import ( // config type represents the settings fields type config struct { - KubeContext string `yaml:"kubeContext"` - Username string `yaml:"username"` - Password string `yaml:"password"` - ClusterURI string `yaml:"clusterURI"` - ServiceAccount string `yaml:"serviceAccount"` - StorageBackend string `yaml:"storageBackend"` - SlackWebhook string `yaml:"slackWebhook"` - ReverseDelete bool `yaml:"reverseDelete"` + KubeContext string `yaml:"kubeContext"` + Username string `yaml:"username"` + Password string `yaml:"password"` + ClusterURI string `yaml:"clusterURI"` + ServiceAccount string `yaml:"serviceAccount"` + StorageBackend string `yaml:"storageBackend"` + SlackWebhook string `yaml:"slackWebhook"` + ReverseDelete bool `yaml:"reverseDelete"` + BearerToken bool `yaml:"bearerToken"` + BearerTokenPath string `yaml:"bearerTokenPath"` } // state type represents the desired state of applications on a k8s cluster. @@ -35,17 +37,14 @@ type state struct { func (s state) validate() (bool, string) { // settings - if s.Settings == (config{}) { - return false, "ERROR: settings validation failed -- no settings table provided in state file." - } else if s.Settings.KubeContext == "" && !getKubeContext() { - return false, "ERROR: settings validation failed -- you have not provided a " + - "kubeContext to use. Can't work without it. Sorry!" + if s.Settings.KubeContext == "" && !getKubeContext() { + return false, "ERROR: settings validation failed -- you have not defined a " + + "kubeContext to use. Either define it in the desired state file or pass a kubeconfig with --kubeconfig to use an existing context." } else if s.Settings.ClusterURI != "" { if _, err := url.ParseRequestURI(s.Settings.ClusterURI); err != nil { return false, "ERROR: settings validation failed -- clusterURI must have a valid URL set in an env variable or passed directly. Either the env var is missing/empty or the URL is invalid." } - if s.Settings.KubeContext == "" { return false, "ERROR: settings validation failed -- KubeContext must be provided if clusterURI is defined." } @@ -53,11 +52,12 @@ func (s state) validate() (bool, string) { return false, "ERROR: settings validation failed -- username must be provided if clusterURI is defined." } if s.Settings.Password == "" { - return false, "ERROR: settings validation failed -- password must be provided if clusterURI is defined." + return false, "ERROR: settings validation failed -- password must be provided (directly or from env var) if clusterURI is defined." } - - if s.Settings.Password == "" { - return false, "ERROR: settings validation failed -- password should be set as an env variable. It is currently missing or empty. " + if s.Settings.BearerToken && s.Settings.BearerTokenPath != "" { + if _, err := os.Stat(s.Settings.BearerTokenPath); err != nil { + return false, "ERROR: settings validation failed -- Bearer Token path " + s.Settings.BearerTokenPath + " is not found. The path has to be relative to the desired state file." + } } } @@ -70,31 +70,35 @@ func (s state) validate() (bool, string) { // certificates if s.Certificates != nil && len(s.Certificates) != 0 { - ok1 := false - if s.Settings.ClusterURI != "" { - ok1 = true + + for key, value := range s.Certificates { + r, path := isValidCert(value) + if !r { + return false, "ERROR: certifications validation failed -- [ " + key + " ] must be a valid S3 or GCS bucket URL or a valid relative file path." + } + s.Certificates[key] = path } - _, ok2 := s.Certificates["caCrt"] - _, ok3 := s.Certificates["caKey"] - if ok1 && (!ok2 || !ok3) { - return false, "ERROR: certifications validation failed -- You want me to connect to your cluster for you " + - "but have not given me the cert/key to do so. Please add [caCrt] and [caKey] under Certifications. You might also need to provide [clientCrt]." - } else if ok1 { - for key, value := range s.Certificates { - r, path := isValidCert(value) - if !r { - return false, "ERROR: certifications validation failed -- [ " + key + " ] must be a valid S3 or GCS bucket URL or a valid relative file path." - } - s.Certificates[key] = path + + _, caCrt := s.Certificates["caCrt"] + _, caKey := s.Certificates["caKey"] + + if s.Settings.ClusterURI != "" && !s.Settings.BearerToken { + if !caCrt || !caKey { + return false, "ERROR: certificates validation failed -- You want me to connect to your cluster for you " + + "but have not given me the cert/key to do so. Please add [caCrt] and [caKey] under Certifications. You might also need to provide [clientCrt]." + } + + } else if s.Settings.ClusterURI != "" && s.Settings.BearerToken { + if !caCrt { + return false, "ERROR: certificates validation failed -- You want me to connect to your cluster with a bearer token " + + "but have not provided [caCrt] in the certificates section of your desired state." } - } else { - log.Println("INFO: certificates provided but not needed. Skipping certificates validation.") } } else { if s.Settings.ClusterURI != "" { - return false, "ERROR: certifications validation failed -- You want me to connect to your cluster for you " + - "but have not given me the cert/key to do so. Please add [caCrt] and [caKey] under Certifications. You might also need to provide [clientCrt]." + return false, "ERROR: certificates validation failed -- You want me to connect to your cluster for you " + + "but have not given me the cert/key to do so. Please add [caCrt] and/or [caKey] under Certifications. You might also need to provide [clientCrt]." } } diff --git a/state_test.go b/state_test.go index 3a298a5b..95219791 100644 --- a/state_test.go +++ b/state_test.go @@ -44,7 +44,7 @@ func Test_state_validate(t *testing.T) { }, want: true, }, { - name: "test case 2 -- settings/nil_value", + name: "test case 2 -- settings/nil_value is allowed", fields: fields{ Metadata: make(map[string]string), Certificates: map[string]string{ @@ -61,7 +61,7 @@ func Test_state_validate(t *testing.T) { }, Apps: make(map[string]*release), }, - want: false, + want: true, }, { name: "test case 3 -- settings/empty_context", fields: fields{ diff --git a/utils.go b/utils.go index bb14b673..96b09067 100644 --- a/utils.go +++ b/utils.go @@ -175,6 +175,12 @@ func resolvePaths(relativeToFile string, s *state) { s.Apps[k] = v } + // resolving paths for Bearer Token path in settings + if s.Settings.BearerTokenPath != "" { + if _, err := url.ParseRequestURI(s.Settings.BearerTokenPath); err != nil { + s.Settings.BearerTokenPath, _ = filepath.Abs(filepath.Join(dir, s.Settings.BearerTokenPath)) + } + } // resolving paths for k8s certificate files for k, v := range s.Certificates { if _, err := url.ParseRequestURI(v); err != nil { @@ -256,7 +262,7 @@ func sliceContains(slice []string, s string) bool { } // downloadFile downloads a file from GCS or AWS buckets and name it with a given outfile -// if downloaded, returns the outfile name. If the file path is local file system path, it is returned as is. +// if downloaded, returns the outfile name. If the file path is local file system path, it is copied to current directory. func downloadFile(path string, outfile string) string { if strings.HasPrefix(path, "s3") { From 1c50882a146e50fd5f8fb3dac125a70e38a2ada9 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Tue, 26 Feb 2019 16:53:50 +0100 Subject: [PATCH 0362/1127] removing unnecessary test --- state.go | 2 +- state_test.go | 57 +++++++++++++++++---------------------------------- 2 files changed, 20 insertions(+), 39 deletions(-) diff --git a/state.go b/state.go index 4f8ca512..d3ff8772 100644 --- a/state.go +++ b/state.go @@ -37,7 +37,7 @@ type state struct { func (s state) validate() (bool, string) { // settings - if s.Settings.KubeContext == "" && !getKubeContext() { + if (s.Settings == (config{}) || s.Settings.KubeContext == "") && !getKubeContext() { return false, "ERROR: settings validation failed -- you have not defined a " + "kubeContext to use. Either define it in the desired state file or pass a kubeconfig with --kubeconfig to use an existing context." } else if s.Settings.ClusterURI != "" { diff --git a/state_test.go b/state_test.go index 95219791..ce8480de 100644 --- a/state_test.go +++ b/state_test.go @@ -44,26 +44,7 @@ func Test_state_validate(t *testing.T) { }, want: true, }, { - name: "test case 2 -- settings/nil_value is allowed", - fields: fields{ - Metadata: make(map[string]string), - Certificates: map[string]string{ - "caCrt": "s3://some-bucket/12345.crt", - "caKey": "s3://some-bucket/12345.key", - }, - Settings: config{}, - Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, - }, - HelmRepos: map[string]string{ - "stable": "https://kubernetes-charts.storage.googleapis.com", - "myrepo": "s3://my-repo/charts", - }, - Apps: make(map[string]*release), - }, - want: true, - }, { - name: "test case 3 -- settings/empty_context", + name: "test case 2 -- settings/empty_context", fields: fields{ Metadata: make(map[string]string), Certificates: map[string]string{ @@ -87,7 +68,7 @@ func Test_state_validate(t *testing.T) { }, want: false, }, { - name: "test case 4 -- settings/optional_params", + name: "test case 3 -- settings/optional_params", fields: fields{ Metadata: make(map[string]string), Certificates: map[string]string{ @@ -108,7 +89,7 @@ func Test_state_validate(t *testing.T) { }, want: true, }, { - name: "test case 5 -- settings/password-passed-directly", + name: "test case 4 -- settings/password-passed-directly", fields: fields{ Metadata: make(map[string]string), Certificates: map[string]string{ @@ -132,7 +113,7 @@ func Test_state_validate(t *testing.T) { }, want: true, }, { - name: "test case 6 -- settings/clusterURI-empty-env-var", + name: "test case 5 -- settings/clusterURI-empty-env-var", fields: fields{ Metadata: make(map[string]string), Certificates: map[string]string{ @@ -156,7 +137,7 @@ func Test_state_validate(t *testing.T) { }, want: false, }, { - name: "test case 7 -- settings/clusterURI-invalid", + name: "test case 6 -- settings/clusterURI-invalid", fields: fields{ Metadata: make(map[string]string), Certificates: map[string]string{ @@ -180,7 +161,7 @@ func Test_state_validate(t *testing.T) { }, want: false, }, { - name: "test case 8 -- certifications/missing key", + name: "test case 7 -- certifications/missing key", fields: fields{ Metadata: make(map[string]string), Certificates: map[string]string{ @@ -203,7 +184,7 @@ func Test_state_validate(t *testing.T) { }, want: false, }, { - name: "test case 9 -- certifications/nil_value", + name: "test case 8 -- certifications/nil_value", fields: fields{ Metadata: make(map[string]string), Certificates: nil, @@ -224,7 +205,7 @@ func Test_state_validate(t *testing.T) { }, want: false, }, { - name: "test case 10 -- certifications/invalid_s3", + name: "test case 9 -- certifications/invalid_s3", fields: fields{ Metadata: make(map[string]string), Certificates: map[string]string{ @@ -248,7 +229,7 @@ func Test_state_validate(t *testing.T) { }, want: false, }, { - name: "test case 11 -- certifications/nil_value_pass", + name: "test case 10 -- certifications/nil_value_pass", fields: fields{ Metadata: make(map[string]string), Certificates: nil, @@ -266,7 +247,7 @@ func Test_state_validate(t *testing.T) { }, want: true, }, { - name: "test case 12 -- namespaces/nil_value", + name: "test case 11 -- namespaces/nil_value", fields: fields{ Metadata: make(map[string]string), Certificates: nil, @@ -282,7 +263,7 @@ func Test_state_validate(t *testing.T) { }, want: false, }, { - name: "test case 13 -- namespaces/empty", + name: "test case 12 -- namespaces/empty", fields: fields{ Metadata: make(map[string]string), Certificates: nil, @@ -298,7 +279,7 @@ func Test_state_validate(t *testing.T) { }, want: false, }, { - name: "test case 14 -- namespaces/use and install tiller", + name: "test case 13 -- namespaces/use and install tiller", fields: fields{ Metadata: make(map[string]string), Certificates: nil, @@ -316,7 +297,7 @@ func Test_state_validate(t *testing.T) { }, want: false, }, { - name: "test case 15 -- namespaces/use tiller with tls-valid", + name: "test case 14 -- namespaces/use tiller with tls-valid", fields: fields{ Metadata: make(map[string]string), Certificates: nil, @@ -334,7 +315,7 @@ func Test_state_validate(t *testing.T) { }, want: true, }, { - name: "test case 16 -- namespaces/use tiller with tls-not enough certs", + name: "test case 15 -- namespaces/use tiller with tls-not enough certs", fields: fields{ Metadata: make(map[string]string), Certificates: nil, @@ -352,7 +333,7 @@ func Test_state_validate(t *testing.T) { }, want: true, }, { - name: "test case 17 -- namespaces/deploy tiller with tls- valid", + name: "test case 16 -- namespaces/deploy tiller with tls- valid", fields: fields{ Metadata: make(map[string]string), Certificates: nil, @@ -370,7 +351,7 @@ func Test_state_validate(t *testing.T) { }, want: true, }, { - name: "test case 18 -- helmRepos/nil_value", + name: "test case 17 -- helmRepos/nil_value", fields: fields{ Metadata: make(map[string]string), Certificates: nil, @@ -385,7 +366,7 @@ func Test_state_validate(t *testing.T) { }, want: false, }, { - name: "test case 19 -- helmRepos/empty", + name: "test case 18 -- helmRepos/empty", fields: fields{ Metadata: make(map[string]string), Certificates: nil, @@ -400,7 +381,7 @@ func Test_state_validate(t *testing.T) { }, want: false, }, { - name: "test case 20 -- helmRepos/empty_repo_value", + name: "test case 19 -- helmRepos/empty_repo_value", fields: fields{ Metadata: make(map[string]string), Certificates: nil, @@ -418,7 +399,7 @@ func Test_state_validate(t *testing.T) { }, want: false, }, { - name: "test case 21 -- helmRepos/invalid_repo_value", + name: "test case 20 -- helmRepos/invalid_repo_value", fields: fields{ Metadata: make(map[string]string), Certificates: nil, From a697cf9653b2381fdcf753b5b9594bd81314c472 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Thu, 7 Mar 2019 15:40:54 +0100 Subject: [PATCH 0363/1127] adding azure storage support --- Gopkg.lock | 17 +++++++++++++++++ Gopkg.toml | 5 ++++- docs/desired_state_specification.md | 12 +++++++----- utils.go | 8 +++++++- 4 files changed, 35 insertions(+), 7 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index a396763b..40dfb19b 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -17,6 +17,22 @@ revision = "97efc2c9ffd9fe8ef47f7f3203dc60bbca547374" version = "v0.28.0" +[[projects]] + digest = "1:d2ccb697dc13c8fbffafa37baae97594d5592ae8f7e113471084137315536e2b" + name = "github.com/Azure/azure-pipeline-go" + packages = ["pipeline"] + pruneopts = "UT" + revision = "b8e3409182fd52e74f7d7bdfbff5833591b3b655" + version = "v0.1.8" + +[[projects]] + digest = "1:c4a5edf3b0f38e709a78dcc945997678a364c2b5adfd48842a3dd349c352f833" + name = "github.com/Azure/azure-storage-blob-go" + packages = ["azblob"] + pruneopts = "UT" + revision = "5152f14ace1c6db66bd9cb57840703a8358fa7bc" + version = "0.3.0" + [[projects]] digest = "1:9f3b30d9f8e0d7040f729b82dcbc8f0dead820a133b3147ce355fc451f32d761" name = "github.com/BurntSushi/toml" @@ -335,6 +351,7 @@ analyzer-version = 1 input-imports = [ "cloud.google.com/go/storage", + "github.com/Azure/azure-storage-blob-go/azblob", "github.com/BurntSushi/toml", "github.com/Praqma/helmsman/aws", "github.com/Praqma/helmsman/gcs", diff --git a/Gopkg.toml b/Gopkg.toml index 5616a255..33f46478 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -24,7 +24,10 @@ # go-tests = true # unused-packages = true - +[[constraint]] + version = "0.3.0" + name = "github.com/Azure/azure-storage-blob-go" + [[constraint]] name = "cloud.google.com/go" version = "0.28.0" diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 8e11e44c..ddd8dd5d 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -46,12 +46,12 @@ Optional : Yes, only needed if you want Helmsman to connect kubectl to your clus Synopsis: defines where to find the certificates needed for connecting kubectl to a k8s cluster. If connection settings (username/password/clusterAPI) are provided in the Settings section below, then you need AT LEAST to provide caCrt and caKey. You can optionally provide a client certificate (caClient) depending on your cluster connection setup. Options: -- **caCrt** : a valid S3/GCS bucket or local relative file path to a certificate file. -- **caKey** : a valid S3/GCS bucket or local relative file path to a client key file. -- **caClient**: a valid S3/GCS bucket or local relative file path to a client certificate file. +- **caCrt** : a valid S3/GCS/Azure bucket or local relative file path to a certificate file. +- **caKey** : a valid S3/GCS/Azure bucket or local relative file path to a client key file. +- **caClient**: a valid S3/GCS/Azure bucket or local relative file path to a client certificate file. -> bucket format is: ://bucket-name/dir1/dir2/.../file.extension +> bucket format is: ://bucket-name/dir1/dir2/.../file.extension Example: @@ -59,6 +59,7 @@ Example: [certificates] caCrt = "s3://myS3bucket/mydir/ca.crt" caKey = "gs://myGCSbucket/ca.key" +#caKey = "az://myAzureContainer/ca.key caClient ="../path/to/my/local/client-certificate.crt" #caClient = "$CA_CLIENT" ``` @@ -67,6 +68,7 @@ caClient ="../path/to/my/local/client-certificate.crt" certificates: caCrt: "s3://myS3bucket/mydir/ca.crt" caKey: "gs://myGCSbucket/ca.key" + #caKey: "az://myAzureContainer/ca.key caClient: "../path/to/my/local/client-certificate.crt" #caClient: "$CA_CLIENT" ``` @@ -145,7 +147,7 @@ Options: > If `installTiller` is not defined or set to false, this flag is ignored. -- The following options are `ALL` needed for deploying Tiller with TLS enabled. If they are not all defined, they will be ignored and Tiller will be deployed without TLS. All of these options can be provided as either: a valid local file path, a valid GCS or S3 bucket URI or an environment variable containing a file path or bucket URI. +- The following options are `ALL` needed for deploying Tiller with TLS enabled. If they are not all defined, they will be ignored and Tiller will be deployed without TLS. All of these options can be provided as either: a valid local file path, a valid GCS or S3 or Azure bucket URI or an environment variable containing a file path or bucket URI. - **caCert**: the CA certificate. - **tillerCert**: the SSL certificate for Tiller. - **tillerKey**: the SSL certificate private key for Tiller. diff --git a/utils.go b/utils.go index 96b09067..96c7ba7b 100644 --- a/utils.go +++ b/utils.go @@ -18,6 +18,7 @@ import ( "github.com/BurntSushi/toml" "github.com/Praqma/helmsman/aws" + "github.com/Praqma/helmsman/azure" "github.com/Praqma/helmsman/gcs" ) @@ -274,6 +275,11 @@ func downloadFile(path string, outfile string) string { tmp := getBucketElements(path) gcs.ReadFile(tmp["bucketName"], tmp["filePath"], outfile, noColors) + } else if strings.HasPrefix(path, "az") { + + tmp := getBucketElements(path) + azure.ReadFile(tmp["bucketName"], tmp["filePath"], outfile, noColors) + } else { log.Println("INFO: " + outfile + " will be used from local file system.") @@ -372,7 +378,7 @@ func logError(msg string) { } // getBucketElements returns a map containing the bucket name and the file path inside the bucket -// this func works for S3 and GCS bucket links of the format: +// this func works for S3, Azure and GCS bucket links of the format: // s3 or gs://bucketname/dir.../file.ext func getBucketElements(link string) map[string]string { From 6217c945c28cbdc3cad3495e59a4c3038aa6870b Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Thu, 7 Mar 2019 15:42:05 +0100 Subject: [PATCH 0364/1127] adding azure package --- azure/azblob.go | 84 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 azure/azblob.go diff --git a/azure/azblob.go b/azure/azblob.go new file mode 100644 index 00000000..5a26c2c5 --- /dev/null +++ b/azure/azblob.go @@ -0,0 +1,84 @@ +package azure + +import ( + "bytes" + "context" + "fmt" + "io" + "log" + "net/url" + "os" + + "github.com/Azure/azure-pipeline-go/pipeline" + "github.com/Azure/azure-storage-blob-go/azblob" + "github.com/logrusorgru/aurora" +) + +// colorizer +var style aurora.Aurora +var accountName string +var accountKey string +var p pipeline.Pipeline + +// auth checks for AZURE_STORAGE_ACCOUNT and AZURE_STORAGE_ACCESS_KEY in the environment +// if env vars are set, it will authenticate and create an azblob request pipeline +// returns false and error message if credentials are not set or are invalid +func auth() (bool, string) { + accountName, accountKey = os.Getenv("AZURE_STORAGE_ACCOUNT"), os.Getenv("AZURE_STORAGE_ACCESS_KEY") + if len(accountName) != 0 && len(accountKey) != 0 { + log.Println("AZURE_STORAGE_ACCOUNT and AZURE_STORAGE_ACCESS_KEY are set in the environment. They will be used to connect to Azure storage.") + // Create a default request pipeline + credential, err := azblob.NewSharedKeyCredential(accountName, accountKey) + if err == nil { + p = azblob.NewPipeline(credential, azblob.PipelineOptions{}) + return true, "" + } + return false, err.Error() + + } + return false, "either the AZURE_STORAGE_ACCOUNT or AZURE_STORAGE_ACCESS_KEY environment variable is not set" +} + +// ReadFile reads a file from storage container and saves it in a desired location. +func ReadFile(containerName string, filename string, outFile string, noColors bool) { + style = aurora.NewAurora(!noColors) + if ok, err := auth(); !ok { + log.Fatal(style.Bold(style.Red("ERROR: " + err))) + } + + URL, _ := url.Parse( + fmt.Sprintf("https://%s.blob.core.windows.net/%s", accountName, containerName)) + + containerURL := azblob.NewContainerURL(*URL, p) + + ctx := context.Background() + + blobURL := containerURL.NewBlockBlobURL(filename) + downloadResponse, err := blobURL.Download(ctx, 0, azblob.CountToEnd, azblob.BlobAccessConditions{}, false) + if err != nil { + log.Fatal(style.Bold(style.Red("ERROR: failed to download file " + filename + " with error: " + err.Error()))) + } + bodyStream := downloadResponse.Body(azblob.RetryReaderOptions{MaxRetryRequests: 20}) + + // read the body into a buffer + downloadedData := bytes.Buffer{} + if _, err = downloadedData.ReadFrom(bodyStream); err != nil { + log.Fatal(style.Bold(style.Red("ERROR: failed to download file " + filename + " with error: " + err.Error()))) + } + fmt.Println("Downloaded the blob: " + downloadedData.String()) + + // create output file and write to it + var writers []io.Writer + file, err := os.Create(outFile) + if err != nil { + log.Fatal(style.Bold(style.Red("ERROR: Failed to create an output file: " + err.Error()))) + } + writers = append(writers, file) + defer file.Close() + + dest := io.MultiWriter(writers...) + if _, err := downloadedData.WriteTo(dest); err != nil { + log.Fatal(style.Bold(style.Red("ERROR: Failed to read object content: " + err.Error()))) + } + log.Println("INFO: Successfully downloaded " + filename + " from Azure storage as " + outFile) +} From 5795fac089df5134b165ee339fc37dd911254c84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Socho=C5=84?= Date: Thu, 7 Mar 2019 14:02:39 -0800 Subject: [PATCH 0365/1127] Ensure version is printed before processing any input files --- init.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/init.go b/init.go index eb4df1ae..78e147a6 100644 --- a/init.go +++ b/init.go @@ -87,13 +87,13 @@ func init() { logVersions() } - if len(files) == 0 { - log.Println("INFO: No desired state files provided.") + if v { + fmt.Println("Helmsman version: " + appVersion) os.Exit(0) } - if v { - fmt.Println("Helmsman version: " + appVersion) + if len(files) == 0 { + log.Println("INFO: No desired state files provided.") os.Exit(0) } From d2204010235613d02275d179e11bc3617ac7edfb Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Fri, 8 Mar 2019 16:15:12 +0100 Subject: [PATCH 0366/1127] making username optional when using bearer token --- kube_helpers.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/kube_helpers.go b/kube_helpers.go index daa63153..288af41a 100644 --- a/kube_helpers.go +++ b/kube_helpers.go @@ -211,6 +211,7 @@ func createContext() (bool, string) { // CA key if caKey != "" { + caKey = downloadFile(caKey, "ca.key") } @@ -232,8 +233,10 @@ func createContext() (bool, string) { setCredentialsCmd := "" if s.Settings.BearerToken { token := readFile(tokenPath) - setCredentialsCmd = "kubectl config set-credentials " + s.Settings.Username + " --username=" + s.Settings.Username + - " --token=" + token + if s.Settings.Username == "" { + s.Settings.Username = "helmsman" + } + setCredentialsCmd = "kubectl config set-credentials " + s.Settings.Username + " --token=" + token } else { setCredentialsCmd = "kubectl config set-credentials " + s.Settings.Username + " --username=" + s.Settings.Username + " --password=" + s.Settings.Password + " --client-key=" + caKey From a7aecd0db47695572b95c86fde5a19acdf433e74 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Fri, 8 Mar 2019 16:16:51 +0100 Subject: [PATCH 0367/1127] improving resiliency and add helm client initialization when planning --- helm_helpers.go | 20 +++++++++++++++++++- main.go | 2 ++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/helm_helpers.go b/helm_helpers.go index db78165f..e069981b 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -90,6 +90,9 @@ func getTillerReleases(tillerNS string) tillerReleases { exitCode, result := cmd.exec(debug, verbose) if exitCode != 0 { if !apply { + if strings.Contains(result, "incompatible versions") { + logError(result) + } log.Println("INFO: " + strings.Replace(result, "Error: ", "", 1)) return tillerReleases{} } @@ -376,7 +379,7 @@ func deployTiller(namespace string, serviceAccount string, defaultServiceAccount } cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm init --upgrade " + sa + tillerNameSpace + tls + storageBackend}, + Args: []string{"-c", "helm init --force-upgrade " + sa + tillerNameSpace + tls + storageBackend}, Description: "initializing helm on the current context and upgrading Tiller on namespace [ " + namespace + " ].", } @@ -386,6 +389,21 @@ func deployTiller(namespace string, serviceAccount string, defaultServiceAccount return true, "" } +// initHelmClientOnly initializes the helm client only (without deploying Tiller) +func initHelmClientOnly() (bool, string) { + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm init --client-only "}, + Description: "initializing helm on the client only.", + } + + if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { + return false, "ERROR: initializing helm on the client : " + err + } + + return true, "" +} + // initHelm initializes helm on a k8s cluster and deploys Tiller in one or more namespaces func initHelm() (bool, string) { diff --git a/main.go b/main.go index 7f78d364..23a70dc9 100644 --- a/main.go +++ b/main.go @@ -71,6 +71,8 @@ func main() { if _, ok := s.Namespaces["kube-system"]; !ok { waitForTiller("kube-system") } + } else { + initHelmClientOnly() } // add repos -- fails if they are not valid From f4023f2971854ecd8f4b98e8712bd991b448e410 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Fri, 8 Mar 2019 16:17:37 +0100 Subject: [PATCH 0368/1127] fixing settings and certs validation and updating error messages --- state.go | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/state.go b/state.go index d3ff8772..95910a9d 100644 --- a/state.go +++ b/state.go @@ -46,19 +46,21 @@ func (s state) validate() (bool, string) { return false, "ERROR: settings validation failed -- clusterURI must have a valid URL set in an env variable or passed directly. Either the env var is missing/empty or the URL is invalid." } if s.Settings.KubeContext == "" { - return false, "ERROR: settings validation failed -- KubeContext must be provided if clusterURI is defined." + return false, "ERROR: settings validation failed -- KubeContext needs to be provided in the settings stanza." } - if s.Settings.Username == "" { - return false, "ERROR: settings validation failed -- username must be provided if clusterURI is defined." + if !s.Settings.BearerToken && s.Settings.Username == "" { + return false, "ERROR: settings validation failed -- username needs to be provided in the settings stanza." } - if s.Settings.Password == "" { - return false, "ERROR: settings validation failed -- password must be provided (directly or from env var) if clusterURI is defined." + if !s.Settings.BearerToken && s.Settings.Password == "" { + return false, "ERROR: settings validation failed -- password needs to be provided (directly or from env var) in the settings stanza." } if s.Settings.BearerToken && s.Settings.BearerTokenPath != "" { if _, err := os.Stat(s.Settings.BearerTokenPath); err != nil { - return false, "ERROR: settings validation failed -- Bearer Token path " + s.Settings.BearerTokenPath + " is not found. The path has to be relative to the desired state file." + return false, "ERROR: settings validation failed -- bearer token path " + s.Settings.BearerTokenPath + " is not found. The path has to be relative to the desired state file." } } + } else if s.Settings.BearerToken && s.Settings.ClusterURI == "" { + return false, "ERROR: settings validation failed -- bearer token is enabled but no cluster URI provided." } // slack webhook validation (if provided) @@ -90,15 +92,14 @@ func (s state) validate() (bool, string) { } else if s.Settings.ClusterURI != "" && s.Settings.BearerToken { if !caCrt { - return false, "ERROR: certificates validation failed -- You want me to connect to your cluster with a bearer token " + - "but have not provided [caCrt] in the certificates section of your desired state." + return false, "ERROR: certificates validation failed -- cluster connection with bearer token is enabled but " + + "[caCrt] is missing. Please provide [caCrt] in the Certifications stanza." } } } else { if s.Settings.ClusterURI != "" { - return false, "ERROR: certificates validation failed -- You want me to connect to your cluster for you " + - "but have not given me the cert/key to do so. Please add [caCrt] and/or [caKey] under Certifications. You might also need to provide [clientCrt]." + return false, "ERROR: certificates validation failed -- kube context setup is required but no certificates stanza provided." } } From 6977aed2f6154c92b58b6ada9964b47836e3031b Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Sat, 9 Mar 2019 13:27:12 +0100 Subject: [PATCH 0369/1127] adding azure package --- Gopkg.lock | 18 +++++++++++ azure/azblob.go | 84 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 azure/azblob.go diff --git a/Gopkg.lock b/Gopkg.lock index a396763b..277ba2bd 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -17,6 +17,22 @@ revision = "97efc2c9ffd9fe8ef47f7f3203dc60bbca547374" version = "v0.28.0" +[[projects]] + digest = "1:d2ccb697dc13c8fbffafa37baae97594d5592ae8f7e113471084137315536e2b" + name = "github.com/Azure/azure-pipeline-go" + packages = ["pipeline"] + pruneopts = "UT" + revision = "b8e3409182fd52e74f7d7bdfbff5833591b3b655" + version = "v0.1.8" + +[[projects]] + digest = "1:435043934aa0a8221e2c660e88dffe588783e9497facf6b517a465a37c58c97b" + name = "github.com/Azure/azure-storage-blob-go" + packages = ["azblob"] + pruneopts = "UT" + revision = "457680cc0804810f6d02958481e0ffdda51d5c60" + version = "0.5.0" + [[projects]] digest = "1:9f3b30d9f8e0d7040f729b82dcbc8f0dead820a133b3147ce355fc451f32d761" name = "github.com/BurntSushi/toml" @@ -335,6 +351,8 @@ analyzer-version = 1 input-imports = [ "cloud.google.com/go/storage", + "github.com/Azure/azure-pipeline-go/pipeline", + "github.com/Azure/azure-storage-blob-go/azblob", "github.com/BurntSushi/toml", "github.com/Praqma/helmsman/aws", "github.com/Praqma/helmsman/gcs", diff --git a/azure/azblob.go b/azure/azblob.go new file mode 100644 index 00000000..5a26c2c5 --- /dev/null +++ b/azure/azblob.go @@ -0,0 +1,84 @@ +package azure + +import ( + "bytes" + "context" + "fmt" + "io" + "log" + "net/url" + "os" + + "github.com/Azure/azure-pipeline-go/pipeline" + "github.com/Azure/azure-storage-blob-go/azblob" + "github.com/logrusorgru/aurora" +) + +// colorizer +var style aurora.Aurora +var accountName string +var accountKey string +var p pipeline.Pipeline + +// auth checks for AZURE_STORAGE_ACCOUNT and AZURE_STORAGE_ACCESS_KEY in the environment +// if env vars are set, it will authenticate and create an azblob request pipeline +// returns false and error message if credentials are not set or are invalid +func auth() (bool, string) { + accountName, accountKey = os.Getenv("AZURE_STORAGE_ACCOUNT"), os.Getenv("AZURE_STORAGE_ACCESS_KEY") + if len(accountName) != 0 && len(accountKey) != 0 { + log.Println("AZURE_STORAGE_ACCOUNT and AZURE_STORAGE_ACCESS_KEY are set in the environment. They will be used to connect to Azure storage.") + // Create a default request pipeline + credential, err := azblob.NewSharedKeyCredential(accountName, accountKey) + if err == nil { + p = azblob.NewPipeline(credential, azblob.PipelineOptions{}) + return true, "" + } + return false, err.Error() + + } + return false, "either the AZURE_STORAGE_ACCOUNT or AZURE_STORAGE_ACCESS_KEY environment variable is not set" +} + +// ReadFile reads a file from storage container and saves it in a desired location. +func ReadFile(containerName string, filename string, outFile string, noColors bool) { + style = aurora.NewAurora(!noColors) + if ok, err := auth(); !ok { + log.Fatal(style.Bold(style.Red("ERROR: " + err))) + } + + URL, _ := url.Parse( + fmt.Sprintf("https://%s.blob.core.windows.net/%s", accountName, containerName)) + + containerURL := azblob.NewContainerURL(*URL, p) + + ctx := context.Background() + + blobURL := containerURL.NewBlockBlobURL(filename) + downloadResponse, err := blobURL.Download(ctx, 0, azblob.CountToEnd, azblob.BlobAccessConditions{}, false) + if err != nil { + log.Fatal(style.Bold(style.Red("ERROR: failed to download file " + filename + " with error: " + err.Error()))) + } + bodyStream := downloadResponse.Body(azblob.RetryReaderOptions{MaxRetryRequests: 20}) + + // read the body into a buffer + downloadedData := bytes.Buffer{} + if _, err = downloadedData.ReadFrom(bodyStream); err != nil { + log.Fatal(style.Bold(style.Red("ERROR: failed to download file " + filename + " with error: " + err.Error()))) + } + fmt.Println("Downloaded the blob: " + downloadedData.String()) + + // create output file and write to it + var writers []io.Writer + file, err := os.Create(outFile) + if err != nil { + log.Fatal(style.Bold(style.Red("ERROR: Failed to create an output file: " + err.Error()))) + } + writers = append(writers, file) + defer file.Close() + + dest := io.MultiWriter(writers...) + if _, err := downloadedData.WriteTo(dest); err != nil { + log.Fatal(style.Bold(style.Red("ERROR: Failed to read object content: " + err.Error()))) + } + log.Println("INFO: Successfully downloaded " + filename + " from Azure storage as " + outFile) +} From 5e30f288c67d15c23beb28c4857fe10bc3b4768d Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Sat, 9 Mar 2019 13:28:34 +0100 Subject: [PATCH 0370/1127] adding azure storage blob dependency --- Gopkg.lock | 6 +++--- Gopkg.toml | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 277ba2bd..646b99cc 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -26,12 +26,12 @@ version = "v0.1.8" [[projects]] - digest = "1:435043934aa0a8221e2c660e88dffe588783e9497facf6b517a465a37c58c97b" + digest = "1:c4a5edf3b0f38e709a78dcc945997678a364c2b5adfd48842a3dd349c352f833" name = "github.com/Azure/azure-storage-blob-go" packages = ["azblob"] pruneopts = "UT" - revision = "457680cc0804810f6d02958481e0ffdda51d5c60" - version = "0.5.0" + revision = "5152f14ace1c6db66bd9cb57840703a8358fa7bc" + version = "0.3.0" [[projects]] digest = "1:9f3b30d9f8e0d7040f729b82dcbc8f0dead820a133b3147ce355fc451f32d761" diff --git a/Gopkg.toml b/Gopkg.toml index 5616a255..33f46478 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -24,7 +24,10 @@ # go-tests = true # unused-packages = true - +[[constraint]] + version = "0.3.0" + name = "github.com/Azure/azure-storage-blob-go" + [[constraint]] name = "cloud.google.com/go" version = "0.28.0" From 4e567712f1443394a5332cc2a59609976f518e00 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Sat, 9 Mar 2019 13:31:08 +0100 Subject: [PATCH 0371/1127] updating dependencies --- Gopkg.lock | 4 ++-- Gopkg.toml | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 646b99cc..74f2f5db 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -42,6 +42,7 @@ version = "v0.3.1" [[projects]] + branch = "master" digest = "1:a3634c13a25998109db4c6298c5b36b79012eafa7fe23cb6d5ab2a552a4f07cb" name = "github.com/Praqma/helmsman" packages = [ @@ -49,8 +50,7 @@ "gcs", ] pruneopts = "UT" - revision = "cf49770b6512bc718855133097f55a2f444600a5" - version = "v1.7.2" + revision = "11f6f3acdf268328ff7d1469e032d351875b1f81" [[projects]] digest = "1:c78f02a2c6a138255ce52eefe18b62fe4e89815e4289819582c643ce51cdbf84" diff --git a/Gopkg.toml b/Gopkg.toml index 33f46478..d22cf5ba 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -38,7 +38,8 @@ [[constraint]] name = "github.com/Praqma/helmsman" - version = "1.7.2" + #version = "1.7.4" + branch = "master" [[constraint]] name = "github.com/aws/aws-sdk-go" From 0fed1cfa38d9febcbbed5979b2a3ee8d5dfa977f Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Tue, 12 Mar 2019 13:58:48 +0100 Subject: [PATCH 0372/1127] support basic auth for helm repos. fixes #211 --- example.toml | 1 + example.yaml | 1 + helm_helpers.go | 21 ++++++++++++++++++--- main.go | 3 +++ state.go | 4 ---- state_test.go | 4 ++-- utils.go | 22 ++++++++++++++++++++-- 7 files changed, 45 insertions(+), 11 deletions(-) diff --git a/example.toml b/example.toml index 87e28bdc..39cfacd2 100644 --- a/example.toml +++ b/example.toml @@ -63,6 +63,7 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" # myS3repo = "s3://my-S3-private-repo/charts" # myGCSrepo = "gs://my-GCS-private-repo/charts" +# custom = "https://user:pass@mycustomrepo.org" # define the desired state of your applications helm charts diff --git a/example.yaml b/example.yaml index 31f0c66b..c258f16b 100644 --- a/example.yaml +++ b/example.yaml @@ -57,6 +57,7 @@ helmRepos: incubator: "http://storage.googleapis.com/kubernetes-charts-incubator" #myS3repo: "s3://my-S3-private-repo/charts" #myGCSrepo: "gs://my-GCS-private-repo/charts" + #custom: "https://user:pass@mycustomrepo.org" # define the desired state of your applications helm charts # each contains the following: diff --git a/helm_helpers.go b/helm_helpers.go index 908f38f6..d20a2480 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -3,6 +3,7 @@ package main import ( "encoding/json" "log" + "net/url" "path/filepath" "regexp" "strconv" @@ -291,16 +292,30 @@ func waitForTiller(namespace string) { // Helm does not mind if a repo with the same name exists. It treats it as an update. func addHelmRepos(repos map[string]string) (bool, string) { - for repoName, url := range repos { + for repoName, repoLink := range repos { + basicAuth := "" // check if repo is in GCS, then perform GCS auth -- needed for private GCS helm repos // failed auth would not throw an error here, as it is possible that the repo is public and does not need authentication - if strings.HasPrefix(url, "gs://") { + if strings.HasPrefix(repoLink, "gs://") { gcs.Auth() } + u, err := url.Parse(repoLink) + if err != nil { + logError("ERROR: failed to add helm repo: " + err.Error()) + } + if u.User != nil { + p, ok := u.User.Password() + if !ok { + logError("ERROR: helm repo " + repoName + " has incomplete basic auth info. Missing the password!") + } + basicAuth = " --username " + u.User.Username() + " --password " + p + + } + cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm repo add " + repoName + " " + strconv.Quote(url)}, + Args: []string{"-c", "helm repo add " + basicAuth + " " + repoName + " " + strconv.Quote(repoLink)}, Description: "adding repo " + repoName, } diff --git a/main.go b/main.go index 23a70dc9..c972dee3 100644 --- a/main.go +++ b/main.go @@ -42,6 +42,9 @@ var destroy bool var showDiff bool var suppressDiffSecrets bool +const stableHelmRepo = "https://kubernetes-charts.storage.googleapis.com" +const incubatorHelmRepo = "http://storage.googleapis.com/kubernetes-charts-incubator" + func main() { // set the kubecontext to be used Or create it if it does not exist if !setKubeContext(s.Settings.KubeContext) { diff --git a/state.go b/state.go index d864bbfb..862a7841 100644 --- a/state.go +++ b/state.go @@ -151,10 +151,6 @@ func (s state) validate() (bool, string) { } // repos - if s.HelmRepos == nil || len(s.HelmRepos) == 0 { - return false, "ERROR: repos validation failed -- I need at least one helm repo " + - "to work with!" - } for k, v := range s.HelmRepos { _, err := url.ParseRequestURI(v) if err != nil { diff --git a/state_test.go b/state_test.go index ce8480de..b6e648e3 100644 --- a/state_test.go +++ b/state_test.go @@ -364,7 +364,7 @@ func Test_state_validate(t *testing.T) { HelmRepos: nil, Apps: make(map[string]*release), }, - want: false, + want: true, }, { name: "test case 18 -- helmRepos/empty", fields: fields{ @@ -379,7 +379,7 @@ func Test_state_validate(t *testing.T) { HelmRepos: map[string]string{}, Apps: make(map[string]*release), }, - want: false, + want: true, }, { name: "test case 19 -- helmRepos/empty_repo_value", fields: fields{ diff --git a/utils.go b/utils.go index 7bb78269..f7ebdabe 100644 --- a/utils.go +++ b/utils.go @@ -47,7 +47,7 @@ func fromTOML(file string, s *state) (bool, string) { if _, err := toml.Decode(tomlFile, s); err != nil { return false, err.Error() } - + addDefaultHelmRepos(s) resolvePaths(file, s) return true, "INFO: Parsed TOML [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps." @@ -90,7 +90,7 @@ func fromYAML(file string, s *state) (bool, string) { if err = yaml.UnmarshalStrict(yamlFile, s); err != nil { return false, err.Error() } - + addDefaultHelmRepos(s) resolvePaths(file, s) return true, "INFO: Parsed YAML [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps." @@ -151,6 +151,24 @@ func stringInSlice(a string, list []string) bool { return false } +// addDefaultHelmRepos adds stable and incubator helm repos to the state if they are not already defined +func addDefaultHelmRepos(s *state) { + if s.HelmRepos == nil || len(s.HelmRepos) == 0 { + s.HelmRepos = map[string]string{ + "stable": stableHelmRepo, + "incubator": incubatorHelmRepo, + } + log.Println("INFO: no helm repos provided, using the default 'stable' and 'incubator' repos.") + } + if _, ok := s.HelmRepos["stable"]; !ok { + s.HelmRepos["stable"] = stableHelmRepo + } + if _, ok := s.HelmRepos["incubator"]; !ok { + s.HelmRepos["incubator"] = incubatorHelmRepo + } +} + +// resolvePaths resolves relative paths of certs/keys/chart and replace them with a absolute paths func resolvePaths(relativeToFile string, s *state) { dir := filepath.Dir(relativeToFile) From a971bdea303f0c73ab0a406cbbb5fd47b08b3350 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Tue, 12 Mar 2019 22:29:12 +0100 Subject: [PATCH 0373/1127] updating docs --- docs/desired_state_specification.md | 34 +-- docs/how_to/README.md | 50 +++++ .../{manipulate_apps.md => apps/basic.md} | 0 .../destroy.md} | 0 .../{test_charts.md => apps/helm_tests.md} | 0 .../moving_across_namespaces.md} | 3 +- .../multiple_values_files.md} | 0 .../order.md} | 0 .../override_namespaces.md} | 0 docs/how_to/apps/protection.md | 31 +++ .../secrets.md} | 9 +- docs/how_to/auth_to_storage_providers.md | 29 +++ docs/how_to/define_namespaces.md | 206 ------------------ .../ci.md} | 2 +- docs/how_to/deployments/inside_k8s.md | 51 +++++ docs/how_to/helm_repos/basic_auth.md | 25 +++ docs/how_to/helm_repos/default.md | 45 ++++ docs/how_to/helm_repos/gcs.md | 30 +++ .../local.md} | 12 +- docs/how_to/helm_repos/pre_configured.md | 21 ++ docs/how_to/helm_repos/s3.md | 29 +++ docs/how_to/namespaces/create.md | 27 +++ .../namespaces/labels_and_annotations.md | 36 +++ docs/how_to/namespaces/limits.md | 42 ++++ docs/how_to/namespaces/protection.md | 32 +++ .../how_to/protect_namespaces_and_releases.md | 12 - .../run_helmsman_with_hosted_cluster.md | 146 ------------- docs/how_to/run_helmsman_with_minikube.md | 86 -------- .../creating_kube_context_with_certs.md | 42 ++++ .../creating_kube_context_with_token.md | 29 +++ docs/how_to/settings/current_kube_context.md | 10 + docs/how_to/settings/existing_kube_context.md | 19 ++ .../deploy_apps_with_specific_tiller.md | 36 +++ docs/how_to/tiller/existing.md | 21 ++ docs/how_to/tiller/multitenancy.md | 56 +++++ .../tiller/prevent_tiller_in_kube_system.md | 18 ++ docs/how_to/tiller/shared.md | 83 +++++++ docs/how_to/use_private_helm_charts.md | 54 ----- example.toml | 4 + example.yaml | 3 + minimal-example.toml | 20 ++ minimal-example.yaml | 19 ++ 42 files changed, 845 insertions(+), 527 deletions(-) create mode 100644 docs/how_to/README.md rename docs/how_to/{manipulate_apps.md => apps/basic.md} (100%) rename docs/how_to/{delete_all_releases.md => apps/destroy.md} (100%) rename docs/how_to/{test_charts.md => apps/helm_tests.md} (100%) rename docs/how_to/{move_charts_across_namespaces.md => apps/moving_across_namespaces.md} (99%) rename docs/how_to/{multiple_value_files.md => apps/multiple_values_files.md} (100%) rename docs/how_to/{use_the_priority_key.md => apps/order.md} (100%) rename docs/how_to/{override_defined_namespaces.md => apps/override_namespaces.md} (100%) create mode 100644 docs/how_to/apps/protection.md rename docs/how_to/{pass_secrets_from_env_variables.md => apps/secrets.md} (83%) create mode 100644 docs/how_to/auth_to_storage_providers.md delete mode 100644 docs/how_to/define_namespaces.md rename docs/how_to/{run_helmsman_in_ci.md => deployments/ci.md} (95%) create mode 100644 docs/how_to/deployments/inside_k8s.md create mode 100644 docs/how_to/helm_repos/basic_auth.md create mode 100644 docs/how_to/helm_repos/default.md create mode 100644 docs/how_to/helm_repos/gcs.md rename docs/how_to/{use_local_charts.md => helm_repos/local.md} (100%) create mode 100644 docs/how_to/helm_repos/pre_configured.md create mode 100644 docs/how_to/helm_repos/s3.md create mode 100644 docs/how_to/namespaces/create.md create mode 100644 docs/how_to/namespaces/labels_and_annotations.md create mode 100644 docs/how_to/namespaces/limits.md create mode 100644 docs/how_to/namespaces/protection.md delete mode 100644 docs/how_to/run_helmsman_with_hosted_cluster.md delete mode 100644 docs/how_to/run_helmsman_with_minikube.md create mode 100644 docs/how_to/settings/creating_kube_context_with_certs.md create mode 100644 docs/how_to/settings/creating_kube_context_with_token.md create mode 100644 docs/how_to/settings/current_kube_context.md create mode 100644 docs/how_to/settings/existing_kube_context.md create mode 100644 docs/how_to/tiller/deploy_apps_with_specific_tiller.md create mode 100644 docs/how_to/tiller/existing.md create mode 100644 docs/how_to/tiller/multitenancy.md create mode 100644 docs/how_to/tiller/prevent_tiller_in_kube_system.md create mode 100644 docs/how_to/tiller/shared.md delete mode 100644 docs/how_to/use_private_helm_charts.md create mode 100644 minimal-example.toml create mode 100644 minimal-example.yaml diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 469a9306..aa501cd9 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -1,5 +1,5 @@ --- -version: v1.7.4 +version: v1.8.0 --- # Helmsman desired state specification @@ -8,9 +8,9 @@ This document describes the specification for how to write your Helm charts desi - [Metadata](#metadata) [Optional] -- metadata for any human reader of the desired state file. - [Certificates](#certificates) [Optional] -- only needed when you want Helmsman to connect kubectl to your cluster for you. -- [Settings](#settings) -- data about your k8s cluster and how to deploy Helm on it if needed. +- [Settings](#settings) [Optional] -- data about your k8s cluster and how to deploy Helm on it if needed. - [Namespaces](#namespaces) -- defines the namespaces where you want your Helm charts to be deployed. -- [Helm Repos](#helm-repos) -- defines the repos where you want to get Helm charts from. +- [Helm Repos](#helm-repos) [Optional] -- defines the repos where you want to get Helm charts from. - [Apps](#apps) -- defines the applications/charts you want to manage in your cluster. @@ -43,7 +43,7 @@ metadata: Optional : Yes, only needed if you want Helmsman to connect kubectl to your cluster for you. -Synopsis: defines where to find the certificates needed for connecting kubectl to a k8s cluster. If connection settings (username/password/clusterAPI) are provided in the Settings section below, then you need AT LEAST to provide caCrt and caKey. You can optionally provide a client certificate (caClient) depending on your cluster connection setup. +Synopsis: defines where to find the certificates needed for connecting kubectl to a k8s cluster. If connection settings (username/password/clusterAPI) are provided in the Settings section below, then you need **AT LEAST** to provide caCrt and caKey. You can optionally provide a client certificate (caClient) depending on your cluster connection setup. Options: - **caCrt** : a valid S3/GCS/Azure bucket or local relative file path to a certificate file. @@ -51,7 +51,7 @@ Options: - **caClient**: a valid S3/GCS/Azure bucket or local relative file path to a client certificate file. -> bucket format is: ://bucket-name/dir1/dir2/.../file.extension +> bucket format is: [s3 or gs or az]://bucket-name/dir1/dir2/.../file.extension Example: @@ -75,19 +75,23 @@ certificates: ## Settings -Optional : No. +Optional : Yes. Synopsis: provides settings for connecting to your k8s cluster and configuring Helm's Tiller in the cluster. +> If you don't provide the `settings` stanza, helmsman would use your current kube context and will deploy Tiller(s) without RBAC service accounts. + Options: -- **kubeContext** : this is always required and defines what context to use in kubectl. Helmsman will try connect to this context first, if it does not exist, it will try to create it (i.e. connect to a k8s cluster) using the options below. +- **kubeContext** : the kube context you want Helmsman to use or create. Helmsman will try connect to this context first, if it does not exist, it will try to create it (i.e. connect to a k8s cluster) using the options below. -The following options can be skipped if your kubectl context is already created and you don't want Helmsman to connect kubectl to your cluster for you. When using Helmsman in CI pipeline, these details are required to connect to your cluster every time the pipeline is executed. +The following options can be skipped if your kubectl context is already created and you don't want Helmsman to connect kubectl to your cluster for you. - **username** : the username to be used for kubectl credentials. - **password** : an environment variable name (starting with `$`) where your password is stored. Get the password from your k8s admin or consult k8s docs on how to get/set it. - **clusterURI** : the URI for your cluster API or the name of an environment variable (starting with `$`) containing the URI. -- **serviceAccount**: the name of the service account to use to initiate helm. This should have enough permissions to allow Helm to work and should exist already in the cluster. More details can be found in [helm's RBAC guide](https://github.com/kubernetes/helm/blob/master/docs/rbac.md) +- **bearerToken**: whether you want helmsman to connect to the cluster using a bearer token. Default is `false` +- **bearerTokenPath**: optional. If bearer token is used, you can specify a custom location for the token file. +- **serviceAccount**: the name of the service account to use to deploy Helm Tiller. This should have enough permissions to allow Helm to work. If the service account does not exist, it will be created. More details can be found in [helm's RBAC guide](https://github.com/kubernetes/helm/blob/master/docs/rbac.md) - **storageBackend** : by default Helm stores release information in configMaps, using secrets is for storage is recommended for security. Setting this flag to `secret` will deploy/upgrade Tiller with the `--storage=secret`. Other values will be skipped and configMaps will be used. - **slackWebhook** : a [Slack](slack.com) Webhook URL to receive Helmsman notifications. This can be passed directly or in an environment variable. - **reverseDelete** : if set to `true` it will reverse the priority order whilst deleting. @@ -220,11 +224,13 @@ namespaces: ## Helm Repos -Optional : No. +Optional : Yes. Synopsis: defines the Helm repos where your charts can be found. You can add as many repos as you like. Public repos can be added without any additional setup. Private repos require authentication. -> AS of version v0.2.0, both AWS S3 and Google GCS buckets can be used for private repos (using the [Helm S3](https://github.com/hypnoglow/helm-s3) and [Helm GCS](https://github.com/nouney/helm-gcs) plugins). +> As of version v0.2.0, both AWS S3 and Google GCS buckets can be used for private repos (using the [Helm S3](https://github.com/hypnoglow/helm-s3) and [Helm GCS](https://github.com/nouney/helm-gcs) plugins). + +> As of version v1.8.0, you can use private repos with basic auth and you can use pre-configured helm repos. Authenticating to private helm repos: - **For S3 repos**: you need to have valid AWS access keys in your environment variables. See [here](https://github.com/hypnoglow/helm-s3#note-on-aws-authentication) for more details. @@ -233,7 +239,7 @@ Authenticating to private helm repos: - Or, set `GCLOUD_CREDENTIALS` environment variable to contain the content of the credentials.json file. Options: -- you can define any key/value pairs where key is the repo name and value is a valid URI for the repo. +- you can define any key/value pairs where key is the repo name and value is a valid URI for the repo. Basic auth info can be added in the repo URL as in the example below. Example: @@ -243,6 +249,7 @@ stable = "https://kubernetes-charts.storage.googleapis.com" incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" myS3repo = "s3://my-S3-private-repo/charts" myGCSrepo = "gs://my-GCS-private-repo/charts" +myPrivateRepo = "https://user:$TOP_SECRET_PASSWORD@mycustomprivaterepo.org" ``` ```yaml @@ -251,6 +258,7 @@ helmRepos: incubator: "http://storage.googleapis.com/kubernetes-charts-incubator" myS3repo: "s3://my-S3-private-repo/charts" myGCSrepo: "gs://my-GCS-private-repo/charts" + myPrivateRepo: "https://user:$TOP_SECRET_PASSWORD@mycustomprivaterepo.org" ``` ## Preconfigured Helm Repos @@ -259,7 +267,7 @@ Optional : Yes. Synopsis: defines the list of helm repositories that the helmsman will consider already preconfigured and thus will not try to overwrite it's configuration. -The primary use-case is if you have some helm repositories that require HTTP basic authentication and you don't want to store the password into the helmsman.yaml. In this case you can execute the following sequence to have those repositories configured: +The primary use-case is if you have some helm repositories that require HTTP basic authentication and you don't want to store the password in the desired state file or as an environment variable. In this case you can execute the following sequence to have those repositories configured: Set up the helmsman configuration: diff --git a/docs/how_to/README.md b/docs/how_to/README.md new file mode 100644 index 00000000..fedecd3a --- /dev/null +++ b/docs/how_to/README.md @@ -0,0 +1,50 @@ +--- +version: v1.8.0 +--- + +# How To Guides + +This page contains a list of guides on how to use Helmsman. + +- Connecting to Kubernetes clusters + - [Using an existing kube context](settings/existing_kube_context.md) + - [Using the current kube context](settings/current_kube_context.md) + - [Connecting with certificates](settings/creating_kube_context_with_certs.md) + - [Connecting with bearer token](settings/creating_kube_context_with_token.md) +- Defining Namespaces + - [Create namespaces](namespaces/create.md) + - [Label namespaces](namespaces/labels_and_annotations.md) + - [Set resource limits for namespaces](namespaces/limits.md) + - [Protecting namespaces](namespaces/protection.md) +- Deploying Helm Tiller + - [Using existing Tillers](tiller/existing.md) + - [Deploy shared Tiller in kube-system](tiller/shared.md) + - [Prevent Deploying Tiller in kube-system](tiller/prevent_tiller_in_kube_system.md) + - [Deploy Multiple Tillers with custom setup for each](tiller/multitenancy.md) + - [Deploy apps with specific Tillers](deploy_apps_with_specific_tiller.md) +- Defining Helm repositories + - [Using default helm repos](helm_repos/default.md) + - [Using private repos in Google GCS](helm_repos/gcs.md) + - [Using private repos in AWS S3](helm_repos/s3.md) + - [Using private repos with basic auth](helm_repos/basic_auth.md) + - [Using pre-configured repos](helm_repos/pre_configured.md) + - [Using local charts](helm_repos/local.md) +- Manipulating Apps + - [Basic operations](apps/basic.md) + - [Passing secrets from env vars](apps/secrets.md) + - [Use multiple values files for apps](apps/multiple_values_files.md) + - [Protect releases (apps)](apps/protection.md) + - [Moving releases (apps) across namespaces](apps/moving_across_namespaces.md) + - [Override defined namespaces](apps/override_namespaces.md) + - [Run helm tests for deployed releases (apps)](apps/helm_tests.md) + - [Define the order of apps operations](apps/order.md) + - [Delete all releases (apps)](apps/destroy.md) +- Running Helmsman in different environments + - [Running Helmsman in CI](deployment/ci.md) + - [Running Helmsman inside your k8s cluster](inside_k8s.md) +- Misc + - [Authenticating to cloud storage providers](auth_to_storage_providers.md) + - [Send slack notifications from Helmsman](send_slack_notifications_from_helmsman.md) + - [Merge multiple desired state files](merge_desired_state_files.md) + - [Multitenant clusters guide](multitenant_clusters_guide.md) + - [Helmsman on Windows 10](helmsman_on_windows10.md) diff --git a/docs/how_to/manipulate_apps.md b/docs/how_to/apps/basic.md similarity index 100% rename from docs/how_to/manipulate_apps.md rename to docs/how_to/apps/basic.md diff --git a/docs/how_to/delete_all_releases.md b/docs/how_to/apps/destroy.md similarity index 100% rename from docs/how_to/delete_all_releases.md rename to docs/how_to/apps/destroy.md diff --git a/docs/how_to/test_charts.md b/docs/how_to/apps/helm_tests.md similarity index 100% rename from docs/how_to/test_charts.md rename to docs/how_to/apps/helm_tests.md diff --git a/docs/how_to/move_charts_across_namespaces.md b/docs/how_to/apps/moving_across_namespaces.md similarity index 99% rename from docs/how_to/move_charts_across_namespaces.md rename to docs/how_to/apps/moving_across_namespaces.md index ba04d3e6..36d89a45 100644 --- a/docs/how_to/move_charts_across_namespaces.md +++ b/docs/how_to/apps/moving_across_namespaces.md @@ -162,4 +162,5 @@ pvc-f791ef92-01ab-11e8-8a7e-02412acf5adc 20Gi RWO Retain Further details: https://github.com/kubernetes/kubernetes/issues/48609 -https://kubernetes.io/docs/tasks/administer-cluster/change-pv-reclaim-policy/ \ No newline at end of file +https://kubernetes.io/docs/tasks/administer-cluster/change-pv-reclaim-policy/ + diff --git a/docs/how_to/multiple_value_files.md b/docs/how_to/apps/multiple_values_files.md similarity index 100% rename from docs/how_to/multiple_value_files.md rename to docs/how_to/apps/multiple_values_files.md diff --git a/docs/how_to/use_the_priority_key.md b/docs/how_to/apps/order.md similarity index 100% rename from docs/how_to/use_the_priority_key.md rename to docs/how_to/apps/order.md diff --git a/docs/how_to/override_defined_namespaces.md b/docs/how_to/apps/override_namespaces.md similarity index 100% rename from docs/how_to/override_defined_namespaces.md rename to docs/how_to/apps/override_namespaces.md diff --git a/docs/how_to/apps/protection.md b/docs/how_to/apps/protection.md new file mode 100644 index 00000000..5de513a6 --- /dev/null +++ b/docs/how_to/apps/protection.md @@ -0,0 +1,31 @@ +--- +version: v1.8.0 +--- + +# Protecting apps (releases) + +You can define apps to be protected using the `protected` field. Please check [this doc](../protect_namespaces_and_releases.md) for details about what protection means and the difference between namespace-level and release-level protection. + +Here is an example of a protected app: + +```toml +[apps] + + [apps.jenkins] + namespace = "staging" + enabled = true + chart = "stable/jenkins" + version = "0.9.1" + protected = true # defining this release to be protected. +``` + +```yaml +apps: + + jenkins: + namespace: "staging" + enabled: true + chart: "stable/jenkins" + version: "0.9.1" + protected: true # defining this release to be protected. +``` diff --git a/docs/how_to/pass_secrets_from_env_variables.md b/docs/how_to/apps/secrets.md similarity index 83% rename from docs/how_to/pass_secrets_from_env_variables.md rename to docs/how_to/apps/secrets.md index c2c361e5..cc00435d 100644 --- a/docs/how_to/pass_secrets_from_env_variables.md +++ b/docs/how_to/apps/secrets.md @@ -2,7 +2,7 @@ version: v1.6.0 --- -# pass secrets from env. variables: +# passing secrets from env variables: Starting from v0.1.3, Helmsman allows you to pass secrets and other user input to helm charts from environment variables as follows: @@ -50,7 +50,8 @@ apps: These input variables will be passed to the chart when it is deployed/upgraded using helm's `--set <>=<>` -You can also keep these environment variables in files, by default `helmsman` will load variables from a `.env` file but you can also specify files by using the `-e` option: +# passing secrets from env files +You can also keep these environment variables in files, by default Helmsman will load variables from a `.env` file but you can also specify files by using the `-e` option: ```bash helmsman -e myVars @@ -70,3 +71,7 @@ Or you can do YAML(ish) style FOO: bar BAR: baz ``` + +# passing secrets using helm secrets plugin + +You can also use the [helm secrets plugin](https://github.com/futuresimple/helm-secrets) to pass your secrets. \ No newline at end of file diff --git a/docs/how_to/auth_to_storage_providers.md b/docs/how_to/auth_to_storage_providers.md new file mode 100644 index 00000000..171c7d00 --- /dev/null +++ b/docs/how_to/auth_to_storage_providers.md @@ -0,0 +1,29 @@ +--- +version: v1.8.0 +--- + +# Authenticating to cloud storage providers + +Helmsman can read files like certificates for connecting to the cluster or TLS certificates for communicating with Tiller from some cloud storage providers; namely: GCS, S3 and Azure blob storage. Below is the authentication requirement for each provider: + +## AWS S3 + +You need to provide ALL the following AWS env variables: + +- `AWS_ACCESS_KEY_ID` +- `AWS_SECRET_ACCESS_KEY` +- `AWS_DEFAULT_REGION` + +## Google GCS + +You need to provide ONE of the following env variables: + +- `GOOGLE_APPLICATION_CREDENTIALS` the absolute path to your Google cloud credentials.json file. +- Or, `GCLOUD_CREDENTIALS` the content of the credentials.json file. + +## Microsoft Azure + +You need to provide ALL of the following env variables: + +- `AZURE_STORAGE_ACCOUNT` +- `AZURE_STORAGE_ACCESS_KEY` \ No newline at end of file diff --git a/docs/how_to/define_namespaces.md b/docs/how_to/define_namespaces.md deleted file mode 100644 index f13067f8..00000000 --- a/docs/how_to/define_namespaces.md +++ /dev/null @@ -1,206 +0,0 @@ ---- -version: v1.6.0 ---- - -# define namespaces - -You can define namespaces to be used in your cluster. If they don't exist, Helmsman will create them for you. - -```toml -#... - -[namespaces] -[namespaces.staging] -[namespaces.production] - protected = true # default is false - -#... -``` - -```yaml - -namespaces: - staging: - production: - protected: true # default is false - -``` - ->For details on protecting a namespace, please check the [namespace/release protection guide](protect_namespaces_and_releases.md) - -## Using your existing Tillers (available from v1.6.0) - -If you would like to use custom configuration when deploying your Tiller, you can do that before using Helmsman and then use the `useTiller` option in your namespace definition. - -This will allow Helmsman to use your existing Tiller as it is. Note that you can't set both `useTiller` and `installTiller` to true at the same time. - -```toml -[namespaces] -[namespaces.production] - useTiller = true -``` - -```yaml -namespaces: - production: - useTiller: true -``` - -## Deploying Tiller into namespaces - -As of `v1.2.0-rc`, you can instruct Helmsman to deploy Tiller into specific namespaces (with or without TLS). - -> By default Tiller will be deployed into `kube-system` even if you don't define kube-system in the namespaces section. To prevent deploying Tiller into `kube-system, see the subsection below. - -```toml -[namespaces] -[namespaces.production] - protected = true - installTiller = true - tillerServiceAccount = "tiller-production" - caCert = "secrets/ca.cert.pem" - tillerCert = "secrets/tiller.cert.pem" - tillerKey = "$TILLER_KEY" # where TILLER_KEY=secrets/tiller.key.pem - clientCert = "gs://mybucket/mydir/helm.cert.pem" - clientKey = "s3://mybucket/mydir/helm.key.pem" -``` - -```yaml -namespaces: - production: - protected: true - installTiller: true - tillerServiceAccount: "tiller-production" - caCert: "secrets/ca.cert.pem" - tillerCert: "secrets/tiller.cert.pem" - tillerKey: "$TILLER_KEY" # where TILLER_KEY=secrets/tiller.key.pem - clientCert: "gs://mybucket/mydir/helm.cert.pem" - clientKey: "s3://mybucket/mydir/helm.key.pem" -``` - -### Preventing Tiller deployment in kube-system - -By default Tiller will be deployed into `kube-system` even if you don't define kube-system in the namespaces section. To prevent this, simply add `kube-system` into your namespaces section. Since `installTiller` for namespaces is by default false, Helmsman will not deploy Tiller in `kube-system`. - -```toml -[namespaces] -[namespaces.kube-system] -# installTiller = false # this line is not needed since the default is false, but can be added for human readability. -``` -```yaml -namespaces: - kube-system: - #installTiller: false # this line is not needed since the default is false, but can be added for human readability. -``` - -## Deploying releases with specific Tillers -You can then tell Helmsman to deploy specific releases in a specific namespace: - -```toml -#... -[apps] - - [apps.jenkins] - name = "jenkins" - description = "jenkins" - namespace = "production" # pointing to the namespace defined above - enabled = true - chart = "stable/jenkins" - version = "0.9.1" - valuesFile = "" - purge = false - test = true - -#... - -``` - -```yaml -#... -apps: - jenkins: - name: "jenkins" - description: "jenkins" - namespace: "production" # pointing to the namespace defined above - enabled: true - chart: "stable/jenkins" - version: "0.9.1" - valuesFile: "" - purge: false - test: true - -#... - -``` - -In the above example, `Jenkins` will be deployed in the production namespace using the Tiller deployed in the production namespace. If the production namespace was not configured to have Tiller deployed there, Jenkins will be deployed using the Tiller in `kube-system`. - -## Setting limit ranges - -As of `v1.7.3-rc`, you can instruct Helmsman to deploy `LimitRange`s into specific namespaces by setting the limits in the namespace specification. - -Example: - -```toml -[namespaces] -# to prevent deploying Tiller into kube-system, use the two lines below -# [namespaces.kube-system] -# installTiller = false # this line can be omitted since installTiller defaults to false -[namespaces.staging] -[namespaces.dev] -useTiller = true # use a Tiller which has been deployed in dev namespace -protected = false -[namespaces.production] -protected = true -installTiller = true -tillerServiceAccount = "tiller-production" -caCert = "secrets/ca.cert.pem" -tillerCert = "secrets/tiller.cert.pem" -tillerKey = "$TILLER_KEY" # where TILLER_KEY=secrets/tiller.key.pem -clientCert = "gs://mybucket/mydir/helm.cert.pem" -clientKey = "s3://mybucket/mydir/helm.key.pem" -[namespaces.production.labels] -env = "prod" -[namespaces.production.annotations] -iam.amazonaws.com/role = "dynamodb-reader" -[namespaces.production.limits] -[namespaces.production.limits.default] -cpu = "300m" -memory = "200Mi" -[namespaces.production.limits.defaultRequest] -cpu = "200m" -memory = "100Mi" -``` - -```yaml -namespaces: - # to prevent deploying Tiller into kube-system, use the two lines below - # kube-system: - # installTiller: false # this line can be omitted since installTiller defaults to false - staging: - dev: - protected: false - useTiller: true # use a Tiller which has been deployed in dev namespace - production: - protected: true - installTiller: true - tillerServiceAccount: "tiller-production" - caCert: "secrets/ca.cert.pem" - tillerCert: "secrets/tiller.cert.pem" - tillerKey: "$TILLER_KEY" # where TILLER_KEY=secrets/tiller.key.pem - clientCert: "gs://mybucket/mydir/helm.cert.pem" - clientKey: "s3://mybucket/mydir/helm.key.pem" - limits: - default: - cpu: "300m" - memory: "200Mi" - defaultRequest: - cpu: "200m" - memory: "100Mi" - labels: - env: "prod" - annotations: - iam.amazonaws.com/role: "dynamodb-reader" -``` - -You can read more about the `LimitRange` specification [here](https://docs.openshift.com/enterprise/3.2/dev_guide/compute_resources.html#dev-viewing-limit-ranges). diff --git a/docs/how_to/run_helmsman_in_ci.md b/docs/how_to/deployments/ci.md similarity index 95% rename from docs/how_to/run_helmsman_in_ci.md rename to docs/how_to/deployments/ci.md index 8464191b..04af63b7 100644 --- a/docs/how_to/run_helmsman_in_ci.md +++ b/docs/how_to/deployments/ci.md @@ -13,7 +13,7 @@ jobs: deploy-apps: docker: - - image: praqma/helmsman:v1.5.0 + - image: praqma/helmsman:v1.8.0 steps: - checkout - run: diff --git a/docs/how_to/deployments/inside_k8s.md b/docs/how_to/deployments/inside_k8s.md new file mode 100644 index 00000000..09c23366 --- /dev/null +++ b/docs/how_to/deployments/inside_k8s.md @@ -0,0 +1,51 @@ +--- +version: v1.8.0 +--- + +# Running Helmsman inside your k8s cluster + +Helmsman can be deployed inside your k8s cluster and can talk to the k8s API using a `bearer token`. + +See [connecting to your cluster with bearer token](../settings/create_kube_context_with_token.md) for more details. + + +Your desired state will look like: + +```toml +[settings] + kubeContext = "test" # the name of the context to be created + bearerToken = true + clusterURI = "https://kubernetes.default" +``` + +```yaml +settings: + kubeContext: "test" # the name of the context to be created + bearerToken: true + clusterURI: "https://kubernetes.default" +``` + +To deploy Helmsman into a k8s cluster, few steps are needed: + +> The steps below assume default namespace + +1. Create a k8s service account + +```bash +$ kubectl create sa helmsman +``` + +2. Create a clusterrolebinding + +```bash +$ kubectl create clusterrolebinding helmsman-cluster-admin --clusterrole=cluster-admin --serviceaccount=default:helmsman +``` + +3. Deploy helmsman + +This command gives an interactive session: + +```bash +$ kubectl run helmsman --restart Never --image praqma/helmsman --serviceaccount=helmsman -- helmsman -f -- sleep 3600 +``` +But you can also create a proper kubernetes deployment and mount a volume to it containing your desired state file(s). \ No newline at end of file diff --git a/docs/how_to/helm_repos/basic_auth.md b/docs/how_to/helm_repos/basic_auth.md new file mode 100644 index 00000000..3121c24d --- /dev/null +++ b/docs/how_to/helm_repos/basic_auth.md @@ -0,0 +1,25 @@ +--- +version: v1.8.0 +--- + +# Using private helm repos with basic auth + +Helmsman allows you to use any private helm repo hosting which supports basic auth (e.g. Artifactory). + +For such repos, you need to add the basic auth information in the repo URL as in the example below: + +```toml + +[helmRepos] +# PASS is an env var containing the password +myPrivateRepo = "https://user:$PASS@myprivaterepo.org" + +``` + +```yaml + +helmRepos: + # PASS is an env var containing the password + myPrivateRepo: "https://user:$PASS@myprivaterepo.org" + +``` \ No newline at end of file diff --git a/docs/how_to/helm_repos/default.md b/docs/how_to/helm_repos/default.md new file mode 100644 index 00000000..bde7a00e --- /dev/null +++ b/docs/how_to/helm_repos/default.md @@ -0,0 +1,45 @@ +--- +version: v1.8.0 +--- + +# Default helm repos + +By default, helm comes with two default repos; `stable` and `incubator`. These two DO NOT need to be defined explicitly in your desired state file (DSF). However, if you would like to configure some repo with the name stable for example, you can override the default repo. + +This example would have `stable` and `incubator` added by default and another `custom` repo defined explicitly: + +```toml + + +[helmRepos] + custom = "https://mycustomrepo.org" + +``` + +```yaml + +helmRepos: + custom: "https://mycustomrepo.org" + + +``` + +This example would have `stable` overriden with a custom repo: + +```toml +... + +[helmRepos] +stable = "https://mycustomstablerepo.com" +... + +``` + +```yaml +... + +helmRepos: + stable: "https://mycustomstablerepo.com" +... + +``` diff --git a/docs/how_to/helm_repos/gcs.md b/docs/how_to/helm_repos/gcs.md new file mode 100644 index 00000000..0f6e6c88 --- /dev/null +++ b/docs/how_to/helm_repos/gcs.md @@ -0,0 +1,30 @@ +--- +version: v1.8.0 +--- + +# Using private helm repos in GCS + +Helmsman allows you to use private charts from private repos. Currently only repos hosted in S3 or GCS buckets are supported for private repos. + +You need to provide one of the following env variables: + +- `GOOGLE_APPLICATION_CREDENTIALS` environment variable to contain the absolute path to your Google cloud credentials.json file. +- Or, `GCLOUD_CREDENTIALS` environment variable to contain the content of the credentials.json file. + +Helmsman uses the [helm GCS](https://github.com/nouney/helm-gcs) plugin to work with GCS helm repos. + +```toml + + +[helmRepos] + gcsRepo = "gs://myrepobucket/charts" + +``` + +```yaml + +helmRepos: + gcsRepo: "gs://myrepobucket/charts" + + +``` \ No newline at end of file diff --git a/docs/how_to/use_local_charts.md b/docs/how_to/helm_repos/local.md similarity index 100% rename from docs/how_to/use_local_charts.md rename to docs/how_to/helm_repos/local.md index 463739a1..9566c0a9 100644 --- a/docs/how_to/use_local_charts.md +++ b/docs/how_to/helm_repos/local.md @@ -6,6 +6,12 @@ version: v1.3.0-rc You can use your locally developed charts. +## From file system + +If you use a file path (relative to the DSF, or absolute) for the ```chart``` attribute +helmsman will try to resolve that chart from the local file system. The chart on the +local file system must have a version matching the version specified in the DSF. + ## Served by Helm You can serve them on localhost using helm's `serve` option. @@ -34,9 +40,3 @@ helmRepos: ``` -## From file system - -If you use a file path (relative to the DSF, or absolute) for the ```chart``` attribute -helmsman will try to resolve that chart from the local file system. The chart on the -local file system must have a version matching the version specified in the DSF. - diff --git a/docs/how_to/helm_repos/pre_configured.md b/docs/how_to/helm_repos/pre_configured.md new file mode 100644 index 00000000..98b1dc00 --- /dev/null +++ b/docs/how_to/helm_repos/pre_configured.md @@ -0,0 +1,21 @@ +--- +version: v1.8.0 +--- + +# Using pre-configured helm repos + +The primary use-case is if you have some helm repositories that require HTTP basic authentication and you don't want to store the password in the desired state file or as an environment variable. In this case you can execute the following sequence to have those repositories configured: + +Set up the helmsman configuration: + +```toml +preconfiguredHelmRepos = [ "myrepo1", "myrepo2" ] +``` + +```yaml +preconfiguredHelmRepos: +- myrepo1 +- myrepo2 +``` + +> In this case you will manually need to execute `helm repo add myrepo1 --username= --password=` \ No newline at end of file diff --git a/docs/how_to/helm_repos/s3.md b/docs/how_to/helm_repos/s3.md new file mode 100644 index 00000000..4d9056b8 --- /dev/null +++ b/docs/how_to/helm_repos/s3.md @@ -0,0 +1,29 @@ +--- +version: v1.8.0 +--- + +# Using private helm repos in S3 + +Helmsman allows you to use private charts from private repos. Currently only repos hosted in S3 or GCS buckets are supported for private repos. + +You need to provide one of the following env variables: + +- AWS_ACCESS_KEY_ID +- AWS_SECRET_ACCESS_KEY +- AWS_DEFAULT_REGION + +Helmsman uses the [helm s3](https://github.com/hypnoglow/helm-s3) plugin to work with S3 helm repos. + +```toml + +[helmRepos] +myPrivateRepo = s3://this-is-a-private-repo/charts + +``` + +```yaml + +helmRepos: + myPrivateRepo: s3://this-is-a-private-repo/charts + +``` \ No newline at end of file diff --git a/docs/how_to/namespaces/create.md b/docs/how_to/namespaces/create.md new file mode 100644 index 00000000..9d98f208 --- /dev/null +++ b/docs/how_to/namespaces/create.md @@ -0,0 +1,27 @@ +--- +version: v1.8.0 +--- + +# Create namespaces + +You can define namespaces to be used in your cluster. If they don't exist, Helmsman will create them for you. + +```toml +#... + +[namespaces] +[namespaces.staging] +[namespaces.production] + +#... +``` + +```yaml + +namespaces: + staging: + production: + +``` + +The example above will create two namespaces; staging and production. \ No newline at end of file diff --git a/docs/how_to/namespaces/labels_and_annotations.md b/docs/how_to/namespaces/labels_and_annotations.md new file mode 100644 index 00000000..9a9307e1 --- /dev/null +++ b/docs/how_to/namespaces/labels_and_annotations.md @@ -0,0 +1,36 @@ +--- +version: v1.8.0 +--- + +# Label & annotate namespaces + +You can define namespaces to be used in your cluster. If they don't exist, Helmsman will create them for you. You can also set some labels to apply for those namespaces. + +```toml +#... + +[namespaces] +[namespaces.staging] + [namespaces.staging.labels] + env = "staging" +[namespaces.production] + [namespaces.production.annotations] + iam.amazonaws.com/role = "dynamodb-reader" + + +#... +``` + +```yaml + +namespaces: + staging: + lables: + env: "staging" + production: + annotations: + iam.amazonaws.com/role: "dynamodb-reader" + +``` + +The above examples create two namespaces; staging and production. The staging namespace has one label `env`= `staging` while the production namespace has one annotation `iam.amazonaws.com/role`=`dynamodb-reader`. \ No newline at end of file diff --git a/docs/how_to/namespaces/limits.md b/docs/how_to/namespaces/limits.md new file mode 100644 index 00000000..609fe89a --- /dev/null +++ b/docs/how_to/namespaces/limits.md @@ -0,0 +1,42 @@ +--- +version: v1.8.0 +--- + +# Define resource limits for namespaces + +You can define namespaces to be used in your cluster. If they don't exist, Helmsman will create them for you. You can also define how much resource limits to set for each namespace. + +You can read more about the `LimitRange` specification [here](https://docs.openshift.com/enterprise/3.2/dev_guide/compute_resources.html#dev-viewing-limit-ranges). + +```toml +#... + +[namespaces] +[namespaces.staging] + [namespaces.staging.limits.default] + cpu = "300m" + memory = "200Mi" + [namespaces.staging.limits.defaultRequest] + cpu = "200m" + memory = "100Mi" +[namespaces.production] + +#... +``` + +```yaml + +namespaces: + staging: + limits: + default: + cpu: "300m" + memory: "200Mi" + defaultRequest: + cpu: "200m" + memory: "100Mi" + production: + +``` + +The example above will create two namespaces; staging and production with resource limits defined for the staging namespace. \ No newline at end of file diff --git a/docs/how_to/namespaces/protection.md b/docs/how_to/namespaces/protection.md new file mode 100644 index 00000000..9a7c4e40 --- /dev/null +++ b/docs/how_to/namespaces/protection.md @@ -0,0 +1,32 @@ +--- +version: v1.8.0 +--- + +# Protecting namespaces + +You can define namespaces to be used in your cluster. If they don't exist, Helmsman will create them for you. + +You can also define certain namespaces to be protected using the `protected` field. Please check [this doc](../protect_namespaces_and_releases.md) for details about what protection means and the difference between namespace-level and release-level protection. + + +```toml +#... + +[namespaces] +[namespaces.staging] +[namespaces.production] + protected = true + +#... +``` + +```yaml + +namespaces: + staging: + production: + protected: true + +``` + +The example above will create two namespaces; staging and production. Where Helmsman sees the production namespace as a protected namespace. \ No newline at end of file diff --git a/docs/how_to/protect_namespaces_and_releases.md b/docs/how_to/protect_namespaces_and_releases.md index 44124138..c5c5e67b 100644 --- a/docs/how_to/protect_namespaces_and_releases.md +++ b/docs/how_to/protect_namespaces_and_releases.md @@ -6,8 +6,6 @@ version: v1.3.0-rc Since helmsman is used with version controlled code and is often configured to be triggered as part of a CI pipeline, accidental mistakes could happen by the user (e.g, disabling a production application and taking out of service as a result of a mistaken change in the desired state file). -Helmsman provides the `plan` flag which helps you see the actions that it will take based on your desired state file before actually doing them. We recommend to use a `plan-hold-approve` pipeline when using helmsman with production clusters. - As of version v1.0.0, helmsman provides a fine-grained mechanism to protect releases/namespaces from accidental desired state file changes. ## Protection definition @@ -42,15 +40,10 @@ Protection is supported in two forms: [apps] [apps.jenkins] - name = "jenkins" - description = "jenkins" namespace = "staging" enabled = true chart = "stable/jenkins" version = "0.9.1" - valuesFile = "" - purge = false - test = false protected = true # defining this release to be protected. ``` @@ -58,15 +51,10 @@ Protection is supported in two forms: apps: jenkins: - name: "jenkins" - description: "jenkins" namespace: "staging" enabled: true chart: "stable/jenkins" version: "0.9.1" - valuesFile: "" - purge: false - test: false protected: true # defining this release to be protected. ``` diff --git a/docs/how_to/run_helmsman_with_hosted_cluster.md b/docs/how_to/run_helmsman_with_hosted_cluster.md deleted file mode 100644 index 1adaf200..00000000 --- a/docs/how_to/run_helmsman_with_hosted_cluster.md +++ /dev/null @@ -1,146 +0,0 @@ ---- -version: v1.3.0-rc ---- - -You can manage Helm charts deployment on a hosted K8S cluster in the cloud or on-prem. You need to include the required information to connect to the cluster in your state file. - -**IMPORTANT**: Helmsman expects certain environment variables to be available depending on where your cluster and connection certificates are hosted. Certificates can be used from S3/GCS buckets or local file system. - -## AWS -If you use s3 buckets for storing certificates or for hosting private helm repos, Helmsman needs valid AWS access keys to be able to retrieve private charts or certificates from your s3 buckets. It expects the keys to be in the following environment variables: - -- AWS_ACCESS_KEY_ID -- AWS_SECRET_ACCESS_KEY -- AWS_DEFAULT_REGION - -## GCS -If you use GCS buckets for storing certificates or for hosting private helm repos, Helmsman needs valid Google Cloud credentials to authenticate reading requests from private buckets. This can be provided in one of two ways: - -- set `GOOGLE_APPLICATION_CREDENTIALS` environment variable to contain the absolute path to your Google cloud credentials.json file. -- Or, set `GCLOUD_CREDENTIALS` environment variable to contain the content of the credentials.json file. - -check [here](https://www.terraform.io/docs/providers/google/index.html#authentication-json-file) for getting the required authentication file. - -## Additional environment variables - -The K8S user password is expected in an environment variable which you can give any name you want and define it in your desired state file. Additionally, you can optionally use environment variables to provide certificate paths and clusterURI. - - -Below is an example state file: - -```toml -[metadata] - org = "orgX" - maintainer = "k8s-admin" - -# Certificates are used to connect to the cluster. Currently, they can only be retrieved from s3 buckets. -[certificates] - caCrt = "s3://your-bucket/ca.crt" # s3 bucket - caKey = "$K8S_CLIENT_KEY" # relative file path - caClient = "gs://your-GCS-bucket/caClient.crt" # GCS bucket - -[settings] - kubeContext = "mycontext" - username = "<>" - password = "$K8S_PASSWORD" # the name of an environment variable containing the k8s password - clusterURI = "$K8S_URI" # cluster API - -[namespaces] -[namespaces.staging] - -[helmRepos] - stable = "https://kubernetes-charts.storage.googleapis.com" - incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" - myrepo = "s3://my-private-repo/charts" - myGCSrepo = "gs://my-GCS-repo/charts" - -[apps] - - [apps.jenkins] - name = "jenkins" - description = "jenkins" - namespace = "staging" - enabled = true - chart = "stable/jenkins" - version = "0.9.1" - valuesFile = "" - purge = false - test = false - - - [apps.artifactory] - name = "artifactory" - description = "artifactory" - namespace = "staging" - enabled = true - chart = "stable/artifactory" - version = "6.2.0" - valuesFile = "" - purge = false - test = false -``` - -```yaml -metadata: - org: "orgX" - maintainer: "k8s-admin" - -# Certificates are used to connect to the cluster. Currently, they can only be retrieved from s3 buckets. -certificates: - caCrt: "s3://your-bucket/ca.crt" # s3 bucket - caKey: "$K8S_CLIENT_KEY" # relative file path - caClient: "gs://your-GCS-bucket/caClient.crt" # GCS bucket - -settings: - kubeContext: "mycontext" - username: "<>" - password: "$K8S_PASSWORD" # the name of an environment variable containing the k8s password - clusterURI: "$K8S_URI" # cluster API - -namespaces: - staging: - -helmRepos: - stable: "https://kubernetes-charts.storage.googleapis.com" - incubator: "http://storage.googleapis.com/kubernetes-charts-incubator" - myrepo: "s3://my-private-repo/charts" - myGCSrepo: "gs://my-GCS-repo/charts" - -apps: - - jenkins: - name: "jenkins" - description: "jenkins" - namespace: "staging" - enabled: true - chart: "stable/jenkins" - version: "0.9.1" - valuesFile: "" - purge: false - test: false - - - artifactory: - name: "artifactory" - description: "artifactory" - namespace: "staging" - enabled: true - chart: "stable/artifactory" - version: "6.2.0" - valuesFile: "" - purge: false - test: false -``` - -The above example requires the following environment variables to be set: - -- AWS_ACCESS_KEY_ID (since S3 is used for helm repo and certificates) -- AWS_SECRET_ACCESS_KEY -- AWS_DEFAULT_REGION -- GOOGLE_APPLICATION_CREDENTIALS (since GCS is used for helm repo and certificates) -- K8S_CLIENT_KEY (used in the file) -- K8S_PASSWORD (used in the file) -- K8S_URI (used in the file) - - -For secure Helm configurations to fit for multi-tenant clusters, check the [multitenancy guide](multitenant_clusters_guide.md). diff --git a/docs/how_to/run_helmsman_with_minikube.md b/docs/how_to/run_helmsman_with_minikube.md deleted file mode 100644 index 56bb3e0e..00000000 --- a/docs/how_to/run_helmsman_with_minikube.md +++ /dev/null @@ -1,86 +0,0 @@ ---- -version: v1.3.0-rc ---- - -You can run Helmsman locally as a binary application with Minikube, you just need to skip all the cluster connection settings in your desired state file. Below is the example.toml desired state file adapted to work with Minikube. - - -```toml -[metadata] -org = "orgX" -maintainer = "k8s-admin" - -[settings] -kubeContext = "minikube" - -[namespaces] -[namespaces.staging] - -[helmRepos] -stable = "https://kubernetes-charts.storage.googleapis.com" - -[apps] - - [apps.jenkins] - name = "jenkins" - description = "jenkins" - namespace = "staging" - enabled = true - chart = "stable/jenkins" - version = "0.9.1" - valuesFile = "" - purge = false - test = false - - - [apps.artifactory] - name = "artifactory" - description = "artifactory" - namespace = "staging" - enabled = true - chart = "stable/artifactory" - version = "6.2.0" - valuesFile = "" - purge = false - test = false -``` - -```yaml -metadata: - org: "orgX" - maintainer: "k8s-admin" - -settings: - kubeContext: "minikube" - -namespaces: - staging: - -helmRepos: - stable: "https://kubernetes-charts.storage.googleapis.com" - -apps: - - jenkins: - name: "jenkins" - description: "jenkins" - namespace: "staging" - enabled: true - chart: "stable/jenkins" - version: "0.9.1" - valuesFile: "" - purge: false - test: false - - - artifactory: - name: "artifactory" - description: "artifactory" - namespace: "staging" - enabled: true - chart: "stable/artifactory" - version: "6.2.0" - valuesFile: "" - purge: false - test: false -``` \ No newline at end of file diff --git a/docs/how_to/settings/creating_kube_context_with_certs.md b/docs/how_to/settings/creating_kube_context_with_certs.md new file mode 100644 index 00000000..0e50ef02 --- /dev/null +++ b/docs/how_to/settings/creating_kube_context_with_certs.md @@ -0,0 +1,42 @@ +--- +version: v1.8.0 +--- + +# Cluster connection -- creating the kube context with certificates + +Helmsman can create the kube context for you (i.e. establish connection to your cluster). This guide describe how its done with certificates. If you want to use bearer tokens, check [this guide](creating_kube_context_with_token.md). + +Creating the context with certs, requires both the `settings` and `certificates` stanzas. + +> If you use GCS, S3, or Azure blob storage for your certificates, you will need to provide means to authenticate to the respective cloud provider in the environment. See [authenticating to cloud storage providers](../auth_to_storage_providers.md) for details. + +```toml +[settings] + kubeContext = "mycontext" # the name of the context to be created + username = "admin" # the cluster user name + password = "$K8S_PASSWORD" # the name of an environment variable containing the k8s password + clusterURI = "${CLUSTER_URI}" # the name of an environment variable containing the cluster API endpoint + #clusterURI = "https://192.168.99.100:8443" # equivalent to the above + +[certificates] + caClient = "gs://mybucket/client.crt" # GCS bucket path + caCrt = "s3://mybucket/ca.crt" # S3 bucket path + # caCrt = "az://myblobcontainer/ca.crt" # Azure blob object + caKey = "../ca.key" # valid local file relative path to the DSF file +``` + +```yaml +settings: + kubeContext: "mycontext" # the name of the context to be created + username: "admin" # the cluster user name + password: "$K8S_PASSWORD" # the name of an environment variable containing the k8s password + clusterURI: "${CLUSTER_URI}" # the name of an environment variable containing the cluster API endpoint + #clusterURI: "https://192.168.99.100:8443" # equivalent to the above + +certificates: + caClient: "gs://mybucket/client.crt" # GCS bucket path + caCrt: "s3://mybucket/ca.crt" # S3 bucket path + #caCrt: "az://myblobcontainer/ca.crt" # Azure blob object + caKey: "../ca.key" # valid local file relative path to the DSF file + +``` diff --git a/docs/how_to/settings/creating_kube_context_with_token.md b/docs/how_to/settings/creating_kube_context_with_token.md new file mode 100644 index 00000000..5ea965c3 --- /dev/null +++ b/docs/how_to/settings/creating_kube_context_with_token.md @@ -0,0 +1,29 @@ +--- +version: v1.8.0 +--- + +# Cluster connection -- creating the kube context with bearer tokens + +Helmsman can create the kube context for you (i.e. establish connection to your cluster). This guide describe how its done with bearer tokens. If you want to use certificates, check [this guide](creating_kube_context_with_certs.md). + +All you need to do is set `bearerToken` to true and set the `clusterURI` to point to your cluster API endpoint in the `settings` stanza. + +> Note: Helmsman and therefore helm will only be able to do what the kubernetes service account (from which the token is taken) allows. + +By default, Helmsman will look for a token in `/var/run/secrets/kubernetes.io/serviceaccount/token`. If you have the token else where, you can specify its path with `bearerTokenPath`. + +```toml +[settings] + kubeContext = "test" # the name of the context to be created + bearerToken = true + clusterURI = "https://kubernetes.default" + # bearerTokenPath = "/path/to/custom/bearer/token/file" +``` + +```yaml +settings: + kubeContext: "test" # the name of the context to be created + bearerToken: true + clusterURI: "https://kubernetes.default" + # bearerTokenPath: "/path/to/custom/bearer/token/file" +``` diff --git a/docs/how_to/settings/current_kube_context.md b/docs/how_to/settings/current_kube_context.md new file mode 100644 index 00000000..31a7f33c --- /dev/null +++ b/docs/how_to/settings/current_kube_context.md @@ -0,0 +1,10 @@ +--- +version: v1.8.0 +--- + +# Cluster connection -- Using the current kube context + +Helmsman can use the current configured kube context. In this case, the `kubeContext` field in the `settings` stanza needs to be left empty. If no other `settings` fields are needed, you can delete the whole `settings` stanza. + + +If you want Helmsman to create the kube context for you, see [this guide](creating_kube_context_with_certs.md) for more details on creating a context with certs or [here](creating_kube_context_with_token.md) for details on creating context with bearer token. \ No newline at end of file diff --git a/docs/how_to/settings/existing_kube_context.md b/docs/how_to/settings/existing_kube_context.md new file mode 100644 index 00000000..f3d8fcb0 --- /dev/null +++ b/docs/how_to/settings/existing_kube_context.md @@ -0,0 +1,19 @@ +--- +version: v1.8.0 +--- + +# Cluster connection -- Using an existing kube context + +Helmsman can use any predefined kube context in the environment. All you need to do is set the context name in the `settings` stanza. + +```toml +[settings] + kubeContext = "minikube" +``` + +```yaml +settings: + kubeContext: "minikube" +``` + +In the examples above, Helmsman tries to set the kube context to `minikube`. If that fails, it will attempt to create that kube context. Creating kube context requires more infromation provided. See [this guide](creating_kube_context_with_certs.md) for more details on creating a context with certs or [here](creating_kube_context_with_token.md) for details on creating context with bearer token. \ No newline at end of file diff --git a/docs/how_to/tiller/deploy_apps_with_specific_tiller.md b/docs/how_to/tiller/deploy_apps_with_specific_tiller.md new file mode 100644 index 00000000..e13f7874 --- /dev/null +++ b/docs/how_to/tiller/deploy_apps_with_specific_tiller.md @@ -0,0 +1,36 @@ +--- +version: v1.8.0 +--- + +# Deploying apps (releases) with specific Tillers +You can then tell Helmsman to deploy specific releases in a specific namespace: + +```toml +#... +[apps] + + [apps.jenkins] + namespace = "production" # pointing to the namespace defined above + enabled = true + chart = "stable/jenkins" + version = "0.9.1" + + +#... + +``` + +```yaml +#... +apps: + jenkins: + namespace: "production" # pointing to the namespace defined above + enabled: true + chart: "stable/jenkins" + version: "0.9.1" + +#... + +``` + +In the above example, `Jenkins` will be deployed in the production namespace using the Tiller deployed in the production namespace. If the production namespace was not configured to have Tiller deployed there, Jenkins will be deployed using the Tiller in `kube-system`. \ No newline at end of file diff --git a/docs/how_to/tiller/existing.md b/docs/how_to/tiller/existing.md new file mode 100644 index 00000000..77f7683f --- /dev/null +++ b/docs/how_to/tiller/existing.md @@ -0,0 +1,21 @@ +--- +version: v1.8.0 +--- + +## Using your existing Tillers (available from v1.6.0) + +If you would like to use custom configuration when deploying your Tiller, you can do that before using Helmsman and then use the `useTiller` option in your namespace definition. + +This will allow Helmsman to use your existing Tiller as it is. Note that you can't set both `useTiller` and `installTiller` to true at the same time. + +```toml +[namespaces] +[namespaces.production] + useTiller = true +``` + +```yaml +namespaces: + production: + useTiller: true +``` \ No newline at end of file diff --git a/docs/how_to/tiller/multitenancy.md b/docs/how_to/tiller/multitenancy.md new file mode 100644 index 00000000..c474c490 --- /dev/null +++ b/docs/how_to/tiller/multitenancy.md @@ -0,0 +1,56 @@ +--- +version: v1.8.0 +--- + +# Deploying multiple Tillers + +You can deploy multiple Tillers in the cluster (max. one per namespace). In each namespace definition you can configure how Tiller is installed. The following options are available: +- with/without RBAC +- with/without TLS +- with cluster-admin clusterrole or with a namespace-limited role or with an pre-configured role. + +> If you use GCS, S3, or Azure blob storage for your certificates, you will need to provide means to authenticate to the respective cloud provider in the environment. See [authenticating to cloud storage providers](../auth_to_storage_providers.md) for details. + + +> More details about using Helmsman in a multitenant cluster can be found [here](../multitenant_clusters_guide.md) + +You can also use pre-configured Tillers in specific namespaces. In the example below, the desired state is: to deploy Tiller in the `production` namespace with TLS and RBAC, and to use a pre-configured Tiller in the `dev` namespace. The `staging` namespace does not have any Tiller to be deployed or used. Tiller is not deployed in `kube-system`. + + +```toml +[namespaces] + # to prevent deploying Tiller into kube-system, use the two lines below + [namespaces.kube-system] + installTiller = false # this line can be omitted since installTiller defaults to false + [namespaces.staging] + [namespaces.dev] + useTiller = true # use a Tiller which has been deployed in dev namespace + [namespaces.production] + installTiller = true + tillerServiceAccount = "tiller-production" + tillerRole = "cluster-admin" + caCert = "secrets/ca.cert.pem" + tillerCert = "az://myblobcontainer/tiller.cert.pem" + tillerKey = "$TILLER_KEY" # where TILLER_KEY=secrets/tiller.key.pem + clientCert = "gs://mybucket/mydir/helm.cert.pem" + clientKey = "s3://mybucket/mydir/helm.key.pem" +``` + +```yaml +namespaces: + # to prevent deploying Tiller into kube-system, use the two lines below + kube-system: + installTiller: false # this line can be omitted since installTiller defaults to false + staging: # no Tiller deployed or used here + dev: + useTiller: true # use a Tiller which has been deployed in dev namespace + production: + installTiller: true + tillerServiceAccount: "tiller-production" + tillerRole: "cluster-admin" + caCert: "secrets/ca.cert.pem" + tillerCert: "az://myblobcontainer/tiller.cert.pem" + tillerKey: "$TILLER_KEY" # where TILLER_KEY=secrets/tiller.key.pem + clientCert: "gs://mybucket/mydir/helm.cert.pem" + clientKey: "s3://mybucket/mydir/helm.key.pem" +``` \ No newline at end of file diff --git a/docs/how_to/tiller/prevent_tiller_in_kube_system.md b/docs/how_to/tiller/prevent_tiller_in_kube_system.md new file mode 100644 index 00000000..1df2ba7d --- /dev/null +++ b/docs/how_to/tiller/prevent_tiller_in_kube_system.md @@ -0,0 +1,18 @@ +--- +version: v1.8.0 +--- + +# Prevent Tiller Deployment in kube-system + +By default Tiller will be deployed into `kube-system` even if you don't define kube-system in the namespaces section. To prevent this, simply add `kube-system` into your namespaces section. Since `installTiller` for namespaces is by default false, Helmsman will not deploy Tiller in `kube-system`. + +```toml +[namespaces] +[namespaces.kube-system] +# installTiller = false # this line is not needed since the default is false, but can be added for human readability. +``` +```yaml +namespaces: + kube-system: + #installTiller: false # this line is not needed since the default is false, but can be added for human readability. +``` \ No newline at end of file diff --git a/docs/how_to/tiller/shared.md b/docs/how_to/tiller/shared.md new file mode 100644 index 00000000..7b8c2bbc --- /dev/null +++ b/docs/how_to/tiller/shared.md @@ -0,0 +1,83 @@ +--- +version: v1.8.0 +--- + +# Deploying a shared Tiller (available from v1.2.0) + +You can instruct Helmsman to deploy Tiller into specific namespaces (with or without TLS). + +> By default Tiller will be deployed into `kube-system` even if you don't define kube-system in the namespaces section. To prevent deploying Tiller into `kube-system, see [preventing Tiller deployment in kube-system](prevent_tiller_in_kube_system.md) + +## Without TLS + +```toml +[namespaces] +[namespaces.production] + installTiller = true +``` + +```yaml +namespaces: + production: + installTiller: true +``` + +## With RBAC service account + +You specify an existing service account to be used for deploying Tiller. If that service account does not exist, Helmsman will attempt to create it. If `tillerRole` (e.g. cluster-admin) is specified, it will be bound to the newly created service account. + +By default, Tiller deployed in kube-system will be given cluster-admin clusterrole. Tiller in other namespaces will be given a custom role that gives it access to that namespace only. The custom role is created using [this template](../../../data/role.yaml). + +```toml +[namespaces] +[namespaces.production] + installTiller = true + tillerServiceAccount = "tiller-production" + tillerRole = "cluster-admin" +[namespaces.staging] + installTiller = true + tillerServiceAccount = "tiller-stagin" +``` + +```yaml +namespaces: + production: + installTiller: true + tillerServiceAccount: "tiller-production" + tillerRole: "cluster-admin" + staging: + installTiller: true + tillerServiceAccount: "tiller-staging" +``` + +The above example will create two service accounts; `tiller-production` and `tiller-staging`. Service account `tiller-production` will be bound to a cluster admin clusterrole while `tiller-staging` will be bound to a newly created role with access to the staging namespace only. + +## With RBAC and TLS + +You have to provide the TLS certificates as below. Certificates can be either located locally or in Google GCS, AWS S3 or Azure blob storage. + +> If you use GCS, S3, or Azure blob storage for your certificates, you will need to provide means to authenticate to the respective cloud provider in the environment. See [authenticating to cloud storage providers](../auth_to_storage_providers.md) for details. + +```toml +[namespaces] +[namespaces.production] + installTiller = true + tillerServiceAccount = "tiller-production" + caCert = "secrets/ca.cert.pem" + tillerCert = "secrets/tiller.cert.pem" + tillerKey = "$TILLER_KEY" # where TILLER_KEY=secrets/tiller.key.pem + clientCert = "gs://mybucket/mydir/helm.cert.pem" + clientKey = "s3://mybucket/mydir/helm.key.pem" +``` + +```yaml +namespaces: + production: + installTiller: true + tillerServiceAccount: "tiller-production" + caCert: "secrets/ca.cert.pem" + tillerCert: "secrets/tiller.cert.pem" + tillerKey: "$TILLER_KEY" # where TILLER_KEY=secrets/tiller.key.pem + clientCert: "gs://mybucket/mydir/helm.cert.pem" + clientKey: "s3://mybucket/mydir/helm.key.pem" +``` \ No newline at end of file diff --git a/docs/how_to/use_private_helm_charts.md b/docs/how_to/use_private_helm_charts.md deleted file mode 100644 index 761d0556..00000000 --- a/docs/how_to/use_private_helm_charts.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -version: v1.3.0-rc ---- - -# use private helm charts - -Helmsman allows you to use private charts from private repos. Currently only repos hosted in S3 or GCS buckets are supported for private repos. - -Other hosting options might be supported in the future. Please open an issue if you require supporting other options. - -define your private repo: - -```toml -... - -[helmRepos] -stable = "https://kubernetes-charts.storage.googleapis.com" -incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" -myPrivateRepo = s3://this-is-a-private-repo/charts - -... - -``` - -```yaml -... - -helmRepos: - stable: "https://kubernetes-charts.storage.googleapis.com" - incubator: "http://storage.googleapis.com/kubernetes-charts-incubator" - myPrivateRepo: s3://this-is-a-private-repo/charts - -... - -``` - -## S3 - -If you are using S3 private repos, you need to provide the following AWS env variables: - -- AWS_ACCESS_KEY_ID -- AWS_SECRET_ACCESS_KEY -- AWS_DEFAULT_REGION - -Helmsman uses the [helm s3](https://github.com/hypnoglow/helm-s3) plugin to work with S3 helm repos. - -## GCS - -If you are using GCS private repos, you need to provide one of the following env variables: - -- `GOOGLE_APPLICATION_CREDENTIALS` environment variable to contain the absolute path to your Google cloud credentials.json file. -- Or, `GCLOUD_CREDENTIALS` environment variable to contain the content of the credentials.json file. - -Helmsman uses the [helm GCS](https://github.com/nouney/helm-gcs) plugin to work with GCS helm repos. diff --git a/example.toml b/example.toml index 39cfacd2..48721aac 100644 --- a/example.toml +++ b/example.toml @@ -25,6 +25,10 @@ # storageBackend = "secret" # default is configMap # slackWebhook = "$slack" # or "your slack webhook url" # reverseDelete = false # reverse the priorities on delete +#### to use bearer token: +# bearerToken = true +# clusterURI = "https://kubernetes.default" + # define your environments and their k8s namespaces diff --git a/example.yaml b/example.yaml index c258f16b..9a7054ac 100644 --- a/example.yaml +++ b/example.yaml @@ -23,6 +23,9 @@ settings: storageBackend: "secret" # default is configMap #slackWebhook: "$slack" # or your slack webhook url #reverseDelete: false # reverse the priorities on delete + #### to use bearer token: + # bearerToken: true + # clusterURI: "https://kubernetes.default" # define your environments and their k8s namespaces namespaces: diff --git a/minimal-example.toml b/minimal-example.toml new file mode 100644 index 00000000..79b3575d --- /dev/null +++ b/minimal-example.toml @@ -0,0 +1,20 @@ +## This is a minimal example. +## It will use your current kube context and will deploy Tiller without RBAC service account. +## For the full config spec and options, check https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md + +[namespaces] + [namespaces.staging] + +[apps] + + [apps.jenkins] + namespace = "staging" + enabled = true + chart = "stable/jenkins" + version = "0.14.3" + + [apps.artifactory] + namespace = "staging" + enabled = true + chart = "stable/artifactory" + version = "7.0.6" \ No newline at end of file diff --git a/minimal-example.yaml b/minimal-example.yaml new file mode 100644 index 00000000..8d28308f --- /dev/null +++ b/minimal-example.yaml @@ -0,0 +1,19 @@ +## This is a minimal example. +## It will use your current kube context and will deploy Tiller without RBAC service account. +## For the full config spec and options, check https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md + +namespaces: + staging: + +apps: + jenkins: + namespace: staging + enabled: true + chart: stable/jenkins + version: 0.14.3 + + artifactory: + namespace: staging + enabled: true + chart: stable/artifactory + version: 7.0.6 \ No newline at end of file From 75c409a20afbb18ad9572012b928fb6b47a0307e Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Tue, 12 Mar 2019 22:30:03 +0100 Subject: [PATCH 0374/1127] releasing v1.8.0 --- README.md | 34 ++++++++-------------------------- main.go | 2 +- release-notes.md | 15 ++++++++++----- 3 files changed, 19 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 5c5a5d9f..dc0eb4e0 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ --- -version: v1.7.4 +version: v1.8.0 --- ![helmsman-logo](docs/images/helmsman.png) @@ -52,15 +52,15 @@ Please make sure the following are installed prior to using `helmsman` as a bina - [helm](https://github.com/helm/helm) (for `helmsman` >= 1.6.0, use helm >= 2.10.0. this is due to a dependency bug #87 ) - [helm-diff](https://github.com/databus23/helm-diff) (`helmsman` >= 1.6.0) -If you use private helm repos, you will need either `helm-gcs` or `helm-s3` plugin. See the [docs](https://github.com/Praqma/helmsman/blob/master/docs/how_to/use_private_helm_charts.md) for details. +If you use private helm repos, you will need either `helm-gcs` or `helm-s3` plugin or you can use basic auth to authenticate to your repos. See the [docs](https://github.com/Praqma/helmsman/blob/master/docs/how_to/use_private_helm_charts.md) for details. Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. ``` # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v1.7.4/helmsman_1.7.4_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.8.0/helmsman_1.8.0_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v1.7.4/helmsman_1.7.4_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.8.0/helmsman_1.8.0_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` @@ -73,38 +73,20 @@ Helmsman has been packaged in Archlinux under `helmsman-bin` for the latest bina # Documentation -- [Documentation and How-Tos](https://github.com/Praqma/helmsman/blob/master/docs/). +- [How-Tos](https://github.com/Praqma/helmsman/blob/master/docs/how_to/). - [Desired state specification](https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md). - [CMD reference](https://github.com/Praqma/helmsman/blob/master/docs/cmd_reference.md) - -Helmsman lets you: - -- [Install/delete/upgrade/rollback your helm charts from code](https://github.com/Praqma/helmsman/blob/master/docs/how_to/manipulate_apps.md). -- [Work safely in a multitenant cluster](https://github.com/Praqma/helmsman/blob/master/docs/how_to/multitenant_clusters_guide.md). -- [Pass secrets/user input to helm charts from environment variables](https://github.com/Praqma/helmsman/blob/master/docs/how_to/pass_secrets_from_env_variables.md). -- [Send Slack notifications from Helmsman](https://github.com/Praqma/helmsman/blob/master/docs/how_to/send_slack_notifications_from_helmsman.md). -- [Test releases when they are first installed](https://github.com/Praqma/helmsman/blob/master/docs/how_to/test_charts.md). -- [Use public and private helm charts](https://github.com/Praqma/helmsman/blob/master/docs/how_to/use_private_helm_charts.md). -- [Use locally developed helm charts (the tar archives)](https://github.com/Praqma/helmsman/blob/master/docs/how_to/use_local_charts.md). -- [Define namespaces to be used in your cluster](https://github.com/Praqma/helmsman/blob/master/docs/how_to/define_namespaces.md). -- [Define resource limits per namespace](https://github.com/Praqma/helmsman/blob/master/docs/how_to/define_namespaces.md). -- [Move charts across namespaces](https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md). -- [Protect namespaces/releases against accidental changes](https://github.com/Praqma/helmsman/blob/master/docs/how_to/protect_namespaces_and_releases.md) -- [Define priorities at which releases are deployed/managed](https://github.com/Praqma/helmsman/blob/master/docs/how_to/use_the_priority_key.md) -- [Override the defined namespaces to deploy all releases in a specific namespace](https://github.com/Praqma/helmsman/blob/master/docs/how_to/override_defined_namespaces.md) - - ## Usage Helmsman can be used in three different settings: -- [As a binary with Minikube](https://github.com/Praqma/helmsman/blob/master/docs/how_to/run_helmsman_with_minikube.md). -- [As a binary with a hosted cluster](https://github.com/Praqma/helmsman/blob/master/docs/how_to/run_helmsman_with_hosted_cluster.md). -- [As a docker image in a CI system or local machine](https://github.com/Praqma/helmsman/blob/master/docs/how_to/run_helmsman_in_ci.md) Always use a tagged docker image from [dockerhub](https://hub.docker.com/r/praqma/helmsman/) as the `latest` image can (at times) be unstable. +- [As a binary with a hosted cluster](https://github.com/Praqma/helmsman/blob/master/docs/how_to/settings). +- [As a docker image in a CI system or local machine](https://github.com/Praqma/helmsman/blob/master/docs/how_to/deployments/ci.md) Always use a tagged docker image from [dockerhub](https://hub.docker.com/r/praqma/helmsman/) as the `latest` image can (at times) be unstable. +- [As a docker image inside a k8s cluster](https://github.com/Praqma/helmsman/blob/master/docs/how_to/deployments/inside_k8s.md) # Contributing diff --git a/main.go b/main.go index c972dee3..d46545dd 100644 --- a/main.go +++ b/main.go @@ -34,7 +34,7 @@ var shouldCleanup bool var skipValidation bool var applyLabels bool var keepUntrackedReleases bool -var appVersion = "v1.7.4" +var appVersion = "v1.8.0" var helmVersion string var kubectlVersion string var dryRun bool diff --git a/release-notes.md b/release-notes.md index be86a30c..ce57af36 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,10 +1,15 @@ -# v1.7.4 +# v1.8.0 > If you are already using an older version of Helmsman than v1.4.0-rc, please read the changes below carefully and follow the upgrade guide [here](docs/migrating_to_v1.4.0-rc.md) +# New features: +- Support for connection to clusters with bearer token and therefore allowing helmsman to run inside a k8s cluster. PR #206 +- Improved cluster connection validation and error messages. PR #206 +- Making the `settings` and `helmRepos` stanzas optional. PR #206 #2014 +- Support for private repos with basic auth. PR #214 +- Support for using pre-configured helm repos. PR #201 +- Support for using Azure blob storage for certificates and keys. PR #213 + # Fixes: -- Bug fixes for: #174 #185 #181 #125 #186 +- Minor bug fixes: PRs #205 #212 -#New features: -- Adding annotation functionality for namespaces (PR #184) -- Adding support to specify Tiller(s) role. \ No newline at end of file From 5e979f7f73f23879e7ab99c7b1696f4fb75ee0e0 Mon Sep 17 00:00:00 2001 From: hatemosphere Date: Sat, 16 Mar 2019 07:23:00 +0200 Subject: [PATCH 0375/1127] Dehardcoding LimitRange type to be able to sed limits for Pod, adding examples to docs --- docs/desired_state_specification.md | 29 +++++++++++++-------- docs/how_to/README.md | 14 +++++----- docs/how_to/namespaces/limits.md | 40 ++++++++++++++++++----------- example.toml | 19 +++++++++----- example.yaml | 16 +++++++----- kube_helpers.go | 6 ++--- namespace.go | 14 +++++----- 7 files changed, 84 insertions(+), 54 deletions(-) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index aa501cd9..77ce3123 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -75,7 +75,7 @@ certificates: ## Settings -Optional : Yes. +Optional : Yes. Synopsis: provides settings for connecting to your k8s cluster and configuring Helm's Tiller in the cluster. @@ -84,7 +84,7 @@ Synopsis: provides settings for connecting to your k8s cluster and configuring H Options: - **kubeContext** : the kube context you want Helmsman to use or create. Helmsman will try connect to this context first, if it does not exist, it will try to create it (i.e. connect to a k8s cluster) using the options below. -The following options can be skipped if your kubectl context is already created and you don't want Helmsman to connect kubectl to your cluster for you. +The following options can be skipped if your kubectl context is already created and you don't want Helmsman to connect kubectl to your cluster for you. - **username** : the username to be used for kubectl credentials. - **password** : an environment variable name (starting with `$`) where your password is stored. Get the password from your k8s admin or consult k8s docs on how to get/set it. @@ -182,13 +182,18 @@ clientKey = "s3://mybucket/mydir/helm.key.pem" env = "prod" [namespaces.production.annotations] iam.amazonaws.com/role = "dynamodb-reader" -[namespaces.production.limits] +[[namespaces.production.limits]] +type = "Container" [namespaces.production.limits.default] cpu = "300m" memory = "200Mi" [namespaces.production.limits.defaultRequest] cpu = "200m" memory = "100Mi" +[[namespaces.production.limits]] +type = "Pod" +[namespaces.production.limits.max] +memory = "300Mi" ``` ```yaml @@ -210,12 +215,16 @@ namespaces: clientCert: "gs://mybucket/mydir/helm.cert.pem" clientKey: "s3://mybucket/mydir/helm.key.pem" limits: - default: - cpu: "300m" - memory: "200Mi" - defaultRequest: - cpu: "200m" - memory: "100Mi" + - type: Container + default: + cpu: "300m" + memory: "200Mi" + defaultRequest: + cpu: "200m" + memory: "100Mi" + - type: Pod + max: + memory: "300Mi" labels: env: "prod" annotations: @@ -239,7 +248,7 @@ Authenticating to private helm repos: - Or, set `GCLOUD_CREDENTIALS` environment variable to contain the content of the credentials.json file. Options: -- you can define any key/value pairs where key is the repo name and value is a valid URI for the repo. Basic auth info can be added in the repo URL as in the example below. +- you can define any key/value pairs where key is the repo name and value is a valid URI for the repo. Basic auth info can be added in the repo URL as in the example below. Example: diff --git a/docs/how_to/README.md b/docs/how_to/README.md index fedecd3a..b2915641 100644 --- a/docs/how_to/README.md +++ b/docs/how_to/README.md @@ -11,23 +11,23 @@ This page contains a list of guides on how to use Helmsman. - [Using the current kube context](settings/current_kube_context.md) - [Connecting with certificates](settings/creating_kube_context_with_certs.md) - [Connecting with bearer token](settings/creating_kube_context_with_token.md) -- Defining Namespaces +- Defining Namespaces - [Create namespaces](namespaces/create.md) - [Label namespaces](namespaces/labels_and_annotations.md) - [Set resource limits for namespaces](namespaces/limits.md) - [Protecting namespaces](namespaces/protection.md) - Deploying Helm Tiller - - [Using existing Tillers](tiller/existing.md) + - [Using existing Tillers](tiller/existing.md) - [Deploy shared Tiller in kube-system](tiller/shared.md) - [Prevent Deploying Tiller in kube-system](tiller/prevent_tiller_in_kube_system.md) - [Deploy Multiple Tillers with custom setup for each](tiller/multitenancy.md) - [Deploy apps with specific Tillers](deploy_apps_with_specific_tiller.md) - Defining Helm repositories - - [Using default helm repos](helm_repos/default.md) + - [Using default helm repos](helm_repos/default.md) - [Using private repos in Google GCS](helm_repos/gcs.md) - - [Using private repos in AWS S3](helm_repos/s3.md) + - [Using private repos in AWS S3](helm_repos/s3.md) - [Using private repos with basic auth](helm_repos/basic_auth.md) - - [Using pre-configured repos](helm_repos/pre_configured.md) + - [Using pre-configured repos](helm_repos/pre_configured.md) - [Using local charts](helm_repos/local.md) - Manipulating Apps - [Basic operations](apps/basic.md) @@ -36,8 +36,8 @@ This page contains a list of guides on how to use Helmsman. - [Protect releases (apps)](apps/protection.md) - [Moving releases (apps) across namespaces](apps/moving_across_namespaces.md) - [Override defined namespaces](apps/override_namespaces.md) - - [Run helm tests for deployed releases (apps)](apps/helm_tests.md) - - [Define the order of apps operations](apps/order.md) + - [Run helm tests for deployed releases (apps)](apps/helm_tests.md) + - [Define the order of apps operations](apps/order.md) - [Delete all releases (apps)](apps/destroy.md) - Running Helmsman in different environments - [Running Helmsman in CI](deployment/ci.md) diff --git a/docs/how_to/namespaces/limits.md b/docs/how_to/namespaces/limits.md index 609fe89a..611cd603 100644 --- a/docs/how_to/namespaces/limits.md +++ b/docs/how_to/namespaces/limits.md @@ -2,23 +2,29 @@ version: v1.8.0 --- -# Define resource limits for namespaces +# Define resource limits for namespaces You can define namespaces to be used in your cluster. If they don't exist, Helmsman will create them for you. You can also define how much resource limits to set for each namespace. -You can read more about the `LimitRange` specification [here](https://docs.openshift.com/enterprise/3.2/dev_guide/compute_resources.html#dev-viewing-limit-ranges). +You can read more about the `LimitRange` specification [here](https://docs.openshift.com/container-platform/3.11/dev_guide/compute_resources.html#dev-limit-ranges). ```toml #... [namespaces] [namespaces.staging] - [namespaces.staging.limits.default] - cpu = "300m" - memory = "200Mi" - [namespaces.staging.limits.defaultRequest] - cpu = "200m" - memory = "100Mi" + [[namespaces.staging.limits]] + type = "Container" + [namespaces.staging.limits.default] + cpu = "300m" + memory = "200Mi" + [namespaces.staging.limits.defaultRequest] + cpu = "200m" + memory = "100Mi" + [[namespaces.staging.limits]] + type = "Pod" + [namespaces.staging.limits.max] + memory = "300Mi" [namespaces.production] #... @@ -29,14 +35,18 @@ You can read more about the `LimitRange` specification [here](https://docs.opens namespaces: staging: limits: - default: - cpu: "300m" - memory: "200Mi" - defaultRequest: - cpu: "200m" - memory: "100Mi" + - type: Container + default: + cpu: "300m" + memory: "200Mi" + defaultRequest: + cpu: "200m" + memory: "100Mi" + - type: Pod + max: + memory: "300Mi" production: ``` -The example above will create two namespaces; staging and production with resource limits defined for the staging namespace. \ No newline at end of file +The example above will create two namespaces; staging and production with resource limits defined for the staging namespace. diff --git a/example.toml b/example.toml index 48721aac..e6c50bdf 100644 --- a/example.toml +++ b/example.toml @@ -38,13 +38,18 @@ [namespaces] [namespaces.production] protected = true - [namespaces.production.limits] - [namespaces.production.limits.default] - cpu = "300m" - memory = "200Mi" - [namespaces.production.limits.defaultRequest] - cpu = "200m" - memory = "100Mi" + [[namespaces.production.limits]] + type = "Container" + [namespaces.production.limits.default] + cpu = "300m" + memory = "200Mi" + [namespaces.production.limits.defaultRequest] + cpu = "200m" + memory = "100Mi" + [[namespaces.production.limits]] + type = "Pod" + [namespaces.production.limits.max] + memory = "300Mi" [namespaces.staging] protected = false installTiller = true diff --git a/example.yaml b/example.yaml index 9a7054ac..7b192d87 100644 --- a/example.yaml +++ b/example.yaml @@ -32,12 +32,16 @@ namespaces: production: protected: true limits: - default: - cpu: "300m" - memory: "200Mi" - defaultRequest: - cpu: "200m" - memory: "100Mi" + - type: Container + default: + cpu: "300m" + memory: "200Mi" + defaultRequest: + cpu: "200m" + memory: "100Mi" + - type: Pod + max: + memory: "300Mi" staging: protected: false installTiller: true diff --git a/kube_helpers.go b/kube_helpers.go index 288af41a..c160a7b1 100644 --- a/kube_helpers.go +++ b/kube_helpers.go @@ -134,18 +134,18 @@ func annotateNamespace(ns string, labels map[string]string) { // setLimits creates a LimitRange resource in the provided Namespace func setLimits(ns string, lims limits) { - if lims == (limits{}) { + if len(lims) == 0 { return } - definition := `--- + definition := ` +--- apiVersion: v1 kind: LimitRange metadata: name: limit-range spec: limits: - - type: Container ` d, err := yaml.Marshal(&lims) if err != nil { diff --git a/namespace.go b/namespace.go index 38dc4913..070e940f 100644 --- a/namespace.go +++ b/namespace.go @@ -6,16 +6,18 @@ import ( // resources type type resources struct { - Cpu string `yaml:"cpu,omitempty"` + CPU string `yaml:"cpu,omitempty"` Memory string `yaml:"memory,omitempty"` } // limits type -type limits struct { - Max resources `yaml:"max,omitempty"` - Min resources `yaml:"min,omitempty"` - Default resources `yaml:"default,omitempty"` - DefaultRequest resources `yaml:"defaultRequest,omitempty"` +type limits []struct { + Max resources `yaml:"max,omitempty"` + Min resources `yaml:"min,omitempty"` + Default resources `yaml:"default,omitempty"` + DefaultRequest resources `yaml:"defaultRequest,omitempty"` + MaxLimitRequestRatio resources `yaml:"maxLimitRequestRatio,omitempty"` + LimitType string `yaml:"type"` } // namespace type represents the fields of a namespace From fb63593c20ac442e9c7f77e0bc5a2e227de64f7e Mon Sep 17 00:00:00 2001 From: hatemosphere Date: Sat, 16 Mar 2019 19:37:38 +0200 Subject: [PATCH 0376/1127] Consistent TOML examples formatting --- example.toml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/example.toml b/example.toml index e6c50bdf..8dfe8b25 100644 --- a/example.toml +++ b/example.toml @@ -40,16 +40,16 @@ protected = true [[namespaces.production.limits]] type = "Container" - [namespaces.production.limits.default] - cpu = "300m" - memory = "200Mi" - [namespaces.production.limits.defaultRequest] - cpu = "200m" - memory = "100Mi" + [namespaces.production.limits.default] + cpu = "300m" + memory = "200Mi" + [namespaces.production.limits.defaultRequest] + cpu = "200m" + memory = "100Mi" [[namespaces.production.limits]] type = "Pod" - [namespaces.production.limits.max] - memory = "300Mi" + [namespaces.production.limits.max] + memory = "300Mi" [namespaces.staging] protected = false installTiller = true From 1c5d2d465bb186449796acc55559a9bfa3aa10cb Mon Sep 17 00:00:00 2001 From: John Krukoff Date: Tue, 19 Mar 2019 12:44:42 -0600 Subject: [PATCH 0377/1127] [master]: Convert AWS credential check to warning. This converts the current check for AWS environment variables to a warning instead of a fatal error. This should allow the AWS SDK to use other methods (such as machine roles) to authenticate. --- aws/aws.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/aws.go b/aws/aws.go index ce2a9a93..207bf2b7 100644 --- a/aws/aws.go +++ b/aws/aws.go @@ -36,7 +36,7 @@ func ReadFile(bucketName string, filename string, outFile string, noColors bool) style = aurora.NewAurora(!noColors) // Checking env vars are set to configure AWS if !checkCredentialsEnvVar() { - log.Fatal(style.Bold(style.Red("ERROR: Failed to find the AWS env vars needed to configure AWS. Please make sure they are set in the environment."))) + log.Println("WARN: Failed to find the AWS env vars needed to configure AWS. Please make sure they are set in the environment.") } // Create Session -- use config (credentials + region) from env vars or aws profile From 84faa0b7017936552511d5b3d87a504aec298c0c Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Wed, 20 Mar 2019 14:50:45 +0100 Subject: [PATCH 0378/1127] fix azure container validation error --- state.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/state.go b/state.go index 862a7841..ae11bd16 100644 --- a/state.go +++ b/state.go @@ -77,7 +77,7 @@ func (s state) validate() (bool, string) { for key, value := range s.Certificates { r, path := isValidCert(value) if !r { - return false, "ERROR: certifications validation failed -- [ " + key + " ] must be a valid S3 or GCS bucket URL or a valid relative file path." + return false, "ERROR: certifications validation failed -- [ " + key + " ] must be a valid S3, GCS, AZ bucket/container URL or a valid relative file path." } s.Certificates[key] = path } @@ -184,7 +184,7 @@ func (s state) validate() (bool, string) { func isValidCert(value string) (bool, string) { _, err1 := url.ParseRequestURI(value) _, err2 := os.Stat(value) - if err2 != nil && (err1 != nil || (!strings.HasPrefix(value, "s3://") && !strings.HasPrefix(value, "gs://"))) { + if err2 != nil && (err1 != nil || (!strings.HasPrefix(value, "s3://") && !strings.HasPrefix(value, "gs://") && !strings.HasPrefix(value, "az://"))) { return false, "" } return true, value From 3e07204d27c114b4fa8d5793526546787c0453d7 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Wed, 20 Mar 2019 14:56:03 +0100 Subject: [PATCH 0379/1127] adding note about special characters --- docs/how_to/helm_repos/basic_auth.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/how_to/helm_repos/basic_auth.md b/docs/how_to/helm_repos/basic_auth.md index 3121c24d..284729e3 100644 --- a/docs/how_to/helm_repos/basic_auth.md +++ b/docs/how_to/helm_repos/basic_auth.md @@ -8,6 +8,8 @@ Helmsman allows you to use any private helm repo hosting which supports basic au For such repos, you need to add the basic auth information in the repo URL as in the example below: +> Be aware that some special characters in the username or password can make the URL invalid. + ```toml [helmRepos] From 18dab9d03441aa7450c2dc73f468424707ed11c2 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Wed, 20 Mar 2019 15:06:45 +0100 Subject: [PATCH 0380/1127] releasing v1.8.1 --- README.md | 8 +++----- main.go | 2 +- release-notes.md | 14 ++++---------- 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index dc0eb4e0..1e45ed47 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,4 @@ ---- -version: v1.8.0 ---- +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v1.8.1&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -58,9 +56,9 @@ If you use private helm repos, you will need either `helm-gcs` or `helm-s3` plug Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. ``` # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v1.8.0/helmsman_1.8.0_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.8.1/helmsman_1.8.1_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v1.8.0/helmsman_1.8.0_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.8.1/helmsman_1.8.1_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/main.go b/main.go index d46545dd..2bac6317 100644 --- a/main.go +++ b/main.go @@ -34,7 +34,7 @@ var shouldCleanup bool var skipValidation bool var applyLabels bool var keepUntrackedReleases bool -var appVersion = "v1.8.0" +var appVersion = "v1.8.1" var helmVersion string var kubectlVersion string var dryRun bool diff --git a/release-notes.md b/release-notes.md index ce57af36..61ae86a7 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,15 +1,9 @@ -# v1.8.0 +# v1.8.1 > If you are already using an older version of Helmsman than v1.4.0-rc, please read the changes below carefully and follow the upgrade guide [here](docs/migrating_to_v1.4.0-rc.md) -# New features: -- Support for connection to clusters with bearer token and therefore allowing helmsman to run inside a k8s cluster. PR #206 -- Improved cluster connection validation and error messages. PR #206 -- Making the `settings` and `helmRepos` stanzas optional. PR #206 #2014 -- Support for private repos with basic auth. PR #214 -- Support for using pre-configured helm repos. PR #201 -- Support for using Azure blob storage for certificates and keys. PR #213 - # Fixes: -- Minor bug fixes: PRs #205 #212 +- Allowing other forms of AWS authentication PR #208 +- Fixing a validation error for Azure blob storage urls. +- Allowing setting LimitRange for pods. From 6d6513473a1224615a7f06019a1f8187c12f396e Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Wed, 20 Mar 2019 15:47:28 +0100 Subject: [PATCH 0381/1127] code cleanup --- azure/azblob.go | 1 - 1 file changed, 1 deletion(-) diff --git a/azure/azblob.go b/azure/azblob.go index 5a26c2c5..20d16515 100644 --- a/azure/azblob.go +++ b/azure/azblob.go @@ -65,7 +65,6 @@ func ReadFile(containerName string, filename string, outFile string, noColors bo if _, err = downloadedData.ReadFrom(bodyStream); err != nil { log.Fatal(style.Bold(style.Red("ERROR: failed to download file " + filename + " with error: " + err.Error()))) } - fmt.Println("Downloaded the blob: " + downloadedData.String()) // create output file and write to it var writers []io.Writer From 987594b8d9376084dc7efc93e0d64f8873c308f8 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Wed, 20 Mar 2019 15:52:24 +0100 Subject: [PATCH 0382/1127] initialize helm client in all cases --- helm_helpers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helm_helpers.go b/helm_helpers.go index d20a2480..48937473 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -422,7 +422,7 @@ func initHelmClientOnly() (bool, string) { // initHelm initializes helm on a k8s cluster and deploys Tiller in one or more namespaces func initHelm() (bool, string) { - + initHelmClientOnly() defaultSA := s.Settings.ServiceAccount for k, ns := range s.Namespaces { From cbf9bc946e695de0ec1e9726b3ef162f809b3152 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Thu, 21 Mar 2019 13:06:26 +0100 Subject: [PATCH 0383/1127] fix toml format --- docs/how_to/namespaces/labels_and_annotations.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/how_to/namespaces/labels_and_annotations.md b/docs/how_to/namespaces/labels_and_annotations.md index 9a9307e1..5cb180f4 100644 --- a/docs/how_to/namespaces/labels_and_annotations.md +++ b/docs/how_to/namespaces/labels_and_annotations.md @@ -15,7 +15,7 @@ You can define namespaces to be used in your cluster. If they don't exist, Helms env = "staging" [namespaces.production] [namespaces.production.annotations] - iam.amazonaws.com/role = "dynamodb-reader" + "iam.amazonaws.com/role" = "dynamodb-reader" #... @@ -33,4 +33,4 @@ namespaces: ``` -The above examples create two namespaces; staging and production. The staging namespace has one label `env`= `staging` while the production namespace has one annotation `iam.amazonaws.com/role`=`dynamodb-reader`. \ No newline at end of file +The above examples create two namespaces; staging and production. The staging namespace has one label `env`= `staging` while the production namespace has one annotation `iam.amazonaws.com/role`=`dynamodb-reader`. From 96e7e6e19f8a55a771c0a276b60ea049afee96f4 Mon Sep 17 00:00:00 2001 From: Greg Sidelinger Date: Mon, 25 Mar 2019 01:31:34 -0400 Subject: [PATCH 0384/1127] Adding namespace to role binding name. --- kube_helpers.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kube_helpers.go b/kube_helpers.go index c160a7b1..ecbe3667 100644 --- a/kube_helpers.go +++ b/kube_helpers.go @@ -357,11 +357,11 @@ func createRoleBinding(role string, saName string, namespace string) (bool, stri if clusterRole { bindingOption = "--clusterrole=" + role } - + roleBinding := namespace + ":" + saName + "-binding" log.Println("INFO: creating " + resource + " for service account [ " + saName + " ] in namespace [ " + namespace + " ] with role: " + role + ".") cmd := command{ Cmd: "bash", - Args: []string{"-c", "kubectl create " + resource + " " + saName + "-binding " + bindingOption + " --serviceaccount " + namespace + ":" + saName + " -n " + namespace}, + Args: []string{"-c", "kubectl create " + resource + " " + roleBinding + " " + bindingOption + " --serviceaccount " + namespace + ":" + saName + " -n " + namespace}, Description: "creating " + resource + " for service account [ " + saName + " ] in namespace [ " + namespace + " ] with role: " + role, } From 19c55e2785c3710a800b0c473c85ecbb23a32759 Mon Sep 17 00:00:00 2001 From: Greg Sidelinger Date: Mon, 25 Mar 2019 01:52:06 -0400 Subject: [PATCH 0385/1127] Only add namespace for clusterrolebindings --- kube_helpers.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/kube_helpers.go b/kube_helpers.go index ecbe3667..e916c12e 100644 --- a/kube_helpers.go +++ b/kube_helpers.go @@ -353,15 +353,17 @@ func createRoleBinding(role string, saName string, namespace string) (bool, stri resource = "clusterrolebinding" } + bindingName := saName + "-binding" bindingOption := "--role=" + role if clusterRole { bindingOption = "--clusterrole=" + role + bindingName = namespace + ":" + saName + "-binding" } - roleBinding := namespace + ":" + saName + "-binding" + log.Println("INFO: creating " + resource + " for service account [ " + saName + " ] in namespace [ " + namespace + " ] with role: " + role + ".") cmd := command{ Cmd: "bash", - Args: []string{"-c", "kubectl create " + resource + " " + roleBinding + " " + bindingOption + " --serviceaccount " + namespace + ":" + saName + " -n " + namespace}, + Args: []string{"-c", "kubectl create " + resource + " " + bindingName + " " + bindingOption + " --serviceaccount " + namespace + ":" + saName + " -n " + namespace}, Description: "creating " + resource + " for service account [ " + saName + " ] in namespace [ " + namespace + " ] with role: " + role, } From eee6cd9bb517facb4fe83e12a85c4dd20b22d0f9 Mon Sep 17 00:00:00 2001 From: hatemosphere Date: Tue, 26 Mar 2019 14:01:17 +0200 Subject: [PATCH 0386/1127] Adding diff for chart version upgrade --- decision_maker.go | 1 + 1 file changed, 1 insertion(+) diff --git a/decision_maker.go b/decision_maker.go index ee4a0b42..db6356cd 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -188,6 +188,7 @@ func inspectUpgradeScenario(r *release, rs releaseState) { if r.Namespace == rs.Namespace { if extractChartName(r.Chart) == getReleaseChartName(rs) && r.Version != getReleaseChartVersion(rs) { // upgrade + diffRelease(r) upgradeRelease(r) logDecision("DECISION: release [ "+r.Name+" ] is desired to be upgraded. Planning this for you!", r.Priority, change) From ec5b332fe66f4583c23ab80fd818f34a6bb4e0b7 Mon Sep 17 00:00:00 2001 From: Nathan Flynn Date: Tue, 26 Mar 2019 16:18:40 +0000 Subject: [PATCH 0387/1127] Typo on word labels Typos on word for labels in namespaces labels example --- docs/how_to/namespaces/labels_and_annotations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how_to/namespaces/labels_and_annotations.md b/docs/how_to/namespaces/labels_and_annotations.md index 5cb180f4..3ffc092c 100644 --- a/docs/how_to/namespaces/labels_and_annotations.md +++ b/docs/how_to/namespaces/labels_and_annotations.md @@ -25,7 +25,7 @@ You can define namespaces to be used in your cluster. If they don't exist, Helms namespaces: staging: - lables: + labels: env: "staging" production: annotations: From 8b6b8c10b2a82685f2f6f99b511224ba900c282b Mon Sep 17 00:00:00 2001 From: Artem Yarmoluk Date: Tue, 16 Apr 2019 20:17:27 +0300 Subject: [PATCH 0388/1127] Cleanup helms secrets if any --- decision_maker.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/decision_maker.go b/decision_maker.go index db6356cd..79092c52 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -335,6 +335,7 @@ func getValuesFiles(r *release) string { logError("Failed to decrypt secret file" + r.SecretsFile) } fileList = append(fileList, r.SecretsFile+".dec") + shouldCleanup = true } else if len(r.SecretsFiles) > 0 { if !helmPluginExists("secrets") { logError("ERROR: helm secrets plugin is not installed/configured correctly. Aborting!") @@ -350,6 +351,7 @@ func getValuesFiles(r *release) string { } } fileList = append(fileList, r.SecretsFiles...) + shouldCleanup = true } if len(fileList) > 0 { From 71e30cf0cfa010100c932296476fcd0399feeecc Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Tue, 23 Apr 2019 10:25:37 +0200 Subject: [PATCH 0389/1127] Add 'autoscaling' apiGroup to data/role.yaml --- data/role.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/role.yaml b/data/role.yaml index 29c24b37..43211a25 100644 --- a/data/role.yaml +++ b/data/role.yaml @@ -6,6 +6,6 @@ metadata: labels: CREATED-BY: HELMSMAN rules: -- apiGroups: ["", "batch", "extensions", "apps", "rbac.authorization.k8s.io"] +- apiGroups: ["", "batch", "extensions", "apps", "autoscaling", "rbac.authorization.k8s.io"] resources: ["*"] verbs: ["*"] From 594975a3696b6f9bfda107e3e83c946638c4c553 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Wed, 24 Apr 2019 18:16:14 +0200 Subject: [PATCH 0390/1127] Add -target flag to limit execution to specific app --- README.md | 3 +++ decision_maker.go | 8 ++++++++ init.go | 8 ++++++++ main.go | 2 ++ 4 files changed, 21 insertions(+) diff --git a/README.md b/README.md index 1e45ed47..44ddc113 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,9 @@ To show debugging details: To run a dry-run: ``` $ helmsman --debug --dry-run -f example.toml ``` +To limit execution to specific application: +``` $ helmsman --debug --dry-run --target artifactory -f example.toml ``` + # Features - **Built for CD**: Helmsman can be used as a docker image or a binary. diff --git a/decision_maker.go b/decision_maker.go index db6356cd..232528eb 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -27,6 +27,14 @@ func makePlan(s *state) *plan { // decide makes a decision about what commands (actions) need to be executed // to make a release section of the desired state come true. func decide(r *release, s *state) { + // check for presence in defined targets + if len(targetMap) > 0 { + if _, ok := targetMap[r.Name]; !ok { + logDecision("DECISION: release [ "+r.Name+" ] is ignored by target flag. Skipping.", r.Priority, noop) + return + } + } + if destroy { if ok, rs := helmReleaseExists(r, "DEPLOYED"); ok { deleteRelease(r, rs) diff --git a/init.go b/init.go index 78e147a6..25175b72 100644 --- a/init.go +++ b/init.go @@ -44,6 +44,7 @@ func init() { flag.BoolVar(&apply, "apply", false, "apply the plan directly") flag.BoolVar(&debug, "debug", false, "show the execution logs") flag.BoolVar(&dryRun, "dry-run", false, "apply the dry-run option for helm commands.") + flag.Var(&target, "target", "limit execution to specific app.") flag.BoolVar(&destroy, "destroy", false, "delete all deployed releases. Purge delete is used if the purge option is set to true for the releases.") flag.BoolVar(&v, "v", false, "show the version") flag.BoolVar(&verbose, "verbose", false, "show verbose execution logs") @@ -182,6 +183,13 @@ func init() { } } + if len(target) > 0 { + targetMap = map[string]bool{} + for _, v := range target { + targetMap[v] = true + } + } + } // toolExists returns true if the tool is present in the environment and false otherwise. diff --git a/main.go b/main.go index 2bac6317..e648cf93 100644 --- a/main.go +++ b/main.go @@ -38,6 +38,8 @@ var appVersion = "v1.8.1" var helmVersion string var kubectlVersion string var dryRun bool +var target stringArray +var targetMap map[string]bool var destroy bool var showDiff bool var suppressDiffSecrets bool From ffb0d886862d50c63f5d3873e4b9abcea14eb98f Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Wed, 24 Apr 2019 19:25:58 +0200 Subject: [PATCH 0391/1127] Add consistent log stdout and stderr streams for logs --- init.go | 12 +++++++----- plan.go | 4 ++-- utils.go | 1 + 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/init.go b/init.go index 78e147a6..227e2450 100644 --- a/init.go +++ b/init.go @@ -26,11 +26,11 @@ const ( ) func printUsage() { - fmt.Println(banner + "\n") - fmt.Println("Helmsman version: " + appVersion) - fmt.Println("Helmsman is a Helm Charts as Code tool which allows you to automate the deployment/management of your Helm charts.") - fmt.Println() - fmt.Println("Usage: helmsman [options]") + log.Println(banner + "\n") + log.Println("Helmsman version: " + appVersion) + log.Println("Helmsman is a Helm Charts as Code tool which allows you to automate the deployment/management of your Helm charts.") + log.Println() + log.Println("Usage: helmsman [options]") flag.PrintDefaults() } @@ -58,6 +58,8 @@ func init() { flag.BoolVar(&showDiff, "show-diff", false, "show helm diff results. Can expose sensitive information.") flag.BoolVar(&suppressDiffSecrets, "suppress-diff-secrets", false, "don't show secrets in helm diff output.") + log.SetOutput(os.Stdout) + flag.Usage = printUsage flag.Parse() diff --git a/plan.go b/plan.go index 7714d35e..ec06ab36 100644 --- a/plan.go +++ b/plan.go @@ -116,10 +116,10 @@ func (p plan) printPlanCmds() { // printPlan prints the decisions made in a plan. func (p plan) printPlan() { - fmt.Println("----------------------") + log.Println("----------------------") log.Println(style.Bold(style.Green("INFO: Plan generated at: " + p.Created.Format("Mon Jan _2 2006 15:04:05")))) for _, decision := range p.Decisions { - fmt.Println(style.Colorize(decision.Description+" -- priority: "+strconv.Itoa(decision.Priority), decisionColor[decision.Type])) + log.Println(style.Colorize(decision.Description+" -- priority: "+strconv.Itoa(decision.Priority), decisionColor[decision.Type])) } } diff --git a/utils.go b/utils.go index f7ebdabe..abf66300 100644 --- a/utils.go +++ b/utils.go @@ -402,6 +402,7 @@ func logError(msg string) { if _, err := url.ParseRequestURI(s.Settings.SlackWebhook); err == nil { notifySlack(msg, s.Settings.SlackWebhook, true, apply) } + log.SetOutput(os.Stderr) log.Fatal(style.Bold(style.Red(msg))) } From bd9ee7e251c8068ccfa68fd2a89b361cc26c0b33 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Wed, 24 Apr 2019 19:26:51 +0200 Subject: [PATCH 0392/1127] Trim error log on error when no -verbose is set --- plan.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plan.go b/plan.go index ec06ab36..ea105934 100644 --- a/plan.go +++ b/plan.go @@ -93,7 +93,11 @@ func (p plan) execPlan() { for _, cmd := range p.Commands { if exitCode, msg := cmd.Command.exec(debug, verbose); exitCode != 0 { - logError("Command returned with exit code: " + string(exitCode) + ". And error message: " + msg) + var errorMsg string + if errorMsg = msg; !verbose { + errorMsg = strings.Split(msg, "---")[0] + } + logError("Command returned with exit code: " + string(exitCode) + ". And error message: " + errorMsg) } else { log.Println(style.Cyan(msg)) if cmd.targetRelease != nil && !dryRun { From 92d1333f48aba06202b17f8bcb1e982a2cc927f3 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Fri, 26 Apr 2019 11:42:07 +0200 Subject: [PATCH 0393/1127] substitute env vars in values files. fixes #226 --- utils.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/utils.go b/utils.go index f7ebdabe..9d4de215 100644 --- a/utils.go +++ b/utils.go @@ -174,16 +174,16 @@ func resolvePaths(relativeToFile string, s *state) { for k, v := range s.Apps { if v.ValuesFile != "" { - v.ValuesFile, _ = filepath.Abs(filepath.Join(dir, v.ValuesFile)) + v.ValuesFile, _ = filepath.Abs(filepath.Join(dir, substituteEnv(v.ValuesFile))) } if v.SecretsFile != "" { - v.SecretsFile, _ = filepath.Abs(filepath.Join(dir, v.SecretsFile)) + v.SecretsFile, _ = filepath.Abs(filepath.Join(dir, substituteEnv(v.SecretsFile))) } for i, f := range v.ValuesFiles { - v.ValuesFiles[i], _ = filepath.Abs(filepath.Join(dir, f)) + v.ValuesFiles[i], _ = filepath.Abs(filepath.Join(dir, substituteEnv(f))) } for i, f := range v.SecretsFiles { - v.SecretsFiles[i], _ = filepath.Abs(filepath.Join(dir, f)) + v.SecretsFiles[i], _ = filepath.Abs(filepath.Join(dir, substituteEnv(f))) } if v.Chart != "" { From cf01789249b8eec783dd1e1cd4d18dd35e5cd2d7 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Fri, 26 Apr 2019 11:46:41 +0200 Subject: [PATCH 0394/1127] Add how-to doc describing -target flag --- .../limit-deployment-to-specific-apps.md | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 docs/how_to/limit-deployment-to-specific-apps.md diff --git a/docs/how_to/limit-deployment-to-specific-apps.md b/docs/how_to/limit-deployment-to-specific-apps.md new file mode 100644 index 00000000..43f2f7b0 --- /dev/null +++ b/docs/how_to/limit-deployment-to-specific-apps.md @@ -0,0 +1,47 @@ +--- +version: v1.9.0 +--- + +# limit execution to explicitly defined apps + +Starting from v1.9.0, Helmsman allows you to pass the `-target` flag multiple times to specify multiple apps +that limits apps considered by Helmsman during this specific execution. +Thanks to this one can deploy specific applications among all defined for an environment. + +## An example + +having environment defined with such apps: + +* example.yaml: +```yaml +... +apps: + jenkins: + namespace: "staging" # maps to the namespace as defined in namespaces above + enabled: true # change to false if you want to delete this app release empty: false: + chart: "stable/jenkins" # changing the chart name means delete and recreate this chart + version: "0.14.3" # chart version + + artifactory: + namespace: "production" # maps to the namespace as defined in namespaces above + enabled: true # change to false if you want to delete this app release empty: false: + chart: "stable/artifactory" # changing the chart name means delete and recreate this chart + version: "7.0.6" # chart version +... +``` + +running Helmsman with `-f example.yaml` would result in checking state and invoking deployment for both jenkins and artifactory application. + +With `-target` flag in command like + +```bash +$ helmsman -f example.yaml -target artifactory ... +``` + +one can execute Helmsman's environment defined with example.yaml limited to only one `artifactory` app. Others are ignored until the flag is defined. + +Multiple applications can be set with `-target`, like + +```bash +$ helmsman -f example.yaml -target artifactory -target jenkins ... +``` From 1f3bf2b9302c8a9dbfc4eda840c7c70a61509f7e Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Fri, 3 May 2019 10:45:58 +0200 Subject: [PATCH 0395/1127] clarifying docs --- docs/desired_state_specification.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 77ce3123..d68b6449 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -146,8 +146,8 @@ Options: - **tillerServiceAccount**: defines what service account to use when deploying Tiller. If this is not set, the following options are considered: - 1. If the `serviceAccount` defined in the `settings` section exists in the namespace you want to deploy Tiller in, it will be used, else - 2. Helmsman creates the service account in that namespace and binds it to a (cluster)role. If the namespace is kube-system and `tillerRole` is unset or is set to cluster-admin, the service account is bound to `cluster-admin` clusterrole. Otherwise, if you specified a `tillerRole`, a new role with that name is created and bound to the service account with rolebinding. If `tillerRole` is unset (for namespaces other than kube-system), the role is called `helmsman-tiller` and is created in the specified namespace to only gives access to that namespace. The custom role is created from a [yaml template](../data/role.yaml). + 1. If the `serviceAccount` defined in the `settings` section exists in the namespace you want to deploy Tiller in, it will be used. + 2. If you defined `serviceAccount` in the `settings` section and it does not exist in the namespace you want to deploy Tiller in, Helmsman creates the service account in that namespace and binds it to a (cluster)role. If the namespace is kube-system and `tillerRole` is unset or is set to cluster-admin, the service account is bound to `cluster-admin` clusterrole. Otherwise, if you specified a `tillerRole`, a new role with that name is created and bound to the service account with rolebinding. If `tillerRole` is unset (for namespaces other than kube-system), the role is called `helmsman-tiller` and is created in the specified namespace to only gives access to that namespace. The custom role is created from a [yaml template](../data/role.yaml). > If `installTiller` is not defined or set to false, this flag is ignored. From 32e91530df5de417b117d76fbc465b0b11153fd1 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Fri, 3 May 2019 13:35:20 +0200 Subject: [PATCH 0396/1127] proper env var substitution in values/secrets files --- helm_helpers.go | 1 - main.go | 11 +++++----- utils.go | 55 +++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 57 insertions(+), 10 deletions(-) diff --git a/helm_helpers.go b/helm_helpers.go index 48937473..d8fa4487 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -381,7 +381,6 @@ func deployTiller(namespace string, serviceAccount string, defaultServiceAccount tls := "" ns := s.Namespaces[namespace] if tillerTLSEnabled(ns) { - shouldCleanup = true // needed to activate cleaning up the certs upon exit tillerCert := namespace + "-tiller.cert" tillerKey := namespace + "-tiller.key" caCert := namespace + "-ca.cert" diff --git a/main.go b/main.go index 2bac6317..86885cee 100644 --- a/main.go +++ b/main.go @@ -30,7 +30,6 @@ var noColors bool var noFancy bool var noNs bool var nsOverride string -var shouldCleanup bool var skipValidation bool var applyLabels bool var keepUntrackedReleases bool @@ -42,6 +41,7 @@ var destroy bool var showDiff bool var suppressDiffSecrets bool +const tempFilesDir = "helmsman-temp-files" const stableHelmRepo = "https://kubernetes-charts.storage.googleapis.com" const incubatorHelmRepo = "http://storage.googleapis.com/kubernetes-charts-incubator" @@ -51,7 +51,6 @@ func main() { if r, msg := createContext(); !r { logError(msg) } - shouldCleanup = true } if apply || dryRun || destroy { @@ -110,9 +109,7 @@ func main() { p.execPlan() } - if shouldCleanup { - cleanup() - } + cleanup() log.Println("INFO: completed successfully!") } @@ -121,6 +118,7 @@ func main() { // It also deletes any Tiller TLS certs and keys // and secret files func cleanup() { + log.Println("INFO: cleaning up sensitive and temp files") if _, err := os.Stat("ca.crt"); err == nil { deleteFile("ca.crt") } @@ -165,4 +163,7 @@ func cleanup() { } } } + + // delete temp files with substituted env vars + os.RemoveAll(tempFilesDir) } diff --git a/utils.go b/utils.go index 9d4de215..251a9ae6 100644 --- a/utils.go +++ b/utils.go @@ -49,6 +49,7 @@ func fromTOML(file string, s *state) (bool, string) { } addDefaultHelmRepos(s) resolvePaths(file, s) + substituteEnvInValuesFiles(s) return true, "INFO: Parsed TOML [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps." } @@ -92,6 +93,7 @@ func fromYAML(file string, s *state) (bool, string) { } addDefaultHelmRepos(s) resolvePaths(file, s) + substituteEnvInValuesFiles(s) return true, "INFO: Parsed YAML [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps." } @@ -121,6 +123,51 @@ func toYAML(file string, s *state) { newFile.Close() } +// substituteEnvInValuesFiles loops through the values/secrets files and substitutes env variables into them +func substituteEnvInValuesFiles(s *state) { + log.Println("INFO: substituting env variables in values and secrets files ...") + for _, v := range s.Apps { + if v.ValuesFile != "" { + v.ValuesFile = substituteEnvInYaml(v.ValuesFile) + } + if v.SecretsFile != "" { + v.SecretsFile = substituteEnvInYaml(v.SecretsFile) + } + for i := range v.ValuesFiles { + v.ValuesFiles[i] = substituteEnvInYaml(v.ValuesFiles[i]) + } + for i := range v.SecretsFiles { + v.SecretsFiles[i] = substituteEnvInYaml(v.SecretsFiles[i]) + } + } +} + +// substituteEnvInYaml substitutes env variables in a Yaml file and creates a temp file with these values. +// Returns the path for the temp file +func substituteEnvInYaml(file string) string { + rawYamlFile, err := ioutil.ReadFile(file) + if err != nil { + logError(err.Error()) + } + yamlFile := substituteEnv(string(rawYamlFile)) + + // create a temp directory + if _, err := os.Stat(tempFilesDir); os.IsNotExist(err) { + err = os.MkdirAll(tempFilesDir, 0755) + if err != nil { + logError(err.Error()) + } + } + + // output file contents with env variables substituted into temp files + outFile := tempFilesDir + string(os.PathSeparator) + filepath.Base(file) + err = ioutil.WriteFile(outFile, []byte(yamlFile), 0644) + if err != nil { + logError(err.Error()) + } + return outFile +} + // invokes either yaml or toml parser considering file extension func fromFile(file string, s *state) (bool, string) { if isOfType(file, []string{".toml"}) { @@ -174,16 +221,16 @@ func resolvePaths(relativeToFile string, s *state) { for k, v := range s.Apps { if v.ValuesFile != "" { - v.ValuesFile, _ = filepath.Abs(filepath.Join(dir, substituteEnv(v.ValuesFile))) + v.ValuesFile, _ = filepath.Abs(filepath.Join(dir, v.ValuesFile)) } if v.SecretsFile != "" { - v.SecretsFile, _ = filepath.Abs(filepath.Join(dir, substituteEnv(v.SecretsFile))) + v.SecretsFile, _ = filepath.Abs(filepath.Join(dir, v.SecretsFile)) } for i, f := range v.ValuesFiles { - v.ValuesFiles[i], _ = filepath.Abs(filepath.Join(dir, substituteEnv(f))) + v.ValuesFiles[i], _ = filepath.Abs(filepath.Join(dir, f)) } for i, f := range v.SecretsFiles { - v.SecretsFiles[i], _ = filepath.Abs(filepath.Join(dir, substituteEnv(f))) + v.SecretsFiles[i], _ = filepath.Abs(filepath.Join(dir, f)) } if v.Chart != "" { From 70b4344727190a9814802284805b215461b77b73 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Fri, 3 May 2019 14:00:07 +0200 Subject: [PATCH 0397/1127] updating docs --- docs/cmd_reference.md | 5 ++++- docs/desired_state_specification.md | 4 +++- docs/how_to/README.md | 19 +++++++++---------- .../{ => misc}/auth_to_storage_providers.md | 0 .../{ => misc}/helmsman_on_windows10.md | 0 .../limit-deployment-to-specific-apps.md | 0 .../{ => misc}/merge_desired_state_files.md | 0 .../{ => misc}/multitenant_clusters_guide.md | 0 .../protect_namespaces_and_releases.md | 0 .../send_slack_notifications_from_helmsman.md | 0 10 files changed, 16 insertions(+), 12 deletions(-) rename docs/how_to/{ => misc}/auth_to_storage_providers.md (100%) rename docs/how_to/{ => misc}/helmsman_on_windows10.md (100%) rename docs/how_to/{ => misc}/limit-deployment-to-specific-apps.md (100%) rename docs/how_to/{ => misc}/merge_desired_state_files.md (100%) rename docs/how_to/{ => misc}/multitenant_clusters_guide.md (100%) rename docs/how_to/{ => misc}/protect_namespaces_and_releases.md (100%) rename docs/how_to/{ => misc}/send_slack_notifications_from_helmsman.md (100%) diff --git a/docs/cmd_reference.md b/docs/cmd_reference.md index e674f248..c89ad16d 100644 --- a/docs/cmd_reference.md +++ b/docs/cmd_reference.md @@ -1,5 +1,5 @@ --- -version: v1.7.3 +version: v1.9.0 --- # CMD reference @@ -63,3 +63,6 @@ This is the list of the available CMD options in Helmsman: `--kubeconfig` path to the kubeconfig file to use for CLI requests + + `--target` + limit execution to specific app. diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index d68b6449..e23aa29f 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -1,5 +1,5 @@ --- -version: v1.8.0 +version: v1.9.0 --- # Helmsman desired state specification @@ -16,6 +16,8 @@ This document describes the specification for how to write your Helm charts desi > You can use environment variables in the desired state files. The environment variable name should start with "$", or encapsulated in "${", "}". "$" characters can be escaped like "$$". +> Starting from v1.9.0, you can also use environment variables in your helm values/secrets files. + ## Metadata Optional : Yes. diff --git a/docs/how_to/README.md b/docs/how_to/README.md index b2915641..1bffb1e2 100644 --- a/docs/how_to/README.md +++ b/docs/how_to/README.md @@ -1,6 +1,3 @@ ---- -version: v1.8.0 ---- # How To Guides @@ -21,7 +18,7 @@ This page contains a list of guides on how to use Helmsman. - [Deploy shared Tiller in kube-system](tiller/shared.md) - [Prevent Deploying Tiller in kube-system](tiller/prevent_tiller_in_kube_system.md) - [Deploy Multiple Tillers with custom setup for each](tiller/multitenancy.md) - - [Deploy apps with specific Tillers](deploy_apps_with_specific_tiller.md) + - [Deploy apps with specific Tillers](tiller/deploy_apps_with_specific_tiller.md) - Defining Helm repositories - [Using default helm repos](helm_repos/default.md) - [Using private repos in Google GCS](helm_repos/gcs.md) @@ -41,10 +38,12 @@ This page contains a list of guides on how to use Helmsman. - [Delete all releases (apps)](apps/destroy.md) - Running Helmsman in different environments - [Running Helmsman in CI](deployment/ci.md) - - [Running Helmsman inside your k8s cluster](inside_k8s.md) + - [Running Helmsman inside your k8s cluster](deployment/inside_k8s.md) - Misc - - [Authenticating to cloud storage providers](auth_to_storage_providers.md) - - [Send slack notifications from Helmsman](send_slack_notifications_from_helmsman.md) - - [Merge multiple desired state files](merge_desired_state_files.md) - - [Multitenant clusters guide](multitenant_clusters_guide.md) - - [Helmsman on Windows 10](helmsman_on_windows10.md) + - [Authenticating to cloud storage providers](misc/auth_to_storage_providers.md) + - [Protecting namespaces and releases](misc/protect_namespaces_and_releases.md) + - [Send slack notifications from Helmsman](misc/send_slack_notifications_from_helmsman.md) + - [Merge multiple desired state files](misc/merge_desired_state_files.md) + - [Limit Helmsman deployment to specific apps](misc/limit-deployment-to-specific-apps.md) + - [Multitenant clusters guide](misc/multitenant_clusters_guide.md) + - [Helmsman on Windows 10](misc/helmsman_on_windows10.md) diff --git a/docs/how_to/auth_to_storage_providers.md b/docs/how_to/misc/auth_to_storage_providers.md similarity index 100% rename from docs/how_to/auth_to_storage_providers.md rename to docs/how_to/misc/auth_to_storage_providers.md diff --git a/docs/how_to/helmsman_on_windows10.md b/docs/how_to/misc/helmsman_on_windows10.md similarity index 100% rename from docs/how_to/helmsman_on_windows10.md rename to docs/how_to/misc/helmsman_on_windows10.md diff --git a/docs/how_to/limit-deployment-to-specific-apps.md b/docs/how_to/misc/limit-deployment-to-specific-apps.md similarity index 100% rename from docs/how_to/limit-deployment-to-specific-apps.md rename to docs/how_to/misc/limit-deployment-to-specific-apps.md diff --git a/docs/how_to/merge_desired_state_files.md b/docs/how_to/misc/merge_desired_state_files.md similarity index 100% rename from docs/how_to/merge_desired_state_files.md rename to docs/how_to/misc/merge_desired_state_files.md diff --git a/docs/how_to/multitenant_clusters_guide.md b/docs/how_to/misc/multitenant_clusters_guide.md similarity index 100% rename from docs/how_to/multitenant_clusters_guide.md rename to docs/how_to/misc/multitenant_clusters_guide.md diff --git a/docs/how_to/protect_namespaces_and_releases.md b/docs/how_to/misc/protect_namespaces_and_releases.md similarity index 100% rename from docs/how_to/protect_namespaces_and_releases.md rename to docs/how_to/misc/protect_namespaces_and_releases.md diff --git a/docs/how_to/send_slack_notifications_from_helmsman.md b/docs/how_to/misc/send_slack_notifications_from_helmsman.md similarity index 100% rename from docs/how_to/send_slack_notifications_from_helmsman.md rename to docs/how_to/misc/send_slack_notifications_from_helmsman.md From 4fcc0d9c307a4baa08c9757fd74e59852abe14a4 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Fri, 3 May 2019 14:00:37 +0200 Subject: [PATCH 0398/1127] releasing v1.9.0 --- README.md | 6 +++--- main.go | 2 +- release-notes.md | 14 +++++++++----- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 44ddc113..0f629ceb 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v1.8.1&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v1.9.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -59,9 +59,9 @@ If you use private helm repos, you will need either `helm-gcs` or `helm-s3` plug Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. ``` # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v1.8.1/helmsman_1.8.1_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.9.0/helmsman_1.9.0_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v1.8.1/helmsman_1.8.1_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.9.0/helmsman_1.9.0_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/main.go b/main.go index 24c8d165..5635496d 100644 --- a/main.go +++ b/main.go @@ -33,7 +33,7 @@ var nsOverride string var skipValidation bool var applyLabels bool var keepUntrackedReleases bool -var appVersion = "v1.8.1" +var appVersion = "v1.9.0" var helmVersion string var kubectlVersion string var dryRun bool diff --git a/release-notes.md b/release-notes.md index 61ae86a7..0881d695 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,9 +1,13 @@ -# v1.8.1 +# v1.9.0 > If you are already using an older version of Helmsman than v1.4.0-rc, please read the changes below carefully and follow the upgrade guide [here](docs/migrating_to_v1.4.0-rc.md) -# Fixes: -- Allowing other forms of AWS authentication PR #208 -- Fixing a validation error for Azure blob storage urls. -- Allowing setting LimitRange for pods. +# Fixes and improvements: +- Cleanup helm secrets if any. PR #232 +- Add `autoscaling` apiGroup to thelmsman-created Tiller roles. PR #235 +- Set proper stderr and stdout for log streams. PR #239 + +# New features: +- Add -target flag to limit execution to specific app. PR #237 +- Substitute environment variables in helm values/secrets files. PR #240 From dcd741e4ce98bdd61b85ae8b44d358957896860f Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Fri, 3 May 2019 14:06:43 +0200 Subject: [PATCH 0399/1127] code cleanup --- decision_maker.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index 5d2fbaa9..232528eb 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -343,7 +343,6 @@ func getValuesFiles(r *release) string { logError("Failed to decrypt secret file" + r.SecretsFile) } fileList = append(fileList, r.SecretsFile+".dec") - shouldCleanup = true } else if len(r.SecretsFiles) > 0 { if !helmPluginExists("secrets") { logError("ERROR: helm secrets plugin is not installed/configured correctly. Aborting!") @@ -359,7 +358,6 @@ func getValuesFiles(r *release) string { } } fileList = append(fileList, r.SecretsFiles...) - shouldCleanup = true } if len(fileList) > 0 { From fad2035b20a8cd7c436104671ada462069a25a72 Mon Sep 17 00:00:00 2001 From: Brenden Matthews Date: Fri, 3 May 2019 21:01:23 -0400 Subject: [PATCH 0400/1127] Improve temporary dir handling. - renamed temp dir from `helmsman-temp-dir` to `.helmsman-tmp` - defer cleanup of temporary files at the start of main() - use ioutil TempDir() instead of custom code There's still one outstanding issue which is that the temp dir won't be cleaned up properly if the program exits early while if an error happens inside init(). --- init.go | 4 ++++ main.go | 10 +++++----- utils.go | 12 +++++------- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/init.go b/init.go index b8557b1f..768d4ad7 100644 --- a/init.go +++ b/init.go @@ -133,6 +133,10 @@ func init() { } } + // wipe & create a temporary directory + os.RemoveAll(tempFilesDir) + _ = os.MkdirAll(tempFilesDir, 0755) + // read the TOML/YAML desired state file var fileState state for _, f := range files { diff --git a/main.go b/main.go index 5635496d..7f3310fa 100644 --- a/main.go +++ b/main.go @@ -43,11 +43,15 @@ var destroy bool var showDiff bool var suppressDiffSecrets bool -const tempFilesDir = "helmsman-temp-files" +const tempFilesDir = ".helmsman-tmp" const stableHelmRepo = "https://kubernetes-charts.storage.googleapis.com" const incubatorHelmRepo = "http://storage.googleapis.com/kubernetes-charts-incubator" func main() { + // delete temp files with substituted env vars when the program terminates + defer os.RemoveAll(tempFilesDir) + defer cleanup() + // set the kubecontext to be used Or create it if it does not exist if !setKubeContext(s.Settings.KubeContext) { if r, msg := createContext(); !r { @@ -111,8 +115,6 @@ func main() { p.execPlan() } - cleanup() - log.Println("INFO: completed successfully!") } @@ -166,6 +168,4 @@ func cleanup() { } } - // delete temp files with substituted env vars - os.RemoveAll(tempFilesDir) } diff --git a/utils.go b/utils.go index a42fdfe2..be14872f 100644 --- a/utils.go +++ b/utils.go @@ -9,6 +9,7 @@ import ( "net/http" "net/url" "os" + "path" "path/filepath" "strconv" "strings" @@ -151,16 +152,13 @@ func substituteEnvInYaml(file string) string { } yamlFile := substituteEnv(string(rawYamlFile)) - // create a temp directory - if _, err := os.Stat(tempFilesDir); os.IsNotExist(err) { - err = os.MkdirAll(tempFilesDir, 0755) - if err != nil { - logError(err.Error()) - } + dir, err := ioutil.TempDir(tempFilesDir, "tmp") + if err != nil { + logError(err.Error()) } // output file contents with env variables substituted into temp files - outFile := tempFilesDir + string(os.PathSeparator) + filepath.Base(file) + outFile := path.Join(dir, filepath.Base(file)) err = ioutil.WriteFile(outFile, []byte(yamlFile), 0644) if err != nil { logError(err.Error()) From d1222ef6ab3ce00ff8ab16deb53023b58b722623 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Mon, 6 May 2019 10:23:22 +0200 Subject: [PATCH 0401/1127] releasing v1.9.1 --- README.md | 6 +++--- docs/desired_state_specification.md | 2 +- main.go | 2 +- release-notes.md | 9 ++------- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 0f629ceb..8aafddee 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v1.9.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v1.9.1&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -59,9 +59,9 @@ If you use private helm repos, you will need either `helm-gcs` or `helm-s3` plug Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. ``` # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v1.9.0/helmsman_1.9.0_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.9.1/helmsman_1.9.1_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v1.9.0/helmsman_1.9.0_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.9.1/helmsman_1.9.1_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index e23aa29f..726af7e0 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -1,5 +1,5 @@ --- -version: v1.9.0 +version: v1.9.1 --- # Helmsman desired state specification diff --git a/main.go b/main.go index 7f3310fa..8b5d1799 100644 --- a/main.go +++ b/main.go @@ -33,7 +33,7 @@ var nsOverride string var skipValidation bool var applyLabels bool var keepUntrackedReleases bool -var appVersion = "v1.9.0" +var appVersion = "v1.9.1" var helmVersion string var kubectlVersion string var dryRun bool diff --git a/release-notes.md b/release-notes.md index 0881d695..5bbc62ee 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,13 +1,8 @@ -# v1.9.0 +# v1.9.1 > If you are already using an older version of Helmsman than v1.4.0-rc, please read the changes below carefully and follow the upgrade guide [here](docs/migrating_to_v1.4.0-rc.md) # Fixes and improvements: -- Cleanup helm secrets if any. PR #232 -- Add `autoscaling` apiGroup to thelmsman-created Tiller roles. PR #235 -- Set proper stderr and stdout for log streams. PR #239 +- Improving temporary files handling. PR #242 (thanks to @brndnmtthws) -# New features: -- Add -target flag to limit execution to specific app. PR #237 -- Substitute environment variables in helm values/secrets files. PR #240 From 9e4f2faf0fbb9e7151ac61af245e1ef106a892c9 Mon Sep 17 00:00:00 2001 From: Frederik Mogensen Date: Tue, 14 May 2019 18:53:43 +0200 Subject: [PATCH 0402/1127] Do not delete untracked releases if the target flag is set. Add tests --- decision_maker_test.go | 115 +++++++++++++++++++++++++++++++++++++++++ helm_helpers.go | 10 +++- 2 files changed, 123 insertions(+), 2 deletions(-) diff --git a/decision_maker_test.go b/decision_maker_test.go index 03aa6292..edff8e58 100644 --- a/decision_maker_test.go +++ b/decision_maker_test.go @@ -76,3 +76,118 @@ func Test_getValuesFiles(t *testing.T) { }) } } + +func Test_decide(t *testing.T) { + type args struct { + r *release + s *state + } + tests := []struct { + name string + targetFlag []string + args args + want decisionType + }{ + { + name: "decide() - targetMap does not contain this service - skip", + targetFlag: []string{"someOtherRelease"}, + args: args{ + r: &release{ + Name: "release1", + Namespace: "namespace", + Enabled: true, + }, + s: &state{}, + }, + want: noop, + }, + { + name: "decide() - targetMap does not contain this service - skip", + targetFlag: []string{"someOtherRelease", "norThisOne"}, + args: args{ + r: &release{ + Name: "release1", + Namespace: "namespace", + Enabled: true, + }, + s: &state{}, + }, + want: noop, + }, + { + name: "decide() - targetMap is empty - will install", + targetFlag: []string{}, + args: args{ + r: &release{ + Name: "release4", + Namespace: "namespace", + Enabled: true, + }, + s: &state{}, + }, + want: create, + }, + { + name: "decide() - targetMap is exactly this service - will install", + targetFlag: []string{"thisRelease"}, + args: args{ + r: &release{ + Name: "thisRelease", + Namespace: "namespace", + Enabled: true, + }, + s: &state{}, + }, + want: create, + }, + { + name: "decide() - targetMap contains this service - will install", + targetFlag: []string{"notThisOne", "thisRelease"}, + args: args{ + r: &release{ + Name: "thisRelease", + Namespace: "namespace", + Enabled: true, + }, + s: &state{}, + }, + want: create, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + targetMap = make(map[string]bool) + + for _, target := range tt.targetFlag { + targetMap[target] = true + } + outcome = plan{} + + // Act + decide(tt.args.r, tt.args.s) + got := outcome.Decisions[0].Type + t.Log(outcome.Decisions[0].Description) + + // Assert + if got != tt.want { + t.Errorf("decide() = %s, want %s", got, tt.want) + } + }) + } +} + +// String allows for pretty printing decisionType const +func (dt decisionType) String() string { + switch dt { + case create: + return "create" + case change: + return "change" + case delete: + return "delete" + case noop: + return "noop" + } + return "unknown" +} diff --git a/helm_helpers.go b/helm_helpers.go index d8fa4487..42d65260 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -485,8 +485,14 @@ func cleanUntrackedReleases() { } else { for ns, releases := range toDelete { for r := range releases { - logDecision("DECISION: untracked release found: release [ "+r+" ] from Tiller in namespace [ "+ns+" ]. It will be deleted.", -800, delete) - deleteUntrackedRelease(r, ns) + if len(targetMap) > 0 { + if _, ok := targetMap[r]; !ok { + logDecision("DECISION: untracked release [ "+r+" ] is ignored by target flag. Skipping.", -800, noop) + } else { + logDecision("DECISION: untracked release found: release [ "+r+" ] from Tiller in namespace [ "+ns+" ]. It will be deleted.", -800, delete) + deleteUntrackedRelease(r, ns) + } + } } } } From 7e3ae00dd49ea6aec50ae267d00011d2c976ab63 Mon Sep 17 00:00:00 2001 From: hatemosphere Date: Tue, 28 May 2019 14:57:20 +0300 Subject: [PATCH 0403/1127] Adding global toggle to be able to disable environment substitution --- docs/cmd_reference.md | 9 ++++++--- init.go | 1 + main.go | 1 + utils.go | 30 +++++++++++++++++++++++++----- 4 files changed, 33 insertions(+), 8 deletions(-) diff --git a/docs/cmd_reference.md b/docs/cmd_reference.md index c89ad16d..d1f47a1a 100644 --- a/docs/cmd_reference.md +++ b/docs/cmd_reference.md @@ -62,7 +62,10 @@ This is the list of the available CMD options in Helmsman: show verbose execution logs. `--kubeconfig` - path to the kubeconfig file to use for CLI requests + path to the kubeconfig file to use for CLI requests. - `--target` - limit execution to specific app. + `--target` + limit execution to specific app. + + `--no-env-subst` + turn off environment substitution globally. diff --git a/init.go b/init.go index 768d4ad7..987dd8de 100644 --- a/init.go +++ b/init.go @@ -58,6 +58,7 @@ func init() { flag.BoolVar(&keepUntrackedReleases, "keep-untracked-releases", false, "keep releases that are managed by Helmsman and are no longer tracked in your desired state.") flag.BoolVar(&showDiff, "show-diff", false, "show helm diff results. Can expose sensitive information.") flag.BoolVar(&suppressDiffSecrets, "suppress-diff-secrets", false, "don't show secrets in helm diff output.") + flag.BoolVar(&noEnvSubst, "no-env-subst", false, "turn off environment substitution globally") log.SetOutput(os.Stdout) diff --git a/main.go b/main.go index 8b5d1799..aa3748ad 100644 --- a/main.go +++ b/main.go @@ -42,6 +42,7 @@ var targetMap map[string]bool var destroy bool var showDiff bool var suppressDiffSecrets bool +var noEnvSubst bool const tempFilesDir = ".helmsman-tmp" const stableHelmRepo = "https://kubernetes-charts.storage.googleapis.com" diff --git a/utils.go b/utils.go index be14872f..efe84eac 100644 --- a/utils.go +++ b/utils.go @@ -41,16 +41,23 @@ func printNamespacesMap(m map[string]namespace) { // It uses the BurntSuchi TOML parser which throws an error if the TOML file is not valid. func fromTOML(file string, s *state) (bool, string) { rawTomlFile, err := ioutil.ReadFile(file) + var tomlFile string if err != nil { return false, err.Error() } - tomlFile := substituteEnv(string(rawTomlFile)) + if !noEnvSubst { + tomlFile = substituteEnv(string(rawTomlFile)) + } else { + tomlFile = string(rawTomlFile) + } if _, err := toml.Decode(tomlFile, s); err != nil { return false, err.Error() } addDefaultHelmRepos(s) resolvePaths(file, s) - substituteEnvInValuesFiles(s) + if !noEnvSubst { + substituteEnvInValuesFiles(s) + } return true, "INFO: Parsed TOML [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps." } @@ -85,16 +92,24 @@ func toTOML(file string, s *state) { // parser which throws an error if the YAML file is not valid. func fromYAML(file string, s *state) (bool, string) { rawYamlFile, err := ioutil.ReadFile(file) + var yamlFile []byte if err != nil { return false, err.Error() } - yamlFile := []byte(substituteEnv(string(rawYamlFile))) + if !noEnvSubst { + yamlFile = []byte(substituteEnv(string(rawYamlFile))) + } else { + yamlFile = []byte(string(rawYamlFile)) + } if err = yaml.UnmarshalStrict(yamlFile, s); err != nil { return false, err.Error() } addDefaultHelmRepos(s) resolvePaths(file, s) - substituteEnvInValuesFiles(s) + + if !noEnvSubst { + substituteEnvInValuesFiles(s) + } return true, "INFO: Parsed YAML [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps." } @@ -147,10 +162,15 @@ func substituteEnvInValuesFiles(s *state) { // Returns the path for the temp file func substituteEnvInYaml(file string) string { rawYamlFile, err := ioutil.ReadFile(file) + var yamlFile string if err != nil { logError(err.Error()) } - yamlFile := substituteEnv(string(rawYamlFile)) + if !noEnvSubst { + yamlFile = substituteEnv(string(rawYamlFile)) + } else { + yamlFile = string(rawYamlFile) + } dir, err := ioutil.TempDir(tempFilesDir, "tmp") if err != nil { From cbfd82a49f2b67988942bb7c24cc32e34ae0534e Mon Sep 17 00:00:00 2001 From: Lachlan Cooper Date: Wed, 29 May 2019 11:08:04 +1000 Subject: [PATCH 0404/1127] Add diff-context flag to set lines of diff context --- decision_maker.go | 6 +++++- docs/cmd_reference.md | 3 +++ init.go | 1 + main.go | 1 + 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/decision_maker.go b/decision_maker.go index 232528eb..74b95e23 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -232,17 +232,21 @@ func diffRelease(r *release) string { exitCode := 0 msg := "" colorFlag := "" + diffContextFlag := "" suppressDiffSecretsFlag := "" if noColors { colorFlag = "--no-color " } + if diffContext != -1 { + diffContextFlag = "--context " + strconv.Itoa(diffContext) + } if suppressDiffSecrets { suppressDiffSecretsFlag = "--suppress-secrets " } cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm diff " + colorFlag + suppressDiffSecretsFlag + "upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + strconv.Quote(r.Version) + " " + getSetValues(r) + getSetStringValues(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r)}, + Args: []string{"-c", "helm diff " + colorFlag + diffContextFlag + suppressDiffSecretsFlag + "upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + strconv.Quote(r.Version) + " " + getSetValues(r) + getSetStringValues(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r)}, Description: "diffing release [ " + r.Name + " ] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } diff --git a/docs/cmd_reference.md b/docs/cmd_reference.md index c89ad16d..b8e60228 100644 --- a/docs/cmd_reference.md +++ b/docs/cmd_reference.md @@ -20,6 +20,9 @@ This is the list of the available CMD options in Helmsman: `--destroy` delete all deployed releases. Purge delete is used if the purge option is set to true for the releases. + `--diff-context num` + number of lines of context to show around changes in helm diff output. + `--dry-run` apply the dry-run option for helm commands. diff --git a/init.go b/init.go index 768d4ad7..2eb41588 100644 --- a/init.go +++ b/init.go @@ -58,6 +58,7 @@ func init() { flag.BoolVar(&keepUntrackedReleases, "keep-untracked-releases", false, "keep releases that are managed by Helmsman and are no longer tracked in your desired state.") flag.BoolVar(&showDiff, "show-diff", false, "show helm diff results. Can expose sensitive information.") flag.BoolVar(&suppressDiffSecrets, "suppress-diff-secrets", false, "don't show secrets in helm diff output.") + flag.IntVar(&diffContext, "diff-context", -1, "number of lines of context to show around changes in helm diff output") log.SetOutput(os.Stdout) diff --git a/main.go b/main.go index 8b5d1799..f1aba2e9 100644 --- a/main.go +++ b/main.go @@ -42,6 +42,7 @@ var targetMap map[string]bool var destroy bool var showDiff bool var suppressDiffSecrets bool +var diffContext string const tempFilesDir = ".helmsman-tmp" const stableHelmRepo = "https://kubernetes-charts.storage.googleapis.com" From ac8f542c889b17a5f2baafd4a43b6b25167abe1c Mon Sep 17 00:00:00 2001 From: Lachlan Cooper Date: Wed, 29 May 2019 11:16:46 +1000 Subject: [PATCH 0405/1127] Fix var type --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index f1aba2e9..b7b6e3b0 100644 --- a/main.go +++ b/main.go @@ -42,7 +42,7 @@ var targetMap map[string]bool var destroy bool var showDiff bool var suppressDiffSecrets bool -var diffContext string +var diffContext int const tempFilesDir = ".helmsman-tmp" const stableHelmRepo = "https://kubernetes-charts.storage.googleapis.com" From c3f8b7cc537dc0cd13a9791543fa25d09fc2d310 Mon Sep 17 00:00:00 2001 From: Pavel Strashkin Date: Wed, 29 May 2019 12:54:37 -0700 Subject: [PATCH 0406/1127] add helm flags to "helm diff upgrade ..." command fixes https://github.com/Praqma/helmsman/issues/250 --- decision_maker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/decision_maker.go b/decision_maker.go index 232528eb..84b2ac68 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -242,7 +242,7 @@ func diffRelease(r *release) string { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm diff " + colorFlag + suppressDiffSecretsFlag + "upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + strconv.Quote(r.Version) + " " + getSetValues(r) + getSetStringValues(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r)}, + Args: []string{"-c", "helm diff " + colorFlag + suppressDiffSecretsFlag + "upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + strconv.Quote(r.Version) + " " + getSetValues(r) + getSetStringValues(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getHelmFlags(r)}, Description: "diffing release [ " + r.Name + " ] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } From a2639ae9c4667b3e0acf8f797b008e5fe0b4f603 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Sat, 8 Jun 2019 09:48:24 +0200 Subject: [PATCH 0407/1127] Add helmFlags example usage to desired_state_specification.md --- docs/desired_state_specification.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 726af7e0..542a3091 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -356,6 +356,9 @@ Example: protected = false wait = true priority = -3 + helmFlags: [ + "--recreate-pods", + ] [apps.jenkins.set] secret1="$SECRET_ENV_VAR1" secret2="SECRET_ENV_VAR2" # works with/without $ at the beginning @@ -378,6 +381,9 @@ apps: protected: false wait: true priority: -3 + helmFlags: [ + "--recreate-pods", + ] set: secret1: "$SECRET_ENV_VAR1" secret2: "$SECRET_ENV_VAR2" From f560e373527811b850bdefef2b1492bd1d519117 Mon Sep 17 00:00:00 2001 From: John Rowley Date: Fri, 21 Dec 2018 21:44:50 -0800 Subject: [PATCH 0408/1127] Add POC of Tillerless helm support using helm-tiller plugin --- command.go | 5 +++ decision_maker.go | 83 ++++++++++++++++++++++++++++------------------- example.yaml | 1 + helm_helpers.go | 18 ++++++---- init.go | 5 +++ main.go | 16 +++++---- release.go | 5 ++- state.go | 1 + 8 files changed, 86 insertions(+), 48 deletions(-) diff --git a/command.go b/command.go index cf690249..5b00ede3 100644 --- a/command.go +++ b/command.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "log" + "os" "os/exec" "strings" "syscall" @@ -44,6 +45,10 @@ func (c command) exec(debug bool, verbose bool) (int, string) { cmd.Stdout = &stdout cmd.Stderr = &stderr + // we need to tell the TILLER to be silent. This will only matter in + // tillerless mode. + cmd.Env = append(os.Environ(), "HELM_TILLER_SILENT=true") + if err := cmd.Start(); err != nil { logError("ERROR: cmd.Start: " + err.Error()) } diff --git a/decision_maker.go b/decision_maker.go index 232528eb..00010c99 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -37,10 +37,10 @@ func decide(r *release, s *state) { if destroy { if ok, rs := helmReleaseExists(r, "DEPLOYED"); ok { - deleteRelease(r, rs) + deleteRelease(r, rs, s) } if ok, rs := helmReleaseExists(r, "FAILED"); ok { - deleteRelease(r, rs) + deleteRelease(r, rs, s) } return } @@ -51,7 +51,7 @@ func decide(r *release, s *state) { if !isProtected(r, rs) { // delete it - deleteRelease(r, rs) + deleteRelease(r, rs, s) } else { logDecision("DECISION: release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ @@ -64,7 +64,7 @@ func decide(r *release, s *state) { } else { // check for install/upgrade/rollback if ok, rs := helmReleaseExists(r, "deployed"); ok { if !isProtected(r, rs) { - inspectUpgradeScenario(r, rs) // upgrade or move + inspectUpgradeScenario(r, rs, s) // upgrade or move } else { logDecision("DECISION: release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ @@ -74,7 +74,7 @@ func decide(r *release, s *state) { } else if ok, rs := helmReleaseExists(r, "deleted"); ok { if !isProtected(r, rs) { - rollbackRelease(r, rs) // rollback + rollbackRelease(r, rs, s) // rollback } else { logDecision("DECISION: release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ @@ -86,7 +86,7 @@ func decide(r *release, s *state) { if !isProtected(r, rs) { logDecision("DECISION: release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is in FAILED state. I will upgrade it for you. Hope it gets fixed!", r.Priority, change) - upgradeRelease(r) + upgradeRelease(r, s) } else { logDecision("DECISION: release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ @@ -94,7 +94,7 @@ func decide(r *release, s *state) { } } else { - installRelease(r) // install a new release + installRelease(r, s) // install a new release } @@ -102,12 +102,29 @@ func decide(r *release, s *state) { } -// testRelease creates a Helm command to test a particular release. -func testRelease(r *release) { +// helmCommandFromConfig returns the command used to invoke helm. If configured to +// operate without a tiller it will return `helm tiller run NAMESPACE -- helm` +// where NAMESPACE is the namespace that the release is configured to use. +// If not configured to run without a tiller will just return `helm`. +func helmCommand(namespace string, tillerless bool) string { + if tillerless { + return "helm tiller run " + namespace + " -- helm" + } + + return "helm" +} +// helmCommandFromConfig calls helmCommand returning the correct way to invoke +// helm. +func helmCommandFromConfig(r *release, s *state) string { + return helmCommand(getDesiredTillerNamespace(r), s.Settings.Tillerless) +} + +// testRelease creates a Helm command to test a particular release. +func testRelease(r *release, s *state) { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm test " + r.Name + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r)}, + Args: []string{"-c", helmCommandFromConfig(r, s) + " test " + r.Name + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r)}, Description: "running tests for release [ " + r.Name + " ]", } outcome.addCommand(cmd, r.Priority, r) @@ -116,11 +133,11 @@ func testRelease(r *release) { } // installRelease creates a Helm command to install a particular release in a particular namespace using a particular Tiller. -func installRelease(r *release) { +func installRelease(r *release, s *state) { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm upgrade --install " + r.Name + " " + r.Chart + " --version " + strconv.Quote(r.Version) + " --namespace " + r.Namespace + getValuesFiles(r) + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getHelmFlags(r)}, + Args: []string{"-c", helmCommandFromConfig(r, s) + " upgrade --install " + r.Name + " " + r.Chart + " --version " + strconv.Quote(r.Version) + " --namespace " + r.Namespace + getValuesFiles(r) + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getHelmFlags(r)}, Description: "installing release [ " + r.Name + " ] in namespace [[ " + r.Namespace + " ]] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } outcome.addCommand(cmd, r.Priority, r) @@ -128,30 +145,30 @@ func installRelease(r *release) { r.Namespace+" ]] using Tiller in [ "+getDesiredTillerNamespace(r)+" ]", r.Priority, create) if r.Test { - testRelease(r) + testRelease(r, s) } } // rollbackRelease evaluates if a rollback action needs to be taken for a given release. // if the release is already deleted but from a different namespace than the one specified in input, // it purge deletes it and create it in the specified namespace. -func rollbackRelease(r *release, rs releaseState) { +func rollbackRelease(r *release, rs releaseState, s *state) { if r.Namespace == rs.Namespace { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm rollback " + r.Name + " " + getReleaseRevision(rs) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r) + getDryRunFlags()}, + Args: []string{"-c", helmCommandFromConfig(r, s) + " rollback " + r.Name + " " + getReleaseRevision(rs) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r) + getDryRunFlags()}, Description: "rolling back release [ " + r.Name + " ] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } outcome.addCommand(cmd, r.Priority, r) - upgradeRelease(r) // this is to reflect any changes in values file(s) + upgradeRelease(r, s) // this is to reflect any changes in values file(s) logDecision("DECISION: release [ "+r.Name+" ] is currently deleted and is desired to be rolledback to "+ "namespace [[ "+r.Namespace+" ]] . It will also be upgraded in case values have changed.", r.Priority, change) } else { - reInstallRelease(r, rs) + reInstallRelease(r, rs, s) logDecision("DECISION: release [ "+r.Name+" ] is deleted BUT from namespace [[ "+rs.Namespace+ " ]]. Will purge delete it from there and install it in namespace [[ "+r.Namespace+" ]]", r.Priority, change) logDecision("WARNING: rolling back release [ "+r.Name+" ] from [[ "+rs.Namespace+" ]] to [[ "+r.Namespace+ @@ -162,7 +179,7 @@ func rollbackRelease(r *release, rs releaseState) { } // deleteRelease deletes a release from a particular Tiller in a k8s cluster -func deleteRelease(r *release, rs releaseState) { +func deleteRelease(r *release, rs releaseState, s *state) { p := "" purgeDesc := "" if r.Purge { @@ -177,7 +194,7 @@ func deleteRelease(r *release, rs releaseState) { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm delete " + p + " " + r.Name + getCurrentTillerNamespaceFlag(rs) + getTLSFlags(r) + getDryRunFlags()}, + Args: []string{"-c", helmCommandFromConfig(r, s) + " delete " + p + " " + r.Name + getCurrentTillerNamespaceFlag(rs) + getTLSFlags(r) + getDryRunFlags()}, Description: "deleting release [ " + r.Name + " ] from namespace [[ " + r.Namespace + " ]] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } outcome.addCommand(cmd, priority, r) @@ -191,24 +208,24 @@ func deleteRelease(r *release, rs releaseState) { // it will be purge deleted and installed in the same namespace using the new chart. // - If the release is NOT in the same namespace specified in the input, // it will be purge deleted and installed in the new namespace. -func inspectUpgradeScenario(r *release, rs releaseState) { +func inspectUpgradeScenario(r *release, rs releaseState, s *state) { if r.Namespace == rs.Namespace { if extractChartName(r.Chart) == getReleaseChartName(rs) && r.Version != getReleaseChartVersion(rs) { // upgrade - diffRelease(r) - upgradeRelease(r) + diffRelease(r, s) + upgradeRelease(r, s) logDecision("DECISION: release [ "+r.Name+" ] is desired to be upgraded. Planning this for you!", r.Priority, change) } else if extractChartName(r.Chart) != getReleaseChartName(rs) { - reInstallRelease(r, rs) + reInstallRelease(r, rs, s) logDecision("DECISION: release [ "+r.Name+" ] is desired to use a new Chart [ "+r.Chart+ " ]. I am planning a purge delete of the current release and will install it with the new chart in namespace [[ "+ r.Namespace+" ]]", r.Priority, change) } else { - if diff := diffRelease(r); diff != "" { - upgradeRelease(r) + if diff := diffRelease(r, s); diff != "" { + upgradeRelease(r, s) logDecision("DECISION: release [ "+r.Name+" ] is currently enabled and have some changed parameters. "+ "I will upgrade it!", r.Priority, change) } else { @@ -217,7 +234,7 @@ func inspectUpgradeScenario(r *release, rs releaseState) { } } } else { - reInstallRelease(r, rs) + reInstallRelease(r, rs, s) logDecision("DECISION: release [ "+r.Name+" ] is desired to be enabled in a new namespace [[ "+r.Namespace+ " ]]. I am planning a purge delete of the current release from namespace [[ "+rs.Namespace+" ]] "+ "and will install it for you in namespace [[ "+r.Namespace+" ]]", r.Priority, change) @@ -228,7 +245,7 @@ func inspectUpgradeScenario(r *release, rs releaseState) { } // diffRelease diffs an existing release with the specified values.yaml -func diffRelease(r *release) string { +func diffRelease(r *release, s *state) string { exitCode := 0 msg := "" colorFlag := "" @@ -242,7 +259,7 @@ func diffRelease(r *release) string { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm diff " + colorFlag + suppressDiffSecretsFlag + "upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + strconv.Quote(r.Version) + " " + getSetValues(r) + getSetStringValues(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r)}, + Args: []string{"-c", helmCommandFromConfig(r, s) + " diff " + colorFlag + suppressDiffSecretsFlag + "upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + strconv.Quote(r.Version) + " " + getSetValues(r) + getSetStringValues(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r)}, Description: "diffing release [ " + r.Name + " ] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } @@ -258,10 +275,10 @@ func diffRelease(r *release) string { } // upgradeRelease upgrades an existing release with the specified values.yaml -func upgradeRelease(r *release) { +func upgradeRelease(r *release, s *state) { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm upgrade " + r.Name + " " + r.Chart + " --version " + strconv.Quote(r.Version) + getValuesFiles(r) + " --force " + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getHelmFlags(r)}, + Args: []string{"-c", helmCommandFromConfig(r, s) + " upgrade " + r.Name + " " + r.Chart + " --version " + strconv.Quote(r.Version) + getValuesFiles(r) + " --force " + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getHelmFlags(r)}, Description: "upgrading release [ " + r.Name + " ] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } @@ -270,18 +287,18 @@ func upgradeRelease(r *release) { // reInstallRelease purge deletes a release and reinstalls it. // This is used when moving a release to another namespace or when changing the chart used for it. -func reInstallRelease(r *release, rs releaseState) { +func reInstallRelease(r *release, rs releaseState, s *state) { delCmd := command{ Cmd: "bash", - Args: []string{"-c", "helm delete --purge " + r.Name + getCurrentTillerNamespaceFlag(rs) + getTLSFlags(r) + getDryRunFlags()}, + Args: []string{"-c", helmCommandFromConfig(r, s) + " delete --purge " + r.Name + getCurrentTillerNamespaceFlag(rs) + getTLSFlags(r) + getDryRunFlags()}, Description: "deleting release [ " + r.Name + " ] from namespace [[ " + r.Namespace + " ]] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } outcome.addCommand(delCmd, r.Priority, r) installCmd := command{ Cmd: "bash", - Args: []string{"-c", "helm upgrade --install " + r.Name + " " + r.Chart + " --version " + strconv.Quote(r.Version) + " --namespace " + r.Namespace + getValuesFiles(r) + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getHelmFlags(r)}, + Args: []string{"-c", helmCommandFromConfig(r, s) + " upgrade --install " + r.Name + " " + r.Chart + " --version " + strconv.Quote(r.Version) + " --namespace " + r.Namespace + getValuesFiles(r) + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getHelmFlags(r)}, Description: "installing release [ " + r.Name + " ] in namespace [[ " + r.Namespace + " ]] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } outcome.addCommand(installCmd, r.Priority, r) diff --git a/example.yaml b/example.yaml index 7b192d87..21e3737d 100644 --- a/example.yaml +++ b/example.yaml @@ -26,6 +26,7 @@ settings: #### to use bearer token: # bearerToken: true # clusterURI: "https://kubernetes.default" + #tillerless: true # runs helm in tillerless mode using # define your environments and their k8s namespaces namespaces: diff --git a/helm_helpers.go b/helm_helpers.go index d8fa4487..d9712e13 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -59,14 +59,16 @@ func getHelmClientVersion() string { // getAllReleases fetches a list of all releases in a k8s cluster func getAllReleases() tillerReleases { + // result := make(map[string]interface{}) var result tillerReleases if _, ok := s.Namespaces["kube-system"]; !ok { - result.Releases = append(result.Releases, getTillerReleases("kube-system").Releases...) + result.Releases = append(result.Releases, getTillerReleases("kube-system", s.Settings.Tillerless).Releases...) } + for ns, v := range s.Namespaces { if v.InstallTiller || v.UseTiller { - result.Releases = append(result.Releases, getTillerReleases(ns).Releases...) + result.Releases = append(result.Releases, getTillerReleases(ns, s.Settings.Tillerless).Releases...) } } @@ -74,7 +76,7 @@ func getAllReleases() tillerReleases { } // getTillerReleases gets releases deployed with a given Tiller (in a given namespace) -func getTillerReleases(tillerNS string) tillerReleases { +func getTillerReleases(tillerNS string, tillerless bool) tillerReleases { v1, _ := version.NewVersion(helmVersion) jsonConstraint, _ := version.NewConstraint(">=2.10.0-rc.1") var outputFormat string @@ -84,7 +86,7 @@ func getTillerReleases(tillerNS string) tillerReleases { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm list --all --max 0 " + outputFormat + " --tiller-namespace " + tillerNS + getNSTLSFlags(tillerNS)}, + Args: []string{"-c", helmCommand(tillerNS, tillerless) + " list --all --max 0 " + outputFormat + " --tiller-namespace " + tillerNS + getNSTLSFlags(tillerNS)}, Description: "listing all existing releases in namespace [ " + tillerNS + " ]...", } @@ -100,6 +102,7 @@ func getTillerReleases(tillerNS string) tillerReleases { logError("ERROR: failed to list all releases in namespace [ " + tillerNS + " ]: " + result) } + var out tillerReleases if jsonConstraint.Check(v1) { json.Unmarshal([]byte(result), &out) @@ -131,6 +134,7 @@ func getTillerReleases(tillerNS string) tillerReleases { // buildState builds the currentState map containing information about all releases existing in a k8s cluster func buildState() { log.Println("INFO: mapping the current helm state ...") + currentState = make(map[string]releaseState) rel := getAllReleases() @@ -486,14 +490,14 @@ func cleanUntrackedReleases() { for ns, releases := range toDelete { for r := range releases { logDecision("DECISION: untracked release found: release [ "+r+" ] from Tiller in namespace [ "+ns+" ]. It will be deleted.", -800, delete) - deleteUntrackedRelease(r, ns) + deleteUntrackedRelease(r, ns, s.Settings.Tillerless) } } } } // deleteUntrackedRelease creates the helm command to purge delete an untracked release -func deleteUntrackedRelease(release string, tillerNamespace string) { +func deleteUntrackedRelease(release string, tillerNamespace string, tillerless bool) { tls := "" ns := s.Namespaces[tillerNamespace] @@ -503,7 +507,7 @@ func deleteUntrackedRelease(release string, tillerNamespace string) { } cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm delete --purge " + release + " --tiller-namespace " + tillerNamespace + tls + getDryRunFlags()}, + Args: []string{"-c", helmCommand(tillerNamespace, tillerless) + " delete --purge " + release + " --tiller-namespace " + tillerNamespace + tls + getDryRunFlags()}, Description: "deleting untracked release [ " + release + " ] from Tiller in namespace [[ " + tillerNamespace + " ]]", } diff --git a/init.go b/init.go index 768d4ad7..6d2f02b8 100644 --- a/init.go +++ b/init.go @@ -172,6 +172,11 @@ func init() { s.print() } + // validate that if we are running in tillerless mode, and we don't have the tiller plugin installed we should fail. + if !helmPluginExists("tiller") && s.Settings.Tillerless { + logError("ERROR: tillerless operation is specified and helm tiller plugin is not installed/configured correctly. Aborting!") + } + if !skipValidation { // validate the desired state content if len(files) > 0 { diff --git a/main.go b/main.go index 8b5d1799..dcb00ab6 100644 --- a/main.go +++ b/main.go @@ -69,15 +69,17 @@ func main() { logError(msg) } - // check if helm Tiller is ready - for k, ns := range s.Namespaces { - if ns.InstallTiller || ns.UseTiller { - waitForTiller(k) + // check if helm Tiller is ready if we aren't running in tillerless mode. + if !s.Settings.Tillerless { + for k, ns := range s.Namespaces { + if ns.InstallTiller || ns.UseTiller { + waitForTiller(k) + } } - } - if _, ok := s.Namespaces["kube-system"]; !ok { - waitForTiller("kube-system") + if _, ok := s.Namespaces["kube-system"]; !ok { + waitForTiller("kube-system") + } } } else { initHelmClientOnly() diff --git a/release.go b/release.go index 6f5630ca..83805afe 100644 --- a/release.go +++ b/release.go @@ -40,7 +40,10 @@ func validateRelease(appLabel string, r *release, names map[string]map[string]bo if r.Name == "" { r.Name = appLabel } - if r.TillerNamespace != "" { + + if s.Settings.Tillerless { + // if we are running in a tillerless environment then lets skip the tiller validation + } else if r.TillerNamespace != "" { if ns, ok := s.Namespaces[r.TillerNamespace]; !ok { return false, "tillerNamespace specified, but the namespace specified does not exist!" } else if !ns.InstallTiller && !ns.UseTiller { diff --git a/state.go b/state.go index ae11bd16..8b7284de 100644 --- a/state.go +++ b/state.go @@ -20,6 +20,7 @@ type config struct { ReverseDelete bool `yaml:"reverseDelete"` BearerToken bool `yaml:"bearerToken"` BearerTokenPath string `yaml:"bearerTokenPath"` + Tillerless bool `yaml:"tillerless"` } // state type represents the desired state of applications on a k8s cluster. From c18ad8c7180520d6c65351d71e6fef048e2b46bc Mon Sep 17 00:00:00 2001 From: John Rowley Date: Sun, 23 Dec 2018 15:47:45 -0800 Subject: [PATCH 0409/1127] Simplify code by using the global settings var --- decision_maker.go | 68 +++++++++++++++++++++++------------------------ helm_helpers.go | 14 +++++----- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index 00010c99..0b7535c2 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -51,7 +51,7 @@ func decide(r *release, s *state) { if !isProtected(r, rs) { // delete it - deleteRelease(r, rs, s) + deleteRelease(r, rs) } else { logDecision("DECISION: release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ @@ -64,7 +64,7 @@ func decide(r *release, s *state) { } else { // check for install/upgrade/rollback if ok, rs := helmReleaseExists(r, "deployed"); ok { if !isProtected(r, rs) { - inspectUpgradeScenario(r, rs, s) // upgrade or move + inspectUpgradeScenario(r, rs) // upgrade or move } else { logDecision("DECISION: release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ @@ -74,7 +74,7 @@ func decide(r *release, s *state) { } else if ok, rs := helmReleaseExists(r, "deleted"); ok { if !isProtected(r, rs) { - rollbackRelease(r, rs, s) // rollback + rollbackRelease(r, rs) // rollback } else { logDecision("DECISION: release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ @@ -86,7 +86,7 @@ func decide(r *release, s *state) { if !isProtected(r, rs) { logDecision("DECISION: release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is in FAILED state. I will upgrade it for you. Hope it gets fixed!", r.Priority, change) - upgradeRelease(r, s) + upgradeRelease(r) } else { logDecision("DECISION: release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ @@ -94,7 +94,7 @@ func decide(r *release, s *state) { } } else { - installRelease(r, s) // install a new release + installRelease(r) // install a new release } @@ -106,8 +106,8 @@ func decide(r *release, s *state) { // operate without a tiller it will return `helm tiller run NAMESPACE -- helm` // where NAMESPACE is the namespace that the release is configured to use. // If not configured to run without a tiller will just return `helm`. -func helmCommand(namespace string, tillerless bool) string { - if tillerless { +func helmCommand(namespace string) string { + if settings.Tillerless { return "helm tiller run " + namespace + " -- helm" } @@ -116,15 +116,15 @@ func helmCommand(namespace string, tillerless bool) string { // helmCommandFromConfig calls helmCommand returning the correct way to invoke // helm. -func helmCommandFromConfig(r *release, s *state) string { - return helmCommand(getDesiredTillerNamespace(r), s.Settings.Tillerless) +func helmCommandFromConfig(r *release) string { + return helmCommand(getDesiredTillerNamespace(r)) } // testRelease creates a Helm command to test a particular release. -func testRelease(r *release, s *state) { +func testRelease(r *release) { cmd := command{ Cmd: "bash", - Args: []string{"-c", helmCommandFromConfig(r, s) + " test " + r.Name + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r)}, + Args: []string{"-c", helmCommandFromConfig(r) + " test " + r.Name + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r)}, Description: "running tests for release [ " + r.Name + " ]", } outcome.addCommand(cmd, r.Priority, r) @@ -133,11 +133,11 @@ func testRelease(r *release, s *state) { } // installRelease creates a Helm command to install a particular release in a particular namespace using a particular Tiller. -func installRelease(r *release, s *state) { +func installRelease(r *release) { cmd := command{ Cmd: "bash", - Args: []string{"-c", helmCommandFromConfig(r, s) + " upgrade --install " + r.Name + " " + r.Chart + " --version " + strconv.Quote(r.Version) + " --namespace " + r.Namespace + getValuesFiles(r) + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getHelmFlags(r)}, + Args: []string{"-c", helmCommandFromConfig(r) + " install " + r.Chart + " -n " + r.Name + " --namespace " + r.Namespace + getValuesFiles(r) + " --version " + r.Version + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r) + getDryRunFlags()}, Description: "installing release [ " + r.Name + " ] in namespace [[ " + r.Namespace + " ]] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } outcome.addCommand(cmd, r.Priority, r) @@ -145,30 +145,30 @@ func installRelease(r *release, s *state) { r.Namespace+" ]] using Tiller in [ "+getDesiredTillerNamespace(r)+" ]", r.Priority, create) if r.Test { - testRelease(r, s) + testRelease(r) } } // rollbackRelease evaluates if a rollback action needs to be taken for a given release. // if the release is already deleted but from a different namespace than the one specified in input, // it purge deletes it and create it in the specified namespace. -func rollbackRelease(r *release, rs releaseState, s *state) { +func rollbackRelease(r *release, rs releaseState) { if r.Namespace == rs.Namespace { cmd := command{ Cmd: "bash", - Args: []string{"-c", helmCommandFromConfig(r, s) + " rollback " + r.Name + " " + getReleaseRevision(rs) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r) + getDryRunFlags()}, + Args: []string{"-c", helmCommandFromConfig(r) + " rollback " + r.Name + " " + getReleaseRevision(rs) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r) + getDryRunFlags()}, Description: "rolling back release [ " + r.Name + " ] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } outcome.addCommand(cmd, r.Priority, r) - upgradeRelease(r, s) // this is to reflect any changes in values file(s) + upgradeRelease(r) // this is to reflect any changes in values file(s) logDecision("DECISION: release [ "+r.Name+" ] is currently deleted and is desired to be rolledback to "+ "namespace [[ "+r.Namespace+" ]] . It will also be upgraded in case values have changed.", r.Priority, change) } else { - reInstallRelease(r, rs, s) + reInstallRelease(r, rs) logDecision("DECISION: release [ "+r.Name+" ] is deleted BUT from namespace [[ "+rs.Namespace+ " ]]. Will purge delete it from there and install it in namespace [[ "+r.Namespace+" ]]", r.Priority, change) logDecision("WARNING: rolling back release [ "+r.Name+" ] from [[ "+rs.Namespace+" ]] to [[ "+r.Namespace+ @@ -179,7 +179,7 @@ func rollbackRelease(r *release, rs releaseState, s *state) { } // deleteRelease deletes a release from a particular Tiller in a k8s cluster -func deleteRelease(r *release, rs releaseState, s *state) { +func deleteRelease(r *release, rs releaseState) { p := "" purgeDesc := "" if r.Purge { @@ -194,7 +194,7 @@ func deleteRelease(r *release, rs releaseState, s *state) { cmd := command{ Cmd: "bash", - Args: []string{"-c", helmCommandFromConfig(r, s) + " delete " + p + " " + r.Name + getCurrentTillerNamespaceFlag(rs) + getTLSFlags(r) + getDryRunFlags()}, + Args: []string{"-c", helmCommandFromConfig(r) + " delete " + p + " " + r.Name + getCurrentTillerNamespaceFlag(rs) + getTLSFlags(r) + getDryRunFlags()}, Description: "deleting release [ " + r.Name + " ] from namespace [[ " + r.Namespace + " ]] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } outcome.addCommand(cmd, priority, r) @@ -208,24 +208,24 @@ func deleteRelease(r *release, rs releaseState, s *state) { // it will be purge deleted and installed in the same namespace using the new chart. // - If the release is NOT in the same namespace specified in the input, // it will be purge deleted and installed in the new namespace. -func inspectUpgradeScenario(r *release, rs releaseState, s *state) { +func inspectUpgradeScenario(r *release, rs releaseState) { if r.Namespace == rs.Namespace { if extractChartName(r.Chart) == getReleaseChartName(rs) && r.Version != getReleaseChartVersion(rs) { // upgrade - diffRelease(r, s) - upgradeRelease(r, s) + diffRelease(r) + upgradeRelease(r) logDecision("DECISION: release [ "+r.Name+" ] is desired to be upgraded. Planning this for you!", r.Priority, change) } else if extractChartName(r.Chart) != getReleaseChartName(rs) { - reInstallRelease(r, rs, s) + reInstallRelease(r, rs) logDecision("DECISION: release [ "+r.Name+" ] is desired to use a new Chart [ "+r.Chart+ " ]. I am planning a purge delete of the current release and will install it with the new chart in namespace [[ "+ r.Namespace+" ]]", r.Priority, change) } else { - if diff := diffRelease(r, s); diff != "" { - upgradeRelease(r, s) + if diff := diffRelease(r); diff != "" { + upgradeRelease(r) logDecision("DECISION: release [ "+r.Name+" ] is currently enabled and have some changed parameters. "+ "I will upgrade it!", r.Priority, change) } else { @@ -234,7 +234,7 @@ func inspectUpgradeScenario(r *release, rs releaseState, s *state) { } } } else { - reInstallRelease(r, rs, s) + reInstallRelease(r, rs) logDecision("DECISION: release [ "+r.Name+" ] is desired to be enabled in a new namespace [[ "+r.Namespace+ " ]]. I am planning a purge delete of the current release from namespace [[ "+rs.Namespace+" ]] "+ "and will install it for you in namespace [[ "+r.Namespace+" ]]", r.Priority, change) @@ -245,7 +245,7 @@ func inspectUpgradeScenario(r *release, rs releaseState, s *state) { } // diffRelease diffs an existing release with the specified values.yaml -func diffRelease(r *release, s *state) string { +func diffRelease(r *release) string { exitCode := 0 msg := "" colorFlag := "" @@ -259,7 +259,7 @@ func diffRelease(r *release, s *state) string { cmd := command{ Cmd: "bash", - Args: []string{"-c", helmCommandFromConfig(r, s) + " diff " + colorFlag + suppressDiffSecretsFlag + "upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + strconv.Quote(r.Version) + " " + getSetValues(r) + getSetStringValues(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r)}, + Args: []string{"-c", helmCommandFromConfig(r) + " diff " + colorFlag + suppressDiffSecretsFlag + "upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + strconv.Quote(r.Version) + " " + getSetValues(r) + getSetStringValues(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r)}, Description: "diffing release [ " + r.Name + " ] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } @@ -275,10 +275,10 @@ func diffRelease(r *release, s *state) string { } // upgradeRelease upgrades an existing release with the specified values.yaml -func upgradeRelease(r *release, s *state) { +func upgradeRelease(r *release) { cmd := command{ Cmd: "bash", - Args: []string{"-c", helmCommandFromConfig(r, s) + " upgrade " + r.Name + " " + r.Chart + " --version " + strconv.Quote(r.Version) + getValuesFiles(r) + " --force " + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getHelmFlags(r)}, + Args: []string{"-c", helmCommandFromConfig(r) + " upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + strconv.Quote(r.Version) + " --force " + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r) + getDryRunFlags()}, Description: "upgrading release [ " + r.Name + " ] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } @@ -287,18 +287,18 @@ func upgradeRelease(r *release, s *state) { // reInstallRelease purge deletes a release and reinstalls it. // This is used when moving a release to another namespace or when changing the chart used for it. -func reInstallRelease(r *release, rs releaseState, s *state) { +func reInstallRelease(r *release, rs releaseState) { delCmd := command{ Cmd: "bash", - Args: []string{"-c", helmCommandFromConfig(r, s) + " delete --purge " + r.Name + getCurrentTillerNamespaceFlag(rs) + getTLSFlags(r) + getDryRunFlags()}, + Args: []string{"-c", helmCommandFromConfig(r) + " delete --purge " + r.Name + getCurrentTillerNamespaceFlag(rs) + getTLSFlags(r) + getDryRunFlags()}, Description: "deleting release [ " + r.Name + " ] from namespace [[ " + r.Namespace + " ]] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } outcome.addCommand(delCmd, r.Priority, r) installCmd := command{ Cmd: "bash", - Args: []string{"-c", helmCommandFromConfig(r, s) + " upgrade --install " + r.Name + " " + r.Chart + " --version " + strconv.Quote(r.Version) + " --namespace " + r.Namespace + getValuesFiles(r) + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getHelmFlags(r)}, + Args: []string{"-c", helmCommandFromConfig(r) + " install " + r.Chart + " --version " + r.Version + " -n " + r.Name + " --namespace " + r.Namespace + getValuesFiles(r) + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r) + getDryRunFlags()}, Description: "installing release [ " + r.Name + " ] in namespace [[ " + r.Namespace + " ]] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } outcome.addCommand(installCmd, r.Priority, r) diff --git a/helm_helpers.go b/helm_helpers.go index d9712e13..1f2f4484 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -63,12 +63,12 @@ func getAllReleases() tillerReleases { // result := make(map[string]interface{}) var result tillerReleases if _, ok := s.Namespaces["kube-system"]; !ok { - result.Releases = append(result.Releases, getTillerReleases("kube-system", s.Settings.Tillerless).Releases...) + result.Releases = append(result.Releases, getTillerReleases("kube-system").Releases...) } for ns, v := range s.Namespaces { if v.InstallTiller || v.UseTiller { - result.Releases = append(result.Releases, getTillerReleases(ns, s.Settings.Tillerless).Releases...) + result.Releases = append(result.Releases, getTillerReleases(ns).Releases...) } } @@ -76,7 +76,7 @@ func getAllReleases() tillerReleases { } // getTillerReleases gets releases deployed with a given Tiller (in a given namespace) -func getTillerReleases(tillerNS string, tillerless bool) tillerReleases { +func getTillerReleases(tillerNS string) tillerReleases { v1, _ := version.NewVersion(helmVersion) jsonConstraint, _ := version.NewConstraint(">=2.10.0-rc.1") var outputFormat string @@ -86,7 +86,7 @@ func getTillerReleases(tillerNS string, tillerless bool) tillerReleases { cmd := command{ Cmd: "bash", - Args: []string{"-c", helmCommand(tillerNS, tillerless) + " list --all --max 0 " + outputFormat + " --tiller-namespace " + tillerNS + getNSTLSFlags(tillerNS)}, + Args: []string{"-c", helmCommand(tillerNS) + " list --all --max 0 " + outputFormat + " --tiller-namespace " + tillerNS + getNSTLSFlags(tillerNS)}, Description: "listing all existing releases in namespace [ " + tillerNS + " ]...", } @@ -490,14 +490,14 @@ func cleanUntrackedReleases() { for ns, releases := range toDelete { for r := range releases { logDecision("DECISION: untracked release found: release [ "+r+" ] from Tiller in namespace [ "+ns+" ]. It will be deleted.", -800, delete) - deleteUntrackedRelease(r, ns, s.Settings.Tillerless) + deleteUntrackedRelease(r, ns) } } } } // deleteUntrackedRelease creates the helm command to purge delete an untracked release -func deleteUntrackedRelease(release string, tillerNamespace string, tillerless bool) { +func deleteUntrackedRelease(release string, tillerNamespace string) { tls := "" ns := s.Namespaces[tillerNamespace] @@ -507,7 +507,7 @@ func deleteUntrackedRelease(release string, tillerNamespace string, tillerless b } cmd := command{ Cmd: "bash", - Args: []string{"-c", helmCommand(tillerNamespace, tillerless) + " delete --purge " + release + " --tiller-namespace " + tillerNamespace + tls + getDryRunFlags()}, + Args: []string{"-c", helmCommand(tillerNamespace) + " delete --purge " + release + " --tiller-namespace " + tillerNamespace + tls + getDryRunFlags()}, Description: "deleting untracked release [ " + release + " ] from Tiller in namespace [[ " + tillerNamespace + " ]]", } From 6a1cdcf8b630d8f2cb05ad6c349ad2ccf7d59056 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Sat, 8 Jun 2019 15:46:12 +0200 Subject: [PATCH 0410/1127] Improve tilerless approach to match it to whole flow --- decision_maker.go | 6 ++--- helm_helpers.go | 47 +++++++++++++++++---------------- main.go | 2 ++ state.go | 66 +++++++++++++++++++++++++---------------------- 4 files changed, 65 insertions(+), 56 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index 0b7535c2..b3f384d0 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -37,10 +37,10 @@ func decide(r *release, s *state) { if destroy { if ok, rs := helmReleaseExists(r, "DEPLOYED"); ok { - deleteRelease(r, rs, s) + deleteRelease(r, rs) } if ok, rs := helmReleaseExists(r, "FAILED"); ok { - deleteRelease(r, rs, s) + deleteRelease(r, rs) } return } @@ -456,7 +456,7 @@ func getDesiredTillerNamespaceFlag(r *release) string { func getDesiredTillerNamespace(r *release) string { if r.TillerNamespace != "" { return r.TillerNamespace - } else if ns, ok := s.Namespaces[r.Namespace]; ok && (ns.InstallTiller || ns.UseTiller) { + } else if ns, ok := s.Namespaces[r.Namespace]; ok && (s.Settings.Tillerless || (ns.InstallTiller || ns.UseTiller)) { return r.Namespace } diff --git a/helm_helpers.go b/helm_helpers.go index 1f2f4484..c6c11570 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -62,12 +62,12 @@ func getAllReleases() tillerReleases { // result := make(map[string]interface{}) var result tillerReleases - if _, ok := s.Namespaces["kube-system"]; !ok { + if _, ok := s.Namespaces["kube-system"]; !ok && !s.Settings.Tillerless { result.Releases = append(result.Releases, getTillerReleases("kube-system").Releases...) } for ns, v := range s.Namespaces { - if v.InstallTiller || v.UseTiller { + if (v.InstallTiller || v.UseTiller) || s.Settings.Tillerless { result.Releases = append(result.Releases, getTillerReleases(ns).Releases...) } } @@ -427,33 +427,36 @@ func initHelmClientOnly() (bool, string) { func initHelm() (bool, string) { initHelmClientOnly() defaultSA := s.Settings.ServiceAccount - - for k, ns := range s.Namespaces { - if tillerTLSEnabled(ns) { - downloadFile(s.Namespaces[k].TillerCert, k+"-tiller.cert") - downloadFile(s.Namespaces[k].TillerKey, k+"-tiller.key") - downloadFile(s.Namespaces[k].CaCert, k+"-ca.cert") - // client cert and key - downloadFile(s.Namespaces[k].ClientCert, k+"-client.cert") - downloadFile(s.Namespaces[k].ClientKey, k+"-client.key") - } - if ns.InstallTiller && k != "kube-system" { - if ok, err := deployTiller(k, ns.TillerServiceAccount, defaultSA, ns.TillerRole); !ok { - return false, err + if !s.Settings.Tillerless { + for k, ns := range s.Namespaces { + if tillerTLSEnabled(ns) { + downloadFile(s.Namespaces[k].TillerCert, k+"-tiller.cert") + downloadFile(s.Namespaces[k].TillerKey, k+"-tiller.key") + downloadFile(s.Namespaces[k].CaCert, k+"-ca.cert") + // client cert and key + downloadFile(s.Namespaces[k].ClientCert, k+"-client.cert") + downloadFile(s.Namespaces[k].ClientKey, k+"-client.key") + } + if ns.InstallTiller && k != "kube-system" { + if ok, err := deployTiller(k, ns.TillerServiceAccount, defaultSA, ns.TillerRole); !ok { + return false, err + } } } - } - if ns, ok := s.Namespaces["kube-system"]; ok { - if ns.InstallTiller { - if ok, err := deployTiller("kube-system", ns.TillerServiceAccount, defaultSA, ns.TillerRole); !ok { + if ns, ok := s.Namespaces["kube-system"]; ok { + if ns.InstallTiller { + if ok, err := deployTiller("kube-system", ns.TillerServiceAccount, defaultSA, ns.TillerRole); !ok { + return false, err + } + } + } else { + if ok, err := deployTiller("kube-system", "", defaultSA, ns.TillerRole); !ok { return false, err } } } else { - if ok, err := deployTiller("kube-system", "", defaultSA, ns.TillerRole); !ok { - return false, err - } + log.Println("INFO: skipping Tiller deployments because Tillerless mode is enabled.") } return true, "" diff --git a/main.go b/main.go index dcb00ab6..951dfd3c 100644 --- a/main.go +++ b/main.go @@ -80,6 +80,8 @@ func main() { if _, ok := s.Namespaces["kube-system"]; !ok { waitForTiller("kube-system") } + } else { + log.Println("INFO: running in TILLERLESS mode") } } else { initHelmClientOnly() diff --git a/state.go b/state.go index 8b7284de..ae8271ff 100644 --- a/state.go +++ b/state.go @@ -111,42 +111,46 @@ func (s state) validate() (bool, string) { return false, "ERROR: namespaces validation failed -- I need at least one namespace " + "to work with!" } + if !s.Settings.Tillerless { + for k, ns := range s.Namespaces { + if ns.InstallTiller && ns.UseTiller { + return false, "ERROR: namespaces validation failed -- installTiller and useTiller can't be used together for namespace [ " + k + " ]" + } + if ns.UseTiller { + log.Println("INFO: namespace validation -- a pre-installed Tiller is desired to be used in namespace [ " + k + " ].") + } else if !ns.InstallTiller { + log.Println("INFO: namespace validation -- Tiller is NOT desired to be deployed in namespace [ " + k + " ].") + } - for k, ns := range s.Namespaces { - if ns.InstallTiller && ns.UseTiller { - return false, "ERROR: namespaces validation failed -- installTiller and useTiller can't be used together for namespace [ " + k + " ]" - } - if ns.UseTiller { - log.Println("INFO: namespace validation -- a pre-installed Tiller is desired to be used in namespace [ " + k + " ].") - } else if !ns.InstallTiller { - log.Println("INFO: namespace validation -- Tiller is NOT desired to be deployed in namespace [ " + k + " ].") - } - - if ns.UseTiller || ns.InstallTiller { - // validating the TLS certs and keys for Tiller - // if they are valid, their values (if they are env vars) are substituted - var ok1, ok2, ok3, ok4, ok5 bool - ok1, ns.CaCert = isValidCert(ns.CaCert) - ok2, ns.ClientCert = isValidCert(ns.ClientCert) - ok3, ns.ClientKey = isValidCert(ns.ClientKey) - ok4, ns.TillerCert = isValidCert(ns.TillerCert) - ok5, ns.TillerKey = isValidCert(ns.TillerKey) - - if ns.InstallTiller { - if !ok1 || !ok2 || !ok3 || !ok4 || !ok5 { - log.Println("INFO: namespace validation -- Either no or invalid certs/keys provided for DEPLOYING Tiller with TLS in namespace [ " + k + " ].") - } else { - log.Println("INFO: namespace validation -- Tiller is desired to be DEPLOYED with TLS in namespace [ " + k + " ]. ") - } - } else if ns.UseTiller { - if !ok1 || !ok2 || !ok3 { - log.Println("INFO: namespace validation -- Either no or invalid certs/keys provided for USING Tiller with TLS in namespace [ " + k + " ].") - } else { - log.Println("INFO: namespace validation -- Tiller is desired to be USED with TLS in namespace [ " + k + " ]. ") + if ns.UseTiller || ns.InstallTiller { + // validating the TLS certs and keys for Tiller + // if they are valid, their values (if they are env vars) are substituted + var ok1, ok2, ok3, ok4, ok5 bool + ok1, ns.CaCert = isValidCert(ns.CaCert) + ok2, ns.ClientCert = isValidCert(ns.ClientCert) + ok3, ns.ClientKey = isValidCert(ns.ClientKey) + ok4, ns.TillerCert = isValidCert(ns.TillerCert) + ok5, ns.TillerKey = isValidCert(ns.TillerKey) + + if ns.InstallTiller { + if !ok1 || !ok2 || !ok3 || !ok4 || !ok5 { + log.Println("INFO: namespace validation -- Either no or invalid certs/keys provided for DEPLOYING Tiller with TLS in namespace [ " + k + " ].") + } else { + log.Println("INFO: namespace validation -- Tiller is desired to be DEPLOYED with TLS in namespace [ " + k + " ]. ") + } + } else if ns.UseTiller { + if !ok1 || !ok2 || !ok3 { + log.Println("INFO: namespace validation -- Either no or invalid certs/keys provided for USING Tiller with TLS in namespace [ " + k + " ].") + } else { + log.Println("INFO: namespace validation -- Tiller is desired to be USED with TLS in namespace [ " + k + " ]. ") + } } } } + } else { + log.Println("INFO: namespace validation -- skipping because Tillerless mode is enabled.") } + } else { log.Println("INFO: ns-override is used to override all namespaces with [ " + nsOverride + " ] Skipping defined namespaces validation.") } From 7503b81a6b9c1168d87ace0fc991176f72f442e1 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Sat, 8 Jun 2019 16:07:17 +0200 Subject: [PATCH 0411/1127] Add tilerless settings to docs --- docs/desired_state_specification.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 726af7e0..2b7bb561 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -97,6 +97,7 @@ The following options can be skipped if your kubectl context is already created - **storageBackend** : by default Helm stores release information in configMaps, using secrets is for storage is recommended for security. Setting this flag to `secret` will deploy/upgrade Tiller with the `--storage=secret`. Other values will be skipped and configMaps will be used. - **slackWebhook** : a [Slack](slack.com) Webhook URL to receive Helmsman notifications. This can be passed directly or in an environment variable. - **reverseDelete** : if set to `true` it will reverse the priority order whilst deleting. +- **tillerless** : setting it to `true` will use [helm-tiller](https://rimusz.net/tillerless-helm) plugin instead of installing Tillers in namespaces. It disables many of the parameters for sections below. > If you use `storageBackend` with a Tiller that has been previously deployed with configMaps as storage backend, you need to migrate your release information from the configMap to the new secret on your own. Helm does not support this yet. @@ -113,6 +114,7 @@ kubeContext = "minikube" # storageBackend = "secret" # slackWebhook = $MY_SLACK_WEBHOOK # reverseDelete = false +# tilerless = true ``` ```yaml @@ -126,6 +128,7 @@ settings: #storageBackend: "secret" #slackWebhook: "$MY_SLACK_WEBHOOK" #reverseDelete: false + #tilerless: true ``` ## Namespaces @@ -135,6 +138,8 @@ Optional : No. Synopsis: defines the namespaces to be used/created in your k8s cluster and whether they are protected or not. It also defines if Tiller should be deployed in these namespaces and with what configurations (TLS and service account). You can add as many namespaces as you like. If a namespace does not already exist, Helmsman will create it. +> All Tillers-related params here will be ignored when `tilerless` is set in `settings` section. + Options: - **protected** : defines if a namespace is protected (true or false). Default false. > For the definition of what a protected namespace means, check the [protection guide](how_to/protect_namespaces_and_releases.md) @@ -315,6 +320,7 @@ Options: 1. If `tillerNamespace`is explicitly defined, it is used. 2. If `tillerNamespace`is not defined and the namespace in which the release will be deployed has a Tiller installed by Helmsman (i.e. has `installTiller set to true` in the [Namespaces](#namespaces) section), Tiller in that namespace is used. 3. If none of the above, the shared Tiller in `kube-system` is used. +> This parameter is ignored when `tilerless` is set in `settings` section and `namespace` of this app is taken for tilerless plugin. - **name** : the Helm release name. Releases must have unique names within a Helm Tiller. If not set, the release name will be taken from the app identifier in your desired state file. e.g, for ` apps.jenkins ` the release name will be `jenkins`. - **description** : a release metadata for human readers. From c02cdba4bd37afed7aba0d9631e2ea533320859b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Socho=C5=84?= Date: Sun, 9 Jun 2019 14:48:10 +0200 Subject: [PATCH 0412/1127] Fix doc links Fix broken links in the documnetation. Some documents were moved without updating references in other documents. --- docs/deployment_strategies.md | 4 ++-- docs/desired_state_specification.md | 6 +++--- docs/how_to/README.md | 4 ++-- docs/how_to/apps/basic.md | 2 +- docs/how_to/apps/moving_across_namespaces.md | 4 ++-- docs/how_to/apps/protection.md | 2 +- docs/how_to/deployments/inside_k8s.md | 4 ++-- docs/how_to/misc/multitenant_clusters_guide.md | 2 +- docs/how_to/misc/protect_namespaces_and_releases.md | 2 +- docs/how_to/namespaces/protection.md | 4 ++-- docs/how_to/settings/creating_kube_context_with_certs.md | 2 +- docs/how_to/tiller/multitenancy.md | 6 +++--- docs/how_to/tiller/shared.md | 4 ++-- 13 files changed, 23 insertions(+), 23 deletions(-) diff --git a/docs/deployment_strategies.md b/docs/deployment_strategies.md index 8cfd8805..65e20cf4 100644 --- a/docs/deployment_strategies.md +++ b/docs/deployment_strategies.md @@ -123,7 +123,7 @@ If you use multiple clusters for multiple purposes, you need at least one Helmsm If you are developing your own applications/services and packaging them in helm charts. It makes sense to automatically deploy these charts to a staging namespace or a dev cluster on every source code commit. -Often, you would have multiple apps developed in separate source code repositories but you would like to test their deployment in the same cluster/namespace. In that case, Helmsman can be used [as part of your CI pipeline](how_to/run_helmsman_in_ci.md) as described in the diagram below: +Often, you would have multiple apps developed in separate source code repositories but you would like to test their deployment in the same cluster/namespace. In that case, Helmsman can be used [as part of your CI pipeline](how_to/deployments/ci.md) as described in the diagram below: > as of v1.1.0 , you can use the `ns-override`flag to force helmsman to deploy/move all apps into a given namespace. For example, you could use this flag in a CI job that gets triggered on commits to the dev branch to deploy all apps into the `staging` namespace. @@ -135,7 +135,7 @@ If you need supporting applications (charts) for your application (e.g, reverse ## Notes on using multiple Helmsman desired state files with the same cluster -Helmsman works with a single desired state file at a time (starting from v1.5.0, you can pass multiple desired state files which get merged at runtime. See the [docs](how_to/merge_desired_state_files.md)) and does not maintain a state anywhere. i.e. it does not have any context awareness about other desired state files used with the same cluster. For this reason, it is the user's responsibility to make sure that: +Helmsman works with a single desired state file at a time (starting from v1.5.0, you can pass multiple desired state files which get merged at runtime. See the [docs](how_to/misc/merge_desired_state_files.md)) and does not maintain a state anywhere. i.e. it does not have any context awareness about other desired state files used with the same cluster. For this reason, it is the user's responsibility to make sure that: - no releases have the same name in different desired state files pointing to the same cluster. If such conflict exists, Helmsman will not raise any errors but that release would be subject to unexpected behavior. diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 726af7e0..e1853801 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -95,7 +95,7 @@ The following options can be skipped if your kubectl context is already created - **bearerTokenPath**: optional. If bearer token is used, you can specify a custom location for the token file. - **serviceAccount**: the name of the service account to use to deploy Helm Tiller. This should have enough permissions to allow Helm to work. If the service account does not exist, it will be created. More details can be found in [helm's RBAC guide](https://github.com/kubernetes/helm/blob/master/docs/rbac.md) - **storageBackend** : by default Helm stores release information in configMaps, using secrets is for storage is recommended for security. Setting this flag to `secret` will deploy/upgrade Tiller with the `--storage=secret`. Other values will be skipped and configMaps will be used. -- **slackWebhook** : a [Slack](slack.com) Webhook URL to receive Helmsman notifications. This can be passed directly or in an environment variable. +- **slackWebhook** : a [Slack](http://slack.com) Webhook URL to receive Helmsman notifications. This can be passed directly or in an environment variable. - **reverseDelete** : if set to `true` it will reverse the priority order whilst deleting. > If you use `storageBackend` with a Tiller that has been previously deployed with configMaps as storage backend, you need to migrate your release information from the configMap to the new secret on your own. Helm does not support this yet. @@ -137,7 +137,7 @@ If a namespace does not already exist, Helmsman will create it. Options: - **protected** : defines if a namespace is protected (true or false). Default false. -> For the definition of what a protected namespace means, check the [protection guide](how_to/protect_namespaces_and_releases.md) +> For the definition of what a protected namespace means, check the [protection guide](how_to/misc/protect_namespaces_and_releases.md) - **installTiller**: defines if Tiller should be deployed in this namespace or not. Default is false. Any chart desired to be deployed into a namespace with a Tiller deployed, will be deployed using that Tiller and not the one in kube-system unless you use the `TillerNamespace` option (see the [Apps](#apps) section below) to use another Tiller. > By default Tiller will be deployed into `kube-system` even if you don't define kube-system in the namespaces section. To prevent deploying Tiller into `kube-system, add kube-system in your namespaces section and set its installTiller to false. - **tillerRole**: specify the role to use. If 'cluster-admin' a clusterrolebinding will be used else a role with a single namespace scope will be created and bound with a rolebinding. @@ -327,7 +327,7 @@ Options: > To use the secrets files you must have the helm-secrets plugin - **purge** : defines whether to use the Helm purge flag when deleting the release. Default is false. - **test** : defines whether to run the chart tests whenever the release is installed. Default is false. -- **protected** : defines if the release should be protected against changes. Namespace-level protection has higher priority than this flag. Check the [protection guide](how_to/protect_namespaces_and_releases.md) for more details. Default is false. +- **protected** : defines if the release should be protected against changes. Namespace-level protection has higher priority than this flag. Check the [protection guide](how_to/misc/protect_namespaces_and_releases.md) for more details. Default is false. - **wait** : defines whether Helmsman should block execution until all k8s resources are in a ready state. Default is false. - **timeout** : helm timeout in seconds. Default 300 seconds. - **noHooks** : helm noHooks option. If true, it will disable pre/post upgrade hooks. Default is false. diff --git a/docs/how_to/README.md b/docs/how_to/README.md index 1bffb1e2..7026ef15 100644 --- a/docs/how_to/README.md +++ b/docs/how_to/README.md @@ -37,8 +37,8 @@ This page contains a list of guides on how to use Helmsman. - [Define the order of apps operations](apps/order.md) - [Delete all releases (apps)](apps/destroy.md) - Running Helmsman in different environments - - [Running Helmsman in CI](deployment/ci.md) - - [Running Helmsman inside your k8s cluster](deployment/inside_k8s.md) + - [Running Helmsman in CI](deployments/ci.md) + - [Running Helmsman inside your k8s cluster](deployments/inside_k8s.md) - Misc - [Authenticating to cloud storage providers](misc/auth_to_storage_providers.md) - [Protecting namespaces and releases](misc/protect_namespaces_and_releases.md) diff --git a/docs/how_to/apps/basic.md b/docs/how_to/apps/basic.md index e16b07b7..105deb96 100644 --- a/docs/how_to/apps/basic.md +++ b/docs/how_to/apps/basic.md @@ -117,7 +117,7 @@ DECISION: release [ artifactory ] is desired to be upgraded. Planning this for y # upgrade releases -Every time you run Helmsman, (unless the release is [protected or deployed in a protected namespace](protect_namespaces_and_releases.md)) it will upgrade existing deployed releases to the version you specified in the desired state file. It also applies the `values.yaml` file you specify with each install/upgrade. This means that when you don't change anything for a specific release, Helmsman would upgrade with the `values.yaml` file you provide (just in case it is a new file or you changed something there.) +Every time you run Helmsman, (unless the release is [protected or deployed in a protected namespace](../misc/protect_namespaces_and_releases.md)) it will upgrade existing deployed releases to the version you specified in the desired state file. It also applies the `values.yaml` file you specify with each install/upgrade. This means that when you don't change anything for a specific release, Helmsman would upgrade with the `values.yaml` file you provide (just in case it is a new file or you changed something there.) If you change the chart, the existing release will be deleted and a new one with the same name will be created using the new chart. diff --git a/docs/how_to/apps/moving_across_namespaces.md b/docs/how_to/apps/moving_across_namespaces.md index 36d89a45..5b9af835 100644 --- a/docs/how_to/apps/moving_across_namespaces.md +++ b/docs/how_to/apps/moving_across_namespaces.md @@ -161,6 +161,6 @@ pvc-f791ef92-01ab-11e8-8a7e-02412acf5adc 20Gi RWO Retain > NOTE: if there are multiple PVs in the Available state and they match capacity and read access for your application, then your application (in the new namespace) might mount to any of them. In this case, either ensure only the right PV is in the available state or make the PV available to a specific PVC - pre-fill `PV.Spec.ClaimRef` with a pointer to a PVC. Leave the `PV.Spec.ClaimRef,UID` empty, as the PVC does not need to exist at this point and you don't know PVC's UID. This PV can be bound only to the specified PVC Further details: -https://github.com/kubernetes/kubernetes/issues/48609 -https://kubernetes.io/docs/tasks/administer-cluster/change-pv-reclaim-policy/ +* https://github.com/kubernetes/kubernetes/issues/48609 +* https://kubernetes.io/docs/tasks/administer-cluster/change-pv-reclaim-policy/ diff --git a/docs/how_to/apps/protection.md b/docs/how_to/apps/protection.md index 5de513a6..0f9a7b7b 100644 --- a/docs/how_to/apps/protection.md +++ b/docs/how_to/apps/protection.md @@ -4,7 +4,7 @@ version: v1.8.0 # Protecting apps (releases) -You can define apps to be protected using the `protected` field. Please check [this doc](../protect_namespaces_and_releases.md) for details about what protection means and the difference between namespace-level and release-level protection. +You can define apps to be protected using the `protected` field. Please check [this doc](../misc/protect_namespaces_and_releases.md) for details about what protection means and the difference between namespace-level and release-level protection. Here is an example of a protected app: diff --git a/docs/how_to/deployments/inside_k8s.md b/docs/how_to/deployments/inside_k8s.md index 09c23366..bc47f728 100644 --- a/docs/how_to/deployments/inside_k8s.md +++ b/docs/how_to/deployments/inside_k8s.md @@ -6,7 +6,7 @@ version: v1.8.0 Helmsman can be deployed inside your k8s cluster and can talk to the k8s API using a `bearer token`. -See [connecting to your cluster with bearer token](../settings/create_kube_context_with_token.md) for more details. +See [connecting to your cluster with bearer token](../settings/creating_kube_context_with_token.md) for more details. Your desired state will look like: @@ -48,4 +48,4 @@ This command gives an interactive session: ```bash $ kubectl run helmsman --restart Never --image praqma/helmsman --serviceaccount=helmsman -- helmsman -f -- sleep 3600 ``` -But you can also create a proper kubernetes deployment and mount a volume to it containing your desired state file(s). \ No newline at end of file +But you can also create a proper kubernetes deployment and mount a volume to it containing your desired state file(s). diff --git a/docs/how_to/misc/multitenant_clusters_guide.md b/docs/how_to/misc/multitenant_clusters_guide.md index 28ffb23a..989edee5 100644 --- a/docs/how_to/misc/multitenant_clusters_guide.md +++ b/docs/how_to/misc/multitenant_clusters_guide.md @@ -42,7 +42,7 @@ namespaces: ``` -By default Tiller will be deployed into `kube-system` even if you don't define kube-system in the namespaces section. To prevent deploying Tiller into `kube-system, you need to explicitly add `kube-system` in your defined namespaces. See the [namespaces guide](define_namespaces.md#preventing_tiller_deployment_in_kube-system) for an example. +By default Tiller will be deployed into `kube-system` even if you don't define kube-system in the namespaces section. To prevent deploying Tiller into `kube-system, you need to explicitly add `kube-system` in your defined namespaces. See the [tiller deployment](../tiller/prevent_tiller_in_kube_system.md) for an example. ## Deploying Tiller with a service account diff --git a/docs/how_to/misc/protect_namespaces_and_releases.md b/docs/how_to/misc/protect_namespaces_and_releases.md index c5c5e67b..9484d824 100644 --- a/docs/how_to/misc/protect_namespaces_and_releases.md +++ b/docs/how_to/misc/protect_namespaces_and_releases.md @@ -65,4 +65,4 @@ apps: - You can combine both types of protection in your desired state file. The namespace-level protection always has a higher priority. - Removing the protection from a namespace means all releases deployed in that namespace are no longer protected. - We recommend using namespace-level protection for production namespace(s) and release-level protection for releases deployed in other namespaces. -- Release/namespace protection is only applied on single desired state files. It is your responsibility to make sure that multiple desired state files (if used) do not conflict with each other (e.g, one defines a particular namespace as protected and another defines it unprotected.) If you use multiple desired state files with the same cluster, please refer to [deployment strategies](../deployment_strategies.md) and [best practice](../best_practice.md) documentation. +- Release/namespace protection is only applied on single desired state files. It is your responsibility to make sure that multiple desired state files (if used) do not conflict with each other (e.g, one defines a particular namespace as protected and another defines it unprotected.) If you use multiple desired state files with the same cluster, please refer to [deployment strategies](../../deployment_strategies.md) and [best practice](../../best_practice.md) documentation. diff --git a/docs/how_to/namespaces/protection.md b/docs/how_to/namespaces/protection.md index 9a7c4e40..419a1376 100644 --- a/docs/how_to/namespaces/protection.md +++ b/docs/how_to/namespaces/protection.md @@ -6,7 +6,7 @@ version: v1.8.0 You can define namespaces to be used in your cluster. If they don't exist, Helmsman will create them for you. -You can also define certain namespaces to be protected using the `protected` field. Please check [this doc](../protect_namespaces_and_releases.md) for details about what protection means and the difference between namespace-level and release-level protection. +You can also define certain namespaces to be protected using the `protected` field. Please check [this doc](../misc/protect_namespaces_and_releases.md) for details about what protection means and the difference between namespace-level and release-level protection. ```toml @@ -29,4 +29,4 @@ namespaces: ``` -The example above will create two namespaces; staging and production. Where Helmsman sees the production namespace as a protected namespace. \ No newline at end of file +The example above will create two namespaces; staging and production. Where Helmsman sees the production namespace as a protected namespace. diff --git a/docs/how_to/settings/creating_kube_context_with_certs.md b/docs/how_to/settings/creating_kube_context_with_certs.md index 0e50ef02..14fdcaf4 100644 --- a/docs/how_to/settings/creating_kube_context_with_certs.md +++ b/docs/how_to/settings/creating_kube_context_with_certs.md @@ -8,7 +8,7 @@ Helmsman can create the kube context for you (i.e. establish connection to your Creating the context with certs, requires both the `settings` and `certificates` stanzas. -> If you use GCS, S3, or Azure blob storage for your certificates, you will need to provide means to authenticate to the respective cloud provider in the environment. See [authenticating to cloud storage providers](../auth_to_storage_providers.md) for details. +> If you use GCS, S3, or Azure blob storage for your certificates, you will need to provide means to authenticate to the respective cloud provider in the environment. See [authenticating to cloud storage providers](../misc/auth_to_storage_providers.md) for details. ```toml [settings] diff --git a/docs/how_to/tiller/multitenancy.md b/docs/how_to/tiller/multitenancy.md index c474c490..b5e6583c 100644 --- a/docs/how_to/tiller/multitenancy.md +++ b/docs/how_to/tiller/multitenancy.md @@ -9,10 +9,10 @@ You can deploy multiple Tillers in the cluster (max. one per namespace). In each - with/without TLS - with cluster-admin clusterrole or with a namespace-limited role or with an pre-configured role. -> If you use GCS, S3, or Azure blob storage for your certificates, you will need to provide means to authenticate to the respective cloud provider in the environment. See [authenticating to cloud storage providers](../auth_to_storage_providers.md) for details. +> If you use GCS, S3, or Azure blob storage for your certificates, you will need to provide means to authenticate to the respective cloud provider in the environment. See [authenticating to cloud storage providers](../misc/auth_to_storage_providers.md) for details. -> More details about using Helmsman in a multitenant cluster can be found [here](../multitenant_clusters_guide.md) +> More details about using Helmsman in a multitenant cluster can be found [here](../misc/multitenant_clusters_guide.md) You can also use pre-configured Tillers in specific namespaces. In the example below, the desired state is: to deploy Tiller in the `production` namespace with TLS and RBAC, and to use a pre-configured Tiller in the `dev` namespace. The `staging` namespace does not have any Tiller to be deployed or used. Tiller is not deployed in `kube-system`. @@ -53,4 +53,4 @@ namespaces: tillerKey: "$TILLER_KEY" # where TILLER_KEY=secrets/tiller.key.pem clientCert: "gs://mybucket/mydir/helm.cert.pem" clientKey: "s3://mybucket/mydir/helm.key.pem" -``` \ No newline at end of file +``` diff --git a/docs/how_to/tiller/shared.md b/docs/how_to/tiller/shared.md index 7b8c2bbc..21f27ec3 100644 --- a/docs/how_to/tiller/shared.md +++ b/docs/how_to/tiller/shared.md @@ -56,7 +56,7 @@ The above example will create two service accounts; `tiller-production` and `til You have to provide the TLS certificates as below. Certificates can be either located locally or in Google GCS, AWS S3 or Azure blob storage. -> If you use GCS, S3, or Azure blob storage for your certificates, you will need to provide means to authenticate to the respective cloud provider in the environment. See [authenticating to cloud storage providers](../auth_to_storage_providers.md) for details. +> If you use GCS, S3, or Azure blob storage for your certificates, you will need to provide means to authenticate to the respective cloud provider in the environment. See [authenticating to cloud storage providers](../misc/auth_to_storage_providers.md) for details. ```toml [namespaces] @@ -80,4 +80,4 @@ namespaces: tillerKey: "$TILLER_KEY" # where TILLER_KEY=secrets/tiller.key.pem clientCert: "gs://mybucket/mydir/helm.cert.pem" clientKey: "s3://mybucket/mydir/helm.key.pem" -``` \ No newline at end of file +``` From 080c80183528dd85381608726b9c6ac2f33cb665 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Socho=C5=84?= Date: Sun, 9 Jun 2019 15:36:34 +0200 Subject: [PATCH 0413/1127] Fix documentation formatting * Ensure titles start with capital latter * Switch from bash to shell * Comment out triple dots in yaml sections, they should represent the fact that some code is a snippet and not a yaml document ending * Add url to Helm chart tests * Minor formatting with newlines here and there --- docs/how_to/apps/basic.md | 22 ++++++++-------- docs/how_to/apps/destroy.md | 2 +- docs/how_to/apps/helm_tests.md | 10 +++++--- docs/how_to/apps/moving_across_namespaces.md | 25 +++++++++++-------- docs/how_to/apps/multiple_values_files.md | 8 +++--- docs/how_to/apps/order.md | 2 +- docs/how_to/apps/override_namespaces.md | 6 ++--- docs/how_to/apps/secrets.md | 12 ++++----- docs/how_to/deployments/ci.md | 4 +-- docs/how_to/deployments/inside_k8s.md | 7 +++--- docs/how_to/helm_repos/default.md | 4 +-- docs/how_to/helm_repos/local.md | 12 ++++----- docs/how_to/helm_repos/pre_configured.md | 6 ++--- docs/how_to/helm_repos/s3.md | 12 ++++----- docs/how_to/misc/helmsman_on_windows10.md | 2 +- .../misc/limit-deployment-to-specific-apps.md | 14 +++++------ docs/how_to/misc/merge_desired_state_files.md | 8 +++--- .../send_slack_notifications_from_helmsman.md | 7 +++--- docs/how_to/namespaces/limits.md | 2 +- .../deploy_apps_with_specific_tiller.md | 6 ++--- .../tiller/prevent_tiller_in_kube_system.md | 3 ++- 21 files changed, 91 insertions(+), 83 deletions(-) diff --git a/docs/how_to/apps/basic.md b/docs/how_to/apps/basic.md index 105deb96..2381e5b5 100644 --- a/docs/how_to/apps/basic.md +++ b/docs/how_to/apps/basic.md @@ -2,11 +2,11 @@ version: v1.5.0 --- -# install releases +# Install releases You can run helmsman with the [example.toml](https://github.com/Praqma/helmsman/blob/master/example.toml) or [example.yaml](https://github.com/Praqma/helmsman/blob/master/example.yaml) file. -``` +```shell $ helmsman --apply -f example.toml 2017/11/19 18:17:57 Parsed [[ example.toml ]] successfully and found [ 2 ] apps. @@ -22,14 +22,14 @@ DECISION: release [ artifactory ] is not present in the current k8s context. Wil ``` -``` +```shell $ helm list --namespace staging NAME REVISION UPDATED STATUS CHART NAMESPACE artifactory 1 Sun Nov 19 18:18:06 2017 DEPLOYED artifactory-6.2.0 staging jenkins 1 Sun Nov 19 18:18:03 2017 DEPLOYED jenkins-0.9.1 staging ``` -# delete releases +# Delete releases You can then change your desire, for example to disable the Jenkins release that was created above by setting `enabled = false` : @@ -37,7 +37,7 @@ Then run Helmsman again and it will detect that you want to delete Jenkins: > Note: As of v1.4.0-rc, deleting the jenkins app entry in the desired state file WILL result in deleting the jenkins release. To prevent this, use the `--keep-untracked-releases` flag with your Helmsman command. -``` +```shell $ helmsman --apply -f example.toml 2017/11/19 18:28:27 Parsed [[ example.toml ]] successfully and found [ 2 ] apps. 2017/11/19 18:28:29 WARN: I could not create namespace [staging ]. It already exists. I am skipping this. @@ -51,7 +51,7 @@ DECISION: release [ artifactory ] is desired to be upgraded. Planning this for y 2017/11/19 18:29:11 INFO: attempting: -- upgrading release [ artifactory ] ``` -``` +```shell $ helm list --namespace staging NAME REVISION UPDATED STATUS CHART NAMESPACE artifactory 2 Sun Nov 19 18:29:11 2017 DEPLOYED artifactory-6.2.0 staging @@ -80,7 +80,7 @@ If you would like the release to be deleted along with its history, you can use ``` ```yaml -... +# ... apps: jenkins: name: "jenkins" @@ -93,15 +93,15 @@ apps: purge: true # this means purge delete this release whenever it is required to be deleted test: false -... +# ... ``` -# rollback releases +# Rollback releases > Rollbacks in helm versions 2.8.2 and higher may not work due to a [bug](https://github.com/helm/helm/issues/3722). Similarly, if you change `enabled` back to `true`, it will figure out that you would like to roll it back. -``` +```shell $ helmsman --apply -f example.toml 2017/11/19 18:30:41 Parsed [[ example.toml ]] successfully and found [ 2 ] apps. 2017/11/19 18:30:42 WARN: I could not create namespace [staging ]. It already exists. I am skipping this. @@ -115,7 +115,7 @@ DECISION: release [ artifactory ] is desired to be upgraded. Planning this for y 2017/11/19 18:30:50 INFO: attempting: -- upgrading release [ artifactory ] ``` -# upgrade releases +# Upgrade releases Every time you run Helmsman, (unless the release is [protected or deployed in a protected namespace](../misc/protect_namespaces_and_releases.md)) it will upgrade existing deployed releases to the version you specified in the desired state file. It also applies the `values.yaml` file you specify with each install/upgrade. This means that when you don't change anything for a specific release, Helmsman would upgrade with the `values.yaml` file you provide (just in case it is a new file or you changed something there.) diff --git a/docs/how_to/apps/destroy.md b/docs/how_to/apps/destroy.md index 90c8c215..83786ce7 100644 --- a/docs/how_to/apps/destroy.md +++ b/docs/how_to/apps/destroy.md @@ -2,7 +2,7 @@ version: v1.6.2 --- -# delete all deployed releases +# Delete all deployed releases Helmsman allows you to delete all the helm releases that were deployed by Helmsman from a given desired state. diff --git a/docs/how_to/apps/helm_tests.md b/docs/how_to/apps/helm_tests.md index 88dc8b57..820cf20b 100644 --- a/docs/how_to/apps/helm_tests.md +++ b/docs/how_to/apps/helm_tests.md @@ -2,7 +2,9 @@ version: v1.3.0-rc --- -# test charts +# Test charts + +Helm allows running [chart tests](https://github.com/helm/helm/blob/master/docs/chart_tests.md). You can specify that you would like a chart to be tested whenever it is installed for the first time using the `test` key as follows: @@ -26,7 +28,7 @@ You can specify that you would like a chart to be tested whenever it is installe ``` ```yaml -... +# ... apps: jenkins: @@ -40,6 +42,6 @@ apps: purge: false test: true # setting this to true, means you want the charts tests to be run on this release when it is installed. -... +#... -``` \ No newline at end of file +``` diff --git a/docs/how_to/apps/moving_across_namespaces.md b/docs/how_to/apps/moving_across_namespaces.md index 5b9af835..9600fd88 100644 --- a/docs/how_to/apps/moving_across_namespaces.md +++ b/docs/how_to/apps/moving_across_namespaces.md @@ -2,7 +2,7 @@ version: v1.3.0-rc --- -# move charts across namespaces +# Move charts across namespaces If you have a workflow for testing a release first in the `staging` namespace then move it to the `production` namespace, Helmsman can help you. @@ -34,7 +34,7 @@ If you have a workflow for testing a release first in the `staging` namespace th ``` ```yaml -... +# ... namespaces: staging: @@ -52,7 +52,7 @@ apps: purge: false test: true -... +# ... ``` @@ -83,7 +83,7 @@ Then if you change the namespace key for jenkins: ``` ```yaml -... +# ... namespaces: staging: @@ -101,7 +101,7 @@ apps: purge: false test: true -... +# ... ``` @@ -120,42 +120,45 @@ Now, the newly created PVC (in the new namespace) will not be able to mount to t 1. You have to make sure the _Reclaim Policy_ of the old PV is set to **Retain**. In dynamic provisioned PVs, the default is Delete. To change it: -``` +```shell kubectl patch pv -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}' ``` 2. Once your old helm release is deleted, the old PVC and PV are still there. Go ahead and delete the PVC -``` +```shell kubectl delete pvc --namespace ``` + Since, we changed the Reclaim Policy to Retain, the PV will stay around (with all your data). 3. The PV is now in the **Released** state but not yet available for mounting. -``` +```shell kubectl get pv NAME CAPACITY ACCESSMODES RECLAIMPOLICY STATUS CLAIM STORAGECLASS REASON AGE ... pvc-f791ef92-01ab-11e8-8a7e-02412acf5adc 20Gi RWO Retain Released staging/myapp-persistent-storage-test-old-0 gp2 5m -``` +```shell + Now, you need to make it Available, for that we need to remove the `PV.Spec.ClaimRef` from the PV spec: -``` +```shell kubectl edit pv # edit the file and save it ``` Now, the PV should become in the **Available** state: -``` +```shell kubectl get pv NAME CAPACITY ACCESSMODES RECLAIMPOLICY STATUS CLAIM STORAGECLASS REASON AGE ... pvc-f791ef92-01ab-11e8-8a7e-02412acf5adc 20Gi RWO Retain Available gp2 7m ``` + 4. Delete the new PVC (and its mounted PV if necessary), then delete your application pod(s) in the new namespace. Assuming you have a deployment/replication controller in place, the pod will be recreated in the new namespace and this time will mount to the old volume and your data will be once again available to your application. > NOTE: if there are multiple PVs in the Available state and they match capacity and read access for your application, then your application (in the new namespace) might mount to any of them. In this case, either ensure only the right PV is in the available state or make the PV available to a specific PVC - pre-fill `PV.Spec.ClaimRef` with a pointer to a PVC. Leave the `PV.Spec.ClaimRef,UID` empty, as the PVC does not need to exist at this point and you don't know PVC's UID. This PV can be bound only to the specified PVC diff --git a/docs/how_to/apps/multiple_values_files.md b/docs/how_to/apps/multiple_values_files.md index f866eee5..b16c8fe1 100644 --- a/docs/how_to/apps/multiple_values_files.md +++ b/docs/how_to/apps/multiple_values_files.md @@ -2,7 +2,7 @@ version: v1.3.0-rc --- -# multiple value files +# Multiple value files You can include multiple yaml value files to separate configuration for different environments. @@ -40,7 +40,7 @@ You can include multiple yaml value files to separate configuration for differen ``` ```yaml -... +# ... apps: jenkins: @@ -65,6 +65,6 @@ apps: valuesFiles: - "../my-jenkins-common-values.yaml" - "../my-jenkins-testing-values.yaml" -... +# ... -``` \ No newline at end of file +``` diff --git a/docs/how_to/apps/order.md b/docs/how_to/apps/order.md index 60d78167..179ce6b5 100644 --- a/docs/how_to/apps/order.md +++ b/docs/how_to/apps/order.md @@ -10,7 +10,7 @@ Priority is an optional flag and has a default value of 0 (zero). If set, it can If you want your apps to be deleted in the reverse order as they where created, you can also use the optional `Settings` flag `reverseDelete`, to achieve this, set it to `true` -## Example +# Example ```toml [metadata] diff --git a/docs/how_to/apps/override_namespaces.md b/docs/how_to/apps/override_namespaces.md index 1f82989e..4708b34d 100644 --- a/docs/how_to/apps/override_namespaces.md +++ b/docs/how_to/apps/override_namespaces.md @@ -10,7 +10,7 @@ This flag overrides all namespaces defined in your DSF with the single one you p # Example -dsf.toml +`dsf.toml`: ```toml [metadata] org = "example.com" @@ -52,7 +52,7 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" valuesFile = "" # leaving it empty uses the default chart values ``` -dsf.yaml +`dsf.yaml`: ```yaml metadata: org: "example.com" @@ -96,7 +96,7 @@ apps: In command line, we run : -``` +```shell helmsman -f dsf.toml --debug --ns-override testing ``` diff --git a/docs/how_to/apps/secrets.md b/docs/how_to/apps/secrets.md index cc00435d..28b1f987 100644 --- a/docs/how_to/apps/secrets.md +++ b/docs/how_to/apps/secrets.md @@ -2,7 +2,7 @@ version: v1.6.0 --- -# passing secrets from env variables: +# Passing secrets from env variables: Starting from v0.1.3, Helmsman allows you to pass secrets and other user input to helm charts from environment variables as follows: @@ -50,16 +50,16 @@ apps: These input variables will be passed to the chart when it is deployed/upgraded using helm's `--set <>=<>` -# passing secrets from env files +# Passing secrets from env files You can also keep these environment variables in files, by default Helmsman will load variables from a `.env` file but you can also specify files by using the `-e` option: -```bash +```shell helmsman -e myVars ``` Below are some examples of valid env files -```bash +```shell # I am a comment and that is OK SOME_VAR=someval FOO=BAR # comments at line end are OK too @@ -72,6 +72,6 @@ FOO: bar BAR: baz ``` -# passing secrets using helm secrets plugin +# Passing secrets using helm secrets plugin -You can also use the [helm secrets plugin](https://github.com/futuresimple/helm-secrets) to pass your secrets. \ No newline at end of file +You can also use the [helm secrets plugin](https://github.com/futuresimple/helm-secrets) to pass your secrets. diff --git a/docs/how_to/deployments/ci.md b/docs/how_to/deployments/ci.md index 04af63b7..f8950305 100644 --- a/docs/how_to/deployments/ci.md +++ b/docs/how_to/deployments/ci.md @@ -7,7 +7,7 @@ version: v1.3.0-rc You can run Helmsman as a job in your CI system using the [helmsman docker image](https://hub.docker.com/r/praqma/helmsman/). The following example is a `config.yml` file for CircleCI but can be replicated for other CI systems. -``` +```yaml version: 2 jobs: @@ -30,4 +30,4 @@ workflows: > IMPORTANT: If your CI build logs are publicly readable, don't use the `--verbose` flag as logs any secrets being passed from env vars to the helm charts. -The `helmsman-deployments.toml` is your desired state file which will version controlled in your git repo. \ No newline at end of file +The `helmsman-deployments.toml` is your desired state file which will version controlled in your git repo. diff --git a/docs/how_to/deployments/inside_k8s.md b/docs/how_to/deployments/inside_k8s.md index bc47f728..d72f47d6 100644 --- a/docs/how_to/deployments/inside_k8s.md +++ b/docs/how_to/deployments/inside_k8s.md @@ -31,13 +31,13 @@ To deploy Helmsman into a k8s cluster, few steps are needed: 1. Create a k8s service account -```bash +```shell $ kubectl create sa helmsman ``` 2. Create a clusterrolebinding -```bash +```shell $ kubectl create clusterrolebinding helmsman-cluster-admin --clusterrole=cluster-admin --serviceaccount=default:helmsman ``` @@ -45,7 +45,8 @@ $ kubectl create clusterrolebinding helmsman-cluster-admin --clusterrole=cluster This command gives an interactive session: -```bash +```shell $ kubectl run helmsman --restart Never --image praqma/helmsman --serviceaccount=helmsman -- helmsman -f -- sleep 3600 ``` + But you can also create a proper kubernetes deployment and mount a volume to it containing your desired state file(s). diff --git a/docs/how_to/helm_repos/default.md b/docs/how_to/helm_repos/default.md index bde7a00e..86679834 100644 --- a/docs/how_to/helm_repos/default.md +++ b/docs/how_to/helm_repos/default.md @@ -36,10 +36,10 @@ stable = "https://mycustomstablerepo.com" ``` ```yaml -... +# ... helmRepos: stable: "https://mycustomstablerepo.com" -... +# ... ``` diff --git a/docs/how_to/helm_repos/local.md b/docs/how_to/helm_repos/local.md index 9566c0a9..7d7a78be 100644 --- a/docs/how_to/helm_repos/local.md +++ b/docs/how_to/helm_repos/local.md @@ -2,7 +2,7 @@ version: v1.3.0-rc --- -# use local helm charts +# Use local helm charts You can use your locally developed charts. @@ -12,7 +12,7 @@ If you use a file path (relative to the DSF, or absolute) for the ```chart``` at helmsman will try to resolve that chart from the local file system. The chart on the local file system must have a version matching the version specified in the DSF. -## Served by Helm +## Served by helm You can serve them on localhost using helm's `serve` option. @@ -22,21 +22,21 @@ You can serve them on localhost using helm's `serve` option. [helmRepos] stable = "https://kubernetes-charts.storage.googleapis.com" incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" -local = http://127.0.0.1:8879 +local = "http://127.0.0.1:8879" ... ``` ```yaml -... +# ... helmRepos: stable: "https://kubernetes-charts.storage.googleapis.com" incubator: "http://storage.googleapis.com/kubernetes-charts-incubator" - local: http://127.0.0.1:8879 + local: "http://127.0.0.1:8879" -... +# ... ``` diff --git a/docs/how_to/helm_repos/pre_configured.md b/docs/how_to/helm_repos/pre_configured.md index 98b1dc00..f4356dba 100644 --- a/docs/how_to/helm_repos/pre_configured.md +++ b/docs/how_to/helm_repos/pre_configured.md @@ -14,8 +14,8 @@ preconfiguredHelmRepos = [ "myrepo1", "myrepo2" ] ```yaml preconfiguredHelmRepos: -- myrepo1 -- myrepo2 + - myrepo1 + - myrepo2 ``` -> In this case you will manually need to execute `helm repo add myrepo1 --username= --password=` \ No newline at end of file +> In this case you will manually need to execute `helm repo add myrepo1 --username= --password=` diff --git a/docs/how_to/helm_repos/s3.md b/docs/how_to/helm_repos/s3.md index 4d9056b8..6c31423f 100644 --- a/docs/how_to/helm_repos/s3.md +++ b/docs/how_to/helm_repos/s3.md @@ -8,22 +8,22 @@ Helmsman allows you to use private charts from private repos. Currently only rep You need to provide one of the following env variables: -- AWS_ACCESS_KEY_ID -- AWS_SECRET_ACCESS_KEY -- AWS_DEFAULT_REGION +- `AWS_ACCESS_KEY_ID` +- `AWS_SECRET_ACCESS_KEY` +- `AWS_DEFAULT_REGION` Helmsman uses the [helm s3](https://github.com/hypnoglow/helm-s3) plugin to work with S3 helm repos. ```toml [helmRepos] -myPrivateRepo = s3://this-is-a-private-repo/charts +myPrivateRepo = "s3://this-is-a-private-repo/charts" ``` ```yaml helmRepos: - myPrivateRepo: s3://this-is-a-private-repo/charts + myPrivateRepo: "s3://this-is-a-private-repo/charts" -``` \ No newline at end of file +``` diff --git a/docs/how_to/misc/helmsman_on_windows10.md b/docs/how_to/misc/helmsman_on_windows10.md index 51490be6..5bee5696 100644 --- a/docs/how_to/misc/helmsman_on_windows10.md +++ b/docs/how_to/misc/helmsman_on_windows10.md @@ -13,7 +13,7 @@ If you have Windows 10 with Docker installed, you **might** be able to run Helms 3. Configure your desired state file to use the kubeContext only. i.e. no cluster connection settings. 2. Run the following command: -``` +```shell docker run --rm -it -v :/root/.kube -v :/tmp praqma/helmsman:v1.0.2 helmsman -f dsf.toml --debug --apply ``` diff --git a/docs/how_to/misc/limit-deployment-to-specific-apps.md b/docs/how_to/misc/limit-deployment-to-specific-apps.md index 43f2f7b0..4815c0d2 100644 --- a/docs/how_to/misc/limit-deployment-to-specific-apps.md +++ b/docs/how_to/misc/limit-deployment-to-specific-apps.md @@ -2,19 +2,19 @@ version: v1.9.0 --- -# limit execution to explicitly defined apps +# Limit execution to explicitly defined apps Starting from v1.9.0, Helmsman allows you to pass the `-target` flag multiple times to specify multiple apps that limits apps considered by Helmsman during this specific execution. Thanks to this one can deploy specific applications among all defined for an environment. -## An example +## Example -having environment defined with such apps: +Having environment defined with such apps: * example.yaml: ```yaml -... +# ... apps: jenkins: namespace: "staging" # maps to the namespace as defined in namespaces above @@ -27,14 +27,14 @@ apps: enabled: true # change to false if you want to delete this app release empty: false: chart: "stable/artifactory" # changing the chart name means delete and recreate this chart version: "7.0.6" # chart version -... +# ... ``` running Helmsman with `-f example.yaml` would result in checking state and invoking deployment for both jenkins and artifactory application. With `-target` flag in command like -```bash +```shell $ helmsman -f example.yaml -target artifactory ... ``` @@ -42,6 +42,6 @@ one can execute Helmsman's environment defined with example.yaml limited to only Multiple applications can be set with `-target`, like -```bash +```shell $ helmsman -f example.yaml -target artifactory -target jenkins ... ``` diff --git a/docs/how_to/misc/merge_desired_state_files.md b/docs/how_to/misc/merge_desired_state_files.md index b6305d7e..01b9137b 100644 --- a/docs/how_to/misc/merge_desired_state_files.md +++ b/docs/how_to/misc/merge_desired_state_files.md @@ -2,7 +2,7 @@ version: v1.5.0 --- -# supply multiple desired state files +# Supply multiple desired state files Starting from v1.5.0, Helmsman allows you to pass the `-f` flag multiple times to specify multiple desired state files that should be merged. This allows us to do things like specify our non-environment-specific config in a `common.toml` file @@ -11,7 +11,7 @@ to do the merging, and is subject to the limitations described there. For example: -* common.toml: +* `common.toml`: ```toml [metadata] org = "Organization Name" @@ -24,7 +24,7 @@ storageBackend = "secret" ... ``` -* nonprod.toml: +* `nonprod.toml`: ```toml [settings] kubeContext = "cluster-nonprod" @@ -39,6 +39,6 @@ kubeContext = "cluster-nonprod" ``` One can then run the following to use the merged config of the above files, with later files override values of earlier ones: -```bash +```shell $ helmsman -f common.toml -f nonprod.toml ... ``` diff --git a/docs/how_to/misc/send_slack_notifications_from_helmsman.md b/docs/how_to/misc/send_slack_notifications_from_helmsman.md index d3cc5eef..8ea271d6 100644 --- a/docs/how_to/misc/send_slack_notifications_from_helmsman.md +++ b/docs/how_to/misc/send_slack_notifications_from_helmsman.md @@ -14,10 +14,11 @@ slackWebhook = $MY_SLACK_WEBHOOK ```yaml settings: - ... - #slackWebhook : "$MY_SLACK_WEBHOOK" + # ... + slackWebhook : "$MY_SLACK_WEBHOOK" + # ... ``` ## Getting a Slack Webhook URL -Follow the [slack guide](https://api.slack.com/incoming-webhooks) for generating a webhook URL. \ No newline at end of file +Follow the [slack guide](https://api.slack.com/incoming-webhooks) for generating a webhook URL. diff --git a/docs/how_to/namespaces/limits.md b/docs/how_to/namespaces/limits.md index 611cd603..233f18fe 100644 --- a/docs/how_to/namespaces/limits.md +++ b/docs/how_to/namespaces/limits.md @@ -49,4 +49,4 @@ namespaces: ``` -The example above will create two namespaces; staging and production with resource limits defined for the staging namespace. +The example above will create two namespaces - staging and production - with resource limits defined for the staging namespace. diff --git a/docs/how_to/tiller/deploy_apps_with_specific_tiller.md b/docs/how_to/tiller/deploy_apps_with_specific_tiller.md index e13f7874..1ce0f02c 100644 --- a/docs/how_to/tiller/deploy_apps_with_specific_tiller.md +++ b/docs/how_to/tiller/deploy_apps_with_specific_tiller.md @@ -21,7 +21,7 @@ You can then tell Helmsman to deploy specific releases in a specific namespace: ``` ```yaml -#... +# ... apps: jenkins: namespace: "production" # pointing to the namespace defined above @@ -29,8 +29,8 @@ apps: chart: "stable/jenkins" version: "0.9.1" -#... +# ... ``` -In the above example, `Jenkins` will be deployed in the production namespace using the Tiller deployed in the production namespace. If the production namespace was not configured to have Tiller deployed there, Jenkins will be deployed using the Tiller in `kube-system`. \ No newline at end of file +In the above example, `Jenkins` will be deployed in the production namespace using the Tiller deployed in the production namespace. If the production namespace was not configured to have Tiller deployed there, Jenkins will be deployed using the Tiller in `kube-system`. diff --git a/docs/how_to/tiller/prevent_tiller_in_kube_system.md b/docs/how_to/tiller/prevent_tiller_in_kube_system.md index 1df2ba7d..ef20c1be 100644 --- a/docs/how_to/tiller/prevent_tiller_in_kube_system.md +++ b/docs/how_to/tiller/prevent_tiller_in_kube_system.md @@ -11,8 +11,9 @@ By default Tiller will be deployed into `kube-system` even if you don't define k [namespaces.kube-system] # installTiller = false # this line is not needed since the default is false, but can be added for human readability. ``` + ```yaml namespaces: kube-system: #installTiller: false # this line is not needed since the default is false, but can be added for human readability. -``` \ No newline at end of file +``` From c24164f73ca2500421bd96cca831623e273961a3 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Tue, 11 Jun 2019 10:43:13 +0200 Subject: [PATCH 0414/1127] Fix toml for helmFlags in docs Co-Authored-By: Lachlan Cooper --- docs/desired_state_specification.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 542a3091..44ccc30c 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -356,7 +356,7 @@ Example: protected = false wait = true priority = -3 - helmFlags: [ + helmFlags = [ "--recreate-pods", ] [apps.jenkins.set] From 021166a2a9a7d4084f12496e5b968a7a6c3b62e1 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Fri, 21 Jun 2019 13:20:20 +0200 Subject: [PATCH 0415/1127] adding helm tiller plugin and bumping dependencies. fixes #179 --- dockerfile/dockerfile | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile index a0f2410a..a504d0e0 100644 --- a/dockerfile/dockerfile +++ b/dockerfile/dockerfile @@ -1,7 +1,7 @@ # This is a docker image for helmsman -FROM golang:1.10-alpine3.7 as builder +FROM golang:1.11.11-alpine3.10 as builder WORKDIR /go/src/ @@ -20,14 +20,14 @@ RUN cd helmsman \ # The image to keep -FROM alpine:3.7 +FROM alpine:3.10 ARG KUBE_VERSION ARG HELM_VERSION ENV KUBE_VERSION ${KUBE_VERSION:-v1.11.3} ENV HELM_VERSION ${HELM_VERSION:-v2.11.0} -ENV HELM_DIFF_VERSION ${HELM_DIFF_VERSION:-v2.11.0+3} +ENV HELM_DIFF_VERSION ${HELM_DIFF_VERSION:-v2.11.0+5} RUN apk --no-cache update \ && apk add --update --no-cache ca-certificates git openssh \ @@ -47,6 +47,7 @@ RUN mkdir -p ~/.helm/plugins \ && helm plugin install https://github.com/nouney/helm-gcs \ && helm plugin install https://github.com/databus23/helm-diff --version ${HELM_DIFF_VERSION} \ && helm plugin install https://github.com/futuresimple/helm-secrets \ + && helm plugin install https://github.com/rimusz/helm-tiller \ && rm -r /tmp/helm-diff /tmp/helm-diff.tgz WORKDIR /tmp From b6cf9dd315c2b2ad4ed94bfb4dfa611e28295017 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Sat, 8 Jun 2019 11:38:29 +0200 Subject: [PATCH 0416/1127] Add TillerRoleConfigFile for namespace to allow create Tiller role with custom permissions --- helm_helpers.go | 10 +++++----- init.go | 1 - kube_helpers.go | 17 ++++++++++++----- namespace.go | 1 + utils.go | 8 ++++++-- 5 files changed, 24 insertions(+), 13 deletions(-) diff --git a/helm_helpers.go b/helm_helpers.go index b19e9efc..e6d74f34 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -346,7 +346,7 @@ func addHelmRepos(repos map[string]string) (bool, string) { // If serviceAccount is not provided (empty string), the defaultServiceAccount is used. // If no defaultServiceAccount is provided, A service account is created and Tiller is deployed with the new service account // If no namespace is provided, Tiller is deployed to kube-system -func deployTiller(namespace string, serviceAccount string, defaultServiceAccount string, role string) (bool, string) { +func deployTiller(namespace string, serviceAccount string, defaultServiceAccount string, role string, roleConfigFile string) (bool, string) { log.Println("INFO: deploying Tiller in namespace [ " + namespace + " ].") sa := "" if serviceAccount != "" { @@ -354,7 +354,7 @@ func deployTiller(namespace string, serviceAccount string, defaultServiceAccount if strings.Contains(err, "NotFound") || strings.Contains(err, "not found") { log.Println("INFO: service account [ " + serviceAccount + " ] does not exist in namespace [ " + namespace + " ] .. attempting to create it ... ") - if _, rbacErr := createRBAC(serviceAccount, namespace, role); rbacErr != "" { + if _, rbacErr := createRBAC(serviceAccount, namespace, role, roleConfigFile); rbacErr != "" { return false, rbacErr } } else { @@ -367,7 +367,7 @@ func deployTiller(namespace string, serviceAccount string, defaultServiceAccount if strings.Contains(err, "NotFound") || strings.Contains(err, "not found") { log.Println("INFO: service account [ " + defaultServiceAccount + " ] does not exist in namespace [ " + namespace + " ] .. attempting to create it ... ") - if _, rbacErr := createRBAC(defaultServiceAccount, namespace, role); rbacErr != "" { + if _, rbacErr := createRBAC(defaultServiceAccount, namespace, role, ""); rbacErr != "" { return false, rbacErr } } else { @@ -446,12 +446,12 @@ func initHelm() (bool, string) { if ns, ok := s.Namespaces["kube-system"]; ok { if ns.InstallTiller { - if ok, err := deployTiller("kube-system", ns.TillerServiceAccount, defaultSA, ns.TillerRole); !ok { + if ok, err := deployTiller("kube-system", ns.TillerServiceAccount, defaultSA, ns.TillerRole, ""); !ok { return false, err } } } else { - if ok, err := deployTiller("kube-system", "", defaultSA, ns.TillerRole); !ok { + if ok, err := deployTiller("kube-system", "", defaultSA, ns.TillerRole, ""); !ok { return false, err } } diff --git a/init.go b/init.go index e338bb8b..1576e5c9 100644 --- a/init.go +++ b/init.go @@ -149,7 +149,6 @@ func init() { } else { logError(msg) } - // Merge Apps that already existed in the state for appName, app := range fileState.Apps { if _, ok := s.Apps[appName]; ok { diff --git a/kube_helpers.go b/kube_helpers.go index e916c12e..aaa0df35 100644 --- a/kube_helpers.go +++ b/kube_helpers.go @@ -31,7 +31,7 @@ func validateServiceAccount(sa string, namespace string) (bool, string) { // createRBAC creates a k8s service account and bind it to a (Cluster)Role // role can be "cluster-admin" or any other custom name // It binds it to a new role called "helmsman-tiller" -func createRBAC(sa string, namespace string, role string) (bool, string) { +func createRBAC(sa string, namespace string, role string, roleConfigFile string) (bool, string) { var ok bool var err string if role == "" { @@ -48,7 +48,7 @@ func createRBAC(sa string, namespace string, role string) (bool, string) { } return false, err } - if ok, err = createRole(namespace, role); ok { + if ok, err = createRole(namespace, role, roleConfigFile); ok { if ok, err = createRoleBinding(role, sa, namespace); ok { return true, "" } @@ -377,10 +377,17 @@ func createRoleBinding(role string, saName string, namespace string) (bool, stri } // createRole creates a k8s Role in a given namespace from a template -func createRole(namespace string, role string) (bool, string) { +func createRole(namespace string, role string, roleConfigFile string) (bool, string) { + var resource []byte + var e error - // load static resource - resource, e := Asset("data/role.yaml") + if roleConfigFile != "" { + // load file from path of TillerRoleConfigFile + resource, e = ioutil.ReadFile(roleConfigFile) + } else { + // load static resource + resource, e = Asset("data/role.yaml") + } if e != nil { logError(e.Error()) } diff --git a/namespace.go b/namespace.go index 070e940f..7902360f 100644 --- a/namespace.go +++ b/namespace.go @@ -27,6 +27,7 @@ type namespace struct { UseTiller bool `yaml:"useTiller"` TillerServiceAccount string `yaml:"tillerServiceAccount"` TillerRole string `yaml:"tillerRole"` + TillerRoleConfigFile string `yaml:"tillerRoleConfigFile"` CaCert string `yaml:"caCert"` TillerCert string `yaml:"tillerCert"` TillerKey string `yaml:"tillerKey"` diff --git a/utils.go b/utils.go index efe84eac..d10d63a3 100644 --- a/utils.go +++ b/utils.go @@ -236,7 +236,12 @@ func addDefaultHelmRepos(s *state) { // resolvePaths resolves relative paths of certs/keys/chart and replace them with a absolute paths func resolvePaths(relativeToFile string, s *state) { dir := filepath.Dir(relativeToFile) - + for ns, v := range s.Namespaces { + if v.TillerRoleConfigFile != "" { + v.TillerRoleConfigFile, _ = filepath.Abs(filepath.Join(dir, v.TillerRoleConfigFile)) + } + s.Namespaces[ns] = v + } for k, v := range s.Apps { if v.ValuesFile != "" { v.ValuesFile, _ = filepath.Abs(filepath.Join(dir, v.ValuesFile)) @@ -266,7 +271,6 @@ func resolvePaths(relativeToFile string, s *state) { } } } - s.Apps[k] = v } // resolving paths for Bearer Token path in settings From c8a1d22fe51284ee3d7b7bf2fea9fdca645dd70c Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Sat, 8 Jun 2019 11:58:26 +0200 Subject: [PATCH 0417/1127] Add docs explaining how to use tillerRoleConfigFile --- docs/desired_state_specification.md | 7 +++- .../how_to/namespaces/custom_helmsman_role.md | 37 +++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 docs/how_to/namespaces/custom_helmsman_role.md diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 8e169579..c0f985d5 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -1,5 +1,5 @@ --- -version: v1.9.1 +version: v1.10.0 --- # Helmsman desired state specification @@ -146,6 +146,7 @@ Options: - **installTiller**: defines if Tiller should be deployed in this namespace or not. Default is false. Any chart desired to be deployed into a namespace with a Tiller deployed, will be deployed using that Tiller and not the one in kube-system unless you use the `TillerNamespace` option (see the [Apps](#apps) section below) to use another Tiller. > By default Tiller will be deployed into `kube-system` even if you don't define kube-system in the namespaces section. To prevent deploying Tiller into `kube-system, add kube-system in your namespaces section and set its installTiller to false. - **tillerRole**: specify the role to use. If 'cluster-admin' a clusterrolebinding will be used else a role with a single namespace scope will be created and bound with a rolebinding. +- **tillerRoleConfigFile**: relative path to file templating custom Tiller role. If `installTiller` is true and `tillerRole` is not `cluster-admin`, then helmsman will create namespace specific Tiller role based on the template file passed with this parameter. - **useTiller**: defines that you would like to use an existing Tiller from that namespace. Can't be set together with `installTiller` - **labels** : defines labels to be added to the namespace, doesn't remove existing labels but updates them if the label key exists with any other different value. You can define any key/value pairs. Default is empty. - **annotations** : defines annotations to be added to the namespace. It behaves the same way as the labels option. @@ -154,7 +155,7 @@ Options: - **tillerServiceAccount**: defines what service account to use when deploying Tiller. If this is not set, the following options are considered: 1. If the `serviceAccount` defined in the `settings` section exists in the namespace you want to deploy Tiller in, it will be used. - 2. If you defined `serviceAccount` in the `settings` section and it does not exist in the namespace you want to deploy Tiller in, Helmsman creates the service account in that namespace and binds it to a (cluster)role. If the namespace is kube-system and `tillerRole` is unset or is set to cluster-admin, the service account is bound to `cluster-admin` clusterrole. Otherwise, if you specified a `tillerRole`, a new role with that name is created and bound to the service account with rolebinding. If `tillerRole` is unset (for namespaces other than kube-system), the role is called `helmsman-tiller` and is created in the specified namespace to only gives access to that namespace. The custom role is created from a [yaml template](../data/role.yaml). + 2. If you defined `serviceAccount` in the `settings` section and it does not exist in the namespace you want to deploy Tiller in, Helmsman creates the service account in that namespace and binds it to a (cluster)role. If the namespace is kube-system and `tillerRole` is unset or is set to cluster-admin, the service account is bound to `cluster-admin` clusterrole. Otherwise, if you specified a `tillerRole`, a new role with that name is created and bound to the service account with rolebinding. If `tillerRole` is unset (for namespaces other than kube-system), the role is called `helmsman-tiller` and is created in the specified namespace to only gives access to that namespace. The custom role is created from a [yaml template](../data/role.yaml) if no `tillerRoleConfigFile` was set, or will use the template file (of the same structure as [yaml template](../data/role.yaml)) to create Role from. > If `installTiller` is not defined or set to false, this flag is ignored. @@ -180,6 +181,7 @@ protected = false protected = true installTiller = true tillerServiceAccount = "tiller-production" +tillerRoleConfigFile = "../roles/helmsman-tiller.yaml" caCert = "secrets/ca.cert.pem" tillerCert = "secrets/tiller.cert.pem" tillerKey = "$TILLER_KEY" # where TILLER_KEY=secrets/tiller.key.pem @@ -216,6 +218,7 @@ namespaces: protected: true installTiller: true tillerServiceAccount: "tiller-production" + tillerRoleConfigFile: "../roles/helmsman-tiller.yaml" caCert: "secrets/ca.cert.pem" tillerCert: "secrets/tiller.cert.pem" tillerKey: "$TILLER_KEY" # where TILLER_KEY=secrets/tiller.key.pem diff --git a/docs/how_to/namespaces/custom_helmsman_role.md b/docs/how_to/namespaces/custom_helmsman_role.md new file mode 100644 index 00000000..3f90b8e3 --- /dev/null +++ b/docs/how_to/namespaces/custom_helmsman_role.md @@ -0,0 +1,37 @@ +--- +version: v1.10.0 +--- + +# Create helmsman per namespace with your custom Role + +You can deploy namespace-specific helmsman Tiller with Service Account having custom Role. + +By default, when defining Namespaces in Desired State Specification file, when `installTiller` is enabled for specific namespace, +it creates the Role to bind the Tiller to with default [yaml template](../../../data/role.yaml). + +If there's a need for custom Role (let's say each namespace has its different and specific requirements to permissions), +you can define `tillerRoleConfigFile`, which is a relative path pointing at a template of a Role (same format as a [yaml template](../../../data/role.yaml)), +so when Helmsman creates Tiller in the namespace with this key, custom Role will be created for Tiller. + +```toml +[namespaces] +[namespaces.dev] +useTiller = true +[namespaces.production] +installTiller = true +tillerServiceAccount = "tiller-production" +tillerRoleConfigFile = "../roles/helmsman-tiller.yaml" +``` + +```yaml +namespaces: + dev: + useTiller: true + production: + installTiller: true + tillerServiceAccount: "tiller-production" + tillerRoleConfigFile: "../roles/helmsman-tiller.yaml" +``` + +The example above will create two namespaces: dev and production, where dev namespace will have its Tiller with default Role, +while production namespace will be managed by its specific Tiller having custom role based on the `"../roles/helmsman-tiller.yaml"` template created by you. From c6a3213031c02df3de0a2c88e0cbc8d6d06f647e Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Sat, 8 Jun 2019 12:01:34 +0200 Subject: [PATCH 0418/1127] Move docs on tillerRoleConfigFile from namespaces to tiller --- docs/how_to/README.md | 1 + .../custom_helmsman_role.md => tiller/custom_tiller_role.md} | 0 2 files changed, 1 insertion(+) rename docs/how_to/{namespaces/custom_helmsman_role.md => tiller/custom_tiller_role.md} (100%) diff --git a/docs/how_to/README.md b/docs/how_to/README.md index 7026ef15..5237ccb7 100644 --- a/docs/how_to/README.md +++ b/docs/how_to/README.md @@ -19,6 +19,7 @@ This page contains a list of guides on how to use Helmsman. - [Prevent Deploying Tiller in kube-system](tiller/prevent_tiller_in_kube_system.md) - [Deploy Multiple Tillers with custom setup for each](tiller/multitenancy.md) - [Deploy apps with specific Tillers](tiller/deploy_apps_with_specific_tiller.md) + - [Custom Tiller Role](tiller/custom_tiller_role.md) - Defining Helm repositories - [Using default helm repos](helm_repos/default.md) - [Using private repos in Google GCS](helm_repos/gcs.md) diff --git a/docs/how_to/namespaces/custom_helmsman_role.md b/docs/how_to/tiller/custom_tiller_role.md similarity index 100% rename from docs/how_to/namespaces/custom_helmsman_role.md rename to docs/how_to/tiller/custom_tiller_role.md From 7a6ba540a4a133eca7b7b0a62ba1ee18a34e6f96 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Sat, 8 Jun 2019 12:10:56 +0200 Subject: [PATCH 0419/1127] Fix tests after adding tillerRoleConfigFile --- release_test.go | 2 +- state_test.go | 36 ++++++++++++++++++------------------ 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/release_test.go b/release_test.go index 6f996e05..656f4222 100644 --- a/release_test.go +++ b/release_test.go @@ -10,7 +10,7 @@ func Test_validateRelease(t *testing.T) { Metadata: make(map[string]string), Certificates: make(map[string]string), Settings: (config{}), - Namespaces: map[string]namespace{"namespace": namespace{false, false, false, "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}}, + Namespaces: map[string]namespace{"namespace": namespace{false, false, false, "", "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}}, HelmRepos: make(map[string]string), Apps: make(map[string]*release), } diff --git a/state_test.go b/state_test.go index b6e648e3..daf07368 100644 --- a/state_test.go +++ b/state_test.go @@ -34,7 +34,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -58,7 +58,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -79,7 +79,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -103,7 +103,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -127,7 +127,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "$URI", // unset env }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -151,7 +151,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https//192.168.99.100:8443", // invalid url }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -174,7 +174,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -195,7 +195,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -219,7 +219,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -237,7 +237,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -287,7 +287,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, true, true, "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, true, true, "", "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -305,7 +305,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, true, "", "", "s3://some-bucket/12345.crt", "", "", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, true, "", "", "", "s3://some-bucket/12345.crt", "", "", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -323,7 +323,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, true, "", "", "", "", "", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, true, "", "", "", "", "", "", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -341,7 +341,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, true, false, "", "", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, true, false, "", "", "", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -359,7 +359,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: nil, Apps: make(map[string]*release), @@ -374,7 +374,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{}, Apps: make(map[string]*release), @@ -389,7 +389,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -407,7 +407,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", From 4b7388301ba3e258595434d37f06ac44723bf65f Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Sat, 8 Jun 2019 13:56:49 +0200 Subject: [PATCH 0420/1127] Rename tillerRoleConfigFile to tillerRoleTemplateFile --- docs/desired_state_specification.md | 8 +++---- docs/how_to/tiller/custom_tiller_role.md | 6 ++--- helm_helpers.go | 6 ++--- kube_helpers.go | 12 +++++----- namespace.go | 28 ++++++++++++------------ utils.go | 4 ++-- 6 files changed, 32 insertions(+), 32 deletions(-) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index c0f985d5..6fd637aa 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -146,7 +146,7 @@ Options: - **installTiller**: defines if Tiller should be deployed in this namespace or not. Default is false. Any chart desired to be deployed into a namespace with a Tiller deployed, will be deployed using that Tiller and not the one in kube-system unless you use the `TillerNamespace` option (see the [Apps](#apps) section below) to use another Tiller. > By default Tiller will be deployed into `kube-system` even if you don't define kube-system in the namespaces section. To prevent deploying Tiller into `kube-system, add kube-system in your namespaces section and set its installTiller to false. - **tillerRole**: specify the role to use. If 'cluster-admin' a clusterrolebinding will be used else a role with a single namespace scope will be created and bound with a rolebinding. -- **tillerRoleConfigFile**: relative path to file templating custom Tiller role. If `installTiller` is true and `tillerRole` is not `cluster-admin`, then helmsman will create namespace specific Tiller role based on the template file passed with this parameter. +- **tillerRoleTemplateFile**: relative path to file templating custom Tiller role. If `installTiller` is true and `tillerRole` is not `cluster-admin`, then helmsman will create namespace specific Tiller role based on the template file passed with this parameter. - **useTiller**: defines that you would like to use an existing Tiller from that namespace. Can't be set together with `installTiller` - **labels** : defines labels to be added to the namespace, doesn't remove existing labels but updates them if the label key exists with any other different value. You can define any key/value pairs. Default is empty. - **annotations** : defines annotations to be added to the namespace. It behaves the same way as the labels option. @@ -155,7 +155,7 @@ Options: - **tillerServiceAccount**: defines what service account to use when deploying Tiller. If this is not set, the following options are considered: 1. If the `serviceAccount` defined in the `settings` section exists in the namespace you want to deploy Tiller in, it will be used. - 2. If you defined `serviceAccount` in the `settings` section and it does not exist in the namespace you want to deploy Tiller in, Helmsman creates the service account in that namespace and binds it to a (cluster)role. If the namespace is kube-system and `tillerRole` is unset or is set to cluster-admin, the service account is bound to `cluster-admin` clusterrole. Otherwise, if you specified a `tillerRole`, a new role with that name is created and bound to the service account with rolebinding. If `tillerRole` is unset (for namespaces other than kube-system), the role is called `helmsman-tiller` and is created in the specified namespace to only gives access to that namespace. The custom role is created from a [yaml template](../data/role.yaml) if no `tillerRoleConfigFile` was set, or will use the template file (of the same structure as [yaml template](../data/role.yaml)) to create Role from. + 2. If you defined `serviceAccount` in the `settings` section and it does not exist in the namespace you want to deploy Tiller in, Helmsman creates the service account in that namespace and binds it to a (cluster)role. If the namespace is kube-system and `tillerRole` is unset or is set to cluster-admin, the service account is bound to `cluster-admin` clusterrole. Otherwise, if you specified a `tillerRole`, a new role with that name is created and bound to the service account with rolebinding. If `tillerRole` is unset (for namespaces other than kube-system), the role is called `helmsman-tiller` and is created in the specified namespace to only gives access to that namespace. The custom role is created from a [yaml template](../data/role.yaml) if no `tillerRoleTemplateFile` was set, or will use the template file (of the same structure as [yaml template](../data/role.yaml)) to create Role from. > If `installTiller` is not defined or set to false, this flag is ignored. @@ -181,7 +181,7 @@ protected = false protected = true installTiller = true tillerServiceAccount = "tiller-production" -tillerRoleConfigFile = "../roles/helmsman-tiller.yaml" +tillerRoleTemplateFile = "../roles/helmsman-tiller.yaml" caCert = "secrets/ca.cert.pem" tillerCert = "secrets/tiller.cert.pem" tillerKey = "$TILLER_KEY" # where TILLER_KEY=secrets/tiller.key.pem @@ -218,7 +218,7 @@ namespaces: protected: true installTiller: true tillerServiceAccount: "tiller-production" - tillerRoleConfigFile: "../roles/helmsman-tiller.yaml" + tillerRoleTemplateFile: "../roles/helmsman-tiller.yaml" caCert: "secrets/ca.cert.pem" tillerCert: "secrets/tiller.cert.pem" tillerKey: "$TILLER_KEY" # where TILLER_KEY=secrets/tiller.key.pem diff --git a/docs/how_to/tiller/custom_tiller_role.md b/docs/how_to/tiller/custom_tiller_role.md index 3f90b8e3..54d7aaf8 100644 --- a/docs/how_to/tiller/custom_tiller_role.md +++ b/docs/how_to/tiller/custom_tiller_role.md @@ -10,7 +10,7 @@ By default, when defining Namespaces in Desired State Specification file, when ` it creates the Role to bind the Tiller to with default [yaml template](../../../data/role.yaml). If there's a need for custom Role (let's say each namespace has its different and specific requirements to permissions), -you can define `tillerRoleConfigFile`, which is a relative path pointing at a template of a Role (same format as a [yaml template](../../../data/role.yaml)), +you can define `tillerRoleTemplateFile`, which is a relative path pointing at a template of a Role (same format as a [yaml template](../../../data/role.yaml)), so when Helmsman creates Tiller in the namespace with this key, custom Role will be created for Tiller. ```toml @@ -20,7 +20,7 @@ useTiller = true [namespaces.production] installTiller = true tillerServiceAccount = "tiller-production" -tillerRoleConfigFile = "../roles/helmsman-tiller.yaml" +tillerRoleTemplateFile = "../roles/helmsman-tiller.yaml" ``` ```yaml @@ -30,7 +30,7 @@ namespaces: production: installTiller: true tillerServiceAccount: "tiller-production" - tillerRoleConfigFile: "../roles/helmsman-tiller.yaml" + tillerRoleTemplateFile: "../roles/helmsman-tiller.yaml" ``` The example above will create two namespaces: dev and production, where dev namespace will have its Tiller with default Role, diff --git a/helm_helpers.go b/helm_helpers.go index e6d74f34..93871779 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -346,7 +346,7 @@ func addHelmRepos(repos map[string]string) (bool, string) { // If serviceAccount is not provided (empty string), the defaultServiceAccount is used. // If no defaultServiceAccount is provided, A service account is created and Tiller is deployed with the new service account // If no namespace is provided, Tiller is deployed to kube-system -func deployTiller(namespace string, serviceAccount string, defaultServiceAccount string, role string, roleConfigFile string) (bool, string) { +func deployTiller(namespace string, serviceAccount string, defaultServiceAccount string, role string, roleTemplateFile string) (bool, string) { log.Println("INFO: deploying Tiller in namespace [ " + namespace + " ].") sa := "" if serviceAccount != "" { @@ -354,7 +354,7 @@ func deployTiller(namespace string, serviceAccount string, defaultServiceAccount if strings.Contains(err, "NotFound") || strings.Contains(err, "not found") { log.Println("INFO: service account [ " + serviceAccount + " ] does not exist in namespace [ " + namespace + " ] .. attempting to create it ... ") - if _, rbacErr := createRBAC(serviceAccount, namespace, role, roleConfigFile); rbacErr != "" { + if _, rbacErr := createRBAC(serviceAccount, namespace, role, roleTemplateFile); rbacErr != "" { return false, rbacErr } } else { @@ -438,7 +438,7 @@ func initHelm() (bool, string) { downloadFile(s.Namespaces[k].ClientKey, k+"-client.key") } if ns.InstallTiller && k != "kube-system" { - if ok, err := deployTiller(k, ns.TillerServiceAccount, defaultSA, ns.TillerRole); !ok { + if ok, err := deployTiller(k, ns.TillerServiceAccount, defaultSA, ns.TillerRole, ns.TillerRoleTemplateFile); !ok { return false, err } } diff --git a/kube_helpers.go b/kube_helpers.go index aaa0df35..d81902b8 100644 --- a/kube_helpers.go +++ b/kube_helpers.go @@ -31,7 +31,7 @@ func validateServiceAccount(sa string, namespace string) (bool, string) { // createRBAC creates a k8s service account and bind it to a (Cluster)Role // role can be "cluster-admin" or any other custom name // It binds it to a new role called "helmsman-tiller" -func createRBAC(sa string, namespace string, role string, roleConfigFile string) (bool, string) { +func createRBAC(sa string, namespace string, role string, roleTemplateFile string) (bool, string) { var ok bool var err string if role == "" { @@ -48,7 +48,7 @@ func createRBAC(sa string, namespace string, role string, roleConfigFile string) } return false, err } - if ok, err = createRole(namespace, role, roleConfigFile); ok { + if ok, err = createRole(namespace, role, roleTemplateFile); ok { if ok, err = createRoleBinding(role, sa, namespace); ok { return true, "" } @@ -377,13 +377,13 @@ func createRoleBinding(role string, saName string, namespace string) (bool, stri } // createRole creates a k8s Role in a given namespace from a template -func createRole(namespace string, role string, roleConfigFile string) (bool, string) { +func createRole(namespace string, role string, roleTemplateFile string) (bool, string) { var resource []byte var e error - if roleConfigFile != "" { - // load file from path of TillerRoleConfigFile - resource, e = ioutil.ReadFile(roleConfigFile) + if roleTemplateFile != "" { + // load file from path of TillerRoleTemplateFile + resource, e = ioutil.ReadFile(roleTemplateFile) } else { // load static resource resource, e = Asset("data/role.yaml") diff --git a/namespace.go b/namespace.go index 7902360f..15fa882a 100644 --- a/namespace.go +++ b/namespace.go @@ -22,20 +22,20 @@ type limits []struct { // namespace type represents the fields of a namespace type namespace struct { - Protected bool `yaml:"protected"` - InstallTiller bool `yaml:"installTiller"` - UseTiller bool `yaml:"useTiller"` - TillerServiceAccount string `yaml:"tillerServiceAccount"` - TillerRole string `yaml:"tillerRole"` - TillerRoleConfigFile string `yaml:"tillerRoleConfigFile"` - CaCert string `yaml:"caCert"` - TillerCert string `yaml:"tillerCert"` - TillerKey string `yaml:"tillerKey"` - ClientCert string `yaml:"clientCert"` - ClientKey string `yaml:"clientKey"` - Limits limits `yaml:"limits,omitempty"` - Labels map[string]string `yaml:"labels"` - Annotations map[string]string `yaml:"annotations"` + Protected bool `yaml:"protected"` + InstallTiller bool `yaml:"installTiller"` + UseTiller bool `yaml:"useTiller"` + TillerServiceAccount string `yaml:"tillerServiceAccount"` + TillerRole string `yaml:"tillerRole"` + TillerRoleTemplateFile string `yaml:"tillerRoleTemplateFile"` + CaCert string `yaml:"caCert"` + TillerCert string `yaml:"tillerCert"` + TillerKey string `yaml:"tillerKey"` + ClientCert string `yaml:"clientCert"` + ClientKey string `yaml:"clientKey"` + Limits limits `yaml:"limits,omitempty"` + Labels map[string]string `yaml:"labels"` + Annotations map[string]string `yaml:"annotations"` } // checkNamespaceDefined checks if a given namespace is defined in the namespaces section of the desired state file diff --git a/utils.go b/utils.go index d10d63a3..d89440cb 100644 --- a/utils.go +++ b/utils.go @@ -237,8 +237,8 @@ func addDefaultHelmRepos(s *state) { func resolvePaths(relativeToFile string, s *state) { dir := filepath.Dir(relativeToFile) for ns, v := range s.Namespaces { - if v.TillerRoleConfigFile != "" { - v.TillerRoleConfigFile, _ = filepath.Abs(filepath.Join(dir, v.TillerRoleConfigFile)) + if v.TillerRoleTemplateFile != "" { + v.TillerRoleTemplateFile, _ = filepath.Abs(filepath.Join(dir, v.TillerRoleTemplateFile)) } s.Namespaces[ns] = v } From 7fc89ed5db554582c15cf63c9f4f507caf1d6c7f Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Sat, 8 Jun 2019 14:48:05 +0200 Subject: [PATCH 0421/1127] Fix specific cases for tillerRoleTemplateFile --- docs/desired_state_specification.md | 9 +++++++-- helm_helpers.go | 24 +++++++++++++++++++++--- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 6fd637aa..7518daee 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -146,7 +146,12 @@ Options: - **installTiller**: defines if Tiller should be deployed in this namespace or not. Default is false. Any chart desired to be deployed into a namespace with a Tiller deployed, will be deployed using that Tiller and not the one in kube-system unless you use the `TillerNamespace` option (see the [Apps](#apps) section below) to use another Tiller. > By default Tiller will be deployed into `kube-system` even if you don't define kube-system in the namespaces section. To prevent deploying Tiller into `kube-system, add kube-system in your namespaces section and set its installTiller to false. - **tillerRole**: specify the role to use. If 'cluster-admin' a clusterrolebinding will be used else a role with a single namespace scope will be created and bound with a rolebinding. -- **tillerRoleTemplateFile**: relative path to file templating custom Tiller role. If `installTiller` is true and `tillerRole` is not `cluster-admin`, then helmsman will create namespace specific Tiller role based on the template file passed with this parameter. +- **tillerRoleTemplateFile**: relative path to file templating custom Tiller role. If `installTiller` is true and `tillerRole` is not `cluster-admin`, then helmsman will create namespace specific Tiller role based on the template file passed with this parameter. When `tillerRole` is empty string, role name defaults to `helmsman-tiller`. + + If `tillerRoleTemplateFile` is set, it will always try to create namespace-scoped Tiller with conditions like below: + - if `tillerRole` was not set, default name of `helmsman-tiller` will be used to createa new Role + - if `tillerServiceAccount` was not set, it will create Service Account with default name of `helmsman` + - **useTiller**: defines that you would like to use an existing Tiller from that namespace. Can't be set together with `installTiller` - **labels** : defines labels to be added to the namespace, doesn't remove existing labels but updates them if the label key exists with any other different value. You can define any key/value pairs. Default is empty. - **annotations** : defines annotations to be added to the namespace. It behaves the same way as the labels option. @@ -155,7 +160,7 @@ Options: - **tillerServiceAccount**: defines what service account to use when deploying Tiller. If this is not set, the following options are considered: 1. If the `serviceAccount` defined in the `settings` section exists in the namespace you want to deploy Tiller in, it will be used. - 2. If you defined `serviceAccount` in the `settings` section and it does not exist in the namespace you want to deploy Tiller in, Helmsman creates the service account in that namespace and binds it to a (cluster)role. If the namespace is kube-system and `tillerRole` is unset or is set to cluster-admin, the service account is bound to `cluster-admin` clusterrole. Otherwise, if you specified a `tillerRole`, a new role with that name is created and bound to the service account with rolebinding. If `tillerRole` is unset (for namespaces other than kube-system), the role is called `helmsman-tiller` and is created in the specified namespace to only gives access to that namespace. The custom role is created from a [yaml template](../data/role.yaml) if no `tillerRoleTemplateFile` was set, or will use the template file (of the same structure as [yaml template](../data/role.yaml)) to create Role from. + 2. If you defined `serviceAccount` in the `settings` section and it does not exist in the namespace you want to deploy Tiller in, Helmsman creates the service account in that namespace and binds it to a (cluster)role. If the namespace is kube-system and `tillerRole` is unset or is set to cluster-admin, the service account is bound to `cluster-admin` clusterrole. Otherwise, if you specified a `tillerRole`, a new role with that name is created and bound to the service account with rolebinding. If `tillerRole` is unset (for namespaces other than kube-system), the role is called `helmsman-tiller` and is created in the specified namespace to only gives access to that namespace. The custom role is created from a [yaml template](../data/role.yaml) if no `tillerRoleTemplateFile` was set, or it will use the `tillerRoleTemplateFile` (of the same structure as [yaml template](../data/role.yaml)) to create Role from. > If `installTiller` is not defined or set to false, this flag is ignored. diff --git a/helm_helpers.go b/helm_helpers.go index 93871779..5e6b82aa 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -362,12 +362,30 @@ func deployTiller(namespace string, serviceAccount string, defaultServiceAccount } } sa = "--service-account " + serviceAccount + } else if serviceAccount == "" && roleTemplateFile != "" { + roleName := "helmsman-tiller" + defaultServiceAccountName := "helmsman" + if role != "" { + roleName = role + } + if ok, err := validateServiceAccount(defaultServiceAccountName, namespace); !ok { + if strings.Contains(err, "NotFound") || strings.Contains(err, "not found") { + + log.Println("INFO: service account [ " + defaultServiceAccountName + " ] does not exist in namespace [ " + namespace + " ] .. attempting to create it ... ") + if _, rbacErr := createRBAC(defaultServiceAccountName, namespace, roleName, roleTemplateFile); rbacErr != "" { + return false, rbacErr + } + } else { + return false, "ERROR: while validating/creating service account [ " + defaultServiceAccountName + " ] in namespace [" + namespace + "]: " + err + } + } + sa = "--service-account " + defaultServiceAccountName } else if defaultServiceAccount != "" { if ok, err := validateServiceAccount(defaultServiceAccount, namespace); !ok { if strings.Contains(err, "NotFound") || strings.Contains(err, "not found") { log.Println("INFO: service account [ " + defaultServiceAccount + " ] does not exist in namespace [ " + namespace + " ] .. attempting to create it ... ") - if _, rbacErr := createRBAC(defaultServiceAccount, namespace, role, ""); rbacErr != "" { + if _, rbacErr := createRBAC(defaultServiceAccount, namespace, role, roleTemplateFile); rbacErr != "" { return false, rbacErr } } else { @@ -446,12 +464,12 @@ func initHelm() (bool, string) { if ns, ok := s.Namespaces["kube-system"]; ok { if ns.InstallTiller { - if ok, err := deployTiller("kube-system", ns.TillerServiceAccount, defaultSA, ns.TillerRole, ""); !ok { + if ok, err := deployTiller("kube-system", ns.TillerServiceAccount, defaultSA, ns.TillerRole, ns.TillerRoleTemplateFile); !ok { return false, err } } } else { - if ok, err := deployTiller("kube-system", "", defaultSA, ns.TillerRole, ""); !ok { + if ok, err := deployTiller("kube-system", "", defaultSA, ns.TillerRole, ns.TillerRoleTemplateFile); !ok { return false, err } } From 604256c17fd3d5b5230b625ff2e136829a48fef1 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Mon, 24 Jun 2019 11:45:37 +0200 Subject: [PATCH 0422/1127] Consider defaultServiceAccount as a defaultServiceAccountName for custom TillerRoleTemplateFile creation --- helm_helpers.go | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/helm_helpers.go b/helm_helpers.go index 5e6b82aa..394b81f8 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -362,12 +362,17 @@ func deployTiller(namespace string, serviceAccount string, defaultServiceAccount } } sa = "--service-account " + serviceAccount - } else if serviceAccount == "" && roleTemplateFile != "" { + } else { roleName := "helmsman-tiller" defaultServiceAccountName := "helmsman" + + if defaultServiceAccount != "" { + defaultServiceAccountName = defaultServiceAccount + } if role != "" { roleName = role } + if ok, err := validateServiceAccount(defaultServiceAccountName, namespace); !ok { if strings.Contains(err, "NotFound") || strings.Contains(err, "not found") { @@ -380,21 +385,7 @@ func deployTiller(namespace string, serviceAccount string, defaultServiceAccount } } sa = "--service-account " + defaultServiceAccountName - } else if defaultServiceAccount != "" { - if ok, err := validateServiceAccount(defaultServiceAccount, namespace); !ok { - if strings.Contains(err, "NotFound") || strings.Contains(err, "not found") { - - log.Println("INFO: service account [ " + defaultServiceAccount + " ] does not exist in namespace [ " + namespace + " ] .. attempting to create it ... ") - if _, rbacErr := createRBAC(defaultServiceAccount, namespace, role, roleTemplateFile); rbacErr != "" { - return false, rbacErr - } - } else { - return false, "ERROR: while validating/creating service account [ " + defaultServiceAccount + " ] in namespace [ " + namespace + "]: " + err - } - } - sa = "--service-account " + defaultServiceAccount } - if namespace == "" { namespace = "kube-system" } From ada4c898c1971cdaa14746ead2de9f6c85e5c07b Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Wed, 26 Jun 2019 14:17:39 +0200 Subject: [PATCH 0423/1127] qoute chart path when validating. fixes #247 --- helm_helpers.go | 2 +- helm_helpers_test.go | 163 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 164 insertions(+), 1 deletion(-) diff --git a/helm_helpers.go b/helm_helpers.go index 394b81f8..e2d44cd1 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -229,7 +229,7 @@ func validateReleaseCharts(apps map[string]*release) (bool, string) { if isLocal { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm inspect chart " + r.Chart}, + Args: []string{"-c", "helm inspect chart '" + r.Chart + "'"}, Description: "validating if chart at " + r.Chart + " is available.", } diff --git a/helm_helpers_test.go b/helm_helpers_test.go index 028ab455..a4a043c0 100644 --- a/helm_helpers_test.go +++ b/helm_helpers_test.go @@ -1,10 +1,173 @@ package main import ( + "os" "testing" "time" ) +func setupTestCase(t *testing.T) func(t *testing.T) { + t.Log("setup test case") + os.MkdirAll("/tmp/helmsman-tests/myapp", os.ModePerm) + os.MkdirAll("/tmp/helmsman-tests/dir-with space/myapp", os.ModePerm) + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm create '/tmp/helmsman-tests/dir-with space/myapp'"}, + Description: "creating an empty local chart directory", + } + if exitCode, msg := cmd.exec(debug, verbose); exitCode != 0 { + logError("Command returned with exit code: " + string(exitCode) + ". And error message: " + msg) + } + + return func(t *testing.T) { + t.Log("teardown test case") + //os.RemoveAll("/tmp/helmsman-tests/") + } +} +func Test_validateReleaseCharts(t *testing.T) { + type args struct { + apps map[string]*release + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "test case 1: valid local path with no chart", + args: args{ + apps: map[string]*release{ + "app": &release{ + Name: "", + Description: "", + Namespace: "", + Enabled: true, + Chart: "/tmp/helmsman-tests/myapp", + Version: "", + ValuesFile: "", + ValuesFiles: []string{}, + SecretsFile: "", + SecretsFiles: []string{}, + Purge: false, + Test: false, + Protected: false, + Wait: false, + Priority: 0, + TillerNamespace: "", + Set: make(map[string]string), + SetString: make(map[string]string), + HelmFlags: []string{}, + NoHooks: false, + Timeout: 0, + }, + }, + }, + want: false, + }, { + name: "test case 2: invalid local path", + args: args{ + apps: map[string]*release{ + "app": &release{ + Name: "", + Description: "", + Namespace: "", + Enabled: true, + Chart: "/tmp/does-not-exist/myapp", + Version: "", + ValuesFile: "", + ValuesFiles: []string{}, + SecretsFile: "", + SecretsFiles: []string{}, + Purge: false, + Test: false, + Protected: false, + Wait: false, + Priority: 0, + TillerNamespace: "", + Set: make(map[string]string), + SetString: make(map[string]string), + HelmFlags: []string{}, + NoHooks: false, + Timeout: 0, + }, + }, + }, + want: false, + }, { + name: "test case 3: valid chart local path with whitespace", + args: args{ + apps: map[string]*release{ + "app": &release{ + Name: "", + Description: "", + Namespace: "", + Enabled: true, + Chart: "/tmp/helmsman-tests/dir-with space/myapp", + Version: "0.1.0", + ValuesFile: "", + ValuesFiles: []string{}, + SecretsFile: "", + SecretsFiles: []string{}, + Purge: false, + Test: false, + Protected: false, + Wait: false, + Priority: 0, + TillerNamespace: "", + Set: make(map[string]string), + SetString: make(map[string]string), + HelmFlags: []string{}, + NoHooks: false, + Timeout: 0, + }, + }, + }, + want: true, + }, { + name: "test case 4: valid chart from repo", + args: args{ + apps: map[string]*release{ + "app": &release{ + Name: "", + Description: "", + Namespace: "", + Enabled: true, + Chart: "stable/prometheus", + Version: "", + ValuesFile: "", + ValuesFiles: []string{}, + SecretsFile: "", + SecretsFiles: []string{}, + Purge: false, + Test: false, + Protected: false, + Wait: false, + Priority: 0, + TillerNamespace: "", + Set: make(map[string]string), + SetString: make(map[string]string), + HelmFlags: []string{}, + NoHooks: false, + Timeout: 0, + }, + }, + }, + want: true, + }, + } + + teardownTestCase := setupTestCase(t) + defer teardownTestCase(t) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + if got, msg := validateReleaseCharts(tt.args.apps); got != tt.want { + t.Errorf("getReleaseChartName() = %v, want %v , msg: %v", got, tt.want, msg) + } + }) + } +} + func Test_getReleaseChartVersion(t *testing.T) { // version string = the first semver-valid string after the last hypen in the chart string. From a66b61740baa5edb13c10ef0241229bdf4466f2b Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Wed, 26 Jun 2019 14:53:56 +0200 Subject: [PATCH 0424/1127] initializing helm client before running tests --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index da71dfd6..3b533ffa 100644 --- a/Makefile +++ b/Makefile @@ -81,6 +81,7 @@ check: $(SRCDIR) test: dep ## Run unit tests @cd $(SRCDIR)helmsman && \ + helm init --client-only && \ go test -v -cover -p=1 -args -f example.toml .PHONY: test From 079f566d0a26e1decc2558493f26a9233f8eb39c Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Wed, 26 Jun 2019 15:12:18 +0200 Subject: [PATCH 0425/1127] releasing v1.10.0 --- README.md | 6 +++--- main.go | 2 +- release-notes.md | 12 ++++++++++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8aafddee..a2c0b203 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v1.9.1&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v1.10.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -59,9 +59,9 @@ If you use private helm repos, you will need either `helm-gcs` or `helm-s3` plug Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. ``` # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v1.9.1/helmsman_1.9.1_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.10.0/helmsman_1.10.0_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v1.9.1/helmsman_1.9.1_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.10.0/helmsman_1.10.0_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/main.go b/main.go index ea3896c0..ae2260dd 100644 --- a/main.go +++ b/main.go @@ -33,7 +33,7 @@ var nsOverride string var skipValidation bool var applyLabels bool var keepUntrackedReleases bool -var appVersion = "v1.9.1" +var appVersion = "v1.10.0" var helmVersion string var kubectlVersion string var dryRun bool diff --git a/release-notes.md b/release-notes.md index 5bbc62ee..83609ec9 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,8 +1,16 @@ -# v1.9.1 +# v1.10.0 > If you are already using an older version of Helmsman than v1.4.0-rc, please read the changes below carefully and follow the upgrade guide [here](docs/migrating_to_v1.4.0-rc.md) # Fixes and improvements: -- Improving temporary files handling. PR #242 (thanks to @brndnmtthws) +- Allow local chart paths to contain white spaces. PR #262 +- Add helm flags to helm diff. PR #252 (thanks to @xaka) +- Fix deleting untracked releases if `-target` flag is set. PR #248 (thanks to @fmotrifork) + +# New features: +- Add global toggle `--no-env-subst` to be able to disable environment substitution. PR #249 (thanks to @hatemosphere) +- Add `--diff-context` flag to set lines of diff context. PR #251 (thanks to @lachlancooper) +- Allow deploying Tiller with custom Role. PR #258 (thanks to @mkubaczyk) +- Support Tillerless mode on Helm 2 using helm-tiller plugin. PR #259 (thanks to @mkubaczyk and @robbert229) From 6b91ad8b6b39fefbdfb7c7342b0fecc127541f93 Mon Sep 17 00:00:00 2001 From: Lachlan Cooper Date: Thu, 27 Jun 2019 11:58:21 +1000 Subject: [PATCH 0426/1127] Fix bug with diff-context flag diffRelease() expects its flags to end with whitespace, which diffContextFlag does not. This bug prevents the flag from working at all. A less brittle approach generally would be to pass all flags as separate elements of the Args field, leaving them to be joined by cmd.exec(). This would however result in variable numbers of spaces depending on which flags are enabled. --- decision_maker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/decision_maker.go b/decision_maker.go index 3b7cbd4a..983f98ad 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -255,7 +255,7 @@ func diffRelease(r *release) string { colorFlag = "--no-color " } if diffContext != -1 { - diffContextFlag = "--context " + strconv.Itoa(diffContext) + diffContextFlag = "--context " + strconv.Itoa(diffContext) + " " } if suppressDiffSecrets { suppressDiffSecretsFlag = "--suppress-secrets " From ab8cc8518f0fe8581cbf07de12c68a85c5de3ea6 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Thu, 27 Jun 2019 16:35:26 +0200 Subject: [PATCH 0427/1127] Remove getHelmFlags from diffRelease func, add getHelmFlags to install/reinstall/deleteRelease --- decision_maker.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index 3b7cbd4a..e558507b 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -137,7 +137,7 @@ func installRelease(r *release) { cmd := command{ Cmd: "bash", - Args: []string{"-c", helmCommandFromConfig(r) + " install " + r.Chart + " -n " + r.Name + " --namespace " + r.Namespace + getValuesFiles(r) + " --version " + r.Version + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r) + getDryRunFlags()}, + Args: []string{"-c", helmCommandFromConfig(r) + " install " + r.Chart + " -n " + r.Name + " --namespace " + r.Namespace + getValuesFiles(r) + " --version " + r.Version + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r) + getDryRunFlags() + getHelmFlags(r)}, Description: "installing release [ " + r.Name + " ] in namespace [[ " + r.Namespace + " ]] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } outcome.addCommand(cmd, r.Priority, r) @@ -263,7 +263,7 @@ func diffRelease(r *release) string { cmd := command{ Cmd: "bash", - Args: []string{"-c", helmCommandFromConfig(r) + " diff " + colorFlag + diffContextFlag + suppressDiffSecretsFlag + "upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + strconv.Quote(r.Version) + " " + getSetValues(r) + getSetStringValues(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getHelmFlags(r)}, + Args: []string{"-c", helmCommandFromConfig(r) + " diff " + colorFlag + diffContextFlag + suppressDiffSecretsFlag + "upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + strconv.Quote(r.Version) + " " + getSetValues(r) + getSetStringValues(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r)}, Description: "diffing release [ " + r.Name + " ] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } @@ -282,7 +282,7 @@ func diffRelease(r *release) string { func upgradeRelease(r *release) { cmd := command{ Cmd: "bash", - Args: []string{"-c", helmCommandFromConfig(r) + " upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + strconv.Quote(r.Version) + " --force " + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r) + getDryRunFlags()}, + Args: []string{"-c", helmCommandFromConfig(r) + " upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + strconv.Quote(r.Version) + " --force " + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r) + getDryRunFlags() + getHelmFlags(r)}, Description: "upgrading release [ " + r.Name + " ] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } @@ -302,7 +302,7 @@ func reInstallRelease(r *release, rs releaseState) { installCmd := command{ Cmd: "bash", - Args: []string{"-c", helmCommandFromConfig(r) + " install " + r.Chart + " --version " + r.Version + " -n " + r.Name + " --namespace " + r.Namespace + getValuesFiles(r) + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r) + getDryRunFlags()}, + Args: []string{"-c", helmCommandFromConfig(r) + " install " + r.Chart + " --version " + r.Version + " -n " + r.Name + " --namespace " + r.Namespace + getValuesFiles(r) + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r) + getDryRunFlags() + getHelmFlags(r)}, Description: "installing release [ " + r.Name + " ] in namespace [[ " + r.Namespace + " ]] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } outcome.addCommand(installCmd, r.Priority, r) From 1ed9171fa3ba9db6dd7b79a03d67ff8f55c1f932 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Fri, 28 Jun 2019 16:15:10 +0200 Subject: [PATCH 0428/1127] Remove explicit flags from release commands and use getHelmFlags for it --- decision_maker.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index e558507b..7a5b3925 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -137,7 +137,7 @@ func installRelease(r *release) { cmd := command{ Cmd: "bash", - Args: []string{"-c", helmCommandFromConfig(r) + " install " + r.Chart + " -n " + r.Name + " --namespace " + r.Namespace + getValuesFiles(r) + " --version " + r.Version + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r) + getDryRunFlags() + getHelmFlags(r)}, + Args: []string{"-c", helmCommandFromConfig(r) + " install " + r.Chart + " -n " + r.Name + " --namespace " + r.Namespace + getValuesFiles(r) + " --version " + r.Version + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getHelmFlags(r)}, Description: "installing release [ " + r.Name + " ] in namespace [[ " + r.Namespace + " ]] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } outcome.addCommand(cmd, r.Priority, r) @@ -282,7 +282,7 @@ func diffRelease(r *release) string { func upgradeRelease(r *release) { cmd := command{ Cmd: "bash", - Args: []string{"-c", helmCommandFromConfig(r) + " upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + strconv.Quote(r.Version) + " --force " + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r) + getDryRunFlags() + getHelmFlags(r)}, + Args: []string{"-c", helmCommandFromConfig(r) + " upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + strconv.Quote(r.Version) + " --force " + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getHelmFlags(r)}, Description: "upgrading release [ " + r.Name + " ] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } @@ -302,7 +302,7 @@ func reInstallRelease(r *release, rs releaseState) { installCmd := command{ Cmd: "bash", - Args: []string{"-c", helmCommandFromConfig(r) + " install " + r.Chart + " --version " + r.Version + " -n " + r.Name + " --namespace " + r.Namespace + getValuesFiles(r) + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r) + getDryRunFlags() + getHelmFlags(r)}, + Args: []string{"-c", helmCommandFromConfig(r) + " install " + r.Chart + " --version " + r.Version + " -n " + r.Name + " --namespace " + r.Namespace + getValuesFiles(r) + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getHelmFlags(r)}, Description: "installing release [ " + r.Name + " ] in namespace [[ " + r.Namespace + " ]] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } outcome.addCommand(installCmd, r.Priority, r) From 394fb4870f7b976f1771a24dd991fee12ed9e6fe Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Sun, 30 Jun 2019 14:27:32 +0200 Subject: [PATCH 0429/1127] releasing v1.10.1 --- README.md | 6 +++--- docs/desired_state_specification.md | 2 +- main.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a2c0b203..b53894a7 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v1.10.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v1.10.1&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -59,9 +59,9 @@ If you use private helm repos, you will need either `helm-gcs` or `helm-s3` plug Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. ``` # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v1.10.0/helmsman_1.10.0_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.10.0/helmsman_1.10.1_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v1.10.0/helmsman_1.10.0_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.10.0/helmsman_1.10.1_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 7518daee..91218f34 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -1,5 +1,5 @@ --- -version: v1.10.0 +version: v1.10.1 --- # Helmsman desired state specification diff --git a/main.go b/main.go index ae2260dd..663b872e 100644 --- a/main.go +++ b/main.go @@ -33,7 +33,7 @@ var nsOverride string var skipValidation bool var applyLabels bool var keepUntrackedReleases bool -var appVersion = "v1.10.0" +var appVersion = "v1.10.1" var helmVersion string var kubectlVersion string var dryRun bool From 2442087add74250823e93a38859e6775ea0478c3 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Thu, 11 Jul 2019 12:13:22 +0200 Subject: [PATCH 0430/1127] Add start & finish apply messages for each chart --- main.go | 2 +- plan.go | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index 663b872e..f5a85e58 100644 --- a/main.go +++ b/main.go @@ -121,7 +121,7 @@ func main() { p.execPlan() } - log.Println("INFO: completed successfully!") + log.Println("INFO: completed applying plan successfully!") } // cleanup deletes the k8s certificates and keys files diff --git a/plan.go b/plan.go index ea105934..ef7a0e14 100644 --- a/plan.go +++ b/plan.go @@ -92,6 +92,8 @@ func (p plan) execPlan() { } for _, cmd := range p.Commands { + log.Println("INFO: start applying [ " + cmd.targetRelease.Name + " ] " + + "in namespace [ " + cmd.targetRelease.Namespace + " ] ") if exitCode, msg := cmd.Command.exec(debug, verbose); exitCode != 0 { var errorMsg string if errorMsg = msg; !verbose { @@ -103,6 +105,8 @@ func (p plan) execPlan() { if cmd.targetRelease != nil && !dryRun { labelResource(cmd.targetRelease) } + log.Println("INFO: finished applying [ " + cmd.targetRelease.Name + " ] " + + "in namespace [ " + cmd.targetRelease.Namespace + " ] ") if _, err := url.ParseRequestURI(s.Settings.SlackWebhook); err == nil { notifySlack(cmd.Command.Description+" ... SUCCESS!", s.Settings.SlackWebhook, false, true) } From 83249f714af892262a138b41c18523483eda8e5d Mon Sep 17 00:00:00 2001 From: amir Date: Thu, 18 Jul 2019 13:16:48 +0430 Subject: [PATCH 0431/1127] Fix helmsman binary path --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b53894a7..e1aa57be 100644 --- a/README.md +++ b/README.md @@ -59,9 +59,9 @@ If you use private helm repos, you will need either `helm-gcs` or `helm-s3` plug Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. ``` # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v1.10.0/helmsman_1.10.1_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.10.1/helmsman_1.10.1_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v1.10.0/helmsman_1.10.1_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.10.1/helmsman_1.10.1_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` From 676f61046a3aa52a3a8edc941c727b658942f0d4 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Thu, 18 Jul 2019 14:40:08 +0200 Subject: [PATCH 0432/1127] Consider -target flag while validating helm charts --- helm_helpers.go | 61 +++++++++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/helm_helpers.go b/helm_helpers.go index e2d44cd1..8a3bc67f 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -224,38 +224,45 @@ func validateReleaseCharts(apps map[string]*release) (bool, string) { versionExtractor := regexp.MustCompile(`version:\s?(.*)`) for app, r := range apps { - - isLocal := filepath.IsAbs(r.Chart) - if isLocal { - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm inspect chart '" + r.Chart + "'"}, - Description: "validating if chart at " + r.Chart + " is available.", + validateCurrentChart := true + if len(targetMap) > 0 { + if _, ok := targetMap[r.Name]; !ok { + validateCurrentChart = false } + } + if validateCurrentChart { + isLocal := filepath.IsAbs(r.Chart) + if isLocal { + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm inspect chart '" + r.Chart + "'"}, + Description: "validating if chart at " + r.Chart + " is available.", + } - if exitCode, output := cmd.exec(debug, verbose); exitCode != 0 { - maybeRepo := filepath.Base(filepath.Dir(r.Chart)) - return false, "ERROR: chart at " + r.Chart + " for app [" + app + "] could not be found. Did you mean to add a repo named '" + maybeRepo + "'?" - } else { - matches := versionExtractor.FindStringSubmatch(output) - if len(matches) == 2 { - version := matches[1] - if r.Version != version { - return false, "ERROR: chart " + r.Chart + " with version " + r.Version + " is specified for " + - "app [" + app + "] but the chart found at that path has version " + version + " which does not match." + if exitCode, output := cmd.exec(debug, verbose); exitCode != 0 { + maybeRepo := filepath.Base(filepath.Dir(r.Chart)) + return false, "ERROR: chart at " + r.Chart + " for app [" + app + "] could not be found. Did you mean to add a repo named '" + maybeRepo + "'?" + } else { + matches := versionExtractor.FindStringSubmatch(output) + if len(matches) == 2 { + version := matches[1] + if r.Version != version { + return false, "ERROR: chart " + r.Chart + " with version " + r.Version + " is specified for " + + "app [" + app + "] but the chart found at that path has version " + version + " which does not match." + } } } - } - } else { - cmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm search " + r.Chart + " --version " + strconv.Quote(r.Version) + " -l"}, - Description: "validating if chart " + r.Chart + "-" + r.Version + " is available in the defined repos.", - } + } else { + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm search " + r.Chart + " --version " + strconv.Quote(r.Version) + " -l"}, + Description: "validating if chart " + r.Chart + "-" + r.Version + " is available in the defined repos.", + } - if exitCode, result := cmd.exec(debug, verbose); exitCode != 0 || strings.Contains(result, "No results found") { - return false, "ERROR: chart " + r.Chart + "-" + r.Version + " is specified for " + - "app [" + app + "] but is not found in the defined repos." + if exitCode, result := cmd.exec(debug, verbose); exitCode != 0 || strings.Contains(result, "No results found") { + return false, "ERROR: chart " + r.Chart + "-" + r.Version + " is specified for " + + "app [" + app + "] but is not found in the defined repos." + } } } From e5a4f80edbc47a45cc1f15df15f200a9b83d1ddc Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Mon, 22 Jul 2019 11:42:01 +0200 Subject: [PATCH 0433/1127] Fix validateReleaseCharts tests --- helm_helpers_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/helm_helpers_test.go b/helm_helpers_test.go index a4a043c0..55a200e3 100644 --- a/helm_helpers_test.go +++ b/helm_helpers_test.go @@ -30,11 +30,13 @@ func Test_validateReleaseCharts(t *testing.T) { } tests := []struct { name string + targetFlag []string args args want bool }{ { name: "test case 1: valid local path with no chart", + targetFlag: []string{}, args: args{ apps: map[string]*release{ "app": &release{ @@ -65,6 +67,7 @@ func Test_validateReleaseCharts(t *testing.T) { want: false, }, { name: "test case 2: invalid local path", + targetFlag: []string{}, args: args{ apps: map[string]*release{ "app": &release{ @@ -95,6 +98,7 @@ func Test_validateReleaseCharts(t *testing.T) { want: false, }, { name: "test case 3: valid chart local path with whitespace", + targetFlag: []string{}, args: args{ apps: map[string]*release{ "app": &release{ @@ -125,6 +129,7 @@ func Test_validateReleaseCharts(t *testing.T) { want: true, }, { name: "test case 4: valid chart from repo", + targetFlag: []string{}, args: args{ apps: map[string]*release{ "app": &release{ @@ -160,7 +165,11 @@ func Test_validateReleaseCharts(t *testing.T) { defer teardownTestCase(t) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + targetMap = make(map[string]bool) + for _, target := range tt.targetFlag { + targetMap[target] = true + } if got, msg := validateReleaseCharts(tt.args.apps); got != tt.want { t.Errorf("getReleaseChartName() = %v, want %v , msg: %v", got, tt.want, msg) } From 2e9af3af70330fe46c9d62389a52da1f6df4b6c3 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Mon, 22 Jul 2019 11:47:58 +0200 Subject: [PATCH 0434/1127] Add tests to charts validation while -target flag is used --- helm_helpers_test.go | 62 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/helm_helpers_test.go b/helm_helpers_test.go index 55a200e3..d81d1400 100644 --- a/helm_helpers_test.go +++ b/helm_helpers_test.go @@ -158,6 +158,68 @@ func Test_validateReleaseCharts(t *testing.T) { }, }, want: true, + }, { + name: "test case 5: invalid local path for chart ignored with -target flag, while other app was targeted", + targetFlag: []string{"notThisOne"}, + args: args{ + apps: map[string]*release{ + "app": &release{ + Name: "app", + Description: "", + Namespace: "", + Enabled: true, + Chart: "/tmp/does-not-exist/myapp", + Version: "", + ValuesFile: "", + ValuesFiles: []string{}, + SecretsFile: "", + SecretsFiles: []string{}, + Purge: false, + Test: false, + Protected: false, + Wait: false, + Priority: 0, + TillerNamespace: "", + Set: make(map[string]string), + SetString: make(map[string]string), + HelmFlags: []string{}, + NoHooks: false, + Timeout: 0, + }, + }, + }, + want: true, + }, { + name: "test case 6: invalid local path for chart included with -target flag", + targetFlag: []string{"app"}, + args: args{ + apps: map[string]*release{ + "app": &release{ + Name: "app", + Description: "", + Namespace: "", + Enabled: true, + Chart: "/tmp/does-not-exist/myapp", + Version: "", + ValuesFile: "", + ValuesFiles: []string{}, + SecretsFile: "", + SecretsFiles: []string{}, + Purge: false, + Test: false, + Protected: false, + Wait: false, + Priority: 0, + TillerNamespace: "", + Set: make(map[string]string), + SetString: make(map[string]string), + HelmFlags: []string{}, + NoHooks: false, + Timeout: 0, + }, + }, + }, + want: false, }, } From c4eec82cd44c91470473ad294e04dc9a97a1a640 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Fri, 26 Jul 2019 11:59:34 +0200 Subject: [PATCH 0435/1127] fix helm init happens before overriding stable repo fixes #245 --- helm_helpers.go | 21 +++++++++++---------- main.go | 14 +++++++------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/helm_helpers.go b/helm_helpers.go index 8a3bc67f..1f9959cc 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -239,19 +239,21 @@ func validateReleaseCharts(apps map[string]*release) (bool, string) { Description: "validating if chart at " + r.Chart + " is available.", } - if exitCode, output := cmd.exec(debug, verbose); exitCode != 0 { + var output string + var exitCode int + if exitCode, output = cmd.exec(debug, verbose); exitCode != 0 { maybeRepo := filepath.Base(filepath.Dir(r.Chart)) return false, "ERROR: chart at " + r.Chart + " for app [" + app + "] could not be found. Did you mean to add a repo named '" + maybeRepo + "'?" - } else { - matches := versionExtractor.FindStringSubmatch(output) - if len(matches) == 2 { - version := matches[1] - if r.Version != version { - return false, "ERROR: chart " + r.Chart + " with version " + r.Version + " is specified for " + - "app [" + app + "] but the chart found at that path has version " + version + " which does not match." - } + } + matches := versionExtractor.FindStringSubmatch(output) + if len(matches) == 2 { + version := matches[1] + if r.Version != version { + return false, "ERROR: chart " + r.Chart + " with version " + r.Version + " is specified for " + + "app [" + app + "] but the chart found at that path has version " + version + " which does not match." } } + } else { cmd := command{ Cmd: "bash", @@ -441,7 +443,6 @@ func initHelmClientOnly() (bool, string) { // initHelm initializes helm on a k8s cluster and deploys Tiller in one or more namespaces func initHelm() (bool, string) { - initHelmClientOnly() defaultSA := s.Settings.ServiceAccount if !s.Settings.Tillerless { for k, ns := range s.Namespaces { diff --git a/main.go b/main.go index f5a85e58..30b2f7ca 100644 --- a/main.go +++ b/main.go @@ -61,6 +61,13 @@ func main() { } } + // init helm client before adding repos + initHelmClientOnly() + // add repos -- fails if they are not valid + if r, msg := addHelmRepos(s.HelmRepos); !r { + logError(msg) + } + if apply || dryRun || destroy { // add/validate namespaces if !noNs { @@ -85,13 +92,6 @@ func main() { } else { log.Println("INFO: running in TILLERLESS mode") } - } else { - initHelmClientOnly() - } - - // add repos -- fails if they are not valid - if r, msg := addHelmRepos(s.HelmRepos); !r { - logError(msg) } if !skipValidation { From dbd45642f0e70cde97c2fde4fd4d4796bbff8a46 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Fri, 26 Jul 2019 13:32:33 +0200 Subject: [PATCH 0436/1127] add --update-deps option --- decision_maker.go | 13 +++++++++++++ helm_helpers.go | 19 +++++++++++++++++-- init.go | 2 +- main.go | 1 + utils.go | 5 +++++ 5 files changed, 37 insertions(+), 3 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index caa28dab..5a31cd31 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -134,6 +134,7 @@ func testRelease(r *release) { // installRelease creates a Helm command to install a particular release in a particular namespace using a particular Tiller. func installRelease(r *release) { + checkChartDepUpdate(r) cmd := command{ Cmd: "bash", @@ -280,6 +281,8 @@ func diffRelease(r *release) string { // upgradeRelease upgrades an existing release with the specified values.yaml func upgradeRelease(r *release) { + checkChartDepUpdate(r) + cmd := command{ Cmd: "bash", Args: []string{"-c", helmCommandFromConfig(r) + " upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + strconv.Quote(r.Version) + " --force " + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getHelmFlags(r)}, @@ -300,6 +303,8 @@ func reInstallRelease(r *release, rs releaseState) { } outcome.addCommand(delCmd, r.Priority, r) + checkChartDepUpdate(r) + installCmd := command{ Cmd: "bash", Args: []string{"-c", helmCommandFromConfig(r) + " install " + r.Chart + " --version " + r.Version + " -n " + r.Name + " --namespace " + r.Namespace + getValuesFiles(r) + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getHelmFlags(r)}, @@ -520,3 +525,11 @@ func getHelmFlags(r *release) string { } return getNoHooks(r) + getTimeout(r) + getDryRunFlags() + flags } + +func checkChartDepUpdate(r *release) { + if updateDeps && isLocalChart(r.Chart) { + if ok, err := updateChartDep(r.Chart); !ok { + logError("ERROR: helm dependency update failed: " + err) + } + } +} diff --git a/helm_helpers.go b/helm_helpers.go index 8a3bc67f..d08d40b6 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -231,8 +231,7 @@ func validateReleaseCharts(apps map[string]*release) (bool, string) { } } if validateCurrentChart { - isLocal := filepath.IsAbs(r.Chart) - if isLocal { + if isLocalChart(r.Chart) { cmd := command{ Cmd: "bash", Args: []string{"-c", "helm inspect chart '" + r.Chart + "'"}, @@ -555,3 +554,19 @@ func decryptSecret(name string) bool { return true } + +// updateChartDep updates dependencies for a local chart +func updateChartDep(chartPath string) (bool, string) { + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm dependency update " + chartPath}, + Description: "Updateing dependency for local chart " + chartPath, + } + + exitCode, err := cmd.exec(debug, verbose) + + if exitCode != 0 { + return false, err + } + return true, "" +} diff --git a/init.go b/init.go index 1576e5c9..e98e4c83 100644 --- a/init.go +++ b/init.go @@ -60,7 +60,7 @@ func init() { flag.BoolVar(&suppressDiffSecrets, "suppress-diff-secrets", false, "don't show secrets in helm diff output.") flag.IntVar(&diffContext, "diff-context", -1, "number of lines of context to show around changes in helm diff output") flag.BoolVar(&noEnvSubst, "no-env-subst", false, "turn off environment substitution globally") - + flag.BoolVar(&updateDeps, "update-deps", false, "run 'helm dep up' for local chart") log.SetOutput(os.Stdout) diff --git a/main.go b/main.go index f5a85e58..49439c7e 100644 --- a/main.go +++ b/main.go @@ -44,6 +44,7 @@ var showDiff bool var suppressDiffSecrets bool var diffContext int var noEnvSubst bool +var updateDeps bool const tempFilesDir = ".helmsman-tmp" const stableHelmRepo = "https://kubernetes-charts.storage.googleapis.com" diff --git a/utils.go b/utils.go index d89440cb..b72da10a 100644 --- a/utils.go +++ b/utils.go @@ -514,3 +514,8 @@ func Indent(s, prefix string) string { } return string(res) } + +// isLocalChart checks if a chart specified in the DSF is a local directory or not +func isLocalChart(chart string) bool { + return filepath.IsAbs(chart) +} From cd2946e0a5e669b5e0616b214120b71b216ca93f Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Fri, 26 Jul 2019 13:47:49 +0200 Subject: [PATCH 0437/1127] make chart dependecy check happen before decisions are made --- decision_maker.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index 5a31cd31..ffe5d6f5 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -18,6 +18,7 @@ func makePlan(s *state) *plan { buildState() for _, r := range s.Apps { + checkChartDepUpdate(r) decide(r, s) } @@ -134,8 +135,6 @@ func testRelease(r *release) { // installRelease creates a Helm command to install a particular release in a particular namespace using a particular Tiller. func installRelease(r *release) { - checkChartDepUpdate(r) - cmd := command{ Cmd: "bash", Args: []string{"-c", helmCommandFromConfig(r) + " install " + r.Chart + " -n " + r.Name + " --namespace " + r.Namespace + getValuesFiles(r) + " --version " + r.Version + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getHelmFlags(r)}, @@ -281,8 +280,6 @@ func diffRelease(r *release) string { // upgradeRelease upgrades an existing release with the specified values.yaml func upgradeRelease(r *release) { - checkChartDepUpdate(r) - cmd := command{ Cmd: "bash", Args: []string{"-c", helmCommandFromConfig(r) + " upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + strconv.Quote(r.Version) + " --force " + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getHelmFlags(r)}, @@ -303,8 +300,6 @@ func reInstallRelease(r *release, rs releaseState) { } outcome.addCommand(delCmd, r.Priority, r) - checkChartDepUpdate(r) - installCmd := command{ Cmd: "bash", Args: []string{"-c", helmCommandFromConfig(r) + " install " + r.Chart + " --version " + r.Version + " -n " + r.Name + " --namespace " + r.Namespace + getValuesFiles(r) + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getHelmFlags(r)}, From 3ea1413e152f207b133fd71b4447fcede99a3238 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Fri, 26 Jul 2019 14:07:29 +0200 Subject: [PATCH 0438/1127] add --force-upgrades option. fixes #270 --- decision_maker.go | 6 +++++- init.go | 2 +- main.go | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index caa28dab..e3b58dc4 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -280,9 +280,13 @@ func diffRelease(r *release) string { // upgradeRelease upgrades an existing release with the specified values.yaml func upgradeRelease(r *release) { + var force string + if forceUpgrades { + force = " --force " + } cmd := command{ Cmd: "bash", - Args: []string{"-c", helmCommandFromConfig(r) + " upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + strconv.Quote(r.Version) + " --force " + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getHelmFlags(r)}, + Args: []string{"-c", helmCommandFromConfig(r) + " upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + strconv.Quote(r.Version) + force + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getHelmFlags(r)}, Description: "upgrading release [ " + r.Name + " ] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } diff --git a/init.go b/init.go index 1576e5c9..14690324 100644 --- a/init.go +++ b/init.go @@ -60,7 +60,7 @@ func init() { flag.BoolVar(&suppressDiffSecrets, "suppress-diff-secrets", false, "don't show secrets in helm diff output.") flag.IntVar(&diffContext, "diff-context", -1, "number of lines of context to show around changes in helm diff output") flag.BoolVar(&noEnvSubst, "no-env-subst", false, "turn off environment substitution globally") - + flag.BoolVar(&forceUpgrades, "force-upgrades", false, "use --force when upgrading helm releases. May cause resources to be recreated.") log.SetOutput(os.Stdout) diff --git a/main.go b/main.go index 30b2f7ca..ba19a52c 100644 --- a/main.go +++ b/main.go @@ -44,6 +44,7 @@ var showDiff bool var suppressDiffSecrets bool var diffContext int var noEnvSubst bool +var forceUpgrades bool const tempFilesDir = ".helmsman-tmp" const stableHelmRepo = "https://kubernetes-charts.storage.googleapis.com" From ea638b73797cd5cda06d9e0b55cfd54be6f5aabc Mon Sep 17 00:00:00 2001 From: "Amal Dev. S" Date: Wed, 31 Jul 2019 18:54:09 +0530 Subject: [PATCH 0439/1127] Landing page for helm_repos docs and using it to fix broken link for use_private_helm_charts.md --- README.md | 2 +- docs/how_to/helm_repos/README.md | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 docs/how_to/helm_repos/README.md diff --git a/README.md b/README.md index e1aa57be..2ae74cfb 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Please make sure the following are installed prior to using `helmsman` as a bina - [helm](https://github.com/helm/helm) (for `helmsman` >= 1.6.0, use helm >= 2.10.0. this is due to a dependency bug #87 ) - [helm-diff](https://github.com/databus23/helm-diff) (`helmsman` >= 1.6.0) -If you use private helm repos, you will need either `helm-gcs` or `helm-s3` plugin or you can use basic auth to authenticate to your repos. See the [docs](https://github.com/Praqma/helmsman/blob/master/docs/how_to/use_private_helm_charts.md) for details. +If you use private helm repos, you will need either `helm-gcs` or `helm-s3` plugin or you can use basic auth to authenticate to your repos. See the [docs](https://github.com/Praqma/helmsman/blob/master/docs/how_to/helm_repos) for details. Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. diff --git a/docs/how_to/helm_repos/README.md b/docs/how_to/helm_repos/README.md new file mode 100644 index 00000000..5f7eeeb8 --- /dev/null +++ b/docs/how_to/helm_repos/README.md @@ -0,0 +1,11 @@ + +# Defining Helm Repositories + +Following list contains guides on how to define helm repositories + +- [Using default helm repos](default.md) +- [Using private repos in Google GCS](gcs.md) +- [Using private repos in AWS S3](s3.md) +- [Using private repos with basic auth](basic_auth.md) +- [Using pre-configured repos](pre_configured.md) +- [Using local charts](local.md) From 4114d7b463eae38cfa613112b728ef54aa20004f Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Thu, 1 Aug 2019 14:12:18 +0200 Subject: [PATCH 0440/1127] releasing v1.11.0 --- README.md | 6 +++--- docs/cmd_reference.md | 8 +++++++- docs/desired_state_specification.md | 2 +- release-notes.md | 14 ++++++-------- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 2ae74cfb..dfa65280 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v1.10.1&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v1.11.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -59,9 +59,9 @@ If you use private helm repos, you will need either `helm-gcs` or `helm-s3` plug Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. ``` # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v1.10.1/helmsman_1.10.1_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.11.0/helmsman_1.11.0_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v1.10.1/helmsman_1.10.1_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.11.0/helmsman_1.11.0_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/docs/cmd_reference.md b/docs/cmd_reference.md index 5e0ff80b..3ef11b40 100644 --- a/docs/cmd_reference.md +++ b/docs/cmd_reference.md @@ -1,5 +1,5 @@ --- -version: v1.9.0 +version: v1.11.0 --- # CMD reference @@ -72,3 +72,9 @@ This is the list of the available CMD options in Helmsman: `--no-env-subst` turn off environment substitution globally. + + `--update-deps` + run 'helm dep up' for local chart + + `--force-upgrades` + use --force when upgrading helm releases. May cause resources to be recreated. diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 91218f34..4fed0bdc 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -1,5 +1,5 @@ --- -version: v1.10.1 +version: v1.11.0 --- # Helmsman desired state specification diff --git a/release-notes.md b/release-notes.md index 83609ec9..06624257 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,16 +1,14 @@ -# v1.10.0 +# v1.11.0 > If you are already using an older version of Helmsman than v1.4.0-rc, please read the changes below carefully and follow the upgrade guide [here](docs/migrating_to_v1.4.0-rc.md) # Fixes and improvements: -- Allow local chart paths to contain white spaces. PR #262 -- Add helm flags to helm diff. PR #252 (thanks to @xaka) -- Fix deleting untracked releases if `-target` flag is set. PR #248 (thanks to @fmotrifork) +- Consider `--target` when validating helm charts. PR#269 +- Fix overriding stable repo with a custom one. PR#271 +- Add start and finish logs when deploying charts. PR#266 # New features: -- Add global toggle `--no-env-subst` to be able to disable environment substitution. PR #249 (thanks to @hatemosphere) -- Add `--diff-context` flag to set lines of diff context. PR #251 (thanks to @lachlancooper) -- Allow deploying Tiller with custom Role. PR #258 (thanks to @mkubaczyk) -- Support Tillerless mode on Helm 2 using helm-tiller plugin. PR #259 (thanks to @mkubaczyk and @robbert229) +- adding `--force-upgrades` option [ changes default upgrade behaviour]. PR#273 +- adding `--update-deps` option for local charts dependency update before use. PR#272 From 5b7c034f09bcf7d08433eb0aa9f4b9aa28d06a61 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Thu, 1 Aug 2019 14:14:49 +0200 Subject: [PATCH 0441/1127] bumping version --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 36e8db1e..27b42073 100644 --- a/main.go +++ b/main.go @@ -33,7 +33,7 @@ var nsOverride string var skipValidation bool var applyLabels bool var keepUntrackedReleases bool -var appVersion = "v1.10.1" +var appVersion = "v1.11.0" var helmVersion string var kubectlVersion string var dryRun bool From d50aaa0760f40b0b69fee75667d0143109a3583e Mon Sep 17 00:00:00 2001 From: Ward Loos Date: Tue, 13 Aug 2019 11:37:20 +0200 Subject: [PATCH 0442/1127] fetch explicit chart version --- decision_maker.go | 8 ++++++++ helm_helpers.go | 24 +++++++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/decision_maker.go b/decision_maker.go index 4c928b7b..dc88de44 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -211,6 +211,14 @@ func deleteRelease(r *release, rs releaseState) { func inspectUpgradeScenario(r *release, rs releaseState) { if r.Namespace == rs.Namespace { + + version, msg := getChartVersion(r) + if msg != "" { + logError(msg) + return + } + r.Version = version + if extractChartName(r.Chart) == getReleaseChartName(rs) && r.Version != getReleaseChartVersion(rs) { // upgrade diffRelease(r) diff --git a/helm_helpers.go b/helm_helpers.go index aa4825a5..353e2e52 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -11,7 +11,6 @@ import ( "time" "github.com/Praqma/helmsman/gcs" - "github.com/hashicorp/go-version" ) var currentState map[string]releaseState @@ -271,6 +270,29 @@ func validateReleaseCharts(apps map[string]*release) (bool, string) { return true, "" } +// getChartVersion fetches the lastest chart version matching the semantic versioning constraints. +func getChartVersion(r *release) (string, string) { + cmd := command{ + Cmd: "bash", + Args: []string{"-c", "helm search " + r.Chart + " --version " + strconv.Quote(r.Version)}, + Description: "getting latest chart version " + r.Chart + "-" + r.Version + "", + } + + if exitCode, result := cmd.exec(debug, verbose); exitCode != 0 || strings.Contains(result, "No results found") { + return "", "ERROR: chart " + r.Chart + "-" + r.Version + " is specified for " + "but version is not found in the defined repo." + } else { + versions := strings.Split(result, "\n") + if len(versions) < 2 { + return "", "ERROR: chart " + r.Chart + "-" + r.Version + " is specified for " + "but version is not found in the defined repo." + } + fields := strings.Split(versions[1], "\t") + if len(fields) != 4 { + return "", "ERROR: chart " + r.Chart + "-" + r.Version + " is specified for " + "but version is not found in the defined repo." + } + return strings.TrimSpace(fields[1]), "" + } +} + // waitForTiller keeps checking if the helm Tiller is ready or not by executing helm list and checking its error (if any) // waits for 5 seconds before each new attempt and eventually terminates after 10 failed attempts. func waitForTiller(namespace string) { From 59aed0543ce35912cb5b84d20af9d1ac623fd039 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Tue, 13 Aug 2019 11:42:01 +0200 Subject: [PATCH 0443/1127] Add --history-max Tiller's parameter support --- docs/desired_state_specification.md | 4 ++++ helm_helpers.go | 16 ++++++++++------ namespace.go | 1 + 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 4fed0bdc..ef0fbf89 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -145,6 +145,8 @@ Options: > For the definition of what a protected namespace means, check the [protection guide](how_to/misc/protect_namespaces_and_releases.md) - **installTiller**: defines if Tiller should be deployed in this namespace or not. Default is false. Any chart desired to be deployed into a namespace with a Tiller deployed, will be deployed using that Tiller and not the one in kube-system unless you use the `TillerNamespace` option (see the [Apps](#apps) section below) to use another Tiller. > By default Tiller will be deployed into `kube-system` even if you don't define kube-system in the namespaces section. To prevent deploying Tiller into `kube-system, add kube-system in your namespaces section and set its installTiller to false. +- **tillerMaxHistory**: specify int value of the maximum number of revisions saved per release by Tiller. +> In order to set the kube-system's Tiller's (a default one, main Tiller) max history, define namespace kube-system and set tillerMaxHistory along with installTiller: true - **tillerRole**: specify the role to use. If 'cluster-admin' a clusterrolebinding will be used else a role with a single namespace scope will be created and bound with a rolebinding. - **tillerRoleTemplateFile**: relative path to file templating custom Tiller role. If `installTiller` is true and `tillerRole` is not `cluster-admin`, then helmsman will create namespace specific Tiller role based on the template file passed with this parameter. When `tillerRole` is empty string, role name defaults to `helmsman-tiller`. @@ -185,6 +187,7 @@ protected = false [namespaces.production] protected = true installTiller = true +tillerMaxHistory = 10 tillerServiceAccount = "tiller-production" tillerRoleTemplateFile = "../roles/helmsman-tiller.yaml" caCert = "secrets/ca.cert.pem" @@ -222,6 +225,7 @@ namespaces: production: protected: true installTiller: true + tillerMaxHistory: 10 tillerServiceAccount: "tiller-production" tillerRoleTemplateFile: "../roles/helmsman-tiller.yaml" caCert: "secrets/ca.cert.pem" diff --git a/helm_helpers.go b/helm_helpers.go index aa4825a5..2e44d3dc 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -354,7 +354,7 @@ func addHelmRepos(repos map[string]string) (bool, string) { // If serviceAccount is not provided (empty string), the defaultServiceAccount is used. // If no defaultServiceAccount is provided, A service account is created and Tiller is deployed with the new service account // If no namespace is provided, Tiller is deployed to kube-system -func deployTiller(namespace string, serviceAccount string, defaultServiceAccount string, role string, roleTemplateFile string) (bool, string) { +func deployTiller(namespace string, serviceAccount string, defaultServiceAccount string, role string, roleTemplateFile string, tillerMaxHistory int) (bool, string) { log.Println("INFO: deploying Tiller in namespace [ " + namespace + " ].") sa := "" if serviceAccount != "" { @@ -392,13 +392,17 @@ func deployTiller(namespace string, serviceAccount string, defaultServiceAccount return false, "ERROR: while validating/creating service account [ " + defaultServiceAccountName + " ] in namespace [" + namespace + "]: " + err } } - sa = "--service-account " + defaultServiceAccountName + sa = " --service-account " + defaultServiceAccountName } if namespace == "" { namespace = "kube-system" } tillerNameSpace := " --tiller-namespace " + namespace + maxHistory := "" + if tillerMaxHistory > 0 { + maxHistory = " --history-max " + strconv.Itoa(tillerMaxHistory) + } tls := "" ns := s.Namespaces[namespace] if tillerTLSEnabled(ns) { @@ -415,7 +419,7 @@ func deployTiller(namespace string, serviceAccount string, defaultServiceAccount } cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm init --force-upgrade " + sa + tillerNameSpace + tls + storageBackend}, + Args: []string{"-c", "helm init --force-upgrade " + maxHistory + sa + tillerNameSpace + tls + storageBackend}, Description: "initializing helm on the current context and upgrading Tiller on namespace [ " + namespace + " ].", } @@ -454,7 +458,7 @@ func initHelm() (bool, string) { downloadFile(s.Namespaces[k].ClientKey, k+"-client.key") } if ns.InstallTiller && k != "kube-system" { - if ok, err := deployTiller(k, ns.TillerServiceAccount, defaultSA, ns.TillerRole, ns.TillerRoleTemplateFile); !ok { + if ok, err := deployTiller(k, ns.TillerServiceAccount, defaultSA, ns.TillerRole, ns.TillerRoleTemplateFile, ns.TillerMaxHistory); !ok { return false, err } } @@ -462,12 +466,12 @@ func initHelm() (bool, string) { if ns, ok := s.Namespaces["kube-system"]; ok { if ns.InstallTiller { - if ok, err := deployTiller("kube-system", ns.TillerServiceAccount, defaultSA, ns.TillerRole, ns.TillerRoleTemplateFile); !ok { + if ok, err := deployTiller("kube-system", ns.TillerServiceAccount, defaultSA, ns.TillerRole, ns.TillerRoleTemplateFile, ns.TillerMaxHistory); !ok { return false, err } } } else { - if ok, err := deployTiller("kube-system", "", defaultSA, ns.TillerRole, ns.TillerRoleTemplateFile); !ok { + if ok, err := deployTiller("kube-system", "", defaultSA, ns.TillerRole, ns.TillerRoleTemplateFile, ns.TillerMaxHistory); !ok { return false, err } } diff --git a/namespace.go b/namespace.go index 15fa882a..8bdd0b20 100644 --- a/namespace.go +++ b/namespace.go @@ -28,6 +28,7 @@ type namespace struct { TillerServiceAccount string `yaml:"tillerServiceAccount"` TillerRole string `yaml:"tillerRole"` TillerRoleTemplateFile string `yaml:"tillerRoleTemplateFile"` + TillerMaxHistory int `yaml:"tillerMaxHistory"` CaCert string `yaml:"caCert"` TillerCert string `yaml:"tillerCert"` TillerKey string `yaml:"tillerKey"` From 312f1d9ec7b35ec3601a8d465f6415b6b33c61aa Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Tue, 13 Aug 2019 11:49:28 +0200 Subject: [PATCH 0444/1127] Fix tests for --max-history parameter added --- release_test.go | 2 +- state_test.go | 36 ++++++++++++++++++------------------ 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/release_test.go b/release_test.go index 656f4222..8e3f5a4a 100644 --- a/release_test.go +++ b/release_test.go @@ -10,7 +10,7 @@ func Test_validateRelease(t *testing.T) { Metadata: make(map[string]string), Certificates: make(map[string]string), Settings: (config{}), - Namespaces: map[string]namespace{"namespace": namespace{false, false, false, "", "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}}, + Namespaces: map[string]namespace{"namespace": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}}, HelmRepos: make(map[string]string), Apps: make(map[string]*release), } diff --git a/state_test.go b/state_test.go index daf07368..4dbde2ba 100644 --- a/state_test.go +++ b/state_test.go @@ -34,7 +34,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -58,7 +58,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -79,7 +79,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -103,7 +103,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -127,7 +127,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "$URI", // unset env }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -151,7 +151,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https//192.168.99.100:8443", // invalid url }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -174,7 +174,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -195,7 +195,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -219,7 +219,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -237,7 +237,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -287,7 +287,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, true, true, "", "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, true, true, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -305,7 +305,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, true, "", "", "", "s3://some-bucket/12345.crt", "", "", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, true, "", "", "", 0,"s3://some-bucket/12345.crt", "", "", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -323,7 +323,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, true, "", "", "", "", "", "", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, true, "", "", "", 0,"", "", "", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -341,7 +341,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, true, false, "", "", "", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, true, false, "", "", "", 0,"s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -359,7 +359,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: nil, Apps: make(map[string]*release), @@ -374,7 +374,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{}, Apps: make(map[string]*release), @@ -389,7 +389,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -407,7 +407,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", From 3e6f1741301ffea6102988639f3d050be4354f74 Mon Sep 17 00:00:00 2001 From: Ward Loos Date: Tue, 13 Aug 2019 11:56:53 +0200 Subject: [PATCH 0445/1127] fix go-version import --- helm_helpers.go | 1 + 1 file changed, 1 insertion(+) diff --git a/helm_helpers.go b/helm_helpers.go index 353e2e52..3da2b6c9 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -11,6 +11,7 @@ import ( "time" "github.com/Praqma/helmsman/gcs" + version "github.com/hashicorp/go-version" ) var currentState map[string]releaseState From 22853ea69cc841422abdc147afcfff31eda6c320 Mon Sep 17 00:00:00 2001 From: Ward Loos Date: Tue, 13 Aug 2019 14:39:59 +0200 Subject: [PATCH 0446/1127] Version should be quoted to support * Fixes bug introduced in 'https://github.com/Praqma/helmsman/commit/c18ad8c7180520d6c65351d71e6fef048e2b46bc#diff-4285687681003b9e1299d0581a920915L140' --- decision_maker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/decision_maker.go b/decision_maker.go index 4c928b7b..93f7969a 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -137,7 +137,7 @@ func testRelease(r *release) { func installRelease(r *release) { cmd := command{ Cmd: "bash", - Args: []string{"-c", helmCommandFromConfig(r) + " install " + r.Chart + " -n " + r.Name + " --namespace " + r.Namespace + getValuesFiles(r) + " --version " + r.Version + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getHelmFlags(r)}, + Args: []string{"-c", helmCommandFromConfig(r) + " install " + r.Chart + " -n " + r.Name + " --namespace " + r.Namespace + getValuesFiles(r) + " --version " + strconv.Quote(r.Version) + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getHelmFlags(r)}, Description: "installing release [ " + r.Name + " ] in namespace [[ " + r.Namespace + " ]] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", } outcome.addCommand(cmd, r.Priority, r) From 6cd92521ba2ea3597fd9cb89c5a0bb5f805e7b9c Mon Sep 17 00:00:00 2001 From: Adam Medzinski Date: Tue, 13 Aug 2019 10:14:28 +0200 Subject: [PATCH 0447/1127] Add ability to disable environment substitution in values files Resolves #280 --- init.go | 1 + main.go | 1 + utils.go | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/init.go b/init.go index e02268a5..b6d3aa0a 100644 --- a/init.go +++ b/init.go @@ -60,6 +60,7 @@ func init() { flag.BoolVar(&suppressDiffSecrets, "suppress-diff-secrets", false, "don't show secrets in helm diff output.") flag.IntVar(&diffContext, "diff-context", -1, "number of lines of context to show around changes in helm diff output") flag.BoolVar(&noEnvSubst, "no-env-subst", false, "turn off environment substitution globally") + flag.BoolVar(&noEnvValuesSubst, "no-env-values-subst", false, "turn off environment substitution in values files") flag.BoolVar(&updateDeps, "update-deps", false, "run 'helm dep up' for local chart") flag.BoolVar(&forceUpgrades, "force-upgrades", false, "use --force when upgrading helm releases. May cause resources to be recreated.") diff --git a/main.go b/main.go index 27b42073..63c3dc6a 100644 --- a/main.go +++ b/main.go @@ -44,6 +44,7 @@ var showDiff bool var suppressDiffSecrets bool var diffContext int var noEnvSubst bool +var noEnvValuesSubst bool var updateDeps bool var forceUpgrades bool diff --git a/utils.go b/utils.go index b72da10a..fb89d823 100644 --- a/utils.go +++ b/utils.go @@ -55,7 +55,7 @@ func fromTOML(file string, s *state) (bool, string) { } addDefaultHelmRepos(s) resolvePaths(file, s) - if !noEnvSubst { + if !noEnvSubst && !noEnvValuesSubst { substituteEnvInValuesFiles(s) } @@ -107,7 +107,7 @@ func fromYAML(file string, s *state) (bool, string) { addDefaultHelmRepos(s) resolvePaths(file, s) - if !noEnvSubst { + if !noEnvSubst && !noEnvValuesSubst { substituteEnvInValuesFiles(s) } From 46c4f17832244563f1a760ed4185c1df6f0fcecc Mon Sep 17 00:00:00 2001 From: wilsonmar Date: Wed, 14 Aug 2019 02:14:16 -0600 Subject: [PATCH 0448/1127] Fixes to grammar and sequencing of text --- docs/best_practice.md | 6 +++--- docs/cmd_reference.md | 32 ++++++++++++++--------------- docs/deployment_strategies.md | 8 ++++---- docs/desired_state_specification.md | 8 ++++---- docs/how_to/README.md | 2 +- docs/why_helmsman.md | 23 +++++++++++++-------- 6 files changed, 43 insertions(+), 36 deletions(-) diff --git a/docs/best_practice.md b/docs/best_practice.md index 940580fb..92b9e02f 100644 --- a/docs/best_practice.md +++ b/docs/best_practice.md @@ -6,15 +6,15 @@ version: v1.0.0 When using Helmsman, we recommend the following best practices: -- Add useful metadata in your desired state files (DSFs) so that others (who have access to them) can make understand what your DSF is for. We recommend the following metadata: organization, maintainer (name and email), and description/purpose. +- Add useful metadata in your desired state files (DSFs) so that others (who have access to them) can make understandable what your DSF is for. We recommend the following metadata: organization, maintainer (name and email), and description/purpose. - Use environment variables to pass K8S connection secrets (password, certificates paths on the local system or AWS/GCS bucket urls and the API URI). This keeps all sensitive information out of your version controlled source code. - Define certain namespaces (e.g, production) as protected namespaces (supported in v1.0.0+) and deploy your production-ready releases there. -- If you use multiple desired state files (DSF) with the same cluster, make sure your namespace protection definitions are identical across all DSFs. +- If you use multiple desired state files (DSFs) with the same cluster, make sure your namespace protection definitions are identical across all DSFs. - Don't maintain the same release in multiple DSFs. -- While the decision on how many DSFs to use and what each can contain is up to you and depends on your case, we recommend coming up with your own rule for how to split them. +- While the decision on how many DSFs to use and what each can contain is up to you and depends on your case, we recommend coming up with your own rules for how to split them. diff --git a/docs/cmd_reference.md b/docs/cmd_reference.md index 3ef11b40..b26f24e3 100644 --- a/docs/cmd_reference.md +++ b/docs/cmd_reference.md @@ -4,7 +4,7 @@ version: v1.11.0 # CMD reference -This is the list of the available CMD options in Helmsman: +This lists available CMD options in Helmsman: > you can find the CMD options for the version you are using by typing: `helmsman -h` or `helmsman --help` @@ -15,7 +15,7 @@ This is the list of the available CMD options in Helmsman: apply Helmsman labels to Helm state for all defined apps. `--debug` - show the execution logs. + show execution logs. `--destroy` delete all deployed releases. Purge delete is used if the purge option is set to true for the releases. @@ -24,7 +24,7 @@ This is the list of the available CMD options in Helmsman: number of lines of context to show around changes in helm diff output. `--dry-run` - apply the dry-run option for helm commands. + apply the dry-run (do not update) option for helm commands. `-e value` file(s) to load environment variables from (default .env), may be supplied more than once. @@ -32,15 +32,24 @@ This is the list of the available CMD options in Helmsman: `-f value` desired state file name(s), may be supplied more than once to merge state files. + `--force-upgrades` + use --force when upgrading helm releases. May cause resources to be recreated. + `--keep-untracked-releases` keep releases that are managed by Helmsman and are no longer tracked in your desired state. + `--kubeconfig` + path to the kubeconfig file to use for CLI requests. + `--no-banner` don't show the banner. `--no-color` don't use colors. + `--no-env-subst` + turn off environment substitution globally. + `--no-fancy` don't display the banner and don't use colors. @@ -59,22 +68,13 @@ This is the list of the available CMD options in Helmsman: `--suppress-diff-secrets` don't show secrets in helm diff output. - `-v` show the version. - - `--verbose` - show verbose execution logs. - - `--kubeconfig` - path to the kubeconfig file to use for CLI requests. - `--target` limit execution to specific app. - `--no-env-subst` - turn off environment substitution globally. - `--update-deps` run 'helm dep up' for local chart - `--force-upgrades` - use --force when upgrading helm releases. May cause resources to be recreated. + `-v` show the version. + + `--verbose` + show verbose execution logs. diff --git a/docs/deployment_strategies.md b/docs/deployment_strategies.md index 65e20cf4..c5d141b0 100644 --- a/docs/deployment_strategies.md +++ b/docs/deployment_strategies.md @@ -121,7 +121,7 @@ If you use multiple clusters for multiple purposes, you need at least one Helmsm ## Deploying your dev charts -If you are developing your own applications/services and packaging them in helm charts. It makes sense to automatically deploy these charts to a staging namespace or a dev cluster on every source code commit. +If you are developing your own applications/services and packaging them in helm charts, it makes sense to automatically deploy these charts to a staging namespace or a dev cluster on every source code commit. Often, you would have multiple apps developed in separate source code repositories but you would like to test their deployment in the same cluster/namespace. In that case, Helmsman can be used [as part of your CI pipeline](how_to/deployments/ci.md) as described in the diagram below: @@ -129,11 +129,11 @@ Often, you would have multiple apps developed in separate source code repositori ![multi-DSF](images/multi-DSF.png) -Each repository will have a Helmsman desired state file (DSF). But it is important to consider the notes below on using multiple desired state files with one cluster. +Each repository will have a Helmsman desired state file (DSF). But it is important to consider the notes below on using multiple desired state files with a single cluster. -If you need supporting applications (charts) for your application (e.g, reverse proxies, DB, k8s dashboard etc.), you can describe the desired state for these in a separate file which can live in another repository. Adding such file in the pipeline where you create your cluster from code makes total "DevOps" sense. +If you need supporting applications (charts) for your application (e.g, reverse proxies, DB, k8s dashboard, etc.), you can describe the desired state for these in a separate file which can live in another repository. Adding such a file in the pipeline where you create your cluster from code makes total "DevOps" sense. -## Notes on using multiple Helmsman desired state files with the same cluster +## Notes on using multiple Helmsman desired state files for the same cluster Helmsman works with a single desired state file at a time (starting from v1.5.0, you can pass multiple desired state files which get merged at runtime. See the [docs](how_to/misc/merge_desired_state_files.md)) and does not maintain a state anywhere. i.e. it does not have any context awareness about other desired state files used with the same cluster. For this reason, it is the user's responsibility to make sure that: diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index ef0fbf89..9b0b3eb3 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -4,7 +4,7 @@ version: v1.11.0 # Helmsman desired state specification -This document describes the specification for how to write your Helm charts desired state file. This can be either [Toml](https://github.com/toml-lang/toml) or [Yaml](http://yaml.org/) file. The desired state file consists of: +This document describes the specification for how to write your Helm charts' desired state file. This can be either a [Toml](https://github.com/toml-lang/toml) or [Yaml](http://yaml.org/) formatted file. The desired state file consists of: - [Metadata](#metadata) [Optional] -- metadata for any human reader of the desired state file. - [Certificates](#certificates) [Optional] -- only needed when you want Helmsman to connect kubectl to your cluster for you. @@ -145,9 +145,9 @@ Options: > For the definition of what a protected namespace means, check the [protection guide](how_to/misc/protect_namespaces_and_releases.md) - **installTiller**: defines if Tiller should be deployed in this namespace or not. Default is false. Any chart desired to be deployed into a namespace with a Tiller deployed, will be deployed using that Tiller and not the one in kube-system unless you use the `TillerNamespace` option (see the [Apps](#apps) section below) to use another Tiller. > By default Tiller will be deployed into `kube-system` even if you don't define kube-system in the namespaces section. To prevent deploying Tiller into `kube-system, add kube-system in your namespaces section and set its installTiller to false. -- **tillerMaxHistory**: specify int value of the maximum number of revisions saved per release by Tiller. +- **tillerMaxHistory**: specifies an int value of the maximum number of revisions saved per release by Tiller. > In order to set the kube-system's Tiller's (a default one, main Tiller) max history, define namespace kube-system and set tillerMaxHistory along with installTiller: true -- **tillerRole**: specify the role to use. If 'cluster-admin' a clusterrolebinding will be used else a role with a single namespace scope will be created and bound with a rolebinding. +- **tillerRole**: specifies the role to use. If 'cluster-admin' a clusterrolebinding will be used else a role with a single namespace scope will be created and bound with a rolebinding. - **tillerRoleTemplateFile**: relative path to file templating custom Tiller role. If `installTiller` is true and `tillerRole` is not `cluster-admin`, then helmsman will create namespace specific Tiller role based on the template file passed with this parameter. When `tillerRole` is empty string, role name defaults to `helmsman-tiller`. If `tillerRoleTemplateFile` is set, it will always try to create namespace-scoped Tiller with conditions like below: @@ -267,7 +267,7 @@ Authenticating to private helm repos: - Or, set `GCLOUD_CREDENTIALS` environment variable to contain the content of the credentials.json file. Options: -- you can define any key/value pairs where key is the repo name and value is a valid URI for the repo. Basic auth info can be added in the repo URL as in the example below. +- you can define any key/value pair where the key is the repo name and value is a valid URI for the repo. Basic auth info can be added in the repo URL as in the example below. Example: diff --git a/docs/how_to/README.md b/docs/how_to/README.md index 5237ccb7..9e67ff87 100644 --- a/docs/how_to/README.md +++ b/docs/how_to/README.md @@ -46,5 +46,5 @@ This page contains a list of guides on how to use Helmsman. - [Send slack notifications from Helmsman](misc/send_slack_notifications_from_helmsman.md) - [Merge multiple desired state files](misc/merge_desired_state_files.md) - [Limit Helmsman deployment to specific apps](misc/limit-deployment-to-specific-apps.md) - - [Multitenant clusters guide](misc/multitenant_clusters_guide.md) + - [Multi-tenant clusters guide](misc/multitenant_clusters_guide.md) - [Helmsman on Windows 10](misc/helmsman_on_windows10.md) diff --git a/docs/why_helmsman.md b/docs/why_helmsman.md index 7b189f22..4f8a870f 100644 --- a/docs/why_helmsman.md +++ b/docs/why_helmsman.md @@ -9,34 +9,41 @@ This document describes the reasoning and need behind the inception of Helmsman. ## Before Helm Helmsman was created with continuous deployment in mind. -When we started using k8s, we deployed applications on our cluster directly from k8s manifest files. Initially, we had a custom shell script added to our CI system to deploy the k8s resources on the cluster. That script could only create the k8s resources from the manifest files. Soon we needed to have a more flexible way to dynamically create/delete those resources. We structured our git repo and used custom file names (adding enabled or disabled into file names) and updated the shell script accordingly. It did not take long before we realized that this does not scale and is difficult to maintain. +When we started using Kubernetes (k8s), we deployed applications on our cluster directly from k8s manifest files. Initially, we had a custom shell script added to our CI system to deploy the k8s resources on the cluster. ![CI-pipeline-before-helm](images/CI-pipeline-before-helm.jpg) +That script could only create the k8s resources from the manifest files. Soon we needed to have a more flexible way to dynamically create/delete those resources. We structured our git repo and used custom file names (adding enabled or disabled into file names) and updated the shell script accordingly. It did not take long before we realized that this does not scale and is difficult to maintain. + ## Helm to the rescue? -While looking for solutions for managing the growing number of k8s manifest files from a CI pipeline, we came to know about Helm and quickly realized its potential. By creating Helm charts, we packaged related k8s manifests together into a single entity "a chart". This reduced the amount of files the CI script has to deal with. However, all the CI shell script could do is package a chart and install/upgrade it in our k8s cluster whenever a new commit is done into the chart's files in git. +While looking for solutions for managing the growing number of k8s manifest files from a CI pipeline, we came to know about Helm and quickly realized its potential. +By creating Helm charts, we packaged related k8s manifests together into a single entity: "a chart". ![CI-pipeline-after-helm](images/CI-pipeline-after-helm.jpg) -But there were a couple of issues here: -1. Helm has more to it than package and install. Operations such as rollback, running chart tests etc. are only doable from the Helm's CLI client. +This reduced the amount of files the CI script has to deal with. However, all the CI shell script could do is package a chart and install/upgrade it in our k8s cluster whenever a new commit is done into the chart's files in git. + +But there were a few issues: +1. Helm has more to it than package and install. Operations such as rollback, running chart tests, etc. are only doable from Helm's CLI client. 2. You have to keep updating your CI script every time you add a chart to k8s. 3. What if you want to do the same on another cluster? you will have to replicate your CI pipeline and possibly change your CI script accordingly. -We have also decided to split the Helm charts development from the git repositories where they are used. This is simply to let us develop the charts independently from the projects where we used them and to allow us to reuse them in different projects. +Helm chart development is split from the git repositories where they are used. This is simply to let us develop the charts independently from the projects where we used them and to allow us to reuse them in different projects. -With all this in mind, we needed a flexible and dynamic solution that can let us deploy and manage Helm charts into multiple k8s cluster independently and with minimum human intervention. Such solution should be generic enough to be reusable for many different projects/cluster. And this is where Helmsman was born! +With all this in mind, we needed a flexible and dynamic solution that can let us deploy and manage Helm charts into multiple k8s clusters independently and with minimum human intervention. Such a solution should be generic enough to be reusable for many different projects/cluster. And this is where Helmsman was born! ## The Helmsman way -In English, [Helmsman](https://www.merriam-webster.com/dictionary/helmsman) is the person at the helm (in a ship). In k8s and Helm context, Helmsman holds the Helm and maintains your Helm charts' lifecycle in your k8s cluster(s). Helmsman gets its directions to navigate from a [declarative file](desired_state_specification.md) maintained by the user (k8s admin). +In English, a [Helmsman](https://www.merriam-webster.com/dictionary/helmsman) is the person at the helm (on a ship). In k8s and Helm context, Helmsman holds the Helm and maintains your Helm charts' lifecycle in your k8s cluster(s). Helmsman gets its directions to navigate from a [declarative file](desired_state_specification.md) maintained by the user (k8s admin). > Although knowledge about Helm and K8S is highly beneficial, such knowledge is NOT required to use Helmsman. -As the diagram below shows, we recommend having a _desired state file_ for each k8s cluster you are managing. Along with that file, you would need to have any custom [values yaml files](https://docs.helm.sh/chart_template_guide/#values-files) for the Helm chart's you deploy on your k8s. Then you could configure your CI pipeline to use Helmsman docker image to process your desired state file whenever a commit is made to it. +As the diagram below shows, we recommend having a Helmsman _desired state file_ for each k8s cluster you are managing. ![CI-pipeline-helmsman](images/CI-pipeline-helmsman.jpg) +Along with that file, you would need to have any custom [values yaml files](https://docs.helm.sh/chart_template_guide/#values-files) for the Helm charts you deploy on your k8s. Then you could configure your CI pipeline to use Helmsman docker images to process your desired state file whenever a commit is made to it. + > Helmsman can also be used manually as a binary tool on a machine which has Helm and Kubectl installed. From eb80953d94f9743efc81ac805aad1842c12b15b4 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Thu, 15 Aug 2019 11:25:48 +0200 Subject: [PATCH 0449/1127] releasing v1.12.0 --- README.md | 6 +++--- docs/cmd_reference.md | 3 +++ docs/desired_state_specification.md | 2 +- init.go | 2 +- main.go | 2 +- release-notes.md | 11 ++++------- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index dfa65280..23502465 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v1.11.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v1.12.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -59,9 +59,9 @@ If you use private helm repos, you will need either `helm-gcs` or `helm-s3` plug Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. ``` # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v1.11.0/helmsman_1.11.0_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.12.0/helmsman_1.12.0_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v1.11.0/helmsman_1.11.0_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v1.12.0/helmsman_1.12.0_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/docs/cmd_reference.md b/docs/cmd_reference.md index b26f24e3..17efb50e 100644 --- a/docs/cmd_reference.md +++ b/docs/cmd_reference.md @@ -50,6 +50,9 @@ This lists available CMD options in Helmsman: `--no-env-subst` turn off environment substitution globally. + `no-env-values-subst` + turn off environment substitution in values files only. + `--no-fancy` don't display the banner and don't use colors. diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 9b0b3eb3..342bbe20 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -1,5 +1,5 @@ --- -version: v1.11.0 +version: v1.12.0 --- # Helmsman desired state specification diff --git a/init.go b/init.go index b6d3aa0a..d5e95916 100644 --- a/init.go +++ b/init.go @@ -60,7 +60,7 @@ func init() { flag.BoolVar(&suppressDiffSecrets, "suppress-diff-secrets", false, "don't show secrets in helm diff output.") flag.IntVar(&diffContext, "diff-context", -1, "number of lines of context to show around changes in helm diff output") flag.BoolVar(&noEnvSubst, "no-env-subst", false, "turn off environment substitution globally") - flag.BoolVar(&noEnvValuesSubst, "no-env-values-subst", false, "turn off environment substitution in values files") + flag.BoolVar(&noEnvValuesSubst, "no-env-values-subst", false, "turn off environment substitution in values files only") flag.BoolVar(&updateDeps, "update-deps", false, "run 'helm dep up' for local chart") flag.BoolVar(&forceUpgrades, "force-upgrades", false, "use --force when upgrading helm releases. May cause resources to be recreated.") diff --git a/main.go b/main.go index 63c3dc6a..4a887240 100644 --- a/main.go +++ b/main.go @@ -33,7 +33,7 @@ var nsOverride string var skipValidation bool var applyLabels bool var keepUntrackedReleases bool -var appVersion = "v1.11.0" +var appVersion = "v1.12.0" var helmVersion string var kubectlVersion string var dryRun bool diff --git a/release-notes.md b/release-notes.md index 06624257..d978b7c1 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,14 +1,11 @@ -# v1.11.0 +# v1.12.0 > If you are already using an older version of Helmsman than v1.4.0-rc, please read the changes below carefully and follow the upgrade guide [here](docs/migrating_to_v1.4.0-rc.md) # Fixes and improvements: -- Consider `--target` when validating helm charts. PR#269 -- Fix overriding stable repo with a custom one. PR#271 -- Add start and finish logs when deploying charts. PR#266 +- Fix chart version validations and support wildcard `*` versions. PRs #283 #284 # New features: -- adding `--force-upgrades` option [ changes default upgrade behaviour]. PR#273 -- adding `--update-deps` option for local charts dependency update before use. PR#272 - +- support specifying `--history-max` tiller parameter in the DSF. PR #282 +- adding `no-env-values-subst` flag to allow disabling environment variables substitution in values files only. PR #281 From e444482f01117f1e03c2647d28cf1a3cba50a490 Mon Sep 17 00:00:00 2001 From: Nicolas Vanheuverzwijn Date: Tue, 20 Aug 2019 09:50:49 -0400 Subject: [PATCH 0450/1127] typo: changed 'featuer' to feature --- CONTRIBUTION.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md index 33b6a50f..1603117f 100644 --- a/CONTRIBUTION.md +++ b/CONTRIBUTION.md @@ -19,7 +19,7 @@ Please make sure you state the purpose of the pull request and that the code you Contribution to the documentation can be done via pull requests or by opening an issue. -## Reporting issues/featuer requests +## Reporting issues/feature requests Please provide details of the issue, versions of helmsman, helm and kubernetes and all possible logs. From ba0eae346194ccce9802ae0b91d9c6ef40ca6e5a Mon Sep 17 00:00:00 2001 From: Adam Medzinski Date: Wed, 21 Aug 2019 09:52:52 +0200 Subject: [PATCH 0451/1127] Add paging for helm list with JSON output This commit allows helmsman to work properly with one Tiller managing hundreds of releases. Unfortunately paging is not possible for output in text format because there is no information if next page is available. Fixes #227 --- helm_helpers.go | 107 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 77 insertions(+), 30 deletions(-) diff --git a/helm_helpers.go b/helm_helpers.go index eb472b5b..1d6fe8e5 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -2,6 +2,8 @@ package main import ( "encoding/json" + "errors" + "fmt" "log" "net/url" "path/filepath" @@ -84,9 +86,79 @@ func getTillerReleases(tillerNS string) tillerReleases { outputFormat = "--output json" } + output, err := helmList(tillerNS, outputFormat, "") + if err != nil { + logError(err.Error()) + } + + var allReleases tillerReleases + if output == "" { + return allReleases + } + + if jsonConstraint.Check(v1) { + allReleases, err = parseJSONListAndFollow(output, tillerNS) + if err != nil { + logError(err.Error()) + } + } else { + allReleases = parseTextList(output) + } + + // appending tiller-namespace to each release found + for i := 0; i < len(allReleases.Releases); i++ { + allReleases.Releases[i].TillerNamespace = tillerNS + } + + return allReleases +} + +func parseJSONListAndFollow(input, tillerNS string) (tillerReleases, error) { + var allReleases tillerReleases + var releases tillerReleases + + for { + output, err := helmList(tillerNS, "--output json", releases.Next) + if err != nil { + return allReleases, err + } + if err := json.Unmarshal([]byte(output), &releases); err != nil { + return allReleases, fmt.Errorf("ERROR: failed to unmarshal Helm CLI output: %s", err) + } + for _, releaseInfo := range releases.Releases { + allReleases.Releases = append(allReleases.Releases, releaseInfo) + } + if releases.Next == "" { + break + } + } + + return allReleases, nil +} + +func parseTextList(input string) tillerReleases { + var out tillerReleases + lines := strings.Split(input, "\n") + for i, l := range lines { + if l == "" || (strings.HasPrefix(strings.TrimSpace(l), "NAME") && strings.HasSuffix(strings.TrimSpace(l), "NAMESPACE")) { + continue + } else { + r, _ := strconv.Atoi(strings.Fields(lines[i])[1]) + t := strings.Fields(lines[i])[2] + " " + strings.Fields(lines[i])[3] + " " + strings.Fields(lines[i])[4] + " " + + strings.Fields(lines[i])[5] + " " + strings.Fields(lines[i])[6] + out.Releases = append(out.Releases, releaseInfo{Name: strings.Fields(lines[i])[0], Revision: r, Updated: t, Status: strings.Fields(lines[i])[7], Chart: strings.Fields(lines[i])[8], Namespace: strings.Fields(lines[i])[9], AppVersion: "", TillerNamespace: ""}) + } + } + return out +} + +func helmList(tillerNS, outputFormat, offset string) (string, error) { + arg := fmt.Sprintf("%s list --all --max 0 --offset \"%s\" %s --tiller-namespace %s %s", + helmCommand(tillerNS), offset, outputFormat, tillerNS, getNSTLSFlags(tillerNS), + ) cmd := command{ Cmd: "bash", - Args: []string{"-c", helmCommand(tillerNS) + " list --all --max 0 " + outputFormat + " --tiller-namespace " + tillerNS + getNSTLSFlags(tillerNS)}, + Args: []string{"-c", arg}, Description: "listing all existing releases in namespace [ " + tillerNS + " ]...", } @@ -94,41 +166,16 @@ func getTillerReleases(tillerNS string) tillerReleases { if exitCode != 0 { if !apply { if strings.Contains(result, "incompatible versions") { - logError(result) + return "", errors.New(result) } log.Println("INFO: " + strings.Replace(result, "Error: ", "", 1)) - return tillerReleases{} + return "", nil } - logError("ERROR: failed to list all releases in namespace [ " + tillerNS + " ]: " + result) + return "", fmt.Errorf("ERROR: failed to list all releases in namespace [ %s ]: %s", tillerNS, result) } - var out tillerReleases - if jsonConstraint.Check(v1) { - json.Unmarshal([]byte(result), &out) - } else { - lines := strings.Split(result, "\n") - for i, l := range lines { - if l == "" || (strings.HasPrefix(strings.TrimSpace(l), "NAME") && strings.HasSuffix(strings.TrimSpace(l), "NAMESPACE")) { - continue - } else { - - r, _ := strconv.Atoi(strings.Fields(lines[i])[1]) - t := strings.Fields(lines[i])[2] + " " + strings.Fields(lines[i])[3] + " " + strings.Fields(lines[i])[4] + " " + - strings.Fields(lines[i])[5] + " " + strings.Fields(lines[i])[6] - - out.Releases = append(out.Releases, releaseInfo{Name: strings.Fields(lines[i])[0], Revision: r, Updated: t, Status: strings.Fields(lines[i])[7], Chart: strings.Fields(lines[i])[8], Namespace: strings.Fields(lines[i])[9], AppVersion: "", TillerNamespace: ""}) - } - } - - } - - // appending tiller-namespace to each release found - for i := 0; i < len(out.Releases); i++ { - out.Releases[i].TillerNamespace = tillerNS - } - - return out + return result, nil } // buildState builds the currentState map containing information about all releases existing in a k8s cluster From 8653834a031bbb2890568a6bf0c92d6c6c9a2074 Mon Sep 17 00:00:00 2001 From: Nicolas Vanheuverzwijn Date: Thu, 22 Aug 2019 14:00:04 -0400 Subject: [PATCH 0452/1127] fix: getChartVersion is skipped when local charts --- decision_maker_test.go | 45 +++++++++++++++++++++++++++++++++++++ helm_helpers.go | 4 ++++ helm_helpers_test.go | 50 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+) diff --git a/decision_maker_test.go b/decision_maker_test.go index edff8e58..08ce6237 100644 --- a/decision_maker_test.go +++ b/decision_maker_test.go @@ -77,6 +77,51 @@ func Test_getValuesFiles(t *testing.T) { } } +func Test_inspectUpgradeScenario(t *testing.T){ + type args struct { + r *release + s releaseState + } + tests := []struct { + name string + args args + want decisionType + }{ + { + name: "inspectUpgradeScenario() - local chart with different chart name should change", + args: args{ + r: &release{ + Name: "release1", + Namespace: "namespace", + Version: "1.0.0", + Chart: "/local/charts", + Enabled: true, + }, + s: releaseState{ + Namespace: "namespace", + Chart: "chart-1.0.0", + }, + }, + want: change, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + outcome = plan{} + + // Act + inspectUpgradeScenario(tt.args.r, tt.args.s) + got := outcome.Decisions[0].Type + t.Log(outcome.Decisions[0].Description) + + // Assert + if got != tt.want { + t.Errorf("decide() = %s, want %s", got, tt.want) + } + }) + } +} + func Test_decide(t *testing.T) { type args struct { r *release diff --git a/helm_helpers.go b/helm_helpers.go index eb472b5b..ffca7cdd 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -272,7 +272,11 @@ func validateReleaseCharts(apps map[string]*release) (bool, string) { } // getChartVersion fetches the lastest chart version matching the semantic versioning constraints. +// If chart is local, returns the given release version func getChartVersion(r *release) (string, string) { + if isLocalChart(r.Chart) { + return r.Version, "" + } cmd := command{ Cmd: "bash", Args: []string{"-c", "helm search " + r.Chart + " --version " + strconv.Quote(r.Version)}, diff --git a/helm_helpers_test.go b/helm_helpers_test.go index d81d1400..57f0c243 100644 --- a/helm_helpers_test.go +++ b/helm_helpers_test.go @@ -339,3 +339,53 @@ func Test_getReleaseChartVersion(t *testing.T) { }) } } + + +func Test_getChartVersion(t *testing.T) { + // version string = the first semver-valid string after the last hypen in the chart string. + + type args struct { + r *release + } + tests := []struct { + name string + args args + want string + }{ + { + name: "getChartVersion - local chart should return given release version", + args: args{ + r: &release{ + Name: "release1", + Namespace: "namespace", + Version: "1.0.0", + Chart: "/local/charts", + Enabled: true, + }, + }, + want: "1.0.0", + }, + { + name: "getChartVersion - unknown chart should error", + args: args{ + r: &release{ + Name: "release1", + Namespace: "namespace", + Version: "1.0.0", + Chart: "random-chart-name-1f8147", + Enabled: true, + }, + }, + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Log(tt.want) + got, _ := getChartVersion(tt.args.r); + if got != tt.want { + t.Errorf("getChartVersion() = %v, want %v", got, tt.want) + } + }) + } +} \ No newline at end of file From 94187a4e1004f6c1687f8f3b6db923f3de02d0c9 Mon Sep 17 00:00:00 2001 From: Emil Diaz Date: Fri, 23 Aug 2019 03:51:45 -0400 Subject: [PATCH 0453/1127] Adding support for SSM Params in the Helm values files --- aws/aws.go | 25 +++++++++++++++++++++++++ init.go | 1 + main.go | 1 + utils.go | 39 ++++++++++++++++++++++++++++++++++----- 4 files changed, 61 insertions(+), 5 deletions(-) diff --git a/aws/aws.go b/aws/aws.go index 207bf2b7..bdd85409 100644 --- a/aws/aws.go +++ b/aws/aws.go @@ -8,6 +8,7 @@ import ( "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/s3" "github.com/aws/aws-sdk-go/service/s3/s3manager" + "github.com/aws/aws-sdk-go/service/ssm" "github.com/logrusorgru/aurora" ) @@ -67,3 +68,27 @@ func ReadFile(bucketName string, filename string, outFile string, noColors bool) log.Println("INFO: Successfully downloaded " + filename + " from S3 as " + outFile) } + + +// ReadSSMParam reads a value from an SSM Parameter +func ReadSSMParam(keyname string, withDecryption bool) string { + // Checking env vars are set to configure AWS + if !checkCredentialsEnvVar() { + log.Println("WARN: Failed to find the AWS env vars needed to configure AWS. Please make sure they are set in the environment.") + } + + // Create Session -- use config (credentials + region) from env vars or aws profile + sess, err := session.NewSession() + + if err != nil { + log.Fatal(style.Bold(style.Red("ERROR: Can't create AWS session: " + err.Error()))) + } + + ssmsvc := ssm.New(sess, aws.NewConfig()) + param, err := ssmsvc.GetParameter(&ssm.GetParameterInput{ + Name: &keyname, + WithDecryption: &withDecryption, + }) + value := *param.Parameter.Value + return value +} \ No newline at end of file diff --git a/init.go b/init.go index d5e95916..14825179 100644 --- a/init.go +++ b/init.go @@ -61,6 +61,7 @@ func init() { flag.IntVar(&diffContext, "diff-context", -1, "number of lines of context to show around changes in helm diff output") flag.BoolVar(&noEnvSubst, "no-env-subst", false, "turn off environment substitution globally") flag.BoolVar(&noEnvValuesSubst, "no-env-values-subst", false, "turn off environment substitution in values files only") + flag.BoolVar(&noSSMSubst, "no-ssm-subst", false, "turn off SSM parameter substitution globally") flag.BoolVar(&updateDeps, "update-deps", false, "run 'helm dep up' for local chart") flag.BoolVar(&forceUpgrades, "force-upgrades", false, "use --force when upgrading helm releases. May cause resources to be recreated.") diff --git a/main.go b/main.go index 4a887240..b093f5b1 100644 --- a/main.go +++ b/main.go @@ -45,6 +45,7 @@ var suppressDiffSecrets bool var diffContext int var noEnvSubst bool var noEnvValuesSubst bool +var noSSMSubst bool var updateDeps bool var forceUpgrades bool diff --git a/utils.go b/utils.go index fb89d823..7535e325 100644 --- a/utils.go +++ b/utils.go @@ -11,6 +11,7 @@ import ( "os" "path" "path/filepath" + "regexp" "strconv" "strings" "time" @@ -18,7 +19,8 @@ import ( "gopkg.in/yaml.v2" "github.com/BurntSushi/toml" - "github.com/Praqma/helmsman/aws" + //"github.com/Praqma/helmsman/aws" + "helmsman/aws" "github.com/Praqma/helmsman/azure" "github.com/Praqma/helmsman/gcs" ) @@ -162,15 +164,17 @@ func substituteEnvInValuesFiles(s *state) { // Returns the path for the temp file func substituteEnvInYaml(file string) string { rawYamlFile, err := ioutil.ReadFile(file) - var yamlFile string + yamlFile := string(rawYamlFile) if err != nil { logError(err.Error()) } if !noEnvSubst { - yamlFile = substituteEnv(string(rawYamlFile)) - } else { - yamlFile = string(rawYamlFile) + yamlFile = substituteEnv(yamlFile) + } + if !noSSMSubst { + yamlFile = substituteSSM(yamlFile) } + log.Println("INFO: \n" + yamlFile) dir, err := ioutil.TempDir(tempFilesDir, "tmp") if err != nil { @@ -349,6 +353,31 @@ func substituteEnv(name string) string { return name } +// substituteSSM checks if a string has an SSM param variable (contains '{{ssm: '), then it returns its value +// if the env variable is empty or unset, an empty string is returned +// if the string does not contain '$', it is returned as is. +func substituteSSM(name string) string { + //log.Println("INFO: Checking for ssm params in: " + name) + if strings.Contains(name, "{{ssm: ") { + //log.Println("INFO: Contains ssm params!") + re := regexp.MustCompile(`{{ssm: ([^~}]+)(~(true))?}}`) + matches := re.FindAllSubmatch([]byte(name), -1) + //log.Println("INFO: Found " + fmt.Sprintf("%q\n", matches)) + for _, match := range matches { + placeholder := string(match[0]) + paramPath := string(match[1]) + withDecryption, err := strconv.ParseBool(string(match[3])) + if err != nil { + fmt.Printf("Invalid decryption argument %T \n", string(match[3])) + } + //log.Println(i, paramPath) + value := aws.ReadSSMParam(paramPath, withDecryption) + name = strings.ReplaceAll(name, placeholder, value) + } + } + return name +} + // sliceContains checks if a string slice contains a given string func sliceContains(slice []string, s string) bool { for _, a := range slice { From 998d84a9bed49b4ee4082398f2897194f3e3104d Mon Sep 17 00:00:00 2001 From: Nicolas Vanheuverzwijn Date: Mon, 26 Aug 2019 16:02:14 -0400 Subject: [PATCH 0454/1127] doc: add setString usage example Show how to use setString with nested yaml key --- docs/desired_state_specification.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 342bbe20..919ede68 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -382,6 +382,7 @@ Example: secret2="SECRET_ENV_VAR2" # works with/without $ at the beginning [apps.jenkins.setString] longInt = "1234567890" + "image.tag" = "1.0.0" ``` ```yaml @@ -407,4 +408,6 @@ apps: secret2: "$SECRET_ENV_VAR2" setString: longInt: "1234567890" + image: + tag: "1.0.0" ``` From f5b1e505308471d41be25c73eada00b6402bb846 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Mon, 2 Sep 2019 12:05:29 +0200 Subject: [PATCH 0455/1127] bumping go version in dockerfiles --- dockerfile/dockerfile | 2 +- test_files/dockerfile | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile index a504d0e0..c301e284 100644 --- a/dockerfile/dockerfile +++ b/dockerfile/dockerfile @@ -1,7 +1,7 @@ # This is a docker image for helmsman -FROM golang:1.11.11-alpine3.10 as builder +FROM golang:1.12.9-alpine3.10 as builder WORKDIR /go/src/ diff --git a/test_files/dockerfile b/test_files/dockerfile index 4e60c84e..c67b8f11 100644 --- a/test_files/dockerfile +++ b/test_files/dockerfile @@ -4,11 +4,11 @@ ARG KUBE_VERSION ARG HELM_VERSION -FROM golang:1.10-alpine3.7 +FROM golang:1.12.9-alpine3.10 ENV KUBE_VERSION ${KUBE_VERSION:-v1.11.3} -ENV HELM_VERSION ${HELM_VERSION:-v2.11.0} - +ENV HELM_VERSION ${HELM_VERSION:-v2.14.3} +ENV GORELEASER_VERSION ${GORELEASER_VERSION:-v0.117.1} RUN apk --no-cache update \ && apk add --update --no-cache ca-certificates git \ && apk add --update -t deps curl tar gzip make bash \ @@ -29,5 +29,9 @@ RUN mkdir -p ~/.helm/plugins \ && helm plugin install https://github.com/futuresimple/helm-secrets \ && rm -r /tmp/helm-diff /tmp/helm-diff.tgz -RUN go get github.com/goreleaser/goreleaser && \ - go get github.com/golang/dep/cmd/dep +RUN mkdir /tmp/goreleaser \ + && curl -L https://github.com/goreleaser/goreleaser/releases/download/${GORELEASER_VERSION}/goreleaser_Linux_arm64.tar.gz | tar zxv -C /tmp/goreleaser \ + && mv /tmp/goreleaser/goreleaser /usr/local/bin/goreleaser \ + && rm -r /tmp/goreleaser \ + && chmod +x /usr/local/bin/goreleaser \ + && go get github.com/golang/dep/cmd/dep \ No newline at end of file From e7f38f861adaa04d9a965276a618154aa0b11f30 Mon Sep 17 00:00:00 2001 From: Emil Diaz Date: Fri, 6 Sep 2019 00:39:30 -0400 Subject: [PATCH 0456/1127] Improving error handling, logging and code organization --- aws/aws.go | 9 ++++++- init.go | 1 + main.go | 1 + utils.go | 77 +++++++++++++++++++++++++++--------------------------- 4 files changed, 49 insertions(+), 39 deletions(-) diff --git a/aws/aws.go b/aws/aws.go index bdd85409..83990005 100644 --- a/aws/aws.go +++ b/aws/aws.go @@ -71,7 +71,9 @@ func ReadFile(bucketName string, filename string, outFile string, noColors bool) // ReadSSMParam reads a value from an SSM Parameter -func ReadSSMParam(keyname string, withDecryption bool) string { +func ReadSSMParam(keyname string, withDecryption bool, noColors bool) string { + style = aurora.NewAurora(!noColors) + // Checking env vars are set to configure AWS if !checkCredentialsEnvVar() { log.Println("WARN: Failed to find the AWS env vars needed to configure AWS. Please make sure they are set in the environment.") @@ -89,6 +91,11 @@ func ReadSSMParam(keyname string, withDecryption bool) string { Name: &keyname, WithDecryption: &withDecryption, }) + + if err != nil { + log.Fatal(style.Bold(style.Red("ERROR: Can't find the SSM Parameter " + keyname + " : " + err.Error()))) + } + value := *param.Parameter.Value return value } \ No newline at end of file diff --git a/init.go b/init.go index 14825179..3bfd974c 100644 --- a/init.go +++ b/init.go @@ -62,6 +62,7 @@ func init() { flag.BoolVar(&noEnvSubst, "no-env-subst", false, "turn off environment substitution globally") flag.BoolVar(&noEnvValuesSubst, "no-env-values-subst", false, "turn off environment substitution in values files only") flag.BoolVar(&noSSMSubst, "no-ssm-subst", false, "turn off SSM parameter substitution globally") + flag.BoolVar(&noSSMValuesSubst, "no-ssm-values-subst", false, "turn off SSM parameter substitution in values files only") flag.BoolVar(&updateDeps, "update-deps", false, "run 'helm dep up' for local chart") flag.BoolVar(&forceUpgrades, "force-upgrades", false, "use --force when upgrading helm releases. May cause resources to be recreated.") diff --git a/main.go b/main.go index b093f5b1..c935521b 100644 --- a/main.go +++ b/main.go @@ -46,6 +46,7 @@ var diffContext int var noEnvSubst bool var noEnvValuesSubst bool var noSSMSubst bool +var noSSMValuesSubst bool var updateDeps bool var forceUpgrades bool diff --git a/utils.go b/utils.go index 7535e325..2fdc7105 100644 --- a/utils.go +++ b/utils.go @@ -19,10 +19,9 @@ import ( "gopkg.in/yaml.v2" "github.com/BurntSushi/toml" - //"github.com/Praqma/helmsman/aws" "helmsman/aws" - "github.com/Praqma/helmsman/azure" - "github.com/Praqma/helmsman/gcs" + "helmsman/azure" + "helmsman/gcs" ) // printMap prints to the console any map of string keys and values. @@ -43,23 +42,26 @@ func printNamespacesMap(m map[string]namespace) { // It uses the BurntSuchi TOML parser which throws an error if the TOML file is not valid. func fromTOML(file string, s *state) (bool, string) { rawTomlFile, err := ioutil.ReadFile(file) - var tomlFile string if err != nil { return false, err.Error() } + + tomlFile := string(rawTomlFile) if !noEnvSubst { - tomlFile = substituteEnv(string(rawTomlFile)) - } else { - tomlFile = string(rawTomlFile) + log.Println("INFO: substituting env variables in file: ", file) + tomlFile = substituteEnv(tomlFile) + } + if !noSSMSubst { + log.Println("INFO: substituting SSM variables in file: ", file) + tomlFile = substituteSSM(tomlFile) } + if _, err := toml.Decode(tomlFile, s); err != nil { return false, err.Error() } addDefaultHelmRepos(s) resolvePaths(file, s) - if !noEnvSubst && !noEnvValuesSubst { - substituteEnvInValuesFiles(s) - } + substituteVarsInValuesFiles(s) return true, "INFO: Parsed TOML [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps." } @@ -94,24 +96,26 @@ func toTOML(file string, s *state) { // parser which throws an error if the YAML file is not valid. func fromYAML(file string, s *state) (bool, string) { rawYamlFile, err := ioutil.ReadFile(file) - var yamlFile []byte if err != nil { return false, err.Error() } + + yamlFile := string(rawYamlFile) if !noEnvSubst { - yamlFile = []byte(substituteEnv(string(rawYamlFile))) - } else { - yamlFile = []byte(string(rawYamlFile)) + log.Println("INFO: substituting env variables in file: ", file) + yamlFile = substituteEnv(yamlFile) + } + if !noSSMSubst { + log.Println("INFO: substituting SSM variables in file: ", file) + yamlFile = substituteSSM(yamlFile) } - if err = yaml.UnmarshalStrict(yamlFile, s); err != nil { + + if err = yaml.UnmarshalStrict([]byte(yamlFile), s); err != nil { return false, err.Error() } addDefaultHelmRepos(s) resolvePaths(file, s) - - if !noEnvSubst && !noEnvValuesSubst { - substituteEnvInValuesFiles(s) - } + substituteVarsInValuesFiles(s) return true, "INFO: Parsed YAML [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps." } @@ -141,40 +145,41 @@ func toYAML(file string, s *state) { newFile.Close() } -// substituteEnvInValuesFiles loops through the values/secrets files and substitutes env variables into them -func substituteEnvInValuesFiles(s *state) { - log.Println("INFO: substituting env variables in values and secrets files ...") +// substituteVarsInValuesFiles loops through the values/secrets files and substitutes variables into them. +func substituteVarsInValuesFiles(s *state) { for _, v := range s.Apps { if v.ValuesFile != "" { - v.ValuesFile = substituteEnvInYaml(v.ValuesFile) + v.ValuesFile = substituteVarsInYaml(v.ValuesFile) } if v.SecretsFile != "" { - v.SecretsFile = substituteEnvInYaml(v.SecretsFile) + v.SecretsFile = substituteVarsInYaml(v.SecretsFile) } for i := range v.ValuesFiles { - v.ValuesFiles[i] = substituteEnvInYaml(v.ValuesFiles[i]) + v.ValuesFiles[i] = substituteVarsInYaml(v.ValuesFiles[i]) } for i := range v.SecretsFiles { - v.SecretsFiles[i] = substituteEnvInYaml(v.SecretsFiles[i]) + v.SecretsFiles[i] = substituteVarsInYaml(v.SecretsFiles[i]) } } } -// substituteEnvInYaml substitutes env variables in a Yaml file and creates a temp file with these values. +// substituteVarsInYaml substitutes variables in a Yaml file and creates a temp file with these values. // Returns the path for the temp file -func substituteEnvInYaml(file string) string { +func substituteVarsInYaml(file string) string { rawYamlFile, err := ioutil.ReadFile(file) - yamlFile := string(rawYamlFile) if err != nil { logError(err.Error()) } - if !noEnvSubst { + + yamlFile := string(rawYamlFile) + if !noEnvSubst && !noEnvValuesSubst { + log.Println("INFO: substituting env variables in file: ", file) yamlFile = substituteEnv(yamlFile) } - if !noSSMSubst { + if !noSSMSubst && !noSSMValuesSubst { + log.Println("INFO: substituting SSM variables in file: ", file) yamlFile = substituteSSM(yamlFile) } - log.Println("INFO: \n" + yamlFile) dir, err := ioutil.TempDir(tempFilesDir, "tmp") if err != nil { @@ -353,16 +358,13 @@ func substituteEnv(name string) string { return name } -// substituteSSM checks if a string has an SSM param variable (contains '{{ssm: '), then it returns its value +// substituteSSM checks if a string has an SSM parameter variable (contains '{{ssm: '), then it returns its value // if the env variable is empty or unset, an empty string is returned // if the string does not contain '$', it is returned as is. func substituteSSM(name string) string { - //log.Println("INFO: Checking for ssm params in: " + name) if strings.Contains(name, "{{ssm: ") { - //log.Println("INFO: Contains ssm params!") re := regexp.MustCompile(`{{ssm: ([^~}]+)(~(true))?}}`) matches := re.FindAllSubmatch([]byte(name), -1) - //log.Println("INFO: Found " + fmt.Sprintf("%q\n", matches)) for _, match := range matches { placeholder := string(match[0]) paramPath := string(match[1]) @@ -370,8 +372,7 @@ func substituteSSM(name string) string { if err != nil { fmt.Printf("Invalid decryption argument %T \n", string(match[3])) } - //log.Println(i, paramPath) - value := aws.ReadSSMParam(paramPath, withDecryption) + value := aws.ReadSSMParam(paramPath, withDecryption, noColors) name = strings.ReplaceAll(name, placeholder, value) } } From 62eb2765c9f7e6655aca6501b1263493144b7da2 Mon Sep 17 00:00:00 2001 From: Konrad Kozicki Date: Mon, 9 Sep 2019 19:51:32 +0200 Subject: [PATCH 0457/1127] Fix invalid spacing --- helm_helpers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helm_helpers.go b/helm_helpers.go index 625c8185..6e887e26 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -443,7 +443,7 @@ func deployTiller(namespace string, serviceAccount string, defaultServiceAccount return false, "ERROR: while validating/creating service account [ " + serviceAccount + " ] in namespace [" + namespace + "]: " + err } } - sa = "--service-account " + serviceAccount + sa = " --service-account " + serviceAccount } else { roleName := "helmsman-tiller" defaultServiceAccountName := "helmsman" From 2d18e920f1295405e1be8b300a0b5b6357c744c1 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Thu, 12 Sep 2019 10:19:39 +0200 Subject: [PATCH 0458/1127] fix CI build/test instructions --- .circleci/config.yml | 2 ++ Makefile | 4 ++-- test_files/dockerfile | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 929f0399..c6d2e069 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -13,6 +13,7 @@ jobs: name: Build helmsman command: | echo "building ..." + export GOOS=linux make build test: @@ -25,6 +26,7 @@ jobs: name: Unit test helmsman command: | echo "running tests ..." + export GOOS=linux make test release: working_directory: "/go/src/helmsman" diff --git a/Makefile b/Makefile index 3b533ffa..636c9b3e 100644 --- a/Makefile +++ b/Makefile @@ -67,7 +67,7 @@ dep-update: $(SRCDIR) ## Ensure vendors with dep build: dep ## Build the package @cd $(SRCDIR)helmsman && \ - go build -ldflags '-X main.version="${TAG}-${DATE}" -extldflags "-static"' + CGO_ENABLED=0 go build -ldflags '-X main.version="${TAG}-${DATE}" -extldflags "-static"' generate: @go generate #${PKGS} @@ -82,7 +82,7 @@ check: $(SRCDIR) test: dep ## Run unit tests @cd $(SRCDIR)helmsman && \ helm init --client-only && \ - go test -v -cover -p=1 -args -f example.toml + CGO_ENABLED=0 go test -v -cover -p=1 -args -f example.toml .PHONY: test cross: dep ## Create binaries for all OSs diff --git a/test_files/dockerfile b/test_files/dockerfile index c67b8f11..6d0b1375 100644 --- a/test_files/dockerfile +++ b/test_files/dockerfile @@ -11,7 +11,7 @@ ENV HELM_VERSION ${HELM_VERSION:-v2.14.3} ENV GORELEASER_VERSION ${GORELEASER_VERSION:-v0.117.1} RUN apk --no-cache update \ && apk add --update --no-cache ca-certificates git \ - && apk add --update -t deps curl tar gzip make bash \ + && apk add --update -t deps curl tar gzip make bash gcc \ && rm -rf /var/cache/apk/* \ && curl -L https://storage.googleapis.com/kubernetes-release/release/${KUBE_VERSION}/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl \ && chmod +x /usr/local/bin/kubectl \ From 06e3f93ad95ffba932ff6e28afcf812d828c8786 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Tue, 1 Oct 2019 11:12:57 +0200 Subject: [PATCH 0459/1127] Add appsTemplates omitempty YAML key to allow YAML anchors in state file --- docs/desired_state_specification.md | 46 +++++++++++++++++++++++++++++ state.go | 1 + 2 files changed, 47 insertions(+) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 919ede68..fa1a4aa3 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -311,6 +311,52 @@ preconfiguredHelmRepos: > In this case you will manually need to execute `helm repo add myrepo1 --username= --password=` +## AppsTemplates + +Optional : Yes. + +Synopsis: allows for YAML (TOML has no variable reference support) object creation, that is ignored by state file importer, but can be used as a reference with YAML anchors to not repeat yourself. Read [this](https://blog.daemonl.com/2016/02/yaml.html) example about YAML anchors. + +Examples: + +```yaml +appsTemplates: + + default: &template + valuesFile: "" + purge: false + test: true + protected: false + wait: true + enabled: true + + custom: &template_custom + valuesFile: "" + purge: true + test: true + protected: false + wait: false + enabled: true + +apps: + jenkins: + <<: *template + name: "jenkins-stage" + namespace: "staging" + chart: "stable/jenkins" + version: "0.9.2" + priority: -3 + + jenkins2: + <<: *template_custom + name: "jenkins-prod" + namespace: "production" + chart: "stable/jenkins" + version: "0.9.0" + priority: -2 + +``` + ## Apps Optional : Yes. diff --git a/state.go b/state.go index ae8271ff..377a26b0 100644 --- a/state.go +++ b/state.go @@ -32,6 +32,7 @@ type state struct { HelmRepos map[string]string `yaml:"helmRepos"` PreconfiguredHelmRepos []string `yaml:"preconfiguredHelmRepos"` Apps map[string]*release `yaml:"apps"` + AppsTemplates map[string]*release `yaml:"appsTemplates,omitempty"` } // validate validates that the values specified in the desired state are valid according to the desired state spec. From 7dc02c4b57e9a7122c6e82c47dc819161296ff23 Mon Sep 17 00:00:00 2001 From: John Montroy Date: Wed, 9 Oct 2019 16:15:10 -0400 Subject: [PATCH 0460/1127] adding flag to opt out of default Helm repos automatic set: https://github.com/Praqma/helmsman/issues/301 --- docs/cmd_reference.md | 5 ++++- init.go | 1 + main.go | 1 + utils.go | 4 ++++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/cmd_reference.md b/docs/cmd_reference.md index 17efb50e..9544ba61 100644 --- a/docs/cmd_reference.md +++ b/docs/cmd_reference.md @@ -1,5 +1,5 @@ --- -version: v1.11.0 +version: v1.11.1 --- # CMD reference @@ -47,6 +47,9 @@ This lists available CMD options in Helmsman: `--no-color` don't use colors. + `--no-default-repos` + don't set default Helm repos from Google for 'stable' and 'incubator'. + `--no-env-subst` turn off environment substitution globally. diff --git a/init.go b/init.go index d5e95916..6dcb97b7 100644 --- a/init.go +++ b/init.go @@ -63,6 +63,7 @@ func init() { flag.BoolVar(&noEnvValuesSubst, "no-env-values-subst", false, "turn off environment substitution in values files only") flag.BoolVar(&updateDeps, "update-deps", false, "run 'helm dep up' for local chart") flag.BoolVar(&forceUpgrades, "force-upgrades", false, "use --force when upgrading helm releases. May cause resources to be recreated.") + flag.BoolVar(&noDefaultRepos, "no-default-repos", false, "don't set default Helm repos from Google for 'stable' and 'incubator'") log.SetOutput(os.Stdout) diff --git a/main.go b/main.go index 4a887240..e87f492e 100644 --- a/main.go +++ b/main.go @@ -47,6 +47,7 @@ var noEnvSubst bool var noEnvValuesSubst bool var updateDeps bool var forceUpgrades bool +var noDefaultRepos bool const tempFilesDir = ".helmsman-tmp" const stableHelmRepo = "https://kubernetes-charts.storage.googleapis.com" diff --git a/utils.go b/utils.go index fb89d823..b1102fb9 100644 --- a/utils.go +++ b/utils.go @@ -218,6 +218,10 @@ func stringInSlice(a string, list []string) bool { // addDefaultHelmRepos adds stable and incubator helm repos to the state if they are not already defined func addDefaultHelmRepos(s *state) { + if noDefaultRepos { + log.Println("INFO: default helm repo set disabled, 'stable' and 'incubator' repos unset.") + return + } if s.HelmRepos == nil || len(s.HelmRepos) == 0 { s.HelmRepos = map[string]string{ "stable": stableHelmRepo, From c9dc00a7110ece99eac5d30dd7c7c77971946c73 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Fri, 18 Oct 2019 10:34:23 +0200 Subject: [PATCH 0461/1127] Standardize logDecision message; fix Tillerless charts deletion --- decision_maker.go | 83 ++++++++++++++++++++++------------------------- helm_helpers.go | 22 ++++++------- kube_helpers.go | 22 ++++++++----- plan.go | 6 ++-- state.go | 8 ++--- utils.go | 21 ++++++++++++ 6 files changed, 91 insertions(+), 71 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index f7a64634..89e9a80b 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -31,7 +31,7 @@ func decide(r *release, s *state) { // check for presence in defined targets if len(targetMap) > 0 { if _, ok := targetMap[r.Name]; !ok { - logDecision("DECISION: release [ "+r.Name+" ] is ignored by target flag. Skipping.", r.Priority, noop) + logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] is ignored by target flag. Skipping.", false), r.Priority, noop) return } } @@ -55,11 +55,11 @@ func decide(r *release, s *state) { deleteRelease(r, rs) } else { - logDecision("DECISION: release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ - "you remove its protection.", r.Priority, noop) + logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ + "you remove its protection.", false), r.Priority, noop) } } else { - logDecision("DECISION: release [ "+r.Name+" ] is set to be disabled but is not yet deployed. Skipping.", r.Priority, noop) + logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] is set to be disabled but is not yet deployed. Skipping.", false), r.Priority, noop) } } else { // check for install/upgrade/rollback @@ -68,8 +68,8 @@ func decide(r *release, s *state) { inspectUpgradeScenario(r, rs) // upgrade or move } else { - logDecision("DECISION: release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ - "you remove its protection.", r.Priority, noop) + logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ + "you remove its protection.", false), r.Priority, noop) } } else if ok, rs := helmReleaseExists(r, "deleted"); ok { @@ -78,20 +78,20 @@ func decide(r *release, s *state) { rollbackRelease(r, rs) // rollback } else { - logDecision("DECISION: release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ - "you remove its protection.", r.Priority, noop) + logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ + "you remove its protection.", false), r.Priority, noop) } } else if ok, rs := helmReleaseExists(r, "failed"); ok { if !isProtected(r, rs) { - logDecision("DECISION: release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is in FAILED state. I will upgrade it for you. Hope it gets fixed!", r.Priority, change) + logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is in FAILED state. I will upgrade it for you. Hope it gets fixed!", false), r.Priority, change) upgradeRelease(r) } else { - logDecision("DECISION: release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ - "you remove its protection.", r.Priority, noop) + logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ + "you remove its protection.", false), r.Priority, noop) } } else { @@ -126,11 +126,10 @@ func testRelease(r *release) { cmd := command{ Cmd: "bash", Args: []string{"-c", helmCommandFromConfig(r) + " test " + r.Name + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r)}, - Description: "running tests for release [ " + r.Name + " ]", + Description: generateCmdDescription(r, "running tests for"), } outcome.addCommand(cmd, r.Priority, r) - logDecision("DECISION: release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is required to be tested when installed. Got it!", r.Priority, noop) - + logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is required to be tested when installed. Got it!", false), r.Priority, noop) } // installRelease creates a Helm command to install a particular release in a particular namespace using a particular Tiller. @@ -138,11 +137,10 @@ func installRelease(r *release) { cmd := command{ Cmd: "bash", Args: []string{"-c", helmCommandFromConfig(r) + " install " + r.Chart + " -n " + r.Name + " --namespace " + r.Namespace + getValuesFiles(r) + " --version " + strconv.Quote(r.Version) + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getHelmFlags(r)}, - Description: "installing release [ " + r.Name + " ] in namespace [[ " + r.Namespace + " ]] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", + Description: generateCmdDescription(r, "installing"), } outcome.addCommand(cmd, r.Priority, r) - logDecision("DECISION: release [ "+r.Name+" ] is not installed. Will install it in namespace [[ "+ - r.Namespace+" ]] using Tiller in [ "+getDesiredTillerNamespace(r)+" ]", r.Priority, create) + logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] is not installed. Will install it in namespace [[ "+ r.Namespace+" ]]", true), r.Priority, create) if r.Test { testRelease(r) @@ -159,21 +157,19 @@ func rollbackRelease(r *release, rs releaseState) { cmd := command{ Cmd: "bash", Args: []string{"-c", helmCommandFromConfig(r) + " rollback " + r.Name + " " + getReleaseRevision(rs) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r) + getDryRunFlags()}, - Description: "rolling back release [ " + r.Name + " ] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", + Description: generateCmdDescription(r, "rolling back"), } outcome.addCommand(cmd, r.Priority, r) upgradeRelease(r) // this is to reflect any changes in values file(s) - logDecision("DECISION: release [ "+r.Name+" ] is currently deleted and is desired to be rolledback to "+ - "namespace [[ "+r.Namespace+" ]] . It will also be upgraded in case values have changed.", r.Priority, change) - + logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] is currently deleted and is desired to be rolledback to "+ + "namespace [[ "+r.Namespace+" ]] . It will also be upgraded in case values have changed.", false), r.Priority, create) } else { - reInstallRelease(r, rs) - logDecision("DECISION: release [ "+r.Name+" ] is deleted BUT from namespace [[ "+rs.Namespace+ - " ]]. Will purge delete it from there and install it in namespace [[ "+r.Namespace+" ]]", r.Priority, change) - logDecision("WARNING: rolling back release [ "+r.Name+" ] from [[ "+rs.Namespace+" ]] to [[ "+r.Namespace+ + logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] is deleted BUT from namespace [[ "+rs.Namespace+ + " ]]. Will purge delete it from there and install it in namespace [[ "+r.Namespace+" ]]", false), r.Priority, create) + logDecision(generateDecisionMessage(r, "WARNING: rolling back release [ "+r.Name+" ] from [[ "+rs.Namespace+" ]] to [[ "+r.Namespace+ " ]] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md"+ - " for details if this release uses PV and PVC.", r.Priority, change) + " for details if this release uses PV and PVC.", false), r.Priority, create) } } @@ -195,10 +191,10 @@ func deleteRelease(r *release, rs releaseState) { cmd := command{ Cmd: "bash", Args: []string{"-c", helmCommandFromConfig(r) + " delete " + p + " " + r.Name + getCurrentTillerNamespaceFlag(rs) + getTLSFlags(r) + getDryRunFlags()}, - Description: "deleting release [ " + r.Name + " ] from namespace [[ " + r.Namespace + " ]] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", + Description: generateCmdDescription(r, "deleting"), } outcome.addCommand(cmd, priority, r) - logDecision("DECISION: release [ "+r.Name+" ] is desired to be deleted "+purgeDesc+". Planning this for you!", priority, delete) + logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] is desired to be deleted "+purgeDesc+". Planning this for you!", false), r.Priority, delete) } // inspectUpgradeScenario evaluates if a release should be upgraded. @@ -223,32 +219,31 @@ func inspectUpgradeScenario(r *release, rs releaseState) { // upgrade diffRelease(r) upgradeRelease(r) - logDecision("DECISION: release [ "+r.Name+" ] is desired to be upgraded. Planning this for you!", r.Priority, change) + logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] is desired to be upgraded. Planning this for you!", false), r.Priority, change) } else if extractChartName(r.Chart) != getReleaseChartName(rs) { reInstallRelease(r, rs) - logDecision("DECISION: release [ "+r.Name+" ] is desired to use a new Chart [ "+r.Chart+ + logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] is desired to use a new Chart [ "+r.Chart+ " ]. I am planning a purge delete of the current release and will install it with the new chart in namespace [[ "+ - r.Namespace+" ]]", r.Priority, change) - + r.Namespace+" ]]", false), r.Priority, change) } else { if diff := diffRelease(r); diff != "" { upgradeRelease(r) - logDecision("DECISION: release [ "+r.Name+" ] is currently enabled and have some changed parameters. "+ - "I will upgrade it!", r.Priority, change) + logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] is currently enabled and have some changed parameters. "+ + "I will upgrade it!", false), r.Priority, change) } else { - logDecision("DECISION: release [ "+r.Name+" ] is desired to be enabled and is currently enabled. "+ - "Nothing to do here!", r.Priority, noop) + logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] is desired to be enabled and is currently enabled. "+ + "Nothing to do here!", false), r.Priority, noop) } } } else { reInstallRelease(r, rs) - logDecision("DECISION: release [ "+r.Name+" ] is desired to be enabled in a new namespace [[ "+r.Namespace+ + logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] is desired to be enabled in a new namespace [[ "+r.Namespace+ " ]]. I am planning a purge delete of the current release from namespace [[ "+rs.Namespace+" ]] "+ - "and will install it for you in namespace [[ "+r.Namespace+" ]]", r.Priority, change) - logDecision("WARNING: moving release [ "+r.Name+" ] from [[ "+rs.Namespace+" ]] to [[ "+r.Namespace+ + "and will install it for you in namespace [[ "+r.Namespace+" ]]", false), r.Priority, change) + logDecision(generateDecisionMessage(r, "WARNING: moving release [ "+r.Name+" ] from [[ "+rs.Namespace+" ]] to [[ "+r.Namespace+ " ]] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md"+ - " for details if this release uses PV and PVC.", r.Priority, change) + " for details if this release uses PV and PVC.", false), r.Priority, change) } } @@ -272,7 +267,7 @@ func diffRelease(r *release) string { cmd := command{ Cmd: "bash", Args: []string{"-c", helmCommandFromConfig(r) + " diff " + colorFlag + diffContextFlag + suppressDiffSecretsFlag + "upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + strconv.Quote(r.Version) + " " + getSetValues(r) + getSetStringValues(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r)}, - Description: "diffing release [ " + r.Name + " ] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", + Description: generateCmdDescription(r, "diffing"), } if exitCode, msg = cmd.exec(debug, verbose); exitCode != 0 { @@ -295,7 +290,7 @@ func upgradeRelease(r *release) { cmd := command{ Cmd: "bash", Args: []string{"-c", helmCommandFromConfig(r) + " upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + strconv.Quote(r.Version) + force + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getHelmFlags(r)}, - Description: "upgrading release [ " + r.Name + " ] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", + Description: generateCmdDescription(r, "upgrading"), } outcome.addCommand(cmd, r.Priority, r) @@ -308,14 +303,14 @@ func reInstallRelease(r *release, rs releaseState) { delCmd := command{ Cmd: "bash", Args: []string{"-c", helmCommandFromConfig(r) + " delete --purge " + r.Name + getCurrentTillerNamespaceFlag(rs) + getTLSFlags(r) + getDryRunFlags()}, - Description: "deleting release [ " + r.Name + " ] from namespace [[ " + r.Namespace + " ]] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", + Description: generateCmdDescription(r, "deleting"), } outcome.addCommand(delCmd, r.Priority, r) installCmd := command{ Cmd: "bash", Args: []string{"-c", helmCommandFromConfig(r) + " install " + r.Chart + " --version " + r.Version + " -n " + r.Name + " --namespace " + r.Namespace + getValuesFiles(r) + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getHelmFlags(r)}, - Description: "installing release [ " + r.Name + " ] in namespace [[ " + r.Namespace + " ]] using Tiller in [ " + getDesiredTillerNamespace(r) + " ]", + Description: generateCmdDescription(r, "installing"), } outcome.addCommand(installCmd, r.Priority, r) } diff --git a/helm_helpers.go b/helm_helpers.go index 6e887e26..b36df867 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -562,19 +562,19 @@ func initHelm() (bool, string) { // NOTE: Untracked releases don't benefit from either namespace or application protection. // NOTE: Removing/Commenting out an app from the desired state makes it untracked. func cleanUntrackedReleases() { - toDelete := make(map[string]map[string]bool) + toDelete := make(map[string]map[*release]bool) log.Println("INFO: checking if any Helmsman managed releases are no longer tracked by your desired state ...") for ns, releases := range getHelmsmanReleases() { for r := range releases { tracked := false for _, app := range s.Apps { - if app.Name == r && getDesiredTillerNamespace(app) == ns { + if app.Name == r.Name && getDesiredTillerNamespace(app) == ns { tracked = true } } if !tracked { if _, ok := toDelete[ns]; !ok { - toDelete[ns] = make(map[string]bool) + toDelete[ns] = make(map[*release]bool) } toDelete[ns][r] = true } @@ -586,13 +586,12 @@ func cleanUntrackedReleases() { } else { for ns, releases := range toDelete { for r := range releases { - if len(targetMap) > 0 { - if _, ok := targetMap[r]; !ok { - logDecision("DECISION: untracked release [ "+r+" ] is ignored by target flag. Skipping.", -800, noop) - } else { - logDecision("DECISION: untracked release found: release [ "+r+" ] from Tiller in namespace [ "+ns+" ]. It will be deleted.", -800, delete) - deleteUntrackedRelease(r, ns) - } + _, inTarget := targetMap[r.Name] + if len(targetMap) > 0 && inTarget { + logDecision(generateDecisionMessage(r, "untracked release [ "+r.Name+" ] is ignored by target flag. Skipping.", false), -800, noop) + } else { + logDecision(generateDecisionMessage(r, "untracked release found: release [ "+r.Name+" ]. It will be deleted", true), -800, delete) + deleteUntrackedRelease(r.Name, ns) } } } @@ -611,7 +610,8 @@ func deleteUntrackedRelease(release string, tillerNamespace string) { cmd := command{ Cmd: "bash", Args: []string{"-c", helmCommand(tillerNamespace) + " delete --purge " + release + " --tiller-namespace " + tillerNamespace + tls + getDryRunFlags()}, - Description: "deleting untracked release [ " + release + " ] from Tiller in namespace [[ " + tillerNamespace + " ]]", + //Description: generateCmdDescription(release, "deleting untracked"), + Description: "x", } outcome.addCommand(cmd, -800, nil) diff --git a/kube_helpers.go b/kube_helpers.go index d81902b8..4b3af7d0 100644 --- a/kube_helpers.go +++ b/kube_helpers.go @@ -92,8 +92,8 @@ func createNamespace(ns string) { Args: []string{"-c", "kubectl create namespace " + ns}, Description: "creating namespace " + ns, } - - if exitCode, _ := cmd.exec(debug, verbose); exitCode != 0 { + exitCode, _ := cmd.exec(debug, verbose) + if exitCode != 0 && verbose { log.Println("WARN: I could not create namespace [ " + ns + " ]. It already exists. I am skipping this.") } @@ -108,7 +108,8 @@ func labelNamespace(ns string, labels map[string]string) { Description: "labeling namespace " + ns, } - if exitCode, _ := cmd.exec(debug, verbose); exitCode != 0 { + exitCode, _ := cmd.exec(debug, verbose) + if exitCode != 0 && verbose { log.Println("WARN: I could not label namespace [ " + ns + " with " + k + "=" + v + " ]. It already exists. I am skipping this.") } @@ -124,7 +125,8 @@ func annotateNamespace(ns string, labels map[string]string) { Description: "annotating namespace " + ns, } - if exitCode, _ := cmd.exec(debug, verbose); exitCode != 0 { + exitCode, _ := cmd.exec(debug, verbose) + if exitCode != 0 && verbose { log.Println("WARN: I could not annotate namespace [ " + ns + " with " + k + "=" + v + " ]. It already exists. I am skipping this.") } @@ -437,9 +439,9 @@ func labelResource(r *release) { // getHelmsmanReleases returns a map of all releases that are labeled with "MANAGED-BY=HELMSMAN" // The releases are categorized by the namespaces in which their Tiller is running // The returned map format is: map[:map[:true]] -func getHelmsmanReleases() map[string]map[string]bool { +func getHelmsmanReleases() map[string]map[*release]bool { var lines []string - releases := make(map[string]map[string]bool) + releases := make(map[string]map[*release]bool) storageBackend := "configmap" if s.Settings.StorageBackend == "secret" { @@ -481,9 +483,13 @@ func getHelmsmanReleases() map[string]map[string]bool { } else { fields := strings.Fields(lines[i]) if _, ok := releases[ns]; !ok { - releases[ns] = make(map[string]bool) + releases[ns] = make(map[*release]bool) + } + for _, r := range s.Apps { + if r.Name == fields[0][0:strings.LastIndex(fields[0], ".v")] { + releases[ns][r] = true + } } - releases[ns][fields[0][0:strings.LastIndex(fields[0], ".v")]] = true } } } diff --git a/plan.go b/plan.go index ef7a0e14..673f7b5d 100644 --- a/plan.go +++ b/plan.go @@ -92,8 +92,7 @@ func (p plan) execPlan() { } for _, cmd := range p.Commands { - log.Println("INFO: start applying [ " + cmd.targetRelease.Name + " ] " + - "in namespace [ " + cmd.targetRelease.Namespace + " ] ") + log.Println("INFO: " + cmd.Command.Description) if exitCode, msg := cmd.Command.exec(debug, verbose); exitCode != 0 { var errorMsg string if errorMsg = msg; !verbose { @@ -105,8 +104,7 @@ func (p plan) execPlan() { if cmd.targetRelease != nil && !dryRun { labelResource(cmd.targetRelease) } - log.Println("INFO: finished applying [ " + cmd.targetRelease.Name + " ] " + - "in namespace [ " + cmd.targetRelease.Namespace + " ] ") + log.Println("INFO: finished " + cmd.Command.Description) if _, err := url.ParseRequestURI(s.Settings.SlackWebhook); err == nil { notifySlack(cmd.Command.Description+" ... SUCCESS!", s.Settings.SlackWebhook, false, true) } diff --git a/state.go b/state.go index 377a26b0..6f8e6b08 100644 --- a/state.go +++ b/state.go @@ -117,9 +117,9 @@ func (s state) validate() (bool, string) { if ns.InstallTiller && ns.UseTiller { return false, "ERROR: namespaces validation failed -- installTiller and useTiller can't be used together for namespace [ " + k + " ]" } - if ns.UseTiller { + if ns.UseTiller && verbose { log.Println("INFO: namespace validation -- a pre-installed Tiller is desired to be used in namespace [ " + k + " ].") - } else if !ns.InstallTiller { + } else if !ns.InstallTiller && verbose { log.Println("INFO: namespace validation -- Tiller is NOT desired to be deployed in namespace [ " + k + " ].") } @@ -136,13 +136,13 @@ func (s state) validate() (bool, string) { if ns.InstallTiller { if !ok1 || !ok2 || !ok3 || !ok4 || !ok5 { log.Println("INFO: namespace validation -- Either no or invalid certs/keys provided for DEPLOYING Tiller with TLS in namespace [ " + k + " ].") - } else { + } else if verbose { log.Println("INFO: namespace validation -- Tiller is desired to be DEPLOYED with TLS in namespace [ " + k + " ]. ") } } else if ns.UseTiller { if !ok1 || !ok2 || !ok3 { log.Println("INFO: namespace validation -- Either no or invalid certs/keys provided for USING Tiller with TLS in namespace [ " + k + " ].") - } else { + } else if verbose { log.Println("INFO: namespace validation -- Tiller is desired to be USED with TLS in namespace [ " + k + " ]. ") } } diff --git a/utils.go b/utils.go index b1102fb9..555a615c 100644 --- a/utils.go +++ b/utils.go @@ -523,3 +523,24 @@ func Indent(s, prefix string) string { func isLocalChart(chart string) bool { return filepath.IsAbs(chart) } + +func generateCmdDescription(r *release, action string) string { + var tillerNamespaceMsg string + if tillerNamespaceMsg = ""; !settings.Tillerless { + tillerNamespaceMsg = "using Tiller in [ " + getDesiredTillerNamespace(r) + " ]" + } + message := fmt.Sprintf("%s release [ " + r.Name + " ] in namespace [[ " + r.Namespace + " ]] %s", action, tillerNamespaceMsg) + return message +} + +func generateDecisionMessage(r *release, message string, isTillerAware bool) string { + var tillerNamespaceMsg string + if tillerNamespaceMsg = ""; !settings.Tillerless { + tillerNamespaceMsg = "using Tiller in [ " + getDesiredTillerNamespace(r) + " ]" + } + baseMessage := "DECISION: " + message + if isTillerAware { + return fmt.Sprintf(baseMessage + " %s", tillerNamespaceMsg) + } + return baseMessage +} \ No newline at end of file From 04c17260271fb9e6a07e3a58cf64726ae8ef8373 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Fri, 18 Oct 2019 11:02:05 +0200 Subject: [PATCH 0462/1127] Fix getHelmsmanReleases ignoring namespaces when Tillerless enabled --- kube_helpers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kube_helpers.go b/kube_helpers.go index 4b3af7d0..33893a91 100644 --- a/kube_helpers.go +++ b/kube_helpers.go @@ -451,7 +451,7 @@ func getHelmsmanReleases() map[string]map[*release]bool { namespaces := make([]string, len(s.Namespaces)) i := 0 for s, v := range s.Namespaces { - if v.InstallTiller || v.UseTiller { + if v.InstallTiller || v.UseTiller || settings.Tillerless { namespaces[i] = s i++ } From 386b2706161bdbcb9c967ff3cbba423ad2522930 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Fri, 18 Oct 2019 11:12:41 +0200 Subject: [PATCH 0463/1127] Fix untracked releases when target; add decisionType ignored with gray color --- decision_maker.go | 2 +- helm_helpers.go | 7 ++++--- plan.go | 10 ++++++---- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index 89e9a80b..5f163aa0 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -31,7 +31,7 @@ func decide(r *release, s *state) { // check for presence in defined targets if len(targetMap) > 0 { if _, ok := targetMap[r.Name]; !ok { - logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] is ignored by target flag. Skipping.", false), r.Priority, noop) + logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] is ignored by target flag. Skipping.", false), r.Priority, ignored) return } } diff --git a/helm_helpers.go b/helm_helpers.go index b36df867..57a6a954 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -586,9 +586,10 @@ func cleanUntrackedReleases() { } else { for ns, releases := range toDelete { for r := range releases { - _, inTarget := targetMap[r.Name] - if len(targetMap) > 0 && inTarget { - logDecision(generateDecisionMessage(r, "untracked release [ "+r.Name+" ] is ignored by target flag. Skipping.", false), -800, noop) + if len(targetMap) > 0 { + if _, inTarget := targetMap[r.Name]; !inTarget { + logDecision(generateDecisionMessage(r, "untracked release [ "+r.Name+" ] is ignored by target flag. Skipping.", false), -800, ignored) + } } else { logDecision(generateDecisionMessage(r, "untracked release found: release [ "+r.Name+" ]. It will be deleted", true), -800, delete) deleteUntrackedRelease(r.Name, ns) diff --git a/plan.go b/plan.go index 673f7b5d..8d44a7ce 100644 --- a/plan.go +++ b/plan.go @@ -20,13 +20,15 @@ const ( change delete noop + ignored ) var decisionColor = map[decisionType]aurora.Color{ - create: aurora.BlueFg, - change: aurora.BrownFg, - delete: aurora.RedFg, - noop: aurora.GreenFg, + create: aurora.BlueFg, + change: aurora.BrownFg, + delete: aurora.RedFg, + noop: aurora.GreenFg, + ignored: aurora.GrayFg, } // orderedDecision type representing a Decision and it's priority weight From faf1b256f230e179197bddcd0cccb2fcd77ca3cd Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Fri, 18 Oct 2019 11:16:09 +0200 Subject: [PATCH 0464/1127] Fix deleteUntrackedRelease description when generateCmdDescription is used --- helm_helpers.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/helm_helpers.go b/helm_helpers.go index 57a6a954..4cea4ba0 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -592,7 +592,7 @@ func cleanUntrackedReleases() { } } else { logDecision(generateDecisionMessage(r, "untracked release found: release [ "+r.Name+" ]. It will be deleted", true), -800, delete) - deleteUntrackedRelease(r.Name, ns) + deleteUntrackedRelease(r, ns) } } } @@ -600,7 +600,7 @@ func cleanUntrackedReleases() { } // deleteUntrackedRelease creates the helm command to purge delete an untracked release -func deleteUntrackedRelease(release string, tillerNamespace string) { +func deleteUntrackedRelease(release *release, tillerNamespace string) { tls := "" ns := s.Namespaces[tillerNamespace] @@ -610,9 +610,8 @@ func deleteUntrackedRelease(release string, tillerNamespace string) { } cmd := command{ Cmd: "bash", - Args: []string{"-c", helmCommand(tillerNamespace) + " delete --purge " + release + " --tiller-namespace " + tillerNamespace + tls + getDryRunFlags()}, - //Description: generateCmdDescription(release, "deleting untracked"), - Description: "x", + Args: []string{"-c", helmCommand(tillerNamespace) + " delete --purge " + release.Name + " --tiller-namespace " + tillerNamespace + tls + getDryRunFlags()}, + Description: generateCmdDescription(release, "deleting untracked"), } outcome.addCommand(cmd, -800, nil) From 096e7455768d5f1938b8df4ceb6543813c07dcc2 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Fri, 18 Oct 2019 11:25:17 +0200 Subject: [PATCH 0465/1127] Fix decisions tests after ignored decisionType was added --- decision_maker_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/decision_maker_test.go b/decision_maker_test.go index 08ce6237..42cf4333 100644 --- a/decision_maker_test.go +++ b/decision_maker_test.go @@ -144,7 +144,7 @@ func Test_decide(t *testing.T) { }, s: &state{}, }, - want: noop, + want: ignored, }, { name: "decide() - targetMap does not contain this service - skip", @@ -157,7 +157,7 @@ func Test_decide(t *testing.T) { }, s: &state{}, }, - want: noop, + want: ignored, }, { name: "decide() - targetMap is empty - will install", From 5f82abda5c80d3941848d733e382ec5d0dbe1b1e Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Fri, 18 Oct 2019 17:33:04 -0400 Subject: [PATCH 0466/1127] Upgrade to go v1.13.3 --- dockerfile/dockerfile | 3 +-- test_files/dockerfile | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile index c301e284..b25e1aa4 100644 --- a/dockerfile/dockerfile +++ b/dockerfile/dockerfile @@ -1,7 +1,7 @@ # This is a docker image for helmsman -FROM golang:1.12.9-alpine3.10 as builder +FROM golang:1.13.3-alpine3.10 as builder WORKDIR /go/src/ @@ -52,4 +52,3 @@ RUN mkdir -p ~/.helm/plugins \ WORKDIR /tmp # ENTRYPOINT ["/bin/helmsman"] - diff --git a/test_files/dockerfile b/test_files/dockerfile index 6d0b1375..f0546ef0 100644 --- a/test_files/dockerfile +++ b/test_files/dockerfile @@ -4,7 +4,7 @@ ARG KUBE_VERSION ARG HELM_VERSION -FROM golang:1.12.9-alpine3.10 +FROM golang:1.13.3-alpine3.10 ENV KUBE_VERSION ${KUBE_VERSION:-v1.11.3} ENV HELM_VERSION ${HELM_VERSION:-v2.14.3} @@ -34,4 +34,4 @@ RUN mkdir /tmp/goreleaser \ && mv /tmp/goreleaser/goreleaser /usr/local/bin/goreleaser \ && rm -r /tmp/goreleaser \ && chmod +x /usr/local/bin/goreleaser \ - && go get github.com/golang/dep/cmd/dep \ No newline at end of file + && go get github.com/golang/dep/cmd/dep From 8afbfb0927b3ce6d45f6ead4231558af66c28e8e Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Fri, 18 Oct 2019 17:36:12 -0400 Subject: [PATCH 0467/1127] goreleaser v0.119.0 --- test_files/dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_files/dockerfile b/test_files/dockerfile index f0546ef0..6e57ea18 100644 --- a/test_files/dockerfile +++ b/test_files/dockerfile @@ -8,7 +8,7 @@ FROM golang:1.13.3-alpine3.10 ENV KUBE_VERSION ${KUBE_VERSION:-v1.11.3} ENV HELM_VERSION ${HELM_VERSION:-v2.14.3} -ENV GORELEASER_VERSION ${GORELEASER_VERSION:-v0.117.1} +ENV GORELEASER_VERSION ${GORELEASER_VERSION:-v0.119.0} RUN apk --no-cache update \ && apk add --update --no-cache ca-certificates git \ && apk add --update -t deps curl tar gzip make bash gcc \ From 9d754110e6277088663885b8ee0edcc84ec40856 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Fri, 18 Oct 2019 17:36:29 -0400 Subject: [PATCH 0468/1127] helm v2.15.0 --- test_files/dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_files/dockerfile b/test_files/dockerfile index 6e57ea18..146cd0ee 100644 --- a/test_files/dockerfile +++ b/test_files/dockerfile @@ -7,7 +7,7 @@ ARG HELM_VERSION FROM golang:1.13.3-alpine3.10 ENV KUBE_VERSION ${KUBE_VERSION:-v1.11.3} -ENV HELM_VERSION ${HELM_VERSION:-v2.14.3} +ENV HELM_VERSION ${HELM_VERSION:-v2.15.0} ENV GORELEASER_VERSION ${GORELEASER_VERSION:-v0.119.0} RUN apk --no-cache update \ && apk add --update --no-cache ca-certificates git \ From 4fb7946f9b4ec7a5cb74c1efef0c92ec0b991f52 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Fri, 18 Oct 2019 17:38:09 -0400 Subject: [PATCH 0469/1127] k8s v1.16.2 --- dockerfile/dockerfile | 2 +- test_files/dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile index b25e1aa4..f7e57154 100644 --- a/dockerfile/dockerfile +++ b/dockerfile/dockerfile @@ -25,7 +25,7 @@ FROM alpine:3.10 ARG KUBE_VERSION ARG HELM_VERSION -ENV KUBE_VERSION ${KUBE_VERSION:-v1.11.3} +ENV KUBE_VERSION ${KUBE_VERSION:-v1.16.2} ENV HELM_VERSION ${HELM_VERSION:-v2.11.0} ENV HELM_DIFF_VERSION ${HELM_DIFF_VERSION:-v2.11.0+5} diff --git a/test_files/dockerfile b/test_files/dockerfile index 146cd0ee..eb641b5e 100644 --- a/test_files/dockerfile +++ b/test_files/dockerfile @@ -6,7 +6,7 @@ ARG HELM_VERSION FROM golang:1.13.3-alpine3.10 -ENV KUBE_VERSION ${KUBE_VERSION:-v1.11.3} +ENV KUBE_VERSION ${KUBE_VERSION:-v1.16.2} ENV HELM_VERSION ${HELM_VERSION:-v2.15.0} ENV GORELEASER_VERSION ${GORELEASER_VERSION:-v0.119.0} RUN apk --no-cache update \ From 7b42a667e6b8377cae419f7a87e7189cf499bdfc Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Fri, 18 Oct 2019 17:49:18 -0400 Subject: [PATCH 0470/1127] helm v2.15.0 --- dockerfile/dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile index f7e57154..61090d14 100644 --- a/dockerfile/dockerfile +++ b/dockerfile/dockerfile @@ -26,7 +26,7 @@ ARG KUBE_VERSION ARG HELM_VERSION ENV KUBE_VERSION ${KUBE_VERSION:-v1.16.2} -ENV HELM_VERSION ${HELM_VERSION:-v2.11.0} +ENV HELM_VERSION ${HELM_VERSION:-v2.15.0} ENV HELM_DIFF_VERSION ${HELM_DIFF_VERSION:-v2.11.0+5} RUN apk --no-cache update \ From aca7ce8f5af7c60e649fed9bb335e65553f26cd7 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Mon, 21 Oct 2019 13:32:56 +0200 Subject: [PATCH 0471/1127] Fix helm secrets dec command when tillerless enabled --- decision_maker.go | 6 +++++- helm_helpers.go | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index 5f163aa0..ec6e5f93 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -109,7 +109,11 @@ func decide(r *release, s *state) { // If not configured to run without a tiller will just return `helm`. func helmCommand(namespace string) string { if settings.Tillerless { - return "helm tiller run " + namespace + " -- helm" + if namespace != "" { + return "helm tiller run " + namespace + " -- helm" + } else { + return "helm tiller run helm" + } } return "helm" diff --git a/helm_helpers.go b/helm_helpers.go index 4cea4ba0..c7be42a1 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -621,7 +621,7 @@ func deleteUntrackedRelease(release *release, tillerNamespace string) { func decryptSecret(name string) bool { cmd := command{ Cmd: "bash", - Args: []string{"-c", "helm secrets dec " + name}, + Args: []string{"-c", helmCommand("") + " secrets dec " + name}, Description: "Decrypting " + name, } From 5e864bb2117a9894176f2c8a55e4a5143873d8ac Mon Sep 17 00:00:00 2001 From: Karol Pucynski Date: Mon, 21 Oct 2019 18:21:15 +0200 Subject: [PATCH 0472/1127] clarify error message --- helm_helpers.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/helm_helpers.go b/helm_helpers.go index c7be42a1..75bb7afb 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -304,11 +304,11 @@ func validateReleaseCharts(apps map[string]*release) (bool, string) { cmd := command{ Cmd: "bash", Args: []string{"-c", "helm search " + r.Chart + " --version " + strconv.Quote(r.Version) + " -l"}, - Description: "validating if chart " + r.Chart + "-" + r.Version + " is available in the defined repos.", + Description: "validating if chart " + r.Chart + " with version " + r.Version + " is available in the defined repos.", } if exitCode, result := cmd.exec(debug, verbose); exitCode != 0 || strings.Contains(result, "No results found") { - return false, "ERROR: chart " + r.Chart + "-" + r.Version + " is specified for " + + return false, "ERROR: chart " + r.Chart + " with version " + r.Version + " is specified for " + "app [" + app + "] but is not found in the defined repos." } } @@ -331,15 +331,15 @@ func getChartVersion(r *release) (string, string) { } if exitCode, result := cmd.exec(debug, verbose); exitCode != 0 || strings.Contains(result, "No results found") { - return "", "ERROR: chart " + r.Chart + "-" + r.Version + " is specified for " + "but version is not found in the defined repo." + return "", "ERROR: chart " + r.Chart + " with version " + r.Version + " is specified for " + "but version is not found in the defined repo." } else { versions := strings.Split(result, "\n") if len(versions) < 2 { - return "", "ERROR: chart " + r.Chart + "-" + r.Version + " is specified for " + "but version is not found in the defined repo." + return "", "ERROR: chart " + r.Chart + " with version " + r.Version + " is specified for " + "but version is not found in the defined repo." } fields := strings.Split(versions[1], "\t") if len(fields) != 4 { - return "", "ERROR: chart " + r.Chart + "-" + r.Version + " is specified for " + "but version is not found in the defined repo." + return "", "ERROR: chart " + r.Chart + " with version " + r.Version + " is specified for " + "but version is not found in the defined repo." } return strings.TrimSpace(fields[1]), "" } From 88d415c019e5def4e52d2434e81aac484b4c713c Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Mon, 21 Oct 2019 21:56:21 +0200 Subject: [PATCH 0473/1127] Add dep ensure to Dockerfile --- dockerfile/dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile index c301e284..b1a6b5a8 100644 --- a/dockerfile/dockerfile +++ b/dockerfile/dockerfile @@ -5,7 +5,7 @@ FROM golang:1.12.9-alpine3.10 as builder WORKDIR /go/src/ -RUN apk --no-cache add make git +RUN apk --no-cache add make git dep RUN git clone https://github.com/Praqma/helmsman.git # build a statically linked binary so that it works on stripped linux images such as alpine/busybox. @@ -15,7 +15,7 @@ RUN cd helmsman \ && LT_SHA=$(git rev-parse ${LastTag}^{}) \ && LC_SHA=$(git rev-parse HEAD) \ && if [ ${LT_SHA} != ${LC_SHA} ]; then TAG=latest-$(date +"%d%m%y"); fi \ - && make dependencies \ + && dep ensure \ && CGO_ENABLED=0 GOOS=linux go install -a -ldflags '-X main.version='$TAG' -extldflags "-static"' . From 1fd65c97aca4489a0240bdb9bab85806ffbb2651 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Mon, 21 Oct 2019 18:58:50 -0400 Subject: [PATCH 0474/1127] goreleaser v0.120.2 --- test_files/dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_files/dockerfile b/test_files/dockerfile index eb641b5e..d36e171c 100644 --- a/test_files/dockerfile +++ b/test_files/dockerfile @@ -8,7 +8,7 @@ FROM golang:1.13.3-alpine3.10 ENV KUBE_VERSION ${KUBE_VERSION:-v1.16.2} ENV HELM_VERSION ${HELM_VERSION:-v2.15.0} -ENV GORELEASER_VERSION ${GORELEASER_VERSION:-v0.119.0} +ENV GORELEASER_VERSION ${GORELEASER_VERSION:-v0.120.2} RUN apk --no-cache update \ && apk add --update --no-cache ca-certificates git \ && apk add --update -t deps curl tar gzip make bash gcc \ From 45488639ce319b9bd865b4c70f4ad57cca8540b0 Mon Sep 17 00:00:00 2001 From: Karol Pucynski Date: Mon, 21 Oct 2019 18:18:52 +0200 Subject: [PATCH 0475/1127] Check if chart dir exists instead just path --- utils.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/utils.go b/utils.go index 555a615c..3788c484 100644 --- a/utils.go +++ b/utils.go @@ -521,7 +521,11 @@ func Indent(s, prefix string) string { // isLocalChart checks if a chart specified in the DSF is a local directory or not func isLocalChart(chart string) bool { - return filepath.IsAbs(chart) + _, err := os.Stat(chart) + if err == nil { + return true + } + return false } func generateCmdDescription(r *release, action string) string { @@ -543,4 +547,4 @@ func generateDecisionMessage(r *release, message string, isTillerAware bool) str return fmt.Sprintf(baseMessage + " %s", tillerNamespaceMsg) } return baseMessage -} \ No newline at end of file +} From 8463438d51a2eff921204fc2a5d4190a01bc237c Mon Sep 17 00:00:00 2001 From: Karol Pucynski Date: Mon, 21 Oct 2019 21:34:21 +0200 Subject: [PATCH 0476/1127] Clarify error messages. --- helm_helpers.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/helm_helpers.go b/helm_helpers.go index 75bb7afb..49c8ba70 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -331,15 +331,15 @@ func getChartVersion(r *release) (string, string) { } if exitCode, result := cmd.exec(debug, verbose); exitCode != 0 || strings.Contains(result, "No results found") { - return "", "ERROR: chart " + r.Chart + " with version " + r.Version + " is specified for " + "but version is not found in the defined repo." + return "", "ERROR: chart " + r.Chart + " with version " + r.Version + " is specified but not found in the helm repos." } else { versions := strings.Split(result, "\n") if len(versions) < 2 { - return "", "ERROR: chart " + r.Chart + " with version " + r.Version + " is specified for " + "but version is not found in the defined repo." + return "", "ERROR: chart " + r.Chart + " with version " + r.Version + " is specified but not found in the helm repos (unrecognized helm output?)." } fields := strings.Split(versions[1], "\t") if len(fields) != 4 { - return "", "ERROR: chart " + r.Chart + " with version " + r.Version + " is specified for " + "but version is not found in the defined repo." + return "", "ERROR: chart " + r.Chart + " with version " + r.Version + " is specified but not found in the helm repos (unrecognized helm output?)." } return strings.TrimSpace(fields[1]), "" } From b889208eb6673e743ce7cdc5976c058c44093901 Mon Sep 17 00:00:00 2001 From: Karol Pucynski Date: Tue, 22 Oct 2019 00:25:47 +0200 Subject: [PATCH 0477/1127] Better error code formatting --- decision_maker.go | 2 +- helm_helpers_test.go | 5 +++-- plan.go | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index ec6e5f93..ae1a82f0 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -275,7 +275,7 @@ func diffRelease(r *release) string { } if exitCode, msg = cmd.exec(debug, verbose); exitCode != 0 { - logError("Command returned with exit code: " + string(exitCode) + ". And error message: " + msg) + logError(fmt.Sprintf("Command returned with exit code: %d. And error message: %s ", exitCode, msg)) } else { if (verbose || showDiff) && msg != "" { fmt.Println(msg) diff --git a/helm_helpers_test.go b/helm_helpers_test.go index 57f0c243..a348432d 100644 --- a/helm_helpers_test.go +++ b/helm_helpers_test.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "os" "testing" "time" @@ -16,7 +17,7 @@ func setupTestCase(t *testing.T) func(t *testing.T) { Description: "creating an empty local chart directory", } if exitCode, msg := cmd.exec(debug, verbose); exitCode != 0 { - logError("Command returned with exit code: " + string(exitCode) + ". And error message: " + msg) + logError(fmt.Sprintf("Command returned with exit code: %d. And error message: %s ", exitCode, msg)) } return func(t *testing.T) { @@ -388,4 +389,4 @@ func Test_getChartVersion(t *testing.T) { } }) } -} \ No newline at end of file +} diff --git a/plan.go b/plan.go index 8d44a7ce..40f4e8bf 100644 --- a/plan.go +++ b/plan.go @@ -100,7 +100,7 @@ func (p plan) execPlan() { if errorMsg = msg; !verbose { errorMsg = strings.Split(msg, "---")[0] } - logError("Command returned with exit code: " + string(exitCode) + ". And error message: " + errorMsg) + logError(fmt.Sprintf("Command returned with exit code: %d. And error message: %s ", exitCode, errorMsg)) } else { log.Println(style.Cyan(msg)) if cmd.targetRelease != nil && !dryRun { From 6fcb501d7ede6725a9fbcba2c0149584eb83993f Mon Sep 17 00:00:00 2001 From: Karol Pucynski Date: Tue, 22 Oct 2019 01:35:14 +0200 Subject: [PATCH 0478/1127] Make "local" tests to use local chart --- decision_maker_test.go | 2 +- helm_helpers_test.go | 2 +- test_files/chart-test/Chart.yaml | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 test_files/chart-test/Chart.yaml diff --git a/decision_maker_test.go b/decision_maker_test.go index 42cf4333..4dd40226 100644 --- a/decision_maker_test.go +++ b/decision_maker_test.go @@ -94,7 +94,7 @@ func Test_inspectUpgradeScenario(t *testing.T){ Name: "release1", Namespace: "namespace", Version: "1.0.0", - Chart: "/local/charts", + Chart: "./test_files/chart-test", Enabled: true, }, s: releaseState{ diff --git a/helm_helpers_test.go b/helm_helpers_test.go index a348432d..547c91e3 100644 --- a/helm_helpers_test.go +++ b/helm_helpers_test.go @@ -360,7 +360,7 @@ func Test_getChartVersion(t *testing.T) { Name: "release1", Namespace: "namespace", Version: "1.0.0", - Chart: "/local/charts", + Chart: "./test_files/chart-test", Enabled: true, }, }, diff --git a/test_files/chart-test/Chart.yaml b/test_files/chart-test/Chart.yaml new file mode 100644 index 00000000..27cbcc07 --- /dev/null +++ b/test_files/chart-test/Chart.yaml @@ -0,0 +1,3 @@ +apiVersion: v1 +name: chart-test +version: 1.0.0 From c6a3dbccc522c770a38d671add2070f7db05fd54 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Tue, 22 Oct 2019 09:31:19 -0400 Subject: [PATCH 0479/1127] k8s v1.14.8 --- dockerfile/dockerfile | 2 +- test_files/dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile index 3f7f4990..bb9a8380 100644 --- a/dockerfile/dockerfile +++ b/dockerfile/dockerfile @@ -25,7 +25,7 @@ FROM alpine:3.10 ARG KUBE_VERSION ARG HELM_VERSION -ENV KUBE_VERSION ${KUBE_VERSION:-v1.16.2} +ENV KUBE_VERSION ${KUBE_VERSION:-v1.14.8} ENV HELM_VERSION ${HELM_VERSION:-v2.15.0} ENV HELM_DIFF_VERSION ${HELM_DIFF_VERSION:-v2.11.0+5} diff --git a/test_files/dockerfile b/test_files/dockerfile index d36e171c..46e6a547 100644 --- a/test_files/dockerfile +++ b/test_files/dockerfile @@ -6,7 +6,7 @@ ARG HELM_VERSION FROM golang:1.13.3-alpine3.10 -ENV KUBE_VERSION ${KUBE_VERSION:-v1.16.2} +ENV KUBE_VERSION ${KUBE_VERSION:-v1.14.8} ENV HELM_VERSION ${HELM_VERSION:-v2.15.0} ENV GORELEASER_VERSION ${GORELEASER_VERSION:-v0.120.2} RUN apk --no-cache update \ From f7688247374586ffa4665bc3d3a748ee702381ce Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Tue, 22 Oct 2019 20:50:21 +0200 Subject: [PATCH 0480/1127] updating release process and documenting it --- .circleci/config.yml | 55 ++++++++++++++++++++++--------------------- CONTRIBUTION.md | 13 ++++++++++ Makefile | 2 +- dockerfile/dockerfile | 3 ++- test_files/dockerfile | 2 +- 5 files changed, 45 insertions(+), 30 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c6d2e069..0e310031 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -29,51 +29,52 @@ jobs: export GOOS=linux make test release: - working_directory: "/go/src/helmsman" - docker: - - image: praqma/helmsman-test + working_directory: "/tmp/go/src/helmsman" + machine: true steps: - checkout - run: name: Release helmsman command: | - TAG_SHA=$(git rev-parse $(git describe --abbrev=0 --tags)) - LAST_COMMIT=$(git rev-parse HEAD) - if [ "${TAG_SHA}" == "${LAST_COMMIT}" ]; then - echo "releasing ..." - make release - else - echo "No release is needed yet." - exit 0 - fi + echo "releasing ..." + make release - # - setup_remote_docker - # - run: - # name: build docker images and push them to dockerhub - # command: | - # TAG=$(git describe --abbrev=0 --tags) - # docker login -u $DOCKER_USER -p $DOCKERHUB - # docker build -t praqma/helmsman:$TAG-helm-v2.8.1 --build-arg HELM_VERSION=v2.8.1 dockerfile/. - # docker push praqma/helmsman:$TAG-helm-v2.8.1 - # docker build -t praqma/helmsman:$TAG-helm-v2.8.0 --build-arg HELM_VERSION=v2.8.0 dockerfile/. - # docker push praqma/helmsman:$TAG-helm-v2.8.0 - # docker build -t praqma/helmsman:$TAG-helm-v2.7.2 --build-arg HELM_VERSION=v2.7.2 dockerfile/. - # docker push praqma/helmsman:$TAG-helm-v2.7.2 + - run: + name: build docker images and push them to dockerhub + command: | + TAG=$(git describe --abbrev=0 --tags) + docker login -u $DOCKER_USER -p $DOCKERHUB + docker build -t praqma/helmsman:$TAG-helm-v2.14.3 --build-arg HELM_VERSION=v2.14.3 --build-arg HELM_DIFF_VERSION=master dockerfile/. --no-cache + docker push praqma/helmsman:$TAG-helm-v2.14.3 + docker build -t praqma/helmsman:$TAG-helm-v2.13.1 --build-arg HELM_VERSION=v2.13.1 --build-arg HELM_DIFF_VERSION=master dockerfile/. --no-cache + docker push praqma/helmsman:$TAG-helm-v2.13.1 + docker build -t praqma/helmsman:$TAG-helm-v2.12.3 --build-arg HELM_VERSION=v2.12.3 --build-arg HELM_DIFF_VERSION=master dockerfile/. --no-cache + docker push praqma/helmsman:$TAG-helm-v2.12.3 + docker build -t praqma/helmsman:$TAG-helm-v2.11.0 --build-arg HELM_VERSION=v2.11.0 --build-arg HELM_DIFF_VERSION=v2.11.0+5 dockerfile/. --no-cache + docker push praqma/helmsman:$TAG-helm-v2.11.0 + docker build -t praqma/helmsman:$TAG-helm-v2.10.0 --build-arg HELM_VERSION=v2.10.0 --build-arg HELM_DIFF_VERSION=v2.10.0+1 dockerfile/. --no-cache + docker push praqma/helmsman:$TAG-helm-v2.10.0 workflows: version: 2 build-test-push-release: jobs: - - build + - build: + filters: + tags: + only: /.*/ - test: requires: - build + filters: + tags: + only: /.*/ - release: requires: - test filters: branches: - only: master + ignore: /.*/ tags: - only: /^v.*/ + only: /v[0-9]+(\.[0-9]+)*(-.*)*/ diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md index 1603117f..8bc0ad3d 100644 --- a/CONTRIBUTION.md +++ b/CONTRIBUTION.md @@ -23,3 +23,16 @@ Contribution to the documentation can be done via pull requests or by opening an Please provide details of the issue, versions of helmsman, helm and kubernetes and all possible logs. +## Releasing Helmsman + +Release is automated from CicrcleCI based on Git tags. [Goreleaser](goreleaser.com) is used to release the binaries and update the release notes on Github while the circleci pipeline builds a set of docker images and pushes them to dockerhub. + +The following steps are needed to cut a release (They assume that you are on master and the code is up to date): +1. Change the version variable in [main.go](main.go) +2. Update the [release-notes.md](release-notes.md) file with new version and changelog. +3. (Optional), if new helm versions are required, update the [circleci config](.circleci/config.yml) and add more docker commands. +4. Commit your changes locally. +5. Create a git tag with the following command: `git tag -a -m "" ` +6. Push your commit and tag with `git push --follow-tags` +7. This should trigger the [pipeline on circleci](https://circleci.com/gh/Praqma/workflows/helmsman) which eventually releases to Github and dockerhub. + diff --git a/Makefile b/Makefile index 636c9b3e..d62a00fb 100644 --- a/Makefile +++ b/Makefile @@ -92,7 +92,7 @@ cross: dep ## Create binaries for all OSs release: dep ## Generate a new release @cd $(SRCDIR)helmsman && \ - goreleaser --release-notes release-notes.md + goreleaser --release-notes release-notes.md --rm-dist tools: ## Get extra tools used by this makefile @go get -u github.com/golang/dep/cmd/dep diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile index b1a6b5a8..d71b311a 100644 --- a/dockerfile/dockerfile +++ b/dockerfile/dockerfile @@ -16,7 +16,8 @@ RUN cd helmsman \ && LC_SHA=$(git rev-parse HEAD) \ && if [ ${LT_SHA} != ${LC_SHA} ]; then TAG=latest-$(date +"%d%m%y"); fi \ && dep ensure \ - && CGO_ENABLED=0 GOOS=linux go install -a -ldflags '-X main.version='$TAG' -extldflags "-static"' . + && make build + # && CGO_ENABLED=0 GOOS=linux go install -a -ldflags '-X main.version='$TAG' -extldflags "-static"' . # The image to keep diff --git a/test_files/dockerfile b/test_files/dockerfile index 6d0b1375..fdeacfa2 100644 --- a/test_files/dockerfile +++ b/test_files/dockerfile @@ -11,7 +11,7 @@ ENV HELM_VERSION ${HELM_VERSION:-v2.14.3} ENV GORELEASER_VERSION ${GORELEASER_VERSION:-v0.117.1} RUN apk --no-cache update \ && apk add --update --no-cache ca-certificates git \ - && apk add --update -t deps curl tar gzip make bash gcc \ + && apk add --update -t deps curl tar gzip make bash gcc git openssh \ && rm -rf /var/cache/apk/* \ && curl -L https://storage.googleapis.com/kubernetes-release/release/${KUBE_VERSION}/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl \ && chmod +x /usr/local/bin/kubectl \ From d5513f0e11da89a4ac1feb98ff4b730d249965c8 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Tue, 22 Oct 2019 21:01:19 +0200 Subject: [PATCH 0481/1127] update docker password var name --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0e310031..b6730ea4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -43,7 +43,7 @@ jobs: name: build docker images and push them to dockerhub command: | TAG=$(git describe --abbrev=0 --tags) - docker login -u $DOCKER_USER -p $DOCKERHUB + docker login -u $DOCKER_USER -p $DOCKER_PASS docker build -t praqma/helmsman:$TAG-helm-v2.14.3 --build-arg HELM_VERSION=v2.14.3 --build-arg HELM_DIFF_VERSION=master dockerfile/. --no-cache docker push praqma/helmsman:$TAG-helm-v2.14.3 docker build -t praqma/helmsman:$TAG-helm-v2.13.1 --build-arg HELM_VERSION=v2.13.1 --build-arg HELM_DIFF_VERSION=master dockerfile/. --no-cache From 3d1a817084c15f2e183584c06c5d812af14cf846 Mon Sep 17 00:00:00 2001 From: Peter Sandberg Brun Date: Wed, 23 Oct 2019 15:48:35 +0200 Subject: [PATCH 0482/1127] Added support of running Helmsman on Windows Any execution of a command using bash with the command parameter has been replaced with just the expected command executable (helm and kubectl). And instead of building the arguments as a single string, they are now built using string arrays. This solves the "quoting" problem, that is there when using a single string with all arguments. For example, before this change, a path had to be quoted on Windows to work. By just building string arrays with all the arguments, this problem is solved by Go' os/exec package. It also solves the problem of using a password protected Helm repo, where the username contains a '$' (for example Harbor creates robot accounts with this). This could be escaped using '$$', but then it still was being interpreted by bash as an environment variable. This could have been solved by adding quotes around in the old solution, but it is just simpler to just use the "native" string array arguments approach as described above. And still if the environment variable was intended as input, then it is already applied by the "substituteEnv" functionality. --- .gitignore | 1 + .goreleaser.yml | 1 + command.go | 15 ++++- decision_maker.go | 130 +++++++++++++++++++++-------------------- decision_maker_test.go | 11 ++-- helm_helpers.go | 106 +++++++++++++++++---------------- helm_helpers_test.go | 41 +++++++------ init.go | 8 +-- init_test.go | 4 +- kube_helpers.go | 78 ++++++++++++------------- release.go | 5 +- utils.go | 9 +++ 12 files changed, 219 insertions(+), 190 deletions(-) diff --git a/.gitignore b/.gitignore index 619a16e7..f6f42a33 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ *.world *.world1 helmsman +helmsman.exe diff --git a/.goreleaser.yml b/.goreleaser.yml index 83cce6e1..6619ee39 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -8,5 +8,6 @@ builds: goos: - darwin - linux + - windows goarch: - amd64 \ No newline at end of file diff --git a/command.go b/command.go index 5b00ede3..c5fc3664 100644 --- a/command.go +++ b/command.go @@ -33,14 +33,22 @@ func (c command) printFullCommand() { // exec executes the executable command and returns the exit code and execution result func (c command) exec(debug bool, verbose bool) (int, string) { + // Only use non-empty string args + args := []string{} + for _, str := range c.Args { + if str != "" { + args = append(args, str) + } + } + if debug { log.Println("INFO: " + c.Description) } if verbose { - log.Println("VERBOSE: " + strings.Join(c.Args[1:], " ")) + log.Println("VERBOSE: " + c.Cmd + " " + strings.Join(args, " ")) } - cmd := exec.Command(c.Cmd, c.Args...) + cmd := exec.Command(c.Cmd, args...) var stdout, stderr bytes.Buffer cmd.Stdout = &stdout cmd.Stderr = &stderr @@ -50,7 +58,8 @@ func (c command) exec(debug bool, verbose bool) (int, string) { cmd.Env = append(os.Environ(), "HELM_TILLER_SILENT=true") if err := cmd.Start(); err != nil { - logError("ERROR: cmd.Start: " + err.Error()) + log.Println("ERROR: cmd.Start: " + err.Error()) + return 1, err.Error() } if err := cmd.Wait(); err != nil { diff --git a/decision_maker.go b/decision_maker.go index ae1a82f0..bb55a396 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -3,6 +3,7 @@ package main import ( "fmt" "regexp" + "runtime" "strconv" "strings" ) @@ -107,29 +108,33 @@ func decide(r *release, s *state) { // operate without a tiller it will return `helm tiller run NAMESPACE -- helm` // where NAMESPACE is the namespace that the release is configured to use. // If not configured to run without a tiller will just return `helm`. -func helmCommand(namespace string) string { +func helmCommand(namespace string) []string { if settings.Tillerless { + if runtime.GOOS == "windows" { + logError("ERROR: Tillerless Helm plugin is not supported on Windows") + } + return []string{"tiller", "run", namespace, "--", "helm"} if namespace != "" { - return "helm tiller run " + namespace + " -- helm" + return []string{"tiller", "run", namespace, "--", "helm"} } else { - return "helm tiller run helm" + return []string{"tiller", "run", "helm"} } } - return "helm" + return nil } // helmCommandFromConfig calls helmCommand returning the correct way to invoke // helm. -func helmCommandFromConfig(r *release) string { +func helmCommandFromConfig(r *release) []string { return helmCommand(getDesiredTillerNamespace(r)) } // testRelease creates a Helm command to test a particular release. func testRelease(r *release) { cmd := command{ - Cmd: "bash", - Args: []string{"-c", helmCommandFromConfig(r) + " test " + r.Name + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r)}, + Cmd: "helm", + Args: concat(helmCommandFromConfig(r), []string{"test", r.Name}, getDesiredTillerNamespaceFlag(r), getTLSFlags(r)), Description: generateCmdDescription(r, "running tests for"), } outcome.addCommand(cmd, r.Priority, r) @@ -139,8 +144,8 @@ func testRelease(r *release) { // installRelease creates a Helm command to install a particular release in a particular namespace using a particular Tiller. func installRelease(r *release) { cmd := command{ - Cmd: "bash", - Args: []string{"-c", helmCommandFromConfig(r) + " install " + r.Chart + " -n " + r.Name + " --namespace " + r.Namespace + getValuesFiles(r) + " --version " + strconv.Quote(r.Version) + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getHelmFlags(r)}, + Cmd: "helm", + Args: concat(helmCommandFromConfig(r), []string{"install", r.Chart, "-n", r.Name, "--namespace", r.Namespace}, getValuesFiles(r), []string{"--version", r.Version}, getSetValues(r), getSetStringValues(r), getWait(r), getDesiredTillerNamespaceFlag(r), getTLSFlags(r), getHelmFlags(r)), Description: generateCmdDescription(r, "installing"), } outcome.addCommand(cmd, r.Priority, r) @@ -159,8 +164,8 @@ func rollbackRelease(r *release, rs releaseState) { if r.Namespace == rs.Namespace { cmd := command{ - Cmd: "bash", - Args: []string{"-c", helmCommandFromConfig(r) + " rollback " + r.Name + " " + getReleaseRevision(rs) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getTimeout(r) + getNoHooks(r) + getDryRunFlags()}, + Cmd: "helm", + Args: concat(helmCommandFromConfig(r), []string{"rollback", r.Name, getReleaseRevision(rs)}, getWait(r), getDesiredTillerNamespaceFlag(r), getTLSFlags(r), getTimeout(r), getNoHooks(r), getDryRunFlags()), Description: generateCmdDescription(r, "rolling back"), } outcome.addCommand(cmd, r.Priority, r) @@ -193,8 +198,8 @@ func deleteRelease(r *release, rs releaseState) { } cmd := command{ - Cmd: "bash", - Args: []string{"-c", helmCommandFromConfig(r) + " delete " + p + " " + r.Name + getCurrentTillerNamespaceFlag(rs) + getTLSFlags(r) + getDryRunFlags()}, + Cmd: "helm", + Args: concat(helmCommandFromConfig(r), []string{"delete", p, r.Name}, getCurrentTillerNamespaceFlag(rs), getTLSFlags(r), getDryRunFlags()), Description: generateCmdDescription(r, "deleting"), } outcome.addCommand(cmd, priority, r) @@ -256,21 +261,21 @@ func diffRelease(r *release) string { exitCode := 0 msg := "" colorFlag := "" - diffContextFlag := "" + diffContextFlag := []string{} suppressDiffSecretsFlag := "" if noColors { - colorFlag = "--no-color " + colorFlag = "--no-color" } if diffContext != -1 { - diffContextFlag = "--context " + strconv.Itoa(diffContext) + " " + diffContextFlag = []string{"--context", strconv.Itoa(diffContext)} } if suppressDiffSecrets { - suppressDiffSecretsFlag = "--suppress-secrets " + suppressDiffSecretsFlag = "--suppress-secrets" } cmd := command{ - Cmd: "bash", - Args: []string{"-c", helmCommandFromConfig(r) + " diff " + colorFlag + diffContextFlag + suppressDiffSecretsFlag + "upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + strconv.Quote(r.Version) + " " + getSetValues(r) + getSetStringValues(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r)}, + Cmd: "helm", + Args: concat(helmCommandFromConfig(r), []string{"diff", colorFlag}, diffContextFlag, []string{suppressDiffSecretsFlag, "upgrade", r.Name, r.Chart}, getValuesFiles(r), []string{"--version", r.Version}, getSetValues(r), getSetStringValues(r), getDesiredTillerNamespaceFlag(r), getTLSFlags(r)), Description: generateCmdDescription(r, "diffing"), } @@ -289,11 +294,11 @@ func diffRelease(r *release) string { func upgradeRelease(r *release) { var force string if forceUpgrades { - force = " --force " + force = "--force" } cmd := command{ - Cmd: "bash", - Args: []string{"-c", helmCommandFromConfig(r) + " upgrade " + r.Name + " " + r.Chart + getValuesFiles(r) + " --version " + strconv.Quote(r.Version) + force + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getHelmFlags(r)}, + Cmd: "helm", + Args: concat(helmCommandFromConfig(r), []string{"upgrade", r.Name, r.Chart}, getValuesFiles(r), []string{"--version", r.Version, force}, getSetValues(r), getSetStringValues(r), getWait(r), getDesiredTillerNamespaceFlag(r), getTLSFlags(r), getHelmFlags(r)), Description: generateCmdDescription(r, "upgrading"), } @@ -305,15 +310,15 @@ func upgradeRelease(r *release) { func reInstallRelease(r *release, rs releaseState) { delCmd := command{ - Cmd: "bash", - Args: []string{"-c", helmCommandFromConfig(r) + " delete --purge " + r.Name + getCurrentTillerNamespaceFlag(rs) + getTLSFlags(r) + getDryRunFlags()}, + Cmd: "helm", + Args: concat(helmCommandFromConfig(r), []string{"delete", "--purge", r.Name}, getCurrentTillerNamespaceFlag(rs), getTLSFlags(r), getDryRunFlags()), Description: generateCmdDescription(r, "deleting"), } outcome.addCommand(delCmd, r.Priority, r) installCmd := command{ - Cmd: "bash", - Args: []string{"-c", helmCommandFromConfig(r) + " install " + r.Chart + " --version " + r.Version + " -n " + r.Name + " --namespace " + r.Namespace + getValuesFiles(r) + getSetValues(r) + getSetStringValues(r) + getWait(r) + getDesiredTillerNamespaceFlag(r) + getTLSFlags(r) + getHelmFlags(r)}, + Cmd: "helm", + Args: concat(helmCommandFromConfig(r), []string{"install", r.Chart, "--version", r.Version, "-n", r.Name, "--namespace", r.Namespace}, getValuesFiles(r), getSetValues(r), getSetStringValues(r), getWait(r), getDesiredTillerNamespaceFlag(r), getTLSFlags(r), getHelmFlags(r)), Description: generateCmdDescription(r, "installing"), } outcome.addCommand(installCmd, r.Priority, r) @@ -342,23 +347,23 @@ func extractChartName(releaseChart string) string { var chartNameExtractor = regexp.MustCompile(`[\\/]([^\\/]+)$`) // getNoHooks returns the no-hooks flag for install/upgrade commands -func getNoHooks(r *release) string { +func getNoHooks(r *release) []string { if r.NoHooks { - return " --no-hooks " + return []string{"--no-hooks"} } - return "" + return []string{} } // getTimeout returns the timeout flag for install/upgrade commands -func getTimeout(r *release) string { +func getTimeout(r *release) []string { if r.Timeout != 0 { - return " --timeout " + strconv.Itoa(r.Timeout) + return []string{"--timeout", strconv.Itoa(r.Timeout)} } - return "" + return []string{} } // getValuesFiles return partial install/upgrade release command to substitute the -f flag in Helm. -func getValuesFiles(r *release) string { +func getValuesFiles(r *release) []string { var fileList []string if r.ValuesFile != "" { @@ -392,36 +397,37 @@ func getValuesFiles(r *release) string { fileList = append(fileList, r.SecretsFiles...) } - if len(fileList) > 0 { - return " -f " + strings.Join(fileList, " -f ") + fileListArgs := []string{} + for _, file := range fileList { + fileListArgs = append(fileListArgs, "-f", file) } - return "" + return fileListArgs } // getSetValues returns --set params to be used with helm install/upgrade commands -func getSetValues(r *release) string { - result := "" +func getSetValues(r *release) []string { + result := []string{} for k, v := range r.Set { - result = result + " --set " + k + "=\"" + strings.Replace(v, ",", "\\,", -1) + "\"" + result = append(result, "--set", k+"="+strings.Replace(v, ",", "\\,", -1)+"") } return result } // getSetStringValues returns --set-string params to be used with helm install/upgrade commands -func getSetStringValues(r *release) string { - result := "" +func getSetStringValues(r *release) []string { + result := []string{} for k, v := range r.SetString { - result = result + " --set-string " + k + "=\"" + strings.Replace(v, ",", "\\,", -1) + "\"" + result = append(result, "--set-string", k+"="+strings.Replace(v, ",", "\\,", -1)+"") } return result } // getWait returns a partial helm command containing the helm wait flag (--wait) if the wait flag for the release was set to true // Otherwise, retruns an empty string -func getWait(r *release) string { - result := "" +func getWait(r *release) []string { + result := []string{} if r.Wait { - result = " --wait" + result = append(result, "--wait") } return result } @@ -463,8 +469,8 @@ func isProtected(r *release, rs releaseState) bool { } // getDesiredTillerNamespaceFlag returns a tiller-namespace flag with which a release is desired to be maintained -func getDesiredTillerNamespaceFlag(r *release) string { - return " --tiller-namespace " + getDesiredTillerNamespace(r) +func getDesiredTillerNamespaceFlag(r *release) []string { + return []string{"--tiller-namespace", getDesiredTillerNamespace(r)} } // getDesiredTillerNamespace returns the Tiller namespace with which a release should be managed @@ -479,35 +485,35 @@ func getDesiredTillerNamespace(r *release) string { } // getCurrentTillerNamespaceFlag returns the tiller-namespace with which a release is currently maintained -func getCurrentTillerNamespaceFlag(rs releaseState) string { +func getCurrentTillerNamespaceFlag(rs releaseState) []string { if rs.TillerNamespace != "" { - return " --tiller-namespace " + rs.TillerNamespace + return []string{"--tiller-namespace", rs.TillerNamespace} } - return "" + return []string{} } // getTLSFlags returns TLS flags with which a release is maintained // If the release where the namespace is to be deployed has Tiller deployed, the TLS flags will use certs/keys for that namespace (if any) // otherwise, it will be the certs/keys for the kube-system namespace. -func getTLSFlags(r *release) string { - tls := "" +func getTLSFlags(r *release) []string { + tls := []string{} ns := s.Namespaces[r.TillerNamespace] if r.TillerNamespace != "" { if tillerTLSEnabled(ns) { - tls = " --tls --tls-ca-cert " + r.TillerNamespace + "-ca.cert --tls-cert " + r.TillerNamespace + "-client.cert --tls-key " + r.TillerNamespace + "-client.key " + tls = append(tls, "--tls", "--tls-ca-cert", r.TillerNamespace+"-ca.cert", "--tls-cert", r.TillerNamespace+"-client.cert", "--tls-key", r.TillerNamespace+"-client.key") } } else if s.Namespaces[r.Namespace].InstallTiller { ns := s.Namespaces[r.Namespace] if tillerTLSEnabled(ns) { - tls = " --tls --tls-ca-cert " + r.Namespace + "-ca.cert --tls-cert " + r.Namespace + "-client.cert --tls-key " + r.Namespace + "-client.key " + tls = append(tls, "--tls", "--tls-ca-cert", r.Namespace+"-ca.cert", "--tls-cert", r.Namespace+"-client.cert", "--tls-key", r.Namespace+"-client.key") } } else { ns := s.Namespaces["kube-system"] if tillerTLSEnabled(ns) { - tls = " --tls --tls-ca-cert kube-system-ca.cert --tls-cert kube-system-client.cert --tls-key kube-system-client.key " + tls = append(tls, "--tls", "--tls-ca-cert", "kube-system-ca.cert", "--tls-cert", "kube-system-client.cert", "--tls-key", "kube-system-client.key") } } @@ -515,21 +521,21 @@ func getTLSFlags(r *release) string { } // getDryRunFlags returns dry-run flag -func getDryRunFlags() string { +func getDryRunFlags() []string { if dryRun { - return " --dry-run --debug " + return []string{"--dry-run", "--debug"} } - return "" + return []string{} } // getHelmFlags returns helm flags -func getHelmFlags(r *release) string { - var flags string +func getHelmFlags(r *release) []string { + var flags []string for _, flag := range r.HelmFlags { - flags = flags + " " + flag + flags = append(flags, flag) } - return getNoHooks(r) + getTimeout(r) + getDryRunFlags() + flags + return concat(getNoHooks(r), getTimeout(r), getDryRunFlags(), flags) } func checkChartDepUpdate(r *release) { diff --git a/decision_maker_test.go b/decision_maker_test.go index 4dd40226..79107f30 100644 --- a/decision_maker_test.go +++ b/decision_maker_test.go @@ -1,6 +1,7 @@ package main import ( + "reflect" "testing" ) @@ -11,7 +12,7 @@ func Test_getValuesFiles(t *testing.T) { tests := []struct { name string args args - want string + want []string }{ { name: "test case 1", @@ -29,7 +30,7 @@ func Test_getValuesFiles(t *testing.T) { }, //s: st, }, - want: " -f test_files/values.yaml", + want: []string{"-f", "test_files/values.yaml"}, }, { name: "test case 2", @@ -47,7 +48,7 @@ func Test_getValuesFiles(t *testing.T) { }, //s: st, }, - want: " -f test_files/values.yaml", + want: []string{"-f", "test_files/values.yaml"}, }, { name: "test case 1", @@ -65,12 +66,12 @@ func Test_getValuesFiles(t *testing.T) { }, //s: st, }, - want: " -f test_files/values.yaml -f test_files/values2.yaml", + want: []string{"-f", "test_files/values.yaml", "-f", "test_files/values2.yaml"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := getValuesFiles(tt.args.r); got != tt.want { + if got := getValuesFiles(tt.args.r); !reflect.DeepEqual(got, tt.want) { t.Errorf("getValuesFiles() = %v, want %v", got, tt.want) } }) diff --git a/helm_helpers.go b/helm_helpers.go index 49c8ba70..71439965 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -47,8 +47,8 @@ type tillerReleases struct { // getHelmClientVersion returns Helm client Version func getHelmClientVersion() string { cmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm version --client --short"}, + Cmd: "helm", + Args: []string{"version", "--client", "--short"}, Description: "checking Helm version ", } @@ -81,9 +81,9 @@ func getAllReleases() tillerReleases { func getTillerReleases(tillerNS string) tillerReleases { v1, _ := version.NewVersion(helmVersion) jsonConstraint, _ := version.NewConstraint(">=2.10.0-rc.1") - var outputFormat string + var outputFormat []string if jsonConstraint.Check(v1) { - outputFormat = "--output json" + outputFormat = append(outputFormat, "--output", "json") } output, err := helmList(tillerNS, outputFormat, "") @@ -118,7 +118,7 @@ func parseJSONListAndFollow(input, tillerNS string) (tillerReleases, error) { var releases tillerReleases for { - output, err := helmList(tillerNS, "--output json", releases.Next) + output, err := helmList(tillerNS, []string{"--output", "json"}, releases.Next) if err != nil { return allReleases, err } @@ -152,13 +152,14 @@ func parseTextList(input string) tillerReleases { return out } -func helmList(tillerNS, outputFormat, offset string) (string, error) { - arg := fmt.Sprintf("%s list --all --max 0 --offset \"%s\" %s --tiller-namespace %s %s", - helmCommand(tillerNS), offset, outputFormat, tillerNS, getNSTLSFlags(tillerNS), - ) +func helmList(tillerNS string, outputFormat []string, offset string) (string, error) { + args := []string{"list", "--all", "--max", "0"} + if offset != "" { + args = append(args, "--offset", offset) + } cmd := command{ - Cmd: "bash", - Args: []string{"-c", arg}, + Cmd: "helm", + Args: concat(helmCommand(tillerNS), args, outputFormat, []string{"--tiller-namespace", tillerNS}, getNSTLSFlags(tillerNS)), Description: "listing all existing releases in namespace [ " + tillerNS + " ]...", } @@ -254,12 +255,11 @@ func getReleaseChartVersion(rs releaseState) string { } // getNSTLSFlags returns TLS flags for a given namespace if it's deployed with TLS -func getNSTLSFlags(namespace string) string { - tls := "" +func getNSTLSFlags(namespace string) []string { + tls := []string{} ns := s.Namespaces[namespace] if tillerTLSEnabled(ns) { - - tls = " --tls --tls-ca-cert " + namespace + "-ca.cert --tls-cert " + namespace + "-client.cert --tls-key " + namespace + "-client.key " + tls = append(tls, "--tls", "--tls-ca-cert", namespace+"-ca.cert", "--tls-cert", namespace+"-client.cert", "--tls-key", namespace+"-client.key") } return tls } @@ -280,8 +280,8 @@ func validateReleaseCharts(apps map[string]*release) (bool, string) { if validateCurrentChart { if isLocalChart(r.Chart) { cmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm inspect chart '" + r.Chart + "'"}, + Cmd: "helm", + Args: []string{"inspect", "chart", r.Chart}, Description: "validating if chart at " + r.Chart + " is available.", } @@ -301,9 +301,13 @@ func validateReleaseCharts(apps map[string]*release) (bool, string) { } } else { + version := r.Version + if len(version) == 0 { + version = "*" + } cmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm search " + r.Chart + " --version " + strconv.Quote(r.Version) + " -l"}, + Cmd: "helm", + Args: []string{"search", r.Chart, "--version", version, "-l"}, Description: "validating if chart " + r.Chart + " with version " + r.Version + " is available in the defined repos.", } @@ -325,8 +329,8 @@ func getChartVersion(r *release) (string, string) { return r.Version, "" } cmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm search " + r.Chart + " --version " + strconv.Quote(r.Version)}, + Cmd: "helm", + Args: []string{"search", r.Chart, "--version", r.Version}, Description: "getting latest chart version " + r.Chart + "-" + r.Version + "", } @@ -352,8 +356,8 @@ func waitForTiller(namespace string) { attempt := 0 cmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm list --tiller-namespace " + namespace + getNSTLSFlags(namespace)}, + Cmd: "helm", + Args: concat([]string{"list", "--tiller-namespace", namespace}, getNSTLSFlags(namespace)), Description: "checking if helm Tiller is ready in namespace [ " + namespace + " ].", } @@ -379,7 +383,7 @@ func waitForTiller(namespace string) { func addHelmRepos(repos map[string]string) (bool, string) { for repoName, repoLink := range repos { - basicAuth := "" + basicAuthArgs := []string{} // check if repo is in GCS, then perform GCS auth -- needed for private GCS helm repos // failed auth would not throw an error here, as it is possible that the repo is public and does not need authentication if strings.HasPrefix(repoLink, "gs://") { @@ -395,13 +399,13 @@ func addHelmRepos(repos map[string]string) (bool, string) { if !ok { logError("ERROR: helm repo " + repoName + " has incomplete basic auth info. Missing the password!") } - basicAuth = " --username " + u.User.Username() + " --password " + p + basicAuthArgs = append(basicAuthArgs, "--username", u.User.Username(), "--password", p) } cmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm repo add " + basicAuth + " " + repoName + " " + strconv.Quote(repoLink)}, + Cmd: "helm", + Args: concat([]string{"repo", "add", repoName, repoLink}, basicAuthArgs), Description: "adding repo " + repoName, } @@ -412,8 +416,8 @@ func addHelmRepos(repos map[string]string) (bool, string) { } cmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm repo update "}, + Cmd: "helm", + Args: []string{"repo", "update"}, Description: "updating helm repos", } @@ -430,7 +434,7 @@ func addHelmRepos(repos map[string]string) (bool, string) { // If no namespace is provided, Tiller is deployed to kube-system func deployTiller(namespace string, serviceAccount string, defaultServiceAccount string, role string, roleTemplateFile string, tillerMaxHistory int) (bool, string) { log.Println("INFO: deploying Tiller in namespace [ " + namespace + " ].") - sa := "" + sa := []string{} if serviceAccount != "" { if ok, err := validateServiceAccount(serviceAccount, namespace); !ok { if strings.Contains(err, "NotFound") || strings.Contains(err, "not found") { @@ -443,7 +447,7 @@ func deployTiller(namespace string, serviceAccount string, defaultServiceAccount return false, "ERROR: while validating/creating service account [ " + serviceAccount + " ] in namespace [" + namespace + "]: " + err } } - sa = " --service-account " + serviceAccount + sa = []string{"--service-account", serviceAccount} } else { roleName := "helmsman-tiller" defaultServiceAccountName := "helmsman" @@ -466,34 +470,34 @@ func deployTiller(namespace string, serviceAccount string, defaultServiceAccount return false, "ERROR: while validating/creating service account [ " + defaultServiceAccountName + " ] in namespace [" + namespace + "]: " + err } } - sa = " --service-account " + defaultServiceAccountName + sa = []string{"--service-account", defaultServiceAccountName} } if namespace == "" { namespace = "kube-system" } - tillerNameSpace := " --tiller-namespace " + namespace + tillerNameSpace := []string{"--tiller-namespace", namespace} - maxHistory := "" + maxHistory := []string{} if tillerMaxHistory > 0 { - maxHistory = " --history-max " + strconv.Itoa(tillerMaxHistory) + maxHistory = []string{"--history-max", strconv.Itoa(tillerMaxHistory)} } - tls := "" + tls := []string{} ns := s.Namespaces[namespace] if tillerTLSEnabled(ns) { tillerCert := namespace + "-tiller.cert" tillerKey := namespace + "-tiller.key" caCert := namespace + "-ca.cert" - tls = " --tiller-tls --tiller-tls-cert " + tillerCert + " --tiller-tls-key " + tillerKey + " --tiller-tls-verify --tls-ca-cert " + caCert + tls = []string{"--tiller-tls", "--tiller-tls-cert", tillerCert, "--tiller-tls-key", tillerKey, "--tiller-tls-verify", "--tls-ca-cert", caCert} } - storageBackend := "" + storageBackend := []string{} if s.Settings.StorageBackend == "secret" { - storageBackend = " --override 'spec.template.spec.containers[0].command'='{/tiller,--storage=secret}'" + storageBackend = []string{"--override", "'spec.template.spec.containers[0].command'='{/tiller,--storage=secret}'"} } cmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm init --force-upgrade " + maxHistory + sa + tillerNameSpace + tls + storageBackend}, + Cmd: "helm", + Args: concat([]string{"init", "--force-upgrade"}, maxHistory, sa, tillerNameSpace, tls, storageBackend), Description: "initializing helm on the current context and upgrading Tiller on namespace [ " + namespace + " ].", } @@ -506,8 +510,8 @@ func deployTiller(namespace string, serviceAccount string, defaultServiceAccount // initHelmClientOnly initializes the helm client only (without deploying Tiller) func initHelmClientOnly() (bool, string) { cmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm init --client-only "}, + Cmd: "helm", + Args: []string{"init", "--client-only"}, Description: "initializing helm on the client only.", } @@ -602,15 +606,15 @@ func cleanUntrackedReleases() { // deleteUntrackedRelease creates the helm command to purge delete an untracked release func deleteUntrackedRelease(release *release, tillerNamespace string) { - tls := "" + tls := []string{} ns := s.Namespaces[tillerNamespace] if tillerTLSEnabled(ns) { - tls = " --tls --tls-ca-cert " + tillerNamespace + "-ca.cert --tls-cert " + tillerNamespace + "-client.cert --tls-key " + tillerNamespace + "-client.key " + tls = []string{"--tls", "--tls-ca-cert", tillerNamespace + "-ca.cert", "--tls-cert", tillerNamespace + "-client.cert", "--tls-key", tillerNamespace + "-client.key"} } cmd := command{ - Cmd: "bash", - Args: []string{"-c", helmCommand(tillerNamespace) + " delete --purge " + release.Name + " --tiller-namespace " + tillerNamespace + tls + getDryRunFlags()}, + Cmd: "helm", + Args: concat(helmCommand(tillerNamespace), []string{"delete", "--purge", release.Name, "--tiller-namespace", tillerNamespace}, tls, getDryRunFlags()), Description: generateCmdDescription(release, "deleting untracked"), } @@ -620,8 +624,8 @@ func deleteUntrackedRelease(release *release, tillerNamespace string) { // decrypt a helm secret file func decryptSecret(name string) bool { cmd := command{ - Cmd: "bash", - Args: []string{"-c", helmCommand("") + " secrets dec " + name}, + Cmd: "helm", + Args: concat(helmCommand(""), []string{"secrets", "dec", name}), Description: "Decrypting " + name, } @@ -637,8 +641,8 @@ func decryptSecret(name string) bool { // updateChartDep updates dependencies for a local chart func updateChartDep(chartPath string) (bool, string) { cmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm dependency update " + chartPath}, + Cmd: "helm", + Args: []string{"dependency", "update", chartPath}, Description: "Updateing dependency for local chart " + chartPath, } diff --git a/helm_helpers_test.go b/helm_helpers_test.go index 547c91e3..0a7b62ad 100644 --- a/helm_helpers_test.go +++ b/helm_helpers_test.go @@ -9,11 +9,11 @@ import ( func setupTestCase(t *testing.T) func(t *testing.T) { t.Log("setup test case") - os.MkdirAll("/tmp/helmsman-tests/myapp", os.ModePerm) - os.MkdirAll("/tmp/helmsman-tests/dir-with space/myapp", os.ModePerm) + os.MkdirAll(os.TempDir()+"/helmsman-tests/myapp", os.ModePerm) + os.MkdirAll(os.TempDir()+"/helmsman-tests/dir-with space/myapp", os.ModePerm) cmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm create '/tmp/helmsman-tests/dir-with space/myapp'"}, + Cmd: "helm", + Args: []string{"create", os.TempDir() + "/helmsman-tests/dir-with space/myapp"}, Description: "creating an empty local chart directory", } if exitCode, msg := cmd.exec(debug, verbose); exitCode != 0 { @@ -30,13 +30,13 @@ func Test_validateReleaseCharts(t *testing.T) { apps map[string]*release } tests := []struct { - name string + name string targetFlag []string - args args - want bool + args args + want bool }{ { - name: "test case 1: valid local path with no chart", + name: "test case 1: valid local path with no chart", targetFlag: []string{}, args: args{ apps: map[string]*release{ @@ -45,7 +45,7 @@ func Test_validateReleaseCharts(t *testing.T) { Description: "", Namespace: "", Enabled: true, - Chart: "/tmp/helmsman-tests/myapp", + Chart: os.TempDir() + "/helmsman-tests/myapp", Version: "", ValuesFile: "", ValuesFiles: []string{}, @@ -67,7 +67,7 @@ func Test_validateReleaseCharts(t *testing.T) { }, want: false, }, { - name: "test case 2: invalid local path", + name: "test case 2: invalid local path", targetFlag: []string{}, args: args{ apps: map[string]*release{ @@ -76,7 +76,7 @@ func Test_validateReleaseCharts(t *testing.T) { Description: "", Namespace: "", Enabled: true, - Chart: "/tmp/does-not-exist/myapp", + Chart: os.TempDir() + "/does-not-exist/myapp", Version: "", ValuesFile: "", ValuesFiles: []string{}, @@ -98,7 +98,7 @@ func Test_validateReleaseCharts(t *testing.T) { }, want: false, }, { - name: "test case 3: valid chart local path with whitespace", + name: "test case 3: valid chart local path with whitespace", targetFlag: []string{}, args: args{ apps: map[string]*release{ @@ -107,7 +107,7 @@ func Test_validateReleaseCharts(t *testing.T) { Description: "", Namespace: "", Enabled: true, - Chart: "/tmp/helmsman-tests/dir-with space/myapp", + Chart: os.TempDir() + "/helmsman-tests/dir-with space/myapp", Version: "0.1.0", ValuesFile: "", ValuesFiles: []string{}, @@ -129,7 +129,7 @@ func Test_validateReleaseCharts(t *testing.T) { }, want: true, }, { - name: "test case 4: valid chart from repo", + name: "test case 4: valid chart from repo", targetFlag: []string{}, args: args{ apps: map[string]*release{ @@ -160,7 +160,7 @@ func Test_validateReleaseCharts(t *testing.T) { }, want: true, }, { - name: "test case 5: invalid local path for chart ignored with -target flag, while other app was targeted", + name: "test case 5: invalid local path for chart ignored with -target flag, while other app was targeted", targetFlag: []string{"notThisOne"}, args: args{ apps: map[string]*release{ @@ -169,7 +169,7 @@ func Test_validateReleaseCharts(t *testing.T) { Description: "", Namespace: "", Enabled: true, - Chart: "/tmp/does-not-exist/myapp", + Chart: os.TempDir() + "/does-not-exist/myapp", Version: "", ValuesFile: "", ValuesFiles: []string{}, @@ -191,7 +191,7 @@ func Test_validateReleaseCharts(t *testing.T) { }, want: true, }, { - name: "test case 6: invalid local path for chart included with -target flag", + name: "test case 6: invalid local path for chart included with -target flag", targetFlag: []string{"app"}, args: args{ apps: map[string]*release{ @@ -200,7 +200,7 @@ func Test_validateReleaseCharts(t *testing.T) { Description: "", Namespace: "", Enabled: true, - Chart: "/tmp/does-not-exist/myapp", + Chart: os.TempDir() + "/does-not-exist/myapp", Version: "", ValuesFile: "", ValuesFiles: []string{}, @@ -234,7 +234,7 @@ func Test_validateReleaseCharts(t *testing.T) { targetMap[target] = true } if got, msg := validateReleaseCharts(tt.args.apps); got != tt.want { - t.Errorf("getReleaseChartName() = %v, want %v , msg: %v", got, tt.want, msg) + t.Errorf("validateReleaseCharts() = %v, want %v , msg: %v", got, tt.want, msg) } }) } @@ -335,7 +335,7 @@ func Test_getReleaseChartVersion(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Log(tt.want) if got := getReleaseChartVersion(tt.args.r); got != tt.want { - t.Errorf("getReleaseChartName() = %v, want %v", got, tt.want) + t.Errorf("getReleaseChartVersion() = %v, want %v", got, tt.want) } }) } @@ -344,7 +344,6 @@ func Test_getReleaseChartVersion(t *testing.T) { func Test_getChartVersion(t *testing.T) { // version string = the first semver-valid string after the last hypen in the chart string. - type args struct { r *release } diff --git a/init.go b/init.go index 6dcb97b7..8e7ed93e 100644 --- a/init.go +++ b/init.go @@ -212,8 +212,8 @@ func init() { // It takes as input the tool's command to check if it is recognizable or not. e.g. helm or kubectl func toolExists(tool string) bool { cmd := command{ - Cmd: "bash", - Args: []string{"-c", tool}, + Cmd: tool, + Args: []string{}, Description: "validating that " + tool + " is installed.", } @@ -230,8 +230,8 @@ func toolExists(tool string) bool { // It takes as input the plugin's name to check if it is recognizable or not. e.g. diff func helmPluginExists(plugin string) bool { cmd := command{ - Cmd: "bash", - Args: []string{"-c", "helm plugin list"}, + Cmd: "helm", + Args: []string{"plugin", "list"}, Description: "validating that " + plugin + " is installed.", } diff --git a/init_test.go b/init_test.go index c27815ce..35028b1d 100644 --- a/init_test.go +++ b/init_test.go @@ -24,9 +24,9 @@ func Test_toolExists(t *testing.T) { }, want: true, }, { - name: "test case 3 -- checking ipconfig exists.", + name: "test case 3 -- checking nonExistingTool exists.", args: args{ - tool: "ipconfig", + tool: "nonExistingTool", }, want: false, }, diff --git a/kube_helpers.go b/kube_helpers.go index 33893a91..fe11e0af 100644 --- a/kube_helpers.go +++ b/kube_helpers.go @@ -14,11 +14,11 @@ func validateServiceAccount(sa string, namespace string) (bool, string) { if namespace == "" { namespace = "default" } - ns := " -n " + namespace + ns := []string{"-n", namespace} cmd := command{ - Cmd: "bash", - Args: []string{"-c", "kubectl get serviceaccount " + sa + ns}, + Cmd: "kubectl", + Args: append([]string{"get", "serviceaccount", sa}, ns...), Description: "validating if serviceaccount [ " + sa + " ] exists in namespace [ " + namespace + " ].", } @@ -88,8 +88,8 @@ func overrideAppsNamespace(newNs string) { // createNamespace creates a namespace in the k8s cluster func createNamespace(ns string) { cmd := command{ - Cmd: "bash", - Args: []string{"-c", "kubectl create namespace " + ns}, + Cmd: "kubectl", + Args: []string{"create", "namespace", ns}, Description: "creating namespace " + ns, } exitCode, _ := cmd.exec(debug, verbose) @@ -103,8 +103,8 @@ func createNamespace(ns string) { func labelNamespace(ns string, labels map[string]string) { for k, v := range labels { cmd := command{ - Cmd: "bash", - Args: []string{"-c", "kubectl label --overwrite namespace/" + ns + " " + k + "=" + v}, + Cmd: "kubectl", + Args: []string{"label", "--overwrite", "namespace/" + ns, k + "=" + v}, Description: "labeling namespace " + ns, } @@ -120,8 +120,8 @@ func labelNamespace(ns string, labels map[string]string) { func annotateNamespace(ns string, labels map[string]string) { for k, v := range labels { cmd := command{ - Cmd: "bash", - Args: []string{"-c", "kubectl annotate --overwrite namespace/" + ns + " " + k + "=" + v}, + Cmd: "kubectl", + Args: []string{"annotate", "--overwrite", "namespace/" + ns, k + "=" + v}, Description: "annotating namespace " + ns, } @@ -161,8 +161,8 @@ spec: } cmd := command{ - Cmd: "bash", - Args: []string{"-c", "kubectl apply -f temp-LimitRange.yaml -n " + ns}, + Cmd: "kubectl", + Args: []string{"apply", "-f", "temp-LimitRange.yaml", "-n", ns}, Description: "creating LimitRange in namespace [ " + ns + " ]", } @@ -232,23 +232,23 @@ func createContext() (bool, string) { } // connecting to the cluster - setCredentialsCmd := "" + setCredentialsCmdArgs := []string{} if s.Settings.BearerToken { token := readFile(tokenPath) if s.Settings.Username == "" { s.Settings.Username = "helmsman" } - setCredentialsCmd = "kubectl config set-credentials " + s.Settings.Username + " --token=" + token + setCredentialsCmdArgs = append(setCredentialsCmdArgs, "config", "set-credentials", s.Settings.Username, "--token="+token) } else { - setCredentialsCmd = "kubectl config set-credentials " + s.Settings.Username + " --username=" + s.Settings.Username + - " --password=" + s.Settings.Password + " --client-key=" + caKey + setCredentialsCmdArgs = append(setCredentialsCmdArgs, "config", "set-credentials", s.Settings.Username, "--username="+s.Settings.Username, + "--password="+s.Settings.Password, "--client-key="+caKey) if caClient != "" { - setCredentialsCmd = setCredentialsCmd + " --client-certificate=" + caClient + setCredentialsCmdArgs = append(setCredentialsCmdArgs, "--client-certificate="+caClient) } } cmd := command{ - Cmd: "bash", - Args: []string{"-c", setCredentialsCmd}, + Cmd: "kubectl", + Args: setCredentialsCmdArgs, Description: "creating kubectl context - setting credentials.", } @@ -257,9 +257,8 @@ func createContext() (bool, string) { } cmd = command{ - Cmd: "bash", - Args: []string{"-c", "kubectl config set-cluster " + s.Settings.KubeContext + " --server=" + s.Settings.ClusterURI + - " --certificate-authority=" + caCrt}, + Cmd: "kubectl", + Args: []string{"config", "set-cluster", s.Settings.KubeContext, "--server=" + s.Settings.ClusterURI, "--certificate-authority=" + caCrt}, Description: "creating kubectl context - setting cluster.", } @@ -268,9 +267,8 @@ func createContext() (bool, string) { } cmd = command{ - Cmd: "bash", - Args: []string{"-c", "kubectl config set-context " + s.Settings.KubeContext + " --cluster=" + s.Settings.KubeContext + - " --user=" + s.Settings.Username}, + Cmd: "kubectl", + Args: []string{"config", "set-context", s.Settings.KubeContext, "--cluster=" + s.Settings.KubeContext, "--user=" + s.Settings.Username}, Description: "creating kubectl context - setting context.", } @@ -293,8 +291,8 @@ func setKubeContext(context string) bool { } cmd := command{ - Cmd: "bash", - Args: []string{"-c", "kubectl config use-context " + context}, + Cmd: "kubectl", + Args: []string{"config", "use-context", context}, Description: "setting kubectl context to [ " + context + " ]", } @@ -312,8 +310,8 @@ func setKubeContext(context string) bool { // It returns false if no context is set. func getKubeContext() bool { cmd := command{ - Cmd: "bash", - Args: []string{"-c", "kubectl config current-context"}, + Cmd: "kubectl", + Args: []string{"config", "current-context"}, Description: "getting kubectl context", } @@ -330,8 +328,8 @@ func getKubeContext() bool { // createServiceAccount creates a service account in a given namespace and associates it with a cluster-admin role func createServiceAccount(saName string, namespace string) (bool, string) { cmd := command{ - Cmd: "bash", - Args: []string{"-c", "kubectl create serviceaccount -n " + namespace + " " + saName}, + Cmd: "kubectl", + Args: []string{"create", "serviceaccount", "-n", namespace, saName}, Description: "creating service account [ " + saName + " ] in namespace [ " + namespace + " ]", } @@ -364,8 +362,8 @@ func createRoleBinding(role string, saName string, namespace string) (bool, stri log.Println("INFO: creating " + resource + " for service account [ " + saName + " ] in namespace [ " + namespace + " ] with role: " + role + ".") cmd := command{ - Cmd: "bash", - Args: []string{"-c", "kubectl create " + resource + " " + bindingName + " " + bindingOption + " --serviceaccount " + namespace + ":" + saName + " -n " + namespace}, + Cmd: "kubectl", + Args: []string{"create", resource, bindingName, bindingOption, "--serviceaccount", namespace + ":" + saName, "-n", namespace}, Description: "creating " + resource + " for service account [ " + saName + " ] in namespace [ " + namespace + " ] with role: " + role, } @@ -396,8 +394,8 @@ func createRole(namespace string, role string, roleTemplateFile string) (bool, s replaceStringInFile(resource, "temp-modified-role.yaml", map[string]string{"<>": namespace, "<>": role}) cmd := command{ - Cmd: "bash", - Args: []string{"-c", "kubectl apply -f temp-modified-role.yaml "}, + Cmd: "kubectl", + Args: []string{"apply", "-f", "temp-modified-role.yaml"}, Description: "creating role [" + role + "] in namespace [ " + namespace + " ]", } @@ -423,8 +421,8 @@ func labelResource(r *release) { } cmd := command{ - Cmd: "bash", - Args: []string{"-c", "kubectl label " + storageBackend + " -n " + getDesiredTillerNamespace(r) + " -l NAME=" + r.Name + " MANAGED-BY=HELMSMAN NAMESPACE=" + r.Namespace + " TILLER_NAMESPACE=" + getDesiredTillerNamespace(r) + " --overwrite"}, + Cmd: "kubectl", + Args: []string{"label", storageBackend, "-n", getDesiredTillerNamespace(r), "-l", "NAME=" + r.Name, "MANAGED-BY=HELMSMAN", "NAMESPACE=" + r.Namespace, "TILLER_NAMESPACE=" + getDesiredTillerNamespace(r), "--overwrite"}, Description: "applying labels to Helm state in [ " + getDesiredTillerNamespace(r) + " ] for " + r.Name, } @@ -463,8 +461,8 @@ func getHelmsmanReleases() map[string]map[*release]bool { for _, ns := range namespaces { cmd := command{ - Cmd: "bash", - Args: []string{"-c", "kubectl get " + storageBackend + " -n " + ns + " -l MANAGED-BY=HELMSMAN"}, + Cmd: "kubectl", + Args: []string{"get", storageBackend, "-n", ns, "-l", "MANAGED-BY=HELMSMAN"}, Description: "getting helm releases which are managed by Helmsman in namespace [[ " + ns + " ]].", } @@ -500,8 +498,8 @@ func getHelmsmanReleases() map[string]map[*release]bool { // getKubectlClientVersion returns kubectl client version func getKubectlClientVersion() string { cmd := command{ - Cmd: "bash", - Args: []string{"-c", "kubectl version --client --short"}, + Cmd: "kubectl", + Args: []string{"version", "--client", "--short"}, Description: "checking kubectl version ", } diff --git a/release.go b/release.go index 83805afe..1a8ade0a 100644 --- a/release.go +++ b/release.go @@ -64,14 +64,15 @@ func validateRelease(appLabel string, r *release, names map[string]map[string]bo return false, "release " + r.Name + " is using namespace [ " + r.Namespace + " ] which is not defined in the Namespaces section of your desired state file." + " Release [ " + r.Name + " ] can't be installed in that Namespace until its defined." } - if r.Chart == "" || !strings.ContainsAny(r.Chart, "/") { + _, err := os.Stat(r.Chart) + if r.Chart == "" || os.IsNotExist(err) && !strings.ContainsAny(r.Chart, "/") { return false, "chart can't be empty and must be of the format: repo/chart." } if r.Version == "" { return false, "version can't be empty." } - _, err := os.Stat(r.ValuesFile) + _, err = os.Stat(r.ValuesFile) if r.ValuesFile != "" && (!isOfType(r.ValuesFile, []string{".yaml", ".yml", ".json"}) || err != nil) { return false, fmt.Sprintf("valuesFile must be a valid relative (from dsf file) file path for a yaml file, or can be left empty (provided path resolved to %q).", r.ValuesFile) } else if r.ValuesFile != "" && len(r.ValuesFiles) > 0 { diff --git a/utils.go b/utils.go index 3788c484..c5af79c3 100644 --- a/utils.go +++ b/utils.go @@ -548,3 +548,12 @@ func generateDecisionMessage(r *release, message string, isTillerAware bool) str } return baseMessage } + +// concat appends all slices to a single slice +func concat(slices ...[]string) []string { + slice := []string{} + for _, item := range slices { + slice = append(slice, item...) + } + return slice +} From 8cbe6ef0756c9e84e11f7c6ddfcc6111303c9c2b Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Wed, 23 Oct 2019 17:42:01 +0100 Subject: [PATCH 0483/1127] Fix the docker build --- Gopkg.lock | 15 --------------- Makefile | 16 +++++++++------- dockerfile/dockerfile | 5 +---- 3 files changed, 10 insertions(+), 26 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index c1307022..ba0fa895 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -41,18 +41,6 @@ revision = "3012a1dbe2e4bd1391d42b32f0577cb7bbc7f005" version = "v0.3.1" -[[projects]] - branch = "master" - digest = "1:6b0fcd019bd122e25de32a7e4c1f9ab6e5413cb8c6e508d882b211d2e2f6e629" - name = "github.com/Praqma/helmsman" - packages = [ - "aws", - "azure", - "gcs", - ] - pruneopts = "UT" - revision = "ed3dc3cc70ccc4bef59acf19d4f4fb79f38529e9" - [[projects]] digest = "1:c78f02a2c6a138255ce52eefe18b62fe4e89815e4289819582c643ce51cdbf84" name = "github.com/aws/aws-sdk-go" @@ -355,9 +343,6 @@ "github.com/Azure/azure-pipeline-go/pipeline", "github.com/Azure/azure-storage-blob-go/azblob", "github.com/BurntSushi/toml", - "github.com/Praqma/helmsman/aws", - "github.com/Praqma/helmsman/azure", - "github.com/Praqma/helmsman/gcs", "github.com/aws/aws-sdk-go/aws", "github.com/aws/aws-sdk-go/aws/session", "github.com/aws/aws-sdk-go/service/s3", diff --git a/Makefile b/Makefile index d62a00fb..ae67b49d 100644 --- a/Makefile +++ b/Makefile @@ -19,10 +19,12 @@ ifeq ($(strip $(GOPATH)),) endif SRCDIR := $(GOPATH)/src/ +PRJDIR := $(CURDIR) ifeq ($(filter $(GOPATH)%,$(CURDIR)),) GOPATH := $(shell mktemp -d "/tmp/dep.XXXXXXXX") SRCDIR := $(GOPATH)/src/ + PRJDIR := $(SRCDIR)helmsman endif ifneq ($(OS),Windows_NT) @@ -56,17 +58,17 @@ $(SRCDIR): @ln -s $(CURDIR) $(SRCDIR) dep: $(SRCDIR) ## Ensure vendors with dep - @cd $(SRCDIR)helmsman && \ + @cd $(PRJDIR) && \ dep ensure .PHONY: dep dep-update: $(SRCDIR) ## Ensure vendors with dep - @cd $(SRCDIR)helmsman && \ + @cd $(PRJDIR) && \ dep ensure --update .PHONY: dep-update build: dep ## Build the package - @cd $(SRCDIR)helmsman && \ + @cd $(PRJDIR) && \ CGO_ENABLED=0 go build -ldflags '-X main.version="${TAG}-${DATE}" -extldflags "-static"' generate: @@ -74,24 +76,24 @@ generate: .PHONY: generate check: $(SRCDIR) - @cd $(SRCDIR)helmsman && \ + @cd $(PRJDIR) && \ dep check && \ go vet #${PKGS} .PHONY: check test: dep ## Run unit tests - @cd $(SRCDIR)helmsman && \ + @cd $(PRJDIR) && \ helm init --client-only && \ CGO_ENABLED=0 go test -v -cover -p=1 -args -f example.toml .PHONY: test cross: dep ## Create binaries for all OSs - @cd $(SRCDIR)helmsman && \ + @cd $(PRJDIR) && \ env CGO_ENABLED=0 gox -os '!freebsd !netbsd' -arch '!arm' -output "dist/{{.Dir}}_{{.OS}}_{{.Arch}}" -ldflags '-X main.Version=${TAG}-${DATE}' .PHONY: cross release: dep ## Generate a new release - @cd $(SRCDIR)helmsman && \ + @cd $(PRJDIR) && \ goreleaser --release-notes release-notes.md --rm-dist tools: ## Get extra tools used by this makefile diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile index ea362bbd..b0f9610a 100644 --- a/dockerfile/dockerfile +++ b/dockerfile/dockerfile @@ -15,10 +15,7 @@ RUN cd helmsman \ && LT_SHA=$(git rev-parse ${LastTag}^{}) \ && LC_SHA=$(git rev-parse HEAD) \ && if [ ${LT_SHA} != ${LC_SHA} ]; then TAG=latest-$(date +"%d%m%y"); fi \ - && dep ensure \ && make build - # && CGO_ENABLED=0 GOOS=linux go install -a -ldflags '-X main.version='$TAG' -extldflags "-static"' . - # The image to keep FROM alpine:3.10 @@ -41,7 +38,7 @@ RUN apk --no-cache update \ && rm -rf /tmp/linux-amd64 \ && chmod +x /usr/local/bin/helm -COPY --from=builder /go/bin/helmsman /bin/helmsman +COPY --from=builder /go/src/helmsman/helmsman /bin/helmsman RUN mkdir -p ~/.helm/plugins \ && helm plugin install https://github.com/hypnoglow/helm-s3.git \ From 043dbf46956e4fcbacf10f7320e28dd36239c9aa Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Wed, 23 Oct 2019 13:58:18 +0200 Subject: [PATCH 0484/1127] Add -group flag to limit execution of deployments to specific group of apps --- decision_maker.go | 10 ++- decision_maker_test.go | 67 +++++++++++++++++++ docs/cmd_reference.md | 3 + docs/desired_state_specification.md | 3 + docs/how_to/README.md | 1 + ...it-deployment-to-specific-group-of-apps.md | 50 ++++++++++++++ helm_helpers.go | 12 ++-- helm_helpers_test.go | 6 +- init.go | 12 ++++ main.go | 2 + release.go | 1 + utils.go | 18 +++++ 12 files changed, 170 insertions(+), 15 deletions(-) create mode 100644 docs/how_to/misc/limit-deployment-to-specific-group-of-apps.md diff --git a/decision_maker.go b/decision_maker.go index bb55a396..e4a9be1f 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -29,12 +29,10 @@ func makePlan(s *state) *plan { // decide makes a decision about what commands (actions) need to be executed // to make a release section of the desired state come true. func decide(r *release, s *state) { - // check for presence in defined targets - if len(targetMap) > 0 { - if _, ok := targetMap[r.Name]; !ok { - logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] is ignored by target flag. Skipping.", false), r.Priority, ignored) - return - } + // check for presence in defined targets or groups + if !isReleaseConsideredToRun(r) { + logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] is ignored due to passed constraints. Skipping.", false), r.Priority, ignored) + return } if destroy { diff --git a/decision_maker_test.go b/decision_maker_test.go index 79107f30..94f9d2e5 100644 --- a/decision_maker_test.go +++ b/decision_maker_test.go @@ -223,6 +223,73 @@ func Test_decide(t *testing.T) { } } +func Test_decide_group(t *testing.T) { + type args struct { + r *release + s *state + } + tests := []struct { + name string + groupFlag []string + targetFlag []string + args args + want decisionType + }{ + { + name: "decide() - groupMap does not contain this service - skip", + groupFlag: []string{"some-group"}, + args: args{ + r: &release{ + Name: "release1", + Namespace: "namespace", + Enabled: true, + }, + s: &state{}, + }, + want: ignored, + }, + { + name: "decide() - groupMap contains this service - proceed", + groupFlag: []string{"run-me"}, + args: args{ + r: &release{ + Name: "release1", + Namespace: "namespace", + Enabled: true, + Group: "run-me", + }, + s: &state{}, + }, + want: create, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + groupMap = make(map[string]bool) + targetMap = make(map[string]bool) + + for _, target := range tt.targetFlag { + groupMap[target] = true + } + for _, group := range tt.groupFlag { + groupMap[group] = true + } + outcome = plan{} + + // Act + decide(tt.args.r, tt.args.s) + got := outcome.Decisions[0].Type + t.Log(outcome.Decisions[0].Description) + + // Assert + if got != tt.want { + t.Errorf("decide() = %s, want %s", got, tt.want) + } + }) + } +} + // String allows for pretty printing decisionType const func (dt decisionType) String() string { switch dt { diff --git a/docs/cmd_reference.md b/docs/cmd_reference.md index 9544ba61..f706b471 100644 --- a/docs/cmd_reference.md +++ b/docs/cmd_reference.md @@ -76,6 +76,9 @@ This lists available CMD options in Helmsman: `--target` limit execution to specific app. + + `--group` + limit execution to specific group of apps. `--update-deps` run 'helm dep up' for local chart diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index fa1a4aa3..88e6db6e 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -374,6 +374,7 @@ Options: - **version** : the chart version. **Optional** +- **group** : group name this apps belongs to. It has no effect until Helmsman's flag `-group` is passed. Check this [doc](how_to/misc/limit-deployment-to-specific-group-of-apps.md) for more details. - **tillerNamespace** : which Tiller to use for deploying this release. This is available starting from v1.4.0-rc The decision on which Tiller to use for deploying a release follows the following criteria: 1. If `tillerNamespace`is explicitly defined, it is used. 2. If `tillerNamespace`is not defined and the namespace in which the release will be deployed has a Tiller installed by Helmsman (i.e. has `installTiller set to true` in the [Namespaces](#namespaces) section), Tiller in that namespace is used. @@ -412,6 +413,7 @@ Example: description = "jenkins" namespace = "staging" enabled = true + group = "critical" chart = "stable/jenkins" version = "0.9.0" valuesFile = "" @@ -438,6 +440,7 @@ apps: description: "jenkins" namespace: "staging" enabled: true + group: "critical" chart: "stable/jenkins" version: "0.9.0" valuesFile: "" diff --git a/docs/how_to/README.md b/docs/how_to/README.md index 9e67ff87..f566b336 100644 --- a/docs/how_to/README.md +++ b/docs/how_to/README.md @@ -46,5 +46,6 @@ This page contains a list of guides on how to use Helmsman. - [Send slack notifications from Helmsman](misc/send_slack_notifications_from_helmsman.md) - [Merge multiple desired state files](misc/merge_desired_state_files.md) - [Limit Helmsman deployment to specific apps](misc/limit-deployment-to-specific-apps.md) + - [Limit Helmsman deployment to specific group of apps](misc/limit-deployment-to-specific-group-of-apps.md) - [Multi-tenant clusters guide](misc/multitenant_clusters_guide.md) - [Helmsman on Windows 10](misc/helmsman_on_windows10.md) diff --git a/docs/how_to/misc/limit-deployment-to-specific-group-of-apps.md b/docs/how_to/misc/limit-deployment-to-specific-group-of-apps.md new file mode 100644 index 00000000..009dbd86 --- /dev/null +++ b/docs/how_to/misc/limit-deployment-to-specific-group-of-apps.md @@ -0,0 +1,50 @@ +--- +version: v1.13.0 +--- + +# Limit execution to explicitly defined group of apps + +Starting from v1.13.0, Helmsman allows you to pass the `-group` flag to specify group of apps +the execution of Helmsman deployment will be limited to. +Thanks to this one can deploy specific applications among all defined for an environment. + +## Example + +Having environment defined with such apps: + +* example.yaml: +```yaml +# ... +apps: + jenkins: + namespace: "staging" # maps to the namespace as defined in namespaces above + group: "critical" # group name + enabled: true # change to false if you want to delete this app release empty: false: + chart: "stable/jenkins" # changing the chart name means delete and recreate this chart + version: "0.14.3" # chart version + + artifactory: + namespace: "production" # maps to the namespace as defined in namespaces above + group: "sidecar" # group name + enabled: true # change to false if you want to delete this app release empty: false: + chart: "stable/artifactory" # changing the chart name means delete and recreate this chart + version: "7.0.6" # chart version +# ... +``` + +running Helmsman with `-f example.yaml` would result in checking state and invoking deployment for both jenkins and artifactory application. + +With `-group` flag in command like + +```shell +$ helmsman -f example.yaml -group critical ... +``` + +one can execute Helmsman's environment defined with example.yaml limited to only one `jenkins` app, since its group is `critical`. +Others are ignored until the flag is defined. + +Multiple applications can be set with `-group`, like + +```shell +$ helmsman -f example.yaml -group critical -group sidecar ... +``` diff --git a/helm_helpers.go b/helm_helpers.go index 71439965..0edbf60e 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -272,10 +272,8 @@ func validateReleaseCharts(apps map[string]*release) (bool, string) { for app, r := range apps { validateCurrentChart := true - if len(targetMap) > 0 { - if _, ok := targetMap[r.Name]; !ok { - validateCurrentChart = false - } + if !isReleaseConsideredToRun(r) { + validateCurrentChart = false } if validateCurrentChart { if isLocalChart(r.Chart) { @@ -590,10 +588,8 @@ func cleanUntrackedReleases() { } else { for ns, releases := range toDelete { for r := range releases { - if len(targetMap) > 0 { - if _, inTarget := targetMap[r.Name]; !inTarget { - logDecision(generateDecisionMessage(r, "untracked release [ "+r.Name+" ] is ignored by target flag. Skipping.", false), -800, ignored) - } + if isReleaseConsideredToRun(r) { + logDecision(generateDecisionMessage(r, "untracked release [ "+r.Name+" ] is ignored by target flag. Skipping.", false), -800, ignored) } else { logDecision(generateDecisionMessage(r, "untracked release found: release [ "+r.Name+" ]. It will be deleted", true), -800, delete) deleteUntrackedRelease(r, ns) diff --git a/helm_helpers_test.go b/helm_helpers_test.go index 0a7b62ad..5369960c 100644 --- a/helm_helpers_test.go +++ b/helm_helpers_test.go @@ -32,6 +32,7 @@ func Test_validateReleaseCharts(t *testing.T) { tests := []struct { name string targetFlag []string + groupFlag []string args args want bool }{ @@ -229,10 +230,13 @@ func Test_validateReleaseCharts(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { targetMap = make(map[string]bool) - + groupMap = make(map[string]bool) for _, target := range tt.targetFlag { targetMap[target] = true } + for _, group := range tt.groupFlag { + groupMap[group] = true + } if got, msg := validateReleaseCharts(tt.args.apps); got != tt.want { t.Errorf("validateReleaseCharts() = %v, want %v , msg: %v", got, tt.want, msg) } diff --git a/init.go b/init.go index 8e7ed93e..d5265459 100644 --- a/init.go +++ b/init.go @@ -45,6 +45,7 @@ func init() { flag.BoolVar(&debug, "debug", false, "show the execution logs") flag.BoolVar(&dryRun, "dry-run", false, "apply the dry-run option for helm commands.") flag.Var(&target, "target", "limit execution to specific app.") + flag.Var(&group, "group", "limit execution to specific group of apps.") flag.BoolVar(&destroy, "destroy", false, "delete all deployed releases. Purge delete is used if the purge option is set to true for the releases.") flag.BoolVar(&v, "v", false, "show the version") flag.BoolVar(&verbose, "verbose", false, "show verbose execution logs") @@ -89,6 +90,10 @@ func init() { logError("ERROR: --destroy and --apply can't be used together.") } + if len(target) > 0 && len(group) > 0 { + logError("ERROR: --target and --group can't be used together.") + } + helmVersion = strings.TrimSpace(strings.SplitN(getHelmClientVersion(), ": ", 2)[1]) kubectlVersion = strings.TrimSpace(strings.SplitN(getKubectlClientVersion(), ": ", 2)[1]) @@ -206,6 +211,13 @@ func init() { } } + if len(group) > 0 { + groupMap = map[string]bool{} + for _, v := range group { + groupMap[v] = true + } + } + } // toolExists returns true if the tool is present in the environment and false otherwise. diff --git a/main.go b/main.go index e87f492e..190d5af1 100644 --- a/main.go +++ b/main.go @@ -38,7 +38,9 @@ var helmVersion string var kubectlVersion string var dryRun bool var target stringArray +var group stringArray var targetMap map[string]bool +var groupMap map[string]bool var destroy bool var showDiff bool var suppressDiffSecrets bool diff --git a/release.go b/release.go index 1a8ade0a..143a608f 100644 --- a/release.go +++ b/release.go @@ -15,6 +15,7 @@ type release struct { Description string `yaml:"description"` Namespace string `yaml:"namespace"` Enabled bool `yaml:"enabled"` + Group string `yaml:"group"` Chart string `yaml:"chart"` Version string `yaml:"version"` ValuesFile string `yaml:"valuesFile"` diff --git a/utils.go b/utils.go index c5af79c3..7925878f 100644 --- a/utils.go +++ b/utils.go @@ -557,3 +557,21 @@ func concat(slices ...[]string) []string { } return slice } + +func isReleaseConsideredToRun(r *release) bool { + if len(targetMap) > 0 { + if _, ok := targetMap[r.Name]; ok { + return true + } else { + return false + } + } + if len(groupMap) > 0 { + if _, ok := groupMap[r.Group]; ok { + return true + } else { + return false + } + } + return true +} From 71b745648dae1df77eb7ab5dcd894b1cc54f0eb1 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Thu, 24 Oct 2019 13:08:26 +0200 Subject: [PATCH 0485/1127] Set isReleaseConsideredToRun to be release's method --- decision_maker.go | 2 +- helm_helpers.go | 4 ++-- release.go | 18 ++++++++++++++++++ utils.go | 18 ------------------ 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/decision_maker.go b/decision_maker.go index e4a9be1f..d76ed8a8 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -30,7 +30,7 @@ func makePlan(s *state) *plan { // to make a release section of the desired state come true. func decide(r *release, s *state) { // check for presence in defined targets or groups - if !isReleaseConsideredToRun(r) { + if !r.isReleaseConsideredToRun() { logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] is ignored due to passed constraints. Skipping.", false), r.Priority, ignored) return } diff --git a/helm_helpers.go b/helm_helpers.go index 0edbf60e..a5241011 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -272,7 +272,7 @@ func validateReleaseCharts(apps map[string]*release) (bool, string) { for app, r := range apps { validateCurrentChart := true - if !isReleaseConsideredToRun(r) { + if !r.isReleaseConsideredToRun() { validateCurrentChart = false } if validateCurrentChart { @@ -588,7 +588,7 @@ func cleanUntrackedReleases() { } else { for ns, releases := range toDelete { for r := range releases { - if isReleaseConsideredToRun(r) { + if r.isReleaseConsideredToRun() { logDecision(generateDecisionMessage(r, "untracked release [ "+r.Name+" ] is ignored by target flag. Skipping.", false), -800, ignored) } else { logDecision(generateDecisionMessage(r, "untracked release found: release [ "+r.Name+" ]. It will be deleted", true), -800, delete) diff --git a/release.go b/release.go index 143a608f..367798cc 100644 --- a/release.go +++ b/release.go @@ -35,6 +35,24 @@ type release struct { Timeout int `yaml:"timeout"` } +func (r *release) isReleaseConsideredToRun() bool { + if len(targetMap) > 0 { + if _, ok := targetMap[r.Name]; ok { + return true + } else { + return false + } + } + if len(groupMap) > 0 { + if _, ok := groupMap[r.Group]; ok { + return true + } else { + return false + } + } + return true +} + // validateRelease validates if a release inside a desired state meets the specifications or not. // check the full specification @ https://github.com/Praqma/helmsman/docs/desired_state_spec.md func validateRelease(appLabel string, r *release, names map[string]map[string]bool, s state) (bool, string) { diff --git a/utils.go b/utils.go index 7925878f..c5af79c3 100644 --- a/utils.go +++ b/utils.go @@ -557,21 +557,3 @@ func concat(slices ...[]string) []string { } return slice } - -func isReleaseConsideredToRun(r *release) bool { - if len(targetMap) > 0 { - if _, ok := targetMap[r.Name]; ok { - return true - } else { - return false - } - } - if len(groupMap) > 0 { - if _, ok := groupMap[r.Group]; ok { - return true - } else { - return false - } - } - return true -} From 996285caaf5b726c2c8539accea9d521a47d5153 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Thu, 24 Oct 2019 17:46:42 +0100 Subject: [PATCH 0486/1127] Fixing some issues when running in tillerless mode --- Gopkg.toml | 7 +------ helm_helpers.go | 46 ++++++++++++++++++++++++++++++++++++---------- main.go | 1 + 3 files changed, 38 insertions(+), 16 deletions(-) diff --git a/Gopkg.toml b/Gopkg.toml index d22cf5ba..09f5bdf1 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -27,7 +27,7 @@ [[constraint]] version = "0.3.0" name = "github.com/Azure/azure-storage-blob-go" - + [[constraint]] name = "cloud.google.com/go" version = "0.28.0" @@ -36,11 +36,6 @@ name = "github.com/BurntSushi/toml" version = "0.3.1" -[[constraint]] - name = "github.com/Praqma/helmsman" - #version = "1.7.4" - branch = "master" - [[constraint]] name = "github.com/aws/aws-sdk-go" version = "1.15.43" diff --git a/helm_helpers.go b/helm_helpers.go index 71439965..63839279 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -334,19 +334,30 @@ func getChartVersion(r *release) (string, string) { Description: "getting latest chart version " + r.Chart + "-" + r.Version + "", } - if exitCode, result := cmd.exec(debug, verbose); exitCode != 0 || strings.Contains(result, "No results found") { + var ( + exitCode int + result string + ) + + if exitCode, result = cmd.exec(debug, verbose); exitCode != 0 || strings.Contains(result, "No results found") { return "", "ERROR: chart " + r.Chart + " with version " + r.Version + " is specified but not found in the helm repos." - } else { - versions := strings.Split(result, "\n") - if len(versions) < 2 { - return "", "ERROR: chart " + r.Chart + " with version " + r.Version + " is specified but not found in the helm repos (unrecognized helm output?)." - } - fields := strings.Split(versions[1], "\t") - if len(fields) != 4 { - return "", "ERROR: chart " + r.Chart + " with version " + r.Version + " is specified but not found in the helm repos (unrecognized helm output?)." + } + versions := strings.Split(result, "\n") + if len(versions) < 2 { + return "", "ERROR: chart " + r.Chart + " with version " + r.Version + " is specified but not found in the helm repos (unrecognized helm output?)." + } + for i, l := range versions { + if l == "" || (strings.HasPrefix(strings.TrimSpace(l), "WARNING") && strings.HasSuffix(strings.TrimSpace(l), "CHART VERSION")) { + continue + } else { + fields := strings.Split(versions[i], "\t") + if len(fields) != 4 { + return "", "ERROR: chart " + r.Chart + " with version " + r.Version + " is specified but not found in the helm repos (unrecognized helm output?)." + } + return strings.TrimSpace(fields[1]), "" } - return strings.TrimSpace(fields[1]), "" } + return "", "ERROR: chart " + r.Chart + " with version " + r.Version + " is specified but not found in the helm repos." } // waitForTiller keeps checking if the helm Tiller is ready or not by executing helm list and checking its error (if any) @@ -522,6 +533,21 @@ func initHelmClientOnly() (bool, string) { return true, "" } +// initHelmTiller initializes the helm tiller plugin +func initHelmTiller() (bool, string) { + cmd := command{ + Cmd: "helm", + Args: []string{"tiler", "install"}, + Description: "initializing helm tiller plugin.", + } + + if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { + return false, "ERROR: initializing helm tiller plugin : " + err + } + + return true, "" +} + // initHelm initializes helm on a k8s cluster and deploys Tiller in one or more namespaces func initHelm() (bool, string) { defaultSA := s.Settings.ServiceAccount diff --git a/main.go b/main.go index e87f492e..7e8d7690 100644 --- a/main.go +++ b/main.go @@ -95,6 +95,7 @@ func main() { } } else { log.Println("INFO: running in TILLERLESS mode") + initHelmTiller() } } From c7524448ac84883120135608390b4d474854dcbf Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Thu, 24 Oct 2019 19:04:22 +0100 Subject: [PATCH 0487/1127] Update helm_helpers.go --- helm_helpers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helm_helpers.go b/helm_helpers.go index 050dcd9b..abb3b07a 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -535,7 +535,7 @@ func initHelmClientOnly() (bool, string) { func initHelmTiller() (bool, string) { cmd := command{ Cmd: "helm", - Args: []string{"tiler", "install"}, + Args: []string{"tiller", "install"}, Description: "initializing helm tiller plugin.", } From 81e977ffa90d7671a1f24de61fd452f84f6feb34 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Fri, 25 Oct 2019 11:01:12 +0100 Subject: [PATCH 0488/1127] fix tests for go 1.13 --- .circleci/config.yml | 11 ++++++++++- Makefile | 25 ++++++++++++++----------- bindata.go | 2 +- decision_maker.go | 3 +-- decision_maker_test.go | 20 ++++++++++---------- dockerfile/dockerfile | 4 ++-- helm_helpers_test.go | 13 ++++++------- init_test.go | 5 +++++ namespace.go | 30 +++++++++++++++--------------- state_test.go | 34 +++++++++++++++++----------------- test_files/dockerfile | 3 ++- utils.go | 4 ++-- 12 files changed, 85 insertions(+), 69 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b6730ea4..a43617eb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -44,6 +44,8 @@ jobs: command: | TAG=$(git describe --abbrev=0 --tags) docker login -u $DOCKER_USER -p $DOCKER_PASS + docker build -t praqma/helmsman:$TAG-helm-v2.15.1 --build-arg HELM_VERSION=v2.15.1 --build-arg HELM_DIFF_VERSION=master dockerfile/. --no-cache + docker push praqma/helmsman:$TAG-helm-v2.15.1 docker build -t praqma/helmsman:$TAG-helm-v2.14.3 --build-arg HELM_VERSION=v2.14.3 --build-arg HELM_DIFF_VERSION=master dockerfile/. --no-cache docker push praqma/helmsman:$TAG-helm-v2.14.3 docker build -t praqma/helmsman:$TAG-helm-v2.13.1 --build-arg HELM_VERSION=v2.13.1 --build-arg HELM_DIFF_VERSION=master dockerfile/. --no-cache @@ -55,6 +57,13 @@ jobs: docker build -t praqma/helmsman:$TAG-helm-v2.10.0 --build-arg HELM_VERSION=v2.10.0 --build-arg HELM_DIFF_VERSION=v2.10.0+1 dockerfile/. --no-cache docker push praqma/helmsman:$TAG-helm-v2.10.0 + - run: + name: build docker test images and push them to dockerhub + command: | + docker login -u $DOCKER_USER -p $DOCKER_PASS + docker build -t praqma/helmsman-test:latest test_files/. --no-cache + docker push praqma/helmsman-test:latest + workflows: version: 2 @@ -69,7 +78,7 @@ workflows: - build filters: tags: - only: /.*/ + only: /.*/ - release: requires: - test diff --git a/Makefile b/Makefile index ae67b49d..8052e2cc 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,11 @@ .DEFAULT_GOAL := help -PKGS := $(shell go list ./... | grep -v /vendor/) -TAG := $(shell git describe --always --tags --abbrev=0 HEAD) -LAST := $(shell git describe --always --tags --abbrev=0 HEAD^) -BODY := "`git log ${LAST}..HEAD --oneline --decorate` `printf '\n\#\#\# [Build Info](${BUILD_URL})'`" -DATE := $(shell date +'%d%m%y') +PKGS := $(shell go list ./... | grep -v /vendor/) +TAG := $(shell git describe --always --tags --abbrev=0 HEAD) +LAST := $(shell git describe --always --tags --abbrev=0 HEAD^) +BODY := "`git log ${LAST}..HEAD --oneline --decorate` `printf '\n\#\#\# [Build Info](${BUILD_URL})'`" +DATE := $(shell date +'%d%m%y') +PRJNAME := $(shell basename "$(PWD)") # Ensure we have an unambiguous GOPATH. GOPATH := $(shell go env GOPATH) @@ -24,7 +25,7 @@ PRJDIR := $(CURDIR) ifeq ($(filter $(GOPATH)%,$(CURDIR)),) GOPATH := $(shell mktemp -d "/tmp/dep.XXXXXXXX") SRCDIR := $(GOPATH)/src/ - PRJDIR := $(SRCDIR)helmsman + PRJDIR := $(SRCDIR)$(PRJNAME) endif ifneq ($(OS),Windows_NT) @@ -34,6 +35,8 @@ ifneq ($(OS),Windows_NT) $(if $(shell which $(exec)),some string,$(error "No $(exec) in PATH, please install $(exec)"))) endif +export CGO_ENABLED=0 + help: @echo "Available options:" @grep -E '^[/1-9a-zA-Z._%-]+:.*?## .*$$' $(MAKEFILE_LIST) \ @@ -69,27 +72,27 @@ dep-update: $(SRCDIR) ## Ensure vendors with dep build: dep ## Build the package @cd $(PRJDIR) && \ - CGO_ENABLED=0 go build -ldflags '-X main.version="${TAG}-${DATE}" -extldflags "-static"' + go build -ldflags '-X main.version="${TAG}-${DATE}" -extldflags "-static"' generate: @go generate #${PKGS} .PHONY: generate -check: $(SRCDIR) +check: $(SRCDIR) fmt @cd $(PRJDIR) && \ dep check && \ go vet #${PKGS} .PHONY: check -test: dep ## Run unit tests +test: dep check ## Run unit tests @cd $(PRJDIR) && \ helm init --client-only && \ - CGO_ENABLED=0 go test -v -cover -p=1 -args -f example.toml + go test -v -cover -p=1 -args -f example.toml .PHONY: test cross: dep ## Create binaries for all OSs @cd $(PRJDIR) && \ - env CGO_ENABLED=0 gox -os '!freebsd !netbsd' -arch '!arm' -output "dist/{{.Dir}}_{{.OS}}_{{.Arch}}" -ldflags '-X main.Version=${TAG}-${DATE}' + gox -os '!freebsd !netbsd' -arch '!arm' -output "dist/{{.Dir}}_{{.OS}}_{{.Arch}}" -ldflags '-X main.Version=${TAG}-${DATE}' .PHONY: cross release: dep ## Generate a new release diff --git a/bindata.go b/bindata.go index 6d744bc3..cf996020 100644 --- a/bindata.go +++ b/bindata.go @@ -182,6 +182,7 @@ type bintree struct { Func func() (*asset, error) Children map[string]*bintree } + var _bintree = &bintree{nil, map[string]*bintree{ "data": &bintree{nil, map[string]*bintree{ "role.yaml": &bintree{dataRoleYaml, map[string]*bintree{}}, @@ -234,4 +235,3 @@ func _filePath(dir, name string) string { cannonicalName := strings.Replace(name, "\\", "/", -1) return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) } - diff --git a/decision_maker.go b/decision_maker.go index d76ed8a8..c06eda48 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -111,7 +111,6 @@ func helmCommand(namespace string) []string { if runtime.GOOS == "windows" { logError("ERROR: Tillerless Helm plugin is not supported on Windows") } - return []string{"tiller", "run", namespace, "--", "helm"} if namespace != "" { return []string{"tiller", "run", namespace, "--", "helm"} } else { @@ -147,7 +146,7 @@ func installRelease(r *release) { Description: generateCmdDescription(r, "installing"), } outcome.addCommand(cmd, r.Priority, r) - logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] is not installed. Will install it in namespace [[ "+ r.Namespace+" ]]", true), r.Priority, create) + logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] is not installed. Will install it in namespace [[ "+r.Namespace+" ]]", true), r.Priority, create) if r.Test { testRelease(r) diff --git a/decision_maker_test.go b/decision_maker_test.go index 94f9d2e5..8c2f0f30 100644 --- a/decision_maker_test.go +++ b/decision_maker_test.go @@ -78,29 +78,29 @@ func Test_getValuesFiles(t *testing.T) { } } -func Test_inspectUpgradeScenario(t *testing.T){ +func Test_inspectUpgradeScenario(t *testing.T) { type args struct { r *release s releaseState } tests := []struct { - name string - args args - want decisionType + name string + args args + want decisionType }{ { - name: "inspectUpgradeScenario() - local chart with different chart name should change", + name: "inspectUpgradeScenario() - local chart with different chart name should change", args: args{ r: &release{ Name: "release1", Namespace: "namespace", - Version: "1.0.0", - Chart: "./test_files/chart-test", + Version: "1.0.0", + Chart: "./test_files/chart-test", Enabled: true, }, s: releaseState{ Namespace: "namespace", - Chart: "chart-1.0.0", + Chart: "chart-1.0.0", }, }, want: change, @@ -236,7 +236,7 @@ func Test_decide_group(t *testing.T) { want decisionType }{ { - name: "decide() - groupMap does not contain this service - skip", + name: "decide() - groupMap does not contain this service - skip", groupFlag: []string{"some-group"}, args: args{ r: &release{ @@ -249,7 +249,7 @@ func Test_decide_group(t *testing.T) { want: ignored, }, { - name: "decide() - groupMap contains this service - proceed", + name: "decide() - groupMap contains this service - proceed", groupFlag: []string{"run-me"}, args: args{ r: &release{ diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile index b0f9610a..0691d663 100644 --- a/dockerfile/dockerfile +++ b/dockerfile/dockerfile @@ -1,7 +1,7 @@ # This is a docker image for helmsman +ARG GO_VERSION=1.13.3 - -FROM golang:1.13.3-alpine3.10 as builder +FROM golang:${GO_VERSION}-alpine3.10 as builder WORKDIR /go/src/ diff --git a/helm_helpers_test.go b/helm_helpers_test.go index 5369960c..249e9c7d 100644 --- a/helm_helpers_test.go +++ b/helm_helpers_test.go @@ -345,7 +345,6 @@ func Test_getReleaseChartVersion(t *testing.T) { } } - func Test_getChartVersion(t *testing.T) { // version string = the first semver-valid string after the last hypen in the chart string. type args struct { @@ -362,8 +361,8 @@ func Test_getChartVersion(t *testing.T) { r: &release{ Name: "release1", Namespace: "namespace", - Version: "1.0.0", - Chart: "./test_files/chart-test", + Version: "1.0.0", + Chart: "./test_files/chart-test", Enabled: true, }, }, @@ -375,8 +374,8 @@ func Test_getChartVersion(t *testing.T) { r: &release{ Name: "release1", Namespace: "namespace", - Version: "1.0.0", - Chart: "random-chart-name-1f8147", + Version: "1.0.0", + Chart: "random-chart-name-1f8147", Enabled: true, }, }, @@ -386,8 +385,8 @@ func Test_getChartVersion(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Log(tt.want) - got, _ := getChartVersion(tt.args.r); - if got != tt.want { + got, _ := getChartVersion(tt.args.r) + if got != tt.want { t.Errorf("getChartVersion() = %v, want %v", got, tt.want) } }) diff --git a/init_test.go b/init_test.go index 35028b1d..6b104c21 100644 --- a/init_test.go +++ b/init_test.go @@ -2,6 +2,11 @@ package main import "testing" +var _ = func() bool { + testing.Init() + return true +}() + func Test_toolExists(t *testing.T) { type args struct { tool string diff --git a/namespace.go b/namespace.go index 8bdd0b20..18a1fc44 100644 --- a/namespace.go +++ b/namespace.go @@ -22,21 +22,21 @@ type limits []struct { // namespace type represents the fields of a namespace type namespace struct { - Protected bool `yaml:"protected"` - InstallTiller bool `yaml:"installTiller"` - UseTiller bool `yaml:"useTiller"` - TillerServiceAccount string `yaml:"tillerServiceAccount"` - TillerRole string `yaml:"tillerRole"` - TillerRoleTemplateFile string `yaml:"tillerRoleTemplateFile"` - TillerMaxHistory int `yaml:"tillerMaxHistory"` - CaCert string `yaml:"caCert"` - TillerCert string `yaml:"tillerCert"` - TillerKey string `yaml:"tillerKey"` - ClientCert string `yaml:"clientCert"` - ClientKey string `yaml:"clientKey"` - Limits limits `yaml:"limits,omitempty"` - Labels map[string]string `yaml:"labels"` - Annotations map[string]string `yaml:"annotations"` + Protected bool `yaml:"protected"` + InstallTiller bool `yaml:"installTiller"` + UseTiller bool `yaml:"useTiller"` + TillerServiceAccount string `yaml:"tillerServiceAccount"` + TillerRole string `yaml:"tillerRole"` + TillerRoleTemplateFile string `yaml:"tillerRoleTemplateFile"` + TillerMaxHistory int `yaml:"tillerMaxHistory"` + CaCert string `yaml:"caCert"` + TillerCert string `yaml:"tillerCert"` + TillerKey string `yaml:"tillerKey"` + ClientCert string `yaml:"clientCert"` + ClientKey string `yaml:"clientKey"` + Limits limits `yaml:"limits,omitempty"` + Labels map[string]string `yaml:"labels"` + Annotations map[string]string `yaml:"annotations"` } // checkNamespaceDefined checks if a given namespace is defined in the namespaces section of the desired state file diff --git a/state_test.go b/state_test.go index 4dbde2ba..d12a7930 100644 --- a/state_test.go +++ b/state_test.go @@ -34,7 +34,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -58,7 +58,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -79,7 +79,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -103,7 +103,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -127,7 +127,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "$URI", // unset env }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -151,7 +151,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https//192.168.99.100:8443", // invalid url }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -174,7 +174,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -219,7 +219,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -237,7 +237,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -287,7 +287,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, true, true, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, true, true, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -305,7 +305,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, true, "", "", "", 0,"s3://some-bucket/12345.crt", "", "", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, true, "", "", "", 0, "s3://some-bucket/12345.crt", "", "", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -323,7 +323,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, true, "", "", "", 0,"", "", "", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, true, "", "", "", 0, "", "", "", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -341,7 +341,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, true, false, "", "", "", 0,"s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, true, false, "", "", "", 0, "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -359,7 +359,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: nil, Apps: make(map[string]*release), @@ -374,7 +374,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{}, Apps: make(map[string]*release), @@ -389,7 +389,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -407,7 +407,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", diff --git a/test_files/dockerfile b/test_files/dockerfile index b4207955..7d4b0ce4 100644 --- a/test_files/dockerfile +++ b/test_files/dockerfile @@ -1,10 +1,11 @@ # This is a docker image for the helmsman test container # It can be pulled from praqma/helmsman-test +ARG GO_VERSION=1.13.3 ARG KUBE_VERSION ARG HELM_VERSION -FROM golang:1.13.3-alpine3.10 +FROM golang:${GO_VERSION}-alpine3.10 ENV KUBE_VERSION ${KUBE_VERSION:-v1.14.8} ENV HELM_VERSION ${HELM_VERSION:-v2.15.0} diff --git a/utils.go b/utils.go index c5af79c3..94ab098e 100644 --- a/utils.go +++ b/utils.go @@ -533,7 +533,7 @@ func generateCmdDescription(r *release, action string) string { if tillerNamespaceMsg = ""; !settings.Tillerless { tillerNamespaceMsg = "using Tiller in [ " + getDesiredTillerNamespace(r) + " ]" } - message := fmt.Sprintf("%s release [ " + r.Name + " ] in namespace [[ " + r.Namespace + " ]] %s", action, tillerNamespaceMsg) + message := fmt.Sprintf("%s release [ "+r.Name+" ] in namespace [[ "+r.Namespace+" ]] %s", action, tillerNamespaceMsg) return message } @@ -544,7 +544,7 @@ func generateDecisionMessage(r *release, message string, isTillerAware bool) str } baseMessage := "DECISION: " + message if isTillerAware { - return fmt.Sprintf(baseMessage + " %s", tillerNamespaceMsg) + return fmt.Sprintf(baseMessage+" %s", tillerNamespaceMsg) } return baseMessage } From b055aafde8b95196e6c72f6fc84750a61c447ddf Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Fri, 25 Oct 2019 11:01:12 +0100 Subject: [PATCH 0489/1127] fix tests for go 1.13 --- .circleci/config.yml | 11 ++++++++++- Makefile | 33 ++++++++++++++++++++++----------- bindata.go | 2 +- decision_maker.go | 3 +-- decision_maker_test.go | 20 ++++++++++---------- dockerfile/dockerfile | 4 ++-- helm_helpers_test.go | 13 ++++++------- init_test.go | 5 +++++ namespace.go | 30 +++++++++++++++--------------- state_test.go | 34 +++++++++++++++++----------------- test_files/dockerfile | 3 ++- utils.go | 4 ++-- 12 files changed, 93 insertions(+), 69 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b6730ea4..a43617eb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -44,6 +44,8 @@ jobs: command: | TAG=$(git describe --abbrev=0 --tags) docker login -u $DOCKER_USER -p $DOCKER_PASS + docker build -t praqma/helmsman:$TAG-helm-v2.15.1 --build-arg HELM_VERSION=v2.15.1 --build-arg HELM_DIFF_VERSION=master dockerfile/. --no-cache + docker push praqma/helmsman:$TAG-helm-v2.15.1 docker build -t praqma/helmsman:$TAG-helm-v2.14.3 --build-arg HELM_VERSION=v2.14.3 --build-arg HELM_DIFF_VERSION=master dockerfile/. --no-cache docker push praqma/helmsman:$TAG-helm-v2.14.3 docker build -t praqma/helmsman:$TAG-helm-v2.13.1 --build-arg HELM_VERSION=v2.13.1 --build-arg HELM_DIFF_VERSION=master dockerfile/. --no-cache @@ -55,6 +57,13 @@ jobs: docker build -t praqma/helmsman:$TAG-helm-v2.10.0 --build-arg HELM_VERSION=v2.10.0 --build-arg HELM_DIFF_VERSION=v2.10.0+1 dockerfile/. --no-cache docker push praqma/helmsman:$TAG-helm-v2.10.0 + - run: + name: build docker test images and push them to dockerhub + command: | + docker login -u $DOCKER_USER -p $DOCKER_PASS + docker build -t praqma/helmsman-test:latest test_files/. --no-cache + docker push praqma/helmsman-test:latest + workflows: version: 2 @@ -69,7 +78,7 @@ workflows: - build filters: tags: - only: /.*/ + only: /.*/ - release: requires: - test diff --git a/Makefile b/Makefile index ae67b49d..94d19c94 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,11 @@ .DEFAULT_GOAL := help -PKGS := $(shell go list ./... | grep -v /vendor/) -TAG := $(shell git describe --always --tags --abbrev=0 HEAD) -LAST := $(shell git describe --always --tags --abbrev=0 HEAD^) -BODY := "`git log ${LAST}..HEAD --oneline --decorate` `printf '\n\#\#\# [Build Info](${BUILD_URL})'`" -DATE := $(shell date +'%d%m%y') +PKGS := $(shell go list ./... | grep -v /vendor/) +TAG := $(shell git describe --always --tags --abbrev=0 HEAD) +LAST := $(shell git describe --always --tags --abbrev=0 HEAD^) +BODY := "`git log ${LAST}..HEAD --oneline --decorate` `printf '\n\#\#\# [Build Info](${BUILD_URL})'`" +DATE := $(shell date +'%d%m%y') +PRJNAME := $(shell basename "$(PWD)") # Ensure we have an unambiguous GOPATH. GOPATH := $(shell go env GOPATH) @@ -24,7 +25,7 @@ PRJDIR := $(CURDIR) ifeq ($(filter $(GOPATH)%,$(CURDIR)),) GOPATH := $(shell mktemp -d "/tmp/dep.XXXXXXXX") SRCDIR := $(GOPATH)/src/ - PRJDIR := $(SRCDIR)helmsman + PRJDIR := $(SRCDIR)$(PRJNAME) endif ifneq ($(OS),Windows_NT) @@ -34,6 +35,8 @@ ifneq ($(OS),Windows_NT) $(if $(shell which $(exec)),some string,$(error "No $(exec) in PATH, please install $(exec)"))) endif +export CGO_ENABLED=0 + help: @echo "Available options:" @grep -E '^[/1-9a-zA-Z._%-]+:.*?## .*$$' $(MAKEFILE_LIST) \ @@ -69,27 +72,27 @@ dep-update: $(SRCDIR) ## Ensure vendors with dep build: dep ## Build the package @cd $(PRJDIR) && \ - CGO_ENABLED=0 go build -ldflags '-X main.version="${TAG}-${DATE}" -extldflags "-static"' + go build -ldflags '-X main.version="${TAG}-${DATE}" -extldflags "-static"' generate: @go generate #${PKGS} .PHONY: generate -check: $(SRCDIR) +check: $(SRCDIR) fmt @cd $(PRJDIR) && \ dep check && \ go vet #${PKGS} .PHONY: check -test: dep ## Run unit tests +test: dep check ## Run unit tests @cd $(PRJDIR) && \ helm init --client-only && \ - CGO_ENABLED=0 go test -v -cover -p=1 -args -f example.toml + go test -v -cover -p=1 -args -f example.toml .PHONY: test cross: dep ## Create binaries for all OSs @cd $(PRJDIR) && \ - env CGO_ENABLED=0 gox -os '!freebsd !netbsd' -arch '!arm' -output "dist/{{.Dir}}_{{.OS}}_{{.Arch}}" -ldflags '-X main.Version=${TAG}-${DATE}' + gox -os '!freebsd !netbsd' -arch '!arm' -output "dist/{{.Dir}}_{{.OS}}_{{.Arch}}" -ldflags '-X main.Version=${TAG}-${DATE}' .PHONY: cross release: dep ## Generate a new release @@ -101,3 +104,11 @@ tools: ## Get extra tools used by this makefile @go get -u github.com/mitchellh/gox @go get -u github.com/goreleaser/goreleaser .PHONY: tools + +helmPlugins: ## Install helm plugins used by Helmsman + @mkdir -p ~/.helm/plugins + @helm plugin install https://github.com/hypnoglow/helm-s3.git + @helm plugin install https://github.com/nouney/helm-gcs + @helm plugin install https://github.com/databus23/helm-diff + @helm plugin install https://github.com/futuresimple/helm-secrets + @helm plugin install https://github.com/rimusz/helm-tiller diff --git a/bindata.go b/bindata.go index 6d744bc3..cf996020 100644 --- a/bindata.go +++ b/bindata.go @@ -182,6 +182,7 @@ type bintree struct { Func func() (*asset, error) Children map[string]*bintree } + var _bintree = &bintree{nil, map[string]*bintree{ "data": &bintree{nil, map[string]*bintree{ "role.yaml": &bintree{dataRoleYaml, map[string]*bintree{}}, @@ -234,4 +235,3 @@ func _filePath(dir, name string) string { cannonicalName := strings.Replace(name, "\\", "/", -1) return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) } - diff --git a/decision_maker.go b/decision_maker.go index d76ed8a8..c06eda48 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -111,7 +111,6 @@ func helmCommand(namespace string) []string { if runtime.GOOS == "windows" { logError("ERROR: Tillerless Helm plugin is not supported on Windows") } - return []string{"tiller", "run", namespace, "--", "helm"} if namespace != "" { return []string{"tiller", "run", namespace, "--", "helm"} } else { @@ -147,7 +146,7 @@ func installRelease(r *release) { Description: generateCmdDescription(r, "installing"), } outcome.addCommand(cmd, r.Priority, r) - logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] is not installed. Will install it in namespace [[ "+ r.Namespace+" ]]", true), r.Priority, create) + logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] is not installed. Will install it in namespace [[ "+r.Namespace+" ]]", true), r.Priority, create) if r.Test { testRelease(r) diff --git a/decision_maker_test.go b/decision_maker_test.go index 94f9d2e5..8c2f0f30 100644 --- a/decision_maker_test.go +++ b/decision_maker_test.go @@ -78,29 +78,29 @@ func Test_getValuesFiles(t *testing.T) { } } -func Test_inspectUpgradeScenario(t *testing.T){ +func Test_inspectUpgradeScenario(t *testing.T) { type args struct { r *release s releaseState } tests := []struct { - name string - args args - want decisionType + name string + args args + want decisionType }{ { - name: "inspectUpgradeScenario() - local chart with different chart name should change", + name: "inspectUpgradeScenario() - local chart with different chart name should change", args: args{ r: &release{ Name: "release1", Namespace: "namespace", - Version: "1.0.0", - Chart: "./test_files/chart-test", + Version: "1.0.0", + Chart: "./test_files/chart-test", Enabled: true, }, s: releaseState{ Namespace: "namespace", - Chart: "chart-1.0.0", + Chart: "chart-1.0.0", }, }, want: change, @@ -236,7 +236,7 @@ func Test_decide_group(t *testing.T) { want decisionType }{ { - name: "decide() - groupMap does not contain this service - skip", + name: "decide() - groupMap does not contain this service - skip", groupFlag: []string{"some-group"}, args: args{ r: &release{ @@ -249,7 +249,7 @@ func Test_decide_group(t *testing.T) { want: ignored, }, { - name: "decide() - groupMap contains this service - proceed", + name: "decide() - groupMap contains this service - proceed", groupFlag: []string{"run-me"}, args: args{ r: &release{ diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile index b0f9610a..0691d663 100644 --- a/dockerfile/dockerfile +++ b/dockerfile/dockerfile @@ -1,7 +1,7 @@ # This is a docker image for helmsman +ARG GO_VERSION=1.13.3 - -FROM golang:1.13.3-alpine3.10 as builder +FROM golang:${GO_VERSION}-alpine3.10 as builder WORKDIR /go/src/ diff --git a/helm_helpers_test.go b/helm_helpers_test.go index 5369960c..249e9c7d 100644 --- a/helm_helpers_test.go +++ b/helm_helpers_test.go @@ -345,7 +345,6 @@ func Test_getReleaseChartVersion(t *testing.T) { } } - func Test_getChartVersion(t *testing.T) { // version string = the first semver-valid string after the last hypen in the chart string. type args struct { @@ -362,8 +361,8 @@ func Test_getChartVersion(t *testing.T) { r: &release{ Name: "release1", Namespace: "namespace", - Version: "1.0.0", - Chart: "./test_files/chart-test", + Version: "1.0.0", + Chart: "./test_files/chart-test", Enabled: true, }, }, @@ -375,8 +374,8 @@ func Test_getChartVersion(t *testing.T) { r: &release{ Name: "release1", Namespace: "namespace", - Version: "1.0.0", - Chart: "random-chart-name-1f8147", + Version: "1.0.0", + Chart: "random-chart-name-1f8147", Enabled: true, }, }, @@ -386,8 +385,8 @@ func Test_getChartVersion(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Log(tt.want) - got, _ := getChartVersion(tt.args.r); - if got != tt.want { + got, _ := getChartVersion(tt.args.r) + if got != tt.want { t.Errorf("getChartVersion() = %v, want %v", got, tt.want) } }) diff --git a/init_test.go b/init_test.go index 35028b1d..6b104c21 100644 --- a/init_test.go +++ b/init_test.go @@ -2,6 +2,11 @@ package main import "testing" +var _ = func() bool { + testing.Init() + return true +}() + func Test_toolExists(t *testing.T) { type args struct { tool string diff --git a/namespace.go b/namespace.go index 8bdd0b20..18a1fc44 100644 --- a/namespace.go +++ b/namespace.go @@ -22,21 +22,21 @@ type limits []struct { // namespace type represents the fields of a namespace type namespace struct { - Protected bool `yaml:"protected"` - InstallTiller bool `yaml:"installTiller"` - UseTiller bool `yaml:"useTiller"` - TillerServiceAccount string `yaml:"tillerServiceAccount"` - TillerRole string `yaml:"tillerRole"` - TillerRoleTemplateFile string `yaml:"tillerRoleTemplateFile"` - TillerMaxHistory int `yaml:"tillerMaxHistory"` - CaCert string `yaml:"caCert"` - TillerCert string `yaml:"tillerCert"` - TillerKey string `yaml:"tillerKey"` - ClientCert string `yaml:"clientCert"` - ClientKey string `yaml:"clientKey"` - Limits limits `yaml:"limits,omitempty"` - Labels map[string]string `yaml:"labels"` - Annotations map[string]string `yaml:"annotations"` + Protected bool `yaml:"protected"` + InstallTiller bool `yaml:"installTiller"` + UseTiller bool `yaml:"useTiller"` + TillerServiceAccount string `yaml:"tillerServiceAccount"` + TillerRole string `yaml:"tillerRole"` + TillerRoleTemplateFile string `yaml:"tillerRoleTemplateFile"` + TillerMaxHistory int `yaml:"tillerMaxHistory"` + CaCert string `yaml:"caCert"` + TillerCert string `yaml:"tillerCert"` + TillerKey string `yaml:"tillerKey"` + ClientCert string `yaml:"clientCert"` + ClientKey string `yaml:"clientKey"` + Limits limits `yaml:"limits,omitempty"` + Labels map[string]string `yaml:"labels"` + Annotations map[string]string `yaml:"annotations"` } // checkNamespaceDefined checks if a given namespace is defined in the namespaces section of the desired state file diff --git a/state_test.go b/state_test.go index 4dbde2ba..d12a7930 100644 --- a/state_test.go +++ b/state_test.go @@ -34,7 +34,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -58,7 +58,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -79,7 +79,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -103,7 +103,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -127,7 +127,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "$URI", // unset env }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -151,7 +151,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https//192.168.99.100:8443", // invalid url }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -174,7 +174,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -219,7 +219,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -237,7 +237,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -287,7 +287,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, true, true, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, true, true, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -305,7 +305,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, true, "", "", "", 0,"s3://some-bucket/12345.crt", "", "", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, true, "", "", "", 0, "s3://some-bucket/12345.crt", "", "", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -323,7 +323,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, true, "", "", "", 0,"", "", "", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, true, "", "", "", 0, "", "", "", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -341,7 +341,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, true, false, "", "", "", 0,"s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, true, false, "", "", "", 0, "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -359,7 +359,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: nil, Apps: make(map[string]*release), @@ -374,7 +374,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{}, Apps: make(map[string]*release), @@ -389,7 +389,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -407,7 +407,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0,"", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", diff --git a/test_files/dockerfile b/test_files/dockerfile index b4207955..7d4b0ce4 100644 --- a/test_files/dockerfile +++ b/test_files/dockerfile @@ -1,10 +1,11 @@ # This is a docker image for the helmsman test container # It can be pulled from praqma/helmsman-test +ARG GO_VERSION=1.13.3 ARG KUBE_VERSION ARG HELM_VERSION -FROM golang:1.13.3-alpine3.10 +FROM golang:${GO_VERSION}-alpine3.10 ENV KUBE_VERSION ${KUBE_VERSION:-v1.14.8} ENV HELM_VERSION ${HELM_VERSION:-v2.15.0} diff --git a/utils.go b/utils.go index c5af79c3..94ab098e 100644 --- a/utils.go +++ b/utils.go @@ -533,7 +533,7 @@ func generateCmdDescription(r *release, action string) string { if tillerNamespaceMsg = ""; !settings.Tillerless { tillerNamespaceMsg = "using Tiller in [ " + getDesiredTillerNamespace(r) + " ]" } - message := fmt.Sprintf("%s release [ " + r.Name + " ] in namespace [[ " + r.Namespace + " ]] %s", action, tillerNamespaceMsg) + message := fmt.Sprintf("%s release [ "+r.Name+" ] in namespace [[ "+r.Namespace+" ]] %s", action, tillerNamespaceMsg) return message } @@ -544,7 +544,7 @@ func generateDecisionMessage(r *release, message string, isTillerAware bool) str } baseMessage := "DECISION: " + message if isTillerAware { - return fmt.Sprintf(baseMessage + " %s", tillerNamespaceMsg) + return fmt.Sprintf(baseMessage+" %s", tillerNamespaceMsg) } return baseMessage } From 9e8e02c2eb1f7d43bffeb523b51d8cedf7011d10 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Thu, 24 Oct 2019 13:05:33 +0200 Subject: [PATCH 0490/1127] Add hiera-eyaml optional support for secrets files --- command.go | 9 +-- command_test.go | 2 +- decision_maker.go | 2 +- dockerfile/dockerfile | 5 +- helm_helpers.go | 59 ++++++++++---- helm_helpers_test.go | 86 ++++++++++++++++++++- init.go | 8 +- kube_helpers.go | 32 ++++---- plan.go | 2 +- state.go | 25 +++--- test_files/keys/private_key.pkcs7.pem | 27 +++++++ test_files/keys/public_key.pkcs7.pem | 18 +++++ test_files/secrets/valid_eyaml_secrets.yaml | 1 + utils.go | 14 ++++ 14 files changed, 234 insertions(+), 56 deletions(-) create mode 100644 test_files/keys/private_key.pkcs7.pem create mode 100644 test_files/keys/public_key.pkcs7.pem create mode 100644 test_files/secrets/valid_eyaml_secrets.yaml diff --git a/command.go b/command.go index c5fc3664..f0aede7f 100644 --- a/command.go +++ b/command.go @@ -31,7 +31,7 @@ func (c command) printFullCommand() { } // exec executes the executable command and returns the exit code and execution result -func (c command) exec(debug bool, verbose bool) (int, string) { +func (c command) exec(debug bool, verbose bool) (int, string, string) { // Only use non-empty string args args := []string{} @@ -59,18 +59,17 @@ func (c command) exec(debug bool, verbose bool) (int, string) { if err := cmd.Start(); err != nil { log.Println("ERROR: cmd.Start: " + err.Error()) - return 1, err.Error() + return 1, err.Error(), "" } if err := cmd.Wait(); err != nil { if exiterr, ok := err.(*exec.ExitError); ok { if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { - return status.ExitStatus(), stderr.String() + return status.ExitStatus(), stderr.String(), "" } } else { logError("ERROR: cmd.Wait: " + err.Error()) } } - - return 0, stdout.String() + return 0, stdout.String(), stderr.String() } diff --git a/command_test.go b/command_test.go index 45b5c397..fa68315b 100644 --- a/command_test.go +++ b/command_test.go @@ -99,7 +99,7 @@ func Test_command_exec(t *testing.T) { Args: tt.fields.Args, Description: tt.fields.Description, } - got, got1 := c.exec(tt.args.debug, tt.args.verbose) + got, got1, _ := c.exec(tt.args.debug, tt.args.verbose) if got != tt.want { t.Errorf("command.exec() got = %v, want %v", got, tt.want) } diff --git a/decision_maker.go b/decision_maker.go index c06eda48..55be9796 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -276,7 +276,7 @@ func diffRelease(r *release) string { Description: generateCmdDescription(r, "diffing"), } - if exitCode, msg = cmd.exec(debug, verbose); exitCode != 0 { + if exitCode, msg, _ = cmd.exec(debug, verbose); exitCode != 0 { logError(fmt.Sprintf("Command returned with exit code: %d. And error message: %s ", exitCode, msg)) } else { if (verbose || showDiff) && msg != "" { diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile index 0691d663..b8aa9942 100644 --- a/dockerfile/dockerfile +++ b/dockerfile/dockerfile @@ -28,7 +28,7 @@ ENV HELM_VERSION ${HELM_VERSION:-v2.15.0} ENV HELM_DIFF_VERSION ${HELM_DIFF_VERSION:-v2.11.0+5} RUN apk --no-cache update \ - && apk add --update --no-cache ca-certificates git openssh \ + && apk add --update --no-cache ca-certificates git openssh ruby \ && apk add --update -t deps curl tar gzip make bash \ && rm -rf /var/cache/apk/* \ && curl -L https://storage.googleapis.com/kubernetes-release/release/${KUBE_VERSION}/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl \ @@ -46,7 +46,8 @@ RUN mkdir -p ~/.helm/plugins \ && helm plugin install https://github.com/databus23/helm-diff --version ${HELM_DIFF_VERSION} \ && helm plugin install https://github.com/futuresimple/helm-secrets \ && helm plugin install https://github.com/rimusz/helm-tiller \ - && rm -r /tmp/helm-diff /tmp/helm-diff.tgz + && rm -r /tmp/helm-diff /tmp/helm-diff.tgz \ + && gem install hiera-eyaml --no-doc WORKDIR /tmp # ENTRYPOINT ["/bin/helmsman"] diff --git a/helm_helpers.go b/helm_helpers.go index abb3b07a..e403ee39 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -52,7 +52,7 @@ func getHelmClientVersion() string { Description: "checking Helm version ", } - exitCode, result := cmd.exec(debug, false) + exitCode, result, _ := cmd.exec(debug, false) if exitCode != 0 { logError("ERROR: while checking helm version: " + result) } @@ -163,7 +163,7 @@ func helmList(tillerNS string, outputFormat []string, offset string) (string, er Description: "listing all existing releases in namespace [ " + tillerNS + " ]...", } - exitCode, result := cmd.exec(debug, verbose) + exitCode, result, _ := cmd.exec(debug, verbose) if exitCode != 0 { if !apply { if strings.Contains(result, "incompatible versions") { @@ -285,7 +285,7 @@ func validateReleaseCharts(apps map[string]*release) (bool, string) { var output string var exitCode int - if exitCode, output = cmd.exec(debug, verbose); exitCode != 0 { + if exitCode, output, _ = cmd.exec(debug, verbose); exitCode != 0 { maybeRepo := filepath.Base(filepath.Dir(r.Chart)) return false, "ERROR: chart at " + r.Chart + " for app [" + app + "] could not be found. Did you mean to add a repo named '" + maybeRepo + "'?" } @@ -309,7 +309,7 @@ func validateReleaseCharts(apps map[string]*release) (bool, string) { Description: "validating if chart " + r.Chart + " with version " + r.Version + " is available in the defined repos.", } - if exitCode, result := cmd.exec(debug, verbose); exitCode != 0 || strings.Contains(result, "No results found") { + if exitCode, result, _ := cmd.exec(debug, verbose); exitCode != 0 || strings.Contains(result, "No results found") { return false, "ERROR: chart " + r.Chart + " with version " + r.Version + " is specified for " + "app [" + app + "] but is not found in the defined repos." } @@ -337,7 +337,7 @@ func getChartVersion(r *release) (string, string) { result string ) - if exitCode, result = cmd.exec(debug, verbose); exitCode != 0 || strings.Contains(result, "No results found") { + if exitCode, result, _ = cmd.exec(debug, verbose); exitCode != 0 || strings.Contains(result, "No results found") { return "", "ERROR: chart " + r.Chart + " with version " + r.Version + " is specified but not found in the helm repos." } versions := strings.Split(result, "\n") @@ -370,7 +370,7 @@ func waitForTiller(namespace string) { Description: "checking if helm Tiller is ready in namespace [ " + namespace + " ].", } - exitCode, err := cmd.exec(debug, verbose) + exitCode, err, _ := cmd.exec(debug, verbose) for attempt < 10 { if exitCode == 0 { @@ -378,7 +378,7 @@ func waitForTiller(namespace string) { } else if strings.Contains(err, "could not find a ready tiller pod") || strings.Contains(err, "could not find tiller") { log.Println("INFO: waiting for helm Tiller to be ready in namespace [" + namespace + "] ...") time.Sleep(5 * time.Second) - exitCode, err = cmd.exec(debug, verbose) + exitCode, err, _ = cmd.exec(debug, verbose) } else { logError("ERROR: while waiting for helm Tiller to be ready in namespace [ " + namespace + " ] : " + err) } @@ -418,7 +418,7 @@ func addHelmRepos(repos map[string]string) (bool, string) { Description: "adding repo " + repoName, } - if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { + if exitCode, err, _ := cmd.exec(debug, verbose); exitCode != 0 { return false, "ERROR: while adding repo [" + repoName + "]: " + err } @@ -430,7 +430,7 @@ func addHelmRepos(repos map[string]string) (bool, string) { Description: "updating helm repos", } - if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { + if exitCode, err, _ := cmd.exec(debug, verbose); exitCode != 0 { return false, "ERROR: while updating helm repos : " + err } @@ -510,7 +510,7 @@ func deployTiller(namespace string, serviceAccount string, defaultServiceAccount Description: "initializing helm on the current context and upgrading Tiller on namespace [ " + namespace + " ].", } - if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { + if exitCode, err, _ := cmd.exec(debug, verbose); exitCode != 0 { return false, "ERROR: while deploying Helm Tiller in namespace [" + namespace + "]: " + err } return true, "" @@ -524,7 +524,7 @@ func initHelmClientOnly() (bool, string) { Description: "initializing helm on the client only.", } - if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { + if exitCode, err, _ := cmd.exec(debug, verbose); exitCode != 0 { return false, "ERROR: initializing helm on the client : " + err } @@ -645,18 +645,45 @@ func deleteUntrackedRelease(release *release, tillerNamespace string) { // decrypt a helm secret file func decryptSecret(name string) bool { - cmd := command{ - Cmd: "helm", - Args: concat(helmCommand(""), []string{"secrets", "dec", name}), + cmd := "helm" + args := concat(helmCommand(""), []string{"secrets", "dec", name}) + + if settings.EyamlEnabled { + cmd = "eyaml" + args = []string{"decrypt", "-f", name} + if settings.EyamlPrivateKeyPath != "" && settings.EyamlPublicKeyPath != "" { + args = append(args, []string{"--pkcs7-private-key", settings.EyamlPrivateKeyPath, "--pkcs7-public-key", settings.EyamlPublicKeyPath}...) + } + } + + command := command{ + Cmd: cmd, + Args: args, Description: "Decrypting " + name, } - exitCode, _ := cmd.exec(debug, false) + exitCode, output, stderr := command.exec(debug, false) if exitCode != 0 { + log.Println("ERROR: " + output) + return false + } else if stderr != "" { + log.Println("ERROR: " + stderr) return false } + if settings.EyamlEnabled { + var outfile string + if isOfType(name, []string{".dec"}) { + outfile = name + } else { + outfile = name + ".dec" + } + err := writeStringToFile(outfile, output) + if err != nil { + logError("ERROR: could not write [ " + outfile + " ] file") + } + } return true } @@ -668,7 +695,7 @@ func updateChartDep(chartPath string) (bool, string) { Description: "Updateing dependency for local chart " + chartPath, } - exitCode, err := cmd.exec(debug, verbose) + exitCode, err, _ := cmd.exec(debug, verbose) if exitCode != 0 { return false, err diff --git a/helm_helpers_test.go b/helm_helpers_test.go index 249e9c7d..c0208e3a 100644 --- a/helm_helpers_test.go +++ b/helm_helpers_test.go @@ -16,7 +16,7 @@ func setupTestCase(t *testing.T) func(t *testing.T) { Args: []string{"create", os.TempDir() + "/helmsman-tests/dir-with space/myapp"}, Description: "creating an empty local chart directory", } - if exitCode, msg := cmd.exec(debug, verbose); exitCode != 0 { + if exitCode, msg, _ := cmd.exec(debug, verbose); exitCode != 0 { logError(fmt.Sprintf("Command returned with exit code: %d. And error message: %s ", exitCode, msg)) } @@ -392,3 +392,87 @@ func Test_getChartVersion(t *testing.T) { }) } } + +func Test_eyamlSecrets(t *testing.T) { + type args struct { + r *release + s *config + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "decryptSecrets - valid eyaml-based secrets decryption", + args: args{ + s: &config { + EyamlEnabled: true, + EyamlPublicKeyPath: "./test_files/keys/public_key.pkcs7.pem", + EyamlPrivateKeyPath: "./test_files/keys/private_key.pkcs7.pem", + }, + r: &release{ + Name: "release1", + Namespace: "namespace", + Version: "1.0.0", + Enabled: true, + SecretsFile: "./test_files/secrets/valid_eyaml_secrets.yaml", + }, + }, + want: true, + }, + { + name: "decryptSecrets - not existing eyaml-based secrets file", + args: args{ + s: &config { + EyamlEnabled: true, + EyamlPublicKeyPath: "./test_files/keys/public_key.pkcs7.pem", + EyamlPrivateKeyPath: "./test_files/keys/private_key.pkcs7.pem", + }, + r: &release{ + Name: "release1", + Namespace: "namespace", + Version: "1.0.0", + Enabled: true, + SecretsFile: "./test_files/secrets/invalid_eyaml_secrets.yaml", + }, + }, + want: false, + }, + { + name: "decryptSecrets - not existing eyaml key", + args: args{ + s: &config { + EyamlEnabled: true, + EyamlPublicKeyPath: "./test_files/keys/public_key.pkcs7.pem2", + EyamlPrivateKeyPath: "./test_files/keys/private_key.pkcs7.pem", + }, + r: &release{ + Name: "release1", + Namespace: "namespace", + Version: "1.0.0", + Enabled: true, + SecretsFile: "./test_files/secrets/valid_eyaml_secrets.yaml", + }, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Log(tt.want) + defaultSettings := settings + defer func() { settings = defaultSettings }() + settings.EyamlEnabled = tt.args.s.EyamlEnabled + settings.EyamlPublicKeyPath = tt.args.s.EyamlPublicKeyPath + settings.EyamlPrivateKeyPath = tt.args.s.EyamlPrivateKeyPath + got := decryptSecret(tt.args.r.SecretsFile) + if got != tt.want { + t.Errorf("decryptSecret() = %v, want %v", got, tt.want) + } + if _, err := os.Stat(tt.args.r.SecretsFile + ".dec"); err == nil { + defer deleteFile(tt.args.r.SecretsFile + ".dec") + } + }) + } +} diff --git a/init.go b/init.go index 9ecdbebd..6149e622 100644 --- a/init.go +++ b/init.go @@ -96,6 +96,10 @@ func init() { logError("ERROR: --target and --group can't be used together.") } + if (settings.EyamlPrivateKeyPath != "" && settings.EyamlPublicKeyPath == "") || (settings.EyamlPrivateKeyPath == "" && settings.EyamlPublicKeyPath != "") { + logError("ERROR: both EyamlPrivateKeyPath and EyamlPublicKeyPath are required") + } + helmVersion = strings.TrimSpace(strings.SplitN(getHelmClientVersion(), ": ", 2)[1]) kubectlVersion = strings.TrimSpace(strings.SplitN(getKubectlClientVersion(), ": ", 2)[1]) @@ -231,7 +235,7 @@ func toolExists(tool string) bool { Description: "validating that " + tool + " is installed.", } - exitCode, _ := cmd.exec(debug, false) + exitCode, _, _ := cmd.exec(debug, false) if exitCode != 0 { return false @@ -249,7 +253,7 @@ func helmPluginExists(plugin string) bool { Description: "validating that " + plugin + " is installed.", } - exitCode, result := cmd.exec(debug, false) + exitCode, result, _ := cmd.exec(debug, false) if exitCode != 0 { return false diff --git a/kube_helpers.go b/kube_helpers.go index fe11e0af..4d7d0c85 100644 --- a/kube_helpers.go +++ b/kube_helpers.go @@ -22,7 +22,7 @@ func validateServiceAccount(sa string, namespace string) (bool, string) { Description: "validating if serviceaccount [ " + sa + " ] exists in namespace [ " + namespace + " ].", } - if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { + if exitCode, err, _ := cmd.exec(debug, verbose); exitCode != 0 { return false, err } return true, "" @@ -92,7 +92,7 @@ func createNamespace(ns string) { Args: []string{"create", "namespace", ns}, Description: "creating namespace " + ns, } - exitCode, _ := cmd.exec(debug, verbose) + exitCode, _, _ := cmd.exec(debug, verbose) if exitCode != 0 && verbose { log.Println("WARN: I could not create namespace [ " + ns + " ]. It already exists. I am skipping this.") @@ -108,7 +108,7 @@ func labelNamespace(ns string, labels map[string]string) { Description: "labeling namespace " + ns, } - exitCode, _ := cmd.exec(debug, verbose) + exitCode, _, _ := cmd.exec(debug, verbose) if exitCode != 0 && verbose { log.Println("WARN: I could not label namespace [ " + ns + " with " + k + "=" + v + " ]. It already exists. I am skipping this.") @@ -125,7 +125,7 @@ func annotateNamespace(ns string, labels map[string]string) { Description: "annotating namespace " + ns, } - exitCode, _ := cmd.exec(debug, verbose) + exitCode, _, _ := cmd.exec(debug, verbose) if exitCode != 0 && verbose { log.Println("WARN: I could not annotate namespace [ " + ns + " with " + k + "=" + v + " ]. It already exists. I am skipping this.") @@ -166,7 +166,7 @@ spec: Description: "creating LimitRange in namespace [ " + ns + " ]", } - exitCode, e := cmd.exec(debug, verbose) + exitCode, e, _ := cmd.exec(debug, verbose) if exitCode != 0 { logError("ERROR: failed to create LimitRange in namespace [ " + ns + " ]: " + e) @@ -252,7 +252,7 @@ func createContext() (bool, string) { Description: "creating kubectl context - setting credentials.", } - if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { + if exitCode, err, _ := cmd.exec(debug, verbose); exitCode != 0 { return false, "ERROR: failed to create context [ " + s.Settings.KubeContext + " ]: " + err } @@ -262,7 +262,7 @@ func createContext() (bool, string) { Description: "creating kubectl context - setting cluster.", } - if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { + if exitCode, err, _ := cmd.exec(debug, verbose); exitCode != 0 { return false, "ERROR: failed to create context [ " + s.Settings.KubeContext + " ]: " + err } @@ -272,7 +272,7 @@ func createContext() (bool, string) { Description: "creating kubectl context - setting context.", } - if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { + if exitCode, err, _ := cmd.exec(debug, verbose); exitCode != 0 { return false, "ERROR: failed to create context [ " + s.Settings.KubeContext + " ]: " + err } @@ -296,7 +296,7 @@ func setKubeContext(context string) bool { Description: "setting kubectl context to [ " + context + " ]", } - exitCode, _ := cmd.exec(debug, verbose) + exitCode, _, _ := cmd.exec(debug, verbose) if exitCode != 0 { log.Println("INFO: KubeContext: " + context + " does not exist. I will try to create it.") @@ -315,7 +315,7 @@ func getKubeContext() bool { Description: "getting kubectl context", } - exitCode, result := cmd.exec(debug, verbose) + exitCode, result, _ := cmd.exec(debug, verbose) if exitCode != 0 || result == "" { log.Println("INFO: Kubectl context is not set") @@ -333,7 +333,7 @@ func createServiceAccount(saName string, namespace string) (bool, string) { Description: "creating service account [ " + saName + " ] in namespace [ " + namespace + " ]", } - exitCode, err := cmd.exec(debug, verbose) + exitCode, err, _ := cmd.exec(debug, verbose) if exitCode != 0 { //logError("ERROR: failed to create service account " + saName + " in namespace [ " + namespace + " ]: " + err) @@ -367,7 +367,7 @@ func createRoleBinding(role string, saName string, namespace string) (bool, stri Description: "creating " + resource + " for service account [ " + saName + " ] in namespace [ " + namespace + " ] with role: " + role, } - exitCode, err := cmd.exec(debug, verbose) + exitCode, err, _ := cmd.exec(debug, verbose) if exitCode != 0 { return false, err @@ -399,7 +399,7 @@ func createRole(namespace string, role string, roleTemplateFile string) (bool, s Description: "creating role [" + role + "] in namespace [ " + namespace + " ]", } - exitCode, err := cmd.exec(debug, verbose) + exitCode, err, _ := cmd.exec(debug, verbose) if exitCode != 0 { return false, err @@ -426,7 +426,7 @@ func labelResource(r *release) { Description: "applying labels to Helm state in [ " + getDesiredTillerNamespace(r) + " ] for " + r.Name, } - exitCode, err := cmd.exec(debug, verbose) + exitCode, err, _ := cmd.exec(debug, verbose) if exitCode != 0 { logError(err) @@ -466,7 +466,7 @@ func getHelmsmanReleases() map[string]map[*release]bool { Description: "getting helm releases which are managed by Helmsman in namespace [[ " + ns + " ]].", } - exitCode, output := cmd.exec(debug, verbose) + exitCode, output, _ := cmd.exec(debug, verbose) if exitCode != 0 { logError(output) @@ -503,7 +503,7 @@ func getKubectlClientVersion() string { Description: "checking kubectl version ", } - exitCode, result := cmd.exec(debug, false) + exitCode, result, _ := cmd.exec(debug, false) if exitCode != 0 { logError("ERROR: while checking kubectl version: " + result) } diff --git a/plan.go b/plan.go index 40f4e8bf..6d0aa708 100644 --- a/plan.go +++ b/plan.go @@ -95,7 +95,7 @@ func (p plan) execPlan() { for _, cmd := range p.Commands { log.Println("INFO: " + cmd.Command.Description) - if exitCode, msg := cmd.Command.exec(debug, verbose); exitCode != 0 { + if exitCode, msg, _ := cmd.Command.exec(debug, verbose); exitCode != 0 { var errorMsg string if errorMsg = msg; !verbose { errorMsg = strings.Split(msg, "---")[0] diff --git a/state.go b/state.go index 6f8e6b08..e8a6c1fa 100644 --- a/state.go +++ b/state.go @@ -10,17 +10,20 @@ import ( // config type represents the settings fields type config struct { - KubeContext string `yaml:"kubeContext"` - Username string `yaml:"username"` - Password string `yaml:"password"` - ClusterURI string `yaml:"clusterURI"` - ServiceAccount string `yaml:"serviceAccount"` - StorageBackend string `yaml:"storageBackend"` - SlackWebhook string `yaml:"slackWebhook"` - ReverseDelete bool `yaml:"reverseDelete"` - BearerToken bool `yaml:"bearerToken"` - BearerTokenPath string `yaml:"bearerTokenPath"` - Tillerless bool `yaml:"tillerless"` + KubeContext string `yaml:"kubeContext"` + Username string `yaml:"username"` + Password string `yaml:"password"` + ClusterURI string `yaml:"clusterURI"` + ServiceAccount string `yaml:"serviceAccount"` + StorageBackend string `yaml:"storageBackend"` + SlackWebhook string `yaml:"slackWebhook"` + ReverseDelete bool `yaml:"reverseDelete"` + BearerToken bool `yaml:"bearerToken"` + BearerTokenPath string `yaml:"bearerTokenPath"` + Tillerless bool `yaml:"tillerless"` + EyamlEnabled bool `yaml:"eyamlEnabled"` + EyamlPrivateKeyPath string `yaml:"eyamlPrivateKeyPath"` + EyamlPublicKeyPath string `yaml:"eyamlPublicKeyPath"` } // state type represents the desired state of applications on a k8s cluster. diff --git a/test_files/keys/private_key.pkcs7.pem b/test_files/keys/private_key.pkcs7.pem new file mode 100644 index 00000000..ab70360e --- /dev/null +++ b/test_files/keys/private_key.pkcs7.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAmB+jCif3+GPmlPrmYgpAcbYh6Ny1iX8V+9hRbfW6A6lVDaAX +2n4uzRupOSBj98hBeakdj/TDI1VJwgbgaHfR+IWNNFOp+pr3uLU54vZHJSeSVm7+ +OhIKumATLEUyMDs2sYCmhc0qKeolI8qOp2jlapJ4b2s/Y/TMRfW3vdMEpukvhMs5 +RB/Aky5gGSnW7AZZyF0tTv7So+GJKmula6rsp7Vx+kr00ccNrvbqLSYmjkDYdMey +AoZ5Slb04zub9kP21Jm8Qw2CeiHZ1h/Jkc41V13CAnM8zSB/tS/9uRXEEuQMiUBe +3I9SfukLCKbsx0oryCdjnt8Gblz8kpq2O4EadwIDAQABAoIBAGhQaXCxb6z4dEl8 +szZPaVmQVzhjAGlEqEKGV3BbrC6OkzBAs5q0JEupyCTQPTzQKXXPreHlKVq1RVqz +dHauk2Ej02wqYsjiMzSJsSQdVTP5KrPycIpJjOm4r+0PlhbUw/B8E7R0t5D+anFc +mO3bVFX8EnH0zQcx+lGO6WxVoz8AYU4EV2oxveR77in/8f7Aj8urrzRdSRnbUgp/ +q27FA2Ct9S9KWn+V+zByTULHN2FjViWqaWqIA+/BbOoe+a7VP6579yK5bRxoCS01 +u1Xv4fEu3d382C3nbqNS0fszce3u7r9rSwat/su42bkjmNC0Gmtk5TVTGuvtv5Nq +IkndF+ECgYEAyVo+Qb7stssiLq39VH0P415rTGv3JrscuwfkOCe6smJO9/KFeQjb +waNjR7KEkgTTqB3s6m8n4GX2AvVMiNbfoLscp6j031Uuq/cWX/DDHTcv9ZN3igcJ +hNWSXIM9Ru5d4GXZGcYiLtpDfp2ekHEtMlIWlceOv1cy+Va5bn7RrsUCgYEAwWkF +MzpRdPoBvfOJImXsyWvz9VksGQ8L9133wB+6n3QsFqdd43MmbvwqN5tZW/qLEFeU +oH4Od8M9fH5IjmJdqyNhNDx9CHaEXLZHaPhdDlJZUzryjIAjn4JMjMhZ4jxr8SHR +zZBqmgGdhQYssXCtJWU1PD/DNWQtQEVTkJVtuAsCgYEAsZ/wh+M7w02TjAZlEqF4 +4KUslrAvyXULNVsS0w8JPdBHxaemY02TP1E5hchP9thXN1me5HjGfsizq4xlxdl4 +Ubx+3NDJpDLrBzzj+iLUnPNQVZ2PuK3YkdwuT3pfFjG1kv2F9Zy6Dwbwv8OgW9/b +dSbBUcRHgzgTea4tyvIJW9kCgYBhvI5yKsBLGqOSt+TOyy7zQmhPzbYpG59ya7vt +DJukRHKbKAycCe6cGzXCT/DCOEPaCEgFKm5pOvJxXOeRfEfVWdWfLgoJIssUhtBj +TU7JE/grxRgYxBA8ZP4GDqDNYLczbWG2PYqBNNvDAzHGoSf+Q7y5K4ecDXmIhwAJ +ilmdrQKBgCnvFxO8mFLCsINfhKTWi8JQUNY+QuQbM9/NztDo6mPj4R8bw3jBEgq9 +fLPV0x7CmuGs5x6D+ZHKKqAaWpPO1e7YiYom2jKv40iIgvdEckYLlpQbCFRfJu/b +/wSppmgeZlUAyftw+0c1Yl+Nqv13KuI4TII+/0fqxRG1T3B2s9jq +-----END RSA PRIVATE KEY----- diff --git a/test_files/keys/public_key.pkcs7.pem b/test_files/keys/public_key.pkcs7.pem new file mode 100644 index 00000000..38970f23 --- /dev/null +++ b/test_files/keys/public_key.pkcs7.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC2TCCAcGgAwIBAgIBATANBgkqhkiG9w0BAQsFADAAMCAXDTE5MTAyNTA5MTg0 +NVoYDzIwNjkxMDEyMDkxODQ1WjAAMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAmB+jCif3+GPmlPrmYgpAcbYh6Ny1iX8V+9hRbfW6A6lVDaAX2n4uzRup +OSBj98hBeakdj/TDI1VJwgbgaHfR+IWNNFOp+pr3uLU54vZHJSeSVm7+OhIKumAT +LEUyMDs2sYCmhc0qKeolI8qOp2jlapJ4b2s/Y/TMRfW3vdMEpukvhMs5RB/Aky5g +GSnW7AZZyF0tTv7So+GJKmula6rsp7Vx+kr00ccNrvbqLSYmjkDYdMeyAoZ5Slb0 +4zub9kP21Jm8Qw2CeiHZ1h/Jkc41V13CAnM8zSB/tS/9uRXEEuQMiUBe3I9SfukL +CKbsx0oryCdjnt8Gblz8kpq2O4EadwIDAQABo1wwWjAPBgNVHRMBAf8EBTADAQH/ +MB0GA1UdDgQWBBTCU7egHRTyB/PuwdcYkGT8N/Lk1TAoBgNVHSMEITAfgBTCU7eg +HRTyB/PuwdcYkGT8N/Lk1aEEpAIwAIIBATANBgkqhkiG9w0BAQsFAAOCAQEAJb6+ +dXiNPUAV5aqM/mEN2lOxpY0RtsN0QE5ZCSgFT1wBVvqI5yAx9kvyYmfkRUbuFbq7 +YkBNNLirljz/QTOqoLvRdnPj3v3Gbs6q7hxS3Mezxrc/6RKOvNn/HfWYmIDx8Oot +5y5h2gElkml/AVrN6lUQi9cl0Za3nm9KHnAElDhHPL1kV3apHMGRGMafJD1+e44I +CjHGzRkj9tZ/11VOnQaKHLlSOhAxenFf0qSylX9ZVKyUyC/rw81d3SrCduSYfxMX +1JwJlb4HAGtjEDY8FoOzCqA506EdZqqS/PhfxovWZ42VBiF+yPh8S1tJ/t0qfAgN +TWRW5Kv+GwkPe9lhRQ== +-----END CERTIFICATE----- diff --git a/test_files/secrets/valid_eyaml_secrets.yaml b/test_files/secrets/valid_eyaml_secrets.yaml new file mode 100644 index 00000000..8c9b4329 --- /dev/null +++ b/test_files/secrets/valid_eyaml_secrets.yaml @@ -0,0 +1 @@ +value: ENC[PKCS7,MIIBeQYJKoZIhvcNAQcDoIIBajCCAWYCAQAxggEhMIIBHQIBADAFMAACAQEwDQYJKoZIhvcNAQEBBQAEggEAkEDncOHeDmZ19t7YpI6cgnuwszv4Hg7N3/h0RN2rKm+TrUag4bMc4ePypgFeGYutLcIkcxT6jmbGxVOWHY86jLojlnrcoYaD3bk9QVAxcrOPkXZRA7jTRkloUKGyXSIb6AMjs/1oGmrAmT2o/DkzrJxEXN6kiXF/W0Jf+StC7wBg/daVCioqUU7YT8NV6Pp7jyNxgFCCf8OTRFv7147/B+1QzBMa/i59O6+s2ERoVut2AT2BcAPA6efRtB5K+lZRi65CIrSAfM5svNHaYAFfTV5yWoZQDDWXbdSA5zzWHPFeiQkiu5QwaI9elgT9BpgWytXdWSBU46vv9EN3ri8FCjA8BgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBCk92BXC05sXpZeBYsUi5OwgBC4BbiUBeDabYS9h0cU1psK] diff --git a/utils.go b/utils.go index cd1c2398..cf4789fd 100644 --- a/utils.go +++ b/utils.go @@ -587,3 +587,17 @@ func concat(slices ...[]string) []string { } return slice } + +func writeStringToFile(filename string, data string) error { + file, err := os.Create(filename) + if err != nil { + return err + } + defer file.Close() + + _, err = io.WriteString(file, data) + if err != nil { + return err + } + return file.Sync() +} From 7a57fd7b4b8f7343358038dd594e55b6aeb0dadb Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Fri, 25 Oct 2019 12:23:31 +0200 Subject: [PATCH 0491/1127] Add docs for hiera-eyaml secrets backend --- docs/desired_state_specification.md | 9 ++++++ docs/how_to/README.md | 1 + .../use-hiera-eyaml-as-secrets-encryption.md | 31 +++++++++++++++++++ 3 files changed, 41 insertions(+) create mode 100644 docs/how_to/settings/use-hiera-eyaml-as-secrets-encryption.md diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 88e6db6e..7a0d8847 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -98,6 +98,9 @@ The following options can be skipped if your kubectl context is already created - **slackWebhook** : a [Slack](http://slack.com) Webhook URL to receive Helmsman notifications. This can be passed directly or in an environment variable. - **reverseDelete** : if set to `true` it will reverse the priority order whilst deleting. - **tillerless** : setting it to `true` will use [helm-tiller](https://rimusz.net/tillerless-helm) plugin instead of installing Tillers in namespaces. It disables many of the parameters for sections below. +- **eyamlEnabled** : if set to `true' it will use [hiera-eyaml](https://github.com/voxpupuli/hiera-eyaml) to decrypt secret files instead of using default helm-secrets based on sops +- **eyamlPrivateKeyPath** : if set with path to the eyaml private key file, it will use it instead of looking for default one in ./keys directory relative to where Helmsman were run. It needs to be defined in conjunction with eyamlPublicKeyPath. +- **eyamlPublicKeyPath** : if set with path to the eyaml public key file, it will use it instead of looking for default one in ./keys directory relative to where Helmsman were run. It needs to be defined in conjunction with eyamlPrivateKeyPath. > If you use `storageBackend` with a Tiller that has been previously deployed with configMaps as storage backend, you need to migrate your release information from the configMap to the new secret on your own. Helm does not support this yet. @@ -115,6 +118,9 @@ kubeContext = "minikube" # slackWebhook = $MY_SLACK_WEBHOOK # reverseDelete = false # tilerless = true +# eyamlEnabled = true +# eyamlPrivateKeyPath = "../keys/custom-key.pem" +# eyamlPublicKeyPath = "../keys/custom-key.pub" ``` ```yaml @@ -129,6 +135,9 @@ settings: #slackWebhook: "$MY_SLACK_WEBHOOK" #reverseDelete: false #tilerless: true + # eyamlEnabled: true + # eyamlPrivateKeyPath: ../keys/custom-key.pem + # eyamlPublicKeyPath: ../keys/custom-key.pub ``` ## Namespaces diff --git a/docs/how_to/README.md b/docs/how_to/README.md index f566b336..789b6252 100644 --- a/docs/how_to/README.md +++ b/docs/how_to/README.md @@ -49,3 +49,4 @@ This page contains a list of guides on how to use Helmsman. - [Limit Helmsman deployment to specific group of apps](misc/limit-deployment-to-specific-group-of-apps.md) - [Multi-tenant clusters guide](misc/multitenant_clusters_guide.md) - [Helmsman on Windows 10](misc/helmsman_on_windows10.md) + - [Use hiera-eyaml as secrets encryption backend](settings/use-hiera-eyaml-as-secrets-encryption.md) diff --git a/docs/how_to/settings/use-hiera-eyaml-as-secrets-encryption.md b/docs/how_to/settings/use-hiera-eyaml-as-secrets-encryption.md new file mode 100644 index 00000000..c617ce21 --- /dev/null +++ b/docs/how_to/settings/use-hiera-eyaml-as-secrets-encryption.md @@ -0,0 +1,31 @@ +--- +version: v1.13.0 +--- + +# Using hiera-eyaml as backend for secrets' encryption + +Helmsman uses helm-secrets as a default solution for secrets' encryption. +And while it is a good off-the-shelve solution it may quickly start causing problems when few developers starts working on the secrets files simultaneously. +SOPS-based secrets can be easily merged or rebased in case of conflicts etc. +That is why another solution for secrets organised in YAMLs were proposed in form of [hiera-eyaml](https://github.com/voxpupuli/hiera-eyaml). + +## Example + +Having environment defined with: + +* example.yaml: +```yaml +settings: + eyamlEnabled: true +``` + +Helmsman will use hiera-eyaml gem to decrypt secrets files defined for applications. +They public and private keys should be placed in `keys` directory with names of `public_key.pkcs7.pem` and `private_key.pkcs7.pem`. +The keys' path can be overwritten with + +```yaml +settings: + eyamlEnabled: true + eyamlPrivateKeyPath: ../keys/custom.pem + eyamlPublicKeyPath: ../keys/custom.pub +``` From 76cb9fd7acb4bd0753fb322252c16fb1ef40162b Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Fri, 25 Oct 2019 12:26:52 +0200 Subject: [PATCH 0492/1127] Fix missing command exec change --- helm_helpers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helm_helpers.go b/helm_helpers.go index e403ee39..ce612489 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -539,7 +539,7 @@ func initHelmTiller() (bool, string) { Description: "initializing helm tiller plugin.", } - if exitCode, err := cmd.exec(debug, verbose); exitCode != 0 { + if exitCode, err, _ := cmd.exec(debug, verbose); exitCode != 0 { return false, "ERROR: initializing helm tiller plugin : " + err } From 618466133affc63f3d8c83590e989835354f582d Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Fri, 25 Oct 2019 12:38:27 +0200 Subject: [PATCH 0493/1127] Add hiera-eyaml gem to tests dockerfile --- test_files/dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test_files/dockerfile b/test_files/dockerfile index 7d4b0ce4..9e3d03d4 100644 --- a/test_files/dockerfile +++ b/test_files/dockerfile @@ -11,7 +11,7 @@ ENV KUBE_VERSION ${KUBE_VERSION:-v1.14.8} ENV HELM_VERSION ${HELM_VERSION:-v2.15.0} ENV GORELEASER_VERSION ${GORELEASER_VERSION:-v0.120.2} RUN apk --no-cache update \ - && apk add --update --no-cache ca-certificates git \ + && apk add --update --no-cache ca-certificates git ruby \ && apk add --update -t deps curl tar gzip make bash gcc git openssh \ && rm -rf /var/cache/apk/* \ && curl -L https://storage.googleapis.com/kubernetes-release/release/${KUBE_VERSION}/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl \ @@ -28,7 +28,8 @@ RUN mkdir -p ~/.helm/plugins \ && helm plugin install https://github.com/nouney/helm-gcs \ && helm plugin install https://github.com/databus23/helm-diff \ && helm plugin install https://github.com/futuresimple/helm-secrets \ - && rm -r /tmp/helm-diff /tmp/helm-diff.tgz + && rm -r /tmp/helm-diff /tmp/helm-diff.tgz \ + && gem install hiera-eyaml --no-doc RUN mkdir /tmp/goreleaser \ && curl -L https://github.com/goreleaser/goreleaser/releases/download/${GORELEASER_VERSION}/goreleaser_Linux_arm64.tar.gz | tar zxv -C /tmp/goreleaser \ From 52faafec92ca1cb1b2923b9965a4a9ea3f8e1134 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Fri, 25 Oct 2019 14:47:12 +0200 Subject: [PATCH 0494/1127] Fix typo in docs for eyaml --- docs/how_to/settings/use-hiera-eyaml-as-secrets-encryption.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how_to/settings/use-hiera-eyaml-as-secrets-encryption.md b/docs/how_to/settings/use-hiera-eyaml-as-secrets-encryption.md index c617ce21..55c5df09 100644 --- a/docs/how_to/settings/use-hiera-eyaml-as-secrets-encryption.md +++ b/docs/how_to/settings/use-hiera-eyaml-as-secrets-encryption.md @@ -6,7 +6,7 @@ version: v1.13.0 Helmsman uses helm-secrets as a default solution for secrets' encryption. And while it is a good off-the-shelve solution it may quickly start causing problems when few developers starts working on the secrets files simultaneously. -SOPS-based secrets can be easily merged or rebased in case of conflicts etc. +SOPS-based secrets can not be easily merged or rebased in case of conflicts etc. That is why another solution for secrets organised in YAMLs were proposed in form of [hiera-eyaml](https://github.com/voxpupuli/hiera-eyaml). ## Example From 6358600c644899c3850ff2ebd30a223cfbc31f52 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Fri, 25 Oct 2019 14:49:50 +0200 Subject: [PATCH 0495/1127] Fix ignoring table headers when getting releases version in tillerless --- helm_helpers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helm_helpers.go b/helm_helpers.go index abb3b07a..b8b24ef4 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -345,7 +345,7 @@ func getChartVersion(r *release) (string, string) { return "", "ERROR: chart " + r.Chart + " with version " + r.Version + " is specified but not found in the helm repos (unrecognized helm output?)." } for i, l := range versions { - if l == "" || (strings.HasPrefix(strings.TrimSpace(l), "WARNING") && strings.HasSuffix(strings.TrimSpace(l), "CHART VERSION")) { + if l == "" || (strings.HasPrefix(strings.TrimSpace(l), "WARNING") || strings.HasSuffix(strings.TrimSpace(l), "DESCRIPTION")) { continue } else { fields := strings.Split(versions[i], "\t") From 675ee7a60f926752c21f935784d3f14741c21afb Mon Sep 17 00:00:00 2001 From: Karol Pucynski Date: Mon, 28 Oct 2019 15:37:47 +0100 Subject: [PATCH 0496/1127] updated docs link --- decision_maker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/decision_maker.go b/decision_maker.go index 55be9796..f757dc14 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -174,7 +174,7 @@ func rollbackRelease(r *release, rs releaseState) { logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] is deleted BUT from namespace [[ "+rs.Namespace+ " ]]. Will purge delete it from there and install it in namespace [[ "+r.Namespace+" ]]", false), r.Priority, create) logDecision(generateDecisionMessage(r, "WARNING: rolling back release [ "+r.Name+" ] from [[ "+rs.Namespace+" ]] to [[ "+r.Namespace+ - " ]] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md"+ + " ]] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/apps/moving_across_namespaces.md"+ " for details if this release uses PV and PVC.", false), r.Priority, create) } From d91c36962f407bdd29aa9b8f1a282e7e45c8175e Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 29 Oct 2019 11:19:56 +0000 Subject: [PATCH 0497/1127] Preparing release 1.13.0 --- main.go | 2 +- release-notes.md | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/main.go b/main.go index cbd2ef12..62e53135 100644 --- a/main.go +++ b/main.go @@ -33,7 +33,7 @@ var nsOverride string var skipValidation bool var applyLabels bool var keepUntrackedReleases bool -var appVersion = "v1.12.0" +var appVersion = "v1.13.0" var helmVersion string var kubectlVersion string var dryRun bool diff --git a/release-notes.md b/release-notes.md index d978b7c1..8287ead6 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,11 +1,16 @@ -# v1.12.0 +# v1.13.0 > If you are already using an older version of Helmsman than v1.4.0-rc, please read the changes below carefully and follow the upgrade guide [here](docs/migrating_to_v1.4.0-rc.md) # Fixes and improvements: -- Fix chart version validations and support wildcard `*` versions. PRs #283 #284 +- Fixed issues when runnin tillerless mode. PRs #320 #325 #305 #307 +- Improved support for running on Windows. PR #287 +- Improved support for local helm charts. PR #314 #291 +- Improved build process and moved to go 1.13.1. PRs #306 #313 #317 #322 # New features: -- support specifying `--history-max` tiller parameter in the DSF. PR #282 -- adding `no-env-values-subst` flag to allow disabling environment variables substitution in values files only. PR #281 - +- support hiera-eyaml as optional solution for secrets encryption. PR #323 +- New group flag to allow releasing a subset of apps +- Added support for SSM params in the Helm values file. PR #295 +- Added support for yaml anchors. PR #302 +- Added flag to opt out of the default Helm repos. PR #304 From 4e4f640500d280d0c625d094825c589455431a0b Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 29 Oct 2019 13:53:39 +0000 Subject: [PATCH 0498/1127] fix builds from master --- Gopkg.lock | 6 +++++- Makefile | 3 ++- utils.go | 6 +++--- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index ba0fa895..4a98d14c 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -42,7 +42,7 @@ version = "v0.3.1" [[projects]] - digest = "1:c78f02a2c6a138255ce52eefe18b62fe4e89815e4289819582c643ce51cdbf84" + digest = "1:958d2bd9e6e441313d4d4eabe37bc98cdab2f1fc96d20e777627bf8f9163ad96" name = "github.com/aws/aws-sdk-go" packages = [ "aws", @@ -72,6 +72,8 @@ "private/protocol", "private/protocol/eventstream", "private/protocol/eventstream/eventstreamapi", + "private/protocol/json/jsonutil", + "private/protocol/jsonrpc", "private/protocol/query", "private/protocol/query/queryutil", "private/protocol/rest", @@ -80,6 +82,7 @@ "service/s3", "service/s3/s3iface", "service/s3/s3manager", + "service/ssm", "service/sts", ] pruneopts = "UT" @@ -347,6 +350,7 @@ "github.com/aws/aws-sdk-go/aws/session", "github.com/aws/aws-sdk-go/service/s3", "github.com/aws/aws-sdk-go/service/s3/s3manager", + "github.com/aws/aws-sdk-go/service/ssm", "github.com/hashicorp/go-version", "github.com/imdario/mergo", "github.com/joho/godotenv", diff --git a/Makefile b/Makefile index cfde6d29..093ef47c 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,8 @@ PRJNAME := $(shell basename "$(PWD)") GOPATH := $(shell go env GOPATH) ifneq "$(or $(findstring :,$(GOPATH)),$(findstring ;,$(GOPATH)))" "" - $(error GOPATHs with multiple entries are not supported) + GOPATH := $(lastword $(subst :, ,$(GOPATH))) + $(info GOPATHs with multiple entries are not supported, defaulting to the last path in GOPATH) endif GOPATH := $(realpath $(GOPATH)) diff --git a/utils.go b/utils.go index cf4789fd..863d94b8 100644 --- a/utils.go +++ b/utils.go @@ -19,9 +19,9 @@ import ( "gopkg.in/yaml.v2" "github.com/BurntSushi/toml" - "helmsman/aws" - "helmsman/azure" - "helmsman/gcs" + "github.com/Praqma/helmsman/aws" + "github.com/Praqma/helmsman/azure" + "github.com/Praqma/helmsman/gcs" ) // printMap prints to the console any map of string keys and values. From c1fbb0db8f69bfa353cd31c22bdda24c16c1faf9 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 29 Oct 2019 14:01:15 +0000 Subject: [PATCH 0499/1127] fix builds from master --- helm_helpers_test.go | 50 ++++++++++++++++++++++---------------------- state.go | 28 ++++++++++++------------- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/helm_helpers_test.go b/helm_helpers_test.go index c0208e3a..17dd8178 100644 --- a/helm_helpers_test.go +++ b/helm_helpers_test.go @@ -406,17 +406,17 @@ func Test_eyamlSecrets(t *testing.T) { { name: "decryptSecrets - valid eyaml-based secrets decryption", args: args{ - s: &config { - EyamlEnabled: true, - EyamlPublicKeyPath: "./test_files/keys/public_key.pkcs7.pem", + s: &config{ + EyamlEnabled: true, + EyamlPublicKeyPath: "./test_files/keys/public_key.pkcs7.pem", EyamlPrivateKeyPath: "./test_files/keys/private_key.pkcs7.pem", }, r: &release{ - Name: "release1", - Namespace: "namespace", - Version: "1.0.0", - Enabled: true, - SecretsFile: "./test_files/secrets/valid_eyaml_secrets.yaml", + Name: "release1", + Namespace: "namespace", + Version: "1.0.0", + Enabled: true, + SecretsFile: "./test_files/secrets/valid_eyaml_secrets.yaml", }, }, want: true, @@ -424,17 +424,17 @@ func Test_eyamlSecrets(t *testing.T) { { name: "decryptSecrets - not existing eyaml-based secrets file", args: args{ - s: &config { - EyamlEnabled: true, - EyamlPublicKeyPath: "./test_files/keys/public_key.pkcs7.pem", + s: &config{ + EyamlEnabled: true, + EyamlPublicKeyPath: "./test_files/keys/public_key.pkcs7.pem", EyamlPrivateKeyPath: "./test_files/keys/private_key.pkcs7.pem", }, r: &release{ - Name: "release1", - Namespace: "namespace", - Version: "1.0.0", - Enabled: true, - SecretsFile: "./test_files/secrets/invalid_eyaml_secrets.yaml", + Name: "release1", + Namespace: "namespace", + Version: "1.0.0", + Enabled: true, + SecretsFile: "./test_files/secrets/invalid_eyaml_secrets.yaml", }, }, want: false, @@ -442,17 +442,17 @@ func Test_eyamlSecrets(t *testing.T) { { name: "decryptSecrets - not existing eyaml key", args: args{ - s: &config { - EyamlEnabled: true, - EyamlPublicKeyPath: "./test_files/keys/public_key.pkcs7.pem2", + s: &config{ + EyamlEnabled: true, + EyamlPublicKeyPath: "./test_files/keys/public_key.pkcs7.pem2", EyamlPrivateKeyPath: "./test_files/keys/private_key.pkcs7.pem", }, r: &release{ - Name: "release1", - Namespace: "namespace", - Version: "1.0.0", - Enabled: true, - SecretsFile: "./test_files/secrets/valid_eyaml_secrets.yaml", + Name: "release1", + Namespace: "namespace", + Version: "1.0.0", + Enabled: true, + SecretsFile: "./test_files/secrets/valid_eyaml_secrets.yaml", }, }, want: false, @@ -467,7 +467,7 @@ func Test_eyamlSecrets(t *testing.T) { settings.EyamlPublicKeyPath = tt.args.s.EyamlPublicKeyPath settings.EyamlPrivateKeyPath = tt.args.s.EyamlPrivateKeyPath got := decryptSecret(tt.args.r.SecretsFile) - if got != tt.want { + if got != tt.want { t.Errorf("decryptSecret() = %v, want %v", got, tt.want) } if _, err := os.Stat(tt.args.r.SecretsFile + ".dec"); err == nil { diff --git a/state.go b/state.go index e8a6c1fa..4b22190f 100644 --- a/state.go +++ b/state.go @@ -10,20 +10,20 @@ import ( // config type represents the settings fields type config struct { - KubeContext string `yaml:"kubeContext"` - Username string `yaml:"username"` - Password string `yaml:"password"` - ClusterURI string `yaml:"clusterURI"` - ServiceAccount string `yaml:"serviceAccount"` - StorageBackend string `yaml:"storageBackend"` - SlackWebhook string `yaml:"slackWebhook"` - ReverseDelete bool `yaml:"reverseDelete"` - BearerToken bool `yaml:"bearerToken"` - BearerTokenPath string `yaml:"bearerTokenPath"` - Tillerless bool `yaml:"tillerless"` - EyamlEnabled bool `yaml:"eyamlEnabled"` - EyamlPrivateKeyPath string `yaml:"eyamlPrivateKeyPath"` - EyamlPublicKeyPath string `yaml:"eyamlPublicKeyPath"` + KubeContext string `yaml:"kubeContext"` + Username string `yaml:"username"` + Password string `yaml:"password"` + ClusterURI string `yaml:"clusterURI"` + ServiceAccount string `yaml:"serviceAccount"` + StorageBackend string `yaml:"storageBackend"` + SlackWebhook string `yaml:"slackWebhook"` + ReverseDelete bool `yaml:"reverseDelete"` + BearerToken bool `yaml:"bearerToken"` + BearerTokenPath string `yaml:"bearerTokenPath"` + Tillerless bool `yaml:"tillerless"` + EyamlEnabled bool `yaml:"eyamlEnabled"` + EyamlPrivateKeyPath string `yaml:"eyamlPrivateKeyPath"` + EyamlPublicKeyPath string `yaml:"eyamlPublicKeyPath"` } // state type represents the desired state of applications on a k8s cluster. From 83e087943633f995fd4dffccea7b365d1e7aba1c Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 29 Oct 2019 14:29:04 +0000 Subject: [PATCH 0500/1127] Preparing release 1.13.0 --- release-notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release-notes.md b/release-notes.md index 8287ead6..37e3a3a1 100644 --- a/release-notes.md +++ b/release-notes.md @@ -9,7 +9,7 @@ - Improved build process and moved to go 1.13.1. PRs #306 #313 #317 #322 # New features: -- support hiera-eyaml as optional solution for secrets encryption. PR #323 +- Support hiera-eyaml as optional solution for secrets encryption. PR #323 - New group flag to allow releasing a subset of apps - Added support for SSM params in the Helm values file. PR #295 - Added support for yaml anchors. PR #302 From ecc1748025d9e1e5176da263d35294e92291e13a Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 29 Oct 2019 15:27:07 +0000 Subject: [PATCH 0501/1127] fix Tag builds --- Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Makefile b/Makefile index 093ef47c..9f6b2350 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,10 @@ PRJNAME := $(shell basename "$(PWD)") # Ensure we have an unambiguous GOPATH. GOPATH := $(shell go env GOPATH) +ifneq ($(strip $(CIRCLE_WORKING_DIRECTORY)),) + GOPATH := $(subst "/src/$(PRJNAME)",,$(CIRCLE_WORKING_DIRECTORY)) +endif + ifneq "$(or $(findstring :,$(GOPATH)),$(findstring ;,$(GOPATH)))" "" GOPATH := $(lastword $(subst :, ,$(GOPATH))) $(info GOPATHs with multiple entries are not supported, defaulting to the last path in GOPATH) From bef4e361d724da496b1eb9dc04683149b80f320d Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 29 Oct 2019 15:40:30 +0000 Subject: [PATCH 0502/1127] fix Tag builds --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9f6b2350..67da7aed 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,8 @@ PRJNAME := $(shell basename "$(PWD)") GOPATH := $(shell go env GOPATH) ifneq ($(strip $(CIRCLE_WORKING_DIRECTORY)),) - GOPATH := $(subst "/src/$(PRJNAME)",,$(CIRCLE_WORKING_DIRECTORY)) + GOPATH := $(subst /src/$(PRJNAME),,$(CIRCLE_WORKING_DIRECTORY)) + $(info "Using CIRCLE_WORKING_DIRECTORY for GOPATH") endif ifneq "$(or $(findstring :,$(GOPATH)),$(findstring ;,$(GOPATH)))" "" From 5d3922ab20dce5c484574ae9c6f4988585ae3dee Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 29 Oct 2019 15:47:51 +0000 Subject: [PATCH 0503/1127] add dep to the test docker image --- test_files/dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_files/dockerfile b/test_files/dockerfile index 9e3d03d4..58b58cf8 100644 --- a/test_files/dockerfile +++ b/test_files/dockerfile @@ -12,7 +12,7 @@ ENV HELM_VERSION ${HELM_VERSION:-v2.15.0} ENV GORELEASER_VERSION ${GORELEASER_VERSION:-v0.120.2} RUN apk --no-cache update \ && apk add --update --no-cache ca-certificates git ruby \ - && apk add --update -t deps curl tar gzip make bash gcc git openssh \ + && apk add --update -t deps curl tar gzip make bash gcc git dep openssh \ && rm -rf /var/cache/apk/* \ && curl -L https://storage.googleapis.com/kubernetes-release/release/${KUBE_VERSION}/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl \ && chmod +x /usr/local/bin/kubectl \ From e1f424d8faa25e8c58a0622c925a5217f4128733 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 29 Oct 2019 16:01:06 +0000 Subject: [PATCH 0504/1127] fix release --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 67da7aed..9cee1c06 100644 --- a/Makefile +++ b/Makefile @@ -101,7 +101,7 @@ cross: dep ## Create binaries for all OSs gox -os '!freebsd !netbsd' -arch '!arm' -output "dist/{{.Dir}}_{{.OS}}_{{.Arch}}" -ldflags '-X main.Version=${TAG}-${DATE}' .PHONY: cross -release: dep ## Generate a new release +release: ## Generate a new release @cd $(PRJDIR) && \ goreleaser --release-notes release-notes.md --rm-dist From 87ea88a872694a45ca3766abc4dc9f5df6fc970a Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Wed, 30 Oct 2019 10:57:13 +0000 Subject: [PATCH 0505/1127] fix missing goreleaser binary in CircleCI --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index a43617eb..bd11a697 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -37,7 +37,7 @@ jobs: name: Release helmsman command: | echo "releasing ..." - make release + curl -sL https://git.io/goreleaser | bash -s -- --release-notes release-notes.md --rm-dist - run: name: build docker images and push them to dockerhub From 518919488bd50f3efd4ce37849ea97da5443cfad Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Sun, 8 Dec 2019 19:05:17 -0500 Subject: [PATCH 0506/1127] Bump go to v1.13.5 --- dockerfile/dockerfile | 2 +- test_files/dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile index b8aa9942..c1abc71f 100644 --- a/dockerfile/dockerfile +++ b/dockerfile/dockerfile @@ -1,5 +1,5 @@ # This is a docker image for helmsman -ARG GO_VERSION=1.13.3 +ARG GO_VERSION=1.13.5 FROM golang:${GO_VERSION}-alpine3.10 as builder diff --git a/test_files/dockerfile b/test_files/dockerfile index 58b58cf8..f6b9ec04 100644 --- a/test_files/dockerfile +++ b/test_files/dockerfile @@ -1,7 +1,7 @@ # This is a docker image for the helmsman test container # It can be pulled from praqma/helmsman-test -ARG GO_VERSION=1.13.3 +ARG GO_VERSION=1.13.5 ARG KUBE_VERSION ARG HELM_VERSION From b64805af6663281e9863da8666146723820a6559 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Wed, 11 Dec 2019 14:08:35 +0000 Subject: [PATCH 0507/1127] use the current helm state to identify untracked releases --- helm_helpers.go | 25 +++++++++++++++---------- kube_helpers.go | 29 +++++++++++++++-------------- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/helm_helpers.go b/helm_helpers.go index 377477e2..cacd851e 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -24,6 +24,7 @@ type releaseState struct { Updated time.Time Status string Chart string + Name string Namespace string TillerNamespace string } @@ -198,6 +199,7 @@ func buildState() { Updated: time, Status: rel.Releases[i].Status, Chart: rel.Releases[i].Chart, + Name: rel.Releases[i].Name, Namespace: rel.Releases[i].Namespace, TillerNamespace: rel.Releases[i].TillerNamespace, } @@ -227,7 +229,6 @@ func helmReleaseExists(r *release, status string) (bool, releaseState) { // getReleaseRevision returns the revision number for a release func getReleaseRevision(rs releaseState) string { - return strconv.Itoa(rs.Revision) } @@ -592,18 +593,22 @@ func initHelm() (bool, string) { func cleanUntrackedReleases() { toDelete := make(map[string]map[*release]bool) log.Println("INFO: checking if any Helmsman managed releases are no longer tracked by your desired state ...") - for ns, releases := range getHelmsmanReleases() { - for r := range releases { - tracked := false - for _, app := range s.Apps { - if app.Name == r.Name && getDesiredTillerNamespace(app) == ns { - tracked = true - } - } + + // List all releases managed my Helmsman + for n, hr := range getHelmsmanReleases() { + for name, tracked := range hr { if !tracked { + rs := currentState[name+"-"+n] + ns := rs.TillerNamespace if _, ok := toDelete[ns]; !ok { toDelete[ns] = make(map[*release]bool) } + r := &release{ + Chart: rs.Chart, + Name: rs.Name, + Namespace: rs.Namespace, + TillerNamespace: rs.TillerNamespace, + } toDelete[ns][r] = true } } @@ -614,7 +619,7 @@ func cleanUntrackedReleases() { } else { for ns, releases := range toDelete { for r := range releases { - if r.isReleaseConsideredToRun() { + if !r.isReleaseConsideredToRun() { logDecision(generateDecisionMessage(r, "untracked release [ "+r.Name+" ] is ignored by target flag. Skipping.", false), -800, ignored) } else { logDecision(generateDecisionMessage(r, "untracked release found: release [ "+r.Name+" ]. It will be deleted", true), -800, delete) diff --git a/kube_helpers.go b/kube_helpers.go index 4d7d0c85..d8213c38 100644 --- a/kube_helpers.go +++ b/kube_helpers.go @@ -3,6 +3,7 @@ package main import ( "io/ioutil" "log" + "regexp" "strings" "gopkg.in/yaml.v2" @@ -437,9 +438,9 @@ func labelResource(r *release) { // getHelmsmanReleases returns a map of all releases that are labeled with "MANAGED-BY=HELMSMAN" // The releases are categorized by the namespaces in which their Tiller is running // The returned map format is: map[:map[:true]] -func getHelmsmanReleases() map[string]map[*release]bool { +func getHelmsmanReleases() map[string]map[string]bool { var lines []string - releases := make(map[string]map[*release]bool) + releases := make(map[string]map[string]bool) storageBackend := "configmap" if s.Settings.StorageBackend == "secret" { @@ -462,7 +463,7 @@ func getHelmsmanReleases() map[string]map[*release]bool { for _, ns := range namespaces { cmd := command{ Cmd: "kubectl", - Args: []string{"get", storageBackend, "-n", ns, "-l", "MANAGED-BY=HELMSMAN"}, + Args: []string{"get", storageBackend, "-n", ns, "-l", "MANAGED-BY=HELMSMAN", "-o", "name"}, Description: "getting helm releases which are managed by Helmsman in namespace [[ " + ns + " ]].", } @@ -475,18 +476,18 @@ func getHelmsmanReleases() map[string]map[*release]bool { lines = strings.Split(output, "\n") } - for i := 0; i < len(lines); i++ { - if lines[i] == "" || strings.HasSuffix(strings.TrimSpace(lines[i]), "AGE") { + for _, r := range lines { + if r == "" { continue - } else { - fields := strings.Fields(lines[i]) - if _, ok := releases[ns]; !ok { - releases[ns] = make(map[*release]bool) - } - for _, r := range s.Apps { - if r.Name == fields[0][0:strings.LastIndex(fields[0], ".v")] { - releases[ns][r] = true - } + } + r = regexp.MustCompile(`(^\w+\/|\.v\d+$)`).ReplaceAllString(r, "") + if _, ok := releases[ns]; !ok { + releases[ns] = make(map[string]bool) + } + releases[ns][r] = false + for _, app := range s.Apps { + if r == app.Name { + releases[ns][r] = true } } } From 56517154d6ab56a990dce8b3179fa0ac6016078e Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Thu, 12 Dec 2019 17:10:02 +0000 Subject: [PATCH 0508/1127] preparing new release --- main.go | 2 +- release-notes.md | 14 ++++---------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/main.go b/main.go index 62e53135..231f330d 100644 --- a/main.go +++ b/main.go @@ -33,7 +33,7 @@ var nsOverride string var skipValidation bool var applyLabels bool var keepUntrackedReleases bool -var appVersion = "v1.13.0" +var appVersion = "v1.13.1" var helmVersion string var kubectlVersion string var dryRun bool diff --git a/release-notes.md b/release-notes.md index 37e3a3a1..101c1cf3 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,16 +1,10 @@ -# v1.13.0 +# v1.13.1 > If you are already using an older version of Helmsman than v1.4.0-rc, please read the changes below carefully and follow the upgrade guide [here](docs/migrating_to_v1.4.0-rc.md) # Fixes and improvements: -- Fixed issues when runnin tillerless mode. PRs #320 #325 #305 #307 -- Improved support for running on Windows. PR #287 -- Improved support for local helm charts. PR #314 #291 -- Improved build process and moved to go 1.13.1. PRs #306 #313 #317 #322 +- Fixes bug that prevented Helmsman from deleting untracked releases; PR #337 +- Bumped to go v1.13.5; PR #333 # New features: -- Support hiera-eyaml as optional solution for secrets encryption. PR #323 -- New group flag to allow releasing a subset of apps -- Added support for SSM params in the Helm values file. PR #295 -- Added support for yaml anchors. PR #302 -- Added flag to opt out of the default Helm repos. PR #304 +None, bug fix release From 931fb033d7a3c8bbaecc932ffe9a0ca27f3be9af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lasse=20H=C3=B8jgaard?= Date: Fri, 13 Dec 2019 09:47:11 +0100 Subject: [PATCH 0509/1127] Annotate namespace with all annotations at once. --- kube_helpers.go | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/kube_helpers.go b/kube_helpers.go index 4d7d0c85..10931c52 100644 --- a/kube_helpers.go +++ b/kube_helpers.go @@ -118,18 +118,24 @@ func labelNamespace(ns string, labels map[string]string) { // annotateNamespace annotates a namespace with provided annotations func annotateNamespace(ns string, labels map[string]string) { + if len(labels) == 0 { + return + } + + var annotations string for k, v := range labels { - cmd := command{ - Cmd: "kubectl", - Args: []string{"annotate", "--overwrite", "namespace/" + ns, k + "=" + v}, - Description: "annotating namespace " + ns, - } + annotations += k + "=" + v + " " + } + cmd := command{ + Cmd: "kubectl", + Args: []string{"annotate", "--overwrite", "namespace/" + ns, annotations}, + Description: "annotating namespace " + ns, + } - exitCode, _, _ := cmd.exec(debug, verbose) - if exitCode != 0 && verbose { - log.Println("WARN: I could not annotate namespace [ " + ns + " with " + k + "=" + v + - " ]. It already exists. I am skipping this.") - } + exitCode, _, _ := cmd.exec(debug, verbose) + if exitCode != 0 && verbose { + log.Println("WARN: I could not annotate namespace [ " + ns + " with " + annotations + + " ]. It already exists. I am skipping this.") } } From cb99b473d88a09b653227a84d44316c2bb44ef57 Mon Sep 17 00:00:00 2001 From: Frederik Mogensen Date: Fri, 13 Dec 2019 13:13:23 +0100 Subject: [PATCH 0510/1127] Annotations should be a slice when passed to a command. Add same speedup for lables on namespaces --- kube_helpers.go | 53 +++++++++++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/kube_helpers.go b/kube_helpers.go index d78d9a84..d8507e15 100644 --- a/kube_helpers.go +++ b/kube_helpers.go @@ -93,50 +93,59 @@ func createNamespace(ns string) { Args: []string{"create", "namespace", ns}, Description: "creating namespace " + ns, } - exitCode, _, _ := cmd.exec(debug, verbose) + exitCode, errMsg, _ := cmd.exec(debug, verbose) if exitCode != 0 && verbose { - log.Println("WARN: I could not create namespace [ " + - ns + " ]. It already exists. I am skipping this.") + log.Printf("WARN: I could not create namespace [ %s ]. Error message: %s", ns, errMsg) } } // labelNamespace labels a namespace with provided labels func labelNamespace(ns string, labels map[string]string) { + if len(labels) == 0 { + return + } + + var labelSlice []string for k, v := range labels { - cmd := command{ - Cmd: "kubectl", - Args: []string{"label", "--overwrite", "namespace/" + ns, k + "=" + v}, - Description: "labeling namespace " + ns, - } + labelSlice = append(labelSlice, k+"="+v) + } - exitCode, _, _ := cmd.exec(debug, verbose) - if exitCode != 0 && verbose { - log.Println("WARN: I could not label namespace [ " + ns + " with " + k + "=" + v + - " ]. It already exists. I am skipping this.") - } + args := []string{"label", "--overwrite", "namespace/" + ns} + args = append(args, labelSlice...) + + cmd := command{ + Cmd: "kubectl", + Args: args, + Description: "labeling namespace " + ns, + } + + exitCode, errMsg, _ := cmd.exec(debug, verbose) + if exitCode != 0 && verbose { + log.Printf("WARN: I could not label namespace [ %s with %v ]. Error message: %s", ns, labelSlice, errMsg) } } // annotateNamespace annotates a namespace with provided annotations -func annotateNamespace(ns string, labels map[string]string) { - if len(labels) == 0 { +func annotateNamespace(ns string, annotations map[string]string) { + if len(annotations) == 0 { return } - var annotations string - for k, v := range labels { - annotations += k + "=" + v + " " + var annotationSlice []string + for k, v := range annotations { + annotationSlice = append(annotationSlice, k+"="+v) } + args := []string{"annotate", "--overwrite", "namespace/" + ns} + args = append(args, annotationSlice...) cmd := command{ Cmd: "kubectl", - Args: []string{"annotate", "--overwrite", "namespace/" + ns, annotations}, + Args: args, Description: "annotating namespace " + ns, } - exitCode, _, _ := cmd.exec(debug, verbose) + exitCode, errMsg, _ := cmd.exec(debug, verbose) if exitCode != 0 && verbose { - log.Println("WARN: I could not annotate namespace [ " + ns + " with " + annotations + - " ]. It already exists. I am skipping this.") + log.Printf("WARN: I could not annotate namespace [ %s with %v ]. Error message: %s", ns, annotationSlice, errMsg) } } From dfc8869ffc6ce9e4cdb77a5cae3021e032c2004c Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Tue, 29 Oct 2019 21:15:04 +0100 Subject: [PATCH 0511/1127] Rewrite Helmsman to support Helm v3 --- .circleci/config.yml | 4 +- .gitignore | 1 + Gopkg.lock | 226 +++++++++++++----- Gopkg.toml | 4 - Makefile | 8 +- aws/aws.go | 2 +- command.go | 14 +- decision_maker.go | 272 ++++++++------------- decision_maker_test.go | 13 +- dockerfile/dockerfile | 7 +- example.toml | 9 - example.yaml | 8 - gcs/gcs.go | 29 ++- helm_helpers.go | 519 +++++++++-------------------------------- helm_helpers_test.go | 16 +- init.go | 69 +++--- init_test.go | 2 +- kube_helpers.go | 142 +++-------- main.go | 54 +---- namespace.go | 20 -- plan.go | 41 ++-- release.go | 38 +-- release_test.go | 2 +- state.go | 133 +++-------- state_test.go | 112 ++------- test_files/dockerfile | 6 +- utils.go | 104 +++------ 27 files changed, 617 insertions(+), 1238 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index bd11a697..a8713011 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -6,7 +6,7 @@ jobs: build: working_directory: "/go/src/helmsman" docker: - - image: praqma/helmsman-test + - image: praqma/helmsman-test:helm3 steps: - checkout - run: @@ -19,7 +19,7 @@ jobs: test: working_directory: "/go/src/helmsman" docker: - - image: praqma/helmsman-test + - image: praqma/helmsman-test:helm3 steps: - checkout - run: diff --git a/.gitignore b/.gitignore index f6f42a33..51c12c71 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ *.world1 helmsman helmsman.exe +keys diff --git a/Gopkg.lock b/Gopkg.lock index 4a98d14c..ffc046b1 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -18,12 +18,12 @@ version = "v0.28.0" [[projects]] - digest = "1:d2ccb697dc13c8fbffafa37baae97594d5592ae8f7e113471084137315536e2b" + digest = "1:279540310125d2b219920588d7e2edb2a85b3317b528839166e896ce6b6f211c" name = "github.com/Azure/azure-pipeline-go" packages = ["pipeline"] pruneopts = "UT" - revision = "b8e3409182fd52e74f7d7bdfbff5833591b3b655" - version = "v0.1.8" + revision = "55fedc85a614dcd0e942a66f302ae3efb83d563c" + version = "v0.1.9" [[projects]] digest = "1:c4a5edf3b0f38e709a78dcc945997678a364c2b5adfd48842a3dd349c352f833" @@ -42,7 +42,26 @@ version = "v0.3.1" [[projects]] - digest = "1:958d2bd9e6e441313d4d4eabe37bc98cdab2f1fc96d20e777627bf8f9163ad96" + digest = "1:cd5d5e021421b663725f1c51b29808dcee703afcb273579e2061e1afe8131a64" + name = "github.com/Praqma/helmsman" + packages = [ + "aws", + "azure", + ] + pruneopts = "UT" + revision = "eb732a11111e881e5d8918e446f4444acb16a1c1" + version = "v1.13.0" + +[[projects]] + digest = "1:efe5038e25001cc88547d8ecc9ed76bbb97c15826b9bb91b931ec30dacb4e3f1" + name = "github.com/apsdehal/go-logger" + packages = ["."] + pruneopts = "UT" + revision = "1abdf898e0242cf7027d832c3e95388315c25de6" + version = "1.3.0" + +[[projects]] + digest = "1:6eee9d8a746572e8235c140a5d4bf1cba39eb99cddf5433b7bab290f11d9d042" name = "github.com/aws/aws-sdk-go" packages = [ "aws", @@ -66,6 +85,7 @@ "internal/ini", "internal/s3err", "internal/sdkio", + "internal/sdkmath", "internal/sdkrand", "internal/sdkuri", "internal/shareddefaults", @@ -84,52 +104,58 @@ "service/s3/s3manager", "service/ssm", "service/sts", + "service/sts/stsiface", ] pruneopts = "UT" - revision = "c1afd6c3340feedd0a2c1cd929dcb291d4bd09ba" - version = "v1.16.23" + revision = "2f232d11486e77d344da0723340b566d3ff7865a" + version = "v1.25.24" [[projects]] - digest = "1:5d1b5a25486fc7d4e133646d834f6fca7ba1cef9903d40e7aa786c41b89e9e91" + branch = "master" + digest = "1:b7cb6054d3dff43b38ad2e92492f220f57ae6087ee797dca298139776749ace8" + name = "github.com/golang/groupcache" + packages = ["lru"] + pruneopts = "UT" + revision = "611e8accdfc92c4187d399e95ce826046d4c8d73" + +[[projects]] + digest = "1:15b834d22254d28e02f4a591ff89427a1045e597b3832660cb1776546e3523b3" name = "github.com/golang/protobuf" packages = [ "proto", + "protoc-gen-go", "protoc-gen-go/descriptor", + "protoc-gen-go/generator", + "protoc-gen-go/generator/internal/remap", + "protoc-gen-go/grpc", + "protoc-gen-go/plugin", "ptypes", "ptypes/any", "ptypes/duration", "ptypes/timestamp", ] pruneopts = "UT" - revision = "aa810b61a9c79d51363740d207bb46cf8e620ed5" - version = "v1.2.0" + revision = "6c65a5562fc06764971b7c5d05c76c75e84bdbf7" + version = "v1.3.2" [[projects]] - digest = "1:cd9864c6366515827a759931746738ede6079faa08df9c584596370d6add135c" + digest = "1:4f880e8e7ff8803aab4bcf2c479eb766435f7decaa1edf080d8f2bf56668de1d" name = "github.com/googleapis/gax-go" packages = [ ".", "v2", ] pruneopts = "UT" - revision = "c8a15bac9b9fe955bd9f900272f9a306465d28cf" - version = "v2.0.3" - -[[projects]] - digest = "1:950caca7dfcf796419232ba996c9c3539d09f26af27ba848c4508e604c13efbb" - name = "github.com/hashicorp/go-version" - packages = ["."] - pruneopts = "UT" - revision = "d40cf49b3a77bba84a7afdbd7f1dc295d114efb1" - version = "v1.1.0" + revision = "bd5b16380fd03dc758d11cef74ba2e3bc8b0e8c2" + version = "v2.0.5" [[projects]] - digest = "1:a0cefd27d12712af4b5018dc7046f245e1e3b5760e2e848c30b171b570708f9b" + digest = "1:78d28d5b84a26159c67ea51996a230da4bc07cac648adaae1dfb5fc0ec8e40d3" name = "github.com/imdario/mergo" packages = ["."] pruneopts = "UT" - revision = "7c29201646fa3de8506f701213473dd407f19646" - version = "v0.3.7" + revision = "1afb36080aec31e0d1528973ebe6721b191b0369" + version = "v0.3.8" [[projects]] digest = "1:bb81097a5b62634f3e9fec1014657855610c82d19b9a40c17612e32651e35dca" @@ -148,22 +174,24 @@ [[projects]] branch = "master" - digest = "1:be40f095cd741773905744f16c1f7a21fd9226ccd0529f019deb7da6d667f71c" + digest = "1:d9cb8387be9021d7f18f55c85e250b6d9ec0a79ad98c6ef586d25c56a2961a6e" name = "github.com/logrusorgru/aurora" packages = ["."] pruneopts = "UT" - revision = "a7b3b318ed4e1ae5b80602b08627267303c68572" + revision = "dc85c304c4348667b70d673924f7f61f12923578" [[projects]] - digest = "1:3b5a3bc35810830ded5e26ef9516e933083a2380d8e57371fdfde3c70d7c6952" + digest = "1:b984f402fbabb0e9eb0476f0ecfa51d0b2ff11cd0ac03538d6284091033b39ae" name = "go.opencensus.io" packages = [ ".", - "exemplar", "internal", "internal/tagencoding", + "metric/metricdata", + "metric/metricproducer", "plugin/ochttp", "plugin/ochttp/propagation/b3", + "resource", "stats", "stats/internal", "stats/view", @@ -174,12 +202,34 @@ "trace/tracestate", ] pruneopts = "UT" - revision = "b7bf3cdb64150a8c8c53b769fdeb2ba581bd4d4b" - version = "v0.18.0" + revision = "59d1ce35d30f3c25ba762169da2a37eab6ffa041" + version = "v0.22.1" + +[[projects]] + branch = "master" + digest = "1:b1444bc98b5838c3116ed23e231fee4fa8509f975abd96e5d9e67e572dd01604" + name = "golang.org/x/exp" + packages = [ + "apidiff", + "cmd/apidiff", + ] + pruneopts = "UT" + revision = "a1ab85dbe136a36c66fbea07de5e3d62a0ce60ad" [[projects]] branch = "master" - digest = "1:9d2f08c64693fbe7177b5980f80c35672c80f12be79bb3bc86948b934d70e4ee" + digest = "1:21d7bad9b7da270fd2d50aba8971a041bd691165c95096a2a4c68db823cbc86a" + name = "golang.org/x/lint" + packages = [ + ".", + "golint", + ] + pruneopts = "UT" + revision = "16217165b5de779cb6a5e4fc81fa9c1166fda457" + +[[projects]] + branch = "master" + digest = "1:2ce67db9864088dee35dab5967f041d2b4ae1c7440dabb44542c1847933fd872" name = "golang.org/x/net" packages = [ "context", @@ -192,11 +242,11 @@ "trace", ] pruneopts = "UT" - revision = "ed066c81e75eba56dd9bd2139ade88125b855585" + revision = "fe3aa8a4527195a6057b3fad46619d7d090e99b5" [[projects]] branch = "master" - digest = "1:511a6232760c10dcb1ebf1ab83ef0291e2baf801f203ca6314759c5458b73a6a" + digest = "1:31e33f76456ccf54819ab4a646cf01271d1a99d7712ab84bf1a9e7b61cd2031b" name = "golang.org/x/oauth2" packages = [ ".", @@ -206,24 +256,26 @@ "jwt", ] pruneopts = "UT" - revision = "5dab4167f31cbd76b407f1486c86b40748bc5073" + revision = "0f29369cfe4552d0e4bcddc57cc75f4d7e672a33" [[projects]] branch = "master" - digest = "1:64ec1e1de5cec5e1579df2ff7ac10b20e3bf1cb4393846f631fb204dd9cb44f6" + digest = "1:999c8e6b10ddb08bc4ea3a56d4e838194c4884eac6ecf90a9aa98ed8475fdf37" name = "golang.org/x/sys" packages = ["unix"] pruneopts = "UT" - revision = "054c452bb702e465e95ce8e7a3d9a6cf0cd1188d" + revision = "f43be2a4598cf3a47be9f94f0c28197ed9eae611" [[projects]] - digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18" + digest = "1:8d8faad6b12a3a4c819a3f9618cb6ee1fa1cfc33253abeeea8b55336721e3405" name = "golang.org/x/text" packages = [ "collate", "collate/build", "internal/colltab", "internal/gen", + "internal/language", + "internal/language/compact", "internal/tag", "internal/triegen", "internal/ucd", @@ -236,18 +288,45 @@ "unicode/rangetable", ] pruneopts = "UT" - revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" - version = "v0.3.0" + revision = "342b2e1fbaa52c93f31447ad2c6abc048c63e475" + version = "v0.3.2" [[projects]] - digest = "1:768c35ec83dd17029060ea581d6ca9fdcaef473ec87e93e4bb750949035f6070" + branch = "master" + digest = "1:6743fddce1cbe389a553362cc7c76f7781a100ca91f8fa977fa1540fa840cb6d" + name = "golang.org/x/tools" + packages = [ + "cmd/goimports", + "go/analysis", + "go/analysis/passes/inspect", + "go/ast/astutil", + "go/ast/inspector", + "go/buildutil", + "go/gcexportdata", + "go/internal/gcimporter", + "go/internal/packagesdriver", + "go/packages", + "go/types/objectpath", + "go/types/typeutil", + "internal/fastwalk", + "internal/gopathwalk", + "internal/imports", + "internal/module", + "internal/semver", + "internal/span", + ] + pruneopts = "UT" + revision = "f02a19dded36842d1403be03d832b809d7a0cdf8" + +[[projects]] + digest = "1:021158246495037835846d6e65d1122c1d4a55625350b24b562ab653929916e2" name = "google.golang.org/api" packages = [ - "gensupport", "googleapi", "googleapi/internal/uritemplates", "googleapi/transport", "internal", + "internal/gensupport", "iterator", "option", "storage/v1", @@ -255,11 +334,11 @@ "transport/http/internal/propagation", ] pruneopts = "UT" - revision = "19e022d8cf43ce81f046bae8cc18c5397cc7732f" - version = "v0.1.0" + revision = "4f42dad4690a01d7f6fa461106c63889ff1be027" + version = "v0.13.0" [[projects]] - digest = "1:fa026a5c59bd2df343ec4a3538e6288dcf4e2ec5281d743ae82c120affe6926a" + digest = "1:3c03b58f57452764a4499c55c582346c0ee78c8a5033affe5bdfd9efd3da5bd1" name = "google.golang.org/appengine" packages = [ ".", @@ -274,24 +353,25 @@ "urlfetch", ] pruneopts = "UT" - revision = "e9657d882bb81064595ca3b56cbe2546bbabf7b1" - version = "v1.4.0" + revision = "971852bfffca25b069c31162ae8f247a3dba083b" + version = "v1.6.5" [[projects]] branch = "master" - digest = "1:a7d48ca460ca1b4f6ccd8c95502443afa05df88aee84de7dbeb667a8754e8fa6" + digest = "1:bef0b15c7587f13aed86ea55e067b863837c18d0a2a47410ee85c3c5008686b3" name = "google.golang.org/genproto" packages = [ "googleapis/api/annotations", "googleapis/iam/v1", "googleapis/rpc/code", "googleapis/rpc/status", + "googleapis/type/expr", ] pruneopts = "UT" - revision = "db91494dd46c1fdcbbde05e5ff5eb56df8f7d79a" + revision = "919d9bdd9fe6f1a5dd95ce5d5e4cdb8fd3c516d0" [[projects]] - digest = "1:9ab5a33d8cb5c120602a34d2e985ce17956a4e8c2edce7e6961568f95e40c09a" + digest = "1:6cd77d0b616d2dcebd363dfecba593f27b0151fc82cdb5fbfb96c5a7cfbc95b5" name = "google.golang.org/grpc" packages = [ ".", @@ -308,6 +388,7 @@ "grpclog", "internal", "internal/backoff", + "internal/balancerload", "internal/binarylog", "internal/channelz", "internal/envconfig", @@ -322,21 +403,56 @@ "resolver", "resolver/dns", "resolver/passthrough", + "serviceconfig", "stats", "status", "tap", ] pruneopts = "UT" - revision = "a02b0774206b209466313a0b525d2c738fe407eb" - version = "v1.18.0" + revision = "f6d0f9ee430895e87ef1ceb5ac8f39725bafceef" + version = "v1.24.0" [[projects]] - digest = "1:4d2e5a73dc1500038e504a8d78b986630e3626dc027bc030ba5c75da257cdb96" + digest = "1:59f10c1537d2199d9115d946927fe31165959a95190849c82ff11e05803528b0" name = "gopkg.in/yaml.v2" packages = ["."] pruneopts = "UT" - revision = "51d6538a90f86fe93ac480b35f37b2be17fef232" - version = "v2.2.2" + revision = "f221b8435cfb71e54062f6c6e99e9ade30b124d5" + version = "v2.2.4" + +[[projects]] + digest = "1:131158a88aad1f94854d0aa21a64af2802d0a470fb0f01cb33c04fafd2047111" + name = "honnef.co/go/tools" + packages = [ + "arg", + "cmd/staticcheck", + "config", + "deprecated", + "facts", + "functions", + "go/types/typeutil", + "internal/cache", + "internal/passes/buildssa", + "internal/renameio", + "internal/sharedcheck", + "lint", + "lint/lintdsl", + "lint/lintutil", + "lint/lintutil/format", + "loader", + "printf", + "simple", + "ssa", + "ssautil", + "staticcheck", + "staticcheck/vrp", + "stylecheck", + "unused", + "version", + ] + pruneopts = "UT" + revision = "afd67930eec2a9ed3e9b19f684d17a062285f16a" + version = "2019.2.3" [solve-meta] analyzer-name = "dep" @@ -346,12 +462,14 @@ "github.com/Azure/azure-pipeline-go/pipeline", "github.com/Azure/azure-storage-blob-go/azblob", "github.com/BurntSushi/toml", + "github.com/Praqma/helmsman/aws", + "github.com/Praqma/helmsman/azure", + "github.com/apsdehal/go-logger", "github.com/aws/aws-sdk-go/aws", "github.com/aws/aws-sdk-go/aws/session", "github.com/aws/aws-sdk-go/service/s3", "github.com/aws/aws-sdk-go/service/s3/s3manager", "github.com/aws/aws-sdk-go/service/ssm", - "github.com/hashicorp/go-version", "github.com/imdario/mergo", "github.com/joho/godotenv", "github.com/logrusorgru/aurora", diff --git a/Gopkg.toml b/Gopkg.toml index 09f5bdf1..3c3e852b 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -40,10 +40,6 @@ name = "github.com/aws/aws-sdk-go" version = "1.15.43" -[[constraint]] - name = "github.com/hashicorp/go-version" - version = "1.1.0" - [[constraint]] name = "github.com/imdario/mergo" version = "0.3.6" diff --git a/Makefile b/Makefile index 9cee1c06..9a383481 100644 --- a/Makefile +++ b/Makefile @@ -90,9 +90,13 @@ check: $(SRCDIR) fmt go vet #${PKGS} .PHONY: check -test: dep check ## Run unit tests +repo: + @cd $(PRJDIR) && \ + helm repo add stable https://kubernetes-charts.storage.googleapis.com +.PHONY: repo + +test: dep check repo ## Run unit tests @cd $(PRJDIR) && \ - helm init --client-only && \ go test -v -cover -p=1 -args -f example.toml .PHONY: test diff --git a/aws/aws.go b/aws/aws.go index 83990005..c7f32656 100644 --- a/aws/aws.go +++ b/aws/aws.go @@ -65,7 +65,7 @@ func ReadFile(bucketName string, filename string, outFile string, noColors bool) log.Fatal(style.Bold(style.Red("ERROR: Failed to download file " + filename + " from S3: " + err.Error()))) } - log.Println("INFO: Successfully downloaded " + filename + " from S3 as " + outFile) + log.Println("Successfully downloaded " + filename + " from S3 as " + outFile) } diff --git a/command.go b/command.go index f0aede7f..41651442 100644 --- a/command.go +++ b/command.go @@ -3,8 +3,6 @@ package main import ( "bytes" "fmt" - "log" - "os" "os/exec" "strings" "syscall" @@ -42,10 +40,10 @@ func (c command) exec(debug bool, verbose bool) (int, string, string) { } if debug { - log.Println("INFO: " + c.Description) + logs.Debug("" + c.Description) } if verbose { - log.Println("VERBOSE: " + c.Cmd + " " + strings.Join(args, " ")) + logs.Debug(c.Cmd + " " + strings.Join(args, " ")) } cmd := exec.Command(c.Cmd, args...) @@ -53,12 +51,8 @@ func (c command) exec(debug bool, verbose bool) (int, string, string) { cmd.Stdout = &stdout cmd.Stderr = &stderr - // we need to tell the TILLER to be silent. This will only matter in - // tillerless mode. - cmd.Env = append(os.Environ(), "HELM_TILLER_SILENT=true") - if err := cmd.Start(); err != nil { - log.Println("ERROR: cmd.Start: " + err.Error()) + logs.Info("cmd.Start: " + err.Error()) return 1, err.Error(), "" } @@ -68,7 +62,7 @@ func (c command) exec(debug bool, verbose bool) (int, string, string) { return status.ExitStatus(), stderr.String(), "" } } else { - logError("ERROR: cmd.Wait: " + err.Error()) + logError("cmd.Wait: " + err.Error()) } } return 0, stdout.String(), stderr.String() diff --git a/decision_maker.go b/decision_maker.go index f757dc14..14be4181 100644 --- a/decision_maker.go +++ b/decision_maker.go @@ -3,13 +3,11 @@ package main import ( "fmt" "regexp" - "runtime" "strconv" "strings" ) var outcome plan -var releases string var settings config // makePlan creates a plan of the actions needed to make the desired state come true. @@ -31,70 +29,66 @@ func makePlan(s *state) *plan { func decide(r *release, s *state) { // check for presence in defined targets or groups if !r.isReleaseConsideredToRun() { - logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] is ignored due to passed constraints. Skipping.", false), r.Priority, ignored) + logDecision("release [ "+r.Name+" ] is ignored due to passed constraints. Skipping.", r.Priority, ignored) return } if destroy { - if ok, rs := helmReleaseExists(r, "DEPLOYED"); ok { - deleteRelease(r, rs) - } - if ok, rs := helmReleaseExists(r, "FAILED"); ok { - deleteRelease(r, rs) + if ok := isReleaseExisting(r, ""); ok { + deleteRelease(r) + return } - return } - // check for deletion if !r.Enabled { - if ok, rs := helmReleaseExists(r, ""); ok { - if !isProtected(r, rs) { + if ok := isReleaseExisting(r, ""); ok { - // delete it - deleteRelease(r, rs) + if isProtected(r) { - } else { - logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ - "you remove its protection.", false), r.Priority, noop) + logDecision("release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ + "protection is removed.", r.Priority, noop) + return } - } else { - logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] is set to be disabled but is not yet deployed. Skipping.", false), r.Priority, noop) + deleteRelease(r) + return } + logDecision("release [ "+r.Name+" ] is disabled. Skipping.", r.Priority, noop) + return - } else { // check for install/upgrade/rollback - if ok, rs := helmReleaseExists(r, "deployed"); ok { - if !isProtected(r, rs) { - inspectUpgradeScenario(r, rs) // upgrade or move + } else { + if ok := isReleaseExisting(r, "deployed"); ok { + if !isProtected(r) { + inspectUpgradeScenario(r) // upgrade or move } else { - logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ - "you remove its protection.", false), r.Priority, noop) + logDecision("release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ + "you remove its protection.", r.Priority, noop) } - } else if ok, rs := helmReleaseExists(r, "deleted"); ok { - if !isProtected(r, rs) { + } else if ok := isReleaseExisting(r, "deleted"); ok { + if !isProtected(r) { - rollbackRelease(r, rs) // rollback + rollbackRelease(r) // rollback } else { - logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ - "you remove its protection.", false), r.Priority, noop) + logDecision("release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ + "you remove its protection.", r.Priority, noop) } - } else if ok, rs := helmReleaseExists(r, "failed"); ok { + } else if ok := isReleaseExisting(r, "failed"); ok { - if !isProtected(r, rs) { + if !isProtected(r) { - logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is in FAILED state. I will upgrade it for you. Hope it gets fixed!", false), r.Priority, change) + logDecision("release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is in FAILED state. Upgrade is scheduled!", r.Priority, change) upgradeRelease(r) } else { - logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ - "you remove its protection.", false), r.Priority, noop) + logDecision("release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ + "you remove its protection.", r.Priority, noop) } } else { - installRelease(r) // install a new release + installRelease(r) } @@ -102,51 +96,26 @@ func decide(r *release, s *state) { } -// helmCommandFromConfig returns the command used to invoke helm. If configured to -// operate without a tiller it will return `helm tiller run NAMESPACE -- helm` -// where NAMESPACE is the namespace that the release is configured to use. -// If not configured to run without a tiller will just return `helm`. -func helmCommand(namespace string) []string { - if settings.Tillerless { - if runtime.GOOS == "windows" { - logError("ERROR: Tillerless Helm plugin is not supported on Windows") - } - if namespace != "" { - return []string{"tiller", "run", namespace, "--", "helm"} - } else { - return []string{"tiller", "run", "helm"} - } - } - - return nil -} - -// helmCommandFromConfig calls helmCommand returning the correct way to invoke -// helm. -func helmCommandFromConfig(r *release) []string { - return helmCommand(getDesiredTillerNamespace(r)) -} - // testRelease creates a Helm command to test a particular release. func testRelease(r *release) { cmd := command{ - Cmd: "helm", - Args: concat(helmCommandFromConfig(r), []string{"test", r.Name}, getDesiredTillerNamespaceFlag(r), getTLSFlags(r)), - Description: generateCmdDescription(r, "running tests for"), + Cmd: helmBin, + Args: []string{"test", "--namespace", r.Namespace, r.Name}, + Description: "running tests for release [ "+r.Name+" ] in namespace [[ "+r.Namespace+" ]]", } outcome.addCommand(cmd, r.Priority, r) - logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is required to be tested when installed. Got it!", false), r.Priority, noop) + logDecision("release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is required to be tested when installed. Got it!", r.Priority, noop) } // installRelease creates a Helm command to install a particular release in a particular namespace using a particular Tiller. func installRelease(r *release) { cmd := command{ - Cmd: "helm", - Args: concat(helmCommandFromConfig(r), []string{"install", r.Chart, "-n", r.Name, "--namespace", r.Namespace}, getValuesFiles(r), []string{"--version", r.Version}, getSetValues(r), getSetStringValues(r), getWait(r), getDesiredTillerNamespaceFlag(r), getTLSFlags(r), getHelmFlags(r)), - Description: generateCmdDescription(r, "installing"), + Cmd: helmBin, + Args: concat([]string{"install", r.Name, r.Chart, "--namespace", r.Namespace}, getValuesFiles(r), []string{"--version", r.Version}, getSetValues(r), getSetStringValues(r), getWait(r), getHelmFlags(r)), + Description: "installing release [ "+r.Name+" ] in namespace [[ "+r.Namespace+" ]]", } outcome.addCommand(cmd, r.Priority, r) - logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] is not installed. Will install it in namespace [[ "+r.Namespace+" ]]", true), r.Priority, create) + logDecision("release [ "+r.Name+" ] is not installed. Will install it in namespace [[ "+r.Namespace+" ]]", r.Priority, create) if r.Test { testRelease(r) @@ -156,51 +125,48 @@ func installRelease(r *release) { // rollbackRelease evaluates if a rollback action needs to be taken for a given release. // if the release is already deleted but from a different namespace than the one specified in input, // it purge deletes it and create it in the specified namespace. -func rollbackRelease(r *release, rs releaseState) { +func rollbackRelease(r *release) { + rs, ok := currentState[fmt.Sprintf("%s-%s", r.Name, r.Namespace)] + if !ok { + return + } if r.Namespace == rs.Namespace { cmd := command{ - Cmd: "helm", - Args: concat(helmCommandFromConfig(r), []string{"rollback", r.Name, getReleaseRevision(rs)}, getWait(r), getDesiredTillerNamespaceFlag(r), getTLSFlags(r), getTimeout(r), getNoHooks(r), getDryRunFlags()), - Description: generateCmdDescription(r, "rolling back"), + Cmd: helmBin, + Args: concat([]string{"rollback", r.Name, getReleaseRevision(rs)}, getWait(r), getTimeout(r), getNoHooks(r), getDryRunFlags()), + Description: "rolling back release [ "+r.Name+" ] in namespace [[ "+r.Namespace+" ]]", } outcome.addCommand(cmd, r.Priority, r) upgradeRelease(r) // this is to reflect any changes in values file(s) - logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] is currently deleted and is desired to be rolledback to "+ - "namespace [[ "+r.Namespace+" ]] . It will also be upgraded in case values have changed.", false), r.Priority, create) + logDecision("release [ "+r.Name+" ] is currently deleted and is desired to be rolledback to "+ + "namespace [[ "+r.Namespace+" ]] . It will also be upgraded in case values have changed.", r.Priority, create) } else { reInstallRelease(r, rs) - logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] is deleted BUT from namespace [[ "+rs.Namespace+ - " ]]. Will purge delete it from there and install it in namespace [[ "+r.Namespace+" ]]", false), r.Priority, create) - logDecision(generateDecisionMessage(r, "WARNING: rolling back release [ "+r.Name+" ] from [[ "+rs.Namespace+" ]] to [[ "+r.Namespace+ + logDecision("release [ "+r.Name+" ] is deleted BUT from namespace [[ "+rs.Namespace+ + " ]]. Will purge delete it from there and install it in namespace [[ "+r.Namespace+" ]]", r.Priority, create) + logDecision("WARNING: rolling back release [ "+r.Name+" ] from [[ "+rs.Namespace+" ]] to [[ "+r.Namespace+ " ]] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/apps/moving_across_namespaces.md"+ - " for details if this release uses PV and PVC.", false), r.Priority, create) + " for details if this release uses PV and PVC.", r.Priority, create) } } // deleteRelease deletes a release from a particular Tiller in a k8s cluster -func deleteRelease(r *release, rs releaseState) { - p := "" - purgeDesc := "" - if r.Purge { - p = "--purge" - purgeDesc = "and purged!" - } - +func deleteRelease(r *release) { priority := r.Priority if settings.ReverseDelete == true { priority = priority * -1 } cmd := command{ - Cmd: "helm", - Args: concat(helmCommandFromConfig(r), []string{"delete", p, r.Name}, getCurrentTillerNamespaceFlag(rs), getTLSFlags(r), getDryRunFlags()), - Description: generateCmdDescription(r, "deleting"), + Cmd: helmBin, + Args: concat([]string{"delete", "--namespace", r.Namespace, r.Name}, getDryRunFlags()), + Description: "deleting release [ "+r.Name+" ] in namespace [[ "+r.Namespace+" ]]", } outcome.addCommand(cmd, priority, r) - logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] is desired to be deleted "+purgeDesc+". Planning this for you!", false), r.Priority, delete) + logDecision(fmt.Sprintf("release [ %s ] is desired to be DELETED.", r.Name), r.Priority, delete) } // inspectUpgradeScenario evaluates if a release should be upgraded. @@ -210,7 +176,12 @@ func deleteRelease(r *release, rs releaseState) { // it will be purge deleted and installed in the same namespace using the new chart. // - If the release is NOT in the same namespace specified in the input, // it will be purge deleted and installed in the new namespace. -func inspectUpgradeScenario(r *release, rs releaseState) { +func inspectUpgradeScenario(r *release) { + + rs, ok := currentState[fmt.Sprintf("%s-%s", r.Name, r.Namespace)] + if !ok { + return + } if r.Namespace == rs.Namespace { @@ -225,31 +196,30 @@ func inspectUpgradeScenario(r *release, rs releaseState) { // upgrade diffRelease(r) upgradeRelease(r) - logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] is desired to be upgraded. Planning this for you!", false), r.Priority, change) + logDecision("release [ "+r.Name+" ] will be upgraded.", r.Priority, change) } else if extractChartName(r.Chart) != getReleaseChartName(rs) { reInstallRelease(r, rs) - logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] is desired to use a new Chart [ "+r.Chart+ - " ]. I am planning a purge delete of the current release and will install it with the new chart in namespace [[ "+ - r.Namespace+" ]]", false), r.Priority, change) + logDecision("release [ "+r.Name+" ] is desired to use a new Chart [ "+r.Chart+ + " ]. Delete of the current release will be planned and new chart will be installed in namespace [[ "+ + r.Namespace+" ]]", r.Priority, change) } else { if diff := diffRelease(r); diff != "" { upgradeRelease(r) - logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] is currently enabled and have some changed parameters. "+ - "I will upgrade it!", false), r.Priority, change) + logDecision("release [ "+r.Name+" ] is installed and has some changes to apply. "+ + "I will upgrade it!", r.Priority, change) } else { - logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] is desired to be enabled and is currently enabled. "+ - "Nothing to do here!", false), r.Priority, noop) + logDecision("release [ "+r.Name+" ] is enabled and has no changes to apply.", r.Priority, noop) } } } else { reInstallRelease(r, rs) - logDecision(generateDecisionMessage(r, "release [ "+r.Name+" ] is desired to be enabled in a new namespace [[ "+r.Namespace+ + logDecision("release [ "+r.Name+" ] is desired to be enabled in a new namespace [[ "+r.Namespace+ " ]]. I am planning a purge delete of the current release from namespace [[ "+rs.Namespace+" ]] "+ - "and will install it for you in namespace [[ "+r.Namespace+" ]]", false), r.Priority, change) - logDecision(generateDecisionMessage(r, "WARNING: moving release [ "+r.Name+" ] from [[ "+rs.Namespace+" ]] to [[ "+r.Namespace+ + "and will install it for you in namespace [[ "+r.Namespace+" ]]", r.Priority, change) + logDecision("WARNING: moving release [ "+r.Name+" ] from [[ "+rs.Namespace+" ]] to [[ "+r.Namespace+ " ]] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md"+ - " for details if this release uses PV and PVC.", false), r.Priority, change) + " for details if this release uses PV and PVC.", r.Priority, change) } } @@ -271,9 +241,9 @@ func diffRelease(r *release) string { } cmd := command{ - Cmd: "helm", - Args: concat(helmCommandFromConfig(r), []string{"diff", colorFlag}, diffContextFlag, []string{suppressDiffSecretsFlag, "upgrade", r.Name, r.Chart}, getValuesFiles(r), []string{"--version", r.Version}, getSetValues(r), getSetStringValues(r), getDesiredTillerNamespaceFlag(r), getTLSFlags(r)), - Description: generateCmdDescription(r, "diffing"), + Cmd: helmBin, + Args: concat([]string{"diff", colorFlag}, diffContextFlag, []string{suppressDiffSecretsFlag, "--namespace", r.Namespace, "upgrade", r.Name, r.Chart}, getValuesFiles(r), []string{"--version", r.Version}, getSetValues(r), getSetStringValues(r)), + Description: "diffing release [ "+r.Name+" ] in namespace [[ "+r.Namespace+" ]]", } if exitCode, msg, _ = cmd.exec(debug, verbose); exitCode != 0 { @@ -294,9 +264,9 @@ func upgradeRelease(r *release) { force = "--force" } cmd := command{ - Cmd: "helm", - Args: concat(helmCommandFromConfig(r), []string{"upgrade", r.Name, r.Chart}, getValuesFiles(r), []string{"--version", r.Version, force}, getSetValues(r), getSetStringValues(r), getWait(r), getDesiredTillerNamespaceFlag(r), getTLSFlags(r), getHelmFlags(r)), - Description: generateCmdDescription(r, "upgrading"), + Cmd: helmBin, + Args: concat([]string{"upgrade", "--namespace", r.Namespace, r.Name, r.Chart}, getValuesFiles(r), []string{"--version", r.Version, force}, getSetValues(r), getSetStringValues(r), getWait(r), getHelmFlags(r)), + Description: "upgrading release [ "+r.Name+" ] in namespace [[ "+r.Namespace+" ]]", } outcome.addCommand(cmd, r.Priority, r) @@ -307,16 +277,16 @@ func upgradeRelease(r *release) { func reInstallRelease(r *release, rs releaseState) { delCmd := command{ - Cmd: "helm", - Args: concat(helmCommandFromConfig(r), []string{"delete", "--purge", r.Name}, getCurrentTillerNamespaceFlag(rs), getTLSFlags(r), getDryRunFlags()), - Description: generateCmdDescription(r, "deleting"), + Cmd: helmBin, + Args: concat([]string{"delete", "--purge", r.Name}, getDryRunFlags()), + Description: "deleting release [ "+r.Name+" ] in namespace [[ "+r.Namespace+" ]]", } outcome.addCommand(delCmd, r.Priority, r) installCmd := command{ - Cmd: "helm", - Args: concat(helmCommandFromConfig(r), []string{"install", r.Chart, "--version", r.Version, "-n", r.Name, "--namespace", r.Namespace}, getValuesFiles(r), getSetValues(r), getSetStringValues(r), getWait(r), getDesiredTillerNamespaceFlag(r), getTLSFlags(r), getHelmFlags(r)), - Description: generateCmdDescription(r, "installing"), + Cmd: helmBin, + Args: concat([]string{"install", r.Chart, "--version", r.Version, "-n", r.Name, "--namespace", r.Namespace}, getValuesFiles(r), getSetValues(r), getSetStringValues(r), getWait(r), getHelmFlags(r)), + Description: "installing release [ "+r.Name+" ] in namespace [[ "+r.Namespace+" ]]", } outcome.addCommand(installCmd, r.Priority, r) } @@ -354,7 +324,7 @@ func getNoHooks(r *release) []string { // getTimeout returns the timeout flag for install/upgrade commands func getTimeout(r *release) []string { if r.Timeout != 0 { - return []string{"--timeout", strconv.Itoa(r.Timeout)} + return []string{"--timeout", strconv.Itoa(r.Timeout) + "s"} } return []string{} } @@ -371,7 +341,7 @@ func getValuesFiles(r *release) []string { if r.SecretsFile != "" { if !helmPluginExists("secrets") { - logError("ERROR: helm secrets plugin is not installed/configured correctly. Aborting!") + logError("helm secrets plugin is not installed/configured correctly. Aborting!") } if ok := decryptSecret(r.SecretsFile); !ok { logError("Failed to decrypt secret file" + r.SecretsFile) @@ -379,7 +349,7 @@ func getValuesFiles(r *release) []string { fileList = append(fileList, r.SecretsFile+".dec") } else if len(r.SecretsFiles) > 0 { if !helmPluginExists("secrets") { - logError("ERROR: helm secrets plugin is not installed/configured correctly. Aborting!") + logError("helm secrets plugin is not installed/configured correctly. Aborting!") } for i := 0; i < len(r.SecretsFiles); i++ { if ok := decryptSecret(r.SecretsFiles[i]); !ok { @@ -446,18 +416,14 @@ func getCurrentNamespaceProtection(rs releaseState) bool { // A protected is release is either: a) deployed in a protected namespace b) flagged as protected in the desired state file // Any release in a protected namespace is protected by default regardless of its flag // returns true if a release is protected, false otherwise -func isProtected(r *release, rs releaseState) bool { +func isProtected(r *release) bool { // if the release does not exist in the cluster, it is not protected - if ok, _ := helmReleaseExists(r, ""); !ok { + if ok := isReleaseExisting(r, ""); !ok { return false } - if getCurrentNamespaceProtection(rs) { - return true - } - - if r.Protected { + if s.Namespaces[r.Namespace].Protected || r.Protected { return true } @@ -465,58 +431,6 @@ func isProtected(r *release, rs releaseState) bool { } -// getDesiredTillerNamespaceFlag returns a tiller-namespace flag with which a release is desired to be maintained -func getDesiredTillerNamespaceFlag(r *release) []string { - return []string{"--tiller-namespace", getDesiredTillerNamespace(r)} -} - -// getDesiredTillerNamespace returns the Tiller namespace with which a release should be managed -func getDesiredTillerNamespace(r *release) string { - if r.TillerNamespace != "" { - return r.TillerNamespace - } else if ns, ok := s.Namespaces[r.Namespace]; ok && (s.Settings.Tillerless || (ns.InstallTiller || ns.UseTiller)) { - return r.Namespace - } - - return "kube-system" -} - -// getCurrentTillerNamespaceFlag returns the tiller-namespace with which a release is currently maintained -func getCurrentTillerNamespaceFlag(rs releaseState) []string { - if rs.TillerNamespace != "" { - return []string{"--tiller-namespace", rs.TillerNamespace} - } - return []string{} -} - -// getTLSFlags returns TLS flags with which a release is maintained -// If the release where the namespace is to be deployed has Tiller deployed, the TLS flags will use certs/keys for that namespace (if any) -// otherwise, it will be the certs/keys for the kube-system namespace. -func getTLSFlags(r *release) []string { - tls := []string{} - ns := s.Namespaces[r.TillerNamespace] - if r.TillerNamespace != "" { - if tillerTLSEnabled(ns) { - - tls = append(tls, "--tls", "--tls-ca-cert", r.TillerNamespace+"-ca.cert", "--tls-cert", r.TillerNamespace+"-client.cert", "--tls-key", r.TillerNamespace+"-client.key") - } - } else if s.Namespaces[r.Namespace].InstallTiller { - ns := s.Namespaces[r.Namespace] - if tillerTLSEnabled(ns) { - - tls = append(tls, "--tls", "--tls-ca-cert", r.Namespace+"-ca.cert", "--tls-cert", r.Namespace+"-client.cert", "--tls-key", r.Namespace+"-client.key") - } - } else { - ns := s.Namespaces["kube-system"] - if tillerTLSEnabled(ns) { - - tls = append(tls, "--tls", "--tls-ca-cert", "kube-system-ca.cert", "--tls-cert", "kube-system-client.cert", "--tls-key", "kube-system-client.key") - } - } - - return tls -} - // getDryRunFlags returns dry-run flag func getDryRunFlags() []string { if dryRun { @@ -538,7 +452,7 @@ func getHelmFlags(r *release) []string { func checkChartDepUpdate(r *release) { if updateDeps && isLocalChart(r.Chart) { if ok, err := updateChartDep(r.Chart); !ok { - logError("ERROR: helm dependency update failed: " + err) + logError("helm dependency update failed: " + err) } } } diff --git a/decision_maker_test.go b/decision_maker_test.go index 8c2f0f30..2503a3db 100644 --- a/decision_maker_test.go +++ b/decision_maker_test.go @@ -81,7 +81,7 @@ func Test_getValuesFiles(t *testing.T) { func Test_inspectUpgradeScenario(t *testing.T) { type args struct { r *release - s releaseState + s *map[string]releaseState } tests := []struct { name string @@ -98,9 +98,11 @@ func Test_inspectUpgradeScenario(t *testing.T) { Chart: "./test_files/chart-test", Enabled: true, }, - s: releaseState{ - Namespace: "namespace", - Chart: "chart-1.0.0", + s: &map[string]releaseState{ + "release1-namespace": { + Namespace: "namespace", + Chart: "chart-1.0.0", + }, }, }, want: change, @@ -109,9 +111,10 @@ func Test_inspectUpgradeScenario(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { outcome = plan{} + currentState = *tt.args.s // Act - inspectUpgradeScenario(tt.args.r, tt.args.s) + inspectUpgradeScenario(tt.args.r) got := outcome.Decisions[0].Type t.Log(outcome.Decisions[0].Description) diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile index c1abc71f..26c73d01 100644 --- a/dockerfile/dockerfile +++ b/dockerfile/dockerfile @@ -24,8 +24,8 @@ ARG KUBE_VERSION ARG HELM_VERSION ENV KUBE_VERSION ${KUBE_VERSION:-v1.14.8} -ENV HELM_VERSION ${HELM_VERSION:-v2.15.0} -ENV HELM_DIFF_VERSION ${HELM_DIFF_VERSION:-v2.11.0+5} +ENV HELM_VERSION ${HELM_VERSION:-v3.0.1} +ENV HELM_DIFF_VERSION ${HELM_DIFF_VERSION:-v3.0.0-rc.7} RUN apk --no-cache update \ && apk add --update --no-cache ca-certificates git openssh ruby \ @@ -33,7 +33,7 @@ RUN apk --no-cache update \ && rm -rf /var/cache/apk/* \ && curl -L https://storage.googleapis.com/kubernetes-release/release/${KUBE_VERSION}/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl \ && chmod +x /usr/local/bin/kubectl \ - && curl -L http://storage.googleapis.com/kubernetes-helm/helm-${HELM_VERSION}-linux-amd64.tar.gz | tar zxv -C /tmp \ + && curl -L https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz | tar zxv -C /tmp \ && mv /tmp/linux-amd64/helm /usr/local/bin/helm \ && rm -rf /tmp/linux-amd64 \ && chmod +x /usr/local/bin/helm @@ -45,7 +45,6 @@ RUN mkdir -p ~/.helm/plugins \ && helm plugin install https://github.com/nouney/helm-gcs \ && helm plugin install https://github.com/databus23/helm-diff --version ${HELM_DIFF_VERSION} \ && helm plugin install https://github.com/futuresimple/helm-secrets \ - && helm plugin install https://github.com/rimusz/helm-tiller \ && rm -r /tmp/helm-diff /tmp/helm-diff.tgz \ && gem install hiera-eyaml --no-doc diff --git a/example.toml b/example.toml index 8dfe8b25..1c19b42e 100644 --- a/example.toml +++ b/example.toml @@ -52,14 +52,6 @@ memory = "300Mi" [namespaces.staging] protected = false - installTiller = true -# tillerServiceAccount = "tiller-staging" # should already exist in the staging namespace -# tillerRole = "cluster-admin" # Give tiller full access to the cluster -# caCert = "secrets/ca.cert.pem" # or an env var, e.g. "$CA_CERT_PATH" -# tillerCert = "secrets/tiller.cert.pem" # or S3 bucket s3://mybucket/tiller.crt -# tillerKey = "secrets/tiller.key.pem" # or GCS bucket gs://mybucket/tiller.key -# clientCert = "secrets/helm.cert.pem" -# clientKey = "secrets/helm.key.pem" [namespaces.staging.labels] env = "staging" @@ -88,7 +80,6 @@ ### Optional values below name = "jenkins" # should be unique across all apps which are managed by the same Tiller valuesFile = "" # leaving it empty uses the default chart values - #tillerNamespace = "kube-system" # which Tiller to use to deploy this release purge = false # will only be considered when there is a delete operation test = false # run the tests when this release is installed for the first time only protected = true diff --git a/example.yaml b/example.yaml index 21e3737d..b7314fb3 100644 --- a/example.yaml +++ b/example.yaml @@ -45,14 +45,6 @@ namespaces: memory: "300Mi" staging: protected: false - installTiller: true - #tillerServiceAccount: "tiller-staging" # should already exist in the staging namespace - #tillerRole: "cluster-admin" # Give tiller full access to the cluster - #caCert: "secrets/ca.cert.pem" # or an env var, e.g. "$CA_CERT_PATH" - #tillerCert: "secrets/tiller.cert.pem" # or S3 bucket s3://mybucket/tiller.crt - #tillerKey: "secrets/tiller.key.pem" # or GCS bucket gs://mybucket/tiller.key - #clientCert: "secrets/helm.cert.pem" - #clientKey: "secrets/helm.key.pem" labels: env: "staging" diff --git a/gcs/gcs.go b/gcs/gcs.go index d0badabf..1d4485eb 100644 --- a/gcs/gcs.go +++ b/gcs/gcs.go @@ -1,9 +1,9 @@ package gcs import ( + "fmt" "io" "io/ioutil" - "log" "os" // Imports the Google Cloud Storage client package. @@ -18,10 +18,9 @@ var style aurora.Aurora // Auth checks for GCLOUD_CREDENTIALS in the environment // returns true if they exist and creates a json credentials file and sets the GOOGLE_APPLICATION_CREDENTIALS env var // returns false if credentials are not found -func Auth() bool { +func Auth() (string, error) { if os.Getenv("GOOGLE_APPLICATION_CREDENTIALS") != "" { - log.Println("INFO: GOOGLE_APPLICATION_CREDENTIALS is already set in the environment.") - return true + return "GOOGLE_APPLICATION_CREDENTIALS is already set in the environment", nil } if os.Getenv("GCLOUD_CREDENTIALS") != "" { @@ -31,26 +30,26 @@ func Auth() bool { err := ioutil.WriteFile(credFile, d, 0644) if err != nil { - log.Fatal(style.Bold(style.Red("ERROR: Cannot create credentials file: " + err.Error()))) + return fmt.Sprintf("Cannot create credentials file: %s", err), err } os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", credFile) - return true + return "ok", nil } - return false + return "can't authenticate", fmt.Errorf("can't authenticate") } // ReadFile reads a file from storage bucket and saves it in a desired location. -func ReadFile(bucketName string, filename string, outFile string, noColors bool) { +func ReadFile(bucketName string, filename string, outFile string, noColors bool) (string, error) { style = aurora.NewAurora(!noColors) - if !Auth() { - log.Fatal(style.Bold(style.Red("ERROR: Failed to find the GCLOUD_CREDENTIALS env var. Please make sure it is set in the environment."))) + if msg, err := Auth(); err != nil { + return msg, nil } ctx := context.Background() client, err := storage.NewClient(ctx) if err != nil { - log.Fatal(style.Bold(style.Red("ERROR: Failed to configure Storage bucket: " + err.Error()))) + return "Failed to configure Storage bucket: ", err } storageBucket := client.Bucket(bucketName) @@ -60,7 +59,7 @@ func ReadFile(bucketName string, filename string, outFile string, noColors bool) // Read the object. r, err := obj.NewReader(ctx) if err != nil { - log.Fatal(style.Bold(style.Red("ERROR: Failed to create object reader: " + err.Error()))) + return fmt.Sprintf("Failed to create object reader: %s", err), err } defer r.Close() @@ -68,14 +67,14 @@ func ReadFile(bucketName string, filename string, outFile string, noColors bool) var writers []io.Writer file, err := os.Create(outFile) if err != nil { - log.Fatal(style.Bold(style.Red("ERROR: Failed to create an output file: " + err.Error()))) + return fmt.Sprintf("Failed to create an output file: %s", err), err } writers = append(writers, file) defer file.Close() dest := io.MultiWriter(writers...) if _, err := io.Copy(dest, r); err != nil { - log.Fatal(style.Bold(style.Red("ERROR: Failed to read object content: " + err.Error()))) + return fmt.Sprintf("Failed to read object content: %s", err), err } - log.Println("INFO: Successfully downloaded " + filename + " from GCS as " + outFile) + return "Successfully downloaded " + filename + " from GCS as " + outFile, nil } diff --git a/helm_helpers.go b/helm_helpers.go index cacd851e..3ee936aa 100644 --- a/helm_helpers.go +++ b/helm_helpers.go @@ -2,18 +2,17 @@ package main import ( "encoding/json" - "errors" "fmt" "log" "net/url" + "os" "path/filepath" "regexp" "strconv" "strings" "time" - "github.com/Praqma/helmsman/gcs" - version "github.com/hashicorp/go-version" + "helmsman/gcs" ) var currentState map[string]releaseState @@ -24,184 +23,86 @@ type releaseState struct { Updated time.Time Status string Chart string - Name string Namespace string - TillerNamespace string } type releaseInfo struct { Name string `json:"Name"` - Revision int `json:"Revision"` + Namespace string `json:"Namespace"` + Revision string `json:"Revision"` Updated string `json:"Updated"` Status string `json:"Status"` Chart string `json:"Chart"` AppVersion string `json:"AppVersion,omitempty"` - Namespace string `json:"Namespace"` - TillerNamespace string `json:",omitempty"` } -type tillerReleases struct { - Next string `json:"Next"` - Releases []releaseInfo `json:"Releases"` +type chartVersion struct { + Name string `json:"name"` + Version string `json:"version"` + AppVersion string `json:"app_version"` + Description string `json:"description"` } // getHelmClientVersion returns Helm client Version -func getHelmClientVersion() string { +func getHelmVersion() string { cmd := command{ - Cmd: "helm", - Args: []string{"version", "--client", "--short"}, + Cmd: helmBin, + Args: []string{"version", "--short"}, Description: "checking Helm version ", } exitCode, result, _ := cmd.exec(debug, false) if exitCode != 0 { - logError("ERROR: while checking helm version: " + result) + logError("while checking helm version: " + result) } return result } -// getAllReleases fetches a list of all releases in a k8s cluster -func getAllReleases() tillerReleases { - - // result := make(map[string]interface{}) - var result tillerReleases - if _, ok := s.Namespaces["kube-system"]; !ok && !s.Settings.Tillerless { - result.Releases = append(result.Releases, getTillerReleases("kube-system").Releases...) - } - - for ns, v := range s.Namespaces { - if (v.InstallTiller || v.UseTiller) || s.Settings.Tillerless { - result.Releases = append(result.Releases, getTillerReleases(ns).Releases...) - } - } - - return result -} - -// getTillerReleases gets releases deployed with a given Tiller (in a given namespace) -func getTillerReleases(tillerNS string) tillerReleases { - v1, _ := version.NewVersion(helmVersion) - jsonConstraint, _ := version.NewConstraint(">=2.10.0-rc.1") - var outputFormat []string - if jsonConstraint.Check(v1) { - outputFormat = append(outputFormat, "--output", "json") - } - - output, err := helmList(tillerNS, outputFormat, "") - if err != nil { - logError(err.Error()) - } - - var allReleases tillerReleases - if output == "" { - return allReleases - } - - if jsonConstraint.Check(v1) { - allReleases, err = parseJSONListAndFollow(output, tillerNS) - if err != nil { - logError(err.Error()) - } - } else { - allReleases = parseTextList(output) - } - - // appending tiller-namespace to each release found - for i := 0; i < len(allReleases.Releases); i++ { - allReleases.Releases[i].TillerNamespace = tillerNS - } - - return allReleases -} - -func parseJSONListAndFollow(input, tillerNS string) (tillerReleases, error) { - var allReleases tillerReleases - var releases tillerReleases - - for { - output, err := helmList(tillerNS, []string{"--output", "json"}, releases.Next) - if err != nil { - return allReleases, err - } - if err := json.Unmarshal([]byte(output), &releases); err != nil { - return allReleases, fmt.Errorf("ERROR: failed to unmarshal Helm CLI output: %s", err) - } - for _, releaseInfo := range releases.Releases { - allReleases.Releases = append(allReleases.Releases, releaseInfo) - } - if releases.Next == "" { - break - } - } - - return allReleases, nil -} - -func parseTextList(input string) tillerReleases { - var out tillerReleases - lines := strings.Split(input, "\n") - for i, l := range lines { - if l == "" || (strings.HasPrefix(strings.TrimSpace(l), "NAME") && strings.HasSuffix(strings.TrimSpace(l), "NAMESPACE")) { - continue - } else { - r, _ := strconv.Atoi(strings.Fields(lines[i])[1]) - t := strings.Fields(lines[i])[2] + " " + strings.Fields(lines[i])[3] + " " + strings.Fields(lines[i])[4] + " " + - strings.Fields(lines[i])[5] + " " + strings.Fields(lines[i])[6] - out.Releases = append(out.Releases, releaseInfo{Name: strings.Fields(lines[i])[0], Revision: r, Updated: t, Status: strings.Fields(lines[i])[7], Chart: strings.Fields(lines[i])[8], Namespace: strings.Fields(lines[i])[9], AppVersion: "", TillerNamespace: ""}) - } - } - return out -} - -func helmList(tillerNS string, outputFormat []string, offset string) (string, error) { - args := []string{"list", "--all", "--max", "0"} - if offset != "" { - args = append(args, "--offset", offset) - } +// getHelmReleases fetches a list of all releases in a k8s cluster +func getHelmReleases() []releaseInfo { + var allReleases []releaseInfo cmd := command{ - Cmd: "helm", - Args: concat(helmCommand(tillerNS), args, outputFormat, []string{"--tiller-namespace", tillerNS}, getNSTLSFlags(tillerNS)), - Description: "listing all existing releases in namespace [ " + tillerNS + " ]...", + Cmd: helmBin, + Args: []string{"list", "--all", "--max", "0", "--output", "json", "--all-namespaces"}, + Description: "listing all existing releases...", } - exitCode, result, _ := cmd.exec(debug, verbose) if exitCode != 0 { - if !apply { - if strings.Contains(result, "incompatible versions") { - return "", errors.New(result) - } - log.Println("INFO: " + strings.Replace(result, "Error: ", "", 1)) - return "", nil - } - - return "", fmt.Errorf("ERROR: failed to list all releases in namespace [ %s ]: %s", tillerNS, result) + logError("failed to list all releases: " + result) } - - return result, nil + if err := json.Unmarshal([]byte(result), &allReleases); err != nil { + logError(fmt.Sprintf("failed to unmarshal Helm CLI output: %s", err)) + } + return allReleases } // buildState builds the currentState map containing information about all releases existing in a k8s cluster func buildState() { - log.Println("INFO: mapping the current helm state ...") + logs.Info("Acquiring current Helm state from cluster...") currentState = make(map[string]releaseState) - rel := getAllReleases() - - for i := 0; i < len(rel.Releases); i++ { - // skipping the header from helm output - time, err := time.Parse("Mon Jan _2 15:04:05 2006", rel.Releases[i].Updated) + rel := getHelmReleases() + + for i := 0; i < len(rel); i++ { + // we need to split the time into parts and make sure milliseconds len = 6, it happens to skip trailing zeros + updatedFields := strings.Fields(rel[i].Updated) + updatedHour := strings.Split(updatedFields[1], ".") + milliseconds := updatedHour[1] + for i := len(milliseconds); i < 9; i++ { + milliseconds = fmt.Sprintf("%s0", milliseconds) + } + date, err := time.Parse("2006-01-02 15:04:05.000000000 -0700 MST", + fmt.Sprintf("%s %s.%s %s %s", updatedFields[0], updatedHour[0], milliseconds, updatedFields[2], updatedFields[3])) if err != nil { - logError("ERROR: while converting release time: " + err.Error()) + logError("while converting release time: " + err.Error()) } - - currentState[rel.Releases[i].Name+"-"+rel.Releases[i].TillerNamespace] = releaseState{ - Revision: rel.Releases[i].Revision, - Updated: time, - Status: rel.Releases[i].Status, - Chart: rel.Releases[i].Chart, - Name: rel.Releases[i].Name, - Namespace: rel.Releases[i].Namespace, - TillerNamespace: rel.Releases[i].TillerNamespace, + revision, _ := strconv.Atoi(rel[i].Revision) + currentState[fmt.Sprintf("%s-%s", rel[i].Name, rel[i].Namespace)] = releaseState{ + Revision: revision, + Updated: date, + Status: rel[i].Status, + Chart: rel[i].Chart, + Namespace: rel[i].Namespace, } } } @@ -210,21 +111,19 @@ func buildState() { // It searches the Current State for releases. // The key format for releases uniqueness is: // If status is provided as an input [deployed, deleted, failed], then the search will verify the release status matches the search status. -func helmReleaseExists(r *release, status string) (bool, releaseState) { - compositeReleaseName := r.Name + "-" + getDesiredTillerNamespace(r) - - v, ok := currentState[compositeReleaseName] +func isReleaseExisting(r *release, status string) bool { + v, ok := currentState[fmt.Sprintf("%s-%s", r.Name, r.Namespace)] if !ok { - return false, v + return false } if status != "" { - if v.Status == strings.ToUpper(status) { - return true, v + if v.Status == status { + return true } - return false, v + return false } - return true, v + return true } // getReleaseRevision returns the revision number for a release @@ -255,16 +154,6 @@ func getReleaseChartVersion(rs releaseState) string { return "" } -// getNSTLSFlags returns TLS flags for a given namespace if it's deployed with TLS -func getNSTLSFlags(namespace string) []string { - tls := []string{} - ns := s.Namespaces[namespace] - if tillerTLSEnabled(ns) { - tls = append(tls, "--tls", "--tls-ca-cert", namespace+"-ca.cert", "--tls-cert", namespace+"-client.cert", "--tls-key", namespace+"-client.key") - } - return tls -} - // validateReleaseCharts validates if the charts defined in a release are valid. // Valid charts are the ones that can be found in the defined repos. // This function uses Helm search to verify if the chart can be found or not. @@ -279,7 +168,7 @@ func validateReleaseCharts(apps map[string]*release) (bool, string) { if validateCurrentChart { if isLocalChart(r.Chart) { cmd := command{ - Cmd: "helm", + Cmd: helmBin, Args: []string{"inspect", "chart", r.Chart}, Description: "validating if chart at " + r.Chart + " is available.", } @@ -288,13 +177,13 @@ func validateReleaseCharts(apps map[string]*release) (bool, string) { var exitCode int if exitCode, output, _ = cmd.exec(debug, verbose); exitCode != 0 { maybeRepo := filepath.Base(filepath.Dir(r.Chart)) - return false, "ERROR: chart at " + r.Chart + " for app [" + app + "] could not be found. Did you mean to add a repo named '" + maybeRepo + "'?" + return false, "chart at " + r.Chart + " for app [" + app + "] could not be found. Did you mean to add a repo named '" + maybeRepo + "'?" } matches := versionExtractor.FindStringSubmatch(output) if len(matches) == 2 { version := matches[1] if r.Version != version { - return false, "ERROR: chart " + r.Chart + " with version " + r.Version + " is specified for " + + return false, "chart " + r.Chart + " with version " + r.Version + " is specified for " + "app [" + app + "] but the chart found at that path has version " + version + " which does not match." } } @@ -305,18 +194,17 @@ func validateReleaseCharts(apps map[string]*release) (bool, string) { version = "*" } cmd := command{ - Cmd: "helm", - Args: []string{"search", r.Chart, "--version", version, "-l"}, + Cmd: helmBin, + Args: []string{"search", "repo", r.Chart, "--version", version, "-l"}, Description: "validating if chart " + r.Chart + " with version " + r.Version + " is available in the defined repos.", } if exitCode, result, _ := cmd.exec(debug, verbose); exitCode != 0 || strings.Contains(result, "No results found") { - return false, "ERROR: chart " + r.Chart + " with version " + r.Version + " is specified for " + + return false, "chart " + r.Chart + " with version " + r.Version + " is specified for " + "app [" + app + "] but is not found in the defined repos." } } } - } return true, "" } @@ -328,8 +216,8 @@ func getChartVersion(r *release) (string, string) { return r.Version, "" } cmd := command{ - Cmd: "helm", - Args: []string{"search", r.Chart, "--version", r.Version}, + Cmd: helmBin, + Args: []string{"search", "repo", r.Chart, "--version", r.Version, "-o", "json"}, Description: "getting latest chart version " + r.Chart + "-" + r.Version + "", } @@ -338,56 +226,24 @@ func getChartVersion(r *release) (string, string) { result string ) - if exitCode, result, _ = cmd.exec(debug, verbose); exitCode != 0 || strings.Contains(result, "No results found") { - return "", "ERROR: chart " + r.Chart + " with version " + r.Version + " is specified but not found in the helm repos." - } - versions := strings.Split(result, "\n") - if len(versions) < 2 { - return "", "ERROR: chart " + r.Chart + " with version " + r.Version + " is specified but not found in the helm repos (unrecognized helm output?)." - } - for i, l := range versions { - if l == "" || (strings.HasPrefix(strings.TrimSpace(l), "WARNING") || strings.HasSuffix(strings.TrimSpace(l), "DESCRIPTION")) { - continue - } else { - fields := strings.Split(versions[i], "\t") - if len(fields) != 4 { - return "", "ERROR: chart " + r.Chart + " with version " + r.Version + " is specified but not found in the helm repos (unrecognized helm output?)." - } - return strings.TrimSpace(fields[1]), "" - } + if exitCode, result, _ = cmd.exec(debug, verbose); exitCode != 0 { + return "", "chart " + r.Chart + " with version " + r.Version + " is specified but not found in the helm repos." } - return "", "ERROR: chart " + r.Chart + " with version " + r.Version + " is specified but not found in the helm repos." -} - -// waitForTiller keeps checking if the helm Tiller is ready or not by executing helm list and checking its error (if any) -// waits for 5 seconds before each new attempt and eventually terminates after 10 failed attempts. -func waitForTiller(namespace string) { - attempt := 0 - - cmd := command{ - Cmd: "helm", - Args: concat([]string{"list", "--tiller-namespace", namespace}, getNSTLSFlags(namespace)), - Description: "checking if helm Tiller is ready in namespace [ " + namespace + " ].", + chartVersions := make([]chartVersion, 0) + if err := json.Unmarshal([]byte(result), &chartVersions); err != nil { + logs.Fatal(fmt.Sprint(err)) } - exitCode, err, _ := cmd.exec(debug, verbose) - - for attempt < 10 { - if exitCode == 0 { - return - } else if strings.Contains(err, "could not find a ready tiller pod") || strings.Contains(err, "could not find tiller") { - log.Println("INFO: waiting for helm Tiller to be ready in namespace [" + namespace + "] ...") - time.Sleep(5 * time.Second) - exitCode, err, _ = cmd.exec(debug, verbose) - } else { - logError("ERROR: while waiting for helm Tiller to be ready in namespace [ " + namespace + " ] : " + err) - } - attempt = attempt + 1 + if len(chartVersions) < 1 { + return "", "chart " + r.Chart + " with version " + r.Version + " is specified but not found in the helm repos." + } else if len(chartVersions) > 1 { + return "", "multiple versions of chart " + r.Chart + " with version " + r.Version + " found in the helm repos." } - logError("ERROR: timeout reached while waiting for helm Tiller to be ready in namespace [ " + namespace + " ]. Aborting!") + return chartVersions[0].Version, "" } + // addHelmRepos adds repositories to Helm if they don't exist already. // Helm does not mind if a repo with the same name exists. It treats it as an update. func addHelmRepos(repos map[string]string) (bool, string) { @@ -397,193 +253,50 @@ func addHelmRepos(repos map[string]string) (bool, string) { // check if repo is in GCS, then perform GCS auth -- needed for private GCS helm repos // failed auth would not throw an error here, as it is possible that the repo is public and does not need authentication if strings.HasPrefix(repoLink, "gs://") { - gcs.Auth() + msg, err := gcs.Auth() + if err != nil { + log.Fatal(msg) + } } u, err := url.Parse(repoLink) if err != nil { - logError("ERROR: failed to add helm repo: " + err.Error()) + logError("failed to add helm repo: " + err.Error()) } if u.User != nil { p, ok := u.User.Password() if !ok { - logError("ERROR: helm repo " + repoName + " has incomplete basic auth info. Missing the password!") + logError("helm repo " + repoName + " has incomplete basic auth info. Missing the password!") } basicAuthArgs = append(basicAuthArgs, "--username", u.User.Username(), "--password", p) } cmd := command{ - Cmd: "helm", + Cmd: helmBin, Args: concat([]string{"repo", "add", repoName, repoLink}, basicAuthArgs), Description: "adding repo " + repoName, } if exitCode, err, _ := cmd.exec(debug, verbose); exitCode != 0 { - return false, "ERROR: while adding repo [" + repoName + "]: " + err + return false, "while adding repo [" + repoName + "]: " + err } } cmd := command{ - Cmd: "helm", + Cmd: helmBin, Args: []string{"repo", "update"}, Description: "updating helm repos", } if exitCode, err, _ := cmd.exec(debug, verbose); exitCode != 0 { - return false, "ERROR: while updating helm repos : " + err + return false, "while updating helm repos : " + err } return true, "" } -// deployTiller deploys Helm's Tiller in a specific namespace with a serviceAccount -// If serviceAccount is not provided (empty string), the defaultServiceAccount is used. -// If no defaultServiceAccount is provided, A service account is created and Tiller is deployed with the new service account -// If no namespace is provided, Tiller is deployed to kube-system -func deployTiller(namespace string, serviceAccount string, defaultServiceAccount string, role string, roleTemplateFile string, tillerMaxHistory int) (bool, string) { - log.Println("INFO: deploying Tiller in namespace [ " + namespace + " ].") - sa := []string{} - if serviceAccount != "" { - if ok, err := validateServiceAccount(serviceAccount, namespace); !ok { - if strings.Contains(err, "NotFound") || strings.Contains(err, "not found") { - - log.Println("INFO: service account [ " + serviceAccount + " ] does not exist in namespace [ " + namespace + " ] .. attempting to create it ... ") - if _, rbacErr := createRBAC(serviceAccount, namespace, role, roleTemplateFile); rbacErr != "" { - return false, rbacErr - } - } else { - return false, "ERROR: while validating/creating service account [ " + serviceAccount + " ] in namespace [" + namespace + "]: " + err - } - } - sa = []string{"--service-account", serviceAccount} - } else { - roleName := "helmsman-tiller" - defaultServiceAccountName := "helmsman" - - if defaultServiceAccount != "" { - defaultServiceAccountName = defaultServiceAccount - } - if role != "" { - roleName = role - } - - if ok, err := validateServiceAccount(defaultServiceAccountName, namespace); !ok { - if strings.Contains(err, "NotFound") || strings.Contains(err, "not found") { - - log.Println("INFO: service account [ " + defaultServiceAccountName + " ] does not exist in namespace [ " + namespace + " ] .. attempting to create it ... ") - if _, rbacErr := createRBAC(defaultServiceAccountName, namespace, roleName, roleTemplateFile); rbacErr != "" { - return false, rbacErr - } - } else { - return false, "ERROR: while validating/creating service account [ " + defaultServiceAccountName + " ] in namespace [" + namespace + "]: " + err - } - } - sa = []string{"--service-account", defaultServiceAccountName} - } - if namespace == "" { - namespace = "kube-system" - } - tillerNameSpace := []string{"--tiller-namespace", namespace} - - maxHistory := []string{} - if tillerMaxHistory > 0 { - maxHistory = []string{"--history-max", strconv.Itoa(tillerMaxHistory)} - } - tls := []string{} - ns := s.Namespaces[namespace] - if tillerTLSEnabled(ns) { - tillerCert := namespace + "-tiller.cert" - tillerKey := namespace + "-tiller.key" - caCert := namespace + "-ca.cert" - - tls = []string{"--tiller-tls", "--tiller-tls-cert", tillerCert, "--tiller-tls-key", tillerKey, "--tiller-tls-verify", "--tls-ca-cert", caCert} - } - - storageBackend := []string{} - if s.Settings.StorageBackend == "secret" { - storageBackend = []string{"--override", "'spec.template.spec.containers[0].command'='{/tiller,--storage=secret}'"} - } - cmd := command{ - Cmd: "helm", - Args: concat([]string{"init", "--force-upgrade"}, maxHistory, sa, tillerNameSpace, tls, storageBackend), - Description: "initializing helm on the current context and upgrading Tiller on namespace [ " + namespace + " ].", - } - - if exitCode, err, _ := cmd.exec(debug, verbose); exitCode != 0 { - return false, "ERROR: while deploying Helm Tiller in namespace [" + namespace + "]: " + err - } - return true, "" -} - -// initHelmClientOnly initializes the helm client only (without deploying Tiller) -func initHelmClientOnly() (bool, string) { - cmd := command{ - Cmd: "helm", - Args: []string{"init", "--client-only"}, - Description: "initializing helm on the client only.", - } - - if exitCode, err, _ := cmd.exec(debug, verbose); exitCode != 0 { - return false, "ERROR: initializing helm on the client : " + err - } - - return true, "" -} - -// initHelmTiller initializes the helm tiller plugin -func initHelmTiller() (bool, string) { - cmd := command{ - Cmd: "helm", - Args: []string{"tiller", "install"}, - Description: "initializing helm tiller plugin.", - } - - if exitCode, err, _ := cmd.exec(debug, verbose); exitCode != 0 { - return false, "ERROR: initializing helm tiller plugin : " + err - } - - return true, "" -} - -// initHelm initializes helm on a k8s cluster and deploys Tiller in one or more namespaces -func initHelm() (bool, string) { - defaultSA := s.Settings.ServiceAccount - if !s.Settings.Tillerless { - for k, ns := range s.Namespaces { - if tillerTLSEnabled(ns) { - downloadFile(s.Namespaces[k].TillerCert, k+"-tiller.cert") - downloadFile(s.Namespaces[k].TillerKey, k+"-tiller.key") - downloadFile(s.Namespaces[k].CaCert, k+"-ca.cert") - // client cert and key - downloadFile(s.Namespaces[k].ClientCert, k+"-client.cert") - downloadFile(s.Namespaces[k].ClientKey, k+"-client.key") - } - if ns.InstallTiller && k != "kube-system" { - if ok, err := deployTiller(k, ns.TillerServiceAccount, defaultSA, ns.TillerRole, ns.TillerRoleTemplateFile, ns.TillerMaxHistory); !ok { - return false, err - } - } - } - - if ns, ok := s.Namespaces["kube-system"]; ok { - if ns.InstallTiller { - if ok, err := deployTiller("kube-system", ns.TillerServiceAccount, defaultSA, ns.TillerRole, ns.TillerRoleTemplateFile, ns.TillerMaxHistory); !ok { - return false, err - } - } - } else { - if ok, err := deployTiller("kube-system", "", defaultSA, ns.TillerRole, ns.TillerRoleTemplateFile, ns.TillerMaxHistory); !ok { - return false, err - } - } - } else { - log.Println("INFO: skipping Tiller deployments because Tillerless mode is enabled.") - } - - return true, "" -} // cleanUntrackedReleases checks for any releases that are managed by Helmsman and are no longer tracked by the desired state // It compares the currently deployed releases with "MANAGED-BY=HELMSMAN" labels with Apps defined in the desired state @@ -592,38 +305,34 @@ func initHelm() (bool, string) { // NOTE: Removing/Commenting out an app from the desired state makes it untracked. func cleanUntrackedReleases() { toDelete := make(map[string]map[*release]bool) - log.Println("INFO: checking if any Helmsman managed releases are no longer tracked by your desired state ...") - - // List all releases managed my Helmsman - for n, hr := range getHelmsmanReleases() { - for name, tracked := range hr { + logs.Info("Checking if any Helmsman managed releases are no longer tracked by your desired state ...") + for ns, releases := range getHelmsmanReleases() { + for r := range releases { + tracked := false + for _, app := range s.Apps { + if app.Name == r.Name && app.Namespace == r.Namespace { + tracked = true + } + } if !tracked { - rs := currentState[name+"-"+n] - ns := rs.TillerNamespace if _, ok := toDelete[ns]; !ok { toDelete[ns] = make(map[*release]bool) } - r := &release{ - Chart: rs.Chart, - Name: rs.Name, - Namespace: rs.Namespace, - TillerNamespace: rs.TillerNamespace, - } toDelete[ns][r] = true } } } if len(toDelete) == 0 { - log.Println("INFO: no untracked releases found.") + logs.Info("No untracked releases found.") } else { - for ns, releases := range toDelete { + for _, releases := range toDelete { for r := range releases { - if !r.isReleaseConsideredToRun() { - logDecision(generateDecisionMessage(r, "untracked release [ "+r.Name+" ] is ignored by target flag. Skipping.", false), -800, ignored) + if r.isReleaseConsideredToRun() { + logDecision("Untracked release [ "+r.Name+" ] is ignored by target flag. Skipping.", -800, ignored) } else { - logDecision(generateDecisionMessage(r, "untracked release found: release [ "+r.Name+" ]. It will be deleted", true), -800, delete) - deleteUntrackedRelease(r, ns) + logDecision("Untracked release found: release [ "+r.Name+" ]. It will be deleted", -800, delete) + deleteUntrackedRelease(r) } } } @@ -631,18 +340,11 @@ func cleanUntrackedReleases() { } // deleteUntrackedRelease creates the helm command to purge delete an untracked release -func deleteUntrackedRelease(release *release, tillerNamespace string) { - - tls := []string{} - ns := s.Namespaces[tillerNamespace] - if tillerTLSEnabled(ns) { - - tls = []string{"--tls", "--tls-ca-cert", tillerNamespace + "-ca.cert", "--tls-cert", tillerNamespace + "-client.cert", "--tls-key", tillerNamespace + "-client.key"} - } +func deleteUntrackedRelease(release *release) { cmd := command{ - Cmd: "helm", - Args: concat(helmCommand(tillerNamespace), []string{"delete", "--purge", release.Name, "--tiller-namespace", tillerNamespace}, tls, getDryRunFlags()), - Description: generateCmdDescription(release, "deleting untracked"), + Cmd: helmBin, + Args: concat([]string{"delete", release.Name, "--namespace", release.Namespace}, getDryRunFlags()), + Description: "deleting not tracked release [ "+release.Name+" ] in namespace [[ "+release.Namespace+" ]]", } outcome.addCommand(cmd, -800, nil) @@ -650,8 +352,8 @@ func deleteUntrackedRelease(release *release, tillerNamespace string) { // decrypt a helm secret file func decryptSecret(name string) bool { - cmd := "helm" - args := concat(helmCommand(""), []string{"secrets", "dec", name}) + cmd := helmBin + args := []string{"secrets", "dec", name} if settings.EyamlEnabled { cmd = "eyaml" @@ -668,12 +370,19 @@ func decryptSecret(name string) bool { } exitCode, output, stderr := command.exec(debug, false) + if !settings.EyamlEnabled { + _, fileNotFound := os.Stat(name + ".dec") + if fileNotFound != nil && !isOfType(name, []string{".dec"}) { + logs.Error(output) + return false + } + } if exitCode != 0 { - log.Println("ERROR: " + output) + logs.Error(output) return false } else if stderr != "" { - log.Println("ERROR: " + stderr) + logs.Error(stderr) return false } @@ -686,7 +395,7 @@ func decryptSecret(name string) bool { } err := writeStringToFile(outfile, output) if err != nil { - logError("ERROR: could not write [ " + outfile + " ] file") + logError("could not write [ " + outfile + " ] file") } } return true @@ -695,9 +404,9 @@ func decryptSecret(name string) bool { // updateChartDep updates dependencies for a local chart func updateChartDep(chartPath string) (bool, string) { cmd := command{ - Cmd: "helm", + Cmd: helmBin, Args: []string{"dependency", "update", chartPath}, - Description: "Updateing dependency for local chart " + chartPath, + Description: "Updating dependency for local chart " + chartPath, } exitCode, err, _ := cmd.exec(debug, verbose) diff --git a/helm_helpers_test.go b/helm_helpers_test.go index 17dd8178..0f313c61 100644 --- a/helm_helpers_test.go +++ b/helm_helpers_test.go @@ -12,7 +12,7 @@ func setupTestCase(t *testing.T) func(t *testing.T) { os.MkdirAll(os.TempDir()+"/helmsman-tests/myapp", os.ModePerm) os.MkdirAll(os.TempDir()+"/helmsman-tests/dir-with space/myapp", os.ModePerm) cmd := command{ - Cmd: "helm", + Cmd: helmBin, Args: []string{"create", os.TempDir() + "/helmsman-tests/dir-with space/myapp"}, Description: "creating an empty local chart directory", } @@ -57,7 +57,6 @@ func Test_validateReleaseCharts(t *testing.T) { Protected: false, Wait: false, Priority: 0, - TillerNamespace: "", Set: make(map[string]string), SetString: make(map[string]string), HelmFlags: []string{}, @@ -88,7 +87,6 @@ func Test_validateReleaseCharts(t *testing.T) { Protected: false, Wait: false, Priority: 0, - TillerNamespace: "", Set: make(map[string]string), SetString: make(map[string]string), HelmFlags: []string{}, @@ -119,7 +117,6 @@ func Test_validateReleaseCharts(t *testing.T) { Protected: false, Wait: false, Priority: 0, - TillerNamespace: "", Set: make(map[string]string), SetString: make(map[string]string), HelmFlags: []string{}, @@ -140,7 +137,7 @@ func Test_validateReleaseCharts(t *testing.T) { Namespace: "", Enabled: true, Chart: "stable/prometheus", - Version: "", + Version: "9.5.2", ValuesFile: "", ValuesFiles: []string{}, SecretsFile: "", @@ -150,7 +147,6 @@ func Test_validateReleaseCharts(t *testing.T) { Protected: false, Wait: false, Priority: 0, - TillerNamespace: "", Set: make(map[string]string), SetString: make(map[string]string), HelmFlags: []string{}, @@ -181,7 +177,6 @@ func Test_validateReleaseCharts(t *testing.T) { Protected: false, Wait: false, Priority: 0, - TillerNamespace: "", Set: make(map[string]string), SetString: make(map[string]string), HelmFlags: []string{}, @@ -212,7 +207,6 @@ func Test_validateReleaseCharts(t *testing.T) { Protected: false, Wait: false, Priority: 0, - TillerNamespace: "", Set: make(map[string]string), SetString: make(map[string]string), HelmFlags: []string{}, @@ -264,7 +258,6 @@ func Test_getReleaseChartVersion(t *testing.T) { Status: "", Chart: "elasticsearch-1.3.0-1", Namespace: "", - TillerNamespace: "", }, }, want: "1.3.0-1", @@ -277,7 +270,6 @@ func Test_getReleaseChartVersion(t *testing.T) { Status: "", Chart: "elasticsearch-1.3.0", Namespace: "", - TillerNamespace: "", }, }, want: "1.3.0", @@ -290,7 +282,6 @@ func Test_getReleaseChartVersion(t *testing.T) { Status: "", Chart: "elastic-search-1.3.0", Namespace: "", - TillerNamespace: "", }, }, want: "1.3.0", @@ -303,7 +294,6 @@ func Test_getReleaseChartVersion(t *testing.T) { Status: "", Chart: "elastic-search-1.3.0+meta.info", Namespace: "", - TillerNamespace: "", }, }, want: "1.3.0+meta.info", @@ -316,7 +306,6 @@ func Test_getReleaseChartVersion(t *testing.T) { Status: "", Chart: "foo", Namespace: "", - TillerNamespace: "", }, }, want: "", @@ -329,7 +318,6 @@ func Test_getReleaseChartVersion(t *testing.T) { Status: "", Chart: "cert-manager-v0.5.2", Namespace: "", - TillerNamespace: "", }, }, want: "v0.5.2", diff --git a/init.go b/init.go index 6149e622..6261145b 100644 --- a/init.go +++ b/init.go @@ -3,17 +3,15 @@ package main import ( "flag" "fmt" - "log" + "github.com/apsdehal/go-logger" "os" "strings" "github.com/imdario/mergo" "github.com/joho/godotenv" - "github.com/logrusorgru/aurora" ) -// colorizer -var style aurora.Aurora +var logs *logger.Logger const ( banner = " _ _ \n" + @@ -26,11 +24,11 @@ const ( ) func printUsage() { - log.Println(banner + "\n") - log.Println("Helmsman version: " + appVersion) - log.Println("Helmsman is a Helm Charts as Code tool which allows you to automate the deployment/management of your Helm charts.") - log.Println() - log.Println("Usage: helmsman [options]") + logs.Info(banner + "\n") + logs.Info("Helmsman version: " + appVersion) + logs.Info("Helmsman is a Helm Charts as Code tool which allows you to automate the deployment/management of your Helm charts.") + logs.Info("") + logs.Info("Usage: helmsman [options]") flag.PrintDefaults() } @@ -61,46 +59,54 @@ func init() { flag.BoolVar(&suppressDiffSecrets, "suppress-diff-secrets", false, "don't show secrets in helm diff output.") flag.IntVar(&diffContext, "diff-context", -1, "number of lines of context to show around changes in helm diff output") flag.BoolVar(&noEnvSubst, "no-env-subst", false, "turn off environment substitution globally") - flag.BoolVar(&noEnvValuesSubst, "no-env-values-subst", false, "turn off environment substitution in values files only") + flag.BoolVar(&noEnvValuesSubst, "no-env-values-subst", true, "turn off environment substitution in values files only") flag.BoolVar(&noSSMSubst, "no-ssm-subst", false, "turn off SSM parameter substitution globally") - flag.BoolVar(&noSSMValuesSubst, "no-ssm-values-subst", false, "turn off SSM parameter substitution in values files only") + flag.BoolVar(&noSSMValuesSubst, "no-ssm-values-subst", true, "turn off SSM parameter substitution in values files only") flag.BoolVar(&updateDeps, "update-deps", false, "run 'helm dep up' for local chart") flag.BoolVar(&forceUpgrades, "force-upgrades", false, "use --force when upgrading helm releases. May cause resources to be recreated.") flag.BoolVar(&noDefaultRepos, "no-default-repos", false, "don't set default Helm repos from Google for 'stable' and 'incubator'") - log.SetOutput(os.Stdout) - flag.Usage = printUsage flag.Parse() + logger.SetDefaultFormat("%{time:2006-01-02 15:04:05} %{level}: %{message}") + var logLevel = logger.InfoLevel + if verbose { + logLevel = logger.DebugLevel + } + if noFancy { noColors = true noBanner = true } - style = aurora.NewAurora(!noColors) + var logColors = 1 + if noColors { + logColors = 0 + } + logs, _ = logger.New("logger", logColors, os.Stdout, logLevel) if !noBanner { fmt.Println(banner + " version: " + appVersion + "\n" + slogan) } if dryRun && apply { - logError("ERROR: --apply and --dry-run can't be used together.") + logError("--apply and --dry-run can't be used together.") } if destroy && apply { - logError("ERROR: --destroy and --apply can't be used together.") + logError("--destroy and --apply can't be used together.") } if len(target) > 0 && len(group) > 0 { - logError("ERROR: --target and --group can't be used together.") + logError("--target and --group can't be used together.") } if (settings.EyamlPrivateKeyPath != "" && settings.EyamlPublicKeyPath == "") || (settings.EyamlPrivateKeyPath == "" && settings.EyamlPublicKeyPath != "") { - logError("ERROR: both EyamlPrivateKeyPath and EyamlPublicKeyPath are required") + logError("both EyamlPrivateKeyPath and EyamlPublicKeyPath are required") } - helmVersion = strings.TrimSpace(strings.SplitN(getHelmClientVersion(), ": ", 2)[1]) + helmVersion = strings.TrimSpace(getHelmVersion()) kubectlVersion = strings.TrimSpace(strings.SplitN(getKubectlClientVersion(), ": ", 2)[1]) if verbose { @@ -113,7 +119,7 @@ func init() { } if len(files) == 0 { - log.Println("INFO: No desired state files provided.") + logs.Info("No desired state files provided.") os.Exit(0) } @@ -122,15 +128,15 @@ func init() { } if !toolExists("kubectl") { - logError("ERROR: kubectl is not installed/configured correctly. Aborting!") + logError("kubectl is not installed/configured correctly. Aborting!") } - if !toolExists("helm") { - logError("ERROR: helm is not installed/configured correctly. Aborting!") + if !toolExists(helmBin) { + logError("" + helmBin + " is not installed/configured correctly. Aborting!") } if !helmPluginExists("diff") { - logError("ERROR: helm diff plugin is not installed/configured correctly. Aborting!") + logError("helm diff plugin is not installed/configured correctly. Aborting!") } // read the env file @@ -159,7 +165,7 @@ func init() { for _, f := range files { result, msg := fromFile(f, &fileState) if result { - log.Printf(msg) + logs.Info(msg) } else { logError(msg) } @@ -188,20 +194,15 @@ func init() { s.print() } - // validate that if we are running in tillerless mode, and we don't have the tiller plugin installed we should fail. - if !helmPluginExists("tiller") && s.Settings.Tillerless { - logError("ERROR: tillerless operation is specified and helm tiller plugin is not installed/configured correctly. Aborting!") - } - if !skipValidation { // validate the desired state content if len(files) > 0 { - if result, msg := s.validate(); !result { // syntax validation - logError(msg) + if err := s.validate(); err != nil { // syntax validation + logs.Error(err.Error()) } } } else { - log.Println("INFO: desired state validation is skipped.") + logs.Info("Desired state validation is skipped.") } if applyLabels { @@ -248,7 +249,7 @@ func toolExists(tool string) bool { // It takes as input the plugin's name to check if it is recognizable or not. e.g. diff func helmPluginExists(plugin string) bool { cmd := command{ - Cmd: "helm", + Cmd: helmBin, Args: []string{"plugin", "list"}, Description: "validating that " + plugin + " is installed.", } diff --git a/init_test.go b/init_test.go index 6b104c21..48b2bf03 100644 --- a/init_test.go +++ b/init_test.go @@ -19,7 +19,7 @@ func Test_toolExists(t *testing.T) { { name: "test case 1 -- checking helm exists.", args: args{ - tool: "helm", + tool: helmBin, }, want: true, }, { diff --git a/kube_helpers.go b/kube_helpers.go index d78d9a84..e0ff7ad1 100644 --- a/kube_helpers.go +++ b/kube_helpers.go @@ -2,65 +2,11 @@ package main import ( "io/ioutil" - "log" - "regexp" "strings" "gopkg.in/yaml.v2" ) -// validateServiceAccount checks if k8s service account exists in a given namespace -// if the provided namespace is empty, it checks in the "default" namespace -func validateServiceAccount(sa string, namespace string) (bool, string) { - if namespace == "" { - namespace = "default" - } - ns := []string{"-n", namespace} - - cmd := command{ - Cmd: "kubectl", - Args: append([]string{"get", "serviceaccount", sa}, ns...), - Description: "validating if serviceaccount [ " + sa + " ] exists in namespace [ " + namespace + " ].", - } - - if exitCode, err, _ := cmd.exec(debug, verbose); exitCode != 0 { - return false, err - } - return true, "" -} - -// createRBAC creates a k8s service account and bind it to a (Cluster)Role -// role can be "cluster-admin" or any other custom name -// It binds it to a new role called "helmsman-tiller" -func createRBAC(sa string, namespace string, role string, roleTemplateFile string) (bool, string) { - var ok bool - var err string - if role == "" { - if namespace == "kube-system" { - role = "cluster-admin" - } else { - role = "helmsman-tiller" - } - } - if ok, err = createServiceAccount(sa, namespace); ok { - if role == "cluster-admin" || (role == "" && namespace == "kube-system") { - if ok, err = createRoleBinding(role, sa, namespace); ok { - return true, "" - } - return false, err - } - if ok, err = createRole(namespace, role, roleTemplateFile); ok { - if ok, err = createRoleBinding(role, sa, namespace); ok { - return true, "" - } - return false, err - } - - return false, err - } - return false, err -} - // addNamespaces creates a set of namespaces in your k8s cluster. // If a namespace with the same name exists, it will skip it. // If --ns-override flag is used, it only creates the provided namespace in that flag @@ -80,7 +26,7 @@ func addNamespaces(namespaces map[string]namespace) { // overrideAppsNamespace replaces all apps namespaces with one specific namespace func overrideAppsNamespace(newNs string) { - log.Println("INFO: overriding apps namespaces with [ " + newNs + " ] ...") + logs.Info("Overriding apps namespaces with [ " + newNs + " ] ...") for _, r := range s.Apps { overrideNamespace(r, newNs) } @@ -95,8 +41,7 @@ func createNamespace(ns string) { } exitCode, _, _ := cmd.exec(debug, verbose) if exitCode != 0 && verbose { - log.Println("WARN: I could not create namespace [ " + - ns + " ]. It already exists. I am skipping this.") + logs.Debug("Namespace [ " + ns + " ] is created.") } } @@ -111,8 +56,8 @@ func labelNamespace(ns string, labels map[string]string) { exitCode, _, _ := cmd.exec(debug, verbose) if exitCode != 0 && verbose { - log.Println("WARN: I could not label namespace [ " + ns + " with " + k + "=" + v + - " ]. It already exists. I am skipping this.") + logs.Warning("Can't label namespace [ " + ns + " with " + k + "=" + v + + " ]. It already exists.") } } } @@ -135,8 +80,8 @@ func annotateNamespace(ns string, labels map[string]string) { exitCode, _, _ := cmd.exec(debug, verbose) if exitCode != 0 && verbose { - log.Println("WARN: I could not annotate namespace [ " + ns + " with " + annotations + - " ]. It already exists. I am skipping this.") + logs.Info("Can't annotate namespace [ " + ns + " with " + annotations + + " ]. It already exists.") } } @@ -176,7 +121,7 @@ spec: exitCode, e, _ := cmd.exec(debug, verbose) if exitCode != 0 { - logError("ERROR: failed to create LimitRange in namespace [ " + ns + " ]: " + e) + logError("failed to create LimitRange in namespace [ " + ns + " ]: " + e) } deleteFile("temp-LimitRange.yaml") @@ -187,18 +132,18 @@ spec: // It returns true if successful, false otherwise func createContext() (bool, string) { if s.Settings.BearerToken && s.Settings.BearerTokenPath == "" { - log.Println("INFO: creating kube context with bearer token from K8S service account.") + logs.Info("Creating kube context with bearer token from K8S service account.") s.Settings.BearerTokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token" } else if s.Settings.BearerToken && s.Settings.BearerTokenPath != "" { - log.Println("INFO: creating kube context with bearer token from " + s.Settings.BearerTokenPath) + logs.Info("Creating kube context with bearer token from " + s.Settings.BearerTokenPath) } else if s.Settings.Password == "" || s.Settings.Username == "" || s.Settings.ClusterURI == "" { - return false, "ERROR: missing information to create context [ " + s.Settings.KubeContext + " ] " + + return false, "missing information to create context [ " + s.Settings.KubeContext + " ] " + "you are either missing PASSWORD, USERNAME or CLUSTERURI in the Settings section of your desired state file." } else if !s.Settings.BearerToken && (s.Certificates == nil || s.Certificates["caCrt"] == "" || s.Certificates["caKey"] == "") { - return false, "ERROR: missing information to create context [ " + s.Settings.KubeContext + " ] " + + return false, "missing information to create context [ " + s.Settings.KubeContext + " ] " + "you are either missing caCrt or caKey or both in the Certifications section of your desired state file." } else if s.Settings.BearerToken && (s.Certificates == nil || s.Certificates["caCrt"] == "") { - return false, "ERROR: missing information to create context [ " + s.Settings.KubeContext + " ] " + + return false, "missing information to create context [ " + s.Settings.KubeContext + " ] " + "caCrt is missing in the Certifications section of your desired state file." } @@ -260,7 +205,7 @@ func createContext() (bool, string) { } if exitCode, err, _ := cmd.exec(debug, verbose); exitCode != 0 { - return false, "ERROR: failed to create context [ " + s.Settings.KubeContext + " ]: " + err + return false, "failed to create context [ " + s.Settings.KubeContext + " ]: " + err } cmd = command{ @@ -270,7 +215,7 @@ func createContext() (bool, string) { } if exitCode, err, _ := cmd.exec(debug, verbose); exitCode != 0 { - return false, "ERROR: failed to create context [ " + s.Settings.KubeContext + " ]: " + err + return false, "failed to create context [ " + s.Settings.KubeContext + " ]: " + err } cmd = command{ @@ -280,14 +225,14 @@ func createContext() (bool, string) { } if exitCode, err, _ := cmd.exec(debug, verbose); exitCode != 0 { - return false, "ERROR: failed to create context [ " + s.Settings.KubeContext + " ]: " + err + return false, "failed to create context [ " + s.Settings.KubeContext + " ]: " + err } if setKubeContext(s.Settings.KubeContext) { return true, "" } - return false, "ERROR: something went wrong while setting the kube context to the newly created one." + return false, "something went wrong while setting the kube context to the newly created one." } // setKubeContext sets your kubectl context to the one specified in the desired state file. @@ -306,7 +251,7 @@ func setKubeContext(context string) bool { exitCode, _, _ := cmd.exec(debug, verbose) if exitCode != 0 { - log.Println("INFO: KubeContext: " + context + " does not exist. I will try to create it.") + logs.Info("KubeContext: " + context + " does not exist. I will try to create it.") return false } @@ -325,7 +270,7 @@ func getKubeContext() bool { exitCode, result, _ := cmd.exec(debug, verbose) if exitCode != 0 || result == "" { - log.Println("INFO: Kubectl context is not set") + logs.Info("Kubectl context is not set") return false } @@ -343,7 +288,7 @@ func createServiceAccount(saName string, namespace string) (bool, string) { exitCode, err, _ := cmd.exec(debug, verbose) if exitCode != 0 { - //logError("ERROR: failed to create service account " + saName + " in namespace [ " + namespace + " ]: " + err) + //logError("failed to create service account " + saName + " in namespace [ " + namespace + " ]: " + err) return false, err } @@ -367,7 +312,7 @@ func createRoleBinding(role string, saName string, namespace string) (bool, stri bindingName = namespace + ":" + saName + "-binding" } - log.Println("INFO: creating " + resource + " for service account [ " + saName + " ] in namespace [ " + namespace + " ] with role: " + role + ".") + logs.Info("Creating " + resource + " for service account [ " + saName + " ] in namespace [ " + namespace + " ] with role: " + role + ".") cmd := command{ Cmd: "kubectl", Args: []string{"create", resource, bindingName, bindingOption, "--serviceaccount", namespace + ":" + saName, "-n", namespace}, @@ -420,17 +365,17 @@ func createRole(namespace string, role string, roleTemplateFile string) (bool, s // labelResource applies Helmsman specific labels to Helm's state resources (secrets/configmaps) func labelResource(r *release) { if r.Enabled { - log.Println("INFO: applying Helmsman labels to [ " + r.Name + " ] in namespace [ " + r.Namespace + " ] ") - storageBackend := "configmap" + logs.Info("Applying Helmsman labels to [ " + r.Name + " ] in namespace [ " + r.Namespace + " ] ") + storageBackend := "secret" - if s.Settings.StorageBackend == "secret" { - storageBackend = "secret" + if s.Settings.StorageBackend != "" { + storageBackend = s.Settings.StorageBackend } cmd := command{ Cmd: "kubectl", - Args: []string{"label", storageBackend, "-n", getDesiredTillerNamespace(r), "-l", "NAME=" + r.Name, "MANAGED-BY=HELMSMAN", "NAMESPACE=" + r.Namespace, "TILLER_NAMESPACE=" + getDesiredTillerNamespace(r), "--overwrite"}, - Description: "applying labels to Helm state in [ " + getDesiredTillerNamespace(r) + " ] for " + r.Name, + Args: []string{"label", storageBackend, "-n", r.Namespace, "-l", "owner=helm,name=" + r.Name, "MANAGED-BY=HELMSMAN", "NAMESPACE=" + r.Namespace, "--overwrite"}, + Description: "applying labels to Helm state for " + r.Name, } exitCode, err, _ := cmd.exec(debug, verbose) @@ -444,29 +389,16 @@ func labelResource(r *release) { // getHelmsmanReleases returns a map of all releases that are labeled with "MANAGED-BY=HELMSMAN" // The releases are categorized by the namespaces in which their Tiller is running // The returned map format is: map[:map[:true]] -func getHelmsmanReleases() map[string]map[string]bool { +func getHelmsmanReleases() map[string]map[*release]bool { var lines []string - releases := make(map[string]map[string]bool) - storageBackend := "configmap" - - if s.Settings.StorageBackend == "secret" { - storageBackend = "secret" - } + releases := make(map[string]map[*release]bool) + storageBackend := "secret" - namespaces := make([]string, len(s.Namespaces)) - i := 0 - for s, v := range s.Namespaces { - if v.InstallTiller || v.UseTiller || settings.Tillerless { - namespaces[i] = s - i++ - } - } - namespaces = namespaces[0:i] - if v, ok := s.Namespaces["kube-system"]; !ok || (ok && (v.UseTiller || v.InstallTiller)) { - namespaces = append(namespaces, "kube-system") + if s.Settings.StorageBackend != "" { + storageBackend = s.Settings.StorageBackend } - for _, ns := range namespaces { + for ns, _ := range s.Namespaces { cmd := command{ Cmd: "kubectl", Args: []string{"get", storageBackend, "-n", ns, "-l", "MANAGED-BY=HELMSMAN", "-o", "name"}, @@ -486,14 +418,12 @@ func getHelmsmanReleases() map[string]map[string]bool { if r == "" { continue } - r = regexp.MustCompile(`(^\w+\/|\.v\d+$)`).ReplaceAllString(r, "") if _, ok := releases[ns]; !ok { - releases[ns] = make(map[string]bool) + releases[ns] = make(map[*release]bool) } - releases[ns][r] = false for _, app := range s.Apps { - if r == app.Name { - releases[ns][r] = true + if strings.Contains(r, app.Name) { + releases[ns][app] = true } } } @@ -512,7 +442,7 @@ func getKubectlClientVersion() string { exitCode, result, _ := cmd.exec(debug, false) if exitCode != 0 { - logError("ERROR: while checking kubectl version: " + result) + logError("while checking kubectl version: " + result) } return result } diff --git a/main.go b/main.go index 231f330d..ca6d0149 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,6 @@ package main import ( - "log" "os" ) @@ -33,7 +32,8 @@ var nsOverride string var skipValidation bool var applyLabels bool var keepUntrackedReleases bool -var appVersion = "v1.13.1" +var appVersion = "v3.0.0-beta1" +var helmBin = "helm" var helmVersion string var kubectlVersion string var dryRun bool @@ -69,8 +69,6 @@ func main() { } } - // init helm client before adding repos - initHelmClientOnly() // add repos -- fails if they are not valid if r, msg := addHelmRepos(s.HelmRepos); !r { logError(msg) @@ -81,26 +79,6 @@ func main() { if !noNs { addNamespaces(s.Namespaces) } - - if r, msg := initHelm(); !r { - logError(msg) - } - - // check if helm Tiller is ready if we aren't running in tillerless mode. - if !s.Settings.Tillerless { - for k, ns := range s.Namespaces { - if ns.InstallTiller || ns.UseTiller { - waitForTiller(k) - } - } - - if _, ok := s.Namespaces["kube-system"]; !ok { - waitForTiller("kube-system") - } - } else { - log.Println("INFO: running in TILLERLESS mode") - initHelmTiller() - } } if !skipValidation { @@ -109,12 +87,12 @@ func main() { logError(msg) } } else { - log.Println("INFO: charts validation is skipped.") + logs.Info("Skipping charts' validation.") } - log.Println("INFO: checking what I need to do for your charts ... ") + logs.Info("Preparing plan...") if destroy { - log.Println("WARN: --destroy is enabled. Your releases will be deleted!") + logs.Info("--destroy is enabled. Your releases will be deleted!") } p := makePlan(&s) @@ -129,15 +107,13 @@ func main() { if apply || dryRun || destroy { p.execPlan() } - - log.Println("INFO: completed applying plan successfully!") } // cleanup deletes the k8s certificates and keys files // It also deletes any Tiller TLS certs and keys // and secret files func cleanup() { - log.Println("INFO: cleaning up sensitive and temp files") + logs.Info("Cleaning up sensitive and temp files.") if _, err := os.Stat("ca.crt"); err == nil { deleteFile("ca.crt") } @@ -154,24 +130,6 @@ func cleanup() { deleteFile("bearer.token") } - for k := range s.Namespaces { - if _, err := os.Stat(k + "-tiller.cert"); err == nil { - deleteFile(k + "-tiller.cert") - } - if _, err := os.Stat(k + "-tiller.key"); err == nil { - deleteFile(k + "-tiller.key") - } - if _, err := os.Stat(k + "-ca.cert"); err == nil { - deleteFile(k + "-ca.cert") - } - if _, err := os.Stat(k + "-client.cert"); err == nil { - deleteFile(k + "-client.cert") - } - if _, err := os.Stat(k + "-client.key"); err == nil { - deleteFile(k + "-client.key") - } - } - for _, app := range s.Apps { if _, err := os.Stat(app.SecretsFile + ".dec"); err == nil { deleteFile(app.SecretsFile + ".dec") diff --git a/namespace.go b/namespace.go index 18a1fc44..a114bbad 100644 --- a/namespace.go +++ b/namespace.go @@ -23,17 +23,6 @@ type limits []struct { // namespace type represents the fields of a namespace type namespace struct { Protected bool `yaml:"protected"` - InstallTiller bool `yaml:"installTiller"` - UseTiller bool `yaml:"useTiller"` - TillerServiceAccount string `yaml:"tillerServiceAccount"` - TillerRole string `yaml:"tillerRole"` - TillerRoleTemplateFile string `yaml:"tillerRoleTemplateFile"` - TillerMaxHistory int `yaml:"tillerMaxHistory"` - CaCert string `yaml:"caCert"` - TillerCert string `yaml:"tillerCert"` - TillerKey string `yaml:"tillerKey"` - ClientCert string `yaml:"clientCert"` - ClientKey string `yaml:"clientKey"` Limits limits `yaml:"limits,omitempty"` Labels map[string]string `yaml:"labels"` Annotations map[string]string `yaml:"annotations"` @@ -52,15 +41,6 @@ func checkNamespaceDefined(ns string, s state) bool { func (n namespace) print() { fmt.Println("") fmt.Println("\tprotected : ", n.Protected) - fmt.Println("\tinstallTiller : ", n.InstallTiller) - fmt.Println("\tuseTiller : ", n.UseTiller) - fmt.Println("\ttillerServiceAccount : ", n.TillerServiceAccount) - fmt.Println("\ttillerRole: ", n.TillerRole) - fmt.Println("\tcaCert : ", n.CaCert) - fmt.Println("\ttillerCert : ", n.TillerCert) - fmt.Println("\ttillerKey : ", n.TillerKey) - fmt.Println("\tclientCert : ", n.ClientCert) - fmt.Println("\tclientKey : ", n.ClientKey) fmt.Println("\tlabels : ") printMap(n.Labels, 2) fmt.Println("------------------- ") diff --git a/plan.go b/plan.go index 6d0aa708..e317022b 100644 --- a/plan.go +++ b/plan.go @@ -2,14 +2,11 @@ package main import ( "fmt" - "log" "net/url" "sort" "strconv" "strings" "time" - - "github.com/logrusorgru/aurora" ) // decisionType type representing type of Decision for console output @@ -23,14 +20,6 @@ const ( ignored ) -var decisionColor = map[decisionType]aurora.Color{ - create: aurora.BlueFg, - change: aurora.BrownFg, - delete: aurora.RedFg, - noop: aurora.GreenFg, - ignored: aurora.GrayFg, -} - // orderedDecision type representing a Decision and it's priority weight type orderedDecision struct { Description string @@ -88,13 +77,13 @@ func (p *plan) addDecision(decision string, priority int, decisionType decisionT func (p plan) execPlan() { p.sortPlan() if len(p.Commands) > 0 { - log.Println("INFO: Executing the plan ... ") + logs.Info("Executing the plan ... ") } else { - log.Println("INFO: Nothing to execute ... ") + logs.Info("Nothing to execute.") } for _, cmd := range p.Commands { - log.Println("INFO: " + cmd.Command.Description) + logs.Notice(cmd.Command.Description) if exitCode, msg, _ := cmd.Command.exec(debug, verbose); exitCode != 0 { var errorMsg string if errorMsg = msg; !verbose { @@ -102,16 +91,20 @@ func (p plan) execPlan() { } logError(fmt.Sprintf("Command returned with exit code: %d. And error message: %s ", exitCode, errorMsg)) } else { - log.Println(style.Cyan(msg)) + logs.Notice(msg) if cmd.targetRelease != nil && !dryRun { labelResource(cmd.targetRelease) } - log.Println("INFO: finished " + cmd.Command.Description) + logs.Notice("Finished " + cmd.Command.Description) if _, err := url.ParseRequestURI(s.Settings.SlackWebhook); err == nil { notifySlack(cmd.Command.Description+" ... SUCCESS!", s.Settings.SlackWebhook, false, true) } } } + + if len(p.Commands) > 0 { + logs.Info("Plan applied.") + } } // printPlanCmds prints the actual commands that will be executed as part of a plan. @@ -124,11 +117,19 @@ func (p plan) printPlanCmds() { // printPlan prints the decisions made in a plan. func (p plan) printPlan() { - log.Println("----------------------") - log.Println(style.Bold(style.Green("INFO: Plan generated at: " + p.Created.Format("Mon Jan _2 2006 15:04:05")))) + logs.Notice("-------- PLAN starts here --------------") for _, decision := range p.Decisions { - log.Println(style.Colorize(decision.Description+" -- priority: "+strconv.Itoa(decision.Priority), decisionColor[decision.Type])) + if decision.Type == ignored { + logs.Info(decision.Description + " -- priority: " + strconv.Itoa(decision.Priority)) + } else if decision.Type == noop { + logs.Info(decision.Description+" -- priority: "+strconv.Itoa(decision.Priority)) + } else if decision.Type == delete { + logs.Warning(decision.Description+" -- priority: "+strconv.Itoa(decision.Priority)) + } else { + logs.Notice(decision.Description+" -- priority: "+strconv.Itoa(decision.Priority)) + } } + logs.Notice("-------- PLAN ends here --------------") } // sendPlanToSlack sends the description of plan commands to slack if a webhook is provided. @@ -147,7 +148,7 @@ func (p plan) sendPlanToSlack() { // sortPlan sorts the slices of commands and decisions based on priorities // the lower the priority value the earlier a command should be attempted func (p plan) sortPlan() { - log.Println("INFO: sorting the commands in the plan based on priorities (order flags) ... ") + logs.Debug("Sorting the commands in the plan based on priorities (order flags) ... ") sort.SliceStable(p.Commands, func(i, j int) bool { return p.Commands[i].Priority < p.Commands[j].Priority diff --git a/release.go b/release.go index 367798cc..b6a877d7 100644 --- a/release.go +++ b/release.go @@ -2,11 +2,8 @@ package main import ( "fmt" - "log" "os" "strings" - - "github.com/hashicorp/go-version" ) // release type representing Helm releases which are described in the desired state @@ -27,7 +24,6 @@ type release struct { Protected bool `yaml:"protected"` Wait bool `yaml:"wait"` Priority int `yaml:"priority"` - TillerNamespace string `yaml:"tillerNamespace"` Set map[string]string `yaml:"set"` SetString map[string]string `yaml:"setString"` HelmFlags []string `yaml:"helmFlags"` @@ -60,20 +56,7 @@ func validateRelease(appLabel string, r *release, names map[string]map[string]bo r.Name = appLabel } - if s.Settings.Tillerless { - // if we are running in a tillerless environment then lets skip the tiller validation - } else if r.TillerNamespace != "" { - if ns, ok := s.Namespaces[r.TillerNamespace]; !ok { - return false, "tillerNamespace specified, but the namespace specified does not exist!" - } else if !ns.InstallTiller && !ns.UseTiller { - return false, "tillerNamespace specified, but that namespace does not have neither installTiller nor useTiller set to true." - } - } else if getDesiredTillerNamespace(r) == "kube-system" { - if ns, ok := s.Namespaces["kube-system"]; ok && !ns.InstallTiller && !ns.UseTiller { - return false, "app is desired to be deployed using Tiller from [[ kube-system ]] but kube-system is not desired to have a Tiller installed nor use an existing Tiller. You can use another Tiller with the 'tillerNamespace' option or deploy Tiller in kube-system. " - } - } - if names[r.Name][getDesiredTillerNamespace(r)] { + if names[r.Name][r.Namespace] { return false, "release name must be unique within a given Tiller." } @@ -124,21 +107,7 @@ func validateRelease(appLabel string, r *release, names map[string]map[string]bo if names[r.Name] == nil { names[r.Name] = make(map[string]bool) } - if r.TillerNamespace != "" { - names[r.Name][r.TillerNamespace] = true - } else if s.Namespaces[r.Namespace].InstallTiller { - names[r.Name][r.Namespace] = true - } else { - names[r.Name]["kube-system"] = true - } - - if len(r.SetString) > 0 { - v1, _ := version.NewVersion(helmVersion) - setStringConstraint, _ := version.NewConstraint(">=2.9.0") - if !setStringConstraint.Check(v1) { - return false, "you are using setString in your desired state, but your helm client does not support it. You need helm v2.9.0 or above for this feature." - } - } + names[r.Name][r.Namespace] = true // add $$ escaping for $ strings os.Setenv("HELMSMAN_DOLLAR", "$") @@ -155,7 +124,7 @@ func validateRelease(appLabel string, r *release, names map[string]map[string]bo // overrideNamespace overrides a release defined namespace with a new given one func overrideNamespace(r *release, newNs string) { - log.Println("INFO: overriding namespace for app: " + r.Name) + logs.Info("Overriding namespace for app: " + r.Name) r.Namespace = newNs } @@ -175,7 +144,6 @@ func (r release) print() { fmt.Println("\tprotected : ", r.Protected) fmt.Println("\twait : ", r.Wait) fmt.Println("\tpriority : ", r.Priority) - fmt.Println("\ttiller namespace : ", r.TillerNamespace) fmt.Println("\tno-hooks : ", r.NoHooks) fmt.Println("\ttimeout : ", r.Timeout) fmt.Println("\tvalues to override from env:") diff --git a/release_test.go b/release_test.go index 8e3f5a4a..9b6a7721 100644 --- a/release_test.go +++ b/release_test.go @@ -10,7 +10,7 @@ func Test_validateRelease(t *testing.T) { Metadata: make(map[string]string), Certificates: make(map[string]string), Settings: (config{}), - Namespaces: map[string]namespace{"namespace": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}}, + Namespaces: map[string]namespace{"namespace": namespace{false, limits{}, make(map[string]string), make(map[string]string)}}, HelmRepos: make(map[string]string), Apps: make(map[string]*release), } diff --git a/state.go b/state.go index 4b22190f..0630e02a 100644 --- a/state.go +++ b/state.go @@ -1,8 +1,8 @@ package main import ( + "errors" "fmt" - "log" "net/url" "os" "strings" @@ -10,20 +10,19 @@ import ( // config type represents the settings fields type config struct { - KubeContext string `yaml:"kubeContext"` - Username string `yaml:"username"` - Password string `yaml:"password"` - ClusterURI string `yaml:"clusterURI"` - ServiceAccount string `yaml:"serviceAccount"` - StorageBackend string `yaml:"storageBackend"` - SlackWebhook string `yaml:"slackWebhook"` - ReverseDelete bool `yaml:"reverseDelete"` - BearerToken bool `yaml:"bearerToken"` - BearerTokenPath string `yaml:"bearerTokenPath"` - Tillerless bool `yaml:"tillerless"` - EyamlEnabled bool `yaml:"eyamlEnabled"` - EyamlPrivateKeyPath string `yaml:"eyamlPrivateKeyPath"` - EyamlPublicKeyPath string `yaml:"eyamlPublicKeyPath"` + KubeContext string `yaml:"kubeContext"` + Username string `yaml:"username"` + Password string `yaml:"password"` + ClusterURI string `yaml:"clusterURI"` + ServiceAccount string `yaml:"serviceAccount"` + StorageBackend string `yaml:"storageBackend"` + SlackWebhook string `yaml:"slackWebhook"` + ReverseDelete bool `yaml:"reverseDelete"` + BearerToken bool `yaml:"bearerToken"` + BearerTokenPath string `yaml:"bearerTokenPath"` + EyamlEnabled bool `yaml:"eyamlEnabled"` + EyamlPrivateKeyPath string `yaml:"eyamlPrivateKeyPath"` + EyamlPublicKeyPath string `yaml:"eyamlPublicKeyPath"` } // state type represents the desired state of applications on a k8s cluster. @@ -40,39 +39,39 @@ type state struct { // validate validates that the values specified in the desired state are valid according to the desired state spec. // check https://github.com/Praqma/Helmsman/docs/desired_state_spec.md for the detailed specification -func (s state) validate() (bool, string) { +func (s state) validate() error { // settings if (s.Settings == (config{}) || s.Settings.KubeContext == "") && !getKubeContext() { - return false, "ERROR: settings validation failed -- you have not defined a " + - "kubeContext to use. Either define it in the desired state file or pass a kubeconfig with --kubeconfig to use an existing context." + return errors.New("settings validation failed -- you have not defined a " + + "kubeContext to use. Either define it in the desired state file or pass a kubeconfig with --kubeconfig to use an existing context") } else if s.Settings.ClusterURI != "" { if _, err := url.ParseRequestURI(s.Settings.ClusterURI); err != nil { - return false, "ERROR: settings validation failed -- clusterURI must have a valid URL set in an env variable or passed directly. Either the env var is missing/empty or the URL is invalid." + return errors.New("settings validation failed -- clusterURI must have a valid URL set in an env variable or passed directly. Either the env var is missing/empty or the URL is invalid") } if s.Settings.KubeContext == "" { - return false, "ERROR: settings validation failed -- KubeContext needs to be provided in the settings stanza." + return errors.New("settings validation failed -- KubeContext needs to be provided in the settings stanza") } if !s.Settings.BearerToken && s.Settings.Username == "" { - return false, "ERROR: settings validation failed -- username needs to be provided in the settings stanza." + return errors.New("settings validation failed -- username needs to be provided in the settings stanza") } if !s.Settings.BearerToken && s.Settings.Password == "" { - return false, "ERROR: settings validation failed -- password needs to be provided (directly or from env var) in the settings stanza." + return errors.New("settings validation failed -- password needs to be provided (directly or from env var) in the settings stanza") } if s.Settings.BearerToken && s.Settings.BearerTokenPath != "" { if _, err := os.Stat(s.Settings.BearerTokenPath); err != nil { - return false, "ERROR: settings validation failed -- bearer token path " + s.Settings.BearerTokenPath + " is not found. The path has to be relative to the desired state file." + return errors.New("settings validation failed -- bearer token path " + s.Settings.BearerTokenPath + " is not found. The path has to be relative to the desired state file") } } } else if s.Settings.BearerToken && s.Settings.ClusterURI == "" { - return false, "ERROR: settings validation failed -- bearer token is enabled but no cluster URI provided." + return errors.New("settings validation failed -- bearer token is enabled but no cluster URI provided") } // slack webhook validation (if provided) if s.Settings.SlackWebhook != "" { if _, err := url.ParseRequestURI(s.Settings.SlackWebhook); err != nil { - return false, "ERROR: settings validation failed -- slackWebhook must be a valid URL." + return errors.New("settings validation failed -- slackWebhook must be a valid URL") } } @@ -82,7 +81,7 @@ func (s state) validate() (bool, string) { for key, value := range s.Certificates { r, path := isValidCert(value) if !r { - return false, "ERROR: certifications validation failed -- [ " + key + " ] must be a valid S3, GCS, AZ bucket/container URL or a valid relative file path." + return errors.New("certifications validation failed -- [ " + key + " ] must be a valid S3, GCS, AZ bucket/container URL or a valid relative file path") } s.Certificates[key] = path } @@ -92,79 +91,39 @@ func (s state) validate() (bool, string) { if s.Settings.ClusterURI != "" && !s.Settings.BearerToken { if !caCrt || !caKey { - return false, "ERROR: certificates validation failed -- You want me to connect to your cluster for you " + - "but have not given me the cert/key to do so. Please add [caCrt] and [caKey] under Certifications. You might also need to provide [clientCrt]." + return errors.New("certificates validation failed -- connection to cluster is required " + + "but no cert/key was given. Please add [caCrt] and [caKey] under Certifications. You might also need to provide [clientCrt]") } } else if s.Settings.ClusterURI != "" && s.Settings.BearerToken { if !caCrt { - return false, "ERROR: certificates validation failed -- cluster connection with bearer token is enabled but " + - "[caCrt] is missing. Please provide [caCrt] in the Certifications stanza." + return errors.New("certificates validation failed -- cluster connection with bearer token is enabled but " + + "[caCrt] is missing. Please provide [caCrt] in the Certifications stanza") } } } else { if s.Settings.ClusterURI != "" { - return false, "ERROR: certificates validation failed -- kube context setup is required but no certificates stanza provided." + return errors.New("certificates validation failed -- kube context setup is required but no certificates stanza provided") } } // namespaces if nsOverride == "" { if s.Namespaces == nil || len(s.Namespaces) == 0 { - return false, "ERROR: namespaces validation failed -- I need at least one namespace " + - "to work with!" - } - if !s.Settings.Tillerless { - for k, ns := range s.Namespaces { - if ns.InstallTiller && ns.UseTiller { - return false, "ERROR: namespaces validation failed -- installTiller and useTiller can't be used together for namespace [ " + k + " ]" - } - if ns.UseTiller && verbose { - log.Println("INFO: namespace validation -- a pre-installed Tiller is desired to be used in namespace [ " + k + " ].") - } else if !ns.InstallTiller && verbose { - log.Println("INFO: namespace validation -- Tiller is NOT desired to be deployed in namespace [ " + k + " ].") - } - - if ns.UseTiller || ns.InstallTiller { - // validating the TLS certs and keys for Tiller - // if they are valid, their values (if they are env vars) are substituted - var ok1, ok2, ok3, ok4, ok5 bool - ok1, ns.CaCert = isValidCert(ns.CaCert) - ok2, ns.ClientCert = isValidCert(ns.ClientCert) - ok3, ns.ClientKey = isValidCert(ns.ClientKey) - ok4, ns.TillerCert = isValidCert(ns.TillerCert) - ok5, ns.TillerKey = isValidCert(ns.TillerKey) - - if ns.InstallTiller { - if !ok1 || !ok2 || !ok3 || !ok4 || !ok5 { - log.Println("INFO: namespace validation -- Either no or invalid certs/keys provided for DEPLOYING Tiller with TLS in namespace [ " + k + " ].") - } else if verbose { - log.Println("INFO: namespace validation -- Tiller is desired to be DEPLOYED with TLS in namespace [ " + k + " ]. ") - } - } else if ns.UseTiller { - if !ok1 || !ok2 || !ok3 { - log.Println("INFO: namespace validation -- Either no or invalid certs/keys provided for USING Tiller with TLS in namespace [ " + k + " ].") - } else if verbose { - log.Println("INFO: namespace validation -- Tiller is desired to be USED with TLS in namespace [ " + k + " ]. ") - } - } - } - } - } else { - log.Println("INFO: namespace validation -- skipping because Tillerless mode is enabled.") + return errors.New("namespaces validation failed -- at least one namespace is required") } } else { - log.Println("INFO: ns-override is used to override all namespaces with [ " + nsOverride + " ] Skipping defined namespaces validation.") + logs.Info("ns-override is used to override all namespaces with [ " + nsOverride + " ] Skipping defined namespaces validation.") } // repos for k, v := range s.HelmRepos { _, err := url.ParseRequestURI(v) if err != nil { - return false, "ERROR: repos validation failed -- repo [" + k + " ] " + - "must have a valid URL." + return errors.New("repos validation failed -- repo [" + k + " ] " + + "must have a valid URL") } continue @@ -173,8 +132,7 @@ func (s state) validate() (bool, string) { // apps if s.Apps == nil { - log.Println("INFO: You have not specified any apps. I have nothing to do. ", - "Horraayyy!.") + logs.Info("No apps specified. Nothing to be executed.") os.Exit(0) } @@ -182,11 +140,11 @@ func (s state) validate() (bool, string) { for appLabel, r := range s.Apps { result, errMsg := validateRelease(appLabel, r, names, s) if !result { - return false, "ERROR: apps validation failed -- for app [" + appLabel + " ]. " + errMsg + return errors.New("apps validation failed -- for app [" + appLabel + " ]. " + errMsg) } } - return true, "" + return nil } // isValidCert checks if a certificate/key path/URI is valid @@ -199,23 +157,6 @@ func isValidCert(value string) (bool, string) { return true, value } -// tillerTLSEnabled checks if Tiller is desired to be deployed with TLS enabled for a given namespace or -// if helmsman is supposed to use an existing Tiller which is secured with TLS. -// For deploying Tiller, TLS is considered desired ONLY if all certs and keys for both Tiller and the Helm client are provided. -// For using an existing Tiller, TLS is considered desired ONLY if "CaCert" & "ClientCert" & "ClientKey" are provided. -func tillerTLSEnabled(ns namespace) bool { - if ns.UseTiller { - if ns.CaCert != "" && ns.ClientCert != "" && ns.ClientKey != "" { - return true - } - } else if ns.InstallTiller { - if ns.CaCert != "" && ns.TillerCert != "" && ns.TillerKey != "" && ns.ClientCert != "" && ns.ClientKey != "" { - return true - } - } - return false -} - // print prints the desired state func (s state) print() { diff --git a/state_test.go b/state_test.go index d12a7930..ec49e2ba 100644 --- a/state_test.go +++ b/state_test.go @@ -34,7 +34,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -58,7 +58,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -79,7 +79,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -103,7 +103,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -127,7 +127,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "$URI", // unset env }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -151,7 +151,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https//192.168.99.100:8443", // invalid url }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -174,7 +174,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -195,7 +195,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -219,7 +219,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -237,7 +237,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -278,78 +278,6 @@ func Test_state_validate(t *testing.T) { Apps: make(map[string]*release), }, want: false, - }, { - name: "test case 13 -- namespaces/use and install tiller", - fields: fields{ - Metadata: make(map[string]string), - Certificates: nil, - Settings: config{ - KubeContext: "minikube", - }, - Namespaces: map[string]namespace{ - "staging": namespace{false, true, true, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, - }, - HelmRepos: map[string]string{ - "stable": "https://kubernetes-charts.storage.googleapis.com", - "myrepo": "s3://my-repo/charts", - }, - Apps: make(map[string]*release), - }, - want: false, - }, { - name: "test case 14 -- namespaces/use tiller with tls-valid", - fields: fields{ - Metadata: make(map[string]string), - Certificates: nil, - Settings: config{ - KubeContext: "minikube", - }, - Namespaces: map[string]namespace{ - "staging": namespace{false, false, true, "", "", "", 0, "s3://some-bucket/12345.crt", "", "", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", (limits{}), make(map[string]string), make(map[string]string)}, - }, - HelmRepos: map[string]string{ - "stable": "https://kubernetes-charts.storage.googleapis.com", - "myrepo": "s3://my-repo/charts", - }, - Apps: make(map[string]*release), - }, - want: true, - }, { - name: "test case 15 -- namespaces/use tiller with tls-not enough certs", - fields: fields{ - Metadata: make(map[string]string), - Certificates: nil, - Settings: config{ - KubeContext: "minikube", - }, - Namespaces: map[string]namespace{ - "staging": namespace{false, false, true, "", "", "", 0, "", "", "", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", (limits{}), make(map[string]string), make(map[string]string)}, - }, - HelmRepos: map[string]string{ - "stable": "https://kubernetes-charts.storage.googleapis.com", - "myrepo": "s3://my-repo/charts", - }, - Apps: make(map[string]*release), - }, - want: true, - }, { - name: "test case 16 -- namespaces/deploy tiller with tls- valid", - fields: fields{ - Metadata: make(map[string]string), - Certificates: nil, - Settings: config{ - KubeContext: "minikube", - }, - Namespaces: map[string]namespace{ - "staging": namespace{false, true, false, "", "", "", 0, "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", "s3://some-bucket/12345.crt", (limits{}), make(map[string]string), make(map[string]string)}, - }, - HelmRepos: map[string]string{ - "stable": "https://kubernetes-charts.storage.googleapis.com", - "myrepo": "s3://my-repo/charts", - }, - Apps: make(map[string]*release), - }, - want: true, }, { name: "test case 17 -- helmRepos/nil_value", fields: fields{ @@ -359,7 +287,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string)}, }, HelmRepos: nil, Apps: make(map[string]*release), @@ -374,7 +302,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{}, Apps: make(map[string]*release), @@ -389,7 +317,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -407,7 +335,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, false, false, "", "", "", 0, "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}, + "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string)}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -430,8 +358,16 @@ func Test_state_validate(t *testing.T) { HelmRepos: tt.fields.HelmRepos, Apps: tt.fields.Apps, } - if got, _ := s.validate(); got != tt.want { - t.Errorf("state.validate() = %v, want %v", got, tt.want) + err := s.validate() + switch err.(type) { + case nil: + if tt.want != true { + t.Errorf("state.validate() = %v, want error", err) + } + case error: + if tt.want != false { + t.Errorf("state.validate() = %v, want nil", err) + } } }) } diff --git a/test_files/dockerfile b/test_files/dockerfile index f6b9ec04..50656249 100644 --- a/test_files/dockerfile +++ b/test_files/dockerfile @@ -8,7 +8,7 @@ ARG HELM_VERSION FROM golang:${GO_VERSION}-alpine3.10 ENV KUBE_VERSION ${KUBE_VERSION:-v1.14.8} -ENV HELM_VERSION ${HELM_VERSION:-v2.15.0} +ENV HELM_VERSION ${HELM_VERSION:-v3.0.1} ENV GORELEASER_VERSION ${GORELEASER_VERSION:-v0.120.2} RUN apk --no-cache update \ && apk add --update --no-cache ca-certificates git ruby \ @@ -16,7 +16,7 @@ RUN apk --no-cache update \ && rm -rf /var/cache/apk/* \ && curl -L https://storage.googleapis.com/kubernetes-release/release/${KUBE_VERSION}/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl \ && chmod +x /usr/local/bin/kubectl \ - && curl -L http://storage.googleapis.com/kubernetes-helm/helm-${HELM_VERSION}-linux-amd64.tar.gz | tar zxv -C /tmp \ + && curl -L https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz | tar zxv -C /tmp \ && mv /tmp/linux-amd64/helm /usr/local/bin/helm \ && rm -rf /tmp/linux-amd64 \ && chmod +x /usr/local/bin/helm @@ -26,7 +26,7 @@ WORKDIR src/helmsman RUN mkdir -p ~/.helm/plugins \ && helm plugin install https://github.com/hypnoglow/helm-s3.git \ && helm plugin install https://github.com/nouney/helm-gcs \ - && helm plugin install https://github.com/databus23/helm-diff \ + && helm plugin install https://github.com/databus23/helm-diff --version v3.0.0-rc.7 \ && helm plugin install https://github.com/futuresimple/helm-secrets \ && rm -r /tmp/helm-diff /tmp/helm-diff.tgz \ && gem install hiera-eyaml --no-doc diff --git a/utils.go b/utils.go index 863d94b8..3aebd9ba 100644 --- a/utils.go +++ b/utils.go @@ -21,7 +21,7 @@ import ( "github.com/BurntSushi/toml" "github.com/Praqma/helmsman/aws" "github.com/Praqma/helmsman/azure" - "github.com/Praqma/helmsman/gcs" + "helmsman/gcs" ) // printMap prints to the console any map of string keys and values. @@ -48,11 +48,11 @@ func fromTOML(file string, s *state) (bool, string) { tomlFile := string(rawTomlFile) if !noEnvSubst { - log.Println("INFO: substituting env variables in file: ", file) + logs.Info("Substituting env variables in file: " + file) tomlFile = substituteEnv(tomlFile) } if !noSSMSubst { - log.Println("INFO: substituting SSM variables in file: ", file) + logs.Debug("Substituting SSM variables in file: " + file) tomlFile = substituteSSM(tomlFile) } @@ -63,13 +63,13 @@ func fromTOML(file string, s *state) (bool, string) { resolvePaths(file, s) substituteVarsInValuesFiles(s) - return true, "INFO: Parsed TOML [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps." + return true, "Parsed TOML [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps." } // toTOML encodes a state type into a TOML file. // It uses the BurntSuchi TOML parser. func toTOML(file string, s *state) { - log.Println("printing generated toml ... ") + logs.Info("Printing generated toml ... ") var buff bytes.Buffer var ( newFile *os.File @@ -102,11 +102,11 @@ func fromYAML(file string, s *state) (bool, string) { yamlFile := string(rawYamlFile) if !noEnvSubst { - log.Println("INFO: substituting env variables in file: ", file) + logs.Debug("Substituting env variables in file: " + file) yamlFile = substituteEnv(yamlFile) } if !noSSMSubst { - log.Println("INFO: substituting SSM variables in file: ", file) + logs.Debug("Substituting SSM variables in file: " + file) yamlFile = substituteSSM(yamlFile) } @@ -117,12 +117,12 @@ func fromYAML(file string, s *state) (bool, string) { resolvePaths(file, s) substituteVarsInValuesFiles(s) - return true, "INFO: Parsed YAML [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps." + return true, "Parsed YAML [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps." } // toYaml encodes a state type into a YAML file func toYAML(file string, s *state) { - log.Println("printing generated yaml ... ") + logs.Info("Printing generated yaml ... ") var buff bytes.Buffer var ( newFile *os.File @@ -173,11 +173,11 @@ func substituteVarsInYaml(file string) string { yamlFile := string(rawYamlFile) if !noEnvSubst && !noEnvValuesSubst { - log.Println("INFO: substituting env variables in file: ", file) + logs.Debug("Substituting env variables in file: " + file) yamlFile = substituteEnv(yamlFile) } if !noSSMSubst && !noSSMValuesSubst { - log.Println("INFO: substituting SSM variables in file: ", file) + logs.Debug("Substituting SSM variables in file: " + file) yamlFile = substituteSSM(yamlFile) } @@ -212,7 +212,7 @@ func toFile(file string, s *state) { } else if isOfType(file, []string{".yaml", ".yml"}) { toYAML(file, s) } else { - logError("ERROR: State file does not have toml/yaml extension.") + logError("State file does not have toml/yaml extension.") } } @@ -228,7 +228,7 @@ func stringInSlice(a string, list []string) bool { // addDefaultHelmRepos adds stable and incubator helm repos to the state if they are not already defined func addDefaultHelmRepos(s *state) { if noDefaultRepos { - log.Println("INFO: default helm repo set disabled, 'stable' and 'incubator' repos unset.") + logs.Info("Default helm repo set disabled, 'stable' and 'incubator' repos unset.") return } if s.HelmRepos == nil || len(s.HelmRepos) == 0 { @@ -236,7 +236,7 @@ func addDefaultHelmRepos(s *state) { "stable": stableHelmRepo, "incubator": incubatorHelmRepo, } - log.Println("INFO: no helm repos provided, using the default 'stable' and 'incubator' repos.") + logs.Info("No helm repos provided, using the default 'stable' and 'incubator' repos.") } if _, ok := s.HelmRepos["stable"]; !ok { s.HelmRepos["stable"] = stableHelmRepo @@ -250,9 +250,6 @@ func addDefaultHelmRepos(s *state) { func resolvePaths(relativeToFile string, s *state) { dir := filepath.Dir(relativeToFile) for ns, v := range s.Namespaces { - if v.TillerRoleTemplateFile != "" { - v.TillerRoleTemplateFile, _ = filepath.Abs(filepath.Join(dir, v.TillerRoleTemplateFile)) - } s.Namespaces[ns] = v } for k, v := range s.Apps { @@ -299,28 +296,6 @@ func resolvePaths(relativeToFile string, s *state) { } s.Certificates[k] = v } - // resolving paths for helm certificate files - for k, v := range s.Namespaces { - if tillerTLSEnabled(v) { - if _, err := url.ParseRequestURI(v.CaCert); err != nil { - v.CaCert, _ = filepath.Abs(filepath.Join(dir, v.CaCert)) - } - if _, err := url.ParseRequestURI(v.ClientCert); err != nil { - v.ClientCert, _ = filepath.Abs(filepath.Join(dir, v.ClientCert)) - } - if _, err := url.ParseRequestURI(v.ClientKey); err != nil { - v.ClientKey, _ = filepath.Abs(filepath.Join(dir, v.ClientKey)) - } - if _, err := url.ParseRequestURI(v.TillerCert); err != nil { - v.TillerCert, _ = filepath.Abs(filepath.Join(dir, v.TillerCert)) - } - if _, err := url.ParseRequestURI(v.TillerKey); err != nil { - v.TillerKey, _ = filepath.Abs(filepath.Join(dir, v.TillerKey)) - } - } - s.Namespaces[k] = v - } - } // isOfType checks if the file extension of a filename/path is the same as "filetype". @@ -339,15 +314,15 @@ func isOfType(filename string, filetypes []string) bool { func readFile(filepath string) string { data, err := ioutil.ReadFile(filepath) if err != nil { - logError("ERROR: failed to read [ " + filepath + " ] file content: " + err.Error()) + logError("failed to read [ " + filepath + " ] file content: " + err.Error()) } return string(data) } // logVersions prints the versions of kubectl and helm to the logs func logVersions() { - log.Println("VERBOSE: kubectl client version: " + kubectlVersion) - log.Println("VERBOSE: Helm client version: " + helmVersion) + logs.Debug("kubectl client version: " + kubectlVersion) + logs.Debug("Helm client version: " + helmVersion) } // substituteEnv checks if a string has an env variable (contains '$'), then it returns its value @@ -404,7 +379,10 @@ func downloadFile(path string, outfile string) string { } else if strings.HasPrefix(path, "gs") { tmp := getBucketElements(path) - gcs.ReadFile(tmp["bucketName"], tmp["filePath"], outfile, noColors) + msg, err := gcs.ReadFile(tmp["bucketName"], tmp["filePath"], outfile, noColors) + if err != nil { + logs.Fatal(msg) + } } else if strings.HasPrefix(path, "az") { @@ -413,7 +391,7 @@ func downloadFile(path string, outfile string) string { } else { - log.Println("INFO: " + outfile + " will be used from local file system.") + logs.Info("" + outfile + " will be used from local file system.") copyFile(path, outfile) } return outfile @@ -423,27 +401,27 @@ func downloadFile(path string, outfile string) string { func copyFile(source string, destination string) { from, err := os.Open(source) if err != nil { - logError("ERROR: while copying " + source + " to " + destination + " : " + err.Error()) + logError("while copying " + source + " to " + destination + " : " + err.Error()) } defer from.Close() to, err := os.OpenFile(destination, os.O_RDWR|os.O_CREATE, 0666) if err != nil { - logError("ERROR: while copying " + source + " to " + destination + " : " + err.Error()) + logError("while copying " + source + " to " + destination + " : " + err.Error()) } defer to.Close() _, err = io.Copy(to, from) if err != nil { - logError("ERROR: while copying " + source + " to " + destination + " : " + err.Error()) + logError("while copying " + source + " to " + destination + " : " + err.Error()) } } // deleteFile deletes a file func deleteFile(path string) { - log.Println("INFO: cleaning up ... deleting " + path) + logs.Info("Cleaning up ... deleting " + path) if err := os.Remove(path); err != nil { - logError("ERROR: could not delete file: " + path) + logError("Could not delete file: " + path) } } @@ -452,7 +430,7 @@ func deleteFile(path string) { // and the webhook URL as well as a flag specifying if this is a failure message or not // It returns true if the sending of the message is successful, otherwise returns false func notifySlack(content string, url string, failure bool, executing bool) bool { - log.Println("INFO: posting notifications to slack ... ") + logs.Info("Posting notifications to slack ... ") color := "#36a64f" // green if failure { @@ -490,7 +468,7 @@ func notifySlack(content string, url string, failure bool, executing bool) bool client := &http.Client{} resp, err := client.Do(req) if err != nil { - logError("ERROR: while sending notifications to slack" + err.Error()) + logError("while sending notifications to slack" + err.Error()) } defer resp.Body.Close() @@ -505,8 +483,7 @@ func logError(msg string) { if _, err := url.ParseRequestURI(s.Settings.SlackWebhook); err == nil { notifySlack(msg, s.Settings.SlackWebhook, true, apply) } - log.SetOutput(os.Stderr) - log.Fatal(style.Bold(style.Red(msg))) + logs.Fatal(msg) } // getBucketElements returns a map containing the bucket name and the file path inside the bucket @@ -558,27 +535,6 @@ func isLocalChart(chart string) bool { return false } -func generateCmdDescription(r *release, action string) string { - var tillerNamespaceMsg string - if tillerNamespaceMsg = ""; !settings.Tillerless { - tillerNamespaceMsg = "using Tiller in [ " + getDesiredTillerNamespace(r) + " ]" - } - message := fmt.Sprintf("%s release [ "+r.Name+" ] in namespace [[ "+r.Namespace+" ]] %s", action, tillerNamespaceMsg) - return message -} - -func generateDecisionMessage(r *release, message string, isTillerAware bool) string { - var tillerNamespaceMsg string - if tillerNamespaceMsg = ""; !settings.Tillerless { - tillerNamespaceMsg = "using Tiller in [ " + getDesiredTillerNamespace(r) + " ]" - } - baseMessage := "DECISION: " + message - if isTillerAware { - return fmt.Sprintf(baseMessage+" %s", tillerNamespaceMsg) - } - return baseMessage -} - // concat appends all slices to a single slice func concat(slices ...[]string) []string { slice := []string{} From 2e3e6dcda17ca456e7c4b2daaf4ee5be907dbfed Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Fri, 13 Dec 2019 21:21:45 +0100 Subject: [PATCH 0512/1127] Change CircleCI --- .circleci/config.yml | 56 ++++++++++--------------------------------- Dockerfile | 38 +++++++++++++++++++++++++++++ Makefile | 2 +- dockerfile/README.md | 17 ------------- dockerfile/dockerfile | 52 ---------------------------------------- scripts/setup.sh | 20 ++++++++++++++++ test_files/dockerfile | 39 ------------------------------ 7 files changed, 71 insertions(+), 153 deletions(-) create mode 100644 Dockerfile delete mode 100644 dockerfile/README.md delete mode 100644 dockerfile/dockerfile create mode 100755 scripts/setup.sh delete mode 100644 test_files/dockerfile diff --git a/.circleci/config.yml b/.circleci/config.yml index a8713011..99f93a03 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,40 +1,24 @@ -# Golang CircleCI 2.0 configuration file -# -# Check https://circleci.com/docs/2.0/language-go/ for more details version: 2 jobs: build: - working_directory: "/go/src/helmsman" docker: - - image: praqma/helmsman-test:helm3 + - image: docker:19.03.5-git steps: - checkout + - setup_remote_docker - run: - name: Build helmsman + name: build docker image command: | - echo "building ..." - export GOOS=linux - make build + docker pull praqma/helmsman:latest + docker build --cache-from=praqma/helmsman:latest -t helmsman . - test: - working_directory: "/go/src/helmsman" - docker: - - image: praqma/helmsman-test:helm3 - steps: - - checkout - - run: - name: Unit test helmsman - command: | - echo "running tests ..." - export GOOS=linux - make test release: working_directory: "/tmp/go/src/helmsman" machine: true steps: - checkout - run: - name: Release helmsman + name: release helmsman command: | echo "releasing ..." curl -sL https://git.io/goreleaser | bash -s -- --release-notes release-notes.md --rm-dist @@ -44,27 +28,19 @@ jobs: command: | TAG=$(git describe --abbrev=0 --tags) docker login -u $DOCKER_USER -p $DOCKER_PASS - docker build -t praqma/helmsman:$TAG-helm-v2.15.1 --build-arg HELM_VERSION=v2.15.1 --build-arg HELM_DIFF_VERSION=master dockerfile/. --no-cache + docker build -t praqma/helmsman:$TAG-helm-v2.15.1 --build-arg HELM_VERSION=v2.15.1 --build-arg HELM_DIFF_VERSION=master . --no-cache docker push praqma/helmsman:$TAG-helm-v2.15.1 - docker build -t praqma/helmsman:$TAG-helm-v2.14.3 --build-arg HELM_VERSION=v2.14.3 --build-arg HELM_DIFF_VERSION=master dockerfile/. --no-cache + docker build -t praqma/helmsman:$TAG-helm-v2.14.3 --build-arg HELM_VERSION=v2.14.3 --build-arg HELM_DIFF_VERSION=master . --no-cache docker push praqma/helmsman:$TAG-helm-v2.14.3 - docker build -t praqma/helmsman:$TAG-helm-v2.13.1 --build-arg HELM_VERSION=v2.13.1 --build-arg HELM_DIFF_VERSION=master dockerfile/. --no-cache + docker build -t praqma/helmsman:$TAG-helm-v2.13.1 --build-arg HELM_VERSION=v2.13.1 --build-arg HELM_DIFF_VERSION=master . --no-cache docker push praqma/helmsman:$TAG-helm-v2.13.1 - docker build -t praqma/helmsman:$TAG-helm-v2.12.3 --build-arg HELM_VERSION=v2.12.3 --build-arg HELM_DIFF_VERSION=master dockerfile/. --no-cache + docker build -t praqma/helmsman:$TAG-helm-v2.12.3 --build-arg HELM_VERSION=v2.12.3 --build-arg HELM_DIFF_VERSION=master . --no-cache docker push praqma/helmsman:$TAG-helm-v2.12.3 - docker build -t praqma/helmsman:$TAG-helm-v2.11.0 --build-arg HELM_VERSION=v2.11.0 --build-arg HELM_DIFF_VERSION=v2.11.0+5 dockerfile/. --no-cache + docker build -t praqma/helmsman:$TAG-helm-v2.11.0 --build-arg HELM_VERSION=v2.11.0 --build-arg HELM_DIFF_VERSION=v2.11.0+5 . --no-cache docker push praqma/helmsman:$TAG-helm-v2.11.0 - docker build -t praqma/helmsman:$TAG-helm-v2.10.0 --build-arg HELM_VERSION=v2.10.0 --build-arg HELM_DIFF_VERSION=v2.10.0+1 dockerfile/. --no-cache + docker build -t praqma/helmsman:$TAG-helm-v2.10.0 --build-arg HELM_VERSION=v2.10.0 --build-arg HELM_DIFF_VERSION=v2.10.0+1 . --no-cache docker push praqma/helmsman:$TAG-helm-v2.10.0 - - run: - name: build docker test images and push them to dockerhub - command: | - docker login -u $DOCKER_USER -p $DOCKER_PASS - docker build -t praqma/helmsman-test:latest test_files/. --no-cache - docker push praqma/helmsman-test:latest - - workflows: version: 2 build-test-push-release: @@ -73,15 +49,7 @@ workflows: filters: tags: only: /.*/ - - test: - requires: - - build - filters: - tags: - only: /.*/ - release: - requires: - - test filters: branches: ignore: /.*/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..f6f30514 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,38 @@ +ARG GO_VERSION="1.13.5" +ARG ALPINE_VERSION="3.10" +ARG GLOBAL_KUBE_VERSION="v1.14.8" +ARG GLOBAL_HELM_VERSION="v3.0.1" +ARG GLOBAL_HELM_DIFF_VERSION="v3.0.0-rc.7" + + +FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} as builder +ARG GLOBAL_KUBE_VERSION +ARG GLOBAL_HELM_VERSION +ARG GLOBAL_HELM_DIFF_VERSION +ENV KUBE_VERSION=$GLOBAL_KUBE_VERSION +ENV HELM_VERSION=$GLOBAL_HELM_VERSION +ENV HELM_DIFF_VERSION=$GLOBAL_HELM_DIFF_VERSION +WORKDIR /go/src/helmsman +COPY scripts/ /tmp/ +RUN sh /tmp/setup.sh \ + && apk --no-cache add dep +COPY . . +RUN make test \ + && LastTag=$(git describe --abbrev=0 --tags) \ + && TAG=$LastTag-$(date +"%d%m%y") \ + && LT_SHA=$(git rev-parse ${LastTag}^{}) \ + && LC_SHA=$(git rev-parse HEAD) \ + && if [ ${LT_SHA} != ${LC_SHA} ]; then TAG=latest-$(date +"%d%m%y"); fi \ + && make build + + +FROM alpine:${ALPINE_VERSION} as base +ARG GLOBAL_KUBE_VERSION +ARG GLOBAL_HELM_VERSION +ARG GLOBAL_HELM_DIFF_VERSION +ENV KUBE_VERSION=$GLOBAL_KUBE_VERSION +ENV HELM_VERSION=$GLOBAL_HELM_VERSION +ENV HELM_DIFF_VERSION=$GLOBAL_HELM_DIFF_VERSION +COPY scripts/ /tmp/ +RUN sh /tmp/setup.sh +COPY --from=builder /go/src/helmsman/helmsman /bin/helmsman diff --git a/Makefile b/Makefile index 9a383481..980d4e9b 100644 --- a/Makefile +++ b/Makefile @@ -68,7 +68,7 @@ $(SRCDIR): dep: $(SRCDIR) ## Ensure vendors with dep @cd $(PRJDIR) && \ - dep ensure + dep ensure -v .PHONY: dep dep-update: $(SRCDIR) ## Ensure vendors with dep diff --git a/dockerfile/README.md b/dockerfile/README.md deleted file mode 100644 index f70b42d9..00000000 --- a/dockerfile/README.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -version: v0.1.2 ---- - -# Usage - -``` -docker run -v $(pwd):/tmp --rm -it \ --e KUBECTL_PASSWORD= \ --e AWS_ACCESS_KEY_ID= \ --e AWS_DEFAULT_REGION= \ --e AWS_SECRET_ACCESS_KEY= \ -praqma/helmsman:v0.1.2 \ -helmsman -debug -apply -f . -``` - -Check the different image tags on [Dockerhub](https://hub.docker.com/r/praqma/helmsman/) \ No newline at end of file diff --git a/dockerfile/dockerfile b/dockerfile/dockerfile deleted file mode 100644 index 26c73d01..00000000 --- a/dockerfile/dockerfile +++ /dev/null @@ -1,52 +0,0 @@ -# This is a docker image for helmsman -ARG GO_VERSION=1.13.5 - -FROM golang:${GO_VERSION}-alpine3.10 as builder - -WORKDIR /go/src/ - -RUN apk --no-cache add make git dep -RUN git clone https://github.com/Praqma/helmsman.git - -# build a statically linked binary so that it works on stripped linux images such as alpine/busybox. -RUN cd helmsman \ - && LastTag=$(git describe --abbrev=0 --tags) \ - && TAG=$LastTag-$(date +"%d%m%y") \ - && LT_SHA=$(git rev-parse ${LastTag}^{}) \ - && LC_SHA=$(git rev-parse HEAD) \ - && if [ ${LT_SHA} != ${LC_SHA} ]; then TAG=latest-$(date +"%d%m%y"); fi \ - && make build - -# The image to keep -FROM alpine:3.10 - -ARG KUBE_VERSION -ARG HELM_VERSION - -ENV KUBE_VERSION ${KUBE_VERSION:-v1.14.8} -ENV HELM_VERSION ${HELM_VERSION:-v3.0.1} -ENV HELM_DIFF_VERSION ${HELM_DIFF_VERSION:-v3.0.0-rc.7} - -RUN apk --no-cache update \ - && apk add --update --no-cache ca-certificates git openssh ruby \ - && apk add --update -t deps curl tar gzip make bash \ - && rm -rf /var/cache/apk/* \ - && curl -L https://storage.googleapis.com/kubernetes-release/release/${KUBE_VERSION}/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl \ - && chmod +x /usr/local/bin/kubectl \ - && curl -L https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz | tar zxv -C /tmp \ - && mv /tmp/linux-amd64/helm /usr/local/bin/helm \ - && rm -rf /tmp/linux-amd64 \ - && chmod +x /usr/local/bin/helm - -COPY --from=builder /go/src/helmsman/helmsman /bin/helmsman - -RUN mkdir -p ~/.helm/plugins \ - && helm plugin install https://github.com/hypnoglow/helm-s3.git \ - && helm plugin install https://github.com/nouney/helm-gcs \ - && helm plugin install https://github.com/databus23/helm-diff --version ${HELM_DIFF_VERSION} \ - && helm plugin install https://github.com/futuresimple/helm-secrets \ - && rm -r /tmp/helm-diff /tmp/helm-diff.tgz \ - && gem install hiera-eyaml --no-doc - -WORKDIR /tmp -# ENTRYPOINT ["/bin/helmsman"] diff --git a/scripts/setup.sh b/scripts/setup.sh new file mode 100755 index 00000000..4799fa3d --- /dev/null +++ b/scripts/setup.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +set -e + +apk add --update --no-cache ca-certificates git openssh ruby curl tar gzip make bash +curl -L https://storage.googleapis.com/kubernetes-release/release/${KUBE_VERSION}/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl +chmod +x /usr/local/bin/kubectl + +curl -L https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz | tar zxv -C /tmp +mv /tmp/linux-amd64/helm /usr/local/bin/helm +rm -rf /tmp/linux-amd64 +chmod +x /usr/local/bin/helm + +mkdir -p ~/.helm/plugins +helm plugin install https://github.com/hypnoglow/helm-s3.git +helm plugin install https://github.com/nouney/helm-gcs +helm plugin install https://github.com/databus23/helm-diff --version ${HELM_DIFF_VERSION} +helm plugin install https://github.com/futuresimple/helm-secrets +rm -r /tmp/helm-diff /tmp/helm-diff.tgz +gem install hiera-eyaml --no-doc diff --git a/test_files/dockerfile b/test_files/dockerfile deleted file mode 100644 index 50656249..00000000 --- a/test_files/dockerfile +++ /dev/null @@ -1,39 +0,0 @@ -# This is a docker image for the helmsman test container -# It can be pulled from praqma/helmsman-test - -ARG GO_VERSION=1.13.5 -ARG KUBE_VERSION -ARG HELM_VERSION - -FROM golang:${GO_VERSION}-alpine3.10 - -ENV KUBE_VERSION ${KUBE_VERSION:-v1.14.8} -ENV HELM_VERSION ${HELM_VERSION:-v3.0.1} -ENV GORELEASER_VERSION ${GORELEASER_VERSION:-v0.120.2} -RUN apk --no-cache update \ - && apk add --update --no-cache ca-certificates git ruby \ - && apk add --update -t deps curl tar gzip make bash gcc git dep openssh \ - && rm -rf /var/cache/apk/* \ - && curl -L https://storage.googleapis.com/kubernetes-release/release/${KUBE_VERSION}/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl \ - && chmod +x /usr/local/bin/kubectl \ - && curl -L https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz | tar zxv -C /tmp \ - && mv /tmp/linux-amd64/helm /usr/local/bin/helm \ - && rm -rf /tmp/linux-amd64 \ - && chmod +x /usr/local/bin/helm - -WORKDIR src/helmsman - -RUN mkdir -p ~/.helm/plugins \ - && helm plugin install https://github.com/hypnoglow/helm-s3.git \ - && helm plugin install https://github.com/nouney/helm-gcs \ - && helm plugin install https://github.com/databus23/helm-diff --version v3.0.0-rc.7 \ - && helm plugin install https://github.com/futuresimple/helm-secrets \ - && rm -r /tmp/helm-diff /tmp/helm-diff.tgz \ - && gem install hiera-eyaml --no-doc - -RUN mkdir /tmp/goreleaser \ - && curl -L https://github.com/goreleaser/goreleaser/releases/download/${GORELEASER_VERSION}/goreleaser_Linux_arm64.tar.gz | tar zxv -C /tmp/goreleaser \ - && mv /tmp/goreleaser/goreleaser /usr/local/bin/goreleaser \ - && rm -r /tmp/goreleaser \ - && chmod +x /usr/local/bin/goreleaser \ - && go get github.com/golang/dep/cmd/dep From 4b5baeea9954669ebe66f04fcb334b3401b80677 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Wed, 11 Dec 2019 21:01:51 +0100 Subject: [PATCH 0513/1127] Reorganize repo structure for helm 3 version --- .dockerignore | 5 + .gitignore | 5 +- .version | 1 + Dockerfile | 4 +- Gopkg.lock | 91 +++-- Gopkg.toml | 2 + Makefile | 11 +- cmd/helmsman/main.go | 9 + example.toml => examples/example.toml | 0 example.yaml => examples/example.yaml | 0 .../minimal-example.toml | 0 .../minimal-example.yaml | 0 bindata.go => internal/app/bindata.go | 2 +- init.go => internal/app/cli.go | 121 ++----- init_test.go => internal/app/cli_test.go | 2 +- command.go => internal/app/command.go | 45 +-- .../app/command_test.go | 2 +- .../app/decision_maker.go | 85 +++-- .../app/decision_maker_test.go | 16 +- .../app/helm_helpers.go | 137 ++++---- .../app/helm_helpers_test.go | 324 +++++++++--------- .../app/kube_helpers.go | 83 +++-- internal/app/logging.go | 60 ++++ main.go => internal/app/main.go | 22 +- namespace.go => internal/app/namespace.go | 10 +- plan.go => internal/app/plan.go | 30 +- plan_test.go => internal/app/plan_test.go | 2 +- release.go => internal/app/release.go | 46 +-- .../app/release_test.go | 28 +- state.go => internal/app/state.go | 32 +- state_test.go => internal/app/state_test.go | 2 +- utils.go => internal/app/utils.go | 95 +++-- utils_test.go => internal/app/utils_test.go | 20 +- {aws => internal/aws}/aws.go | 5 +- {azure => internal/azure}/azblob.go | 0 {data => internal/data}/role.yaml | 6 +- {gcs => internal/gcs}/gcs.go | 4 +- {test_files => tests}/chart-test/Chart.yaml | 0 {test_files => tests}/invalid_example.toml | 0 {test_files => tests}/invalid_example.yaml | 0 .../keys/private_key.pkcs7.pem | 0 .../keys/public_key.pkcs7.pem | 0 .../secrets/valid_eyaml_secrets.yaml | 0 {test_files => tests}/values.xml | 0 {test_files => tests}/values.yaml | 0 {test_files => tests}/values2.yaml | 0 46 files changed, 671 insertions(+), 636 deletions(-) create mode 100644 .dockerignore create mode 100644 .version create mode 100644 cmd/helmsman/main.go rename example.toml => examples/example.toml (100%) rename example.yaml => examples/example.yaml (100%) rename minimal-example.toml => examples/minimal-example.toml (100%) rename minimal-example.yaml => examples/minimal-example.yaml (100%) rename bindata.go => internal/app/bindata.go (99%) rename init.go => internal/app/cli.go (66%) rename init_test.go => internal/app/cli_test.go (99%) rename command.go => internal/app/command.go (62%) rename command_test.go => internal/app/command_test.go (99%) rename decision_maker.go => internal/app/decision_maker.go (77%) rename decision_maker_test.go => internal/app/decision_maker_test.go (93%) rename helm_helpers.go => internal/app/helm_helpers.go (72%) rename helm_helpers_test.go => internal/app/helm_helpers_test.go (53%) rename kube_helpers.go => internal/app/kube_helpers.go (82%) create mode 100644 internal/app/logging.go rename main.go => internal/app/main.go (90%) rename namespace.go => internal/app/namespace.go (80%) rename plan.go => internal/app/plan.go (80%) rename plan_test.go => internal/app/plan_test.go (99%) rename release.go => internal/app/release.go (80%) rename release_test.go => internal/app/release_test.go (90%) rename state.go => internal/app/state.go (86%) rename state_test.go => internal/app/state_test.go (99%) rename utils.go => internal/app/utils.go (85%) rename utils_test.go => internal/app/utils_test.go (95%) rename {aws => internal/aws}/aws.go (99%) rename {azure => internal/azure}/azblob.go (100%) rename {data => internal/data}/role.yaml (54%) rename {gcs => internal/gcs}/gcs.go (96%) rename {test_files => tests}/chart-test/Chart.yaml (100%) rename {test_files => tests}/invalid_example.toml (100%) rename {test_files => tests}/invalid_example.yaml (100%) rename {test_files => tests}/keys/private_key.pkcs7.pem (100%) rename {test_files => tests}/keys/public_key.pkcs7.pem (100%) rename {test_files => tests}/secrets/valid_eyaml_secrets.yaml (100%) rename {test_files => tests}/values.xml (100%) rename {test_files => tests}/values.yaml (100%) rename {test_files => tests}/values2.yaml (100%) diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..88e16c2a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +.circleci +keys +vendor +.gitignore +.idea diff --git a/.gitignore b/.gitignore index 51c12c71..c4ca8794 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,7 @@ *.world1 helmsman helmsman.exe -keys +keys/* +!tests/secrets/keys +!cmd/**/* +.helmsman-tmp diff --git a/.version b/.version new file mode 100644 index 00000000..fc910349 --- /dev/null +++ b/.version @@ -0,0 +1 @@ +v3.0.0-beta1 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index f6f30514..01c06366 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,7 @@ ARG GLOBAL_HELM_DIFF_VERSION ENV KUBE_VERSION=$GLOBAL_KUBE_VERSION ENV HELM_VERSION=$GLOBAL_HELM_VERSION ENV HELM_DIFF_VERSION=$GLOBAL_HELM_DIFF_VERSION -WORKDIR /go/src/helmsman +WORKDIR /go/src/github.com/Praqma/helmsman COPY scripts/ /tmp/ RUN sh /tmp/setup.sh \ && apk --no-cache add dep @@ -35,4 +35,4 @@ ENV HELM_VERSION=$GLOBAL_HELM_VERSION ENV HELM_DIFF_VERSION=$GLOBAL_HELM_DIFF_VERSION COPY scripts/ /tmp/ RUN sh /tmp/setup.sh -COPY --from=builder /go/src/helmsman/helmsman /bin/helmsman +COPY --from=builder /go/src/github.com/Praqma/helmsman/helmsman /bin/helmsman diff --git a/Gopkg.lock b/Gopkg.lock index ffc046b1..d6a12999 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -41,17 +41,6 @@ revision = "3012a1dbe2e4bd1391d42b32f0577cb7bbc7f005" version = "v0.3.1" -[[projects]] - digest = "1:cd5d5e021421b663725f1c51b29808dcee703afcb273579e2061e1afe8131a64" - name = "github.com/Praqma/helmsman" - packages = [ - "aws", - "azure", - ] - pruneopts = "UT" - revision = "eb732a11111e881e5d8918e446f4444acb16a1c1" - version = "v1.13.0" - [[projects]] digest = "1:efe5038e25001cc88547d8ecc9ed76bbb97c15826b9bb91b931ec30dacb4e3f1" name = "github.com/apsdehal/go-logger" @@ -61,10 +50,11 @@ version = "1.3.0" [[projects]] - digest = "1:6eee9d8a746572e8235c140a5d4bf1cba39eb99cddf5433b7bab290f11d9d042" + digest = "1:1742529f9132aa5c14ffadd6c2e87c211ef47fe9b6df1139de41489179f13da0" name = "github.com/aws/aws-sdk-go" packages = [ "aws", + "aws/arn", "aws/awserr", "aws/awsutil", "aws/client", @@ -100,6 +90,7 @@ "private/protocol/restxml", "private/protocol/xml/xmlutil", "service/s3", + "service/s3/internal/arn", "service/s3/s3iface", "service/s3/s3manager", "service/ssm", @@ -107,8 +98,8 @@ "service/sts/stsiface", ] pruneopts = "UT" - revision = "2f232d11486e77d344da0723340b566d3ff7865a" - version = "v1.25.24" + revision = "b4f52c45248fcab90c27f5509504b840ca2fcf10" + version = "v1.26.2" [[projects]] branch = "master" @@ -174,14 +165,14 @@ [[projects]] branch = "master" - digest = "1:d9cb8387be9021d7f18f55c85e250b6d9ec0a79ad98c6ef586d25c56a2961a6e" + digest = "1:82589c260d8f6281bbcdda5f99638e2667054d0760e3783c1ea44dac5df67394" name = "github.com/logrusorgru/aurora" packages = ["."] pruneopts = "UT" - revision = "dc85c304c4348667b70d673924f7f61f12923578" + revision = "66b7ad493a23a2523bac50571522bbfe5b90a835" [[projects]] - digest = "1:b984f402fbabb0e9eb0476f0ecfa51d0b2ff11cd0ac03538d6284091033b39ae" + digest = "1:7b6ca9454e8d16e34b251f181a4ae430a25805acca64ea46b0448583b44fcfe6" name = "go.opencensus.io" packages = [ ".", @@ -202,8 +193,8 @@ "trace/tracestate", ] pruneopts = "UT" - revision = "59d1ce35d30f3c25ba762169da2a37eab6ffa041" - version = "v0.22.1" + revision = "aad2c527c5defcf89b5afab7f37274304195a6b2" + version = "v0.22.2" [[projects]] branch = "master" @@ -214,27 +205,28 @@ "cmd/apidiff", ] pruneopts = "UT" - revision = "a1ab85dbe136a36c66fbea07de5e3d62a0ce60ad" + revision = "2f50522955873285d9bf17ec91e55aec8ae82edc" [[projects]] branch = "master" - digest = "1:21d7bad9b7da270fd2d50aba8971a041bd691165c95096a2a4c68db823cbc86a" + digest = "1:334b27eac455cb6567ea28cd424230b07b1a64334a2f861a8075ac26ce10af43" name = "golang.org/x/lint" packages = [ ".", "golint", ] pruneopts = "UT" - revision = "16217165b5de779cb6a5e4fc81fa9c1166fda457" + revision = "fdd1cda4f05fd1fd86124f0ef9ce31a0b72c8448" [[projects]] branch = "master" - digest = "1:2ce67db9864088dee35dab5967f041d2b4ae1c7440dabb44542c1847933fd872" + digest = "1:17be6a1681d96acc66795d9618ae4a44405e4a1d5964193cdc5282f2ae151602" name = "golang.org/x/net" packages = [ "context", "context/ctxhttp", "http/httpguts", + "http/httpproxy", "http2", "http2/hpack", "idna", @@ -242,11 +234,11 @@ "trace", ] pruneopts = "UT" - revision = "fe3aa8a4527195a6057b3fad46619d7d090e99b5" + revision = "c0dbc17a35534bf2e581d7a942408dc936316da4" [[projects]] branch = "master" - digest = "1:31e33f76456ccf54819ab4a646cf01271d1a99d7712ab84bf1a9e7b61cd2031b" + digest = "1:e9dec12f847de7d8cd7c1d3d0d89aa40a345a768fd20e10d378a260da9ad65aa" name = "golang.org/x/oauth2" packages = [ ".", @@ -256,15 +248,19 @@ "jwt", ] pruneopts = "UT" - revision = "0f29369cfe4552d0e4bcddc57cc75f4d7e672a33" + revision = "858c2ad4c8b6c5d10852cb89079f6ca1c7309787" [[projects]] branch = "master" - digest = "1:999c8e6b10ddb08bc4ea3a56d4e838194c4884eac6ecf90a9aa98ed8475fdf37" + digest = "1:9cb08334ba1fb31cca05f3703d21189db6f6ba40d2d28c9884bf31707b91d16b" name = "golang.org/x/sys" - packages = ["unix"] + packages = [ + "unix", + "windows", + "windows/registry", + ] pruneopts = "UT" - revision = "f43be2a4598cf3a47be9f94f0c28197ed9eae611" + revision = "ac6580df4449443a05718fd7858c1f91ad5f8d20" [[projects]] digest = "1:8d8faad6b12a3a4c819a3f9618cb6ee1fa1cfc33253abeeea8b55336721e3405" @@ -293,7 +289,7 @@ [[projects]] branch = "master" - digest = "1:6743fddce1cbe389a553362cc7c76f7781a100ca91f8fa977fa1540fa840cb6d" + digest = "1:3ad1f2cdd02ce959277d4e6680d6c89b92f4caeaed3963b2467418a48b810421" name = "golang.org/x/tools" packages = [ "cmd/goimports", @@ -313,20 +309,19 @@ "internal/imports", "internal/module", "internal/semver", - "internal/span", ] pruneopts = "UT" - revision = "f02a19dded36842d1403be03d832b809d7a0cdf8" + revision = "04c2e8eff935f58e9a5568b6b73542f91e0491e6" [[projects]] - digest = "1:021158246495037835846d6e65d1122c1d4a55625350b24b562ab653929916e2" + digest = "1:d1204867de197747eaeedf6eb705968289bce4796c9e9f48ef7c8ef27a227528" name = "google.golang.org/api" packages = [ "googleapi", - "googleapi/internal/uritemplates", "googleapi/transport", "internal", "internal/gensupport", + "internal/third_party/uritemplates", "iterator", "option", "storage/v1", @@ -334,8 +329,8 @@ "transport/http/internal/propagation", ] pruneopts = "UT" - revision = "4f42dad4690a01d7f6fa461106c63889ff1be027" - version = "v0.13.0" + revision = "8a410c21381766a810817fd6200fce8838ecb277" + version = "v0.14.0" [[projects]] digest = "1:3c03b58f57452764a4499c55c582346c0ee78c8a5033affe5bdfd9efd3da5bd1" @@ -358,7 +353,7 @@ [[projects]] branch = "master" - digest = "1:bef0b15c7587f13aed86ea55e067b863837c18d0a2a47410ee85c3c5008686b3" + digest = "1:c21fbca8f5af1be04e383c4c57be0b85abeea2fe370a54dc805b1b04e443c9e9" name = "google.golang.org/genproto" packages = [ "googleapis/api/annotations", @@ -368,13 +363,14 @@ "googleapis/type/expr", ] pruneopts = "UT" - revision = "919d9bdd9fe6f1a5dd95ce5d5e4cdb8fd3c516d0" + revision = "0243a4be9c8f1264d238fdc2895620b4d9baf9e1" [[projects]] - digest = "1:6cd77d0b616d2dcebd363dfecba593f27b0151fc82cdb5fbfb96c5a7cfbc95b5" + digest = "1:b59ce3ddb11daeeccccc9cb3183b58ebf8e9a779f1c853308cd91612e817a301" name = "google.golang.org/grpc" packages = [ ".", + "backoff", "balancer", "balancer/base", "balancer/roundrobin", @@ -390,10 +386,13 @@ "internal/backoff", "internal/balancerload", "internal/binarylog", + "internal/buffer", "internal/channelz", "internal/envconfig", "internal/grpcrand", "internal/grpcsync", + "internal/resolver/dns", + "internal/resolver/passthrough", "internal/syscall", "internal/transport", "keepalive", @@ -401,24 +400,22 @@ "naming", "peer", "resolver", - "resolver/dns", - "resolver/passthrough", "serviceconfig", "stats", "status", "tap", ] pruneopts = "UT" - revision = "f6d0f9ee430895e87ef1ceb5ac8f39725bafceef" - version = "v1.24.0" + revision = "1a3960e4bd028ac0cec0a2afd27d7d8e67c11514" + version = "v1.25.1" [[projects]] - digest = "1:59f10c1537d2199d9115d946927fe31165959a95190849c82ff11e05803528b0" + digest = "1:b75b3deb2bce8bc079e16bb2aecfe01eb80098f5650f9e93e5643ca8b7b73737" name = "gopkg.in/yaml.v2" packages = ["."] pruneopts = "UT" - revision = "f221b8435cfb71e54062f6c6e99e9ade30b124d5" - version = "v2.2.4" + revision = "1f64d6156d11335c3f22d9330b0ad14fc1e789ce" + version = "v2.2.7" [[projects]] digest = "1:131158a88aad1f94854d0aa21a64af2802d0a470fb0f01cb33c04fafd2047111" @@ -462,8 +459,6 @@ "github.com/Azure/azure-pipeline-go/pipeline", "github.com/Azure/azure-storage-blob-go/azblob", "github.com/BurntSushi/toml", - "github.com/Praqma/helmsman/aws", - "github.com/Praqma/helmsman/azure", "github.com/apsdehal/go-logger", "github.com/aws/aws-sdk-go/aws", "github.com/aws/aws-sdk-go/aws/session", diff --git a/Gopkg.toml b/Gopkg.toml index 3c3e852b..c11b6e78 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -24,6 +24,8 @@ # go-tests = true # unused-packages = true +ignored = ["github.com/Praqma/helmsman"] + [[constraint]] version = "0.3.0" name = "github.com/Azure/azure-storage-blob-go" diff --git a/Makefile b/Makefile index 980d4e9b..0b12e553 100644 --- a/Makefile +++ b/Makefile @@ -55,7 +55,7 @@ clean: ## Remove build artifacts .PHONY: clean fmt: ## Reformat package sources - @go fmt + @go fmt ./... .PHONY: fmt dependencies: ## Ensure all the necessary dependencies @@ -73,12 +73,12 @@ dep: $(SRCDIR) ## Ensure vendors with dep dep-update: $(SRCDIR) ## Ensure vendors with dep @cd $(PRJDIR) && \ - dep ensure --update + dep ensure -v --update .PHONY: dep-update build: dep ## Build the package @cd $(PRJDIR) && \ - go build -ldflags '-X main.version="${TAG}-${DATE}" -extldflags "-static"' + go build -o helmsman -ldflags '-X main.version="${TAG}-${DATE}" -extldflags "-static"' cmd/helmsman/main.go generate: @go generate #${PKGS} @@ -87,7 +87,7 @@ generate: check: $(SRCDIR) fmt @cd $(PRJDIR) && \ dep check && \ - go vet #${PKGS} + go vet ./... .PHONY: check repo: @@ -96,8 +96,7 @@ repo: .PHONY: repo test: dep check repo ## Run unit tests - @cd $(PRJDIR) && \ - go test -v -cover -p=1 -args -f example.toml + @go test -v -cover -p=1 ./... -args -f ../../examples/example.toml .PHONY: test cross: dep ## Create binaries for all OSs diff --git a/cmd/helmsman/main.go b/cmd/helmsman/main.go new file mode 100644 index 00000000..41b75880 --- /dev/null +++ b/cmd/helmsman/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "github.com/Praqma/helmsman/internal/app" +) + +func main() { + app.Main() +} diff --git a/example.toml b/examples/example.toml similarity index 100% rename from example.toml rename to examples/example.toml diff --git a/example.yaml b/examples/example.yaml similarity index 100% rename from example.yaml rename to examples/example.yaml diff --git a/minimal-example.toml b/examples/minimal-example.toml similarity index 100% rename from minimal-example.toml rename to examples/minimal-example.toml diff --git a/minimal-example.yaml b/examples/minimal-example.yaml similarity index 100% rename from minimal-example.yaml rename to examples/minimal-example.yaml diff --git a/bindata.go b/internal/app/bindata.go similarity index 99% rename from bindata.go rename to internal/app/bindata.go index cf996020..b88bf261 100644 --- a/bindata.go +++ b/internal/app/bindata.go @@ -3,7 +3,7 @@ // data/role.yaml // DO NOT EDIT! -package main +package app import ( "bytes" diff --git a/init.go b/internal/app/cli.go similarity index 66% rename from init.go rename to internal/app/cli.go index 6261145b..bd9e5935 100644 --- a/init.go +++ b/internal/app/cli.go @@ -1,20 +1,17 @@ -package main +package app import ( "flag" "fmt" - "github.com/apsdehal/go-logger" - "os" - "strings" - "github.com/imdario/mergo" "github.com/joho/godotenv" + "os" + "strings" ) -var logs *logger.Logger - const ( - banner = " _ _ \n" + + banner = "\n" + + " _ _ \n" + "| | | | \n" + "| |__ ___| |_ __ ___ ___ _ __ ___ __ _ _ __\n" + "| '_ \\ / _ \\ | '_ ` _ \\/ __| '_ ` _ \\ / _` | '_ \\ \n" + @@ -24,17 +21,15 @@ const ( ) func printUsage() { - logs.Info(banner + "\n") - logs.Info("Helmsman version: " + appVersion) - logs.Info("Helmsman is a Helm Charts as Code tool which allows you to automate the deployment/management of your Helm charts.") - logs.Info("") - logs.Info("Usage: helmsman [options]") + log.Info(banner + "\n") + log.Info("Helmsman version: " + appVersion) + log.Info("Helmsman is a Helm Charts as Code tool which allows you to automate the deployment/management of your Helm charts.") + log.Info("") + log.Info("Usage: helmsman [options]") flag.PrintDefaults() } -// init is executed after all package vars are initialized [before the main() func in this case]. -// It checks if Helm and Kubectl exist and configures: the connection to the k8s cluster, helm repos, namespaces, etc. -func init() { +func Cli() { //parsing command line flags flag.Var(&files, "f", "desired state file name(s), may be supplied more than once to merge state files") flag.Var(&envFiles, "e", "file(s) to load environment variables from (default .env), may be supplied more than once") @@ -65,53 +60,40 @@ func init() { flag.BoolVar(&updateDeps, "update-deps", false, "run 'helm dep up' for local chart") flag.BoolVar(&forceUpgrades, "force-upgrades", false, "use --force when upgrading helm releases. May cause resources to be recreated.") flag.BoolVar(&noDefaultRepos, "no-default-repos", false, "don't set default Helm repos from Google for 'stable' and 'incubator'") - flag.Usage = printUsage flag.Parse() - logger.SetDefaultFormat("%{time:2006-01-02 15:04:05} %{level}: %{message}") - var logLevel = logger.InfoLevel - if verbose { - logLevel = logger.DebugLevel - } - if noFancy { noColors = true noBanner = true } - - var logColors = 1 - if noColors { - logColors = 0 - } - logs, _ = logger.New("logger", logColors, os.Stdout, logLevel) + initLogs(verbose, noColors) if !noBanner { - fmt.Println(banner + " version: " + appVersion + "\n" + slogan) + log.Info(fmt.Sprintf("%s version: %s\n%s", banner, appVersion, slogan)) } if dryRun && apply { - logError("--apply and --dry-run can't be used together.") + log.Fatal("--apply and --dry-run can't be used together.") } if destroy && apply { - logError("--destroy and --apply can't be used together.") + log.Fatal("--destroy and --apply can't be used together.") } if len(target) > 0 && len(group) > 0 { - logError("--target and --group can't be used together.") + log.Fatal("--target and --group can't be used together.") } if (settings.EyamlPrivateKeyPath != "" && settings.EyamlPublicKeyPath == "") || (settings.EyamlPrivateKeyPath == "" && settings.EyamlPublicKeyPath != "") { - logError("both EyamlPrivateKeyPath and EyamlPublicKeyPath are required") + log.Fatal("both EyamlPrivateKeyPath and EyamlPublicKeyPath are required") } helmVersion = strings.TrimSpace(getHelmVersion()) - kubectlVersion = strings.TrimSpace(strings.SplitN(getKubectlClientVersion(), ": ", 2)[1]) + log.Verbose("Helm client version: " + helmVersion) - if verbose { - logVersions() - } + kubectlVersion = strings.TrimSpace(strings.SplitN(getKubectlClientVersion(), ": ", 2)[1]) + log.Verbose("kubectl client version: " + kubectlVersion) if v { fmt.Println("Helmsman version: " + appVersion) @@ -119,7 +101,7 @@ func init() { } if len(files) == 0 { - logs.Info("No desired state files provided.") + log.Info("No desired state files provided.") os.Exit(0) } @@ -128,15 +110,15 @@ func init() { } if !toolExists("kubectl") { - logError("kubectl is not installed/configured correctly. Aborting!") + log.Fatal("kubectl is not installed/configured correctly. Aborting!") } if !toolExists(helmBin) { - logError("" + helmBin + " is not installed/configured correctly. Aborting!") + log.Fatal("" + helmBin + " is not installed/configured correctly. Aborting!") } if !helmPluginExists("diff") { - logError("helm diff plugin is not installed/configured correctly. Aborting!") + log.Fatal("helm diff plugin is not installed/configured correctly. Aborting!") } // read the env file @@ -144,7 +126,7 @@ func init() { if _, err := os.Stat(".env"); err == nil { err = godotenv.Load() if err != nil { - logError("Error loading .env file") + log.Fatal("Error loading .env file") } } } @@ -152,7 +134,7 @@ func init() { for _, e := range envFiles { err := godotenv.Load(e) if err != nil { - logError("Error loading " + e + " env file") + log.Fatal("Error loading " + e + " env file") } } @@ -165,28 +147,28 @@ func init() { for _, f := range files { result, msg := fromFile(f, &fileState) if result { - logs.Info(msg) + log.Info(msg) } else { - logError(msg) + log.Fatal(msg) } // Merge Apps that already existed in the state for appName, app := range fileState.Apps { if _, ok := s.Apps[appName]; ok { if err := mergo.Merge(s.Apps[appName], app, mergo.WithAppendSlice, mergo.WithOverride); err != nil { - logError("Failed to merge " + appName + " from desired state file" + f) + log.Fatal("Failed to merge " + appName + " from desired state file" + f) } } } // Merge the remaining Apps if err := mergo.Merge(&s.Apps, &fileState.Apps); err != nil { - logError("Failed to merge desired state file" + f) + log.Fatal("Failed to merge desired state file" + f) } // All the apps are already merged, make fileState.Apps empty to avoid conflicts in the final merge fileState.Apps = make(map[string]*release) if err := mergo.Merge(&s, &fileState, mergo.WithAppendSlice, mergo.WithOverride); err != nil { - logError("Failed to merge desired state file" + f) + log.Fatal("Failed to merge desired state file" + f) } } @@ -198,11 +180,11 @@ func init() { // validate the desired state content if len(files) > 0 { if err := s.validate(); err != nil { // syntax validation - logs.Error(err.Error()) + log.Error(err.Error()) } } } else { - logs.Info("Desired state validation is skipped.") + log.Info("Desired state validation is skipped.") } if applyLabels { @@ -224,41 +206,4 @@ func init() { groupMap[v] = true } } - -} - -// toolExists returns true if the tool is present in the environment and false otherwise. -// It takes as input the tool's command to check if it is recognizable or not. e.g. helm or kubectl -func toolExists(tool string) bool { - cmd := command{ - Cmd: tool, - Args: []string{}, - Description: "validating that " + tool + " is installed.", - } - - exitCode, _, _ := cmd.exec(debug, false) - - if exitCode != 0 { - return false - } - - return true -} - -// helmPluginExists returns true if the plugin is present in the environment and false otherwise. -// It takes as input the plugin's name to check if it is recognizable or not. e.g. diff -func helmPluginExists(plugin string) bool { - cmd := command{ - Cmd: helmBin, - Args: []string{"plugin", "list"}, - Description: "validating that " + plugin + " is installed.", - } - - exitCode, result, _ := cmd.exec(debug, false) - - if exitCode != 0 { - return false - } - - return strings.Contains(result, plugin) } diff --git a/init_test.go b/internal/app/cli_test.go similarity index 99% rename from init_test.go rename to internal/app/cli_test.go index 48b2bf03..fddbdec4 100644 --- a/init_test.go +++ b/internal/app/cli_test.go @@ -1,4 +1,4 @@ -package main +package app import "testing" diff --git a/command.go b/internal/app/command.go similarity index 62% rename from command.go rename to internal/app/command.go index 41651442..e15cbe16 100644 --- a/command.go +++ b/internal/app/command.go @@ -1,4 +1,4 @@ -package main +package app import ( "bytes" @@ -16,21 +16,8 @@ type command struct { Description string } -//printDescription prints the description of a command -func (c command) printDescription() { - - fmt.Println(c.Description) -} - -// printFullCommand prints the executable command. -func (c command) printFullCommand() { - - fmt.Println(c.Description, " by running : \"", c.Cmd, " ", c.Args, " \"") -} - // exec executes the executable command and returns the exit code and execution result func (c command) exec(debug bool, verbose bool) (int, string, string) { - // Only use non-empty string args args := []string{} for _, str := range c.Args { @@ -39,20 +26,18 @@ func (c command) exec(debug bool, verbose bool) (int, string, string) { } } + log.Verbose(c.Description) + if debug { - logs.Debug("" + c.Description) - } - if verbose { - logs.Debug(c.Cmd + " " + strings.Join(args, " ")) + log.Debug(fmt.Sprintf("%s %s", c.Cmd, strings.Join(args, " "))) } - cmd := exec.Command(c.Cmd, args...) var stdout, stderr bytes.Buffer cmd.Stdout = &stdout cmd.Stderr = &stderr if err := cmd.Start(); err != nil { - logs.Info("cmd.Start: " + err.Error()) + log.Info("cmd.Start: " + err.Error()) return 1, err.Error(), "" } @@ -62,8 +47,26 @@ func (c command) exec(debug bool, verbose bool) (int, string, string) { return status.ExitStatus(), stderr.String(), "" } } else { - logError("cmd.Wait: " + err.Error()) + log.Fatal("cmd.Wait: " + err.Error()) } } return 0, stdout.String(), stderr.String() } + +// toolExists returns true if the tool is present in the environment and false otherwise. +// It takes as input the tool's command to check if it is recognizable or not. e.g. helm or kubectl +func toolExists(tool string) bool { + cmd := command{ + Cmd: tool, + Args: []string{}, + Description: "Validating that [ " + tool + " ] is installed", + } + + exitCode, _, _ := cmd.exec(debug, false) + + if exitCode != 0 { + return false + } + + return true +} diff --git a/command_test.go b/internal/app/command_test.go similarity index 99% rename from command_test.go rename to internal/app/command_test.go index fa68315b..0820d90d 100644 --- a/command_test.go +++ b/internal/app/command_test.go @@ -1,4 +1,4 @@ -package main +package app import ( "strings" diff --git a/decision_maker.go b/internal/app/decision_maker.go similarity index 77% rename from decision_maker.go rename to internal/app/decision_maker.go index 14be4181..c000aae5 100644 --- a/decision_maker.go +++ b/internal/app/decision_maker.go @@ -1,4 +1,4 @@ -package main +package app import ( "fmt" @@ -29,7 +29,7 @@ func makePlan(s *state) *plan { func decide(r *release, s *state) { // check for presence in defined targets or groups if !r.isReleaseConsideredToRun() { - logDecision("release [ "+r.Name+" ] is ignored due to passed constraints. Skipping.", r.Priority, ignored) + logDecision("Release [ "+r.Name+" ] ignored", r.Priority, ignored) return } @@ -45,14 +45,14 @@ func decide(r *release, s *state) { if isProtected(r) { - logDecision("release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ + logDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ "protection is removed.", r.Priority, noop) return } deleteRelease(r) return } - logDecision("release [ "+r.Name+" ] is disabled. Skipping.", r.Priority, noop) + logDecision("Release [ "+r.Name+" ] disabled", r.Priority, noop) return } else { @@ -71,7 +71,7 @@ func decide(r *release, s *state) { rollbackRelease(r) // rollback } else { - logDecision("release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ + logDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ "you remove its protection.", r.Priority, noop) } @@ -79,11 +79,11 @@ func decide(r *release, s *state) { if !isProtected(r) { - logDecision("release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is in FAILED state. Upgrade is scheduled!", r.Priority, change) + logDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is in FAILED state. Upgrade is scheduled!", r.Priority, change) upgradeRelease(r) } else { - logDecision("release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ + logDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ "you remove its protection.", r.Priority, noop) } } else { @@ -101,10 +101,10 @@ func testRelease(r *release) { cmd := command{ Cmd: helmBin, Args: []string{"test", "--namespace", r.Namespace, r.Name}, - Description: "running tests for release [ "+r.Name+" ] in namespace [[ "+r.Namespace+" ]]", + Description: "Running tests for release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ]", } outcome.addCommand(cmd, r.Priority, r) - logDecision("release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is required to be tested when installed. Got it!", r.Priority, noop) + logDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is required to be tested during installation", r.Priority, noop) } // installRelease creates a Helm command to install a particular release in a particular namespace using a particular Tiller. @@ -112,10 +112,10 @@ func installRelease(r *release) { cmd := command{ Cmd: helmBin, Args: concat([]string{"install", r.Name, r.Chart, "--namespace", r.Namespace}, getValuesFiles(r), []string{"--version", r.Version}, getSetValues(r), getSetStringValues(r), getWait(r), getHelmFlags(r)), - Description: "installing release [ "+r.Name+" ] in namespace [[ "+r.Namespace+" ]]", + Description: "Installing release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ]", } outcome.addCommand(cmd, r.Priority, r) - logDecision("release [ "+r.Name+" ] is not installed. Will install it in namespace [[ "+r.Namespace+" ]]", r.Priority, create) + logDecision("Release [ "+r.Name+" ] will be installed in [ "+r.Namespace+" ] namespace", r.Priority, create) if r.Test { testRelease(r) @@ -136,18 +136,18 @@ func rollbackRelease(r *release) { cmd := command{ Cmd: helmBin, Args: concat([]string{"rollback", r.Name, getReleaseRevision(rs)}, getWait(r), getTimeout(r), getNoHooks(r), getDryRunFlags()), - Description: "rolling back release [ "+r.Name+" ] in namespace [[ "+r.Namespace+" ]]", + Description: "Rolling back release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ]", } outcome.addCommand(cmd, r.Priority, r) upgradeRelease(r) // this is to reflect any changes in values file(s) - logDecision("release [ "+r.Name+" ] is currently deleted and is desired to be rolledback to "+ - "namespace [[ "+r.Namespace+" ]] . It will also be upgraded in case values have changed.", r.Priority, create) + logDecision("Release [ "+r.Name+" ] was deleted and is desired to be rolled back to "+ + "namespace [ "+r.Namespace+" ]", r.Priority, create) } else { reInstallRelease(r, rs) - logDecision("release [ "+r.Name+" ] is deleted BUT from namespace [[ "+rs.Namespace+ - " ]]. Will purge delete it from there and install it in namespace [[ "+r.Namespace+" ]]", r.Priority, create) - logDecision("WARNING: rolling back release [ "+r.Name+" ] from [[ "+rs.Namespace+" ]] to [[ "+r.Namespace+ - " ]] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/apps/moving_across_namespaces.md"+ + logDecision("Release [ "+r.Name+" ] is deleted BUT from namespace [ "+rs.Namespace+ + " ]. Will purge delete it from there and install it in namespace [ "+r.Namespace+" ]", r.Priority, create) + logDecision("WARNING: rolling back release [ "+r.Name+" ] from [ "+rs.Namespace+" ] to [ "+r.Namespace+ + " ] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/apps/moving_across_namespaces.md"+ " for details if this release uses PV and PVC.", r.Priority, create) } @@ -162,8 +162,8 @@ func deleteRelease(r *release) { cmd := command{ Cmd: helmBin, - Args: concat([]string{"delete", "--namespace", r.Namespace, r.Name}, getDryRunFlags()), - Description: "deleting release [ "+r.Name+" ] in namespace [[ "+r.Namespace+" ]]", + Args: concat([]string{"uninstall", "--namespace", r.Namespace, r.Name}, getDryRunFlags()), + Description: "Deleting release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ]", } outcome.addCommand(cmd, priority, r) logDecision(fmt.Sprintf("release [ %s ] is desired to be DELETED.", r.Name), r.Priority, delete) @@ -187,7 +187,7 @@ func inspectUpgradeScenario(r *release) { version, msg := getChartVersion(r) if msg != "" { - logError(msg) + log.Fatal(msg) return } r.Version = version @@ -196,29 +196,28 @@ func inspectUpgradeScenario(r *release) { // upgrade diffRelease(r) upgradeRelease(r) - logDecision("release [ "+r.Name+" ] will be upgraded.", r.Priority, change) + logDecision("Release [ "+r.Name+" ] will be updated", r.Priority, change) } else if extractChartName(r.Chart) != getReleaseChartName(rs) { reInstallRelease(r, rs) - logDecision("release [ "+r.Name+" ] is desired to use a new Chart [ "+r.Chart+ - " ]. Delete of the current release will be planned and new chart will be installed in namespace [[ "+ - r.Namespace+" ]]", r.Priority, change) + logDecision("Release [ "+r.Name+" ] is desired to use a new chart [ "+r.Chart+ + " ]. Delete of the current release will be planned and new chart will be installed in namespace [ "+ + r.Namespace+" ]", r.Priority, change) } else { if diff := diffRelease(r); diff != "" { upgradeRelease(r) - logDecision("release [ "+r.Name+" ] is installed and has some changes to apply. "+ - "I will upgrade it!", r.Priority, change) + logDecision("Release [ "+r.Name+" ] will be updated", r.Priority, change) } else { - logDecision("release [ "+r.Name+" ] is enabled and has no changes to apply.", r.Priority, noop) + logDecision("Release [ "+r.Name+" ] installed and up-to-date", r.Priority, noop) } } } else { reInstallRelease(r, rs) - logDecision("release [ "+r.Name+" ] is desired to be enabled in a new namespace [[ "+r.Namespace+ - " ]]. I am planning a purge delete of the current release from namespace [[ "+rs.Namespace+" ]] "+ - "and will install it for you in namespace [[ "+r.Namespace+" ]]", r.Priority, change) - logDecision("WARNING: moving release [ "+r.Name+" ] from [[ "+rs.Namespace+" ]] to [[ "+r.Namespace+ - " ]] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md"+ + logDecision("Release [ "+r.Name+" ] is desired to be enabled in a new namespace [ "+r.Namespace+ + " ]. Uninstall of the current release from namespace [ "+rs.Namespace+" ] will be performed "+ + "and then installation in namespace [ "+r.Namespace+" ] will take place", r.Priority, change) + logDecision("WARNING: moving release [ "+r.Name+" ] from [ "+rs.Namespace+" ] to [ "+r.Namespace+ + " ] might not correctly connect existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md"+ " for details if this release uses PV and PVC.", r.Priority, change) } } @@ -243,11 +242,11 @@ func diffRelease(r *release) string { cmd := command{ Cmd: helmBin, Args: concat([]string{"diff", colorFlag}, diffContextFlag, []string{suppressDiffSecretsFlag, "--namespace", r.Namespace, "upgrade", r.Name, r.Chart}, getValuesFiles(r), []string{"--version", r.Version}, getSetValues(r), getSetStringValues(r)), - Description: "diffing release [ "+r.Name+" ] in namespace [[ "+r.Namespace+" ]]", + Description: "Diffing release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ]", } if exitCode, msg, _ = cmd.exec(debug, verbose); exitCode != 0 { - logError(fmt.Sprintf("Command returned with exit code: %d. And error message: %s ", exitCode, msg)) + log.Fatal(fmt.Sprintf("Command returned with exit code: %d. And error message: %s ", exitCode, msg)) } else { if (verbose || showDiff) && msg != "" { fmt.Println(msg) @@ -266,7 +265,7 @@ func upgradeRelease(r *release) { cmd := command{ Cmd: helmBin, Args: concat([]string{"upgrade", "--namespace", r.Namespace, r.Name, r.Chart}, getValuesFiles(r), []string{"--version", r.Version, force}, getSetValues(r), getSetStringValues(r), getWait(r), getHelmFlags(r)), - Description: "upgrading release [ "+r.Name+" ] in namespace [[ "+r.Namespace+" ]]", + Description: "Upgrading release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ]", } outcome.addCommand(cmd, r.Priority, r) @@ -279,14 +278,14 @@ func reInstallRelease(r *release, rs releaseState) { delCmd := command{ Cmd: helmBin, Args: concat([]string{"delete", "--purge", r.Name}, getDryRunFlags()), - Description: "deleting release [ "+r.Name+" ] in namespace [[ "+r.Namespace+" ]]", + Description: "Deleting release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ]", } outcome.addCommand(delCmd, r.Priority, r) installCmd := command{ Cmd: helmBin, Args: concat([]string{"install", r.Chart, "--version", r.Version, "-n", r.Name, "--namespace", r.Namespace}, getValuesFiles(r), getSetValues(r), getSetStringValues(r), getWait(r), getHelmFlags(r)), - Description: "installing release [ "+r.Name+" ] in namespace [[ "+r.Namespace+" ]]", + Description: "Installing release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ]", } outcome.addCommand(installCmd, r.Priority, r) } @@ -341,19 +340,19 @@ func getValuesFiles(r *release) []string { if r.SecretsFile != "" { if !helmPluginExists("secrets") { - logError("helm secrets plugin is not installed/configured correctly. Aborting!") + log.Fatal("helm secrets plugin is not installed/configured correctly. Aborting!") } if ok := decryptSecret(r.SecretsFile); !ok { - logError("Failed to decrypt secret file" + r.SecretsFile) + log.Fatal("Failed to decrypt secret file" + r.SecretsFile) } fileList = append(fileList, r.SecretsFile+".dec") } else if len(r.SecretsFiles) > 0 { if !helmPluginExists("secrets") { - logError("helm secrets plugin is not installed/configured correctly. Aborting!") + log.Fatal("helm secrets plugin is not installed/configured correctly. Aborting!") } for i := 0; i < len(r.SecretsFiles); i++ { if ok := decryptSecret(r.SecretsFiles[i]); !ok { - logError("Failed to decrypt secret file" + r.SecretsFiles[i]) + log.Fatal("Failed to decrypt secret file" + r.SecretsFiles[i]) } // if .dec extension is added before to the secret filename, don't add it again. // This happens at upgrade time (where diff and upgrade both call this function) @@ -452,7 +451,7 @@ func getHelmFlags(r *release) []string { func checkChartDepUpdate(r *release) { if updateDeps && isLocalChart(r.Chart) { if ok, err := updateChartDep(r.Chart); !ok { - logError("helm dependency update failed: " + err) + log.Fatal("helm dependency update failed: " + err) } } } diff --git a/decision_maker_test.go b/internal/app/decision_maker_test.go similarity index 93% rename from decision_maker_test.go rename to internal/app/decision_maker_test.go index 2503a3db..31017dff 100644 --- a/decision_maker_test.go +++ b/internal/app/decision_maker_test.go @@ -1,4 +1,4 @@ -package main +package app import ( "reflect" @@ -24,13 +24,13 @@ func Test_getValuesFiles(t *testing.T) { Enabled: true, Chart: "repo/chartX", Version: "1.0", - ValuesFile: "test_files/values.yaml", + ValuesFile: "../../tests/values.yaml", Purge: true, Test: true, }, //s: st, }, - want: []string{"-f", "test_files/values.yaml"}, + want: []string{"-f", "../../tests/values.yaml"}, }, { name: "test case 2", @@ -42,13 +42,13 @@ func Test_getValuesFiles(t *testing.T) { Enabled: true, Chart: "repo/chartX", Version: "1.0", - ValuesFiles: []string{"test_files/values.yaml"}, + ValuesFiles: []string{"../../tests/values.yaml"}, Purge: true, Test: true, }, //s: st, }, - want: []string{"-f", "test_files/values.yaml"}, + want: []string{"-f", "../../tests/values.yaml"}, }, { name: "test case 1", @@ -60,13 +60,13 @@ func Test_getValuesFiles(t *testing.T) { Enabled: true, Chart: "repo/chartX", Version: "1.0", - ValuesFiles: []string{"test_files/values.yaml", "test_files/values2.yaml"}, + ValuesFiles: []string{"../../tests/values.yaml", "../../tests/values2.yaml"}, Purge: true, Test: true, }, //s: st, }, - want: []string{"-f", "test_files/values.yaml", "-f", "test_files/values2.yaml"}, + want: []string{"-f", "../../tests/values.yaml", "-f", "../../tests/values2.yaml"}, }, } for _, tt := range tests { @@ -95,7 +95,7 @@ func Test_inspectUpgradeScenario(t *testing.T) { Name: "release1", Namespace: "namespace", Version: "1.0.0", - Chart: "./test_files/chart-test", + Chart: "./../../tests/chart-test", Enabled: true, }, s: &map[string]releaseState{ diff --git a/helm_helpers.go b/internal/app/helm_helpers.go similarity index 72% rename from helm_helpers.go rename to internal/app/helm_helpers.go index 3ee936aa..275845e7 100644 --- a/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -1,9 +1,8 @@ -package main +package app import ( "encoding/json" "fmt" - "log" "net/url" "os" "path/filepath" @@ -12,35 +11,35 @@ import ( "strings" "time" - "helmsman/gcs" + "github.com/Praqma/helmsman/internal/gcs" ) var currentState map[string]releaseState // releaseState represents the current state of a release type releaseState struct { - Revision int - Updated time.Time - Status string - Chart string - Namespace string + Revision int + Updated time.Time + Status string + Chart string + Namespace string } type releaseInfo struct { - Name string `json:"Name"` - Namespace string `json:"Namespace"` - Revision string `json:"Revision"` - Updated string `json:"Updated"` - Status string `json:"Status"` - Chart string `json:"Chart"` - AppVersion string `json:"AppVersion,omitempty"` + Name string `json:"Name"` + Namespace string `json:"Namespace"` + Revision string `json:"Revision"` + Updated string `json:"Updated"` + Status string `json:"Status"` + Chart string `json:"Chart"` + AppVersion string `json:"AppVersion,omitempty"` } type chartVersion struct { - Name string `json:"name"` - Version string `json:"version"` - AppVersion string `json:"app_version"` - Description string `json:"description"` + Name string `json:"name"` + Version string `json:"version"` + AppVersion string `json:"app_version"` + Description string `json:"description"` } // getHelmClientVersion returns Helm client Version @@ -48,12 +47,12 @@ func getHelmVersion() string { cmd := command{ Cmd: helmBin, Args: []string{"version", "--short"}, - Description: "checking Helm version ", + Description: "Checking Helm version", } exitCode, result, _ := cmd.exec(debug, false) if exitCode != 0 { - logError("while checking helm version: " + result) + log.Fatal("While checking helm version: " + result) } return result } @@ -64,21 +63,21 @@ func getHelmReleases() []releaseInfo { cmd := command{ Cmd: helmBin, Args: []string{"list", "--all", "--max", "0", "--output", "json", "--all-namespaces"}, - Description: "listing all existing releases...", + Description: "Listing all existing releases...", } exitCode, result, _ := cmd.exec(debug, verbose) if exitCode != 0 { - logError("failed to list all releases: " + result) + log.Fatal("Failed to list all releases: " + result) } if err := json.Unmarshal([]byte(result), &allReleases); err != nil { - logError(fmt.Sprintf("failed to unmarshal Helm CLI output: %s", err)) + log.Fatal(fmt.Sprintf("failed to unmarshal Helm CLI output: %s", err)) } return allReleases } // buildState builds the currentState map containing information about all releases existing in a k8s cluster func buildState() { - logs.Info("Acquiring current Helm state from cluster...") + log.Info("Acquiring current Helm state from cluster...") currentState = make(map[string]releaseState) rel := getHelmReleases() @@ -94,15 +93,15 @@ func buildState() { date, err := time.Parse("2006-01-02 15:04:05.000000000 -0700 MST", fmt.Sprintf("%s %s.%s %s %s", updatedFields[0], updatedHour[0], milliseconds, updatedFields[2], updatedFields[3])) if err != nil { - logError("while converting release time: " + err.Error()) + log.Fatal("While converting release time: " + err.Error()) } revision, _ := strconv.Atoi(rel[i].Revision) currentState[fmt.Sprintf("%s-%s", rel[i].Name, rel[i].Namespace)] = releaseState{ - Revision: revision, - Updated: date, - Status: rel[i].Status, - Chart: rel[i].Chart, - Namespace: rel[i].Namespace, + Revision: revision, + Updated: date, + Status: rel[i].Status, + Chart: rel[i].Chart, + Namespace: rel[i].Namespace, } } } @@ -170,21 +169,21 @@ func validateReleaseCharts(apps map[string]*release) (bool, string) { cmd := command{ Cmd: helmBin, Args: []string{"inspect", "chart", r.Chart}, - Description: "validating if chart at " + r.Chart + " is available.", + Description: "Validating [ " + r.Chart + " ] chart's availability", } var output string var exitCode int if exitCode, output, _ = cmd.exec(debug, verbose); exitCode != 0 { maybeRepo := filepath.Base(filepath.Dir(r.Chart)) - return false, "chart at " + r.Chart + " for app [" + app + "] could not be found. Did you mean to add a repo named '" + maybeRepo + "'?" + return false, "Chart [ " + r.Chart + " ] for app [" + app + "] can't be found. Did you mean to add a repo [ " + maybeRepo + " ]?" } matches := versionExtractor.FindStringSubmatch(output) if len(matches) == 2 { version := matches[1] if r.Version != version { - return false, "chart " + r.Chart + " with version " + r.Version + " is specified for " + - "app [" + app + "] but the chart found at that path has version " + version + " which does not match." + return false, "Chart [ " + r.Chart + " ] with version [ " + r.Version + " ] is specified for " + + "app [" + app + "] but the chart found at that path has version [ " + version + " ] which does not match." } } @@ -196,12 +195,12 @@ func validateReleaseCharts(apps map[string]*release) (bool, string) { cmd := command{ Cmd: helmBin, Args: []string{"search", "repo", r.Chart, "--version", version, "-l"}, - Description: "validating if chart " + r.Chart + " with version " + r.Version + " is available in the defined repos.", + Description: "Validating [ " + r.Chart + " ] chart's version [ " + r.Version + " ] availability", } if exitCode, result, _ := cmd.exec(debug, verbose); exitCode != 0 || strings.Contains(result, "No results found") { - return false, "chart " + r.Chart + " with version " + r.Version + " is specified for " + - "app [" + app + "] but is not found in the defined repos." + return false, "Chart [ " + r.Chart + " ] with version [ " + r.Version + " ] is specified for " + + "app [" + app + "] but was not found" } } } @@ -218,7 +217,7 @@ func getChartVersion(r *release) (string, string) { cmd := command{ Cmd: helmBin, Args: []string{"search", "repo", r.Chart, "--version", r.Version, "-o", "json"}, - Description: "getting latest chart version " + r.Chart + "-" + r.Version + "", + Description: "Getting latest chart's version " + r.Chart + "-" + r.Version + "", } var ( @@ -227,23 +226,22 @@ func getChartVersion(r *release) (string, string) { ) if exitCode, result, _ = cmd.exec(debug, verbose); exitCode != 0 { - return "", "chart " + r.Chart + " with version " + r.Version + " is specified but not found in the helm repos." + return "", "Chart [ " + r.Chart + " ] with version [ " + r.Version + " ] is specified but not found in the helm repositories" } chartVersions := make([]chartVersion, 0) if err := json.Unmarshal([]byte(result), &chartVersions); err != nil { - logs.Fatal(fmt.Sprint(err)) + log.Fatal(fmt.Sprint(err)) } if len(chartVersions) < 1 { - return "", "chart " + r.Chart + " with version " + r.Version + " is specified but not found in the helm repos." + return "", "Chart [ " + r.Chart + " ] with version [ " + r.Version + " ] is specified but not found in the helm repositories" } else if len(chartVersions) > 1 { - return "", "multiple versions of chart " + r.Chart + " with version " + r.Version + " found in the helm repos." + return "", "Multiple versions of chart [ " + r.Chart + " ] with version [ " + r.Version + " ] found in the helm repositories" } return chartVersions[0].Version, "" } - // addHelmRepos adds repositories to Helm if they don't exist already. // Helm does not mind if a repo with the same name exists. It treats it as an update. func addHelmRepos(repos map[string]string) (bool, string) { @@ -261,12 +259,12 @@ func addHelmRepos(repos map[string]string) (bool, string) { u, err := url.Parse(repoLink) if err != nil { - logError("failed to add helm repo: " + err.Error()) + log.Fatal("failed to add helm repo: " + err.Error()) } if u.User != nil { p, ok := u.User.Password() if !ok { - logError("helm repo " + repoName + " has incomplete basic auth info. Missing the password!") + log.Fatal("helm repo " + repoName + " has incomplete basic auth info. Missing the password!") } basicAuthArgs = append(basicAuthArgs, "--username", u.User.Username(), "--password", p) @@ -275,11 +273,11 @@ func addHelmRepos(repos map[string]string) (bool, string) { cmd := command{ Cmd: helmBin, Args: concat([]string{"repo", "add", repoName, repoLink}, basicAuthArgs), - Description: "adding repo " + repoName, + Description: "Adding helm repository [ " + repoName + " ]", } if exitCode, err, _ := cmd.exec(debug, verbose); exitCode != 0 { - return false, "while adding repo [" + repoName + "]: " + err + return false, "While adding helm repository [" + repoName + "]: " + err } } @@ -287,17 +285,16 @@ func addHelmRepos(repos map[string]string) (bool, string) { cmd := command{ Cmd: helmBin, Args: []string{"repo", "update"}, - Description: "updating helm repos", + Description: "Updating helm repositories", } if exitCode, err, _ := cmd.exec(debug, verbose); exitCode != 0 { - return false, "while updating helm repos : " + err + return false, "While updating helm repos : " + err } return true, "" } - // cleanUntrackedReleases checks for any releases that are managed by Helmsman and are no longer tracked by the desired state // It compares the currently deployed releases with "MANAGED-BY=HELMSMAN" labels with Apps defined in the desired state // For all untracked releases found, a decision is made to delete them and is added to the Helmsman plan @@ -305,7 +302,7 @@ func addHelmRepos(repos map[string]string) (bool, string) { // NOTE: Removing/Commenting out an app from the desired state makes it untracked. func cleanUntrackedReleases() { toDelete := make(map[string]map[*release]bool) - logs.Info("Checking if any Helmsman managed releases are no longer tracked by your desired state ...") + log.Info("Checking if any Helmsman managed releases are no longer tracked by your desired state ...") for ns, releases := range getHelmsmanReleases() { for r := range releases { tracked := false @@ -324,14 +321,14 @@ func cleanUntrackedReleases() { } if len(toDelete) == 0 { - logs.Info("No untracked releases found.") + log.Info("No untracked releases found") } else { for _, releases := range toDelete { for r := range releases { if r.isReleaseConsideredToRun() { - logDecision("Untracked release [ "+r.Name+" ] is ignored by target flag. Skipping.", -800, ignored) + logDecision("Untracked release [ "+r.Name+" ] found but it's ignored by target flag", -800, ignored) } else { - logDecision("Untracked release found: release [ "+r.Name+" ]. It will be deleted", -800, delete) + logDecision("Untracked release [ "+r.Name+" ] found and it will be deleted", -800, delete) deleteUntrackedRelease(r) } } @@ -344,7 +341,7 @@ func deleteUntrackedRelease(release *release) { cmd := command{ Cmd: helmBin, Args: concat([]string{"delete", release.Name, "--namespace", release.Namespace}, getDryRunFlags()), - Description: "deleting not tracked release [ "+release.Name+" ] in namespace [[ "+release.Namespace+" ]]", + Description: "Deleting untracked release [ " + release.Name + " ] in namespace [ " + release.Namespace + " ]", } outcome.addCommand(cmd, -800, nil) @@ -373,16 +370,16 @@ func decryptSecret(name string) bool { if !settings.EyamlEnabled { _, fileNotFound := os.Stat(name + ".dec") if fileNotFound != nil && !isOfType(name, []string{".dec"}) { - logs.Error(output) + log.Error(output) return false } } if exitCode != 0 { - logs.Error(output) + log.Error(output) return false } else if stderr != "" { - logs.Error(stderr) + log.Error(stderr) return false } @@ -395,7 +392,7 @@ func decryptSecret(name string) bool { } err := writeStringToFile(outfile, output) if err != nil { - logError("could not write [ " + outfile + " ] file") + log.Fatal("Can't write [ " + outfile + " ] file") } } return true @@ -406,7 +403,7 @@ func updateChartDep(chartPath string) (bool, string) { cmd := command{ Cmd: helmBin, Args: []string{"dependency", "update", chartPath}, - Description: "Updating dependency for local chart " + chartPath, + Description: "Updating dependency for local chart [ " + chartPath + " ]", } exitCode, err, _ := cmd.exec(debug, verbose) @@ -416,3 +413,21 @@ func updateChartDep(chartPath string) (bool, string) { } return true, "" } + +// helmPluginExists returns true if the plugin is present in the environment and false otherwise. +// It takes as input the plugin's name to check if it is recognizable or not. e.g. diff +func helmPluginExists(plugin string) bool { + cmd := command{ + Cmd: helmBin, + Args: []string{"plugin", "list"}, + Description: "Validating that [ " + plugin + " ] is installed", + } + + exitCode, result, _ := cmd.exec(debug, false) + + if exitCode != 0 { + return false + } + + return strings.Contains(result, plugin) +} diff --git a/helm_helpers_test.go b/internal/app/helm_helpers_test.go similarity index 53% rename from helm_helpers_test.go rename to internal/app/helm_helpers_test.go index 0f313c61..4686b9bc 100644 --- a/helm_helpers_test.go +++ b/internal/app/helm_helpers_test.go @@ -1,4 +1,4 @@ -package main +package app import ( "fmt" @@ -17,7 +17,7 @@ func setupTestCase(t *testing.T) func(t *testing.T) { Description: "creating an empty local chart directory", } if exitCode, msg, _ := cmd.exec(debug, verbose); exitCode != 0 { - logError(fmt.Sprintf("Command returned with exit code: %d. And error message: %s ", exitCode, msg)) + log.Fatal(fmt.Sprintf("Command returned with exit code: %d. And error message: %s ", exitCode, msg)) } return func(t *testing.T) { @@ -42,26 +42,26 @@ func Test_validateReleaseCharts(t *testing.T) { args: args{ apps: map[string]*release{ "app": &release{ - Name: "", - Description: "", - Namespace: "", - Enabled: true, - Chart: os.TempDir() + "/helmsman-tests/myapp", - Version: "", - ValuesFile: "", - ValuesFiles: []string{}, - SecretsFile: "", - SecretsFiles: []string{}, - Purge: false, - Test: false, - Protected: false, - Wait: false, - Priority: 0, - Set: make(map[string]string), - SetString: make(map[string]string), - HelmFlags: []string{}, - NoHooks: false, - Timeout: 0, + Name: "", + Description: "", + Namespace: "", + Enabled: true, + Chart: os.TempDir() + "/helmsman-tests/myapp", + Version: "", + ValuesFile: "", + ValuesFiles: []string{}, + SecretsFile: "", + SecretsFiles: []string{}, + Purge: false, + Test: false, + Protected: false, + Wait: false, + Priority: 0, + Set: make(map[string]string), + SetString: make(map[string]string), + HelmFlags: []string{}, + NoHooks: false, + Timeout: 0, }, }, }, @@ -72,26 +72,26 @@ func Test_validateReleaseCharts(t *testing.T) { args: args{ apps: map[string]*release{ "app": &release{ - Name: "", - Description: "", - Namespace: "", - Enabled: true, - Chart: os.TempDir() + "/does-not-exist/myapp", - Version: "", - ValuesFile: "", - ValuesFiles: []string{}, - SecretsFile: "", - SecretsFiles: []string{}, - Purge: false, - Test: false, - Protected: false, - Wait: false, - Priority: 0, - Set: make(map[string]string), - SetString: make(map[string]string), - HelmFlags: []string{}, - NoHooks: false, - Timeout: 0, + Name: "", + Description: "", + Namespace: "", + Enabled: true, + Chart: os.TempDir() + "/does-not-exist/myapp", + Version: "", + ValuesFile: "", + ValuesFiles: []string{}, + SecretsFile: "", + SecretsFiles: []string{}, + Purge: false, + Test: false, + Protected: false, + Wait: false, + Priority: 0, + Set: make(map[string]string), + SetString: make(map[string]string), + HelmFlags: []string{}, + NoHooks: false, + Timeout: 0, }, }, }, @@ -102,26 +102,26 @@ func Test_validateReleaseCharts(t *testing.T) { args: args{ apps: map[string]*release{ "app": &release{ - Name: "", - Description: "", - Namespace: "", - Enabled: true, - Chart: os.TempDir() + "/helmsman-tests/dir-with space/myapp", - Version: "0.1.0", - ValuesFile: "", - ValuesFiles: []string{}, - SecretsFile: "", - SecretsFiles: []string{}, - Purge: false, - Test: false, - Protected: false, - Wait: false, - Priority: 0, - Set: make(map[string]string), - SetString: make(map[string]string), - HelmFlags: []string{}, - NoHooks: false, - Timeout: 0, + Name: "", + Description: "", + Namespace: "", + Enabled: true, + Chart: os.TempDir() + "/helmsman-tests/dir-with space/myapp", + Version: "0.1.0", + ValuesFile: "", + ValuesFiles: []string{}, + SecretsFile: "", + SecretsFiles: []string{}, + Purge: false, + Test: false, + Protected: false, + Wait: false, + Priority: 0, + Set: make(map[string]string), + SetString: make(map[string]string), + HelmFlags: []string{}, + NoHooks: false, + Timeout: 0, }, }, }, @@ -132,26 +132,26 @@ func Test_validateReleaseCharts(t *testing.T) { args: args{ apps: map[string]*release{ "app": &release{ - Name: "", - Description: "", - Namespace: "", - Enabled: true, - Chart: "stable/prometheus", - Version: "9.5.2", - ValuesFile: "", - ValuesFiles: []string{}, - SecretsFile: "", - SecretsFiles: []string{}, - Purge: false, - Test: false, - Protected: false, - Wait: false, - Priority: 0, - Set: make(map[string]string), - SetString: make(map[string]string), - HelmFlags: []string{}, - NoHooks: false, - Timeout: 0, + Name: "", + Description: "", + Namespace: "", + Enabled: true, + Chart: "stable/prometheus", + Version: "9.5.2", + ValuesFile: "", + ValuesFiles: []string{}, + SecretsFile: "", + SecretsFiles: []string{}, + Purge: false, + Test: false, + Protected: false, + Wait: false, + Priority: 0, + Set: make(map[string]string), + SetString: make(map[string]string), + HelmFlags: []string{}, + NoHooks: false, + Timeout: 0, }, }, }, @@ -162,26 +162,26 @@ func Test_validateReleaseCharts(t *testing.T) { args: args{ apps: map[string]*release{ "app": &release{ - Name: "app", - Description: "", - Namespace: "", - Enabled: true, - Chart: os.TempDir() + "/does-not-exist/myapp", - Version: "", - ValuesFile: "", - ValuesFiles: []string{}, - SecretsFile: "", - SecretsFiles: []string{}, - Purge: false, - Test: false, - Protected: false, - Wait: false, - Priority: 0, - Set: make(map[string]string), - SetString: make(map[string]string), - HelmFlags: []string{}, - NoHooks: false, - Timeout: 0, + Name: "app", + Description: "", + Namespace: "", + Enabled: true, + Chart: os.TempDir() + "/does-not-exist/myapp", + Version: "", + ValuesFile: "", + ValuesFiles: []string{}, + SecretsFile: "", + SecretsFiles: []string{}, + Purge: false, + Test: false, + Protected: false, + Wait: false, + Priority: 0, + Set: make(map[string]string), + SetString: make(map[string]string), + HelmFlags: []string{}, + NoHooks: false, + Timeout: 0, }, }, }, @@ -192,26 +192,26 @@ func Test_validateReleaseCharts(t *testing.T) { args: args{ apps: map[string]*release{ "app": &release{ - Name: "app", - Description: "", - Namespace: "", - Enabled: true, - Chart: os.TempDir() + "/does-not-exist/myapp", - Version: "", - ValuesFile: "", - ValuesFiles: []string{}, - SecretsFile: "", - SecretsFiles: []string{}, - Purge: false, - Test: false, - Protected: false, - Wait: false, - Priority: 0, - Set: make(map[string]string), - SetString: make(map[string]string), - HelmFlags: []string{}, - NoHooks: false, - Timeout: 0, + Name: "app", + Description: "", + Namespace: "", + Enabled: true, + Chart: os.TempDir() + "/does-not-exist/myapp", + Version: "", + ValuesFile: "", + ValuesFiles: []string{}, + SecretsFile: "", + SecretsFiles: []string{}, + Purge: false, + Test: false, + Protected: false, + Wait: false, + Priority: 0, + Set: make(map[string]string), + SetString: make(map[string]string), + HelmFlags: []string{}, + NoHooks: false, + Timeout: 0, }, }, }, @@ -253,11 +253,11 @@ func Test_getReleaseChartVersion(t *testing.T) { name: "test case 1: there is a pre-release version", args: args{ r: releaseState{ - Revision: 0, - Updated: time.Now(), - Status: "", - Chart: "elasticsearch-1.3.0-1", - Namespace: "", + Revision: 0, + Updated: time.Now(), + Status: "", + Chart: "elasticsearch-1.3.0-1", + Namespace: "", }, }, want: "1.3.0-1", @@ -265,11 +265,11 @@ func Test_getReleaseChartVersion(t *testing.T) { name: "test case 2: normal case", args: args{ r: releaseState{ - Revision: 0, - Updated: time.Now(), - Status: "", - Chart: "elasticsearch-1.3.0", - Namespace: "", + Revision: 0, + Updated: time.Now(), + Status: "", + Chart: "elasticsearch-1.3.0", + Namespace: "", }, }, want: "1.3.0", @@ -277,11 +277,11 @@ func Test_getReleaseChartVersion(t *testing.T) { name: "test case 3: there is a hypen in the name", args: args{ r: releaseState{ - Revision: 0, - Updated: time.Now(), - Status: "", - Chart: "elastic-search-1.3.0", - Namespace: "", + Revision: 0, + Updated: time.Now(), + Status: "", + Chart: "elastic-search-1.3.0", + Namespace: "", }, }, want: "1.3.0", @@ -289,11 +289,11 @@ func Test_getReleaseChartVersion(t *testing.T) { name: "test case 4: there is meta information", args: args{ r: releaseState{ - Revision: 0, - Updated: time.Now(), - Status: "", - Chart: "elastic-search-1.3.0+meta.info", - Namespace: "", + Revision: 0, + Updated: time.Now(), + Status: "", + Chart: "elastic-search-1.3.0+meta.info", + Namespace: "", }, }, want: "1.3.0+meta.info", @@ -301,11 +301,11 @@ func Test_getReleaseChartVersion(t *testing.T) { name: "test case 5: an invalid string", args: args{ r: releaseState{ - Revision: 0, - Updated: time.Now(), - Status: "", - Chart: "foo", - Namespace: "", + Revision: 0, + Updated: time.Now(), + Status: "", + Chart: "foo", + Namespace: "", }, }, want: "", @@ -313,11 +313,11 @@ func Test_getReleaseChartVersion(t *testing.T) { name: "test case 6: version includes v", args: args{ r: releaseState{ - Revision: 0, - Updated: time.Now(), - Status: "", - Chart: "cert-manager-v0.5.2", - Namespace: "", + Revision: 0, + Updated: time.Now(), + Status: "", + Chart: "cert-manager-v0.5.2", + Namespace: "", }, }, want: "v0.5.2", @@ -350,7 +350,7 @@ func Test_getChartVersion(t *testing.T) { Name: "release1", Namespace: "namespace", Version: "1.0.0", - Chart: "./test_files/chart-test", + Chart: "./../../tests/chart-test", Enabled: true, }, }, @@ -396,15 +396,15 @@ func Test_eyamlSecrets(t *testing.T) { args: args{ s: &config{ EyamlEnabled: true, - EyamlPublicKeyPath: "./test_files/keys/public_key.pkcs7.pem", - EyamlPrivateKeyPath: "./test_files/keys/private_key.pkcs7.pem", + EyamlPublicKeyPath: "./../../tests/keys/public_key.pkcs7.pem", + EyamlPrivateKeyPath: "./../../tests/keys/private_key.pkcs7.pem", }, r: &release{ Name: "release1", Namespace: "namespace", Version: "1.0.0", Enabled: true, - SecretsFile: "./test_files/secrets/valid_eyaml_secrets.yaml", + SecretsFile: "./../../tests/secrets/valid_eyaml_secrets.yaml", }, }, want: true, @@ -414,15 +414,15 @@ func Test_eyamlSecrets(t *testing.T) { args: args{ s: &config{ EyamlEnabled: true, - EyamlPublicKeyPath: "./test_files/keys/public_key.pkcs7.pem", - EyamlPrivateKeyPath: "./test_files/keys/private_key.pkcs7.pem", + EyamlPublicKeyPath: "./../../tests/keys/public_key.pkcs7.pem", + EyamlPrivateKeyPath: "./../../tests/keys/private_key.pkcs7.pem", }, r: &release{ Name: "release1", Namespace: "namespace", Version: "1.0.0", Enabled: true, - SecretsFile: "./test_files/secrets/invalid_eyaml_secrets.yaml", + SecretsFile: "./../../tests/secrets/invalid_eyaml_secrets.yaml", }, }, want: false, @@ -432,15 +432,15 @@ func Test_eyamlSecrets(t *testing.T) { args: args{ s: &config{ EyamlEnabled: true, - EyamlPublicKeyPath: "./test_files/keys/public_key.pkcs7.pem2", - EyamlPrivateKeyPath: "./test_files/keys/private_key.pkcs7.pem", + EyamlPublicKeyPath: "./../../tests/keys/public_key.pkcs7.pem2", + EyamlPrivateKeyPath: "./../../tests/keys/private_key.pkcs7.pem", }, r: &release{ Name: "release1", Namespace: "namespace", Version: "1.0.0", Enabled: true, - SecretsFile: "./test_files/secrets/valid_eyaml_secrets.yaml", + SecretsFile: "./../../tests/secrets/valid_eyaml_secrets.yaml", }, }, want: false, diff --git a/kube_helpers.go b/internal/app/kube_helpers.go similarity index 82% rename from kube_helpers.go rename to internal/app/kube_helpers.go index e0ff7ad1..7c591ea5 100644 --- a/kube_helpers.go +++ b/internal/app/kube_helpers.go @@ -1,4 +1,4 @@ -package main +package app import ( "io/ioutil" @@ -26,7 +26,7 @@ func addNamespaces(namespaces map[string]namespace) { // overrideAppsNamespace replaces all apps namespaces with one specific namespace func overrideAppsNamespace(newNs string) { - logs.Info("Overriding apps namespaces with [ " + newNs + " ] ...") + log.Info("Overriding apps namespaces with [ " + newNs + " ] ...") for _, r := range s.Apps { overrideNamespace(r, newNs) } @@ -34,14 +34,26 @@ func overrideAppsNamespace(newNs string) { // createNamespace creates a namespace in the k8s cluster func createNamespace(ns string) { + checkCmd := command{ + Cmd: "kubectl", + Args: []string{"get", "namespace", ns}, + Description: "Looking for namespace [ " + ns + " ]", + } + checkExitCode, _, _ := checkCmd.exec(debug, verbose) + if checkExitCode == 0 { + log.Verbose("Namespace [ " + ns + " ] exists") + return + } cmd := command{ Cmd: "kubectl", Args: []string{"create", "namespace", ns}, - Description: "creating namespace " + ns, + Description: "Creating namespace [ " + ns + " ]", } - exitCode, _, _ := cmd.exec(debug, verbose) - if exitCode != 0 && verbose { - logs.Debug("Namespace [ " + ns + " ] is created.") + exitCode, err, _ := cmd.exec(debug, verbose) + if exitCode == 0 { + log.Info("Namespace [ " + ns + " ] created") + } else { + log.Fatal("Failed creating namespace [ " + ns + " ] with error: " + err) } } @@ -51,12 +63,12 @@ func labelNamespace(ns string, labels map[string]string) { cmd := command{ Cmd: "kubectl", Args: []string{"label", "--overwrite", "namespace/" + ns, k + "=" + v}, - Description: "labeling namespace " + ns, + Description: "Labeling namespace [ " + ns + " ]", } exitCode, _, _ := cmd.exec(debug, verbose) if exitCode != 0 && verbose { - logs.Warning("Can't label namespace [ " + ns + " with " + k + "=" + v + + log.Warning("Can't label namespace [ " + ns + " with " + k + "=" + v + " ]. It already exists.") } } @@ -75,12 +87,12 @@ func annotateNamespace(ns string, labels map[string]string) { cmd := command{ Cmd: "kubectl", Args: []string{"annotate", "--overwrite", "namespace/" + ns, annotations}, - Description: "annotating namespace " + ns, + Description: "Annotating namespace [ " + ns + " ]", } exitCode, _, _ := cmd.exec(debug, verbose) if exitCode != 0 && verbose { - logs.Info("Can't annotate namespace [ " + ns + " with " + annotations + + log.Info("Can't annotate namespace [ " + ns + " with " + annotations + " ]. It already exists.") } } @@ -103,25 +115,25 @@ spec: ` d, err := yaml.Marshal(&lims) if err != nil { - logError(err.Error()) + log.Fatal(err.Error()) } definition = definition + Indent(string(d), strings.Repeat(" ", 4)) if err := ioutil.WriteFile("temp-LimitRange.yaml", []byte(definition), 0666); err != nil { - logError(err.Error()) + log.Fatal(err.Error()) } cmd := command{ Cmd: "kubectl", Args: []string{"apply", "-f", "temp-LimitRange.yaml", "-n", ns}, - Description: "creating LimitRange in namespace [ " + ns + " ]", + Description: "Creating LimitRange in namespace [ " + ns + " ]", } exitCode, e, _ := cmd.exec(debug, verbose) if exitCode != 0 { - logError("failed to create LimitRange in namespace [ " + ns + " ]: " + e) + log.Fatal("Failed to create LimitRange in namespace [ " + ns + " ] with error: " + e) } deleteFile("temp-LimitRange.yaml") @@ -132,10 +144,10 @@ spec: // It returns true if successful, false otherwise func createContext() (bool, string) { if s.Settings.BearerToken && s.Settings.BearerTokenPath == "" { - logs.Info("Creating kube context with bearer token from K8S service account.") + log.Info("Creating kube context with bearer token from K8S service account.") s.Settings.BearerTokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token" } else if s.Settings.BearerToken && s.Settings.BearerTokenPath != "" { - logs.Info("Creating kube context with bearer token from " + s.Settings.BearerTokenPath) + log.Info("Creating kube context with bearer token from " + s.Settings.BearerTokenPath) } else if s.Settings.Password == "" || s.Settings.Username == "" || s.Settings.ClusterURI == "" { return false, "missing information to create context [ " + s.Settings.KubeContext + " ] " + "you are either missing PASSWORD, USERNAME or CLUSTERURI in the Settings section of your desired state file." @@ -201,7 +213,7 @@ func createContext() (bool, string) { cmd := command{ Cmd: "kubectl", Args: setCredentialsCmdArgs, - Description: "creating kubectl context - setting credentials.", + Description: "Creating kubectl context - setting credentials", } if exitCode, err, _ := cmd.exec(debug, verbose); exitCode != 0 { @@ -211,7 +223,7 @@ func createContext() (bool, string) { cmd = command{ Cmd: "kubectl", Args: []string{"config", "set-cluster", s.Settings.KubeContext, "--server=" + s.Settings.ClusterURI, "--certificate-authority=" + caCrt}, - Description: "creating kubectl context - setting cluster.", + Description: "Creating kubectl context - setting cluster", } if exitCode, err, _ := cmd.exec(debug, verbose); exitCode != 0 { @@ -221,7 +233,7 @@ func createContext() (bool, string) { cmd = command{ Cmd: "kubectl", Args: []string{"config", "set-context", s.Settings.KubeContext, "--cluster=" + s.Settings.KubeContext, "--user=" + s.Settings.Username}, - Description: "creating kubectl context - setting context.", + Description: "Creating kubectl context - setting context", } if exitCode, err, _ := cmd.exec(debug, verbose); exitCode != 0 { @@ -245,13 +257,13 @@ func setKubeContext(context string) bool { cmd := command{ Cmd: "kubectl", Args: []string{"config", "use-context", context}, - Description: "setting kubectl context to [ " + context + " ]", + Description: "Setting kube context to [ " + context + " ]", } exitCode, _, _ := cmd.exec(debug, verbose) if exitCode != 0 { - logs.Info("KubeContext: " + context + " does not exist. I will try to create it.") + log.Info("Kubectl context [ " + context + " ] does not exist. Attempting to create it...") return false } @@ -264,13 +276,13 @@ func getKubeContext() bool { cmd := command{ Cmd: "kubectl", Args: []string{"config", "current-context"}, - Description: "getting kubectl context", + Description: "Getting kubectl context", } exitCode, result, _ := cmd.exec(debug, verbose) if exitCode != 0 || result == "" { - logs.Info("Kubectl context is not set") + log.Info("Kubectl context is not set") return false } @@ -282,13 +294,13 @@ func createServiceAccount(saName string, namespace string) (bool, string) { cmd := command{ Cmd: "kubectl", Args: []string{"create", "serviceaccount", "-n", namespace, saName}, - Description: "creating service account [ " + saName + " ] in namespace [ " + namespace + " ]", + Description: "Creating service account [ " + saName + " ] in namespace [ " + namespace + " ]", } exitCode, err, _ := cmd.exec(debug, verbose) if exitCode != 0 { - //logError("failed to create service account " + saName + " in namespace [ " + namespace + " ]: " + err) + //log.Fatal("failed to create service account " + saName + " in namespace [ " + namespace + " ]: " + err) return false, err } @@ -312,11 +324,11 @@ func createRoleBinding(role string, saName string, namespace string) (bool, stri bindingName = namespace + ":" + saName + "-binding" } - logs.Info("Creating " + resource + " for service account [ " + saName + " ] in namespace [ " + namespace + " ] with role: " + role + ".") + log.Info("Creating " + resource + " for service account [ " + saName + " ] in namespace [ " + namespace + " ] with role: " + role + ".") cmd := command{ Cmd: "kubectl", Args: []string{"create", resource, bindingName, bindingOption, "--serviceaccount", namespace + ":" + saName, "-n", namespace}, - Description: "creating " + resource + " for service account [ " + saName + " ] in namespace [ " + namespace + " ] with role: " + role, + Description: "Creating " + resource + " for service account [ " + saName + " ] in namespace [ " + namespace + " ] with role: " + role, } exitCode, err, _ := cmd.exec(debug, verbose) @@ -341,14 +353,14 @@ func createRole(namespace string, role string, roleTemplateFile string) (bool, s resource, e = Asset("data/role.yaml") } if e != nil { - logError(e.Error()) + log.Fatal(e.Error()) } replaceStringInFile(resource, "temp-modified-role.yaml", map[string]string{"<>": namespace, "<>": role}) cmd := command{ Cmd: "kubectl", Args: []string{"apply", "-f", "temp-modified-role.yaml"}, - Description: "creating role [" + role + "] in namespace [ " + namespace + " ]", + Description: "Creating role [" + role + "] in namespace [ " + namespace + " ]", } exitCode, err, _ := cmd.exec(debug, verbose) @@ -365,7 +377,6 @@ func createRole(namespace string, role string, roleTemplateFile string) (bool, s // labelResource applies Helmsman specific labels to Helm's state resources (secrets/configmaps) func labelResource(r *release) { if r.Enabled { - logs.Info("Applying Helmsman labels to [ " + r.Name + " ] in namespace [ " + r.Namespace + " ] ") storageBackend := "secret" if s.Settings.StorageBackend != "" { @@ -375,13 +386,13 @@ func labelResource(r *release) { cmd := command{ Cmd: "kubectl", Args: []string{"label", storageBackend, "-n", r.Namespace, "-l", "owner=helm,name=" + r.Name, "MANAGED-BY=HELMSMAN", "NAMESPACE=" + r.Namespace, "--overwrite"}, - Description: "applying labels to Helm state for " + r.Name, + Description: "Applying Helmsman labels to [ " + r.Name + " ] release", } exitCode, err, _ := cmd.exec(debug, verbose) if exitCode != 0 { - logError(err) + log.Fatal(err) } } } @@ -402,13 +413,13 @@ func getHelmsmanReleases() map[string]map[*release]bool { cmd := command{ Cmd: "kubectl", Args: []string{"get", storageBackend, "-n", ns, "-l", "MANAGED-BY=HELMSMAN", "-o", "name"}, - Description: "getting helm releases which are managed by Helmsman in namespace [[ " + ns + " ]].", + Description: "Getting Helmsman-managed releases in namespace [ " + ns + " ]", } exitCode, output, _ := cmd.exec(debug, verbose) if exitCode != 0 { - logError(output) + log.Fatal(output) } if strings.ToUpper("No resources found.") != strings.ToUpper(strings.TrimSpace(output)) { lines = strings.Split(output, "\n") @@ -437,12 +448,12 @@ func getKubectlClientVersion() string { cmd := command{ Cmd: "kubectl", Args: []string{"version", "--client", "--short"}, - Description: "checking kubectl version ", + Description: "Checking kubectl version", } exitCode, result, _ := cmd.exec(debug, false) if exitCode != 0 { - logError("while checking kubectl version: " + result) + log.Fatal("While checking kubectl version: " + result) } return result } diff --git a/internal/app/logging.go b/internal/app/logging.go new file mode 100644 index 00000000..57a3d602 --- /dev/null +++ b/internal/app/logging.go @@ -0,0 +1,60 @@ +package app + +import ( + "github.com/apsdehal/go-logger" + "net/url" + "os" +) + +type Logger struct { + *logger.Logger +} + +var log *Logger +var baseLogger *logger.Logger + +func (l *Logger) Info(message string) { + baseLogger.Info(message) +} + +func (l *Logger) Debug(message string) { + baseLogger.Debug(message) +} + +func (l *Logger) Verbose(message string) { + if verbose { + baseLogger.Info(message) + } +} + +func (l *Logger) Error(message string) { + baseLogger.Error(message) +} + +func (l *Logger) Warning(message string) { + baseLogger.Warning(message) +} + +func (l *Logger) Notice(message string) { + baseLogger.Notice(message) +} + +func (l *Logger) Fatal(message string) { + if _, err := url.ParseRequestURI(s.Settings.SlackWebhook); err == nil { + notifySlack(message, s.Settings.SlackWebhook, true, apply) + } + baseLogger.Fatal(message) +} + +func initLogs(verbose bool, noColors bool) { + logger.SetDefaultFormat("%{time:2006-01-02 15:04:05} %{level}: %{message}") + logLevel := logger.InfoLevel + if verbose { + logLevel = logger.DebugLevel + } + colors := 1 + if noColors { + colors = 0 + } + baseLogger, _ = logger.New("logger", colors, os.Stdout, logLevel) +} diff --git a/main.go b/internal/app/main.go similarity index 90% rename from main.go rename to internal/app/main.go index ca6d0149..aa372654 100644 --- a/main.go +++ b/internal/app/main.go @@ -1,4 +1,4 @@ -package main +package app import ( "os" @@ -57,7 +57,11 @@ const tempFilesDir = ".helmsman-tmp" const stableHelmRepo = "https://kubernetes-charts.storage.googleapis.com" const incubatorHelmRepo = "http://storage.googleapis.com/kubernetes-charts-incubator" -func main() { +func init() { + Cli() +} + +func Main() { // delete temp files with substituted env vars when the program terminates defer os.RemoveAll(tempFilesDir) defer cleanup() @@ -65,13 +69,13 @@ func main() { // set the kubecontext to be used Or create it if it does not exist if !setKubeContext(s.Settings.KubeContext) { if r, msg := createContext(); !r { - logError(msg) + log.Fatal(msg) } } // add repos -- fails if they are not valid if r, msg := addHelmRepos(s.HelmRepos); !r { - logError(msg) + log.Fatal(msg) } if apply || dryRun || destroy { @@ -84,15 +88,15 @@ func main() { if !skipValidation { // validate charts-versions exist in defined repos if r, msg := validateReleaseCharts(s.Apps); !r { - logError(msg) + log.Fatal(msg) } } else { - logs.Info("Skipping charts' validation.") + log.Info("Skipping charts' validation.") } - logs.Info("Preparing plan...") + log.Info("Preparing plan...") if destroy { - logs.Info("--destroy is enabled. Your releases will be deleted!") + log.Info("--destroy is enabled. Your releases will be deleted!") } p := makePlan(&s) @@ -113,7 +117,7 @@ func main() { // It also deletes any Tiller TLS certs and keys // and secret files func cleanup() { - logs.Info("Cleaning up sensitive and temp files.") + log.Verbose("Cleaning up sensitive and temp files") if _, err := os.Stat("ca.crt"); err == nil { deleteFile("ca.crt") } diff --git a/namespace.go b/internal/app/namespace.go similarity index 80% rename from namespace.go rename to internal/app/namespace.go index a114bbad..e45d6cfb 100644 --- a/namespace.go +++ b/internal/app/namespace.go @@ -1,4 +1,4 @@ -package main +package app import ( "fmt" @@ -22,10 +22,10 @@ type limits []struct { // namespace type represents the fields of a namespace type namespace struct { - Protected bool `yaml:"protected"` - Limits limits `yaml:"limits,omitempty"` - Labels map[string]string `yaml:"labels"` - Annotations map[string]string `yaml:"annotations"` + Protected bool `yaml:"protected"` + Limits limits `yaml:"limits,omitempty"` + Labels map[string]string `yaml:"labels"` + Annotations map[string]string `yaml:"annotations"` } // checkNamespaceDefined checks if a given namespace is defined in the namespaces section of the desired state file diff --git a/plan.go b/internal/app/plan.go similarity index 80% rename from plan.go rename to internal/app/plan.go index e317022b..6913d5dd 100644 --- a/plan.go +++ b/internal/app/plan.go @@ -1,4 +1,4 @@ -package main +package app import ( "fmt" @@ -77,25 +77,25 @@ func (p *plan) addDecision(decision string, priority int, decisionType decisionT func (p plan) execPlan() { p.sortPlan() if len(p.Commands) > 0 { - logs.Info("Executing the plan ... ") + log.Info("Executing plan... ") } else { - logs.Info("Nothing to execute.") + log.Info("Nothing to execute") } for _, cmd := range p.Commands { - logs.Notice(cmd.Command.Description) + log.Notice(cmd.Command.Description) if exitCode, msg, _ := cmd.Command.exec(debug, verbose); exitCode != 0 { var errorMsg string if errorMsg = msg; !verbose { errorMsg = strings.Split(msg, "---")[0] } - logError(fmt.Sprintf("Command returned with exit code: %d. And error message: %s ", exitCode, errorMsg)) + log.Fatal(fmt.Sprintf("Command returned [ %d ] exit code and error message [ %s ]", exitCode, errorMsg)) } else { - logs.Notice(msg) + log.Notice(msg) if cmd.targetRelease != nil && !dryRun { labelResource(cmd.targetRelease) } - logs.Notice("Finished " + cmd.Command.Description) + log.Notice("Finished: " + cmd.Command.Description) if _, err := url.ParseRequestURI(s.Settings.SlackWebhook); err == nil { notifySlack(cmd.Command.Description+" ... SUCCESS!", s.Settings.SlackWebhook, false, true) } @@ -103,7 +103,7 @@ func (p plan) execPlan() { } if len(p.Commands) > 0 { - logs.Info("Plan applied.") + log.Info("Plan applied") } } @@ -117,19 +117,19 @@ func (p plan) printPlanCmds() { // printPlan prints the decisions made in a plan. func (p plan) printPlan() { - logs.Notice("-------- PLAN starts here --------------") + log.Notice("-------- PLAN starts here --------------") for _, decision := range p.Decisions { if decision.Type == ignored { - logs.Info(decision.Description + " -- priority: " + strconv.Itoa(decision.Priority)) + log.Info(decision.Description + " -- priority: " + strconv.Itoa(decision.Priority)) } else if decision.Type == noop { - logs.Info(decision.Description+" -- priority: "+strconv.Itoa(decision.Priority)) + log.Info(decision.Description + " -- priority: " + strconv.Itoa(decision.Priority)) } else if decision.Type == delete { - logs.Warning(decision.Description+" -- priority: "+strconv.Itoa(decision.Priority)) + log.Warning(decision.Description + " -- priority: " + strconv.Itoa(decision.Priority)) } else { - logs.Notice(decision.Description+" -- priority: "+strconv.Itoa(decision.Priority)) + log.Notice(decision.Description + " -- priority: " + strconv.Itoa(decision.Priority)) } } - logs.Notice("-------- PLAN ends here --------------") + log.Notice("-------- PLAN ends here --------------") } // sendPlanToSlack sends the description of plan commands to slack if a webhook is provided. @@ -148,7 +148,7 @@ func (p plan) sendPlanToSlack() { // sortPlan sorts the slices of commands and decisions based on priorities // the lower the priority value the earlier a command should be attempted func (p plan) sortPlan() { - logs.Debug("Sorting the commands in the plan based on priorities (order flags) ... ") + log.Verbose("Sorting the commands in the plan based on priorities (order flags) ... ") sort.SliceStable(p.Commands, func(i, j int) bool { return p.Commands[i].Priority < p.Commands[j].Priority diff --git a/plan_test.go b/internal/app/plan_test.go similarity index 99% rename from plan_test.go rename to internal/app/plan_test.go index bd3114d4..fcd92289 100644 --- a/plan_test.go +++ b/internal/app/plan_test.go @@ -1,4 +1,4 @@ -package main +package app import ( "reflect" diff --git a/release.go b/internal/app/release.go similarity index 80% rename from release.go rename to internal/app/release.go index b6a877d7..40591d42 100644 --- a/release.go +++ b/internal/app/release.go @@ -1,4 +1,4 @@ -package main +package app import ( "fmt" @@ -8,27 +8,27 @@ import ( // release type representing Helm releases which are described in the desired state type release struct { - Name string `yaml:"name"` - Description string `yaml:"description"` - Namespace string `yaml:"namespace"` - Enabled bool `yaml:"enabled"` - Group string `yaml:"group"` - Chart string `yaml:"chart"` - Version string `yaml:"version"` - ValuesFile string `yaml:"valuesFile"` - ValuesFiles []string `yaml:"valuesFiles"` - SecretsFile string `yaml:"secretsFile"` - SecretsFiles []string `yaml:"secretsFiles"` - Purge bool `yaml:"purge"` - Test bool `yaml:"test"` - Protected bool `yaml:"protected"` - Wait bool `yaml:"wait"` - Priority int `yaml:"priority"` - Set map[string]string `yaml:"set"` - SetString map[string]string `yaml:"setString"` - HelmFlags []string `yaml:"helmFlags"` - NoHooks bool `yaml:"noHooks"` - Timeout int `yaml:"timeout"` + Name string `yaml:"name"` + Description string `yaml:"description"` + Namespace string `yaml:"namespace"` + Enabled bool `yaml:"enabled"` + Group string `yaml:"group"` + Chart string `yaml:"chart"` + Version string `yaml:"version"` + ValuesFile string `yaml:"valuesFile"` + ValuesFiles []string `yaml:"valuesFiles"` + SecretsFile string `yaml:"secretsFile"` + SecretsFiles []string `yaml:"secretsFiles"` + Purge bool `yaml:"purge"` + Test bool `yaml:"test"` + Protected bool `yaml:"protected"` + Wait bool `yaml:"wait"` + Priority int `yaml:"priority"` + Set map[string]string `yaml:"set"` + SetString map[string]string `yaml:"setString"` + HelmFlags []string `yaml:"helmFlags"` + NoHooks bool `yaml:"noHooks"` + Timeout int `yaml:"timeout"` } func (r *release) isReleaseConsideredToRun() bool { @@ -124,7 +124,7 @@ func validateRelease(appLabel string, r *release, names map[string]map[string]bo // overrideNamespace overrides a release defined namespace with a new given one func overrideNamespace(r *release, newNs string) { - logs.Info("Overriding namespace for app: " + r.Name) + log.Info("Overriding namespace for app: " + r.Name) r.Namespace = newNs } diff --git a/release_test.go b/internal/app/release_test.go similarity index 90% rename from release_test.go rename to internal/app/release_test.go index 9b6a7721..169fa4af 100644 --- a/release_test.go +++ b/internal/app/release_test.go @@ -1,4 +1,4 @@ -package main +package app import ( "strings" @@ -35,7 +35,7 @@ func Test_validateRelease(t *testing.T) { Enabled: true, Chart: "repo/chartX", Version: "1.0", - ValuesFile: "test_files/values.yaml", + ValuesFile: "../../tests/values.yaml", Purge: true, Test: true, }, @@ -71,14 +71,14 @@ func Test_validateRelease(t *testing.T) { Enabled: true, Chart: "repo/chartX", Version: "1.0", - ValuesFile: "test_files/values.xml", + ValuesFile: "../../tests/values.xml", Purge: true, Test: true, }, s: st, }, want: false, - want1: "valuesFile must be a valid relative (from dsf file) file path for a yaml file, or can be left empty (provided path resolved to \"test_files/values.xml\").", + want1: "valuesFile must be a valid relative (from dsf file) file path for a yaml file, or can be left empty (provided path resolved to \"../../tests/values.xml\").", }, { name: "test case 4", args: args{ @@ -89,7 +89,7 @@ func Test_validateRelease(t *testing.T) { Enabled: true, Chart: "repo/chartX", Version: "1.0", - ValuesFile: "test_files/values.yaml", + ValuesFile: "../../tests/values.yaml", Purge: true, Test: true, }, @@ -107,7 +107,7 @@ func Test_validateRelease(t *testing.T) { Enabled: true, Chart: "repo/chartX", Version: "1.0", - ValuesFile: "test_files/values.yaml", + ValuesFile: "../../tests/values.yaml", Purge: true, Test: true, }, @@ -125,7 +125,7 @@ func Test_validateRelease(t *testing.T) { Enabled: true, Chart: "repo/chartX", Version: "1.0", - ValuesFile: "test_files/values.yaml", + ValuesFile: "../../tests/values.yaml", Purge: true, Test: true, }, @@ -143,7 +143,7 @@ func Test_validateRelease(t *testing.T) { Enabled: true, Chart: "chartX", Version: "1.0", - ValuesFile: "test_files/values.yaml", + ValuesFile: "../../tests/values.yaml", Purge: true, Test: true, }, @@ -161,7 +161,7 @@ func Test_validateRelease(t *testing.T) { Enabled: true, Chart: "", Version: "1.0", - ValuesFile: "test_files/values.yaml", + ValuesFile: "../../tests/values.yaml", Purge: true, Test: true, }, @@ -179,7 +179,7 @@ func Test_validateRelease(t *testing.T) { Enabled: true, Chart: "repo/chartX", Version: "", - ValuesFile: "test_files/values.yaml", + ValuesFile: "../../tests/values.yaml", Purge: true, Test: true, }, @@ -197,7 +197,7 @@ func Test_validateRelease(t *testing.T) { Enabled: true, Chart: "repo/chartX", Version: "1.0", - ValuesFile: "test_files/values.yaml", + ValuesFile: "../../tests/values.yaml", Purge: true, Test: true, }, @@ -215,7 +215,7 @@ func Test_validateRelease(t *testing.T) { Enabled: true, Chart: "repo/chartX", Version: "1.0", - ValuesFile: "test_files/values.yaml", + ValuesFile: "../../tests/values.yaml", ValuesFiles: []string{"xyz.yaml"}, Purge: true, Test: true, @@ -252,7 +252,7 @@ func Test_validateRelease(t *testing.T) { Enabled: true, Chart: "repo/chartX", Version: "1.0", - ValuesFiles: []string{"./test_files/values.yaml", "test_files/values2.yaml"}, + ValuesFiles: []string{"./../../tests/values.yaml", "../../tests/values2.yaml"}, Purge: true, Test: true, }, @@ -270,7 +270,7 @@ func Test_validateRelease(t *testing.T) { Enabled: true, Chart: "repo/chartX", Version: "1.0", - ValuesFiles: []string{"./test_files/values.yaml", "test_files/values2.yaml"}, + ValuesFiles: []string{"./../../tests/values.yaml", "../../tests/values2.yaml"}, Purge: true, Test: true, Set: map[string]string{"some_var": "$SOME_VAR"}, diff --git a/state.go b/internal/app/state.go similarity index 86% rename from state.go rename to internal/app/state.go index 0630e02a..702c9a47 100644 --- a/state.go +++ b/internal/app/state.go @@ -1,4 +1,4 @@ -package main +package app import ( "errors" @@ -10,19 +10,19 @@ import ( // config type represents the settings fields type config struct { - KubeContext string `yaml:"kubeContext"` - Username string `yaml:"username"` - Password string `yaml:"password"` - ClusterURI string `yaml:"clusterURI"` - ServiceAccount string `yaml:"serviceAccount"` - StorageBackend string `yaml:"storageBackend"` - SlackWebhook string `yaml:"slackWebhook"` - ReverseDelete bool `yaml:"reverseDelete"` - BearerToken bool `yaml:"bearerToken"` - BearerTokenPath string `yaml:"bearerTokenPath"` - EyamlEnabled bool `yaml:"eyamlEnabled"` - EyamlPrivateKeyPath string `yaml:"eyamlPrivateKeyPath"` - EyamlPublicKeyPath string `yaml:"eyamlPublicKeyPath"` + KubeContext string `yaml:"kubeContext"` + Username string `yaml:"username"` + Password string `yaml:"password"` + ClusterURI string `yaml:"clusterURI"` + ServiceAccount string `yaml:"serviceAccount"` + StorageBackend string `yaml:"storageBackend"` + SlackWebhook string `yaml:"slackWebhook"` + ReverseDelete bool `yaml:"reverseDelete"` + BearerToken bool `yaml:"bearerToken"` + BearerTokenPath string `yaml:"bearerTokenPath"` + EyamlEnabled bool `yaml:"eyamlEnabled"` + EyamlPrivateKeyPath string `yaml:"eyamlPrivateKeyPath"` + EyamlPublicKeyPath string `yaml:"eyamlPublicKeyPath"` } // state type represents the desired state of applications on a k8s cluster. @@ -115,7 +115,7 @@ func (s state) validate() error { } } else { - logs.Info("ns-override is used to override all namespaces with [ " + nsOverride + " ] Skipping defined namespaces validation.") + log.Info("ns-override is used to override all namespaces with [ " + nsOverride + " ] Skipping defined namespaces validation.") } // repos @@ -132,7 +132,7 @@ func (s state) validate() error { // apps if s.Apps == nil { - logs.Info("No apps specified. Nothing to be executed.") + log.Info("No apps specified. Nothing to be executed.") os.Exit(0) } diff --git a/state_test.go b/internal/app/state_test.go similarity index 99% rename from state_test.go rename to internal/app/state_test.go index ec49e2ba..3599950b 100644 --- a/state_test.go +++ b/internal/app/state_test.go @@ -1,4 +1,4 @@ -package main +package app import ( "os" diff --git a/utils.go b/internal/app/utils.go similarity index 85% rename from utils.go rename to internal/app/utils.go index 3aebd9ba..b2f36b18 100644 --- a/utils.go +++ b/internal/app/utils.go @@ -1,11 +1,10 @@ -package main +package app import ( "bytes" "fmt" "io" "io/ioutil" - "log" "net/http" "net/url" "os" @@ -19,15 +18,15 @@ import ( "gopkg.in/yaml.v2" "github.com/BurntSushi/toml" - "github.com/Praqma/helmsman/aws" - "github.com/Praqma/helmsman/azure" - "helmsman/gcs" + "github.com/Praqma/helmsman/internal/aws" + "github.com/Praqma/helmsman/internal/azure" + "github.com/Praqma/helmsman/internal/gcs" ) // printMap prints to the console any map of string keys and values. func printMap(m map[string]string, indent int) { for key, value := range m { - fmt.Println(strings.Repeat("\t", indent)+key, " : ", value) + fmt.Println(strings.Repeat("\t", indent)+key, ": ", value) } } @@ -48,11 +47,11 @@ func fromTOML(file string, s *state) (bool, string) { tomlFile := string(rawTomlFile) if !noEnvSubst { - logs.Info("Substituting env variables in file: " + file) + log.Verbose("Substituting env variables in file: " + file) tomlFile = substituteEnv(tomlFile) } if !noSSMSubst { - logs.Debug("Substituting SSM variables in file: " + file) + log.Verbose("Substituting SSM variables in file: " + file) tomlFile = substituteSSM(tomlFile) } @@ -63,13 +62,13 @@ func fromTOML(file string, s *state) (bool, string) { resolvePaths(file, s) substituteVarsInValuesFiles(s) - return true, "Parsed TOML [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps." + return true, "Parsed TOML [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps" } // toTOML encodes a state type into a TOML file. // It uses the BurntSuchi TOML parser. func toTOML(file string, s *state) { - logs.Info("Printing generated toml ... ") + log.Info("Printing generated toml ... ") var buff bytes.Buffer var ( newFile *os.File @@ -77,18 +76,18 @@ func toTOML(file string, s *state) { ) if err := toml.NewEncoder(&buff).Encode(s); err != nil { - logError(err.Error()) + log.Fatal(err.Error()) os.Exit(1) } newFile, err = os.Create(file) if err != nil { - logError(err.Error()) + log.Fatal(err.Error()) } bytesWritten, err := newFile.Write(buff.Bytes()) if err != nil { - logError(err.Error()) + log.Fatal(err.Error()) } - log.Printf("Wrote %d bytes.\n", bytesWritten) + log.Info(fmt.Sprintf("Wrote %d bytes.\n", bytesWritten)) newFile.Close() } @@ -102,11 +101,11 @@ func fromYAML(file string, s *state) (bool, string) { yamlFile := string(rawYamlFile) if !noEnvSubst { - logs.Debug("Substituting env variables in file: " + file) + log.Verbose("Substituting env variables in file: " + file) yamlFile = substituteEnv(yamlFile) } if !noSSMSubst { - logs.Debug("Substituting SSM variables in file: " + file) + log.Verbose("Substituting SSM variables in file: " + file) yamlFile = substituteSSM(yamlFile) } @@ -117,12 +116,12 @@ func fromYAML(file string, s *state) (bool, string) { resolvePaths(file, s) substituteVarsInValuesFiles(s) - return true, "Parsed YAML [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps." + return true, "Parsed YAML [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps" } // toYaml encodes a state type into a YAML file func toYAML(file string, s *state) { - logs.Info("Printing generated yaml ... ") + log.Info("Printing generated yaml ... ") var buff bytes.Buffer var ( newFile *os.File @@ -130,18 +129,18 @@ func toYAML(file string, s *state) { ) if err := yaml.NewEncoder(&buff).Encode(s); err != nil { - logError(err.Error()) + log.Fatal(err.Error()) os.Exit(1) } newFile, err = os.Create(file) if err != nil { - logError(err.Error()) + log.Fatal(err.Error()) } bytesWritten, err := newFile.Write(buff.Bytes()) if err != nil { - logError(err.Error()) + log.Fatal(err.Error()) } - log.Printf("Wrote %d bytes.\n", bytesWritten) + log.Info(fmt.Sprintf("Wrote %d bytes.\n", bytesWritten)) newFile.Close() } @@ -168,29 +167,29 @@ func substituteVarsInValuesFiles(s *state) { func substituteVarsInYaml(file string) string { rawYamlFile, err := ioutil.ReadFile(file) if err != nil { - logError(err.Error()) + log.Fatal(err.Error()) } yamlFile := string(rawYamlFile) if !noEnvSubst && !noEnvValuesSubst { - logs.Debug("Substituting env variables in file: " + file) + log.Verbose("Substituting env variables in file: " + file) yamlFile = substituteEnv(yamlFile) } if !noSSMSubst && !noSSMValuesSubst { - logs.Debug("Substituting SSM variables in file: " + file) + log.Verbose("Substituting SSM variables in file: " + file) yamlFile = substituteSSM(yamlFile) } dir, err := ioutil.TempDir(tempFilesDir, "tmp") if err != nil { - logError(err.Error()) + log.Fatal(err.Error()) } // output file contents with env variables substituted into temp files outFile := path.Join(dir, filepath.Base(file)) err = ioutil.WriteFile(outFile, []byte(yamlFile), 0644) if err != nil { - logError(err.Error()) + log.Fatal(err.Error()) } return outFile } @@ -212,7 +211,7 @@ func toFile(file string, s *state) { } else if isOfType(file, []string{".yaml", ".yml"}) { toYAML(file, s) } else { - logError("State file does not have toml/yaml extension.") + log.Fatal("State file does not have toml/yaml extension.") } } @@ -228,7 +227,7 @@ func stringInSlice(a string, list []string) bool { // addDefaultHelmRepos adds stable and incubator helm repos to the state if they are not already defined func addDefaultHelmRepos(s *state) { if noDefaultRepos { - logs.Info("Default helm repo set disabled, 'stable' and 'incubator' repos unset.") + log.Info("Default helm repo set disabled, 'stable' and 'incubator' repos unset.") return } if s.HelmRepos == nil || len(s.HelmRepos) == 0 { @@ -236,7 +235,7 @@ func addDefaultHelmRepos(s *state) { "stable": stableHelmRepo, "incubator": incubatorHelmRepo, } - logs.Info("No helm repos provided, using the default 'stable' and 'incubator' repos.") + log.Info("No helm repos provided, using the default 'stable' and 'incubator' repos.") } if _, ok := s.HelmRepos["stable"]; !ok { s.HelmRepos["stable"] = stableHelmRepo @@ -314,17 +313,11 @@ func isOfType(filename string, filetypes []string) bool { func readFile(filepath string) string { data, err := ioutil.ReadFile(filepath) if err != nil { - logError("failed to read [ " + filepath + " ] file content: " + err.Error()) + log.Fatal("failed to read [ " + filepath + " ] file content: " + err.Error()) } return string(data) } -// logVersions prints the versions of kubectl and helm to the logs -func logVersions() { - logs.Debug("kubectl client version: " + kubectlVersion) - logs.Debug("Helm client version: " + helmVersion) -} - // substituteEnv checks if a string has an env variable (contains '$'), then it returns its value // if the env variable is empty or unset, an empty string is returned // if the string does not contain '$', it is returned as is. @@ -381,7 +374,7 @@ func downloadFile(path string, outfile string) string { tmp := getBucketElements(path) msg, err := gcs.ReadFile(tmp["bucketName"], tmp["filePath"], outfile, noColors) if err != nil { - logs.Fatal(msg) + log.Fatal(msg) } } else if strings.HasPrefix(path, "az") { @@ -391,7 +384,7 @@ func downloadFile(path string, outfile string) string { } else { - logs.Info("" + outfile + " will be used from local file system.") + log.Info("" + outfile + " will be used from local file system.") copyFile(path, outfile) } return outfile @@ -401,27 +394,27 @@ func downloadFile(path string, outfile string) string { func copyFile(source string, destination string) { from, err := os.Open(source) if err != nil { - logError("while copying " + source + " to " + destination + " : " + err.Error()) + log.Fatal("while copying " + source + " to " + destination + " : " + err.Error()) } defer from.Close() to, err := os.OpenFile(destination, os.O_RDWR|os.O_CREATE, 0666) if err != nil { - logError("while copying " + source + " to " + destination + " : " + err.Error()) + log.Fatal("while copying " + source + " to " + destination + " : " + err.Error()) } defer to.Close() _, err = io.Copy(to, from) if err != nil { - logError("while copying " + source + " to " + destination + " : " + err.Error()) + log.Fatal("while copying " + source + " to " + destination + " : " + err.Error()) } } // deleteFile deletes a file func deleteFile(path string) { - logs.Info("Cleaning up ... deleting " + path) + log.Info("Cleaning up... deleting " + path) if err := os.Remove(path); err != nil { - logError("Could not delete file: " + path) + log.Fatal("Could not delete file: " + path) } } @@ -430,7 +423,7 @@ func deleteFile(path string) { // and the webhook URL as well as a flag specifying if this is a failure message or not // It returns true if the sending of the message is successful, otherwise returns false func notifySlack(content string, url string, failure bool, executing bool) bool { - logs.Info("Posting notifications to slack ... ") + log.Info("Posting notifications to slack ... ") color := "#36a64f" // green if failure { @@ -468,7 +461,7 @@ func notifySlack(content string, url string, failure bool, executing bool) bool client := &http.Client{} resp, err := client.Do(req) if err != nil { - logError("while sending notifications to slack" + err.Error()) + log.Fatal("while sending notifications to slack" + err.Error()) } defer resp.Body.Close() @@ -478,14 +471,6 @@ func notifySlack(content string, url string, failure bool, executing bool) bool return false } -// logError sends a notification on slack if a webhook URL is provided and logs the error before terminating. -func logError(msg string) { - if _, err := url.ParseRequestURI(s.Settings.SlackWebhook); err == nil { - notifySlack(msg, s.Settings.SlackWebhook, true, apply) - } - logs.Fatal(msg) -} - // getBucketElements returns a map containing the bucket name and the file path inside the bucket // this func works for S3, Azure and GCS bucket links of the format: // s3 or gs://bucketname/dir.../file.ext @@ -507,7 +492,7 @@ func replaceStringInFile(input []byte, outfile string, replacements map[string]s } if err := ioutil.WriteFile(outfile, output, 0666); err != nil { - logError(err.Error()) + log.Fatal(err.Error()) } } diff --git a/utils_test.go b/internal/app/utils_test.go similarity index 95% rename from utils_test.go rename to internal/app/utils_test.go index 94404742..31def8e6 100644 --- a/utils_test.go +++ b/internal/app/utils_test.go @@ -1,4 +1,4 @@ -package main +package app import ( "os" @@ -36,14 +36,14 @@ func Test_fromTOML(t *testing.T) { { name: "test case 1 -- invalid TOML", args: args{ - file: "test_files/invalid_example.toml", + file: "../../../tests/invalid_example.toml", s: new(state), }, want: false, }, { name: "test case 2 -- valid TOML", args: args{ - file: "example.toml", + file: "../../examples/example.toml", s: new(state), }, want: true, @@ -72,7 +72,7 @@ func Test_fromTOML_Expand(t *testing.T) { { name: "test case 1 -- valid TOML expand ClusterURI", args: args{ - file: "example.toml", + file: "../../examples/example.toml", s: new(state), }, section: "Settings", @@ -82,7 +82,7 @@ func Test_fromTOML_Expand(t *testing.T) { { name: "test case 2 -- valid TOML expand org", args: args{ - file: "example.toml", + file: "../../examples/example.toml", s: new(state), }, section: "Metadata", @@ -171,14 +171,14 @@ func Test_fromYAML(t *testing.T) { { name: "test case 1 -- invalid YAML", args: args{ - file: "test_files/invalid_example.yaml", + file: "../../tests/invalid_example.yaml", s: new(state), }, want: false, }, { name: "test case 2 -- valid TOML", args: args{ - file: "example.yaml", + file: "../../examples/example.yaml", s: new(state), }, want: true, @@ -208,7 +208,7 @@ func Test_fromYAML_Expand(t *testing.T) { { name: "test case 1 -- valid YAML expand ClusterURI", args: args{ - file: "example.yaml", + file: "../../examples/example.yaml", s: new(state), }, section: "Settings", @@ -218,7 +218,7 @@ func Test_fromYAML_Expand(t *testing.T) { { name: "test case 2 -- valid YAML expand org", args: args{ - file: "example.yaml", + file: "../../examples/example.yaml", s: new(state), }, section: "Metadata", @@ -355,7 +355,7 @@ func Test_readFile(t *testing.T) { { name: "test case 1 -- successful reading.", args: args{ - filepath: "test_files/values.yaml", + filepath: "../../tests/values.yaml", }, want: "", }, diff --git a/aws/aws.go b/internal/aws/aws.go similarity index 99% rename from aws/aws.go rename to internal/aws/aws.go index c7f32656..8d4acc40 100644 --- a/aws/aws.go +++ b/internal/aws/aws.go @@ -69,9 +69,8 @@ func ReadFile(bucketName string, filename string, outFile string, noColors bool) } - // ReadSSMParam reads a value from an SSM Parameter -func ReadSSMParam(keyname string, withDecryption bool, noColors bool) string { +func ReadSSMParam(keyname string, withDecryption bool, noColors bool) string { style = aurora.NewAurora(!noColors) // Checking env vars are set to configure AWS @@ -98,4 +97,4 @@ func ReadSSMParam(keyname string, withDecryption bool, noColors bool) string { value := *param.Parameter.Value return value -} \ No newline at end of file +} diff --git a/azure/azblob.go b/internal/azure/azblob.go similarity index 100% rename from azure/azblob.go rename to internal/azure/azblob.go diff --git a/data/role.yaml b/internal/data/role.yaml similarity index 54% rename from data/role.yaml rename to internal/data/role.yaml index 43211a25..6e61de13 100644 --- a/data/role.yaml +++ b/internal/data/role.yaml @@ -6,6 +6,6 @@ metadata: labels: CREATED-BY: HELMSMAN rules: -- apiGroups: ["", "batch", "extensions", "apps", "autoscaling", "rbac.authorization.k8s.io"] - resources: ["*"] - verbs: ["*"] + - apiGroups: ["", "batch", "extensions", "apps", "autoscaling", "rbac.authorization.k8s.io"] + resources: ["*"] + verbs: ["*"] diff --git a/gcs/gcs.go b/internal/gcs/gcs.go similarity index 96% rename from gcs/gcs.go rename to internal/gcs/gcs.go index 1d4485eb..3c6d36f8 100644 --- a/gcs/gcs.go +++ b/internal/gcs/gcs.go @@ -9,7 +9,7 @@ import ( // Imports the Google Cloud Storage client package. "cloud.google.com/go/storage" "github.com/logrusorgru/aurora" - "golang.org/x/net/context" + netContext "golang.org/x/net/context" ) // colorizer @@ -46,7 +46,7 @@ func ReadFile(bucketName string, filename string, outFile string, noColors bool) return msg, nil } - ctx := context.Background() + ctx := netContext.Background() client, err := storage.NewClient(ctx) if err != nil { return "Failed to configure Storage bucket: ", err diff --git a/test_files/chart-test/Chart.yaml b/tests/chart-test/Chart.yaml similarity index 100% rename from test_files/chart-test/Chart.yaml rename to tests/chart-test/Chart.yaml diff --git a/test_files/invalid_example.toml b/tests/invalid_example.toml similarity index 100% rename from test_files/invalid_example.toml rename to tests/invalid_example.toml diff --git a/test_files/invalid_example.yaml b/tests/invalid_example.yaml similarity index 100% rename from test_files/invalid_example.yaml rename to tests/invalid_example.yaml diff --git a/test_files/keys/private_key.pkcs7.pem b/tests/keys/private_key.pkcs7.pem similarity index 100% rename from test_files/keys/private_key.pkcs7.pem rename to tests/keys/private_key.pkcs7.pem diff --git a/test_files/keys/public_key.pkcs7.pem b/tests/keys/public_key.pkcs7.pem similarity index 100% rename from test_files/keys/public_key.pkcs7.pem rename to tests/keys/public_key.pkcs7.pem diff --git a/test_files/secrets/valid_eyaml_secrets.yaml b/tests/secrets/valid_eyaml_secrets.yaml similarity index 100% rename from test_files/secrets/valid_eyaml_secrets.yaml rename to tests/secrets/valid_eyaml_secrets.yaml diff --git a/test_files/values.xml b/tests/values.xml similarity index 100% rename from test_files/values.xml rename to tests/values.xml diff --git a/test_files/values.yaml b/tests/values.yaml similarity index 100% rename from test_files/values.yaml rename to tests/values.yaml diff --git a/test_files/values2.yaml b/tests/values2.yaml similarity index 100% rename from test_files/values2.yaml rename to tests/values2.yaml From a1e1f6f07c0ebaf80ade05121cf5f5830db019ae Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Tue, 17 Dec 2019 18:29:27 +0100 Subject: [PATCH 0514/1127] Fix make cross --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 0b12e553..73cdabd6 100644 --- a/Makefile +++ b/Makefile @@ -101,7 +101,7 @@ test: dep check repo ## Run unit tests cross: dep ## Create binaries for all OSs @cd $(PRJDIR) && \ - gox -os '!freebsd !netbsd' -arch '!arm' -output "dist/{{.Dir}}_{{.OS}}_{{.Arch}}" -ldflags '-X main.Version=${TAG}-${DATE}' + gox -os '!freebsd !netbsd' -arch '!arm' -output "dist/{{.Dir}}_{{.OS}}_{{.Arch}}" -ldflags '-X main.Version=${TAG}-${DATE}' ./... .PHONY: cross release: ## Generate a new release From e132203c636bdc8bf31afc854fa5f7049bd2f5f3 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Tue, 17 Dec 2019 18:59:05 +0100 Subject: [PATCH 0515/1127] Disable log of var substitution for every file --- internal/app/cli.go | 13 +++++++++++++ internal/app/utils.go | 6 ------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/internal/app/cli.go b/internal/app/cli.go index bd9e5935..6204ee5b 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -143,8 +143,21 @@ func Cli() { _ = os.MkdirAll(tempFilesDir, 0755) // read the TOML/YAML desired state file + if !noEnvSubst { + log.Verbose("Substitution of env variables enabled") + if !noEnvValuesSubst { + log.Verbose("Substitution of env variables in values enabled") + } + } + if !noSSMSubst { + log.Verbose("Substitution of SSM variables enabled") + if !noSSMValuesSubst { + log.Verbose("Substitution of SSM variables in values enabled") + } + } var fileState state for _, f := range files { + result, msg := fromFile(f, &fileState) if result { log.Info(msg) diff --git a/internal/app/utils.go b/internal/app/utils.go index b2f36b18..09c93396 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -47,11 +47,9 @@ func fromTOML(file string, s *state) (bool, string) { tomlFile := string(rawTomlFile) if !noEnvSubst { - log.Verbose("Substituting env variables in file: " + file) tomlFile = substituteEnv(tomlFile) } if !noSSMSubst { - log.Verbose("Substituting SSM variables in file: " + file) tomlFile = substituteSSM(tomlFile) } @@ -101,11 +99,9 @@ func fromYAML(file string, s *state) (bool, string) { yamlFile := string(rawYamlFile) if !noEnvSubst { - log.Verbose("Substituting env variables in file: " + file) yamlFile = substituteEnv(yamlFile) } if !noSSMSubst { - log.Verbose("Substituting SSM variables in file: " + file) yamlFile = substituteSSM(yamlFile) } @@ -172,11 +168,9 @@ func substituteVarsInYaml(file string) string { yamlFile := string(rawYamlFile) if !noEnvSubst && !noEnvValuesSubst { - log.Verbose("Substituting env variables in file: " + file) yamlFile = substituteEnv(yamlFile) } if !noSSMSubst && !noSSMValuesSubst { - log.Verbose("Substituting SSM variables in file: " + file) yamlFile = substituteSSM(yamlFile) } From 8a58cd9a80000bd4198a2c751881ad4a00803a6a Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Thu, 19 Dec 2019 18:06:51 +0100 Subject: [PATCH 0516/1127] Remove unused tiller-related functions; fix panic on wrong flag; fix DSF validation --- .circleci/config.yml | 14 +-- Dockerfile | 2 +- internal/app/bindata.go | 237 ----------------------------------- internal/app/cli.go | 31 ++--- internal/app/helm_helpers.go | 12 +- internal/app/kube_helpers.go | 85 ------------- internal/app/main.go | 1 - internal/data/role.yaml | 11 -- 8 files changed, 21 insertions(+), 372 deletions(-) delete mode 100644 internal/app/bindata.go delete mode 100644 internal/data/role.yaml diff --git a/.circleci/config.yml b/.circleci/config.yml index 99f93a03..4ab3dc90 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -28,18 +28,8 @@ jobs: command: | TAG=$(git describe --abbrev=0 --tags) docker login -u $DOCKER_USER -p $DOCKER_PASS - docker build -t praqma/helmsman:$TAG-helm-v2.15.1 --build-arg HELM_VERSION=v2.15.1 --build-arg HELM_DIFF_VERSION=master . --no-cache - docker push praqma/helmsman:$TAG-helm-v2.15.1 - docker build -t praqma/helmsman:$TAG-helm-v2.14.3 --build-arg HELM_VERSION=v2.14.3 --build-arg HELM_DIFF_VERSION=master . --no-cache - docker push praqma/helmsman:$TAG-helm-v2.14.3 - docker build -t praqma/helmsman:$TAG-helm-v2.13.1 --build-arg HELM_VERSION=v2.13.1 --build-arg HELM_DIFF_VERSION=master . --no-cache - docker push praqma/helmsman:$TAG-helm-v2.13.1 - docker build -t praqma/helmsman:$TAG-helm-v2.12.3 --build-arg HELM_VERSION=v2.12.3 --build-arg HELM_DIFF_VERSION=master . --no-cache - docker push praqma/helmsman:$TAG-helm-v2.12.3 - docker build -t praqma/helmsman:$TAG-helm-v2.11.0 --build-arg HELM_VERSION=v2.11.0 --build-arg HELM_DIFF_VERSION=v2.11.0+5 . --no-cache - docker push praqma/helmsman:$TAG-helm-v2.11.0 - docker build -t praqma/helmsman:$TAG-helm-v2.10.0 --build-arg HELM_VERSION=v2.10.0 --build-arg HELM_DIFF_VERSION=v2.10.0+1 . --no-cache - docker push praqma/helmsman:$TAG-helm-v2.10.0 + docker build -t praqma/helmsman:$TAG-helm-v3.0.2 --build-arg HELM_VERSION=v3.0.2 . --no-cache + docker push praqma/helmsman:$TAG-helm-v3.0.2 workflows: version: 2 diff --git a/Dockerfile b/Dockerfile index 01c06366..2061bb27 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ ARG GO_VERSION="1.13.5" ARG ALPINE_VERSION="3.10" ARG GLOBAL_KUBE_VERSION="v1.14.8" -ARG GLOBAL_HELM_VERSION="v3.0.1" +ARG GLOBAL_HELM_VERSION="v3.0.2" ARG GLOBAL_HELM_DIFF_VERSION="v3.0.0-rc.7" diff --git a/internal/app/bindata.go b/internal/app/bindata.go deleted file mode 100644 index b88bf261..00000000 --- a/internal/app/bindata.go +++ /dev/null @@ -1,237 +0,0 @@ -// Code generated by go-bindata. -// sources: -// data/role.yaml -// DO NOT EDIT! - -package app - -import ( - "bytes" - "compress/gzip" - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" - "strings" - "time" -) - -func bindataRead(data []byte, name string) ([]byte, error) { - gz, err := gzip.NewReader(bytes.NewBuffer(data)) - if err != nil { - return nil, fmt.Errorf("Read %q: %v", name, err) - } - - var buf bytes.Buffer - _, err = io.Copy(&buf, gz) - clErr := gz.Close() - - if err != nil { - return nil, fmt.Errorf("Read %q: %v", name, err) - } - if clErr != nil { - return nil, err - } - - return buf.Bytes(), nil -} - -type asset struct { - bytes []byte - info os.FileInfo -} - -type bindataFileInfo struct { - name string - size int64 - mode os.FileMode - modTime time.Time -} - -func (fi bindataFileInfo) Name() string { - return fi.name -} -func (fi bindataFileInfo) Size() int64 { - return fi.size -} -func (fi bindataFileInfo) Mode() os.FileMode { - return fi.mode -} -func (fi bindataFileInfo) ModTime() time.Time { - return fi.modTime -} -func (fi bindataFileInfo) IsDir() bool { - return false -} -func (fi bindataFileInfo) Sys() interface{} { - return nil -} - -var _dataRoleYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x74\x8e\xc1\x4a\x03\x31\x10\x86\xef\x79\x8a\x61\x8f\xe2\xae\xf4\x26\xa1\x14\xaa\x2e\x7a\xb0\x1e\xaa\x08\x22\x1e\x26\xe9\x40\x43\xd3\x4c\x98\x49\x8a\xf8\xf4\x92\x20\x7b\xf3\x34\xf3\xfd\x3f\x33\x7c\xa7\x90\x0e\x16\xf6\x1c\xc9\x60\x0e\xef\x24\x1a\x38\x59\x10\x87\x7e\xc2\x5a\x8e\x2c\xe1\x07\x4b\xe0\x34\x9d\x6e\x75\x0a\x7c\x73\x59\x39\x2a\xb8\x32\x67\x2a\x78\xc0\x82\xd6\x00\x24\x3c\x93\x85\xf5\x5a\x38\xd2\xd8\x60\xb3\xf9\x4b\x35\xa3\xef\xd5\x02\xbd\x8a\xe8\x28\x6a\x3b\x05\xb8\xdf\xcf\xdb\xb7\xf9\x61\xbc\xfb\xb0\xf0\x34\x3f\xef\x5e\x77\xdb\x17\x23\x35\x92\x5a\x33\x02\xe6\xf0\x28\x5c\xb3\x5a\xf8\x1c\x86\x6b\x18\x1c\x16\x7f\x6c\x0b\x7d\x17\x4a\xcd\x56\x1b\x61\xce\x7d\xfe\x2b\x3e\x7c\x19\x00\x21\xe5\x2a\x9e\xfa\xb7\xab\x1e\x5d\x48\xdc\x82\xbf\x01\x00\x00\xff\xff\xfa\x4b\x3c\xe3\x0e\x01\x00\x00") - -func dataRoleYamlBytes() ([]byte, error) { - return bindataRead( - _dataRoleYaml, - "data/role.yaml", - ) -} - -func dataRoleYaml() (*asset, error) { - bytes, err := dataRoleYamlBytes() - if err != nil { - return nil, err - } - - info := bindataFileInfo{name: "data/role.yaml", size: 270, mode: os.FileMode(420), modTime: time.Unix(1550050901, 0)} - a := &asset{bytes: bytes, info: info} - return a, nil -} - -// Asset loads and returns the asset for the given name. -// It returns an error if the asset could not be found or -// could not be loaded. -func Asset(name string) ([]byte, error) { - cannonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[cannonicalName]; ok { - a, err := f() - if err != nil { - return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) - } - return a.bytes, nil - } - return nil, fmt.Errorf("Asset %s not found", name) -} - -// MustAsset is like Asset but panics when Asset would return an error. -// It simplifies safe initialization of global variables. -func MustAsset(name string) []byte { - a, err := Asset(name) - if err != nil { - panic("asset: Asset(" + name + "): " + err.Error()) - } - - return a -} - -// AssetInfo loads and returns the asset info for the given name. -// It returns an error if the asset could not be found or -// could not be loaded. -func AssetInfo(name string) (os.FileInfo, error) { - cannonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[cannonicalName]; ok { - a, err := f() - if err != nil { - return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) - } - return a.info, nil - } - return nil, fmt.Errorf("AssetInfo %s not found", name) -} - -// AssetNames returns the names of the assets. -func AssetNames() []string { - names := make([]string, 0, len(_bindata)) - for name := range _bindata { - names = append(names, name) - } - return names -} - -// _bindata is a table, holding each asset generator, mapped to its name. -var _bindata = map[string]func() (*asset, error){ - "data/role.yaml": dataRoleYaml, -} - -// AssetDir returns the file names below a certain -// directory embedded in the file by go-bindata. -// For example if you run go-bindata on data/... and data contains the -// following hierarchy: -// data/ -// foo.txt -// img/ -// a.png -// b.png -// then AssetDir("data") would return []string{"foo.txt", "img"} -// AssetDir("data/img") would return []string{"a.png", "b.png"} -// AssetDir("foo.txt") and AssetDir("notexist") would return an error -// AssetDir("") will return []string{"data"}. -func AssetDir(name string) ([]string, error) { - node := _bintree - if len(name) != 0 { - cannonicalName := strings.Replace(name, "\\", "/", -1) - pathList := strings.Split(cannonicalName, "/") - for _, p := range pathList { - node = node.Children[p] - if node == nil { - return nil, fmt.Errorf("Asset %s not found", name) - } - } - } - if node.Func != nil { - return nil, fmt.Errorf("Asset %s not found", name) - } - rv := make([]string, 0, len(node.Children)) - for childName := range node.Children { - rv = append(rv, childName) - } - return rv, nil -} - -type bintree struct { - Func func() (*asset, error) - Children map[string]*bintree -} - -var _bintree = &bintree{nil, map[string]*bintree{ - "data": &bintree{nil, map[string]*bintree{ - "role.yaml": &bintree{dataRoleYaml, map[string]*bintree{}}, - }}, -}} - -// RestoreAsset restores an asset under the given directory -func RestoreAsset(dir, name string) error { - data, err := Asset(name) - if err != nil { - return err - } - info, err := AssetInfo(name) - if err != nil { - return err - } - err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) - if err != nil { - return err - } - err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) - if err != nil { - return err - } - err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) - if err != nil { - return err - } - return nil -} - -// RestoreAssets restores an asset under the given directory recursively -func RestoreAssets(dir, name string) error { - children, err := AssetDir(name) - // File - if err != nil { - return RestoreAsset(dir, name) - } - // Dir - for _, child := range children { - err = RestoreAssets(dir, filepath.Join(name, child)) - if err != nil { - return err - } - } - return nil -} - -func _filePath(dir, name string) string { - cannonicalName := strings.Replace(name, "\\", "/", -1) - return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) -} diff --git a/internal/app/cli.go b/internal/app/cli.go index 6204ee5b..a747732b 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -21,11 +21,11 @@ const ( ) func printUsage() { - log.Info(banner + "\n") - log.Info("Helmsman version: " + appVersion) - log.Info("Helmsman is a Helm Charts as Code tool which allows you to automate the deployment/management of your Helm charts.") - log.Info("") - log.Info("Usage: helmsman [options]") + fmt.Printf(banner + "\n") + fmt.Printf("Helmsman version: " + appVersion) + fmt.Printf("Helmsman is a Helm Charts as Code tool which allows you to automate the deployment/management of your Helm charts.") + fmt.Printf("") + fmt.Printf("Usage: helmsman [options]") flag.PrintDefaults() } @@ -48,7 +48,6 @@ func Cli() { flag.BoolVar(&noNs, "no-ns", false, "don't create namespaces") flag.StringVar(&nsOverride, "ns-override", "", "override defined namespaces with this one") flag.BoolVar(&skipValidation, "skip-validation", false, "skip desired state validation") - flag.BoolVar(&applyLabels, "apply-labels", false, "apply Helmsman labels to Helm state for all defined apps.") flag.BoolVar(&keepUntrackedReleases, "keep-untracked-releases", false, "keep releases that are managed by Helmsman and are no longer tracked in your desired state.") flag.BoolVar(&showDiff, "show-diff", false, "show helm diff results. Can expose sensitive information.") flag.BoolVar(&suppressDiffSecrets, "suppress-diff-secrets", false, "don't show secrets in helm diff output.") @@ -63,6 +62,11 @@ func Cli() { flag.Usage = printUsage flag.Parse() + if v { + fmt.Println("Helmsman version: " + appVersion) + os.Exit(0) + } + if noFancy { noColors = true noBanner = true @@ -70,7 +74,7 @@ func Cli() { initLogs(verbose, noColors) if !noBanner { - log.Info(fmt.Sprintf("%s version: %s\n%s", banner, appVersion, slogan)) + fmt.Printf("%s version: %s\n%s", banner, appVersion, slogan) } if dryRun && apply { @@ -95,11 +99,6 @@ func Cli() { kubectlVersion = strings.TrimSpace(strings.SplitN(getKubectlClientVersion(), ": ", 2)[1]) log.Verbose("kubectl client version: " + kubectlVersion) - if v { - fmt.Println("Helmsman version: " + appVersion) - os.Exit(0) - } - if len(files) == 0 { log.Info("No desired state files provided.") os.Exit(0) @@ -193,19 +192,13 @@ func Cli() { // validate the desired state content if len(files) > 0 { if err := s.validate(); err != nil { // syntax validation - log.Error(err.Error()) + log.Fatal(err.Error()) } } } else { log.Info("Desired state validation is skipped.") } - if applyLabels { - for _, r := range s.Apps { - labelResource(r) - } - } - if len(target) > 0 { targetMap = map[string]bool{} for _, v := range target { diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index 275845e7..30dffe69 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -106,9 +106,9 @@ func buildState() { } } -// helmRealseExists checks if a Helm release is/was deployed in a k8s cluster. +// isReleaseExisting checks if a Helm release is/was deployed in a k8s cluster. // It searches the Current State for releases. -// The key format for releases uniqueness is: +// The key format for releases uniqueness is: // If status is provided as an input [deployed, deleted, failed], then the search will verify the release status matches the search status. func isReleaseExisting(r *release, status string) bool { v, ok := currentState[fmt.Sprintf("%s-%s", r.Name, r.Namespace)] @@ -329,18 +329,18 @@ func cleanUntrackedReleases() { logDecision("Untracked release [ "+r.Name+" ] found but it's ignored by target flag", -800, ignored) } else { logDecision("Untracked release [ "+r.Name+" ] found and it will be deleted", -800, delete) - deleteUntrackedRelease(r) + uninstallUntrackedRelease(r) } } } } } -// deleteUntrackedRelease creates the helm command to purge delete an untracked release -func deleteUntrackedRelease(release *release) { +// uninstallUntrackedRelease creates the helm command to purge delete an untracked release +func uninstallUntrackedRelease(release *release) { cmd := command{ Cmd: helmBin, - Args: concat([]string{"delete", release.Name, "--namespace", release.Namespace}, getDryRunFlags()), + Args: concat([]string{"uninstall", release.Name, "--namespace", release.Namespace}, getDryRunFlags()), Description: "Deleting untracked release [ " + release.Name + " ] in namespace [ " + release.Namespace + " ]", } diff --git a/internal/app/kube_helpers.go b/internal/app/kube_helpers.go index 3285bb35..dbdd44ab 100644 --- a/internal/app/kube_helpers.go +++ b/internal/app/kube_helpers.go @@ -300,91 +300,6 @@ func getKubeContext() bool { return true } -// createServiceAccount creates a service account in a given namespace and associates it with a cluster-admin role -func createServiceAccount(saName string, namespace string) (bool, string) { - cmd := command{ - Cmd: "kubectl", - Args: []string{"create", "serviceaccount", "-n", namespace, saName}, - Description: "Creating service account [ " + saName + " ] in namespace [ " + namespace + " ]", - } - - exitCode, err, _ := cmd.exec(debug, verbose) - - if exitCode != 0 { - //log.Fatal("failed to create service account " + saName + " in namespace [ " + namespace + " ]: " + err) - return false, err - } - - return true, "" -} - -// createRoleBinding creates a role binding in a given namespace for a service account with a cluster-role/role in the cluster. -func createRoleBinding(role string, saName string, namespace string) (bool, string) { - clusterRole := false - resource := "rolebinding" - - if role == "cluster-admin" { - clusterRole = true - resource = "clusterrolebinding" - } - - bindingName := saName + "-binding" - bindingOption := "--role=" + role - if clusterRole { - bindingOption = "--clusterrole=" + role - bindingName = namespace + ":" + saName + "-binding" - } - - log.Info("Creating " + resource + " for service account [ " + saName + " ] in namespace [ " + namespace + " ] with role: " + role + ".") - cmd := command{ - Cmd: "kubectl", - Args: []string{"create", resource, bindingName, bindingOption, "--serviceaccount", namespace + ":" + saName, "-n", namespace}, - Description: "Creating " + resource + " for service account [ " + saName + " ] in namespace [ " + namespace + " ] with role: " + role, - } - - exitCode, err, _ := cmd.exec(debug, verbose) - - if exitCode != 0 { - return false, err - } - - return true, "" -} - -// createRole creates a k8s Role in a given namespace from a template -func createRole(namespace string, role string, roleTemplateFile string) (bool, string) { - var resource []byte - var e error - - if roleTemplateFile != "" { - // load file from path of TillerRoleTemplateFile - resource, e = ioutil.ReadFile(roleTemplateFile) - } else { - // load static resource - resource, e = Asset("data/role.yaml") - } - if e != nil { - log.Fatal(e.Error()) - } - replaceStringInFile(resource, "temp-modified-role.yaml", map[string]string{"<>": namespace, "<>": role}) - - cmd := command{ - Cmd: "kubectl", - Args: []string{"apply", "-f", "temp-modified-role.yaml"}, - Description: "Creating role [" + role + "] in namespace [ " + namespace + " ]", - } - - exitCode, err, _ := cmd.exec(debug, verbose) - - if exitCode != 0 { - return false, err - } - - deleteFile("temp-modified-role.yaml") - - return true, "" -} - // labelResource applies Helmsman specific labels to Helm's state resources (secrets/configmaps) func labelResource(r *release) { if r.Enabled { diff --git a/internal/app/main.go b/internal/app/main.go index aa372654..0403696b 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -30,7 +30,6 @@ var noFancy bool var noNs bool var nsOverride string var skipValidation bool -var applyLabels bool var keepUntrackedReleases bool var appVersion = "v3.0.0-beta1" var helmBin = "helm" diff --git a/internal/data/role.yaml b/internal/data/role.yaml deleted file mode 100644 index 6e61de13..00000000 --- a/internal/data/role.yaml +++ /dev/null @@ -1,11 +0,0 @@ -kind: Role -apiVersion: rbac.authorization.k8s.io/v1beta1 -metadata: - name: <> - namespace: <> - labels: - CREATED-BY: HELMSMAN -rules: - - apiGroups: ["", "batch", "extensions", "apps", "autoscaling", "rbac.authorization.k8s.io"] - resources: ["*"] - verbs: ["*"] From 81a94c24460ccd7accc7e097a196aaf31efaf3a5 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Thu, 19 Dec 2019 19:50:13 +0100 Subject: [PATCH 0517/1127] Fix delete installing all apps when run on empty cluster --- internal/app/cli.go | 4 ++-- internal/app/decision_maker.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/app/cli.go b/internal/app/cli.go index a747732b..37c2da9e 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -21,8 +21,8 @@ const ( ) func printUsage() { - fmt.Printf(banner + "\n") - fmt.Printf("Helmsman version: " + appVersion) + fmt.Printf(banner) + fmt.Printf("Helmsman version: " + appVersion + "\n") fmt.Printf("Helmsman is a Helm Charts as Code tool which allows you to automate the deployment/management of your Helm charts.") fmt.Printf("") fmt.Printf("Usage: helmsman [options]") diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index c000aae5..fbdbdb3c 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -36,8 +36,8 @@ func decide(r *release, s *state) { if destroy { if ok := isReleaseExisting(r, ""); ok { deleteRelease(r) - return } + return } if !r.Enabled { From ca9d31d86f73e8fea0d26c3821122840b4a31a9d Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Fri, 20 Dec 2019 12:15:44 +0100 Subject: [PATCH 0518/1127] minor style improvements --- internal/app/main.go | 1 + internal/app/release.go | 7 +++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/app/main.go b/internal/app/main.go index 0403696b..6c71574d 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -60,6 +60,7 @@ func init() { Cli() } +// Main is the app main function func Main() { // delete temp files with substituted env vars when the program terminates defer os.RemoveAll(tempFilesDir) diff --git a/internal/app/release.go b/internal/app/release.go index 40591d42..063a67a4 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -31,20 +31,19 @@ type release struct { Timeout int `yaml:"timeout"` } +// isReleaseConsideredToRun checks if a release is being targeted for operations as specified by user cmd flags (--group or --target) func (r *release) isReleaseConsideredToRun() bool { if len(targetMap) > 0 { if _, ok := targetMap[r.Name]; ok { return true - } else { - return false } + return false } if len(groupMap) > 0 { if _, ok := groupMap[r.Group]; ok { return true - } else { - return false } + return false } return true } From e917e9fbd96ae8d13754ff53ac8fe0abafe17e8d Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Fri, 20 Dec 2019 12:16:04 +0100 Subject: [PATCH 0519/1127] minor style improvements --- internal/app/cli.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/app/cli.go b/internal/app/cli.go index 37c2da9e..e00829e9 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -3,10 +3,11 @@ package app import ( "flag" "fmt" - "github.com/imdario/mergo" - "github.com/joho/godotenv" "os" "strings" + + "github.com/imdario/mergo" + "github.com/joho/godotenv" ) const ( @@ -29,6 +30,7 @@ func printUsage() { flag.PrintDefaults() } +// Cli parses cmd flags, validates them and performs some initializations func Cli() { //parsing command line flags flag.Var(&files, "f", "desired state file name(s), may be supplied more than once to merge state files") From 8afe166ec538e1ed1ad61e8007ccab3f2e2ab4f4 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Fri, 20 Dec 2019 12:16:30 +0100 Subject: [PATCH 0520/1127] fix detecting untracked releases --- internal/app/helm_helpers.go | 22 ++++++++++------------ internal/app/kube_helpers.go | 20 ++++++++------------ 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index 30dffe69..38e4367c 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -18,6 +18,7 @@ var currentState map[string]releaseState // releaseState represents the current state of a release type releaseState struct { + Name string Revision int Updated time.Time Status string @@ -97,6 +98,7 @@ func buildState() { } revision, _ := strconv.Atoi(rel[i].Revision) currentState[fmt.Sprintf("%s-%s", rel[i].Name, rel[i].Namespace)] = releaseState{ + Name: rel[i].Name, Revision: revision, Updated: date, Status: rel[i].Status, @@ -296,12 +298,12 @@ func addHelmRepos(repos map[string]string) (bool, string) { } // cleanUntrackedReleases checks for any releases that are managed by Helmsman and are no longer tracked by the desired state -// It compares the currently deployed releases with "MANAGED-BY=HELMSMAN" labels with Apps defined in the desired state -// For all untracked releases found, a decision is made to delete them and is added to the Helmsman plan +// It compares the currently deployed releases labeled with "MANAGED-BY=HELMSMAN" with Apps defined in the desired state +// For all untracked releases found, a decision is made to uninstall them and is added to the Helmsman plan // NOTE: Untracked releases don't benefit from either namespace or application protection. // NOTE: Removing/Commenting out an app from the desired state makes it untracked. func cleanUntrackedReleases() { - toDelete := make(map[string]map[*release]bool) + toDelete := make(map[string]map[releaseState]bool) log.Info("Checking if any Helmsman managed releases are no longer tracked by your desired state ...") for ns, releases := range getHelmsmanReleases() { for r := range releases { @@ -313,7 +315,7 @@ func cleanUntrackedReleases() { } if !tracked { if _, ok := toDelete[ns]; !ok { - toDelete[ns] = make(map[*release]bool) + toDelete[ns] = make(map[releaseState]bool) } toDelete[ns][r] = true } @@ -325,19 +327,15 @@ func cleanUntrackedReleases() { } else { for _, releases := range toDelete { for r := range releases { - if r.isReleaseConsideredToRun() { - logDecision("Untracked release [ "+r.Name+" ] found but it's ignored by target flag", -800, ignored) - } else { - logDecision("Untracked release [ "+r.Name+" ] found and it will be deleted", -800, delete) - uninstallUntrackedRelease(r) - } + logDecision("Untracked release [ "+r.Name+" ] found and it will be deleted", -800, delete) + uninstallUntrackedRelease(r) } } } } -// uninstallUntrackedRelease creates the helm command to purge delete an untracked release -func uninstallUntrackedRelease(release *release) { +// uninstallUntrackedRelease creates the helm command to uninstall an untracked release +func uninstallUntrackedRelease(release releaseState) { cmd := command{ Cmd: helmBin, Args: concat([]string{"uninstall", release.Name, "--namespace", release.Namespace}, getDryRunFlags()), diff --git a/internal/app/kube_helpers.go b/internal/app/kube_helpers.go index dbdd44ab..9037b259 100644 --- a/internal/app/kube_helpers.go +++ b/internal/app/kube_helpers.go @@ -324,18 +324,18 @@ func labelResource(r *release) { } // getHelmsmanReleases returns a map of all releases that are labeled with "MANAGED-BY=HELMSMAN" -// The releases are categorized by the namespaces in which their Tiller is running -// The returned map format is: map[:map[:true]] -func getHelmsmanReleases() map[string]map[*release]bool { +// The releases are categorized by the namespaces in which they are deployed +// The returned map format is: map[:map[:true]] +func getHelmsmanReleases() map[string]map[releaseState]bool { var lines []string - releases := make(map[string]map[*release]bool) + releases := make(map[string]map[releaseState]bool) storageBackend := "secret" if s.Settings.StorageBackend != "" { storageBackend = s.Settings.StorageBackend } - for ns, _ := range s.Namespaces { + for ns := range s.Namespaces { cmd := command{ Cmd: "kubectl", Args: []string{"get", storageBackend, "-n", ns, "-l", "MANAGED-BY=HELMSMAN", "-o", "name"}, @@ -356,16 +356,12 @@ func getHelmsmanReleases() map[string]map[*release]bool { continue } if _, ok := releases[ns]; !ok { - releases[ns] = make(map[*release]bool) - } - for _, app := range s.Apps { - if strings.Contains(r, app.Name) { - releases[ns][app] = true - } + releases[ns] = make(map[releaseState]bool) } + releaseName := strings.Split(strings.Split(r, "/")[1], ".")[4] + releases[ns][currentState[releaseName+"-"+ns]] = true } } - return releases } From 7fe56c1edd74404b3ecd2305aabfcc234c5aed33 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Fri, 20 Dec 2019 12:16:54 +0100 Subject: [PATCH 0521/1127] updating example.yaml --- examples/example.yaml | 48 +++++++++++++------------------------------ 1 file changed, 14 insertions(+), 34 deletions(-) diff --git a/examples/example.yaml b/examples/example.yaml index b7314fb3..5a10708d 100644 --- a/examples/example.yaml +++ b/examples/example.yaml @@ -1,4 +1,4 @@ -# version: v1.5.0 +# version: v3.0.0-beta1 # metadata -- add as many key/value pairs as you want metadata: org: "example.com/$ORG_PATH/" @@ -8,7 +8,7 @@ metadata: # paths to the certificate for connecting to the cluster # You can skip this if you use Helmsman on a machine with kubectl already connected to your k8s cluster. # you have to use exact key names here : 'caCrt' for certificate and 'caKey' for the key and caClient for the client certificate -certificates: +# certificates: #caClient: "gs://mybucket/client.crt" # GCS bucket path #caCrt: "s3://mybucket/ca.crt" # S3 bucket path #caKey: "../ca.key" # valid local file relative path @@ -17,20 +17,19 @@ settings: kubeContext: "minikube" # will try connect to this context first, if it does not exist, it will be created using the details below #username: "admin" #password: "$K8S_PASSWORD" # the name of an environment variable containing the k8s password - clusterURI: "$SET_URI" # the name of an environment variable containing the cluster API + #clusterURI: "$SET_URI" # the name of an environment variable containing the cluster API #clusterURI: "https://192.168.99.100:8443" # equivalent to the above #serviceAccount: "foo" # k8s serviceaccount must be already defined, validation error will be thrown otherwise - storageBackend: "secret" # default is configMap + storageBackend: "secret" #slackWebhook: "$slack" # or your slack webhook url #reverseDelete: false # reverse the priorities on delete #### to use bearer token: # bearerToken: true # clusterURI: "https://kubernetes.default" - #tillerless: true # runs helm in tillerless mode using # define your environments and their k8s namespaces namespaces: - production: + test1: protected: true limits: - type: Container @@ -53,8 +52,8 @@ namespaces: # syntax: repo_name: "repo_url" # only private repos hosted in s3 buckets are now supported helmRepos: - stable: "https://kubernetes-charts.storage.googleapis.com" - incubator: "http://storage.googleapis.com/kubernetes-charts-incubator" + argo: "https://argoproj.github.io/argo-helm" + jfrog: "https://charts.jfrog.io" #myS3repo: "s3://my-S3-private-repo/charts" #myGCSrepo: "gs://my-GCS-private-repo/charts" #custom: "https://user:pass@mycustomrepo.org" @@ -62,44 +61,25 @@ helmRepos: # define the desired state of your applications helm charts # each contains the following: - apps: # jenkins will be deployed using the Tiller in the staging namespace - jenkins: - namespace: "staging" # maps to the namespace as defined in namespaces above + argo: + namespace: "test1" # maps to the namespace as defined in namespaces above enabled: true # change to false if you want to delete this app release empty: false: - chart: "stable/jenkins" # changing the chart name means delete and recreate this chart - version: "0.14.3" # chart version - ### Optional values below - name: "jenkins" # should be unique across all apps - description: "jenkins" - valuesFile: "" # leaving it empty uses the default chart values - purge: false # will only be considered when there is a delete operation - test: false # run the tests when this release is installed for the first time only + chart: "argo/argo" # changing the chart name means delete and recreate this chart + version: "0.6.4" # chart version protected: true priority: -3 wait: true - #tillerNamespace: "kube-system" # which Tiller to use to deploy this release - set: # values to override values from values.yaml with values from env vars-- useful for passing secrets to charts - AdminPassword: "$JENKINS_PASSWORD" # $JENKINS_PASSWORD must exist in the environment - AdminUser: "admin" - setString: - MyLongIntVar: "1234567890" # artifactory will be deployed using the Tiller in the kube-system namespace artifactory: - namespace: "production" # maps to the namespace as defined in namespaces above + namespace: "test1" # maps to the namespace as defined in namespaces above enabled: true # change to false if you want to delete this app release empty: false: - chart: "stable/artifactory" # changing the chart name means delete and recreate this chart - version: "7.0.6" # chart version - ### Optional values below - name: "artifactory" # should be unique across all apps - description: "artifactory" - valuesFile: "" # leaving it empty uses the default chart values - purge: false # will only be considered when there is a delete operation - test: false # run the tests when this release is installed for the first time only + chart: "jfrog/artifactory" # changing the chart name means delete and recreate this chart + version: "8.3.2" # chart version priority: -2 # See https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md#apps for more apps options From 614c633b836378c88ab82da1fddea1c0ff91f555 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Fri, 20 Dec 2019 13:28:38 +0100 Subject: [PATCH 0522/1127] add a check for the allowed helm versions --- internal/app/cli.go | 17 ++++++++++++++--- internal/app/helm_helpers.go | 2 +- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/internal/app/cli.go b/internal/app/cli.go index 37c2da9e..7b546762 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -3,10 +3,12 @@ package app import ( "flag" "fmt" - "github.com/imdario/mergo" - "github.com/joho/godotenv" "os" "strings" + + version "github.com/hashicorp/go-version" + "github.com/imdario/mergo" + "github.com/joho/godotenv" ) const ( @@ -94,7 +96,16 @@ func Cli() { } helmVersion = strings.TrimSpace(getHelmVersion()) - log.Verbose("Helm client version: " + helmVersion) + extractedHelmVersion := helmVersion + if !strings.HasPrefix(helmVersion, "v") { + extractedHelmVersion = strings.TrimSpace(strings.Split(helmVersion, ":")[1]) + } + log.Verbose("Helm client version: " + extractedHelmVersion) + v1, _ := version.NewVersion(extractedHelmVersion) + jsonConstraint, _ := version.NewConstraint(">=3.0.0") + if !jsonConstraint.Check(v1) { + log.Fatal("this version of Helmsman does not work with helm releases older than 3.0.0") + } kubectlVersion = strings.TrimSpace(strings.SplitN(getKubectlClientVersion(), ": ", 2)[1]) log.Verbose("kubectl client version: " + kubectlVersion) diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index 30dffe69..96af045f 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -46,7 +46,7 @@ type chartVersion struct { func getHelmVersion() string { cmd := command{ Cmd: helmBin, - Args: []string{"version", "--short"}, + Args: []string{"version", "--short", "-c"}, Description: "Checking Helm version", } From 1ea456c53926e4eaaf42be13ea0381d7febf1fb3 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Fri, 20 Dec 2019 17:07:43 +0100 Subject: [PATCH 0523/1127] set HELM_DRIVER env var to respect storage driver choice --- internal/app/cli.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/internal/app/cli.go b/internal/app/cli.go index 37c2da9e..110edafa 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -3,10 +3,11 @@ package app import ( "flag" "fmt" - "github.com/imdario/mergo" - "github.com/joho/godotenv" "os" "strings" + + "github.com/imdario/mergo" + "github.com/joho/godotenv" ) const ( @@ -188,6 +189,10 @@ func Cli() { s.print() } + if s.Settings.StorageBackend != "" { + os.Setenv("HELM_DRIVER", s.Settings.StorageBackend) + } + if !skipValidation { // validate the desired state content if len(files) > 0 { From 44be1e6ccdaa60bbd87b91bd5185f0203810d163 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Fri, 20 Dec 2019 21:45:07 +0100 Subject: [PATCH 0524/1127] add context label to identify different DSFs --- examples/example.yaml | 5 +++ internal/app/cli.go | 18 ++++++++--- internal/app/decision_maker.go | 56 ++++++++++++++++++---------------- internal/app/helm_helpers.go | 28 +++++++++-------- internal/app/kube_helpers.go | 35 +++++++++++++-------- internal/app/main.go | 1 + internal/app/state.go | 4 +++ 7 files changed, 92 insertions(+), 55 deletions(-) diff --git a/examples/example.yaml b/examples/example.yaml index 5a10708d..ce5aae10 100644 --- a/examples/example.yaml +++ b/examples/example.yaml @@ -13,6 +13,11 @@ metadata: #caCrt: "s3://mybucket/ca.crt" # S3 bucket path #caKey: "../ca.key" # valid local file relative path +# context defines the context of this Desired State File. +# It is used to allow Helmsman identify which releases are managed by which DSF. +# Therefore, it is important that each DSF uses a unique context. +context: test-infra # defaults to file name if not provided + settings: kubeContext: "minikube" # will try connect to this context first, if it does not exist, it will be created using the details below #username: "admin" diff --git a/internal/app/cli.go b/internal/app/cli.go index 27541fe6..c96c9c0a 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -200,10 +200,6 @@ func Cli() { s.print() } - if s.Settings.StorageBackend != "" { - os.Setenv("HELM_DRIVER", s.Settings.StorageBackend) - } - if !skipValidation { // validate the desired state content if len(files) > 0 { @@ -215,6 +211,20 @@ func Cli() { log.Info("Desired state validation is skipped.") } + if s.Settings.StorageBackend != "" { + os.Setenv("HELM_DRIVER", s.Settings.StorageBackend) + } + + // set default storage background to secret if not set by user + if s.Settings.StorageBackend == "" { + s.Settings.StorageBackend = "secret" + } + + // if there is no user-defined context name in the DSF(s), use the default context name + if s.Context == "" { + s.Context = defaultContextName + } + if len(target) > 0 { targetMap = map[string]bool{} for _, v := range target { diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index fbdbdb3c..609baba6 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -55,43 +55,47 @@ func decide(r *release, s *state) { logDecision("Release [ "+r.Name+" ] disabled", r.Priority, noop) return - } else { - if ok := isReleaseExisting(r, "deployed"); ok { - if !isProtected(r) { - inspectUpgradeScenario(r) // upgrade or move + } - } else { - logDecision("release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ - "you remove its protection.", r.Priority, noop) - } + if ok := isReleaseExisting(r, "deployed"); ok { + if !isProtected(r) { + inspectUpgradeScenario(r) // upgrade or move - } else if ok := isReleaseExisting(r, "deleted"); ok { - if !isProtected(r) { + } else { + logDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ + "you remove its protection.", r.Priority, noop) + } - rollbackRelease(r) // rollback + } else if ok := isReleaseExisting(r, "deleted"); ok { + if !isProtected(r) { - } else { - logDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ - "you remove its protection.", r.Priority, noop) - } + rollbackRelease(r) // rollback - } else if ok := isReleaseExisting(r, "failed"); ok { + } else { + logDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ + "you remove its protection.", r.Priority, noop) + } - if !isProtected(r) { + } else if ok := isReleaseExisting(r, "failed"); ok { - logDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is in FAILED state. Upgrade is scheduled!", r.Priority, change) - upgradeRelease(r) + if !isProtected(r) { - } else { - logDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ - "you remove its protection.", r.Priority, noop) - } - } else { + logDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is in FAILED state. Upgrade is scheduled!", r.Priority, change) + upgradeRelease(r) + } else { + logDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ + "you remove its protection.", r.Priority, noop) + } + } else { + // If there is no release in the cluster with this name and in this namespace, then install it! + if _, ok := currentState[fmt.Sprintf("%s-%s", r.Name, r.Namespace)]; !ok { installRelease(r) - + } else { + // A release with the same name and in the same namespace exists, but it has a different context label (managed by another DSF) + log.Fatal("Release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ] already exists but is not managed by the" + + " current context: [ " + s.Context + " ]. Applying changes will likely cause conflicts. Change the release name or namespace.") } - } } diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index 5ff935c4..d28f78f5 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -18,12 +18,13 @@ var currentState map[string]releaseState // releaseState represents the current state of a release type releaseState struct { - Name string - Revision int - Updated time.Time - Status string - Chart string - Namespace string + Name string + Revision int + Updated time.Time + Status string + Chart string + Namespace string + HelmsmanContext string } type releaseInfo struct { @@ -98,12 +99,13 @@ func buildState() { } revision, _ := strconv.Atoi(rel[i].Revision) currentState[fmt.Sprintf("%s-%s", rel[i].Name, rel[i].Namespace)] = releaseState{ - Name: rel[i].Name, - Revision: revision, - Updated: date, - Status: rel[i].Status, - Chart: rel[i].Chart, - Namespace: rel[i].Namespace, + Name: rel[i].Name, + Revision: revision, + Updated: date, + Status: rel[i].Status, + Chart: rel[i].Chart, + Namespace: rel[i].Namespace, + HelmsmanContext: getReleaseContext(rel[i].Name, rel[i].Namespace), } } } @@ -114,7 +116,7 @@ func buildState() { // If status is provided as an input [deployed, deleted, failed], then the search will verify the release status matches the search status. func isReleaseExisting(r *release, status string) bool { v, ok := currentState[fmt.Sprintf("%s-%s", r.Name, r.Namespace)] - if !ok { + if !ok || v.HelmsmanContext != s.Context { return false } diff --git a/internal/app/kube_helpers.go b/internal/app/kube_helpers.go index 9037b259..e6a9a458 100644 --- a/internal/app/kube_helpers.go +++ b/internal/app/kube_helpers.go @@ -303,15 +303,11 @@ func getKubeContext() bool { // labelResource applies Helmsman specific labels to Helm's state resources (secrets/configmaps) func labelResource(r *release) { if r.Enabled { - storageBackend := "secret" - - if s.Settings.StorageBackend != "" { - storageBackend = s.Settings.StorageBackend - } + storageBackend := s.Settings.StorageBackend cmd := command{ Cmd: "kubectl", - Args: []string{"label", storageBackend, "-n", r.Namespace, "-l", "owner=helm,name=" + r.Name, "MANAGED-BY=HELMSMAN", "NAMESPACE=" + r.Namespace, "--overwrite"}, + Args: []string{"label", storageBackend, "-n", r.Namespace, "-l", "owner=helm,name=" + r.Name, "MANAGED-BY=HELMSMAN", "NAMESPACE=" + r.Namespace, "HELMSMAN_CONTEXT=" + s.Context, "--overwrite"}, Description: "Applying Helmsman labels to [ " + r.Name + " ] release", } @@ -323,22 +319,37 @@ func labelResource(r *release) { } } +// getReleaseContext extracts the Helmsman release context from the helm storage driver objects (secret or configmap) labels +func getReleaseContext(releaseName string, namespace string) string { + storageBackend := s.Settings.StorageBackend + // kubectl get secrets -n test1 -l MANAGED-BY=HELMSMAN -o=jsonpath='{.items[0].metadata.labels.HELMSMAN_CONTEXT}' + // kubectl get secret sh.helm.release.v1.argo.v1 -n test1 -o=jsonpath='{.metadata.labels.HELMSMAN_CONTEXT}' + cmd := command{ + Cmd: "kubectl", + Args: []string{"get", storageBackend, "-n", namespace, "sh.helm.release.v1." + releaseName + ".v1", "-o=jsonpath={.metadata.labels.HELMSMAN_CONTEXT}"}, + Description: "Getting Helmsman context for [ " + releaseName + " ] release", + } + + exitCode, out, _ := cmd.exec(debug, verbose) + + if exitCode != 0 { + log.Fatal(out) + } + return strings.TrimSpace(out) +} + // getHelmsmanReleases returns a map of all releases that are labeled with "MANAGED-BY=HELMSMAN" // The releases are categorized by the namespaces in which they are deployed // The returned map format is: map[:map[:true]] func getHelmsmanReleases() map[string]map[releaseState]bool { var lines []string releases := make(map[string]map[releaseState]bool) - storageBackend := "secret" - - if s.Settings.StorageBackend != "" { - storageBackend = s.Settings.StorageBackend - } + storageBackend := s.Settings.StorageBackend for ns := range s.Namespaces { cmd := command{ Cmd: "kubectl", - Args: []string{"get", storageBackend, "-n", ns, "-l", "MANAGED-BY=HELMSMAN", "-o", "name"}, + Args: []string{"get", storageBackend, "-n", ns, "-l", "MANAGED-BY=HELMSMAN", "-l", "HELMSMAN_CONTEXT=" + s.Context, "-o", "name"}, Description: "Getting Helmsman-managed releases in namespace [ " + ns + " ]", } diff --git a/internal/app/main.go b/internal/app/main.go index 6c71574d..fd90c288 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -55,6 +55,7 @@ var noDefaultRepos bool const tempFilesDir = ".helmsman-tmp" const stableHelmRepo = "https://kubernetes-charts.storage.googleapis.com" const incubatorHelmRepo = "http://storage.googleapis.com/kubernetes-charts-incubator" +const defaultContextName = "default" func init() { Cli() diff --git a/internal/app/state.go b/internal/app/state.go index 702c9a47..031e8cc8 100644 --- a/internal/app/state.go +++ b/internal/app/state.go @@ -30,6 +30,7 @@ type state struct { Metadata map[string]string `yaml:"metadata"` Certificates map[string]string `yaml:"certificates"` Settings config `yaml:"settings"` + Context string `yaml:"context"` Namespaces map[string]namespace `yaml:"namespaces"` HelmRepos map[string]string `yaml:"helmRepos"` PreconfiguredHelmRepos []string `yaml:"preconfiguredHelmRepos"` @@ -163,6 +164,9 @@ func (s state) print() { fmt.Println("\nMetadata: ") fmt.Println("--------- ") printMap(s.Metadata, 0) + fmt.Println("\nContext: ") + fmt.Println("--------- ") + fmt.Println(s.Context) fmt.Println("\nCertificates: ") fmt.Println("--------- ") printMap(s.Certificates, 0) From 6b248dca6311c54d599ce8a6f7f86d1112d6a071 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Fri, 20 Dec 2019 22:11:41 +0100 Subject: [PATCH 0525/1127] update docs --- docs/how_to/misc/merge_desired_state_files.md | 70 +++++++++++++++++++ examples/example.toml | 4 ++ examples/example.yaml | 2 +- 3 files changed, 75 insertions(+), 1 deletion(-) diff --git a/docs/how_to/misc/merge_desired_state_files.md b/docs/how_to/misc/merge_desired_state_files.md index 01b9137b..c1e9e98c 100644 --- a/docs/how_to/misc/merge_desired_state_files.md +++ b/docs/how_to/misc/merge_desired_state_files.md @@ -42,3 +42,73 @@ One can then run the following to use the merged config of the above files, with ```shell $ helmsman -f common.toml -f nonprod.toml ... ``` + +## Distinguishing releases deployed from different Desired State Files + +When using multiple DSFs -and since Helmsman doesn't maintain any external state-, it has been possible for operations from one DSF to cause problems to releases deployed by other DSFs. A typical example is that releases deployed by other DSFs are considered `untracked` and get scheduled for deleting. Workarounds existed (e.g. using the `--keep-untracked-releases`, `--target` and `--group` flags). + +Starting from Helmsman v3.0.0-beta1, `context` is introduced to define the context in which a DSF is used. This context is used as the ID of that specific DSF and must be unique across the used DSFs. The context is then used to label the different releases to link them to the DSF they were first deployed from. These labels are then checked by Helmsman on each run to make sure operations are limited to releases from a specific context. + +Here is how it is used: + +* `prod.yaml`: +```yaml +settings: + kubeContext: "cluster" + storageBackend: "secret" + +namespaces: + infra: + protected: true + +... +``` + +* `infra.yaml`: +```yaml +context: infra-apps +settings: + kubeContext: "cluster" + storageBackend: "secret" + +namespaces: + infra: + protected: true + +apps: + external-dns: + namespace: infra + valuesFile: "./external-dns/values.yaml" + ... + + cert-issuer: + namespace: infra + valuesFile: "./cert-issuer/nonprod.yaml" + ... +... +``` + +* `prod.yaml`: +```yaml +context: prod-apps +settings: + kubeContext: "cluster" + storageBackend: "secret" + +namespaces: + prod: + protected: true + +apps: + my-prod-app: + namespace: prod + valuesFile: "./my-prod-app/values.yaml" + ... +... +``` + +### Limitations + +- If no context is provided in DSF (or merged DSFs), `default` is applied as a default context. This means any set of DSFs that don't define custom contexts can still operate on each other's releases (same behavior as in Helmsman 1.x). + +- When merging multiple DSFs, context from the firs DSF in the list gets overridden by the context in the last DSF. diff --git a/examples/example.toml b/examples/example.toml index 1c19b42e..d705edba 100644 --- a/examples/example.toml +++ b/examples/example.toml @@ -14,6 +14,10 @@ # caCrt = "s3://mybucket/ca.crt" # S3 bucket path # caKey = "../ca.key" # valid local file relative path +# context defines the context of this Desired State File. +# It is used to allow Helmsman identify which releases are managed by which DSF. +# Therefore, it is important that each DSF uses a unique context. +context= "test-infra" # defaults to "default" if not provided [settings] kubeContext = "minikube" # will try connect to this context first, if it does not exist, it will be created using the details below diff --git a/examples/example.yaml b/examples/example.yaml index ce5aae10..661eeefd 100644 --- a/examples/example.yaml +++ b/examples/example.yaml @@ -16,7 +16,7 @@ metadata: # context defines the context of this Desired State File. # It is used to allow Helmsman identify which releases are managed by which DSF. # Therefore, it is important that each DSF uses a unique context. -context: test-infra # defaults to file name if not provided +context: test-infra # defaults to "default" if not provided settings: kubeContext: "minikube" # will try connect to this context first, if it does not exist, it will be created using the details below From 4eaa0309ff50f4ca24c3b950c595b5aa7bba5884 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Fri, 20 Dec 2019 22:24:22 +0100 Subject: [PATCH 0526/1127] adding missing docs --- docs/desired_state_specification.md | 11 +++++++++++ docs/how_to/misc/merge_desired_state_files.md | 15 ++------------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 7a0d8847..24c0d283 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -8,6 +8,7 @@ This document describes the specification for how to write your Helm charts' des - [Metadata](#metadata) [Optional] -- metadata for any human reader of the desired state file. - [Certificates](#certificates) [Optional] -- only needed when you want Helmsman to connect kubectl to your cluster for you. +- [Context](#context) [optional] -- define the context in which a DSF is used. - [Settings](#settings) [Optional] -- data about your k8s cluster and how to deploy Helm on it if needed. - [Namespaces](#namespaces) -- defines the namespaces where you want your Helm charts to be deployed. - [Helm Repos](#helm-repos) [Optional] -- defines the repos where you want to get Helm charts from. @@ -74,6 +75,16 @@ certificates: caClient: "../path/to/my/local/client-certificate.crt" #caClient: "$CA_CLIENT" ``` +## Context + +Optional : Yes. + +Synopsis: defines the context in which a DSF is used. This context is used as the ID of that specific DSF and must be unique across the used DSFs. If not defined, `default` is used. Check [here](how_to/misc/merge_desired_state_files.md) for more details on the limitations. + +```yaml +context: prod-apps +... +``` ## Settings diff --git a/docs/how_to/misc/merge_desired_state_files.md b/docs/how_to/misc/merge_desired_state_files.md index c1e9e98c..32d585e1 100644 --- a/docs/how_to/misc/merge_desired_state_files.md +++ b/docs/how_to/misc/merge_desired_state_files.md @@ -51,19 +51,6 @@ Starting from Helmsman v3.0.0-beta1, `context` is introduced to define the conte Here is how it is used: -* `prod.yaml`: -```yaml -settings: - kubeContext: "cluster" - storageBackend: "secret" - -namespaces: - infra: - protected: true - -... -``` - * `infra.yaml`: ```yaml context: infra-apps @@ -112,3 +99,5 @@ apps: - If no context is provided in DSF (or merged DSFs), `default` is applied as a default context. This means any set of DSFs that don't define custom contexts can still operate on each other's releases (same behavior as in Helmsman 1.x). - When merging multiple DSFs, context from the firs DSF in the list gets overridden by the context in the last DSF. + +- If multiple DSFs use the same context name, they will mess up each other's releases. From 722e4a594f5e19a79455c3a5abb679cd479622a4 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Fri, 20 Dec 2019 23:09:28 +0100 Subject: [PATCH 0527/1127] fix tests --- examples/example.toml | 13 +++++++------ internal/app/decision_maker_test.go | 24 +++++++++++++++++++++--- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/examples/example.toml b/examples/example.toml index d705edba..a2137753 100644 --- a/examples/example.toml +++ b/examples/example.toml @@ -1,4 +1,10 @@ -# version: v1.6.2 +# version: v3.0.0-beta1 + +# context defines the context of this Desired State File. +# It is used to allow Helmsman identify which releases are managed by which DSF. +# Therefore, it is important that each DSF uses a unique context. +context= "test-infra" # defaults to "default" if not provided + # metadata -- add as many key/value pairs as you want [metadata] org = "example.com/${ORG_PATH}/" @@ -14,11 +20,6 @@ # caCrt = "s3://mybucket/ca.crt" # S3 bucket path # caKey = "../ca.key" # valid local file relative path -# context defines the context of this Desired State File. -# It is used to allow Helmsman identify which releases are managed by which DSF. -# Therefore, it is important that each DSF uses a unique context. -context= "test-infra" # defaults to "default" if not provided - [settings] kubeContext = "minikube" # will try connect to this context first, if it does not exist, it will be created using the details below # username = "admin" diff --git a/internal/app/decision_maker_test.go b/internal/app/decision_maker_test.go index 31017dff..8a80c3b3 100644 --- a/internal/app/decision_maker_test.go +++ b/internal/app/decision_maker_test.go @@ -228,8 +228,9 @@ func Test_decide(t *testing.T) { func Test_decide_group(t *testing.T) { type args struct { - r *release - s *state + r *release + s *state + currentState *map[string]releaseState } tests := []struct { name string @@ -248,6 +249,12 @@ func Test_decide_group(t *testing.T) { Enabled: true, }, s: &state{}, + currentState: &map[string]releaseState{ + "release1-namespace": { + Namespace: "namespace", + Chart: "chart-1.0.0", + }, + }, }, want: ignored, }, @@ -261,7 +268,17 @@ func Test_decide_group(t *testing.T) { Enabled: true, Group: "run-me", }, - s: &state{}, + s: &state{ + Context: "default", + }, + currentState: &map[string]releaseState{ + "release2-namespace": { + Name: "release2", + Namespace: "namespace", + Chart: "chart-1.0.0", + HelmsmanContext: "some-other-context", + }, + }, }, want: create, }, @@ -271,6 +288,7 @@ func Test_decide_group(t *testing.T) { t.Run(tt.name, func(t *testing.T) { groupMap = make(map[string]bool) targetMap = make(map[string]bool) + currentState = *tt.args.currentState for _, target := range tt.targetFlag { groupMap[target] = true From 9c8902bc4d8ff4724d7dd53b0413c9d0eadbc78c Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Sat, 21 Dec 2019 13:58:41 +0100 Subject: [PATCH 0528/1127] deprecate the Purge option --- internal/app/decision_maker_test.go | 3 --- internal/app/helm_helpers_test.go | 6 ------ internal/app/release.go | 2 -- internal/app/release_test.go | 14 -------------- 4 files changed, 25 deletions(-) diff --git a/internal/app/decision_maker_test.go b/internal/app/decision_maker_test.go index 8a80c3b3..707d4d76 100644 --- a/internal/app/decision_maker_test.go +++ b/internal/app/decision_maker_test.go @@ -25,7 +25,6 @@ func Test_getValuesFiles(t *testing.T) { Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", - Purge: true, Test: true, }, //s: st, @@ -43,7 +42,6 @@ func Test_getValuesFiles(t *testing.T) { Chart: "repo/chartX", Version: "1.0", ValuesFiles: []string{"../../tests/values.yaml"}, - Purge: true, Test: true, }, //s: st, @@ -61,7 +59,6 @@ func Test_getValuesFiles(t *testing.T) { Chart: "repo/chartX", Version: "1.0", ValuesFiles: []string{"../../tests/values.yaml", "../../tests/values2.yaml"}, - Purge: true, Test: true, }, //s: st, diff --git a/internal/app/helm_helpers_test.go b/internal/app/helm_helpers_test.go index 4686b9bc..b23b795c 100644 --- a/internal/app/helm_helpers_test.go +++ b/internal/app/helm_helpers_test.go @@ -52,7 +52,6 @@ func Test_validateReleaseCharts(t *testing.T) { ValuesFiles: []string{}, SecretsFile: "", SecretsFiles: []string{}, - Purge: false, Test: false, Protected: false, Wait: false, @@ -82,7 +81,6 @@ func Test_validateReleaseCharts(t *testing.T) { ValuesFiles: []string{}, SecretsFile: "", SecretsFiles: []string{}, - Purge: false, Test: false, Protected: false, Wait: false, @@ -112,7 +110,6 @@ func Test_validateReleaseCharts(t *testing.T) { ValuesFiles: []string{}, SecretsFile: "", SecretsFiles: []string{}, - Purge: false, Test: false, Protected: false, Wait: false, @@ -142,7 +139,6 @@ func Test_validateReleaseCharts(t *testing.T) { ValuesFiles: []string{}, SecretsFile: "", SecretsFiles: []string{}, - Purge: false, Test: false, Protected: false, Wait: false, @@ -172,7 +168,6 @@ func Test_validateReleaseCharts(t *testing.T) { ValuesFiles: []string{}, SecretsFile: "", SecretsFiles: []string{}, - Purge: false, Test: false, Protected: false, Wait: false, @@ -202,7 +197,6 @@ func Test_validateReleaseCharts(t *testing.T) { ValuesFiles: []string{}, SecretsFile: "", SecretsFiles: []string{}, - Purge: false, Test: false, Protected: false, Wait: false, diff --git a/internal/app/release.go b/internal/app/release.go index 063a67a4..9b9031de 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -19,7 +19,6 @@ type release struct { ValuesFiles []string `yaml:"valuesFiles"` SecretsFile string `yaml:"secretsFile"` SecretsFiles []string `yaml:"secretsFiles"` - Purge bool `yaml:"purge"` Test bool `yaml:"test"` Protected bool `yaml:"protected"` Wait bool `yaml:"wait"` @@ -138,7 +137,6 @@ func (r release) print() { fmt.Println("\tversion : ", r.Version) fmt.Println("\tvaluesFile : ", r.ValuesFile) fmt.Println("\tvaluesFiles : ", strings.Join(r.ValuesFiles, ",")) - fmt.Println("\tpurge : ", r.Purge) fmt.Println("\ttest : ", r.Test) fmt.Println("\tprotected : ", r.Protected) fmt.Println("\twait : ", r.Wait) diff --git a/internal/app/release_test.go b/internal/app/release_test.go index 169fa4af..fd5aad47 100644 --- a/internal/app/release_test.go +++ b/internal/app/release_test.go @@ -36,7 +36,6 @@ func Test_validateRelease(t *testing.T) { Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", - Purge: true, Test: true, }, s: st, @@ -54,7 +53,6 @@ func Test_validateRelease(t *testing.T) { Chart: "repo/chartX", Version: "1.0", ValuesFile: "xyz.yaml", - Purge: true, Test: true, }, s: st, @@ -72,7 +70,6 @@ func Test_validateRelease(t *testing.T) { Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.xml", - Purge: true, Test: true, }, s: st, @@ -90,7 +87,6 @@ func Test_validateRelease(t *testing.T) { Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", - Purge: true, Test: true, }, s: st, @@ -108,7 +104,6 @@ func Test_validateRelease(t *testing.T) { Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", - Purge: true, Test: true, }, s: st, @@ -126,7 +121,6 @@ func Test_validateRelease(t *testing.T) { Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", - Purge: true, Test: true, }, s: st, @@ -144,7 +138,6 @@ func Test_validateRelease(t *testing.T) { Chart: "chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", - Purge: true, Test: true, }, s: st, @@ -162,7 +155,6 @@ func Test_validateRelease(t *testing.T) { Chart: "", Version: "1.0", ValuesFile: "../../tests/values.yaml", - Purge: true, Test: true, }, s: st, @@ -180,7 +172,6 @@ func Test_validateRelease(t *testing.T) { Chart: "repo/chartX", Version: "", ValuesFile: "../../tests/values.yaml", - Purge: true, Test: true, }, s: st, @@ -198,7 +189,6 @@ func Test_validateRelease(t *testing.T) { Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", - Purge: true, Test: true, }, s: st, @@ -217,7 +207,6 @@ func Test_validateRelease(t *testing.T) { Version: "1.0", ValuesFile: "../../tests/values.yaml", ValuesFiles: []string{"xyz.yaml"}, - Purge: true, Test: true, }, s: st, @@ -235,7 +224,6 @@ func Test_validateRelease(t *testing.T) { Chart: "repo/chartX", Version: "1.0", ValuesFiles: []string{"xyz.yaml"}, - Purge: true, Test: true, }, s: st, @@ -253,7 +241,6 @@ func Test_validateRelease(t *testing.T) { Chart: "repo/chartX", Version: "1.0", ValuesFiles: []string{"./../../tests/values.yaml", "../../tests/values2.yaml"}, - Purge: true, Test: true, }, s: st, @@ -271,7 +258,6 @@ func Test_validateRelease(t *testing.T) { Chart: "repo/chartX", Version: "1.0", ValuesFiles: []string{"./../../tests/values.yaml", "../../tests/values2.yaml"}, - Purge: true, Test: true, Set: map[string]string{"some_var": "$SOME_VAR"}, }, From 4727ef0a098c2b094aaa91ffe5dbeac5f0b8f389 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Sat, 21 Dec 2019 13:59:22 +0100 Subject: [PATCH 0529/1127] polishing docs and examples --- docs/best_practice.md | 17 +- docs/cmd_reference.md | 23 +-- docs/deployment_strategies.md | 19 +- docs/desired_state_specification.md | 91 ++-------- docs/how_to/README.md | 15 +- docs/how_to/apps/basic.md | 27 +-- docs/how_to/apps/destroy.md | 4 +- docs/how_to/apps/helm_tests.md | 6 +- docs/how_to/apps/moving_across_namespaces.md | 10 +- docs/how_to/apps/multiple_values_files.md | 9 +- docs/how_to/apps/order.md | 6 +- docs/how_to/apps/override_namespaces.md | 6 +- docs/how_to/apps/secrets.md | 10 +- docs/how_to/deployments/ci.md | 8 +- docs/how_to/helm_repos/default.md | 6 +- docs/how_to/helm_repos/local.md | 27 --- docs/how_to/misc/helmsman_on_windows10.md | 19 -- .../misc/limit-deployment-to-specific-apps.md | 10 +- ...it-deployment-to-specific-group-of-apps.md | 10 +- docs/how_to/misc/merge_desired_state_files.md | 2 +- .../how_to/misc/multitenant_clusters_guide.md | 164 ------------------ .../use-hiera-eyaml-as-secrets-encryption.md | 4 +- docs/how_to/tiller/custom_tiller_role.md | 37 ---- .../deploy_apps_with_specific_tiller.md | 36 ---- docs/how_to/tiller/existing.md | 21 --- docs/how_to/tiller/multitenancy.md | 56 ------ .../tiller/prevent_tiller_in_kube_system.md | 19 -- docs/how_to/tiller/shared.md | 83 --------- docs/migrating_to_v1.4.0-rc.md | 8 - examples/example.toml | 34 ++-- examples/example.yaml | 18 +- 31 files changed, 124 insertions(+), 681 deletions(-) delete mode 100644 docs/how_to/misc/helmsman_on_windows10.md delete mode 100644 docs/how_to/misc/multitenant_clusters_guide.md delete mode 100644 docs/how_to/tiller/custom_tiller_role.md delete mode 100644 docs/how_to/tiller/deploy_apps_with_specific_tiller.md delete mode 100644 docs/how_to/tiller/existing.md delete mode 100644 docs/how_to/tiller/multitenancy.md delete mode 100644 docs/how_to/tiller/prevent_tiller_in_kube_system.md delete mode 100644 docs/how_to/tiller/shared.md delete mode 100644 docs/migrating_to_v1.4.0-rc.md diff --git a/docs/best_practice.md b/docs/best_practice.md index 92b9e02f..c72e4f9e 100644 --- a/docs/best_practice.md +++ b/docs/best_practice.md @@ -1,20 +1,27 @@ --- -version: v1.0.0 +version: v3.0.0-beta1 --- # Best Practice When using Helmsman, we recommend the following best practices: -- Add useful metadata in your desired state files (DSFs) so that others (who have access to them) can make understandable what your DSF is for. We recommend the following metadata: organization, maintainer (name and email), and description/purpose. +- Add useful metadata in your desired state files (DSFs) so that others (who have access to them) can understand what your DSF is for. We recommend the following metadata: organization, maintainer (name and email), and description/purpose. -- Use environment variables to pass K8S connection secrets (password, certificates paths on the local system or AWS/GCS bucket urls and the API URI). This keeps all sensitive information out of your version controlled source code. +- Define `context` (see [the DSF spec](desired_state_specification.md#context)) for each DSF. This helps prevent different DSFs from operating on each other's releases. -- Define certain namespaces (e.g, production) as protected namespaces (supported in v1.0.0+) and deploy your production-ready releases there. +- Store your DSFs in git (or any other VCS) so that you have an audit trail of your deployments. You can also rollback to a previous state by going back to previous commits. +> Rollback can be more complex regarding application data. + +- Do not store secrets in your DSFs! Use one of [the supported ways to pass secrets to your releases](how_to/apps/secrets.md). + +- To protect against accidental operations, define certain namespaces (e.g, production) as protected namespaces (supported in v1.0.0+) and deploy your production-ready releases there. - If you use multiple desired state files (DSFs) with the same cluster, make sure your namespace protection definitions are identical across all DSFs. +- When using multiple DSFs, make sure that apps managed in the same namespace are in one DSF. This avoids the need for defining the same namespace (with its settings) across multiple DSFs + - Don't maintain the same release in multiple DSFs. -- While the decision on how many DSFs to use and what each can contain is up to you and depends on your case, we recommend coming up with your own rules for how to split them. +- While the decision on how many DSFs to use and what each can contain is up to you and depends on your case, we recommend coming up with your own rules for how to split them. For example, you can have one for infra (3rd party tools), one for staging, and one for production apps. diff --git a/docs/cmd_reference.md b/docs/cmd_reference.md index f706b471..08edc91b 100644 --- a/docs/cmd_reference.md +++ b/docs/cmd_reference.md @@ -1,5 +1,5 @@ --- -version: v1.11.1 +version: v3.0.0-beta1 --- # CMD reference @@ -11,14 +11,11 @@ This lists available CMD options in Helmsman: `--apply` apply the plan directly. - `--apply-labels` - apply Helmsman labels to Helm state for all defined apps. - `--debug` show execution logs. `--destroy` - delete all deployed releases. Purge delete is used if the purge option is set to true for the releases. + delete all deployed releases. `--diff-context num` number of lines of context to show around changes in helm diff output. @@ -36,7 +33,7 @@ This lists available CMD options in Helmsman: use --force when upgrading helm releases. May cause resources to be recreated. `--keep-untracked-releases` - keep releases that are managed by Helmsman and are no longer tracked in your desired state. + keep releases that are managed by Helmsman from the used DSFs in the command, and are no longer tracked in your desired state. `--kubeconfig` path to the kubeconfig file to use for CLI requests. @@ -53,8 +50,8 @@ This lists available CMD options in Helmsman: `--no-env-subst` turn off environment substitution globally. - `no-env-values-subst` - turn off environment substitution in values files only. + `--no-env-values-subst` + turn off environment substitution in values files only. (default true). `--no-fancy` don't display the banner and don't use colors. @@ -62,6 +59,12 @@ This lists available CMD options in Helmsman: `--no-ns` don't create namespaces. + `-no-ssm-subst` + turn off SSM parameter substitution globally. + + `-no-ssm-values-subst` + turn off SSM parameter substitution in values files only (default true). + `--ns-override string` override defined namespaces with this one. @@ -72,7 +75,7 @@ This lists available CMD options in Helmsman: skip desired state validation. `--suppress-diff-secrets` - don't show secrets in helm diff output. + don't show secrets in helm diff output. (default true). `--target` limit execution to specific app. @@ -83,7 +86,7 @@ This lists available CMD options in Helmsman: `--update-deps` run 'helm dep up' for local chart - `-v` show the version. + `--v` show the version. `--verbose` show verbose execution logs. diff --git a/docs/deployment_strategies.md b/docs/deployment_strategies.md index c5d141b0..e5bbbfda 100644 --- a/docs/deployment_strategies.md +++ b/docs/deployment_strategies.md @@ -1,5 +1,5 @@ --- -version: v1.1.0 +version: v3.0.0-beta1 --- # Deployment Strategies @@ -135,10 +135,21 @@ If you need supporting applications (charts) for your application (e.g, reverse ## Notes on using multiple Helmsman desired state files for the same cluster -Helmsman works with a single desired state file at a time (starting from v1.5.0, you can pass multiple desired state files which get merged at runtime. See the [docs](how_to/misc/merge_desired_state_files.md)) and does not maintain a state anywhere. i.e. it does not have any context awareness about other desired state files used with the same cluster. For this reason, it is the user's responsibility to make sure that: +Helmsman v3.0.0-beta1 introduces the `context` stanza. +When having multiple DSFs operating on different releases, it is essential to use the `context` stanza in each DSF to define what context the DSF covers. The user-provided value for `context` is used by Helmsman to label and distinguish which DSF manages which deployed releases in the cluster. This way, each helmsman operation will only operate on releases within the context defined in the DSF. -- no releases have the same name in different desired state files pointing to the same cluster. If such conflict exists, Helmsman will not raise any errors but that release would be subject to unexpected behavior. +When having multiple DSFs be aware of the following: -- protected namespaces are defined protected in all the desired state files. Otherwise, namespace protection can be accidentally compromised if the same release name is used across multiple desired state files. +- If no context is provided in the DSF (or merged DSFs), `default` is applied as a default context. This means any set of DSFs that don't define custom contexts can still operate on each other's releases (same behavior as in Helmsman 1.x). + + - If you don't define context in your DSFs, you would need to use the `--keep-untracked-releases` flag to avoid different DSFs deleting each other's releases. + +- When merging multiple DSFs in one Helmsman operation, context from the firs DSF in the list gets overridden by the context in the last DSF. + + - If multiple DSFs use the same context name, they will mess up each other's releases. + +- If two releases from two different DSFs (each with its own context) have the same name and namespace, Helmsman will only allow the first one of them to be installed. The second will be blocked by Helmsman. + +- If you deploy releases from multiple DSF to one namespace (not recommended!), that namespace's protection config does not automatically cascade between DSFs. You will have to enable the protection in each of the DSFs. Also please refer to the [best practice](best_practice.md) document. diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 24c0d283..4d32fd10 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -1,5 +1,5 @@ --- -version: v1.12.0 +version: v3.0.0-beta1 --- # Helmsman desired state specification @@ -90,9 +90,9 @@ context: prod-apps Optional : Yes. -Synopsis: provides settings for connecting to your k8s cluster and configuring Helm's Tiller in the cluster. +Synopsis: provides settings for connecting to your k8s cluster. -> If you don't provide the `settings` stanza, helmsman would use your current kube context and will deploy Tiller(s) without RBAC service accounts. +> If you don't provide the `settings` stanza, helmsman would use your current kube context. Options: - **kubeContext** : the kube context you want Helmsman to use or create. Helmsman will try connect to this context first, if it does not exist, it will try to create it (i.e. connect to a k8s cluster) using the options below. @@ -104,16 +104,13 @@ The following options can be skipped if your kubectl context is already created - **clusterURI** : the URI for your cluster API or the name of an environment variable (starting with `$`) containing the URI. - **bearerToken**: whether you want helmsman to connect to the cluster using a bearer token. Default is `false` - **bearerTokenPath**: optional. If bearer token is used, you can specify a custom location for the token file. -- **serviceAccount**: the name of the service account to use to deploy Helm Tiller. This should have enough permissions to allow Helm to work. If the service account does not exist, it will be created. More details can be found in [helm's RBAC guide](https://github.com/kubernetes/helm/blob/master/docs/rbac.md) -- **storageBackend** : by default Helm stores release information in configMaps, using secrets is for storage is recommended for security. Setting this flag to `secret` will deploy/upgrade Tiller with the `--storage=secret`. Other values will be skipped and configMaps will be used. +- **storageBackend** : by default Helm v3 stores release information in secrets, using secrets for storage is recommended for security. - **slackWebhook** : a [Slack](http://slack.com) Webhook URL to receive Helmsman notifications. This can be passed directly or in an environment variable. - **reverseDelete** : if set to `true` it will reverse the priority order whilst deleting. -- **tillerless** : setting it to `true` will use [helm-tiller](https://rimusz.net/tillerless-helm) plugin instead of installing Tillers in namespaces. It disables many of the parameters for sections below. - **eyamlEnabled** : if set to `true' it will use [hiera-eyaml](https://github.com/voxpupuli/hiera-eyaml) to decrypt secret files instead of using default helm-secrets based on sops - **eyamlPrivateKeyPath** : if set with path to the eyaml private key file, it will use it instead of looking for default one in ./keys directory relative to where Helmsman were run. It needs to be defined in conjunction with eyamlPublicKeyPath. - **eyamlPublicKeyPath** : if set with path to the eyaml public key file, it will use it instead of looking for default one in ./keys directory relative to where Helmsman were run. It needs to be defined in conjunction with eyamlPrivateKeyPath. -> If you use `storageBackend` with a Tiller that has been previously deployed with configMaps as storage backend, you need to migrate your release information from the configMap to the new secret on your own. Helm does not support this yet. Example: @@ -155,66 +152,29 @@ settings: Optional : No. -Synopsis: defines the namespaces to be used/created in your k8s cluster and whether they are protected or not. It also defines if Tiller should be deployed in these namespaces and with what configurations (TLS and service account). You can add as many namespaces as you like. +Synopsis: defines the namespaces to be used/created in your k8s cluster and whether they are protected or not. You can add as many namespaces as you need. If a namespace does not already exist, Helmsman will create it. -> All Tillers-related params here will be ignored when `tilerless` is set in `settings` section. - Options: - **protected** : defines if a namespace is protected (true or false). Default false. > For the definition of what a protected namespace means, check the [protection guide](how_to/misc/protect_namespaces_and_releases.md) -- **installTiller**: defines if Tiller should be deployed in this namespace or not. Default is false. Any chart desired to be deployed into a namespace with a Tiller deployed, will be deployed using that Tiller and not the one in kube-system unless you use the `TillerNamespace` option (see the [Apps](#apps) section below) to use another Tiller. -> By default Tiller will be deployed into `kube-system` even if you don't define kube-system in the namespaces section. To prevent deploying Tiller into `kube-system, add kube-system in your namespaces section and set its installTiller to false. -- **tillerMaxHistory**: specifies an int value of the maximum number of revisions saved per release by Tiller. -> In order to set the kube-system's Tiller's (a default one, main Tiller) max history, define namespace kube-system and set tillerMaxHistory along with installTiller: true -- **tillerRole**: specifies the role to use. If 'cluster-admin' a clusterrolebinding will be used else a role with a single namespace scope will be created and bound with a rolebinding. -- **tillerRoleTemplateFile**: relative path to file templating custom Tiller role. If `installTiller` is true and `tillerRole` is not `cluster-admin`, then helmsman will create namespace specific Tiller role based on the template file passed with this parameter. When `tillerRole` is empty string, role name defaults to `helmsman-tiller`. - - If `tillerRoleTemplateFile` is set, it will always try to create namespace-scoped Tiller with conditions like below: - - if `tillerRole` was not set, default name of `helmsman-tiller` will be used to createa new Role - - if `tillerServiceAccount` was not set, it will create Service Account with default name of `helmsman` - -- **useTiller**: defines that you would like to use an existing Tiller from that namespace. Can't be set together with `installTiller` -- **labels** : defines labels to be added to the namespace, doesn't remove existing labels but updates them if the label key exists with any other different value. You can define any key/value pairs. Default is empty. -- **annotations** : defines annotations to be added to the namespace. It behaves the same way as the labels option. -- **limits** : defines a [LimitRange](https://kubernetes.io/docs/tasks/administer-cluster/manage-resources/memory-default-namespace/) to be configured on the namespace -- **tillerServiceAccount**: defines what service account to use when deploying Tiller. If this is not set, the following options are considered: +- **labels** : defines labels to be added to the namespace, doesn't remove existing labels but updates them if the label key exists with any other different value. You can define any key/value pairs. Default is empty. - 1. If the `serviceAccount` defined in the `settings` section exists in the namespace you want to deploy Tiller in, it will be used. - 2. If you defined `serviceAccount` in the `settings` section and it does not exist in the namespace you want to deploy Tiller in, Helmsman creates the service account in that namespace and binds it to a (cluster)role. If the namespace is kube-system and `tillerRole` is unset or is set to cluster-admin, the service account is bound to `cluster-admin` clusterrole. Otherwise, if you specified a `tillerRole`, a new role with that name is created and bound to the service account with rolebinding. If `tillerRole` is unset (for namespaces other than kube-system), the role is called `helmsman-tiller` and is created in the specified namespace to only gives access to that namespace. The custom role is created from a [yaml template](../data/role.yaml) if no `tillerRoleTemplateFile` was set, or it will use the `tillerRoleTemplateFile` (of the same structure as [yaml template](../data/role.yaml)) to create Role from. +- **annotations** : defines annotations to be added to the namespace. It behaves the same way as the labels option. - > If `installTiller` is not defined or set to false, this flag is ignored. +- **limits** : defines a [LimitRange](https://kubernetes.io/docs/tasks/administer-cluster/manage-resources/memory-default-namespace/) to be configured on the namespace -- The following options are `ALL` needed for deploying Tiller with TLS enabled. If they are not all defined, they will be ignored and Tiller will be deployed without TLS. All of these options can be provided as either: a valid local file path, a valid GCS or S3 or Azure bucket URI or an environment variable containing a file path or bucket URI. - - **caCert**: the CA certificate. - - **tillerCert**: the SSL certificate for Tiller. - - **tillerKey**: the SSL certificate private key for Tiller. - - **clientCert**: the SSL certificate for the Helm client. - - **clientKey**: the SSL certificate private key for the Helm client. Example: ```toml [namespaces] -# to prevent deploying Tiller into kube-system, use the two lines below -# [namespaces.kube-system] -# installTiller = false # this line can be omitted since installTiller defaults to false [namespaces.staging] [namespaces.dev] -useTiller = true # use a Tiller which has been deployed in dev namespace protected = false [namespaces.production] protected = true -installTiller = true -tillerMaxHistory = 10 -tillerServiceAccount = "tiller-production" -tillerRoleTemplateFile = "../roles/helmsman-tiller.yaml" -caCert = "secrets/ca.cert.pem" -tillerCert = "secrets/tiller.cert.pem" -tillerKey = "$TILLER_KEY" # where TILLER_KEY=secrets/tiller.key.pem -clientCert = "gs://mybucket/mydir/helm.cert.pem" -clientKey = "s3://mybucket/mydir/helm.key.pem" [namespaces.production.labels] env = "prod" [namespaces.production.annotations] @@ -235,24 +195,11 @@ memory = "300Mi" ```yaml namespaces: - # to prevent deploying Tiller into kube-system, use the two lines below - # kube-system: - # installTiller: false # this line can be omitted since installTiller defaults to false staging: dev: protected: false - useTiller: true # use a Tiller which has been deployed in dev namespace production: protected: true - installTiller: true - tillerMaxHistory: 10 - tillerServiceAccount: "tiller-production" - tillerRoleTemplateFile: "../roles/helmsman-tiller.yaml" - caCert: "secrets/ca.cert.pem" - tillerCert: "secrets/tiller.cert.pem" - tillerKey: "$TILLER_KEY" # where TILLER_KEY=secrets/tiller.key.pem - clientCert: "gs://mybucket/mydir/helm.cert.pem" - clientKey: "s3://mybucket/mydir/helm.key.pem" limits: - type: Container default: @@ -274,18 +221,20 @@ namespaces: Optional : Yes. -Synopsis: defines the Helm repos where your charts can be found. You can add as many repos as you like. Public repos can be added without any additional setup. Private repos require authentication. +Synopsis: defines the Helm repos where your charts can be found. You can add as many repos as you need. Public repos can be added without any additional setup. Private repos require authentication. > As of version v0.2.0, both AWS S3 and Google GCS buckets can be used for private repos (using the [Helm S3](https://github.com/hypnoglow/helm-s3) and [Helm GCS](https://github.com/nouney/helm-gcs) plugins). > As of version v1.8.0, you can use private repos with basic auth and you can use pre-configured helm repos. -Authenticating to private helm repos: +Authenticating to private cloud helm repos: - **For S3 repos**: you need to have valid AWS access keys in your environment variables. See [here](https://github.com/hypnoglow/helm-s3#note-on-aws-authentication) for more details. - **For GCS repos**: check [here](https://www.terraform.io/docs/providers/google/index.html#authentication-json-file) for getting the required authentication file. Once you have the file, you have two options, either: - set `GOOGLE_APPLICATION_CREDENTIALS` environment variable to contain the absolute path to your Google cloud credentials.json file. - Or, set `GCLOUD_CREDENTIALS` environment variable to contain the content of the credentials.json file. +> You can also provide basic auth to access private repos that support basic auth. See the example below. + Options: - you can define any key/value pair where the key is the repo name and value is a valid URI for the repo. Basic auth info can be added in the repo URL as in the example below. @@ -317,6 +266,8 @@ Synopsis: defines the list of helm repositories that the helmsman will consider The primary use-case is if you have some helm repositories that require HTTP basic authentication and you don't want to store the password in the desired state file or as an environment variable. In this case you can execute the following sequence to have those repositories configured: +> In this case you will need to execute `helm repo add myrepo1 --username= --password=` manually first. + Set up the helmsman configuration: ```toml @@ -329,10 +280,10 @@ preconfiguredHelmRepos: - myrepo2 ``` -> In this case you will manually need to execute `helm repo add myrepo1 --username= --password=` - ## AppsTemplates +> This feature is only for YAML. + Optional : Yes. Synopsis: allows for YAML (TOML has no variable reference support) object creation, that is ignored by state file importer, but can be used as a reference with YAML anchors to not repeat yourself. Read [this](https://blog.daemonl.com/2016/02/yaml.html) example about YAML anchors. @@ -383,7 +334,7 @@ Optional : Yes. Synopsis: defines the releases (instances of Helm charts) you would like to manage in your k8s cluster. -Releases must have unique names which are defined under `apps`. Example: in `[apps.jenkins]`, the release name will be `jenkins` and it should be unique within the Tiller which manages it . +Releases must have unique names which are defined under `apps`. Example: in `[apps.jenkins]`, the release name will be `jenkins` and it should be unique within the DSF. Options: @@ -395,13 +346,6 @@ Options: **Optional** - **group** : group name this apps belongs to. It has no effect until Helmsman's flag `-group` is passed. Check this [doc](how_to/misc/limit-deployment-to-specific-group-of-apps.md) for more details. -- **tillerNamespace** : which Tiller to use for deploying this release. This is available starting from v1.4.0-rc The decision on which Tiller to use for deploying a release follows the following criteria: - 1. If `tillerNamespace`is explicitly defined, it is used. - 2. If `tillerNamespace`is not defined and the namespace in which the release will be deployed has a Tiller installed by Helmsman (i.e. has `installTiller set to true` in the [Namespaces](#namespaces) section), Tiller in that namespace is used. - 3. If none of the above, the shared Tiller in `kube-system` is used. -> This parameter is ignored when `tilerless` is set in `settings` section and `namespace` of this app is taken for tilerless plugin. - -- **name** : the Helm release name. Releases must have unique names within a Helm Tiller. If not set, the release name will be taken from the app identifier in your desired state file. e.g, for ` apps.jenkins ` the release name will be `jenkins`. - **description** : a release metadata for human readers. - **valuesFile** : a valid path to custom Helm values.yaml file. File extension must be `yaml`. Cannot be used with valuesFiles together. Leaving it empty uses the default chart values. - **valuesFiles** : array of valid paths to custom Helm values.yaml file. File extension must be `yaml`. Cannot be used with valuesFile together. Leaving it empty uses the default chart values. @@ -410,7 +354,6 @@ Options: - **secretsFiles** : array of valid paths to custom Helm secrets.yaml file. File extension must be `yaml`. Cannot be used with secretsFile together. Leaving it empty uses the default chart secrets. > The secrets file(s) path is resolved when the DSF yaml/toml file is loaded, relative to the path that the dsf was loaded from. > To use the secrets files you must have the helm-secrets plugin -- **purge** : defines whether to use the Helm purge flag when deleting the release. Default is false. - **test** : defines whether to run the chart tests whenever the release is installed. Default is false. - **protected** : defines if the release should be protected against changes. Namespace-level protection has higher priority than this flag. Check the [protection guide](how_to/misc/protect_namespaces_and_releases.md) for more details. Default is false. - **wait** : defines whether Helmsman should block execution until all k8s resources are in a ready state. Default is false. diff --git a/docs/how_to/README.md b/docs/how_to/README.md index 789b6252..2980fb13 100644 --- a/docs/how_to/README.md +++ b/docs/how_to/README.md @@ -1,7 +1,9 @@ # How To Guides -This page contains a list of guides on how to use Helmsman. +This page contains a list of guides on how to use Helmsman. + +It is recommended that you also check the [DSF spec](../desired_state_specification.md), [cmd reference](../cmd_reference.md), and the [best practice guide](../best_practice.md). - Connecting to Kubernetes clusters - [Using an existing kube context](settings/existing_kube_context.md) @@ -13,13 +15,6 @@ This page contains a list of guides on how to use Helmsman. - [Label namespaces](namespaces/labels_and_annotations.md) - [Set resource limits for namespaces](namespaces/limits.md) - [Protecting namespaces](namespaces/protection.md) -- Deploying Helm Tiller - - [Using existing Tillers](tiller/existing.md) - - [Deploy shared Tiller in kube-system](tiller/shared.md) - - [Prevent Deploying Tiller in kube-system](tiller/prevent_tiller_in_kube_system.md) - - [Deploy Multiple Tillers with custom setup for each](tiller/multitenancy.md) - - [Deploy apps with specific Tillers](tiller/deploy_apps_with_specific_tiller.md) - - [Custom Tiller Role](tiller/custom_tiller_role.md) - Defining Helm repositories - [Using default helm repos](helm_repos/default.md) - [Using private repos in Google GCS](helm_repos/gcs.md) @@ -29,7 +24,7 @@ This page contains a list of guides on how to use Helmsman. - [Using local charts](helm_repos/local.md) - Manipulating Apps - [Basic operations](apps/basic.md) - - [Passing secrets from env vars](apps/secrets.md) + - [Passing secrets to releases](apps/secrets.md) - [Use multiple values files for apps](apps/multiple_values_files.md) - [Protect releases (apps)](apps/protection.md) - [Moving releases (apps) across namespaces](apps/moving_across_namespaces.md) @@ -47,6 +42,4 @@ This page contains a list of guides on how to use Helmsman. - [Merge multiple desired state files](misc/merge_desired_state_files.md) - [Limit Helmsman deployment to specific apps](misc/limit-deployment-to-specific-apps.md) - [Limit Helmsman deployment to specific group of apps](misc/limit-deployment-to-specific-group-of-apps.md) - - [Multi-tenant clusters guide](misc/multitenant_clusters_guide.md) - - [Helmsman on Windows 10](misc/helmsman_on_windows10.md) - [Use hiera-eyaml as secrets encryption backend](settings/use-hiera-eyaml-as-secrets-encryption.md) diff --git a/docs/how_to/apps/basic.md b/docs/how_to/apps/basic.md index 2381e5b5..91187c72 100644 --- a/docs/how_to/apps/basic.md +++ b/docs/how_to/apps/basic.md @@ -1,5 +1,5 @@ --- -version: v1.5.0 +version: v3.0.0-beta1 --- # Install releases @@ -57,40 +57,17 @@ NAME REVISION UPDATED STATUS CHART NAMESPA artifactory 2 Sun Nov 19 18:29:11 2017 DEPLOYED artifactory-6.2.0 staging ``` -If you would like the release to be deleted along with its history, you can use the `purge` flag in your desired state file as follows: - -> NOTE: purge deleting a release means you can't roll it back. - -```toml -... -[apps] - - [apps.jenkins] - name = "jenkins" - description = "jenkins" - namespace = "staging" - enabled = false # this tells helmsman to delete it - chart = "stable/jenkins" - version = "0.9.1" - valuesFile = "" - purge = true # this means purge delete this release whenever it is required to be deleted - test = false - -... -``` ```yaml # ... apps: jenkins: - name: "jenkins" description: "jenkins" namespace: "staging" enabled: false # this tells helmsman to delete it chart: "stable/jenkins" version: "0.9.1" valuesFile: "" - purge: true # this means purge delete this release whenever it is required to be deleted test: false # ... @@ -117,7 +94,7 @@ DECISION: release [ artifactory ] is desired to be upgraded. Planning this for y # Upgrade releases -Every time you run Helmsman, (unless the release is [protected or deployed in a protected namespace](../misc/protect_namespaces_and_releases.md)) it will upgrade existing deployed releases to the version you specified in the desired state file. It also applies the `values.yaml` file you specify with each install/upgrade. This means that when you don't change anything for a specific release, Helmsman would upgrade with the `values.yaml` file you provide (just in case it is a new file or you changed something there.) +Every time you run Helmsman, (unless the release is [protected or deployed in a protected namespace](../misc/protect_namespaces_and_releases.md)) it will check if upgrade is necessary (using the helm-diff plugin) and only upgrade if there are changes. If you change the chart, the existing release will be deleted and a new one with the same name will be created using the new chart. diff --git a/docs/how_to/apps/destroy.md b/docs/how_to/apps/destroy.md index 83786ce7..ef03a818 100644 --- a/docs/how_to/apps/destroy.md +++ b/docs/how_to/apps/destroy.md @@ -1,5 +1,5 @@ --- -version: v1.6.2 +version: v3.0.0-beta1 --- # Delete all deployed releases @@ -8,7 +8,5 @@ Helmsman allows you to delete all the helm releases that were deployed by Helmsm The `--destroy` flag will remove all deployed releases from a given desired state file (DSF). Note that this does not currently delete the namespaces nor the Kubernetes contexts created. -The deletion of releases will respect the `purge` options in the desired state file. i.e. only if `purge` is true for release A, then the destruction of A will be a purge delete - This was originally requested in issue [#88](https://github.com/Praqma/helmsman/issues/88). diff --git a/docs/how_to/apps/helm_tests.md b/docs/how_to/apps/helm_tests.md index 820cf20b..bd9f3429 100644 --- a/docs/how_to/apps/helm_tests.md +++ b/docs/how_to/apps/helm_tests.md @@ -1,5 +1,5 @@ --- -version: v1.3.0-rc +version: v3.0.0-beta1 --- # Test charts @@ -13,14 +13,12 @@ You can specify that you would like a chart to be tested whenever it is installe [apps] [apps.jenkins] - name = "jenkins" description = "jenkins" namespace = "staging" enabled = true chart = "stable/jenkins" version = "0.9.1" valuesFile = "" - purge = false test = true # setting this to true, means you want the charts tests to be run on this release when it is installed. ... @@ -32,14 +30,12 @@ You can specify that you would like a chart to be tested whenever it is installe apps: jenkins: - name: "jenkins" description: "jenkins" namespace: "staging" enabled: true chart: "stable/jenkins" version: "0.9.1" valuesFile: "" - purge: false test: true # setting this to true, means you want the charts tests to be run on this release when it is installed. #... diff --git a/docs/how_to/apps/moving_across_namespaces.md b/docs/how_to/apps/moving_across_namespaces.md index 9600fd88..47d975fd 100644 --- a/docs/how_to/apps/moving_across_namespaces.md +++ b/docs/how_to/apps/moving_across_namespaces.md @@ -1,5 +1,5 @@ --- -version: v1.3.0-rc +version: v3.0.0-beta1 --- # Move charts across namespaces @@ -19,14 +19,12 @@ If you have a workflow for testing a release first in the `staging` namespace th [apps] [apps.jenkins] - name = "jenkins" description = "jenkins" namespace = "staging" # this is where it is deployed enabled = true chart = "stable/jenkins" version = "0.9.1" valuesFile = "" - purge = false test = true ... @@ -42,14 +40,12 @@ namespaces: apps: jenkins: - name: "jenkins" description: "jenkins" namespace: "staging" # this is where it is deployed enabled: true chart: "stable/jenkins" version: "0.9.1" valuesFile: "" - purge: false test: true # ... @@ -68,14 +64,12 @@ Then if you change the namespace key for jenkins: [apps] [apps.jenkins] - name = "jenkins" description = "jenkins" namespace = "production" # we want to move it to production enabled = true chart = "stable/jenkins" version = "0.9.1" valuesFile = "" - purge = false test = true ... @@ -91,14 +85,12 @@ namespaces: apps: jenkins: - name: "jenkins" description: "jenkins" namespace: "production" # we want to move it to production enabled: true chart: "stable/jenkins" version: "0.9.1" valuesFile: "" - purge: false test: true # ... diff --git a/docs/how_to/apps/multiple_values_files.md b/docs/how_to/apps/multiple_values_files.md index b16c8fe1..5c9f63b0 100644 --- a/docs/how_to/apps/multiple_values_files.md +++ b/docs/how_to/apps/multiple_values_files.md @@ -1,5 +1,5 @@ --- -version: v1.3.0-rc +version: v3.3.0-beta1 --- # Multiple value files @@ -10,8 +10,7 @@ You can include multiple yaml value files to separate configuration for differen ... [apps] - [apps.jenkins] - name = "jenkins-prod" # should be unique across all apps + [apps.jenkins-prod] description = "production jenkins" namespace = "production" enabled = true @@ -24,7 +23,6 @@ You can include multiple yaml value files to separate configuration for differen # the jenkins release below is being tested in the staging namespace [apps.jenkins-test] - name = "jenkins-test" # should be unique across all apps description = "test release of jenkins, testing xyz feature" namespace = "staging" enabled = true @@ -43,8 +41,7 @@ You can include multiple yaml value files to separate configuration for differen # ... apps: - jenkins: - name: "jenkins-prod" # should be unique across all apps + jenkins-prod: description: "production jenkins" namespace: "production" enabled: true diff --git a/docs/how_to/apps/order.md b/docs/how_to/apps/order.md index 179ce6b5..a22473a5 100644 --- a/docs/how_to/apps/order.md +++ b/docs/how_to/apps/order.md @@ -1,5 +1,5 @@ --- -version: v1.3.0-rc +version: v3.0.0-beta1 --- # Using the priority key for Apps @@ -33,7 +33,6 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" [apps] [apps.jenkins] - name = "jenkins" # should be unique across all apps description = "jenkins" namespace = "staging" # maps to the namespace as defined in environments above enabled = true # change to false if you want to delete this app release [empty = false] @@ -43,7 +42,6 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" priority= -2 [apps.jenkins1] - name = "jenkins1" # should be unique across all apps description = "jenkins" namespace = "staging" # maps to the namespace as defined in environments above enabled = true # change to false if you want to delete this app release [empty = false] @@ -53,7 +51,6 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" [apps.jenkins2] - name = "jenkins2" # should be unique across all apps description = "jenkins" namespace = "production" # maps to the namespace as defined in environments above enabled = true # change to false if you want to delete this app release [empty = false] @@ -63,7 +60,6 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" priority= -3 [apps.artifactory] - name = "artifactory" # should be unique across all apps description = "artifactory" namespace = "staging" # maps to the namespace as defined in environments above enabled = true # change to false if you want to delete this app release [empty = false] diff --git a/docs/how_to/apps/override_namespaces.md b/docs/how_to/apps/override_namespaces.md index 4708b34d..3fbf3d74 100644 --- a/docs/how_to/apps/override_namespaces.md +++ b/docs/how_to/apps/override_namespaces.md @@ -1,5 +1,5 @@ --- -version: v1.3.0-rc +version: v3.0.0-beta1 --- # Override defined namespaces from command line @@ -34,7 +34,6 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" [apps] [apps.jenkins] - name = "jenkins" # should be unique across all apps description = "jenkins" namespace = "production" # maps to the namespace as defined in environments above enabled = true # change to false if you want to delete this app release [empty = false] @@ -43,7 +42,6 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" valuesFile = "" # leaving it empty uses the default chart values [apps.artifactory] - name = "artifactory" # should be unique across all apps description = "artifactory" namespace = "staging" # maps to the namespace as defined in environments above enabled = true # change to false if you want to delete this app release [empty = false] @@ -76,7 +74,6 @@ helmRepos: apps: jenkins: - name: "jenkins" # should be unique across all apps description: "jenkins" namespace: "production" # maps to the namespace as defined in environments above enabled: true # change to false if you want to delete this app release [empty: false] @@ -85,7 +82,6 @@ apps: valuesFile: "" # leaving it empty uses the default chart values artifactory: - name: "artifactory" # should be unique across all apps description: "artifactory" namespace: "staging" # maps to the namespace as defined in environments above enabled: true # change to false if you want to delete this app release [empty: false] diff --git a/docs/how_to/apps/secrets.md b/docs/how_to/apps/secrets.md index 28b1f987..b46de4b9 100644 --- a/docs/how_to/apps/secrets.md +++ b/docs/how_to/apps/secrets.md @@ -1,5 +1,5 @@ --- -version: v1.6.0 +version: v3.0.0-beta1 --- # Passing secrets from env variables: @@ -11,14 +11,12 @@ Starting from v0.1.3, Helmsman allows you to pass secrets and other user input t [apps] [apps.jira] - name = "jira" description = "jira" namespace = "staging" enabled = true chart = "myrepo/jira" version = "0.1.5" valuesFile = "applications/jira-values.yaml" - purge = false test = true [apps.jira.set] # the format is [apps.<>.set] db_username= "$JIRA_DB_USERNAME" # pass any number of key/value pairs where the key is the input expected by the helm charts and the value is an env variable name starting with $ @@ -32,14 +30,12 @@ Starting from v0.1.3, Helmsman allows you to pass secrets and other user input t apps: jira: - name: "jira" description: "jira" namespace: "staging" enabled: true chart: "myrepo/jira" version: "0.1.5" valuesFile: "applications/jira-values.yaml" - purge: false test: true set: db_username: "$JIRA_DB_USERNAME" # pass any number of key/value pairs where the key is the input expected by the helm charts and the value is an env variable name starting with $ @@ -75,3 +71,7 @@ BAR: baz # Passing secrets using helm secrets plugin You can also use the [helm secrets plugin](https://github.com/futuresimple/helm-secrets) to pass your secrets. + +# Passing secrets using hiera eyaml + +An alternative method is to use heira eyaml as described in [this guide](../settings/use-hiera-eyaml-as-secrets-encryption.md). \ No newline at end of file diff --git a/docs/how_to/deployments/ci.md b/docs/how_to/deployments/ci.md index f8950305..ec7e4870 100644 --- a/docs/how_to/deployments/ci.md +++ b/docs/how_to/deployments/ci.md @@ -1,5 +1,5 @@ --- -version: v1.3.0-rc +version: v3.0.0-beta1 --- # Run Helmsman in CI @@ -13,12 +13,12 @@ jobs: deploy-apps: docker: - - image: praqma/helmsman:v1.8.0 + - image: praqma/helmsman:v3.0.0-beta1 steps: - checkout - run: name: Deploy Helm Packages using helmsman - command: helmsman --debug --apply -f helmsman-deployments.toml + command: helmsman --apply -f helmsman-deployments.toml workflows: @@ -28,6 +28,6 @@ workflows: - deploy-apps ``` -> IMPORTANT: If your CI build logs are publicly readable, don't use the `--verbose` flag as logs any secrets being passed from env vars to the helm charts. +> IMPORTANT: If your CI build logs are publicly readable, don't use the `--verbose` together with `--debug` flags as logs any secrets being passed from env vars to the helm charts. The `helmsman-deployments.toml` is your desired state file which will version controlled in your git repo. diff --git a/docs/how_to/helm_repos/default.md b/docs/how_to/helm_repos/default.md index 86679834..b6af780e 100644 --- a/docs/how_to/helm_repos/default.md +++ b/docs/how_to/helm_repos/default.md @@ -1,10 +1,12 @@ --- -version: v1.8.0 +version: v3.0.0-beta1 --- # Default helm repos -By default, helm comes with two default repos; `stable` and `incubator`. These two DO NOT need to be defined explicitly in your desired state file (DSF). However, if you would like to configure some repo with the name stable for example, you can override the default repo. +Helm v3 no longer adds the `stable` and `incubator` repos by default. However, Helmsman v3.0.0-beta1 still adds these two repos by default. These two DO NOT need to be defined explicitly in your desired state file (DSF). However, if you would like to configure some repo with the name stable for example, you can override the default repo. + +> You can disable the automatic addition of these two repos, use the `--no-default-repos` flag. This example would have `stable` and `incubator` added by default and another `custom` repo defined explicitly: diff --git a/docs/how_to/helm_repos/local.md b/docs/how_to/helm_repos/local.md index 7d7a78be..aaee2b4c 100644 --- a/docs/how_to/helm_repos/local.md +++ b/docs/how_to/helm_repos/local.md @@ -12,31 +12,4 @@ If you use a file path (relative to the DSF, or absolute) for the ```chart``` at helmsman will try to resolve that chart from the local file system. The chart on the local file system must have a version matching the version specified in the DSF. -## Served by helm - -You can serve them on localhost using helm's `serve` option. - -```toml -... - -[helmRepos] -stable = "https://kubernetes-charts.storage.googleapis.com" -incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" -local = "http://127.0.0.1:8879" - -... - -``` - -```yaml -# ... - -helmRepos: - stable: "https://kubernetes-charts.storage.googleapis.com" - incubator: "http://storage.googleapis.com/kubernetes-charts-incubator" - local: "http://127.0.0.1:8879" - -# ... - -``` diff --git a/docs/how_to/misc/helmsman_on_windows10.md b/docs/how_to/misc/helmsman_on_windows10.md deleted file mode 100644 index 5bee5696..00000000 --- a/docs/how_to/misc/helmsman_on_windows10.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -version: v1.1.0 ---- - -> This guide has not been thoroughly tested. - -# Using Helmsman from a docker image on Windows 10 - -If you have Windows 10 with Docker installed, you **might** be able to run Helmsman in a linux container on Windows. - -1. Switch to the Linux containers from the docker tray icon. -2. Configure your local kubectl on Windows to connect to your cluster. -3. Configure your desired state file to use the kubeContext only. i.e. no cluster connection settings. -2. Run the following command: - -```shell -docker run --rm -it -v :/root/.kube -v :/tmp praqma/helmsman:v1.0.2 helmsman -f dsf.toml --debug --apply -``` - diff --git a/docs/how_to/misc/limit-deployment-to-specific-apps.md b/docs/how_to/misc/limit-deployment-to-specific-apps.md index 4815c0d2..23da9e60 100644 --- a/docs/how_to/misc/limit-deployment-to-specific-apps.md +++ b/docs/how_to/misc/limit-deployment-to-specific-apps.md @@ -4,7 +4,7 @@ version: v1.9.0 # Limit execution to explicitly defined apps -Starting from v1.9.0, Helmsman allows you to pass the `-target` flag multiple times to specify multiple apps +Starting from v1.9.0, Helmsman allows you to pass the `--target` flag multiple times to specify multiple apps that limits apps considered by Helmsman during this specific execution. Thanks to this one can deploy specific applications among all defined for an environment. @@ -32,16 +32,16 @@ apps: running Helmsman with `-f example.yaml` would result in checking state and invoking deployment for both jenkins and artifactory application. -With `-target` flag in command like +With `--target` flag in command like ```shell -$ helmsman -f example.yaml -target artifactory ... +$ helmsman -f example.yaml --target artifactory ... ``` one can execute Helmsman's environment defined with example.yaml limited to only one `artifactory` app. Others are ignored until the flag is defined. -Multiple applications can be set with `-target`, like +Multiple applications can be set with `--target`, like ```shell -$ helmsman -f example.yaml -target artifactory -target jenkins ... +$ helmsman -f example.yaml --target artifactory --target jenkins ... ``` diff --git a/docs/how_to/misc/limit-deployment-to-specific-group-of-apps.md b/docs/how_to/misc/limit-deployment-to-specific-group-of-apps.md index 009dbd86..880265d5 100644 --- a/docs/how_to/misc/limit-deployment-to-specific-group-of-apps.md +++ b/docs/how_to/misc/limit-deployment-to-specific-group-of-apps.md @@ -4,7 +4,7 @@ version: v1.13.0 # Limit execution to explicitly defined group of apps -Starting from v1.13.0, Helmsman allows you to pass the `-group` flag to specify group of apps +Starting from v1.13.0, Helmsman allows you to pass the `--group` flag to specify group of apps the execution of Helmsman deployment will be limited to. Thanks to this one can deploy specific applications among all defined for an environment. @@ -34,17 +34,17 @@ apps: running Helmsman with `-f example.yaml` would result in checking state and invoking deployment for both jenkins and artifactory application. -With `-group` flag in command like +With `--group` flag in command like ```shell -$ helmsman -f example.yaml -group critical ... +$ helmsman -f example.yaml --group critical ... ``` one can execute Helmsman's environment defined with example.yaml limited to only one `jenkins` app, since its group is `critical`. Others are ignored until the flag is defined. -Multiple applications can be set with `-group`, like +Multiple applications can be set with `--group`, like ```shell -$ helmsman -f example.yaml -group critical -group sidecar ... +$ helmsman -f example.yaml --group critical --group sidecar ... ``` diff --git a/docs/how_to/misc/merge_desired_state_files.md b/docs/how_to/misc/merge_desired_state_files.md index 32d585e1..c096fdac 100644 --- a/docs/how_to/misc/merge_desired_state_files.md +++ b/docs/how_to/misc/merge_desired_state_files.md @@ -1,5 +1,5 @@ --- -version: v1.5.0 +version: v3.0.0-beta1 --- # Supply multiple desired state files diff --git a/docs/how_to/misc/multitenant_clusters_guide.md b/docs/how_to/misc/multitenant_clusters_guide.md deleted file mode 100644 index 989edee5..00000000 --- a/docs/how_to/misc/multitenant_clusters_guide.md +++ /dev/null @@ -1,164 +0,0 @@ ---- -version: v1.5.0 ---- - -# Multitenant Clusters Guide - -This guide helps you use Helmsman to secure your Helm deployment with service accounts and TLS. - ->Checkout Helm's [security guide](https://github.com/kubernetes/helm/blob/master/docs/securing_installation.md) - -> These features are available starting from v1.2.0-rc - -## Deploying Tiller in multiple namespaces - -In a multitenant cluster, it is a good idea to separate the Helm work of different users. You can achieve that by deploying Tiller in multiple namespaces. This is done in the `namespaces` section using the `installTiller` flag: - -```toml - -[namespaces] - [namespaces.staging] - installTiller = true - [namespaces.production] - installTiller = true - [namespaces.developer1] - installTiller = true - [namespaces.developer2] - installTiller = true - -``` - -```yaml - -namespaces: - staging: - installTiller: true - production: - installTiller: true - developer1: - installTiller: true - developer2: - installTiller: true - -``` - -By default Tiller will be deployed into `kube-system` even if you don't define kube-system in the namespaces section. To prevent deploying Tiller into `kube-system, you need to explicitly add `kube-system` in your defined namespaces. See the [tiller deployment](../tiller/prevent_tiller_in_kube_system.md) for an example. - -## Deploying Tiller with a service account - -For K8S clusters with RBAC enabled, you will need to initialize Helm with a service account. Check [Helm's RBAC guide](https://github.com/kubernetes/helm/blob/master/docs/rbac.md). - -Helmsman lets you deploy each of the Tillers with a different k8s service account Or with a default service account of your choice. - -```toml - -[settings] -# other options -serviceAccount = "default-tiller-sa" - -[namespaces] - [namespaces.staging] - installTiller = true - tillerServiceAccount = "custom-sa" - - [namespaces.production] - installTiller = true - - [namespaces.developer1] - installTiller = true - tillerServiceAccount = "dev1-sa" - - [namespaces.developer2] - installTiller = true - tillerServiceAccount = "dev2-sa" - -``` - -```yaml - -settings: - # other options - serviceAccount: "default-tiller-sa" - -namespaces: - staging: - installTiller: true - tillerServiceAccount: "custom-sa" - - production: - installTiller: true - - developer1: - installTiller: true - tillerServiceAccount: "dev1-sa" - - developer2: - installTiller: true - tillerServiceAccount: "dev2-sa" - -``` - -If `tillerServiceAccount` is not defined, the following options are considered: - - 1. If the `serviceAccount` defined in the `settings` section exists in the namespace you want to deploy Tiller in, it will be used, else - 2. Helmsman creates the service account in that namespace and binds it to a role. If the namespace is kube-system, the service account is bound to `cluster-admin` clusterrole. Otherwise, a new role called `helmsman-tiller` is created in that namespace and only gives access to that namespace. - - -In the example above, namespaces `staging, developer1 & developer2` will have Tiller deployed with different service accounts. -The `production` namespace ,however, will be deployed using the `default-tiller-sa` service account defined in the `settings` section -assuming it exists in the production namespace-. If this one is not defined, Helmsman creates a new service account and binds it to a new role that only gives access to the `production` namespace. - -## Deploying Tiller with TLS enabled - -In a multitenant setting, it is also recommended to deploy Tiller with TLS enabled. This is also done in the `namespaces` section: - -```toml - -[namespaces] - [namespaces.kube-system] - installTiller = true - caCert = "secrets/kube-system/ca.cert.pem" - tillerCert = "secrets/kube-system/tiller.cert.pem" - tillerKey = "$TILLER_KEY" # where TILLER_KEY=secrets/kube-system/tiller.key.pem - clientCert = "gs://mybucket/mydir/helm.cert.pem" - clientKey = "s3://mybucket/mydir/helm.key.pem" - - [namespaces.staging] - installTiller = true - - [namespaces.production] - installTiller = true - tillerServiceAccount = "tiller-production" - caCert = "secrets/ca.cert.pem" - tillerCert = "secrets/tiller.cert.pem" - tillerKey = "$TILLER_KEY" # where TILLER_KEY=secrets/tiller.key.pem - clientCert = "gs://mybucket/mydir/helm.cert.pem" - clientKey = "s3://mybucket/mydir/helm.key.pem" - -``` - -```yaml - -namespaces: - kube-system: - installTiller: false # has no effect. Tiller is always deployed in kube-system - caCert: "secrets/kube-system/ca.cert.pem" - tillerCert: "secrets/kube-system/tiller.cert.pem" - tillerKey: "$TILLER_KEY" # where TILLER_KEY=secrets/kube-system/tiller.key.pem - clientCert: "gs://mybucket/mydir/helm.cert.pem" - clientKey: "s3://mybucket/mydir/helm.key.pem" - - staging: - installTiller: true - - production: - installTiller: true - tillerServiceAccount: "tiller-production" - caCert: "secrets/ca.cert.pem" - tillerCert: "secrets/tiller.cert.pem" - tillerKey: "$TILLER_KEY" # where TILLER_KEY=secrets/tiller.key.pem - clientCert: "gs://mybucket/mydir/helm.cert.pem" - clientKey: "s3://mybucket/mydir/helm.key.pem" - -``` - - diff --git a/docs/how_to/settings/use-hiera-eyaml-as-secrets-encryption.md b/docs/how_to/settings/use-hiera-eyaml-as-secrets-encryption.md index 55c5df09..eb2b0c81 100644 --- a/docs/how_to/settings/use-hiera-eyaml-as-secrets-encryption.md +++ b/docs/how_to/settings/use-hiera-eyaml-as-secrets-encryption.md @@ -5,9 +5,9 @@ version: v1.13.0 # Using hiera-eyaml as backend for secrets' encryption Helmsman uses helm-secrets as a default solution for secrets' encryption. -And while it is a good off-the-shelve solution it may quickly start causing problems when few developers starts working on the secrets files simultaneously. +And while it is a good off-the-shelve solution it may quickly start causing problems when few developers start working on the secrets files simultaneously. SOPS-based secrets can not be easily merged or rebased in case of conflicts etc. -That is why another solution for secrets organised in YAMLs were proposed in form of [hiera-eyaml](https://github.com/voxpupuli/hiera-eyaml). +That is why another solution for secrets organised in YAMLs was proposed in [hiera-eyaml](https://github.com/voxpupuli/hiera-eyaml). ## Example diff --git a/docs/how_to/tiller/custom_tiller_role.md b/docs/how_to/tiller/custom_tiller_role.md deleted file mode 100644 index 54d7aaf8..00000000 --- a/docs/how_to/tiller/custom_tiller_role.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -version: v1.10.0 ---- - -# Create helmsman per namespace with your custom Role - -You can deploy namespace-specific helmsman Tiller with Service Account having custom Role. - -By default, when defining Namespaces in Desired State Specification file, when `installTiller` is enabled for specific namespace, -it creates the Role to bind the Tiller to with default [yaml template](../../../data/role.yaml). - -If there's a need for custom Role (let's say each namespace has its different and specific requirements to permissions), -you can define `tillerRoleTemplateFile`, which is a relative path pointing at a template of a Role (same format as a [yaml template](../../../data/role.yaml)), -so when Helmsman creates Tiller in the namespace with this key, custom Role will be created for Tiller. - -```toml -[namespaces] -[namespaces.dev] -useTiller = true -[namespaces.production] -installTiller = true -tillerServiceAccount = "tiller-production" -tillerRoleTemplateFile = "../roles/helmsman-tiller.yaml" -``` - -```yaml -namespaces: - dev: - useTiller: true - production: - installTiller: true - tillerServiceAccount: "tiller-production" - tillerRoleTemplateFile: "../roles/helmsman-tiller.yaml" -``` - -The example above will create two namespaces: dev and production, where dev namespace will have its Tiller with default Role, -while production namespace will be managed by its specific Tiller having custom role based on the `"../roles/helmsman-tiller.yaml"` template created by you. diff --git a/docs/how_to/tiller/deploy_apps_with_specific_tiller.md b/docs/how_to/tiller/deploy_apps_with_specific_tiller.md deleted file mode 100644 index 1ce0f02c..00000000 --- a/docs/how_to/tiller/deploy_apps_with_specific_tiller.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -version: v1.8.0 ---- - -# Deploying apps (releases) with specific Tillers -You can then tell Helmsman to deploy specific releases in a specific namespace: - -```toml -#... -[apps] - - [apps.jenkins] - namespace = "production" # pointing to the namespace defined above - enabled = true - chart = "stable/jenkins" - version = "0.9.1" - - -#... - -``` - -```yaml -# ... -apps: - jenkins: - namespace: "production" # pointing to the namespace defined above - enabled: true - chart: "stable/jenkins" - version: "0.9.1" - -# ... - -``` - -In the above example, `Jenkins` will be deployed in the production namespace using the Tiller deployed in the production namespace. If the production namespace was not configured to have Tiller deployed there, Jenkins will be deployed using the Tiller in `kube-system`. diff --git a/docs/how_to/tiller/existing.md b/docs/how_to/tiller/existing.md deleted file mode 100644 index 77f7683f..00000000 --- a/docs/how_to/tiller/existing.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -version: v1.8.0 ---- - -## Using your existing Tillers (available from v1.6.0) - -If you would like to use custom configuration when deploying your Tiller, you can do that before using Helmsman and then use the `useTiller` option in your namespace definition. - -This will allow Helmsman to use your existing Tiller as it is. Note that you can't set both `useTiller` and `installTiller` to true at the same time. - -```toml -[namespaces] -[namespaces.production] - useTiller = true -``` - -```yaml -namespaces: - production: - useTiller: true -``` \ No newline at end of file diff --git a/docs/how_to/tiller/multitenancy.md b/docs/how_to/tiller/multitenancy.md deleted file mode 100644 index b5e6583c..00000000 --- a/docs/how_to/tiller/multitenancy.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -version: v1.8.0 ---- - -# Deploying multiple Tillers - -You can deploy multiple Tillers in the cluster (max. one per namespace). In each namespace definition you can configure how Tiller is installed. The following options are available: -- with/without RBAC -- with/without TLS -- with cluster-admin clusterrole or with a namespace-limited role or with an pre-configured role. - -> If you use GCS, S3, or Azure blob storage for your certificates, you will need to provide means to authenticate to the respective cloud provider in the environment. See [authenticating to cloud storage providers](../misc/auth_to_storage_providers.md) for details. - - -> More details about using Helmsman in a multitenant cluster can be found [here](../misc/multitenant_clusters_guide.md) - -You can also use pre-configured Tillers in specific namespaces. In the example below, the desired state is: to deploy Tiller in the `production` namespace with TLS and RBAC, and to use a pre-configured Tiller in the `dev` namespace. The `staging` namespace does not have any Tiller to be deployed or used. Tiller is not deployed in `kube-system`. - - -```toml -[namespaces] - # to prevent deploying Tiller into kube-system, use the two lines below - [namespaces.kube-system] - installTiller = false # this line can be omitted since installTiller defaults to false - [namespaces.staging] - [namespaces.dev] - useTiller = true # use a Tiller which has been deployed in dev namespace - [namespaces.production] - installTiller = true - tillerServiceAccount = "tiller-production" - tillerRole = "cluster-admin" - caCert = "secrets/ca.cert.pem" - tillerCert = "az://myblobcontainer/tiller.cert.pem" - tillerKey = "$TILLER_KEY" # where TILLER_KEY=secrets/tiller.key.pem - clientCert = "gs://mybucket/mydir/helm.cert.pem" - clientKey = "s3://mybucket/mydir/helm.key.pem" -``` - -```yaml -namespaces: - # to prevent deploying Tiller into kube-system, use the two lines below - kube-system: - installTiller: false # this line can be omitted since installTiller defaults to false - staging: # no Tiller deployed or used here - dev: - useTiller: true # use a Tiller which has been deployed in dev namespace - production: - installTiller: true - tillerServiceAccount: "tiller-production" - tillerRole: "cluster-admin" - caCert: "secrets/ca.cert.pem" - tillerCert: "az://myblobcontainer/tiller.cert.pem" - tillerKey: "$TILLER_KEY" # where TILLER_KEY=secrets/tiller.key.pem - clientCert: "gs://mybucket/mydir/helm.cert.pem" - clientKey: "s3://mybucket/mydir/helm.key.pem" -``` diff --git a/docs/how_to/tiller/prevent_tiller_in_kube_system.md b/docs/how_to/tiller/prevent_tiller_in_kube_system.md deleted file mode 100644 index ef20c1be..00000000 --- a/docs/how_to/tiller/prevent_tiller_in_kube_system.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -version: v1.8.0 ---- - -# Prevent Tiller Deployment in kube-system - -By default Tiller will be deployed into `kube-system` even if you don't define kube-system in the namespaces section. To prevent this, simply add `kube-system` into your namespaces section. Since `installTiller` for namespaces is by default false, Helmsman will not deploy Tiller in `kube-system`. - -```toml -[namespaces] -[namespaces.kube-system] -# installTiller = false # this line is not needed since the default is false, but can be added for human readability. -``` - -```yaml -namespaces: - kube-system: - #installTiller: false # this line is not needed since the default is false, but can be added for human readability. -``` diff --git a/docs/how_to/tiller/shared.md b/docs/how_to/tiller/shared.md deleted file mode 100644 index 21f27ec3..00000000 --- a/docs/how_to/tiller/shared.md +++ /dev/null @@ -1,83 +0,0 @@ ---- -version: v1.8.0 ---- - -# Deploying a shared Tiller (available from v1.2.0) - -You can instruct Helmsman to deploy Tiller into specific namespaces (with or without TLS). - -> By default Tiller will be deployed into `kube-system` even if you don't define kube-system in the namespaces section. To prevent deploying Tiller into `kube-system, see [preventing Tiller deployment in kube-system](prevent_tiller_in_kube_system.md) - -## Without TLS - -```toml -[namespaces] -[namespaces.production] - installTiller = true -``` - -```yaml -namespaces: - production: - installTiller: true -``` - -## With RBAC service account - -You specify an existing service account to be used for deploying Tiller. If that service account does not exist, Helmsman will attempt to create it. If `tillerRole` (e.g. cluster-admin) is specified, it will be bound to the newly created service account. - -By default, Tiller deployed in kube-system will be given cluster-admin clusterrole. Tiller in other namespaces will be given a custom role that gives it access to that namespace only. The custom role is created using [this template](../../../data/role.yaml). - -```toml -[namespaces] -[namespaces.production] - installTiller = true - tillerServiceAccount = "tiller-production" - tillerRole = "cluster-admin" -[namespaces.staging] - installTiller = true - tillerServiceAccount = "tiller-stagin" -``` - -```yaml -namespaces: - production: - installTiller: true - tillerServiceAccount: "tiller-production" - tillerRole: "cluster-admin" - staging: - installTiller: true - tillerServiceAccount: "tiller-staging" -``` - -The above example will create two service accounts; `tiller-production` and `tiller-staging`. Service account `tiller-production` will be bound to a cluster admin clusterrole while `tiller-staging` will be bound to a newly created role with access to the staging namespace only. - -## With RBAC and TLS - -You have to provide the TLS certificates as below. Certificates can be either located locally or in Google GCS, AWS S3 or Azure blob storage. - -> If you use GCS, S3, or Azure blob storage for your certificates, you will need to provide means to authenticate to the respective cloud provider in the environment. See [authenticating to cloud storage providers](../misc/auth_to_storage_providers.md) for details. - -```toml -[namespaces] -[namespaces.production] - installTiller = true - tillerServiceAccount = "tiller-production" - caCert = "secrets/ca.cert.pem" - tillerCert = "secrets/tiller.cert.pem" - tillerKey = "$TILLER_KEY" # where TILLER_KEY=secrets/tiller.key.pem - clientCert = "gs://mybucket/mydir/helm.cert.pem" - clientKey = "s3://mybucket/mydir/helm.key.pem" -``` - -```yaml -namespaces: - production: - installTiller: true - tillerServiceAccount: "tiller-production" - caCert: "secrets/ca.cert.pem" - tillerCert: "secrets/tiller.cert.pem" - tillerKey: "$TILLER_KEY" # where TILLER_KEY=secrets/tiller.key.pem - clientCert: "gs://mybucket/mydir/helm.cert.pem" - clientKey: "s3://mybucket/mydir/helm.key.pem" -``` diff --git a/docs/migrating_to_v1.4.0-rc.md b/docs/migrating_to_v1.4.0-rc.md deleted file mode 100644 index 07c63ae0..00000000 --- a/docs/migrating_to_v1.4.0-rc.md +++ /dev/null @@ -1,8 +0,0 @@ -# Migrating to Helmsman v1.4.0-rc or higher - -This document highlights the main changes between Helmsman v1.4.0-rc and the older versions. While the changes are still backward-compatible, the behavior and the internals have changed. The list below highlights those changes: - -- Helmsman v1.4.0-rc tracks the releases it manages by applying specific labels to their Helm state (stored in Helm's configured backend storage). For smooth transition when upgrading to v1.4.0-rc, you should run `helmsman -f --apply-labels` once. This will label all releases from your desired state as with a `MANAGED-BY=Helmsman` label. The `--apply-labels`is safe to run multiple times. - -- After each run, Helmsman v1.4.0-rc looks for, and deletes any releases with the `MANAGED-BY=Helmsman` label which are no longer existing in your desired state. This means that **deleting/commenting out an app from your desired state file will result in its deletion**. You can disable this cleanup by adding the flag `--keep-untracked-releases` to your Helmsman commands. - diff --git a/examples/example.toml b/examples/example.toml index a2137753..0edc5792 100644 --- a/examples/example.toml +++ b/examples/example.toml @@ -26,8 +26,7 @@ context= "test-infra" # defaults to "default" if not provided # password = "$K8S_PASSWORD" # the name of an environment variable containing the k8s password clusterURI = "${SET_URI}" # the name of an environment variable containing the cluster API # #clusterURI = "https://192.168.99.100:8443" # equivalent to the above - # serviceAccount = "tiller" # k8s serviceaccount. If it does not exist, it will be created. - # storageBackend = "secret" # default is configMap +# storageBackend = "secret" # default is secret # slackWebhook = "$slack" # or "your slack webhook url" # reverseDelete = false # reverse the priorities on delete #### to use bearer token: @@ -65,8 +64,8 @@ context= "test-infra" # defaults to "default" if not provided # syntax: repo_name = "repo_url" # only private repos hosted in s3 buckets are now supported [helmRepos] - stable = "https://kubernetes-charts.storage.googleapis.com" - incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" + argo = "https://argoproj.github.io/argo-helm" + jfrog = "https://charts.jfrog.io" # myS3repo = "s3://my-S3-private-repo/charts" # myGCSrepo = "gs://my-GCS-private-repo/charts" # custom = "https://user:pass@mycustomrepo.org" @@ -76,38 +75,35 @@ context= "test-infra" # defaults to "default" if not provided # each contains the following: [apps] - # jenkins will be deployed using the Tiller in the staging namespace - [apps.jenkins] + [apps.argo] namespace = "staging" # maps to the namespace as defined in namespaces above enabled = true # change to false if you want to delete this app release [default = false] - chart = "stable/jenkins" # changing the chart name means delete and recreate this release - version = "0.14.3" # chart version + chart = "argo/argo" # changing the chart name means delete and recreate this release + version = "0.6.4" # chart version ### Optional values below - name = "jenkins" # should be unique across all apps which are managed by the same Tiller valuesFile = "" # leaving it empty uses the default chart values - purge = false # will only be considered when there is a delete operation test = false # run the tests when this release is installed for the first time only protected = true priority= -3 wait = true - # [apps.jenkins.setString] # values to override values from values.yaml with values from env vars or directly entered-- useful for passing secrets to charts - # AdminPassword="$JENKINS_PASSWORD" # $JENKINS_PASSWORD must exist in the environment + # [apps.argo.setString] # values to override values from values.yaml with values from env vars or directly entered-- useful for passing secrets to charts + # AdminPassword="$SOME_PASSWORD" # $JENKINS_PASSWORD must exist in the environment # MyLongIntVar="1234567890" - [apps.jenkins.set] - AdminUser="admin" + # [apps.argo.set] + # installCRD="true" - # artifactory will be deployed using the Tiller in the kube-system namespace [apps.artifactory] namespace = "production" # maps to the namespace as defined in namespaces above enabled = true # change to false if you want to delete this app release [default = false] - chart = "stable/artifactory" # changing the chart name means delete and recreate this release - version = "7.0.6" # chart version + chart = "jfrog/artifactory" # changing the chart name means delete and recreate this release + version = "8.3.2" # chart version ### Optional values below - name = "artifactory" # should be unique across all apps which are managed by the same Tiller valuesFile = "" # leaving it empty uses the default chart values - purge = false # will only be considered when there is a delete operation test = false # run the tests when this release is installed for the first time only priority= -2 + noHooks= false + timeout= 300 + helmFlags= [] # additional helm flags for this release # See https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md#apps for more apps options diff --git a/examples/example.yaml b/examples/example.yaml index 661eeefd..df344702 100644 --- a/examples/example.yaml +++ b/examples/example.yaml @@ -24,7 +24,6 @@ settings: #password: "$K8S_PASSWORD" # the name of an environment variable containing the k8s password #clusterURI: "$SET_URI" # the name of an environment variable containing the cluster API #clusterURI: "https://192.168.99.100:8443" # equivalent to the above - #serviceAccount: "foo" # k8s serviceaccount must be already defined, validation error will be thrown otherwise storageBackend: "secret" #slackWebhook: "$slack" # or your slack webhook url #reverseDelete: false # reverse the priorities on delete @@ -34,7 +33,7 @@ settings: # define your environments and their k8s namespaces namespaces: - test1: + production: protected: true limits: - type: Container @@ -68,23 +67,30 @@ helmRepos: apps: - # jenkins will be deployed using the Tiller in the staging namespace argo: - namespace: "test1" # maps to the namespace as defined in namespaces above + namespace: "staging" # maps to the namespace as defined in namespaces above enabled: true # change to false if you want to delete this app release empty: false: chart: "argo/argo" # changing the chart name means delete and recreate this chart version: "0.6.4" # chart version + ### Optional values below + valuesFile: "" # leaving it empty uses the default chart values + test: false protected: true priority: -3 wait: true - # artifactory will be deployed using the Tiller in the kube-system namespace artifactory: - namespace: "test1" # maps to the namespace as defined in namespaces above + namespace: "production" # maps to the namespace as defined in namespaces above enabled: true # change to false if you want to delete this app release empty: false: chart: "jfrog/artifactory" # changing the chart name means delete and recreate this chart version: "8.3.2" # chart version + ### Optional values below + valuesFile: "" + test: false priority: -2 + noHooks: false + timeout: 300 + helmsFlags: {} # additional helm flags for this release # See https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md#apps for more apps options From 17bae3c2b009d7df26381ecba20b4ee5eac75a3a Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Sat, 21 Dec 2019 14:00:02 +0100 Subject: [PATCH 0530/1127] updating readme and contribution --- CONTRIBUTION.md | 20 ++++++++++++++++---- README.md | 12 ++++++++---- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md index 8bc0ad3d..f7e3c107 100644 --- a/CONTRIBUTION.md +++ b/CONTRIBUTION.md @@ -4,16 +4,28 @@ Pull requests, feeback/feature requests are all welcome. This guide will be upda ## Build helmsman from source -To build helmsman from source, you need go:1.9+. Follow the steps below: +To build helmsman from source, you need go:1.13+. Follow the steps below: ``` git clone https://github.com/Praqma/helmsman.git make build ``` +## The branches and tags + +`master` is where Helmsman latest code lives. +`1.x` this is where Helmsman versions 1.x lives. +> Helmsman v1.x supports helm v2.x only and will no longer be supported except for bug fixes and minor changes. + ## Submitting pull requests -Please make sure you state the purpose of the pull request and that the code you submit is documented. If in doubt, [this guide](https://blog.github.com/2015-01-21-how-to-write-the-perfect-pull-request/) offers some good tips on writing a PR. +- If your PR is for Helmsman v1.x, it should target the `1.x` branch. +- Please make sure you state the purpose of the pull request and that the code you submit is documented. If in doubt, [this guide](https://blog.github.com/2015-01-21-how-to-write-the-perfect-pull-request/) offers some good tips on writing a PR. +- Please make sure you update the documentation with new features or the changes your PR adds. The following places are required. + - Update existing [how_to](docs/how_to/) guides or create new ones. + - If necessary, Update the [Desired State File spec](docs/desired_state_specification.md) + - If adding new flags, Update the [cmd reference](docs/cmd_reference.md) +- Please add tests wherever possible to test your new changes. ## Contribution to documentation @@ -25,10 +37,10 @@ Please provide details of the issue, versions of helmsman, helm and kubernetes a ## Releasing Helmsman -Release is automated from CicrcleCI based on Git tags. [Goreleaser](goreleaser.com) is used to release the binaries and update the release notes on Github while the circleci pipeline builds a set of docker images and pushes them to dockerhub. +Release is automated from CicrcleCI based on Git tags. [Goreleaser](goreleaser.com) is used to release the binaries and update the release notes on Github while the CircleCI pipeline builds a set of docker images and pushes them to dockerhub. The following steps are needed to cut a release (They assume that you are on master and the code is up to date): -1. Change the version variable in [main.go](main.go) +1. Change the version variable in [main.go](internal/app/main.go) and in [.version](.version) 2. Update the [release-notes.md](release-notes.md) file with new version and changelog. 3. (Optional), if new helm versions are required, update the [circleci config](.circleci/config.yml) and add more docker commands. 4. Commit your changes locally. diff --git a/README.md b/README.md index 23502465..4b7f132a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v1.12.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.0.0-beta1&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) +> Helmsman v3.0.0 works only with Helm versions >=3.0.0. For older Helm versions, use Helmsman v1.x + # What is Helmsman? Helmsman is a Helm Charts (k8s applications) as Code tool which allows you to automate the deployment/management of your Helm charts from version controlled code. @@ -50,7 +52,7 @@ To limit execution to specific application: Please make sure the following are installed prior to using `helmsman` as a binary (the docker image contains all of them): - [kubectl](https://github.com/kubernetes/kubectl) -- [helm](https://github.com/helm/helm) (for `helmsman` >= 1.6.0, use helm >= 2.10.0. this is due to a dependency bug #87 ) +- [helm](https://github.com/helm/helm) (for `helmsman` >= 1.6.0) - [helm-diff](https://github.com/databus23/helm-diff) (`helmsman` >= 1.6.0) If you use private helm repos, you will need either `helm-gcs` or `helm-s3` plugin or you can use basic auth to authenticate to your repos. See the [docs](https://github.com/Praqma/helmsman/blob/master/docs/how_to/helm_repos) for details. @@ -59,9 +61,9 @@ If you use private helm repos, you will need either `helm-gcs` or `helm-s3` plug Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. ``` # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v1.12.0/helmsman_1.12.0_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.0.0-beta1/helmsman_3.0.0-beta1_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v1.12.0/helmsman_1.12.0_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.0.0-beta1/helmsman_3.0.0-beta1_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` @@ -74,6 +76,8 @@ Helmsman has been packaged in Archlinux under `helmsman-bin` for the latest bina # Documentation +> Documentation for Helmsman v1.x can be found at: https://github.com/Praqma/helmsman/tree/1.x/docs + - [How-Tos](https://github.com/Praqma/helmsman/blob/master/docs/how_to/). - [Desired state specification](https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md). From e494dfac9306042a8e5e6f2e347399950e5ee57f Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Sat, 21 Dec 2019 14:00:45 +0100 Subject: [PATCH 0531/1127] changing some flags defaults --- internal/app/cli.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/app/cli.go b/internal/app/cli.go index c96c9c0a..78b82b9c 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -27,7 +27,7 @@ func printUsage() { fmt.Printf("Helmsman version: " + appVersion + "\n") fmt.Printf("Helmsman is a Helm Charts as Code tool which allows you to automate the deployment/management of your Helm charts.") fmt.Printf("") - fmt.Printf("Usage: helmsman [options]") + fmt.Printf("Usage: helmsman [options]\n") flag.PrintDefaults() } @@ -42,7 +42,7 @@ func Cli() { flag.BoolVar(&dryRun, "dry-run", false, "apply the dry-run option for helm commands.") flag.Var(&target, "target", "limit execution to specific app.") flag.Var(&group, "group", "limit execution to specific group of apps.") - flag.BoolVar(&destroy, "destroy", false, "delete all deployed releases. Purge delete is used if the purge option is set to true for the releases.") + flag.BoolVar(&destroy, "destroy", false, "delete all deployed releases.") flag.BoolVar(&v, "v", false, "show the version") flag.BoolVar(&verbose, "verbose", false, "show verbose execution logs") flag.BoolVar(&noBanner, "no-banner", false, "don't show the banner") @@ -51,12 +51,12 @@ func Cli() { flag.BoolVar(&noNs, "no-ns", false, "don't create namespaces") flag.StringVar(&nsOverride, "ns-override", "", "override defined namespaces with this one") flag.BoolVar(&skipValidation, "skip-validation", false, "skip desired state validation") - flag.BoolVar(&keepUntrackedReleases, "keep-untracked-releases", false, "keep releases that are managed by Helmsman and are no longer tracked in your desired state.") + flag.BoolVar(&keepUntrackedReleases, "keep-untracked-releases", false, "keep releases that are managed by Helmsman from the used DSFs in the command, and are no longer tracked in your desired state.") flag.BoolVar(&showDiff, "show-diff", false, "show helm diff results. Can expose sensitive information.") - flag.BoolVar(&suppressDiffSecrets, "suppress-diff-secrets", false, "don't show secrets in helm diff output.") + flag.BoolVar(&suppressDiffSecrets, "suppress-diff-secrets", true, "don't show secrets in helm diff output. (default true).") flag.IntVar(&diffContext, "diff-context", -1, "number of lines of context to show around changes in helm diff output") flag.BoolVar(&noEnvSubst, "no-env-subst", false, "turn off environment substitution globally") - flag.BoolVar(&noEnvValuesSubst, "no-env-values-subst", true, "turn off environment substitution in values files only") + flag.BoolVar(&noEnvValuesSubst, "no-env-values-subst", true, "turn off environment substitution in values files only. (default true).") flag.BoolVar(&noSSMSubst, "no-ssm-subst", false, "turn off SSM parameter substitution globally") flag.BoolVar(&noSSMValuesSubst, "no-ssm-values-subst", true, "turn off SSM parameter substitution in values files only") flag.BoolVar(&updateDeps, "update-deps", false, "run 'helm dep up' for local chart") From 5b4b2bd5abbbc6d8108d262b30982994c8350287 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Sat, 21 Dec 2019 14:15:07 +0100 Subject: [PATCH 0532/1127] fix typo --- examples/example.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/example.yaml b/examples/example.yaml index df344702..ce6e13b8 100644 --- a/examples/example.yaml +++ b/examples/example.yaml @@ -91,6 +91,6 @@ apps: priority: -2 noHooks: false timeout: 300 - helmsFlags: {} # additional helm flags for this release + helmFlags: {} # additional helm flags for this release # See https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md#apps for more apps options From 2fbc931a594da8e0f486d5928efcebfbb884770c Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Sat, 21 Dec 2019 14:22:27 +0100 Subject: [PATCH 0533/1127] fix typo ..again --- examples/example.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/example.yaml b/examples/example.yaml index ce6e13b8..657b65b6 100644 --- a/examples/example.yaml +++ b/examples/example.yaml @@ -91,6 +91,6 @@ apps: priority: -2 noHooks: false timeout: 300 - helmFlags: {} # additional helm flags for this release + helmFlags: [] # additional helm flags for this release # See https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md#apps for more apps options From cc09f94fad27cc843539a61f87bc141b50d017b2 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Sat, 21 Dec 2019 14:29:11 +0100 Subject: [PATCH 0534/1127] updating release notes --- release-notes.md | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/release-notes.md b/release-notes.md index 101c1cf3..174da7e4 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,10 +1,18 @@ -# v1.13.1 +# v3.0.0-beta1 -> If you are already using an older version of Helmsman than v1.4.0-rc, please read the changes below carefully and follow the upgrade guide [here](docs/migrating_to_v1.4.0-rc.md) +This is a major release to support Helm v3. +It is recommended you read the [Helm 3 migration guide](https://helm.sh/docs/topics/v2_v3_migration/) before using this release. -# Fixes and improvements: -- Fixes bug that prevented Helmsman from deleting untracked releases; PR #337 -- Bumped to go v1.13.5; PR #333 +> Starting from this release, support for Helmsman v1.x will be limited to bug fixes. + +The following are the most important changes: +- A new and improved logger. +- Restructuring the code. +- Introducing the `context` stanza to define a context for each DSF. More details [here](docs/misc/merge_desired_state_files). +- Deprecating all the DSF stanzas related to Tiller. +- Deprecating the `purge` option for releases. +- The default value for `storageBackend` is now `secret`. +- The `--suppress-diff-secrets` cmd flag is enabled by default. +- The `--no-env-values-subst` cmd flag is enabled by default. +- The `--no-ssm-values-subst` cmd flag is enabled by default. -# New features: -None, bug fix release From 8b9f187d5d39bf3f76f94fa805558d4c139bbb4e Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Sat, 21 Dec 2019 14:41:47 +0100 Subject: [PATCH 0535/1127] explicitly setting the main func --- .goreleaser.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index 6619ee39..b4041cb5 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -10,4 +10,5 @@ builds: - linux - windows goarch: - - amd64 \ No newline at end of file + - amd64 + main: ./cmd/helmsman/main.go \ No newline at end of file From 94bb0d9c31e34d5321ba480bd42a05afffaf8c10 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Sat, 21 Dec 2019 14:47:31 +0100 Subject: [PATCH 0536/1127] releasing v3.0.0-beta1 --- .goreleaser.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index b4041cb5..20e0f8ac 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -11,4 +11,4 @@ builds: - windows goarch: - amd64 - main: ./cmd/helmsman/main.go \ No newline at end of file + main: ./cmd/helmsman/main.go \ No newline at end of file From dc476986462d963487661b84665dc751a82f4d74 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Sat, 21 Dec 2019 14:58:32 +0100 Subject: [PATCH 0537/1127] updating ci config --- .circleci/config.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4ab3dc90..2fc08b24 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -21,6 +21,7 @@ jobs: name: release helmsman command: | echo "releasing ..." + make build curl -sL https://git.io/goreleaser | bash -s -- --release-notes release-notes.md --rm-dist - run: @@ -40,6 +41,8 @@ workflows: tags: only: /.*/ - release: + requires: + - build filters: branches: ignore: /.*/ From 05d5a37a499ec658f8d399b89e556f624d87ce8d Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Sat, 21 Dec 2019 15:18:38 +0100 Subject: [PATCH 0538/1127] adding dep in the release step --- .circleci/config.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2fc08b24..4e429404 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -21,6 +21,7 @@ jobs: name: release helmsman command: | echo "releasing ..." + curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh make build curl -sL https://git.io/goreleaser | bash -s -- --release-notes release-notes.md --rm-dist @@ -41,8 +42,6 @@ workflows: tags: only: /.*/ - release: - requires: - - build filters: branches: ignore: /.*/ From 1e1ea726ddd073722bf254bf6df7c4edd30dfde0 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Sat, 21 Dec 2019 15:28:49 +0100 Subject: [PATCH 0539/1127] fix dep install --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4e429404..7d239093 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -21,6 +21,7 @@ jobs: name: release helmsman command: | echo "releasing ..." + mkdir /home/circleci/.go_workspace/bin curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh make build curl -sL https://git.io/goreleaser | bash -s -- --release-notes release-notes.md --rm-dist From 56a8ed94a84b4bd372dd4941767a2c38683236c3 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Sat, 21 Dec 2019 15:36:15 +0100 Subject: [PATCH 0540/1127] trying a different workdirectory for the release job --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7d239093..f7dd0222 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -13,7 +13,7 @@ jobs: docker build --cache-from=praqma/helmsman:latest -t helmsman . release: - working_directory: "/tmp/go/src/helmsman" + working_directory: "/go/src/github.com/Praqma/helmsman" machine: true steps: - checkout From 21845392db813677312d679b71efaf5f0c90743f Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Sat, 21 Dec 2019 16:33:38 +0100 Subject: [PATCH 0541/1127] using Go modules --- .circleci/config.yml | 2 +- Gopkg.lock | 37 +++++++--- go.mod | 33 +++++++++ go.sum | 164 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 227 insertions(+), 9 deletions(-) create mode 100644 go.mod create mode 100644 go.sum diff --git a/.circleci/config.yml b/.circleci/config.yml index f7dd0222..7d239093 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -13,7 +13,7 @@ jobs: docker build --cache-from=praqma/helmsman:latest -t helmsman . release: - working_directory: "/go/src/github.com/Praqma/helmsman" + working_directory: "/tmp/go/src/helmsman" machine: true steps: - checkout diff --git a/Gopkg.lock b/Gopkg.lock index d6a12999..a9a4ad00 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -41,6 +41,19 @@ revision = "3012a1dbe2e4bd1391d42b32f0577cb7bbc7f005" version = "v0.3.1" +[[projects]] + digest = "1:c3fabcdfd716824af4b7b60fd85c15df57c7bbed220a0703212e347b42e7a803" + name = "github.com/Praqma/helmsman" + packages = [ + "internal/app", + "internal/aws", + "internal/azure", + "internal/gcs", + ] + pruneopts = "UT" + revision = "16473a838593436cbb46748fa37d0c89a73d369e" + version = "v3.0.0-beta1" + [[projects]] digest = "1:efe5038e25001cc88547d8ecc9ed76bbb97c15826b9bb91b931ec30dacb4e3f1" name = "github.com/apsdehal/go-logger" @@ -140,6 +153,14 @@ revision = "bd5b16380fd03dc758d11cef74ba2e3bc8b0e8c2" version = "v2.0.5" +[[projects]] + digest = "1:88e0b0baeb9072f0a4afbcf12dda615fc8be001d1802357538591155998da21b" + name = "github.com/hashicorp/go-version" + packages = ["."] + pruneopts = "UT" + revision = "ac23dc3fea5d1a983c43f6a0f6e2c13f0195d8bd" + version = "v1.2.0" + [[projects]] digest = "1:78d28d5b84a26159c67ea51996a230da4bc07cac648adaae1dfb5fc0ec8e40d3" name = "github.com/imdario/mergo" @@ -220,13 +241,12 @@ [[projects]] branch = "master" - digest = "1:17be6a1681d96acc66795d9618ae4a44405e4a1d5964193cdc5282f2ae151602" + digest = "1:676f320d34ccfa88bfa6d04bdf388ed7062af175355c805ef57ccda1a3f13432" name = "golang.org/x/net" packages = [ "context", "context/ctxhttp", "http/httpguts", - "http/httpproxy", "http2", "http2/hpack", "idna", @@ -252,13 +272,9 @@ [[projects]] branch = "master" - digest = "1:9cb08334ba1fb31cca05f3703d21189db6f6ba40d2d28c9884bf31707b91d16b" + digest = "1:634414cd43ed62a728aaf9b3e61fde85f012880f9b32b7d00678b6fd887ff9c1" name = "golang.org/x/sys" - packages = [ - "unix", - "windows", - "windows/registry", - ] + packages = ["unix"] pruneopts = "UT" revision = "ac6580df4449443a05718fd7858c1f91ad5f8d20" @@ -459,12 +475,17 @@ "github.com/Azure/azure-pipeline-go/pipeline", "github.com/Azure/azure-storage-blob-go/azblob", "github.com/BurntSushi/toml", + "github.com/Praqma/helmsman/internal/app", + "github.com/Praqma/helmsman/internal/aws", + "github.com/Praqma/helmsman/internal/azure", + "github.com/Praqma/helmsman/internal/gcs", "github.com/apsdehal/go-logger", "github.com/aws/aws-sdk-go/aws", "github.com/aws/aws-sdk-go/aws/session", "github.com/aws/aws-sdk-go/service/s3", "github.com/aws/aws-sdk-go/service/s3/s3manager", "github.com/aws/aws-sdk-go/service/ssm", + "github.com/hashicorp/go-version", "github.com/imdario/mergo", "github.com/joho/godotenv", "github.com/logrusorgru/aurora", diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..c0a51ca6 --- /dev/null +++ b/go.mod @@ -0,0 +1,33 @@ +module github.com/Praqma/helmsman + +go 1.13 + +require ( + cloud.google.com/go v0.38.0 + github.com/Azure/azure-pipeline-go v0.1.9 + github.com/Azure/azure-storage-blob-go v0.0.0-20181022225951-5152f14ace1c + github.com/BurntSushi/toml v0.3.1 + github.com/apsdehal/go-logger v0.0.0-20190515211354-1abdf898e024 + github.com/aws/aws-sdk-go v1.26.2 + github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 + github.com/golang/protobuf v1.3.2 + github.com/hashicorp/go-version v1.2.0 + github.com/imdario/mergo v0.3.8 + github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af + github.com/joho/godotenv v1.3.0 + github.com/logrusorgru/aurora v0.0.0-20191116043053-66b7ad493a23 + go.opencensus.io v0.22.2 + golang.org/x/exp v0.0.0-20191129062945-2f5052295587 + golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f + golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 + golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 + golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 + golang.org/x/text v0.3.2 + golang.org/x/tools v0.0.0-20191213221258-04c2e8eff935 + google.golang.org/api v0.14.0 + google.golang.org/appengine v1.6.5 + google.golang.org/genproto v0.0.0-20191206224255-0243a4be9c8f + google.golang.org/grpc v1.25.1 + gopkg.in/yaml.v2 v2.2.7 + honnef.co/go/tools v0.0.1-2019.2.3 +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..4b1beade --- /dev/null +++ b/go.sum @@ -0,0 +1,164 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.28.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/azure-pipeline-go v0.1.8/go.mod h1:XA1kFWRVhSK+KNFiOhfv83Fv8L9achrP7OxIzeTn1Yg= +github.com/Azure/azure-pipeline-go v0.1.9 h1:u7JFb9fFTE6Y/j8ae2VK33ePrRqJqoCM/IWkQdAZ+rg= +github.com/Azure/azure-pipeline-go v0.1.9/go.mod h1:XA1kFWRVhSK+KNFiOhfv83Fv8L9achrP7OxIzeTn1Yg= +github.com/Azure/azure-storage-blob-go v0.0.0-20181022225951-5152f14ace1c h1:Y5ueznoCekgCWBytF1Q9lTpZ3tJeX37dQtCcGjMCLYI= +github.com/Azure/azure-storage-blob-go v0.0.0-20181022225951-5152f14ace1c/go.mod h1:oGfmITT1V6x//CswqY2gtAHND+xIP64/qL7a5QJix0Y= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/apsdehal/go-logger v0.0.0-20190515211354-1abdf898e024 h1:dfZ6RF0UxHqt7xPz0r7h00apsaa6rIrFhT6Xly55Exk= +github.com/apsdehal/go-logger v0.0.0-20190515211354-1abdf898e024/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= +github.com/aws/aws-sdk-go v1.26.2 h1:MzYLmCeny4bMQcAbYcucIduVZKp0sEf1eRLvHpKI5Is= +github.com/aws/aws-sdk-go v1.26.2/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 h1:uHTyIjqVhYRhLbJ8nIiOJHkEZZ+5YoOsAbD3sk82NiE= +github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ= +github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/logrusorgru/aurora v0.0.0-20191116043053-66b7ad493a23 h1:Wp7NjqGKGN9te9N/rvXYRhlVcrulGdxnz8zadXWs7fc= +github.com/logrusorgru/aurora v0.0.0-20191116043053-66b7ad493a23/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 h1:pE8b58s1HRDMi8RDc79m0HISf9D4TzseP40cEA6IGfs= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191213221258-04c2e8eff935/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.14.0 h1:uMf5uLi4eQMRrMKhCplNik4U4H8Z6C1br3zOtAa/aDE= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20191206224255-0243a4be9c8f h1:naitw5DILWPQvG0oG04mR9jF8fmKpRdW3E3zzKA4D0Y= +google.golang.org/genproto v0.0.0-20191206224255-0243a4be9c8f/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1 h1:wdKvqQk7IttEw92GoRyKG2IDrUIpgpj6H6m81yfeMW0= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= From e846fdf06e35b0ba97ff5cee22b9d091083333d7 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Sat, 21 Dec 2019 17:02:09 +0100 Subject: [PATCH 0542/1127] reconfiguring goreleaser job to run in docker --- .circleci/config.yml | 37 ++++++++++++++++++++++++++++--------- go.sum | 1 + 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7d239093..8db7380c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -11,20 +11,31 @@ jobs: command: | docker pull praqma/helmsman:latest docker build --cache-from=praqma/helmsman:latest -t helmsman . - + release: - working_directory: "/tmp/go/src/helmsman" - machine: true + docker: + - image: goreleaser/goreleaser + working_directory: "/go/src/github.com/Praqma/helmsman" steps: - checkout - run: - name: release helmsman + name: release command: | - echo "releasing ..." - mkdir /home/circleci/.go_workspace/bin - curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh - make build - curl -sL https://git.io/goreleaser | bash -s -- --release-notes release-notes.md --rm-dist + release --release-notes release-notes.md + + docker-release: + working_directory: "/tmp/go/src/helmsman" + machine: true + steps: + - checkout + # - run: + # name: release helmsman + # command: | + # echo "releasing ..." + # mkdir /home/circleci/.go_workspace/bin + # curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh + # make build + # curl -sL https://git.io/goreleaser | bash -s -- --release-notes release-notes.md --rm-dist - run: name: build docker images and push them to dockerhub @@ -48,3 +59,11 @@ workflows: ignore: /.*/ tags: only: /v[0-9]+(\.[0-9]+)*(-.*)*/ + - docker-release: + requires: + - release + filters: + branches: + ignore: /.*/ + tags: + only: /v[0-9]+(\.[0-9]+)*(-.*)*/ diff --git a/go.sum b/go.sum index 4b1beade..e90cb6ba 100644 --- a/go.sum +++ b/go.sum @@ -111,6 +111,7 @@ golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 h1:gSbV7h1NRL2G1xTg/owz62CST1oJBmxy4QpMMregXVQ= golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From bef4460cbc5058d3d1d3e397ba5fb254065eb1d7 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Sat, 21 Dec 2019 17:10:20 +0100 Subject: [PATCH 0543/1127] add git to the goreleaser image --- .circleci/config.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8db7380c..95f0771a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -11,17 +11,21 @@ jobs: command: | docker pull praqma/helmsman:latest docker build --cache-from=praqma/helmsman:latest -t helmsman . - + release: docker: - image: goreleaser/goreleaser + entrypoint: /bin/bash working_directory: "/go/src/github.com/Praqma/helmsman" steps: + - run: + name: install git + command: apk update && apk add --no-cache git - checkout - run: name: release command: | - release --release-notes release-notes.md + goreleaser release --release-notes release-notes.md docker-release: working_directory: "/tmp/go/src/helmsman" From d041992d25538bc9a62999196710078893c1469a Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Sat, 21 Dec 2019 17:12:00 +0100 Subject: [PATCH 0544/1127] add openssh to the goreleaser image --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 95f0771a..c8037cbf 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -20,7 +20,7 @@ jobs: steps: - run: name: install git - command: apk update && apk add --no-cache git + command: apk update && apk add --no-cache git openssh - checkout - run: name: release From 458f1e1894d9a217e16a4606f4c45d93e167832a Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Sun, 22 Dec 2019 11:08:58 +0100 Subject: [PATCH 0545/1127] Add goroutines-based paralle execution of plan and charts availability checking --- Gopkg.lock | 17 ----- internal/app/decision_maker.go | 18 +++-- internal/app/decision_maker_test.go | 14 ++-- internal/app/helm_helpers.go | 109 ++++++++++++++++------------ internal/app/helm_helpers_test.go | 25 +++++-- internal/app/main.go | 4 +- 6 files changed, 105 insertions(+), 82 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index a9a4ad00..7e4fad17 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -41,19 +41,6 @@ revision = "3012a1dbe2e4bd1391d42b32f0577cb7bbc7f005" version = "v0.3.1" -[[projects]] - digest = "1:c3fabcdfd716824af4b7b60fd85c15df57c7bbed220a0703212e347b42e7a803" - name = "github.com/Praqma/helmsman" - packages = [ - "internal/app", - "internal/aws", - "internal/azure", - "internal/gcs", - ] - pruneopts = "UT" - revision = "16473a838593436cbb46748fa37d0c89a73d369e" - version = "v3.0.0-beta1" - [[projects]] digest = "1:efe5038e25001cc88547d8ecc9ed76bbb97c15826b9bb91b931ec30dacb4e3f1" name = "github.com/apsdehal/go-logger" @@ -475,10 +462,6 @@ "github.com/Azure/azure-pipeline-go/pipeline", "github.com/Azure/azure-storage-blob-go/azblob", "github.com/BurntSushi/toml", - "github.com/Praqma/helmsman/internal/app", - "github.com/Praqma/helmsman/internal/aws", - "github.com/Praqma/helmsman/internal/azure", - "github.com/Praqma/helmsman/internal/gcs", "github.com/apsdehal/go-logger", "github.com/aws/aws-sdk-go/aws", "github.com/aws/aws-sdk-go/aws/session", diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index 609baba6..9261e8c2 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -5,6 +5,7 @@ import ( "regexp" "strconv" "strings" + "sync" ) var outcome plan @@ -16,17 +17,21 @@ func makePlan(s *state) *plan { outcome = createPlan() buildState() + wg := sync.WaitGroup{} for _, r := range s.Apps { checkChartDepUpdate(r) - decide(r, s) + wg.Add(1) + go decide(r, s, &wg) } + wg.Wait() return &outcome } // decide makes a decision about what commands (actions) need to be executed // to make a release section of the desired state come true. -func decide(r *release, s *state) { +func decide(r *release, s *state, wg *sync.WaitGroup) { + defer wg.Done() // check for presence in defined targets or groups if !r.isReleaseConsideredToRun() { logDecision("Release [ "+r.Name+" ] ignored", r.Priority, ignored) @@ -97,6 +102,7 @@ func decide(r *release, s *state) { " current context: [ " + s.Context + " ]. Applying changes will likely cause conflicts. Change the release name or namespace.") } } + return } @@ -346,8 +352,8 @@ func getValuesFiles(r *release) []string { if !helmPluginExists("secrets") { log.Fatal("helm secrets plugin is not installed/configured correctly. Aborting!") } - if ok := decryptSecret(r.SecretsFile); !ok { - log.Fatal("Failed to decrypt secret file" + r.SecretsFile) + if err := decryptSecret(r.SecretsFile); err != nil { + log.Fatal(err.Error()) } fileList = append(fileList, r.SecretsFile+".dec") } else if len(r.SecretsFiles) > 0 { @@ -355,8 +361,8 @@ func getValuesFiles(r *release) []string { log.Fatal("helm secrets plugin is not installed/configured correctly. Aborting!") } for i := 0; i < len(r.SecretsFiles); i++ { - if ok := decryptSecret(r.SecretsFiles[i]); !ok { - log.Fatal("Failed to decrypt secret file" + r.SecretsFiles[i]) + if err := decryptSecret(r.SecretsFiles[i]); err != nil { + log.Fatal(err.Error()) } // if .dec extension is added before to the secret filename, don't add it again. // This happens at upgrade time (where diff and upgrade both call this function) diff --git a/internal/app/decision_maker_test.go b/internal/app/decision_maker_test.go index 707d4d76..17dd3ef8 100644 --- a/internal/app/decision_maker_test.go +++ b/internal/app/decision_maker_test.go @@ -2,6 +2,7 @@ package app import ( "reflect" + "sync" "testing" ) @@ -209,9 +210,11 @@ func Test_decide(t *testing.T) { targetMap[target] = true } outcome = plan{} - + wg := sync.WaitGroup{} + wg.Add(1) // Act - decide(tt.args.r, tt.args.s) + decide(tt.args.r, tt.args.s, &wg) + wg.Wait() got := outcome.Decisions[0].Type t.Log(outcome.Decisions[0].Description) @@ -294,9 +297,10 @@ func Test_decide_group(t *testing.T) { groupMap[group] = true } outcome = plan{} - - // Act - decide(tt.args.r, tt.args.s) + wg := sync.WaitGroup{} + wg.Add(1) + decide(tt.args.r, tt.args.s, &wg) + wg.Wait() got := outcome.Decisions[0].Type t.Log(outcome.Decisions[0].Description) diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index d28f78f5..c3c24741 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -2,6 +2,7 @@ package app import ( "encoding/json" + "errors" "fmt" "net/url" "os" @@ -9,6 +10,7 @@ import ( "regexp" "strconv" "strings" + "sync" "time" "github.com/Praqma/helmsman/internal/gcs" @@ -160,56 +162,72 @@ func getReleaseChartVersion(rs releaseState) string { // validateReleaseCharts validates if the charts defined in a release are valid. // Valid charts are the ones that can be found in the defined repos. // This function uses Helm search to verify if the chart can be found or not. -func validateReleaseCharts(apps map[string]*release) (bool, string) { +func validateReleaseCharts(apps map[string]*release) error { versionExtractor := regexp.MustCompile(`version:\s?(.*)`) + wg := sync.WaitGroup{} + c := make(chan string, len(apps)) for app, r := range apps { - validateCurrentChart := true - if !r.isReleaseConsideredToRun() { - validateCurrentChart = false - } - if validateCurrentChart { - if isLocalChart(r.Chart) { - cmd := command{ - Cmd: helmBin, - Args: []string{"inspect", "chart", r.Chart}, - Description: "Validating [ " + r.Chart + " ] chart's availability", - } + wg.Add(1) + go func(app string, r *release, wg *sync.WaitGroup, c chan string) { + defer wg.Done() + validateCurrentChart := true + if !r.isReleaseConsideredToRun() { + validateCurrentChart = false + } + if validateCurrentChart { + if isLocalChart(r.Chart) { + cmd := command{ + Cmd: helmBin, + Args: []string{"inspect", "chart", r.Chart}, + Description: "Validating [ " + r.Chart + " ] chart's availability", + } - var output string - var exitCode int - if exitCode, output, _ = cmd.exec(debug, verbose); exitCode != 0 { - maybeRepo := filepath.Base(filepath.Dir(r.Chart)) - return false, "Chart [ " + r.Chart + " ] for app [" + app + "] can't be found. Did you mean to add a repo [ " + maybeRepo + " ]?" - } - matches := versionExtractor.FindStringSubmatch(output) - if len(matches) == 2 { - version := matches[1] - if r.Version != version { - return false, "Chart [ " + r.Chart + " ] with version [ " + r.Version + " ] is specified for " + - "app [" + app + "] but the chart found at that path has version [ " + version + " ] which does not match." + var output string + var exitCode int + if exitCode, output, _ = cmd.exec(debug, verbose); exitCode != 0 { + maybeRepo := filepath.Base(filepath.Dir(r.Chart)) + c <- "Chart [ " + r.Chart + " ] for app [" + app + "] can't be found. Did you mean to add a repo [ " + maybeRepo + " ]?" + return + } + matches := versionExtractor.FindStringSubmatch(output) + if len(matches) == 2 { + version := matches[1] + if r.Version != version { + c <- "Chart [ " + r.Chart + " ] with version [ " + r.Version + " ] is specified for " + + "app [" + app + "] but the chart found at that path has version [ " + version + " ] which does not match." + return + } } - } - } else { - version := r.Version - if len(version) == 0 { - version = "*" - } - cmd := command{ - Cmd: helmBin, - Args: []string{"search", "repo", r.Chart, "--version", version, "-l"}, - Description: "Validating [ " + r.Chart + " ] chart's version [ " + r.Version + " ] availability", - } + } else { + version := r.Version + if len(version) == 0 { + version = "*" + } + cmd := command{ + Cmd: helmBin, + Args: []string{"search", "repo", r.Chart, "--version", version, "-l"}, + Description: "Validating [ " + r.Chart + " ] chart's version [ " + r.Version + " ] availability", + } - if exitCode, result, _ := cmd.exec(debug, verbose); exitCode != 0 || strings.Contains(result, "No results found") { - return false, "Chart [ " + r.Chart + " ] with version [ " + r.Version + " ] is specified for " + - "app [" + app + "] but was not found" + if exitCode, result, _ := cmd.exec(debug, verbose); exitCode != 0 || strings.Contains(result, "No results found") { + c <- "Chart [ " + r.Chart + " ] with version [ " + r.Version + " ] is specified for " + + "app [" + app + "] but was not found" + return + } } } + }(app, r, &wg, c) + } + wg.Wait() + if len(c) > 0 { + err := <-c + if err != "" { + return errors.New(err) } } - return true, "" + return nil } // getChartVersion fetches the lastest chart version matching the semantic versioning constraints. @@ -348,7 +366,7 @@ func uninstallUntrackedRelease(release releaseState) { } // decrypt a helm secret file -func decryptSecret(name string) bool { +func decryptSecret(name string) error { cmd := helmBin args := []string{"secrets", "dec", name} @@ -370,17 +388,14 @@ func decryptSecret(name string) bool { if !settings.EyamlEnabled { _, fileNotFound := os.Stat(name + ".dec") if fileNotFound != nil && !isOfType(name, []string{".dec"}) { - log.Error(output) - return false + return errors.New(output) } } if exitCode != 0 { - log.Error(output) - return false + return errors.New(output) } else if stderr != "" { - log.Error(stderr) - return false + return errors.New(output) } if settings.EyamlEnabled { @@ -395,7 +410,7 @@ func decryptSecret(name string) bool { log.Fatal("Can't write [ " + outfile + " ] file") } } - return true + return nil } // updateChartDep updates dependencies for a local chart diff --git a/internal/app/helm_helpers_test.go b/internal/app/helm_helpers_test.go index b23b795c..40861917 100644 --- a/internal/app/helm_helpers_test.go +++ b/internal/app/helm_helpers_test.go @@ -225,8 +225,16 @@ func Test_validateReleaseCharts(t *testing.T) { for _, group := range tt.groupFlag { groupMap[group] = true } - if got, msg := validateReleaseCharts(tt.args.apps); got != tt.want { - t.Errorf("validateReleaseCharts() = %v, want %v , msg: %v", got, tt.want, msg) + err := validateReleaseCharts(tt.args.apps) + switch err.(type) { + case nil: + if tt.want != true { + t.Errorf("validateReleaseCharts() = %v, want error", err) + } + case error: + if tt.want != false { + t.Errorf("validateReleaseCharts() = %v, want nil", err) + } } }) } @@ -448,9 +456,16 @@ func Test_eyamlSecrets(t *testing.T) { settings.EyamlEnabled = tt.args.s.EyamlEnabled settings.EyamlPublicKeyPath = tt.args.s.EyamlPublicKeyPath settings.EyamlPrivateKeyPath = tt.args.s.EyamlPrivateKeyPath - got := decryptSecret(tt.args.r.SecretsFile) - if got != tt.want { - t.Errorf("decryptSecret() = %v, want %v", got, tt.want) + err := decryptSecret(tt.args.r.SecretsFile) + switch err.(type) { + case nil: + if tt.want != true { + t.Errorf("decryptSecret() = %v, want error", err) + } + case error: + if tt.want != false { + t.Errorf("decryptSecret() = %v, want nil", err) + } } if _, err := os.Stat(tt.args.r.SecretsFile + ".dec"); err == nil { defer deleteFile(tt.args.r.SecretsFile + ".dec") diff --git a/internal/app/main.go b/internal/app/main.go index fd90c288..ad6d9b43 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -88,8 +88,8 @@ func Main() { if !skipValidation { // validate charts-versions exist in defined repos - if r, msg := validateReleaseCharts(s.Apps); !r { - log.Fatal(msg) + if err := validateReleaseCharts(s.Apps); err != nil { + log.Fatal(err.Error()) } } else { log.Info("Skipping charts' validation.") From 89850b2626a9a999edf959b6733a04db131ceeda Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 24 Dec 2019 14:15:26 +0000 Subject: [PATCH 0546/1127] reorganisig the code using receiver methods --- CONTRIBUTION.md | 16 +- Gopkg.lock | 479 ------------------ Gopkg.toml | 67 --- Makefile | 72 +-- README.md | 8 +- docs/best_practice.md | 6 +- docs/cmd_reference.md | 16 +- docs/deployment_strategies.md | 10 +- docs/desired_state_specification.md | 8 +- docs/how_to/README.md | 2 +- docs/how_to/apps/basic.md | 2 +- docs/how_to/apps/destroy.md | 2 +- docs/how_to/apps/helm_tests.md | 4 +- docs/how_to/apps/moving_across_namespaces.md | 2 +- docs/how_to/apps/order.md | 2 +- docs/how_to/apps/override_namespaces.md | 2 +- docs/how_to/apps/secrets.md | 2 +- docs/how_to/deployments/ci.md | 4 +- docs/how_to/deployments/inside_k8s.md | 8 +- docs/how_to/helm_repos/basic_auth.md | 4 +- docs/how_to/helm_repos/default.md | 6 +- docs/how_to/helm_repos/gcs.md | 2 +- docs/how_to/misc/auth_to_storage_providers.md | 2 +- .../misc/limit-deployment-to-specific-apps.md | 2 +- ...it-deployment-to-specific-group-of-apps.md | 4 +- docs/how_to/misc/merge_desired_state_files.md | 12 +- .../namespaces/labels_and_annotations.md | 4 +- .../creating_kube_context_with_certs.md | 6 +- .../creating_kube_context_with_token.md | 4 +- .../use-hiera-eyaml-as-secrets-encryption.md | 4 +- docs/why_helmsman.md | 8 +- examples/example.toml | 4 +- examples/example.yaml | 6 +- examples/minimal-example.toml | 22 +- examples/minimal-example.yaml | 8 +- go.mod | 26 +- go.sum | 41 +- internal/app/cli.go | 10 +- internal/app/command.go | 6 +- internal/app/decision_maker.go | 443 +++++----------- internal/app/decision_maker_test.go | 23 +- internal/app/helm_helpers.go | 370 +------------- internal/app/helm_helpers_test.go | 475 ----------------- internal/app/helm_release.go | 84 +++ internal/app/helm_time.go | 44 ++ internal/app/kube_helpers.go | 40 +- internal/app/main.go | 20 +- internal/app/namespace.go | 5 +- internal/app/release.go | 375 +++++++++++++- internal/app/release_test.go | 381 +++++++++++++- internal/app/state.go | 2 +- internal/app/utils.go | 64 ++- internal/app/utils_test.go | 91 ++++ release-notes.md | 7 +- 54 files changed, 1361 insertions(+), 1956 deletions(-) delete mode 100644 Gopkg.lock delete mode 100644 Gopkg.toml delete mode 100644 internal/app/helm_helpers_test.go create mode 100644 internal/app/helm_release.go create mode 100644 internal/app/helm_time.go diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md index f7e3c107..8d132853 100644 --- a/CONTRIBUTION.md +++ b/CONTRIBUTION.md @@ -13,15 +13,15 @@ make build ## The branches and tags -`master` is where Helmsman latest code lives. -`1.x` this is where Helmsman versions 1.x lives. -> Helmsman v1.x supports helm v2.x only and will no longer be supported except for bug fixes and minor changes. +`master` is where Helmsman latest code lives. +`1.x` this is where Helmsman versions 1.x lives. +> Helmsman v1.x supports helm v2.x only and will no longer be supported except for bug fixes and minor changes. ## Submitting pull requests -- If your PR is for Helmsman v1.x, it should target the `1.x` branch. +- If your PR is for Helmsman v1.x, it should target the `1.x` branch. - Please make sure you state the purpose of the pull request and that the code you submit is documented. If in doubt, [this guide](https://blog.github.com/2015-01-21-how-to-write-the-perfect-pull-request/) offers some good tips on writing a PR. -- Please make sure you update the documentation with new features or the changes your PR adds. The following places are required. +- Please make sure you update the documentation with new features or the changes your PR adds. The following places are required. - Update existing [how_to](docs/how_to/) guides or create new ones. - If necessary, Update the [Desired State File spec](docs/desired_state_specification.md) - If adding new flags, Update the [cmd reference](docs/cmd_reference.md) @@ -43,8 +43,8 @@ The following steps are needed to cut a release (They assume that you are on mas 1. Change the version variable in [main.go](internal/app/main.go) and in [.version](.version) 2. Update the [release-notes.md](release-notes.md) file with new version and changelog. 3. (Optional), if new helm versions are required, update the [circleci config](.circleci/config.yml) and add more docker commands. -4. Commit your changes locally. +4. Commit your changes locally. 5. Create a git tag with the following command: `git tag -a -m "" ` 6. Push your commit and tag with `git push --follow-tags` -7. This should trigger the [pipeline on circleci](https://circleci.com/gh/Praqma/workflows/helmsman) which eventually releases to Github and dockerhub. - +7. This should trigger the [pipeline on circleci](https://circleci.com/gh/Praqma/workflows/helmsman) which eventually releases to Github and dockerhub. + diff --git a/Gopkg.lock b/Gopkg.lock deleted file mode 100644 index 7e4fad17..00000000 --- a/Gopkg.lock +++ /dev/null @@ -1,479 +0,0 @@ -# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. - - -[[projects]] - digest = "1:9854327327a05877e8c528f6830dcadd05d149f5be7c6405ac499c0202e9d43b" - name = "cloud.google.com/go" - packages = [ - "compute/metadata", - "iam", - "internal", - "internal/optional", - "internal/trace", - "internal/version", - "storage", - ] - pruneopts = "UT" - revision = "97efc2c9ffd9fe8ef47f7f3203dc60bbca547374" - version = "v0.28.0" - -[[projects]] - digest = "1:279540310125d2b219920588d7e2edb2a85b3317b528839166e896ce6b6f211c" - name = "github.com/Azure/azure-pipeline-go" - packages = ["pipeline"] - pruneopts = "UT" - revision = "55fedc85a614dcd0e942a66f302ae3efb83d563c" - version = "v0.1.9" - -[[projects]] - digest = "1:c4a5edf3b0f38e709a78dcc945997678a364c2b5adfd48842a3dd349c352f833" - name = "github.com/Azure/azure-storage-blob-go" - packages = ["azblob"] - pruneopts = "UT" - revision = "5152f14ace1c6db66bd9cb57840703a8358fa7bc" - version = "0.3.0" - -[[projects]] - digest = "1:9f3b30d9f8e0d7040f729b82dcbc8f0dead820a133b3147ce355fc451f32d761" - name = "github.com/BurntSushi/toml" - packages = ["."] - pruneopts = "UT" - revision = "3012a1dbe2e4bd1391d42b32f0577cb7bbc7f005" - version = "v0.3.1" - -[[projects]] - digest = "1:efe5038e25001cc88547d8ecc9ed76bbb97c15826b9bb91b931ec30dacb4e3f1" - name = "github.com/apsdehal/go-logger" - packages = ["."] - pruneopts = "UT" - revision = "1abdf898e0242cf7027d832c3e95388315c25de6" - version = "1.3.0" - -[[projects]] - digest = "1:1742529f9132aa5c14ffadd6c2e87c211ef47fe9b6df1139de41489179f13da0" - name = "github.com/aws/aws-sdk-go" - packages = [ - "aws", - "aws/arn", - "aws/awserr", - "aws/awsutil", - "aws/client", - "aws/client/metadata", - "aws/corehandlers", - "aws/credentials", - "aws/credentials/ec2rolecreds", - "aws/credentials/endpointcreds", - "aws/credentials/processcreds", - "aws/credentials/stscreds", - "aws/csm", - "aws/defaults", - "aws/ec2metadata", - "aws/endpoints", - "aws/request", - "aws/session", - "aws/signer/v4", - "internal/ini", - "internal/s3err", - "internal/sdkio", - "internal/sdkmath", - "internal/sdkrand", - "internal/sdkuri", - "internal/shareddefaults", - "private/protocol", - "private/protocol/eventstream", - "private/protocol/eventstream/eventstreamapi", - "private/protocol/json/jsonutil", - "private/protocol/jsonrpc", - "private/protocol/query", - "private/protocol/query/queryutil", - "private/protocol/rest", - "private/protocol/restxml", - "private/protocol/xml/xmlutil", - "service/s3", - "service/s3/internal/arn", - "service/s3/s3iface", - "service/s3/s3manager", - "service/ssm", - "service/sts", - "service/sts/stsiface", - ] - pruneopts = "UT" - revision = "b4f52c45248fcab90c27f5509504b840ca2fcf10" - version = "v1.26.2" - -[[projects]] - branch = "master" - digest = "1:b7cb6054d3dff43b38ad2e92492f220f57ae6087ee797dca298139776749ace8" - name = "github.com/golang/groupcache" - packages = ["lru"] - pruneopts = "UT" - revision = "611e8accdfc92c4187d399e95ce826046d4c8d73" - -[[projects]] - digest = "1:15b834d22254d28e02f4a591ff89427a1045e597b3832660cb1776546e3523b3" - name = "github.com/golang/protobuf" - packages = [ - "proto", - "protoc-gen-go", - "protoc-gen-go/descriptor", - "protoc-gen-go/generator", - "protoc-gen-go/generator/internal/remap", - "protoc-gen-go/grpc", - "protoc-gen-go/plugin", - "ptypes", - "ptypes/any", - "ptypes/duration", - "ptypes/timestamp", - ] - pruneopts = "UT" - revision = "6c65a5562fc06764971b7c5d05c76c75e84bdbf7" - version = "v1.3.2" - -[[projects]] - digest = "1:4f880e8e7ff8803aab4bcf2c479eb766435f7decaa1edf080d8f2bf56668de1d" - name = "github.com/googleapis/gax-go" - packages = [ - ".", - "v2", - ] - pruneopts = "UT" - revision = "bd5b16380fd03dc758d11cef74ba2e3bc8b0e8c2" - version = "v2.0.5" - -[[projects]] - digest = "1:88e0b0baeb9072f0a4afbcf12dda615fc8be001d1802357538591155998da21b" - name = "github.com/hashicorp/go-version" - packages = ["."] - pruneopts = "UT" - revision = "ac23dc3fea5d1a983c43f6a0f6e2c13f0195d8bd" - version = "v1.2.0" - -[[projects]] - digest = "1:78d28d5b84a26159c67ea51996a230da4bc07cac648adaae1dfb5fc0ec8e40d3" - name = "github.com/imdario/mergo" - packages = ["."] - pruneopts = "UT" - revision = "1afb36080aec31e0d1528973ebe6721b191b0369" - version = "v0.3.8" - -[[projects]] - digest = "1:bb81097a5b62634f3e9fec1014657855610c82d19b9a40c17612e32651e35dca" - name = "github.com/jmespath/go-jmespath" - packages = ["."] - pruneopts = "UT" - revision = "c2b33e84" - -[[projects]] - digest = "1:ecd9aa82687cf31d1585d4ac61d0ba180e42e8a6182b85bd785fcca8dfeefc1b" - name = "github.com/joho/godotenv" - packages = ["."] - pruneopts = "UT" - revision = "23d116af351c84513e1946b527c88823e476be13" - version = "v1.3.0" - -[[projects]] - branch = "master" - digest = "1:82589c260d8f6281bbcdda5f99638e2667054d0760e3783c1ea44dac5df67394" - name = "github.com/logrusorgru/aurora" - packages = ["."] - pruneopts = "UT" - revision = "66b7ad493a23a2523bac50571522bbfe5b90a835" - -[[projects]] - digest = "1:7b6ca9454e8d16e34b251f181a4ae430a25805acca64ea46b0448583b44fcfe6" - name = "go.opencensus.io" - packages = [ - ".", - "internal", - "internal/tagencoding", - "metric/metricdata", - "metric/metricproducer", - "plugin/ochttp", - "plugin/ochttp/propagation/b3", - "resource", - "stats", - "stats/internal", - "stats/view", - "tag", - "trace", - "trace/internal", - "trace/propagation", - "trace/tracestate", - ] - pruneopts = "UT" - revision = "aad2c527c5defcf89b5afab7f37274304195a6b2" - version = "v0.22.2" - -[[projects]] - branch = "master" - digest = "1:b1444bc98b5838c3116ed23e231fee4fa8509f975abd96e5d9e67e572dd01604" - name = "golang.org/x/exp" - packages = [ - "apidiff", - "cmd/apidiff", - ] - pruneopts = "UT" - revision = "2f50522955873285d9bf17ec91e55aec8ae82edc" - -[[projects]] - branch = "master" - digest = "1:334b27eac455cb6567ea28cd424230b07b1a64334a2f861a8075ac26ce10af43" - name = "golang.org/x/lint" - packages = [ - ".", - "golint", - ] - pruneopts = "UT" - revision = "fdd1cda4f05fd1fd86124f0ef9ce31a0b72c8448" - -[[projects]] - branch = "master" - digest = "1:676f320d34ccfa88bfa6d04bdf388ed7062af175355c805ef57ccda1a3f13432" - name = "golang.org/x/net" - packages = [ - "context", - "context/ctxhttp", - "http/httpguts", - "http2", - "http2/hpack", - "idna", - "internal/timeseries", - "trace", - ] - pruneopts = "UT" - revision = "c0dbc17a35534bf2e581d7a942408dc936316da4" - -[[projects]] - branch = "master" - digest = "1:e9dec12f847de7d8cd7c1d3d0d89aa40a345a768fd20e10d378a260da9ad65aa" - name = "golang.org/x/oauth2" - packages = [ - ".", - "google", - "internal", - "jws", - "jwt", - ] - pruneopts = "UT" - revision = "858c2ad4c8b6c5d10852cb89079f6ca1c7309787" - -[[projects]] - branch = "master" - digest = "1:634414cd43ed62a728aaf9b3e61fde85f012880f9b32b7d00678b6fd887ff9c1" - name = "golang.org/x/sys" - packages = ["unix"] - pruneopts = "UT" - revision = "ac6580df4449443a05718fd7858c1f91ad5f8d20" - -[[projects]] - digest = "1:8d8faad6b12a3a4c819a3f9618cb6ee1fa1cfc33253abeeea8b55336721e3405" - name = "golang.org/x/text" - packages = [ - "collate", - "collate/build", - "internal/colltab", - "internal/gen", - "internal/language", - "internal/language/compact", - "internal/tag", - "internal/triegen", - "internal/ucd", - "language", - "secure/bidirule", - "transform", - "unicode/bidi", - "unicode/cldr", - "unicode/norm", - "unicode/rangetable", - ] - pruneopts = "UT" - revision = "342b2e1fbaa52c93f31447ad2c6abc048c63e475" - version = "v0.3.2" - -[[projects]] - branch = "master" - digest = "1:3ad1f2cdd02ce959277d4e6680d6c89b92f4caeaed3963b2467418a48b810421" - name = "golang.org/x/tools" - packages = [ - "cmd/goimports", - "go/analysis", - "go/analysis/passes/inspect", - "go/ast/astutil", - "go/ast/inspector", - "go/buildutil", - "go/gcexportdata", - "go/internal/gcimporter", - "go/internal/packagesdriver", - "go/packages", - "go/types/objectpath", - "go/types/typeutil", - "internal/fastwalk", - "internal/gopathwalk", - "internal/imports", - "internal/module", - "internal/semver", - ] - pruneopts = "UT" - revision = "04c2e8eff935f58e9a5568b6b73542f91e0491e6" - -[[projects]] - digest = "1:d1204867de197747eaeedf6eb705968289bce4796c9e9f48ef7c8ef27a227528" - name = "google.golang.org/api" - packages = [ - "googleapi", - "googleapi/transport", - "internal", - "internal/gensupport", - "internal/third_party/uritemplates", - "iterator", - "option", - "storage/v1", - "transport/http", - "transport/http/internal/propagation", - ] - pruneopts = "UT" - revision = "8a410c21381766a810817fd6200fce8838ecb277" - version = "v0.14.0" - -[[projects]] - digest = "1:3c03b58f57452764a4499c55c582346c0ee78c8a5033affe5bdfd9efd3da5bd1" - name = "google.golang.org/appengine" - packages = [ - ".", - "internal", - "internal/app_identity", - "internal/base", - "internal/datastore", - "internal/log", - "internal/modules", - "internal/remote_api", - "internal/urlfetch", - "urlfetch", - ] - pruneopts = "UT" - revision = "971852bfffca25b069c31162ae8f247a3dba083b" - version = "v1.6.5" - -[[projects]] - branch = "master" - digest = "1:c21fbca8f5af1be04e383c4c57be0b85abeea2fe370a54dc805b1b04e443c9e9" - name = "google.golang.org/genproto" - packages = [ - "googleapis/api/annotations", - "googleapis/iam/v1", - "googleapis/rpc/code", - "googleapis/rpc/status", - "googleapis/type/expr", - ] - pruneopts = "UT" - revision = "0243a4be9c8f1264d238fdc2895620b4d9baf9e1" - -[[projects]] - digest = "1:b59ce3ddb11daeeccccc9cb3183b58ebf8e9a779f1c853308cd91612e817a301" - name = "google.golang.org/grpc" - packages = [ - ".", - "backoff", - "balancer", - "balancer/base", - "balancer/roundrobin", - "binarylog/grpc_binarylog_v1", - "codes", - "connectivity", - "credentials", - "credentials/internal", - "encoding", - "encoding/proto", - "grpclog", - "internal", - "internal/backoff", - "internal/balancerload", - "internal/binarylog", - "internal/buffer", - "internal/channelz", - "internal/envconfig", - "internal/grpcrand", - "internal/grpcsync", - "internal/resolver/dns", - "internal/resolver/passthrough", - "internal/syscall", - "internal/transport", - "keepalive", - "metadata", - "naming", - "peer", - "resolver", - "serviceconfig", - "stats", - "status", - "tap", - ] - pruneopts = "UT" - revision = "1a3960e4bd028ac0cec0a2afd27d7d8e67c11514" - version = "v1.25.1" - -[[projects]] - digest = "1:b75b3deb2bce8bc079e16bb2aecfe01eb80098f5650f9e93e5643ca8b7b73737" - name = "gopkg.in/yaml.v2" - packages = ["."] - pruneopts = "UT" - revision = "1f64d6156d11335c3f22d9330b0ad14fc1e789ce" - version = "v2.2.7" - -[[projects]] - digest = "1:131158a88aad1f94854d0aa21a64af2802d0a470fb0f01cb33c04fafd2047111" - name = "honnef.co/go/tools" - packages = [ - "arg", - "cmd/staticcheck", - "config", - "deprecated", - "facts", - "functions", - "go/types/typeutil", - "internal/cache", - "internal/passes/buildssa", - "internal/renameio", - "internal/sharedcheck", - "lint", - "lint/lintdsl", - "lint/lintutil", - "lint/lintutil/format", - "loader", - "printf", - "simple", - "ssa", - "ssautil", - "staticcheck", - "staticcheck/vrp", - "stylecheck", - "unused", - "version", - ] - pruneopts = "UT" - revision = "afd67930eec2a9ed3e9b19f684d17a062285f16a" - version = "2019.2.3" - -[solve-meta] - analyzer-name = "dep" - analyzer-version = 1 - input-imports = [ - "cloud.google.com/go/storage", - "github.com/Azure/azure-pipeline-go/pipeline", - "github.com/Azure/azure-storage-blob-go/azblob", - "github.com/BurntSushi/toml", - "github.com/apsdehal/go-logger", - "github.com/aws/aws-sdk-go/aws", - "github.com/aws/aws-sdk-go/aws/session", - "github.com/aws/aws-sdk-go/service/s3", - "github.com/aws/aws-sdk-go/service/s3/s3manager", - "github.com/aws/aws-sdk-go/service/ssm", - "github.com/hashicorp/go-version", - "github.com/imdario/mergo", - "github.com/joho/godotenv", - "github.com/logrusorgru/aurora", - "golang.org/x/net/context", - "gopkg.in/yaml.v2", - ] - solver-name = "gps-cdcl" - solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml deleted file mode 100644 index c11b6e78..00000000 --- a/Gopkg.toml +++ /dev/null @@ -1,67 +0,0 @@ -# Gopkg.toml example -# -# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html -# for detailed Gopkg.toml documentation. -# -# required = ["github.com/user/thing/cmd/thing"] -# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] -# -# [[constraint]] -# name = "github.com/user/project" -# version = "1.0.0" -# -# [[constraint]] -# name = "github.com/user/project2" -# branch = "dev" -# source = "github.com/myfork/project2" -# -# [[override]] -# name = "github.com/x/y" -# version = "2.4.0" -# -# [prune] -# non-go = false -# go-tests = true -# unused-packages = true - -ignored = ["github.com/Praqma/helmsman"] - -[[constraint]] - version = "0.3.0" - name = "github.com/Azure/azure-storage-blob-go" - -[[constraint]] - name = "cloud.google.com/go" - version = "0.28.0" - -[[constraint]] - name = "github.com/BurntSushi/toml" - version = "0.3.1" - -[[constraint]] - name = "github.com/aws/aws-sdk-go" - version = "1.15.43" - -[[constraint]] - name = "github.com/imdario/mergo" - version = "0.3.6" - -[[constraint]] - name = "github.com/joho/godotenv" - version = "1.3.0" - -[[constraint]] - branch = "master" - name = "github.com/logrusorgru/aurora" - -[[constraint]] - branch = "master" - name = "golang.org/x/net" - -[[constraint]] - name = "gopkg.in/yaml.v2" - version = "2.2.2" - -[prune] - go-tests = true - unused-packages = true diff --git a/Makefile b/Makefile index 73cdabd6..581839af 100644 --- a/Makefile +++ b/Makefile @@ -15,25 +15,6 @@ ifneq ($(strip $(CIRCLE_WORKING_DIRECTORY)),) $(info "Using CIRCLE_WORKING_DIRECTORY for GOPATH") endif -ifneq "$(or $(findstring :,$(GOPATH)),$(findstring ;,$(GOPATH)))" "" - GOPATH := $(lastword $(subst :, ,$(GOPATH))) - $(info GOPATHs with multiple entries are not supported, defaulting to the last path in GOPATH) -endif - -GOPATH := $(realpath $(GOPATH)) -ifeq ($(strip $(GOPATH)),) - $(error GOPATH is not set and could not be automatically determined) -endif - -SRCDIR := $(GOPATH)/src/ -PRJDIR := $(CURDIR) - -ifeq ($(filter $(GOPATH)%,$(CURDIR)),) - GOPATH := $(shell mktemp -d "/tmp/dep.XXXXXXXX") - SRCDIR := $(GOPATH)/src/ - PRJDIR := $(SRCDIR)$(PRJNAME) -endif - ifneq ($(OS),Windows_NT) # Before we start test that we have the mandatory executables available EXECUTABLES = go @@ -42,6 +23,8 @@ ifneq ($(OS),Windows_NT) endif export CGO_ENABLED=0 +export GO111MODULE=on +export GOFLAGS=-mod=vendor help: @echo "Available options:" @@ -58,60 +41,48 @@ fmt: ## Reformat package sources @go fmt ./... .PHONY: fmt -dependencies: ## Ensure all the necessary dependencies - @go get -t -d -v ./... -.PHONY: dependencies +vet: fmt + @go vet ./... +.PHONY: vet -$(SRCDIR): - @mkdir -p $(SRCDIR) - @ln -s $(CURDIR) $(SRCDIR) +deps: ## Install depdendencies. Runs `go get` internally. + @GOFLAGS="" go get -t -d -v ./... + @GOFLAGS="" go mod tidy + @GOFLAGS="" go mod vendor -dep: $(SRCDIR) ## Ensure vendors with dep - @cd $(PRJDIR) && \ - dep ensure -v -.PHONY: dep -dep-update: $(SRCDIR) ## Ensure vendors with dep - @cd $(PRJDIR) && \ - dep ensure -v --update -.PHONY: dep-update +update-deps: ## Update depdendencies. Runs `go get -u` internally. + @GOFLAGS="" go get -u + @GOFLAGS="" go mod tidy + @GOFLAGS="" go mod vendor -build: dep ## Build the package - @cd $(PRJDIR) && \ - go build -o helmsman -ldflags '-X main.version="${TAG}-${DATE}" -extldflags "-static"' cmd/helmsman/main.go +build: vet deps ## Build the package + @go build -o helmsman -ldflags '-X main.version="${TAG}-${DATE}" -extldflags "-static"' cmd/helmsman/main.go generate: @go generate #${PKGS} .PHONY: generate -check: $(SRCDIR) fmt - @cd $(PRJDIR) && \ - dep check && \ - go vet ./... -.PHONY: check - repo: - @cd $(PRJDIR) && \ - helm repo add stable https://kubernetes-charts.storage.googleapis.com + @helm repo add stable https://kubernetes-charts.storage.googleapis.com .PHONY: repo -test: dep check repo ## Run unit tests +test: deps vet repo ## Run unit tests @go test -v -cover -p=1 ./... -args -f ../../examples/example.toml .PHONY: test -cross: dep ## Create binaries for all OSs - @cd $(PRJDIR) && \ - gox -os '!freebsd !netbsd' -arch '!arm' -output "dist/{{.Dir}}_{{.OS}}_{{.Arch}}" -ldflags '-X main.Version=${TAG}-${DATE}' ./... +cross: deps ## Create binaries for all OSs + @gox -os '!freebsd !netbsd' -arch '!arm' -output "dist/{{.Dir}}_{{.OS}}_{{.Arch}}" -ldflags '-X main.Version=${TAG}-${DATE}' ./... .PHONY: cross release: ## Generate a new release - @cd $(PRJDIR) && \ - goreleaser --release-notes release-notes.md --rm-dist + @goreleaser --release-notes release-notes.md --rm-dist tools: ## Get extra tools used by this makefile @go get -u github.com/golang/dep/cmd/dep @go get -u github.com/mitchellh/gox @go get -u github.com/goreleaser/goreleaser + @gem install hiera-eyaml .PHONY: tools helmPlugins: ## Install helm plugins used by Helmsman @@ -120,5 +91,4 @@ helmPlugins: ## Install helm plugins used by Helmsman @helm plugin install https://github.com/nouney/helm-gcs @helm plugin install https://github.com/databus23/helm-diff @helm plugin install https://github.com/futuresimple/helm-secrets - @helm plugin install https://github.com/rimusz/helm-tiller .PHONY: helmPlugins diff --git a/README.md b/README.md index 4b7f132a..d9fe7114 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.0.0-beta1&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.0.0-beta2&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -61,9 +61,9 @@ If you use private helm repos, you will need either `helm-gcs` or `helm-s3` plug Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. ``` # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.0.0-beta1/helmsman_3.0.0-beta1_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.0.0-beta2/helmsman_3.0.0-beta1_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.0.0-beta1/helmsman_3.0.0-beta1_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.0.0-beta2/helmsman_3.0.0-beta1_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` @@ -91,7 +91,7 @@ Helmsman can be used in three different settings: - [As a binary with a hosted cluster](https://github.com/Praqma/helmsman/blob/master/docs/how_to/settings). - [As a docker image in a CI system or local machine](https://github.com/Praqma/helmsman/blob/master/docs/how_to/deployments/ci.md) Always use a tagged docker image from [dockerhub](https://hub.docker.com/r/praqma/helmsman/) as the `latest` image can (at times) be unstable. -- [As a docker image inside a k8s cluster](https://github.com/Praqma/helmsman/blob/master/docs/how_to/deployments/inside_k8s.md) +- [As a docker image inside a k8s cluster](https://github.com/Praqma/helmsman/blob/master/docs/how_to/deployments/inside_k8s.md) # Contributing diff --git a/docs/best_practice.md b/docs/best_practice.md index c72e4f9e..ec49e90f 100644 --- a/docs/best_practice.md +++ b/docs/best_practice.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta1 +version: v3.0.0-beta2 --- # Best Practice @@ -10,7 +10,7 @@ When using Helmsman, we recommend the following best practices: - Define `context` (see [the DSF spec](desired_state_specification.md#context)) for each DSF. This helps prevent different DSFs from operating on each other's releases. -- Store your DSFs in git (or any other VCS) so that you have an audit trail of your deployments. You can also rollback to a previous state by going back to previous commits. +- Store your DSFs in git (or any other VCS) so that you have an audit trail of your deployments. You can also rollback to a previous state by going back to previous commits. > Rollback can be more complex regarding application data. - Do not store secrets in your DSFs! Use one of [the supported ways to pass secrets to your releases](how_to/apps/secrets.md). @@ -23,5 +23,5 @@ When using Helmsman, we recommend the following best practices: - Don't maintain the same release in multiple DSFs. -- While the decision on how many DSFs to use and what each can contain is up to you and depends on your case, we recommend coming up with your own rules for how to split them. For example, you can have one for infra (3rd party tools), one for staging, and one for production apps. +- While the decision on how many DSFs to use and what each can contain is up to you and depends on your case, we recommend coming up with your own rules for how to split them. For example, you can have one for infra (3rd party tools), one for staging, and one for production apps. diff --git a/docs/cmd_reference.md b/docs/cmd_reference.md index 08edc91b..87854d48 100644 --- a/docs/cmd_reference.md +++ b/docs/cmd_reference.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta1 +version: v3.0.0-beta2 --- # CMD reference @@ -30,7 +30,7 @@ This lists available CMD options in Helmsman: desired state file name(s), may be supplied more than once to merge state files. `--force-upgrades` - use --force when upgrading helm releases. May cause resources to be recreated. + use --force when upgrading helm releases. May cause resources to be recreated. `--keep-untracked-releases` keep releases that are managed by Helmsman from the used DSFs in the command, and are no longer tracked in your desired state. @@ -46,12 +46,12 @@ This lists available CMD options in Helmsman: `--no-default-repos` don't set default Helm repos from Google for 'stable' and 'incubator'. - + `--no-env-subst` turn off environment substitution globally. `--no-env-values-subst` - turn off environment substitution in values files only. (default true). + turn off environment substitution in values files only. (default true). `--no-fancy` don't display the banner and don't use colors. @@ -61,9 +61,9 @@ This lists available CMD options in Helmsman: `-no-ssm-subst` turn off SSM parameter substitution globally. - + `-no-ssm-values-subst` - turn off SSM parameter substitution in values files only (default true). + turn off SSM parameter substitution in values files only (default true). `--ns-override string` override defined namespaces with this one. @@ -79,12 +79,12 @@ This lists available CMD options in Helmsman: `--target` limit execution to specific app. - + `--group` limit execution to specific group of apps. `--update-deps` - run 'helm dep up' for local chart + run 'helm dep up' for local chart `--v` show the version. diff --git a/docs/deployment_strategies.md b/docs/deployment_strategies.md index e5bbbfda..42d1c148 100644 --- a/docs/deployment_strategies.md +++ b/docs/deployment_strategies.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta1 +version: v3.0.0-beta2 --- # Deployment Strategies @@ -135,12 +135,12 @@ If you need supporting applications (charts) for your application (e.g, reverse ## Notes on using multiple Helmsman desired state files for the same cluster -Helmsman v3.0.0-beta1 introduces the `context` stanza. -When having multiple DSFs operating on different releases, it is essential to use the `context` stanza in each DSF to define what context the DSF covers. The user-provided value for `context` is used by Helmsman to label and distinguish which DSF manages which deployed releases in the cluster. This way, each helmsman operation will only operate on releases within the context defined in the DSF. +Helmsman v3.0.0-beta2 introduces the `context` stanza. +When having multiple DSFs operating on different releases, it is essential to use the `context` stanza in each DSF to define what context the DSF covers. The user-provided value for `context` is used by Helmsman to label and distinguish which DSF manages which deployed releases in the cluster. This way, each helmsman operation will only operate on releases within the context defined in the DSF. When having multiple DSFs be aware of the following: -- If no context is provided in the DSF (or merged DSFs), `default` is applied as a default context. This means any set of DSFs that don't define custom contexts can still operate on each other's releases (same behavior as in Helmsman 1.x). +- If no context is provided in the DSF (or merged DSFs), `default` is applied as a default context. This means any set of DSFs that don't define custom contexts can still operate on each other's releases (same behavior as in Helmsman 1.x). - If you don't define context in your DSFs, you would need to use the `--keep-untracked-releases` flag to avoid different DSFs deleting each other's releases. @@ -150,6 +150,6 @@ When having multiple DSFs be aware of the following: - If two releases from two different DSFs (each with its own context) have the same name and namespace, Helmsman will only allow the first one of them to be installed. The second will be blocked by Helmsman. -- If you deploy releases from multiple DSF to one namespace (not recommended!), that namespace's protection config does not automatically cascade between DSFs. You will have to enable the protection in each of the DSFs. +- If you deploy releases from multiple DSF to one namespace (not recommended!), that namespace's protection config does not automatically cascade between DSFs. You will have to enable the protection in each of the DSFs. Also please refer to the [best practice](best_practice.md) document. diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 4d32fd10..dba344d3 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta1 +version: v3.0.0-beta2 --- # Helmsman desired state specification @@ -84,7 +84,7 @@ Synopsis: defines the context in which a DSF is used. This context is used as th ```yaml context: prod-apps ... -``` +``` ## Settings @@ -104,7 +104,7 @@ The following options can be skipped if your kubectl context is already created - **clusterURI** : the URI for your cluster API or the name of an environment variable (starting with `$`) containing the URI. - **bearerToken**: whether you want helmsman to connect to the cluster using a bearer token. Default is `false` - **bearerTokenPath**: optional. If bearer token is used, you can specify a custom location for the token file. -- **storageBackend** : by default Helm v3 stores release information in secrets, using secrets for storage is recommended for security. +- **storageBackend** : by default Helm v3 stores release information in secrets, using secrets for storage is recommended for security. - **slackWebhook** : a [Slack](http://slack.com) Webhook URL to receive Helmsman notifications. This can be passed directly or in an environment variable. - **reverseDelete** : if set to `true` it will reverse the priority order whilst deleting. - **eyamlEnabled** : if set to `true' it will use [hiera-eyaml](https://github.com/voxpupuli/hiera-eyaml) to decrypt secret files instead of using default helm-secrets based on sops @@ -233,7 +233,7 @@ Authenticating to private cloud helm repos: - set `GOOGLE_APPLICATION_CREDENTIALS` environment variable to contain the absolute path to your Google cloud credentials.json file. - Or, set `GCLOUD_CREDENTIALS` environment variable to contain the content of the credentials.json file. -> You can also provide basic auth to access private repos that support basic auth. See the example below. +> You can also provide basic auth to access private repos that support basic auth. See the example below. Options: - you can define any key/value pair where the key is the repo name and value is a valid URI for the repo. Basic auth info can be added in the repo URL as in the example below. diff --git a/docs/how_to/README.md b/docs/how_to/README.md index 2980fb13..f6409c02 100644 --- a/docs/how_to/README.md +++ b/docs/how_to/README.md @@ -1,7 +1,7 @@ # How To Guides -This page contains a list of guides on how to use Helmsman. +This page contains a list of guides on how to use Helmsman. It is recommended that you also check the [DSF spec](../desired_state_specification.md), [cmd reference](../cmd_reference.md), and the [best practice guide](../best_practice.md). diff --git a/docs/how_to/apps/basic.md b/docs/how_to/apps/basic.md index 91187c72..677b6782 100644 --- a/docs/how_to/apps/basic.md +++ b/docs/how_to/apps/basic.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta1 +version: v3.0.0-beta2 --- # Install releases diff --git a/docs/how_to/apps/destroy.md b/docs/how_to/apps/destroy.md index ef03a818..f6d2d94e 100644 --- a/docs/how_to/apps/destroy.md +++ b/docs/how_to/apps/destroy.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta1 +version: v3.0.0-beta2 --- # Delete all deployed releases diff --git a/docs/how_to/apps/helm_tests.md b/docs/how_to/apps/helm_tests.md index bd9f3429..a15a77fa 100644 --- a/docs/how_to/apps/helm_tests.md +++ b/docs/how_to/apps/helm_tests.md @@ -1,10 +1,10 @@ --- -version: v3.0.0-beta1 +version: v3.0.0-beta2 --- # Test charts -Helm allows running [chart tests](https://github.com/helm/helm/blob/master/docs/chart_tests.md). +Helm allows running [chart tests](https://github.com/helm/helm/blob/master/docs/chart_tests.md). You can specify that you would like a chart to be tested whenever it is installed for the first time using the `test` key as follows: diff --git a/docs/how_to/apps/moving_across_namespaces.md b/docs/how_to/apps/moving_across_namespaces.md index 47d975fd..2cae21e5 100644 --- a/docs/how_to/apps/moving_across_namespaces.md +++ b/docs/how_to/apps/moving_across_namespaces.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta1 +version: v3.0.0-beta2 --- # Move charts across namespaces diff --git a/docs/how_to/apps/order.md b/docs/how_to/apps/order.md index a22473a5..c705bc9b 100644 --- a/docs/how_to/apps/order.md +++ b/docs/how_to/apps/order.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta1 +version: v3.0.0-beta2 --- # Using the priority key for Apps diff --git a/docs/how_to/apps/override_namespaces.md b/docs/how_to/apps/override_namespaces.md index 3fbf3d74..8ee6a81e 100644 --- a/docs/how_to/apps/override_namespaces.md +++ b/docs/how_to/apps/override_namespaces.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta1 +version: v3.0.0-beta2 --- # Override defined namespaces from command line diff --git a/docs/how_to/apps/secrets.md b/docs/how_to/apps/secrets.md index b46de4b9..d410f577 100644 --- a/docs/how_to/apps/secrets.md +++ b/docs/how_to/apps/secrets.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta1 +version: v3.0.0-beta2 --- # Passing secrets from env variables: diff --git a/docs/how_to/deployments/ci.md b/docs/how_to/deployments/ci.md index ec7e4870..249f0389 100644 --- a/docs/how_to/deployments/ci.md +++ b/docs/how_to/deployments/ci.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta1 +version: v3.0.0-beta2 --- # Run Helmsman in CI @@ -13,7 +13,7 @@ jobs: deploy-apps: docker: - - image: praqma/helmsman:v3.0.0-beta1 + - image: praqma/helmsman:v3.0.0-beta2 steps: - checkout - run: diff --git a/docs/how_to/deployments/inside_k8s.md b/docs/how_to/deployments/inside_k8s.md index d72f47d6..e46cc48c 100644 --- a/docs/how_to/deployments/inside_k8s.md +++ b/docs/how_to/deployments/inside_k8s.md @@ -9,7 +9,7 @@ Helmsman can be deployed inside your k8s cluster and can talk to the k8s API usi See [connecting to your cluster with bearer token](../settings/creating_kube_context_with_token.md) for more details. -Your desired state will look like: +Your desired state will look like: ```toml [settings] @@ -22,7 +22,7 @@ Your desired state will look like: settings: kubeContext: "test" # the name of the context to be created bearerToken: true - clusterURI: "https://kubernetes.default" + clusterURI: "https://kubernetes.default" ``` To deploy Helmsman into a k8s cluster, few steps are needed: @@ -32,10 +32,10 @@ To deploy Helmsman into a k8s cluster, few steps are needed: 1. Create a k8s service account ```shell -$ kubectl create sa helmsman +$ kubectl create sa helmsman ``` -2. Create a clusterrolebinding +2. Create a clusterrolebinding ```shell $ kubectl create clusterrolebinding helmsman-cluster-admin --clusterrole=cluster-admin --serviceaccount=default:helmsman diff --git a/docs/how_to/helm_repos/basic_auth.md b/docs/how_to/helm_repos/basic_auth.md index 284729e3..0ea2b544 100644 --- a/docs/how_to/helm_repos/basic_auth.md +++ b/docs/how_to/helm_repos/basic_auth.md @@ -4,11 +4,11 @@ version: v1.8.0 # Using private helm repos with basic auth -Helmsman allows you to use any private helm repo hosting which supports basic auth (e.g. Artifactory). +Helmsman allows you to use any private helm repo hosting which supports basic auth (e.g. Artifactory). For such repos, you need to add the basic auth information in the repo URL as in the example below: -> Be aware that some special characters in the username or password can make the URL invalid. +> Be aware that some special characters in the username or password can make the URL invalid. ```toml diff --git a/docs/how_to/helm_repos/default.md b/docs/how_to/helm_repos/default.md index b6af780e..04cc4571 100644 --- a/docs/how_to/helm_repos/default.md +++ b/docs/how_to/helm_repos/default.md @@ -1,10 +1,10 @@ --- -version: v3.0.0-beta1 +version: v3.0.0-beta2 --- # Default helm repos -Helm v3 no longer adds the `stable` and `incubator` repos by default. However, Helmsman v3.0.0-beta1 still adds these two repos by default. These two DO NOT need to be defined explicitly in your desired state file (DSF). However, if you would like to configure some repo with the name stable for example, you can override the default repo. +Helm v3 no longer adds the `stable` and `incubator` repos by default. However, Helmsman v3.0.0-beta2 still adds these two repos by default. These two DO NOT need to be defined explicitly in your desired state file (DSF). However, if you would like to configure some repo with the name stable for example, you can override the default repo. > You can disable the automatic addition of these two repos, use the `--no-default-repos` flag. @@ -12,7 +12,7 @@ This example would have `stable` and `incubator` added by default and another `c ```toml - + [helmRepos] custom = "https://mycustomrepo.org" diff --git a/docs/how_to/helm_repos/gcs.md b/docs/how_to/helm_repos/gcs.md index 0f6e6c88..f4a47826 100644 --- a/docs/how_to/helm_repos/gcs.md +++ b/docs/how_to/helm_repos/gcs.md @@ -15,7 +15,7 @@ Helmsman uses the [helm GCS](https://github.com/nouney/helm-gcs) plugin to work ```toml - + [helmRepos] gcsRepo = "gs://myrepobucket/charts" diff --git a/docs/how_to/misc/auth_to_storage_providers.md b/docs/how_to/misc/auth_to_storage_providers.md index 171c7d00..3d980771 100644 --- a/docs/how_to/misc/auth_to_storage_providers.md +++ b/docs/how_to/misc/auth_to_storage_providers.md @@ -26,4 +26,4 @@ You need to provide ONE of the following env variables: You need to provide ALL of the following env variables: - `AZURE_STORAGE_ACCOUNT` -- `AZURE_STORAGE_ACCESS_KEY` \ No newline at end of file +- `AZURE_STORAGE_ACCESS_KEY` \ No newline at end of file diff --git a/docs/how_to/misc/limit-deployment-to-specific-apps.md b/docs/how_to/misc/limit-deployment-to-specific-apps.md index 23da9e60..79939d48 100644 --- a/docs/how_to/misc/limit-deployment-to-specific-apps.md +++ b/docs/how_to/misc/limit-deployment-to-specific-apps.md @@ -5,7 +5,7 @@ version: v1.9.0 # Limit execution to explicitly defined apps Starting from v1.9.0, Helmsman allows you to pass the `--target` flag multiple times to specify multiple apps -that limits apps considered by Helmsman during this specific execution. +that limits apps considered by Helmsman during this specific execution. Thanks to this one can deploy specific applications among all defined for an environment. ## Example diff --git a/docs/how_to/misc/limit-deployment-to-specific-group-of-apps.md b/docs/how_to/misc/limit-deployment-to-specific-group-of-apps.md index 880265d5..62649542 100644 --- a/docs/how_to/misc/limit-deployment-to-specific-group-of-apps.md +++ b/docs/how_to/misc/limit-deployment-to-specific-group-of-apps.md @@ -5,7 +5,7 @@ version: v1.13.0 # Limit execution to explicitly defined group of apps Starting from v1.13.0, Helmsman allows you to pass the `--group` flag to specify group of apps -the execution of Helmsman deployment will be limited to. +the execution of Helmsman deployment will be limited to. Thanks to this one can deploy specific applications among all defined for an environment. ## Example @@ -40,7 +40,7 @@ With `--group` flag in command like $ helmsman -f example.yaml --group critical ... ``` -one can execute Helmsman's environment defined with example.yaml limited to only one `jenkins` app, since its group is `critical`. +one can execute Helmsman's environment defined with example.yaml limited to only one `jenkins` app, since its group is `critical`. Others are ignored until the flag is defined. Multiple applications can be set with `--group`, like diff --git a/docs/how_to/misc/merge_desired_state_files.md b/docs/how_to/misc/merge_desired_state_files.md index c096fdac..971b1b99 100644 --- a/docs/how_to/misc/merge_desired_state_files.md +++ b/docs/how_to/misc/merge_desired_state_files.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta1 +version: v3.0.0-beta2 --- # Supply multiple desired state files @@ -45,11 +45,11 @@ $ helmsman -f common.toml -f nonprod.toml ... ## Distinguishing releases deployed from different Desired State Files -When using multiple DSFs -and since Helmsman doesn't maintain any external state-, it has been possible for operations from one DSF to cause problems to releases deployed by other DSFs. A typical example is that releases deployed by other DSFs are considered `untracked` and get scheduled for deleting. Workarounds existed (e.g. using the `--keep-untracked-releases`, `--target` and `--group` flags). +When using multiple DSFs -and since Helmsman doesn't maintain any external state-, it has been possible for operations from one DSF to cause problems to releases deployed by other DSFs. A typical example is that releases deployed by other DSFs are considered `untracked` and get scheduled for deleting. Workarounds existed (e.g. using the `--keep-untracked-releases`, `--target` and `--group` flags). -Starting from Helmsman v3.0.0-beta1, `context` is introduced to define the context in which a DSF is used. This context is used as the ID of that specific DSF and must be unique across the used DSFs. The context is then used to label the different releases to link them to the DSF they were first deployed from. These labels are then checked by Helmsman on each run to make sure operations are limited to releases from a specific context. +Starting from Helmsman v3.0.0-beta2, `context` is introduced to define the context in which a DSF is used. This context is used as the ID of that specific DSF and must be unique across the used DSFs. The context is then used to label the different releases to link them to the DSF they were first deployed from. These labels are then checked by Helmsman on each run to make sure operations are limited to releases from a specific context. -Here is how it is used: +Here is how it is used: * `infra.yaml`: ```yaml @@ -58,7 +58,7 @@ settings: kubeContext: "cluster" storageBackend: "secret" -namespaces: +namespaces: infra: protected: true @@ -82,7 +82,7 @@ settings: kubeContext: "cluster" storageBackend: "secret" -namespaces: +namespaces: prod: protected: true diff --git a/docs/how_to/namespaces/labels_and_annotations.md b/docs/how_to/namespaces/labels_and_annotations.md index 3ffc092c..1656bf43 100644 --- a/docs/how_to/namespaces/labels_and_annotations.md +++ b/docs/how_to/namespaces/labels_and_annotations.md @@ -16,7 +16,7 @@ You can define namespaces to be used in your cluster. If they don't exist, Helms [namespaces.production] [namespaces.production.annotations] "iam.amazonaws.com/role" = "dynamodb-reader" - + #... ``` @@ -30,7 +30,7 @@ namespaces: production: annotations: iam.amazonaws.com/role: "dynamodb-reader" - + ``` The above examples create two namespaces; staging and production. The staging namespace has one label `env`= `staging` while the production namespace has one annotation `iam.amazonaws.com/role`=`dynamodb-reader`. diff --git a/docs/how_to/settings/creating_kube_context_with_certs.md b/docs/how_to/settings/creating_kube_context_with_certs.md index 14fdcaf4..b7104daa 100644 --- a/docs/how_to/settings/creating_kube_context_with_certs.md +++ b/docs/how_to/settings/creating_kube_context_with_certs.md @@ -22,7 +22,7 @@ Creating the context with certs, requires both the `settings` and `certificates` caClient = "gs://mybucket/client.crt" # GCS bucket path caCrt = "s3://mybucket/ca.crt" # S3 bucket path # caCrt = "az://myblobcontainer/ca.crt" # Azure blob object - caKey = "../ca.key" # valid local file relative path to the DSF file + caKey = "../ca.key" # valid local file relative path to the DSF file ``` ```yaml @@ -33,10 +33,10 @@ settings: clusterURI: "${CLUSTER_URI}" # the name of an environment variable containing the cluster API endpoint #clusterURI: "https://192.168.99.100:8443" # equivalent to the above -certificates: +certificates: caClient: "gs://mybucket/client.crt" # GCS bucket path caCrt: "s3://mybucket/ca.crt" # S3 bucket path #caCrt: "az://myblobcontainer/ca.crt" # Azure blob object caKey: "../ca.key" # valid local file relative path to the DSF file - + ``` diff --git a/docs/how_to/settings/creating_kube_context_with_token.md b/docs/how_to/settings/creating_kube_context_with_token.md index 5ea965c3..bcabb4bb 100644 --- a/docs/how_to/settings/creating_kube_context_with_token.md +++ b/docs/how_to/settings/creating_kube_context_with_token.md @@ -6,7 +6,7 @@ version: v1.8.0 Helmsman can create the kube context for you (i.e. establish connection to your cluster). This guide describe how its done with bearer tokens. If you want to use certificates, check [this guide](creating_kube_context_with_certs.md). -All you need to do is set `bearerToken` to true and set the `clusterURI` to point to your cluster API endpoint in the `settings` stanza. +All you need to do is set `bearerToken` to true and set the `clusterURI` to point to your cluster API endpoint in the `settings` stanza. > Note: Helmsman and therefore helm will only be able to do what the kubernetes service account (from which the token is taken) allows. @@ -24,6 +24,6 @@ By default, Helmsman will look for a token in `/var/run/secrets/kubernetes.io/se settings: kubeContext: "test" # the name of the context to be created bearerToken: true - clusterURI: "https://kubernetes.default" + clusterURI: "https://kubernetes.default" # bearerTokenPath: "/path/to/custom/bearer/token/file" ``` diff --git a/docs/how_to/settings/use-hiera-eyaml-as-secrets-encryption.md b/docs/how_to/settings/use-hiera-eyaml-as-secrets-encryption.md index eb2b0c81..15de8a8d 100644 --- a/docs/how_to/settings/use-hiera-eyaml-as-secrets-encryption.md +++ b/docs/how_to/settings/use-hiera-eyaml-as-secrets-encryption.md @@ -4,7 +4,7 @@ version: v1.13.0 # Using hiera-eyaml as backend for secrets' encryption -Helmsman uses helm-secrets as a default solution for secrets' encryption. +Helmsman uses helm-secrets as a default solution for secrets' encryption. And while it is a good off-the-shelve solution it may quickly start causing problems when few developers start working on the secrets files simultaneously. SOPS-based secrets can not be easily merged or rebased in case of conflicts etc. That is why another solution for secrets organised in YAMLs was proposed in [hiera-eyaml](https://github.com/voxpupuli/hiera-eyaml). @@ -21,7 +21,7 @@ settings: Helmsman will use hiera-eyaml gem to decrypt secrets files defined for applications. They public and private keys should be placed in `keys` directory with names of `public_key.pkcs7.pem` and `private_key.pkcs7.pem`. -The keys' path can be overwritten with +The keys' path can be overwritten with ```yaml settings: diff --git a/docs/why_helmsman.md b/docs/why_helmsman.md index 4f8a870f..ae39ac2b 100644 --- a/docs/why_helmsman.md +++ b/docs/why_helmsman.md @@ -9,7 +9,7 @@ This document describes the reasoning and need behind the inception of Helmsman. ## Before Helm Helmsman was created with continuous deployment in mind. -When we started using Kubernetes (k8s), we deployed applications on our cluster directly from k8s manifest files. Initially, we had a custom shell script added to our CI system to deploy the k8s resources on the cluster. +When we started using Kubernetes (k8s), we deployed applications on our cluster directly from k8s manifest files. Initially, we had a custom shell script added to our CI system to deploy the k8s resources on the cluster. ![CI-pipeline-before-helm](images/CI-pipeline-before-helm.jpg) @@ -17,8 +17,8 @@ That script could only create the k8s resources from the manifest files. Soon we ## Helm to the rescue? -While looking for solutions for managing the growing number of k8s manifest files from a CI pipeline, we came to know about Helm and quickly realized its potential. -By creating Helm charts, we packaged related k8s manifests together into a single entity: "a chart". +While looking for solutions for managing the growing number of k8s manifest files from a CI pipeline, we came to know about Helm and quickly realized its potential. +By creating Helm charts, we packaged related k8s manifests together into a single entity: "a chart". ![CI-pipeline-after-helm](images/CI-pipeline-after-helm.jpg) @@ -39,7 +39,7 @@ In English, a [Helmsman](https://www.merriam-webster.com/dictionary/helmsman) is > Although knowledge about Helm and K8S is highly beneficial, such knowledge is NOT required to use Helmsman. -As the diagram below shows, we recommend having a Helmsman _desired state file_ for each k8s cluster you are managing. +As the diagram below shows, we recommend having a Helmsman _desired state file_ for each k8s cluster you are managing. ![CI-pipeline-helmsman](images/CI-pipeline-helmsman.jpg) diff --git a/examples/example.toml b/examples/example.toml index 0edc5792..3a94c332 100644 --- a/examples/example.toml +++ b/examples/example.toml @@ -1,6 +1,6 @@ -# version: v3.0.0-beta1 +# version: v3.0.0-beta2 -# context defines the context of this Desired State File. +# context defines the context of this Desired State File. # It is used to allow Helmsman identify which releases are managed by which DSF. # Therefore, it is important that each DSF uses a unique context. context= "test-infra" # defaults to "default" if not provided diff --git a/examples/example.yaml b/examples/example.yaml index 657b65b6..fed8fc7c 100644 --- a/examples/example.yaml +++ b/examples/example.yaml @@ -1,4 +1,4 @@ -# version: v3.0.0-beta1 +# version: v3.0.0-beta2 # metadata -- add as many key/value pairs as you want metadata: org: "example.com/$ORG_PATH/" @@ -13,7 +13,7 @@ metadata: #caCrt: "s3://mybucket/ca.crt" # S3 bucket path #caKey: "../ca.key" # valid local file relative path -# context defines the context of this Desired State File. +# context defines the context of this Desired State File. # It is used to allow Helmsman identify which releases are managed by which DSF. # Therefore, it is important that each DSF uses a unique context. context: test-infra # defaults to "default" if not provided @@ -24,7 +24,7 @@ settings: #password: "$K8S_PASSWORD" # the name of an environment variable containing the k8s password #clusterURI: "$SET_URI" # the name of an environment variable containing the cluster API #clusterURI: "https://192.168.99.100:8443" # equivalent to the above - storageBackend: "secret" + storageBackend: "secret" #slackWebhook: "$slack" # or your slack webhook url #reverseDelete: false # reverse the priorities on delete #### to use bearer token: diff --git a/examples/minimal-example.toml b/examples/minimal-example.toml index 79b3575d..1f55dcc0 100644 --- a/examples/minimal-example.toml +++ b/examples/minimal-example.toml @@ -1,6 +1,6 @@ -## This is a minimal example. +## This is a minimal example. ## It will use your current kube context and will deploy Tiller without RBAC service account. -## For the full config spec and options, check https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md +## For the full config spec and options, check https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md [namespaces] [namespaces.staging] @@ -8,13 +8,13 @@ [apps] [apps.jenkins] - namespace = "staging" - enabled = true - chart = "stable/jenkins" - version = "0.14.3" - + namespace = "staging" + enabled = true + chart = "stable/jenkins" + version = "0.14.3" + [apps.artifactory] - namespace = "staging" - enabled = true - chart = "stable/artifactory" - version = "7.0.6" \ No newline at end of file + namespace = "staging" + enabled = true + chart = "stable/artifactory" + version = "7.0.6" diff --git a/examples/minimal-example.yaml b/examples/minimal-example.yaml index 8d28308f..29208b88 100644 --- a/examples/minimal-example.yaml +++ b/examples/minimal-example.yaml @@ -1,6 +1,6 @@ -## This is a minimal example. +## This is a minimal example. ## It will use your current kube context and will deploy Tiller without RBAC service account. -## For the full config spec and options, check https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md +## For the full config spec and options, check https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md namespaces: staging: @@ -11,9 +11,9 @@ apps: enabled: true chart: stable/jenkins version: 0.14.3 - + artifactory: namespace: staging enabled: true chart: stable/artifactory - version: 7.0.6 \ No newline at end of file + version: 7.0.6 diff --git a/go.mod b/go.mod index c0a51ca6..daedaed8 100644 --- a/go.mod +++ b/go.mod @@ -9,25 +9,21 @@ require ( github.com/BurntSushi/toml v0.3.1 github.com/apsdehal/go-logger v0.0.0-20190515211354-1abdf898e024 github.com/aws/aws-sdk-go v1.26.2 - github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 - github.com/golang/protobuf v1.3.2 + github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 // indirect github.com/hashicorp/go-version v1.2.0 github.com/imdario/mergo v0.3.8 - github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af github.com/joho/godotenv v1.3.0 + github.com/kr/pretty v0.1.0 // indirect github.com/logrusorgru/aurora v0.0.0-20191116043053-66b7ad493a23 - go.opencensus.io v0.22.2 - golang.org/x/exp v0.0.0-20191129062945-2f5052295587 - golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f + github.com/pkg/errors v0.8.1 // indirect + go.opencensus.io v0.22.2 // indirect golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 - golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 - golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 - golang.org/x/text v0.3.2 - golang.org/x/tools v0.0.0-20191213221258-04c2e8eff935 - google.golang.org/api v0.14.0 - google.golang.org/appengine v1.6.5 - google.golang.org/genproto v0.0.0-20191206224255-0243a4be9c8f - google.golang.org/grpc v1.25.1 + golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 // indirect + golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 // indirect + google.golang.org/api v0.14.0 // indirect + google.golang.org/appengine v1.6.5 // indirect + google.golang.org/genproto v0.0.0-20191206224255-0243a4be9c8f // indirect + google.golang.org/grpc v1.25.1 // indirect + gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect gopkg.in/yaml.v2 v2.2.7 - honnef.co/go/tools v0.0.1-2019.2.3 ) diff --git a/go.sum b/go.sum index e90cb6ba..afb32cee 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,7 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.28.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-pipeline-go v0.1.8/go.mod h1:XA1kFWRVhSK+KNFiOhfv83Fv8L9achrP7OxIzeTn1Yg= github.com/Azure/azure-pipeline-go v0.1.9 h1:u7JFb9fFTE6Y/j8ae2VK33ePrRqJqoCM/IWkQdAZ+rg= github.com/Azure/azure-pipeline-go v0.1.9/go.mod h1:XA1kFWRVhSK+KNFiOhfv83Fv8L9achrP7OxIzeTn1Yg= @@ -11,17 +9,17 @@ github.com/Azure/azure-storage-blob-go v0.0.0-20181022225951-5152f14ace1c h1:Y5u github.com/Azure/azure-storage-blob-go v0.0.0-20181022225951-5152f14ace1c/go.mod h1:oGfmITT1V6x//CswqY2gtAHND+xIP64/qL7a5QJix0Y= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/apsdehal/go-logger v0.0.0-20190515211354-1abdf898e024 h1:dfZ6RF0UxHqt7xPz0r7h00apsaa6rIrFhT6Xly55Exk= github.com/apsdehal/go-logger v0.0.0-20190515211354-1abdf898e024/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= github.com/aws/aws-sdk-go v1.26.2 h1:MzYLmCeny4bMQcAbYcucIduVZKp0sEf1eRLvHpKI5Is= github.com/aws/aws-sdk-go v1.26.2/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 h1:uHTyIjqVhYRhLbJ8nIiOJHkEZZ+5YoOsAbD3sk82NiE= @@ -34,10 +32,11 @@ github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= @@ -52,44 +51,36 @@ github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/logrusorgru/aurora v0.0.0-20191116043053-66b7ad493a23 h1:Wp7NjqGKGN9te9N/rvXYRhlVcrulGdxnz8zadXWs7fc= github.com/logrusorgru/aurora v0.0.0-20191116043053-66b7ad493a23/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -107,8 +98,6 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 h1:gSbV7h1NRL2G1xTg/owz62CST1oJBmxy4QpMMregXVQ= @@ -125,18 +114,13 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191213221258-04c2e8eff935/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.14.0 h1:uMf5uLi4eQMRrMKhCplNik4U4H8Z6C1br3zOtAa/aDE= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -153,8 +137,8 @@ google.golang.org/grpc v1.25.1 h1:wdKvqQk7IttEw92GoRyKG2IDrUIpgpj6H6m81yfeMW0= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -162,4 +146,3 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= diff --git a/internal/app/cli.go b/internal/app/cli.go index 78b82b9c..6fcebae8 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -23,7 +23,7 @@ const ( ) func printUsage() { - fmt.Printf(banner) + fmt.Print(banner) fmt.Printf("Helmsman version: " + appVersion + "\n") fmt.Printf("Helmsman is a Helm Charts as Code tool which allows you to automate the deployment/management of your Helm charts.") fmt.Printf("") @@ -239,3 +239,11 @@ func Cli() { } } } + +// getDryRunFlags returns dry-run flag +func getDryRunFlags() []string { + if dryRun { + return []string{"--dry-run", "--debug"} + } + return []string{} +} diff --git a/internal/app/command.go b/internal/app/command.go index e15cbe16..61d5acf2 100644 --- a/internal/app/command.go +++ b/internal/app/command.go @@ -64,9 +64,5 @@ func toolExists(tool string) bool { exitCode, _, _ := cmd.exec(debug, false) - if exitCode != 0 { - return false - } - - return true + return exitCode == 0 } diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index 9261e8c2..9127bd3e 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -3,25 +3,43 @@ package app import ( "fmt" "regexp" - "strconv" "strings" "sync" ) var outcome plan -var settings config + +type currentState map[string]helmRelease + +// logDecision adds the decisions made to the plan. +// Depending on the debug flag being set or not, it will either log the the decision to output or not. +func logDecision(decision string, priority int, decisionType decisionType) { + outcome.addDecision(decision, priority, decisionType) +} + +// buildState builds the currentState map containing information about all releases existing in a k8s cluster +func buildState() currentState { + log.Info("Acquiring current Helm state from cluster...") + + cs := make(map[string]helmRelease) + rel := getHelmReleases() + + for _, r := range rel { + r.HelmsmanContext = getReleaseContext(r.Name, r.Namespace) + cs[fmt.Sprintf("%s-%s", r.Name, r.Namespace)] = r + } + return cs +} // makePlan creates a plan of the actions needed to make the desired state come true. -func makePlan(s *state) *plan { - settings = s.Settings +func (cs *currentState) makePlan(s *state) *plan { outcome = createPlan() - buildState() wg := sync.WaitGroup{} for _, r := range s.Apps { - checkChartDepUpdate(r) + r.checkChartDepUpdate() wg.Add(1) - go decide(r, s, &wg) + go cs.decide(r, s, &wg) } wg.Wait() @@ -30,31 +48,31 @@ func makePlan(s *state) *plan { // decide makes a decision about what commands (actions) need to be executed // to make a release section of the desired state come true. -func decide(r *release, s *state, wg *sync.WaitGroup) { +func (cs *currentState) decide(r *release, s *state, wg *sync.WaitGroup) { defer wg.Done() // check for presence in defined targets or groups - if !r.isReleaseConsideredToRun() { + if !r.isConsideredToRun() { logDecision("Release [ "+r.Name+" ] ignored", r.Priority, ignored) return } if destroy { - if ok := isReleaseExisting(r, ""); ok { - deleteRelease(r) + if ok := cs.releaseExists(r, ""); ok { + r.uninstall() } return } if !r.Enabled { - if ok := isReleaseExisting(r, ""); ok { + if ok := cs.releaseExists(r, ""); ok { - if isProtected(r) { + if cs.isProtected(r) { logDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ "protection is removed.", r.Priority, noop) return } - deleteRelease(r) + r.uninstall() return } logDecision("Release [ "+r.Name+" ] disabled", r.Priority, noop) @@ -62,31 +80,31 @@ func decide(r *release, s *state, wg *sync.WaitGroup) { } - if ok := isReleaseExisting(r, "deployed"); ok { - if !isProtected(r) { - inspectUpgradeScenario(r) // upgrade or move + if ok := cs.releaseExists(r, "deployed"); ok { + if !cs.isProtected(r) { + cs.inspectUpgradeScenario(r) // upgrade or move } else { logDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ "you remove its protection.", r.Priority, noop) } - } else if ok := isReleaseExisting(r, "deleted"); ok { - if !isProtected(r) { + } else if ok := cs.releaseExists(r, "deleted"); ok { + if !cs.isProtected(r) { - rollbackRelease(r) // rollback + r.rollback(cs) // rollback } else { logDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ "you remove its protection.", r.Priority, noop) } - } else if ok := isReleaseExisting(r, "failed"); ok { + } else if ok := cs.releaseExists(r, "failed"); ok { - if !isProtected(r) { + if !cs.isProtected(r) { logDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is in FAILED state. Upgrade is scheduled!", r.Priority, change) - upgradeRelease(r) + r.upgrade() } else { logDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ @@ -94,89 +112,105 @@ func decide(r *release, s *state, wg *sync.WaitGroup) { } } else { // If there is no release in the cluster with this name and in this namespace, then install it! - if _, ok := currentState[fmt.Sprintf("%s-%s", r.Name, r.Namespace)]; !ok { - installRelease(r) + if _, ok := (*cs)[fmt.Sprintf("%s-%s", r.Name, r.Namespace)]; !ok { + r.install() } else { // A release with the same name and in the same namespace exists, but it has a different context label (managed by another DSF) log.Fatal("Release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ] already exists but is not managed by the" + " current context: [ " + s.Context + " ]. Applying changes will likely cause conflicts. Change the release name or namespace.") } } - return - -} - -// testRelease creates a Helm command to test a particular release. -func testRelease(r *release) { - cmd := command{ - Cmd: helmBin, - Args: []string{"test", "--namespace", r.Namespace, r.Name}, - Description: "Running tests for release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ]", - } - outcome.addCommand(cmd, r.Priority, r) - logDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is required to be tested during installation", r.Priority, noop) } -// installRelease creates a Helm command to install a particular release in a particular namespace using a particular Tiller. -func installRelease(r *release) { - cmd := command{ - Cmd: helmBin, - Args: concat([]string{"install", r.Name, r.Chart, "--namespace", r.Namespace}, getValuesFiles(r), []string{"--version", r.Version}, getSetValues(r), getSetStringValues(r), getWait(r), getHelmFlags(r)), - Description: "Installing release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ]", +// releaseExists checks if a Helm release is/was deployed in a k8s cluster. +// It searches the Current State for releases. +// The key format for releases uniqueness is: +// If status is provided as an input [deployed, deleted, failed], then the search will verify the release status matches the search status. +func (cs *currentState) releaseExists(r *release, status string) bool { + v, ok := (*cs)[fmt.Sprintf("%s-%s", r.Name, r.Namespace)] + if !ok || v.HelmsmanContext != s.Context { + return false } - outcome.addCommand(cmd, r.Priority, r) - logDecision("Release [ "+r.Name+" ] will be installed in [ "+r.Namespace+" ] namespace", r.Priority, create) - if r.Test { - testRelease(r) + if status != "" { + return v.Status == status } + return true } -// rollbackRelease evaluates if a rollback action needs to be taken for a given release. -// if the release is already deleted but from a different namespace than the one specified in input, -// it purge deletes it and create it in the specified namespace. -func rollbackRelease(r *release) { - rs, ok := currentState[fmt.Sprintf("%s-%s", r.Name, r.Namespace)] - if !ok { - return - } - - if r.Namespace == rs.Namespace { +// getHelmsmanReleases returns a map of all releases that are labeled with "MANAGED-BY=HELMSMAN" +// The releases are categorized by the namespaces in which they are deployed +// The returned map format is: map[:map[:true]] +func (cs *currentState) getHelmsmanReleases() map[string]map[helmRelease]bool { + var lines []string + releases := make(map[string]map[helmRelease]bool) + storageBackend := s.Settings.StorageBackend + for ns := range s.Namespaces { cmd := command{ - Cmd: helmBin, - Args: concat([]string{"rollback", r.Name, getReleaseRevision(rs)}, getWait(r), getTimeout(r), getNoHooks(r), getDryRunFlags()), - Description: "Rolling back release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ]", + Cmd: "kubectl", + Args: []string{"get", storageBackend, "-n", ns, "-l", "MANAGED-BY=HELMSMAN", "-l", "HELMSMAN_CONTEXT=" + s.Context, "-o", "name"}, + Description: "Getting Helmsman-managed releases in namespace [ " + ns + " ]", + } + + exitCode, output, _ := cmd.exec(debug, verbose) + + if exitCode != 0 { + log.Fatal(output) + } + if strings.EqualFold("No resources found.", strings.TrimSpace(output)) { + lines = strings.Split(output, "\n") } - outcome.addCommand(cmd, r.Priority, r) - upgradeRelease(r) // this is to reflect any changes in values file(s) - logDecision("Release [ "+r.Name+" ] was deleted and is desired to be rolled back to "+ - "namespace [ "+r.Namespace+" ]", r.Priority, create) - } else { - reInstallRelease(r, rs) - logDecision("Release [ "+r.Name+" ] is deleted BUT from namespace [ "+rs.Namespace+ - " ]. Will purge delete it from there and install it in namespace [ "+r.Namespace+" ]", r.Priority, create) - logDecision("WARNING: rolling back release [ "+r.Name+" ] from [ "+rs.Namespace+" ] to [ "+r.Namespace+ - " ] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/apps/moving_across_namespaces.md"+ - " for details if this release uses PV and PVC.", r.Priority, create) + for _, r := range lines { + if r == "" { + continue + } + if _, ok := releases[ns]; !ok { + releases[ns] = make(map[helmRelease]bool) + } + releaseName := strings.Split(strings.Split(r, "/")[1], ".")[4] + releases[ns][(*cs)[releaseName+"-"+ns]] = true + } } + return releases } -// deleteRelease deletes a release from a particular Tiller in a k8s cluster -func deleteRelease(r *release) { - priority := r.Priority - if settings.ReverseDelete == true { - priority = priority * -1 +// cleanUntrackedReleases checks for any releases that are managed by Helmsman and are no longer tracked by the desired state +// It compares the currently deployed releases labeled with "MANAGED-BY=HELMSMAN" with Apps defined in the desired state +// For all untracked releases found, a decision is made to uninstall them and is added to the Helmsman plan +// NOTE: Untracked releases don't benefit from either namespace or application protection. +// NOTE: Removing/Commenting out an app from the desired state makes it untracked. +func (cs *currentState) cleanUntrackedReleases() { + toDelete := make(map[string]map[helmRelease]bool) + log.Info("Checking if any Helmsman managed releases are no longer tracked by your desired state ...") + for ns, releases := range cs.getHelmsmanReleases() { + for r := range releases { + tracked := false + for _, app := range s.Apps { + if app.Name == r.Name && app.Namespace == r.Namespace { + tracked = true + } + } + if !tracked { + if _, ok := toDelete[ns]; !ok { + toDelete[ns] = make(map[helmRelease]bool) + } + toDelete[ns][r] = true + } + } } - cmd := command{ - Cmd: helmBin, - Args: concat([]string{"uninstall", "--namespace", r.Namespace, r.Name}, getDryRunFlags()), - Description: "Deleting release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ]", + if len(toDelete) == 0 { + log.Info("No untracked releases found") + } else { + for _, releases := range toDelete { + for r := range releases { + logDecision("Untracked release [ "+r.Name+" ] found and it will be deleted", -800, delete) + r.uninstall() + } + } } - outcome.addCommand(cmd, priority, r) - logDecision(fmt.Sprintf("release [ %s ] is desired to be DELETED.", r.Name), r.Priority, delete) } // inspectUpgradeScenario evaluates if a release should be upgraded. @@ -186,43 +220,43 @@ func deleteRelease(r *release) { // it will be purge deleted and installed in the same namespace using the new chart. // - If the release is NOT in the same namespace specified in the input, // it will be purge deleted and installed in the new namespace. -func inspectUpgradeScenario(r *release) { +func (cs *currentState) inspectUpgradeScenario(r *release) { - rs, ok := currentState[fmt.Sprintf("%s-%s", r.Name, r.Namespace)] + rs, ok := (*cs)[fmt.Sprintf("%s-%s", r.Name, r.Namespace)] if !ok { return } if r.Namespace == rs.Namespace { - version, msg := getChartVersion(r) + version, msg := r.getChartVersion() if msg != "" { log.Fatal(msg) return } r.Version = version - if extractChartName(r.Chart) == getReleaseChartName(rs) && r.Version != getReleaseChartVersion(rs) { + if extractChartName(r.Chart) == rs.getChartName() && r.Version != rs.getChartVersion() { // upgrade - diffRelease(r) - upgradeRelease(r) + r.diff() + r.upgrade() logDecision("Release [ "+r.Name+" ] will be updated", r.Priority, change) - } else if extractChartName(r.Chart) != getReleaseChartName(rs) { - reInstallRelease(r, rs) + } else if extractChartName(r.Chart) != rs.getChartName() { + r.reInstall(rs) logDecision("Release [ "+r.Name+" ] is desired to use a new chart [ "+r.Chart+ " ]. Delete of the current release will be planned and new chart will be installed in namespace [ "+ r.Namespace+" ]", r.Priority, change) } else { - if diff := diffRelease(r); diff != "" { - upgradeRelease(r) + if diff := r.diff(); diff != "" { + r.upgrade() logDecision("Release [ "+r.Name+" ] will be updated", r.Priority, change) } else { logDecision("Release [ "+r.Name+" ] installed and up-to-date", r.Priority, noop) } } } else { - reInstallRelease(r, rs) + r.reInstall(rs) logDecision("Release [ "+r.Name+" ] is desired to be enabled in a new namespace [ "+r.Namespace+ " ]. Uninstall of the current release from namespace [ "+rs.Namespace+" ] will be performed "+ "and then installation in namespace [ "+r.Namespace+" ] will take place", r.Priority, change) @@ -232,203 +266,14 @@ func inspectUpgradeScenario(r *release) { } } -// diffRelease diffs an existing release with the specified values.yaml -func diffRelease(r *release) string { - exitCode := 0 - msg := "" - colorFlag := "" - diffContextFlag := []string{} - suppressDiffSecretsFlag := "" - if noColors { - colorFlag = "--no-color" - } - if diffContext != -1 { - diffContextFlag = []string{"--context", strconv.Itoa(diffContext)} - } - if suppressDiffSecrets { - suppressDiffSecretsFlag = "--suppress-secrets" - } - - cmd := command{ - Cmd: helmBin, - Args: concat([]string{"diff", colorFlag}, diffContextFlag, []string{suppressDiffSecretsFlag, "--namespace", r.Namespace, "upgrade", r.Name, r.Chart}, getValuesFiles(r), []string{"--version", r.Version}, getSetValues(r), getSetStringValues(r)), - Description: "Diffing release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ]", - } - - if exitCode, msg, _ = cmd.exec(debug, verbose); exitCode != 0 { - log.Fatal(fmt.Sprintf("Command returned with exit code: %d. And error message: %s ", exitCode, msg)) - } else { - if (verbose || showDiff) && msg != "" { - fmt.Println(msg) - } - } - - return msg -} - -// upgradeRelease upgrades an existing release with the specified values.yaml -func upgradeRelease(r *release) { - var force string - if forceUpgrades { - force = "--force" - } - cmd := command{ - Cmd: helmBin, - Args: concat([]string{"upgrade", "--namespace", r.Namespace, r.Name, r.Chart}, getValuesFiles(r), []string{"--version", r.Version, force}, getSetValues(r), getSetStringValues(r), getWait(r), getHelmFlags(r)), - Description: "Upgrading release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ]", - } - - outcome.addCommand(cmd, r.Priority, r) -} - -// reInstallRelease purge deletes a release and reinstalls it. -// This is used when moving a release to another namespace or when changing the chart used for it. -func reInstallRelease(r *release, rs releaseState) { - - delCmd := command{ - Cmd: helmBin, - Args: concat([]string{"delete", "--purge", r.Name}, getDryRunFlags()), - Description: "Deleting release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ]", - } - outcome.addCommand(delCmd, r.Priority, r) - - installCmd := command{ - Cmd: helmBin, - Args: concat([]string{"install", r.Chart, "--version", r.Version, "-n", r.Name, "--namespace", r.Namespace}, getValuesFiles(r), getSetValues(r), getSetStringValues(r), getWait(r), getHelmFlags(r)), - Description: "Installing release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ]", - } - outcome.addCommand(installCmd, r.Priority, r) -} - -// logDecision adds the decisions made to the plan. -// Depending on the debug flag being set or not, it will either log the the decision to output or not. -func logDecision(decision string, priority int, decisionType decisionType) { - - outcome.addDecision(decision, priority, decisionType) - -} - -// extractChartName extracts the Helm chart name from full chart name in the desired state. -// example: it extracts "chartY" from "repoX/chartY" and "chartZ" from "c:\charts\chartZ" -func extractChartName(releaseChart string) string { - - m := chartNameExtractor.FindStringSubmatch(releaseChart) - if len(m) == 2 { - return m[1] - } - - return "" -} - -var chartNameExtractor = regexp.MustCompile(`[\\/]([^\\/]+)$`) - -// getNoHooks returns the no-hooks flag for install/upgrade commands -func getNoHooks(r *release) []string { - if r.NoHooks { - return []string{"--no-hooks"} - } - return []string{} -} - -// getTimeout returns the timeout flag for install/upgrade commands -func getTimeout(r *release) []string { - if r.Timeout != 0 { - return []string{"--timeout", strconv.Itoa(r.Timeout) + "s"} - } - return []string{} -} - -// getValuesFiles return partial install/upgrade release command to substitute the -f flag in Helm. -func getValuesFiles(r *release) []string { - var fileList []string - - if r.ValuesFile != "" { - fileList = append(fileList, r.ValuesFile) - } else if len(r.ValuesFiles) > 0 { - fileList = append(fileList, r.ValuesFiles...) - } - - if r.SecretsFile != "" { - if !helmPluginExists("secrets") { - log.Fatal("helm secrets plugin is not installed/configured correctly. Aborting!") - } - if err := decryptSecret(r.SecretsFile); err != nil { - log.Fatal(err.Error()) - } - fileList = append(fileList, r.SecretsFile+".dec") - } else if len(r.SecretsFiles) > 0 { - if !helmPluginExists("secrets") { - log.Fatal("helm secrets plugin is not installed/configured correctly. Aborting!") - } - for i := 0; i < len(r.SecretsFiles); i++ { - if err := decryptSecret(r.SecretsFiles[i]); err != nil { - log.Fatal(err.Error()) - } - // if .dec extension is added before to the secret filename, don't add it again. - // This happens at upgrade time (where diff and upgrade both call this function) - if !isOfType(r.SecretsFiles[i], []string{".dec"}) { - r.SecretsFiles[i] = r.SecretsFiles[i] + ".dec" - } - } - fileList = append(fileList, r.SecretsFiles...) - } - - fileListArgs := []string{} - for _, file := range fileList { - fileListArgs = append(fileListArgs, "-f", file) - } - return fileListArgs -} - -// getSetValues returns --set params to be used with helm install/upgrade commands -func getSetValues(r *release) []string { - result := []string{} - for k, v := range r.Set { - result = append(result, "--set", k+"="+strings.Replace(v, ",", "\\,", -1)+"") - } - return result -} - -// getSetStringValues returns --set-string params to be used with helm install/upgrade commands -func getSetStringValues(r *release) []string { - result := []string{} - for k, v := range r.SetString { - result = append(result, "--set-string", k+"="+strings.Replace(v, ",", "\\,", -1)+"") - } - return result -} - -// getWait returns a partial helm command containing the helm wait flag (--wait) if the wait flag for the release was set to true -// Otherwise, retruns an empty string -func getWait(r *release) []string { - result := []string{} - if r.Wait { - result = append(result, "--wait") - } - return result -} - -// getDesiredNamespace returns the namespace of a release -func getDesiredNamespace(r *release) string { - - return r.Namespace -} - -// getCurrentNamespaceProtection returns the protection state for the namespace where a release is currently installed. -// It returns true if a namespace is defined as protected in the desired state file, false otherwise. -func getCurrentNamespaceProtection(rs releaseState) bool { - - return s.Namespaces[rs.Namespace].Protected -} - // isProtected checks if a release is protected or not. // A protected is release is either: a) deployed in a protected namespace b) flagged as protected in the desired state file // Any release in a protected namespace is protected by default regardless of its flag // returns true if a release is protected, false otherwise -func isProtected(r *release) bool { +func (cs *currentState) isProtected(r *release) bool { // if the release does not exist in the cluster, it is not protected - if ok := isReleaseExisting(r, ""); !ok { + if ok := cs.releaseExists(r, ""); !ok { return false } @@ -440,28 +285,16 @@ func isProtected(r *release) bool { } -// getDryRunFlags returns dry-run flag -func getDryRunFlags() []string { - if dryRun { - return []string{"--dry-run", "--debug"} - } - return []string{} -} +var chartNameExtractor = regexp.MustCompile(`[\\/]([^\\/]+)$`) -// getHelmFlags returns helm flags -func getHelmFlags(r *release) []string { - var flags []string +// extractChartName extracts the Helm chart name from full chart name in the desired state. +// example: it extracts "chartY" from "repoX/chartY" and "chartZ" from "c:\charts\chartZ" +func extractChartName(releaseChart string) string { - for _, flag := range r.HelmFlags { - flags = append(flags, flag) + m := chartNameExtractor.FindStringSubmatch(releaseChart) + if len(m) == 2 { + return m[1] } - return concat(getNoHooks(r), getTimeout(r), getDryRunFlags(), flags) -} -func checkChartDepUpdate(r *release) { - if updateDeps && isLocalChart(r.Chart) { - if ok, err := updateChartDep(r.Chart); !ok { - log.Fatal("helm dependency update failed: " + err) - } - } + return "" } diff --git a/internal/app/decision_maker_test.go b/internal/app/decision_maker_test.go index 17dd3ef8..efdc298b 100644 --- a/internal/app/decision_maker_test.go +++ b/internal/app/decision_maker_test.go @@ -69,7 +69,7 @@ func Test_getValuesFiles(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := getValuesFiles(tt.args.r); !reflect.DeepEqual(got, tt.want) { + if got := tt.args.r.getValuesFiles(); !reflect.DeepEqual(got, tt.want) { t.Errorf("getValuesFiles() = %v, want %v", got, tt.want) } }) @@ -79,7 +79,7 @@ func Test_getValuesFiles(t *testing.T) { func Test_inspectUpgradeScenario(t *testing.T) { type args struct { r *release - s *map[string]releaseState + s *map[string]helmRelease } tests := []struct { name string @@ -96,7 +96,7 @@ func Test_inspectUpgradeScenario(t *testing.T) { Chart: "./../../tests/chart-test", Enabled: true, }, - s: &map[string]releaseState{ + s: &map[string]helmRelease{ "release1-namespace": { Namespace: "namespace", Chart: "chart-1.0.0", @@ -109,10 +109,10 @@ func Test_inspectUpgradeScenario(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { outcome = plan{} - currentState = *tt.args.s + cs := currentState(*tt.args.s) // Act - inspectUpgradeScenario(tt.args.r) + cs.inspectUpgradeScenario(tt.args.r) got := outcome.Decisions[0].Type t.Log(outcome.Decisions[0].Description) @@ -205,6 +205,7 @@ func Test_decide(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { targetMap = make(map[string]bool) + cs := currentState(make(map[string]helmRelease)) for _, target := range tt.targetFlag { targetMap[target] = true @@ -213,7 +214,7 @@ func Test_decide(t *testing.T) { wg := sync.WaitGroup{} wg.Add(1) // Act - decide(tt.args.r, tt.args.s, &wg) + cs.decide(tt.args.r, tt.args.s, &wg) wg.Wait() got := outcome.Decisions[0].Type t.Log(outcome.Decisions[0].Description) @@ -230,7 +231,7 @@ func Test_decide_group(t *testing.T) { type args struct { r *release s *state - currentState *map[string]releaseState + currentState *map[string]helmRelease } tests := []struct { name string @@ -249,7 +250,7 @@ func Test_decide_group(t *testing.T) { Enabled: true, }, s: &state{}, - currentState: &map[string]releaseState{ + currentState: &map[string]helmRelease{ "release1-namespace": { Namespace: "namespace", Chart: "chart-1.0.0", @@ -271,7 +272,7 @@ func Test_decide_group(t *testing.T) { s: &state{ Context: "default", }, - currentState: &map[string]releaseState{ + currentState: &map[string]helmRelease{ "release2-namespace": { Name: "release2", Namespace: "namespace", @@ -288,7 +289,7 @@ func Test_decide_group(t *testing.T) { t.Run(tt.name, func(t *testing.T) { groupMap = make(map[string]bool) targetMap = make(map[string]bool) - currentState = *tt.args.currentState + cs := currentState(*tt.args.currentState) for _, target := range tt.targetFlag { groupMap[target] = true @@ -299,7 +300,7 @@ func Test_decide_group(t *testing.T) { outcome = plan{} wg := sync.WaitGroup{} wg.Add(1) - decide(tt.args.r, tt.args.s, &wg) + cs.decide(tt.args.r, tt.args.s, &wg) wg.Wait() got := outcome.Decisions[0].Type t.Log(outcome.Decisions[0].Description) diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index c3c24741..640a591f 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -1,51 +1,12 @@ package app import ( - "encoding/json" - "errors" - "fmt" "net/url" - "os" - "path/filepath" - "regexp" - "strconv" "strings" - "sync" - "time" "github.com/Praqma/helmsman/internal/gcs" ) -var currentState map[string]releaseState - -// releaseState represents the current state of a release -type releaseState struct { - Name string - Revision int - Updated time.Time - Status string - Chart string - Namespace string - HelmsmanContext string -} - -type releaseInfo struct { - Name string `json:"Name"` - Namespace string `json:"Namespace"` - Revision string `json:"Revision"` - Updated string `json:"Updated"` - Status string `json:"Status"` - Chart string `json:"Chart"` - AppVersion string `json:"AppVersion,omitempty"` -} - -type chartVersion struct { - Name string `json:"name"` - Version string `json:"version"` - AppVersion string `json:"app_version"` - Description string `json:"description"` -} - // getHelmClientVersion returns Helm client Version func getHelmVersion() string { cmd := command{ @@ -61,207 +22,38 @@ func getHelmVersion() string { return result } -// getHelmReleases fetches a list of all releases in a k8s cluster -func getHelmReleases() []releaseInfo { - var allReleases []releaseInfo +// helmPluginExists returns true if the plugin is present in the environment and false otherwise. +// It takes as input the plugin's name to check if it is recognizable or not. e.g. diff +func helmPluginExists(plugin string) bool { cmd := command{ Cmd: helmBin, - Args: []string{"list", "--all", "--max", "0", "--output", "json", "--all-namespaces"}, - Description: "Listing all existing releases...", - } - exitCode, result, _ := cmd.exec(debug, verbose) - if exitCode != 0 { - log.Fatal("Failed to list all releases: " + result) - } - if err := json.Unmarshal([]byte(result), &allReleases); err != nil { - log.Fatal(fmt.Sprintf("failed to unmarshal Helm CLI output: %s", err)) - } - return allReleases -} - -// buildState builds the currentState map containing information about all releases existing in a k8s cluster -func buildState() { - log.Info("Acquiring current Helm state from cluster...") - - currentState = make(map[string]releaseState) - rel := getHelmReleases() - - for i := 0; i < len(rel); i++ { - // we need to split the time into parts and make sure milliseconds len = 6, it happens to skip trailing zeros - updatedFields := strings.Fields(rel[i].Updated) - updatedHour := strings.Split(updatedFields[1], ".") - milliseconds := updatedHour[1] - for i := len(milliseconds); i < 9; i++ { - milliseconds = fmt.Sprintf("%s0", milliseconds) - } - date, err := time.Parse("2006-01-02 15:04:05.000000000 -0700 MST", - fmt.Sprintf("%s %s.%s %s %s", updatedFields[0], updatedHour[0], milliseconds, updatedFields[2], updatedFields[3])) - if err != nil { - log.Fatal("While converting release time: " + err.Error()) - } - revision, _ := strconv.Atoi(rel[i].Revision) - currentState[fmt.Sprintf("%s-%s", rel[i].Name, rel[i].Namespace)] = releaseState{ - Name: rel[i].Name, - Revision: revision, - Updated: date, - Status: rel[i].Status, - Chart: rel[i].Chart, - Namespace: rel[i].Namespace, - HelmsmanContext: getReleaseContext(rel[i].Name, rel[i].Namespace), - } + Args: []string{"plugin", "list"}, + Description: "Validating that [ " + plugin + " ] is installed", } -} -// isReleaseExisting checks if a Helm release is/was deployed in a k8s cluster. -// It searches the Current State for releases. -// The key format for releases uniqueness is: -// If status is provided as an input [deployed, deleted, failed], then the search will verify the release status matches the search status. -func isReleaseExisting(r *release, status string) bool { - v, ok := currentState[fmt.Sprintf("%s-%s", r.Name, r.Namespace)] - if !ok || v.HelmsmanContext != s.Context { - return false - } + exitCode, result, _ := cmd.exec(debug, false) - if status != "" { - if v.Status == status { - return true - } + if exitCode != 0 { return false } - return true -} - -// getReleaseRevision returns the revision number for a release -func getReleaseRevision(rs releaseState) string { - return strconv.Itoa(rs.Revision) -} -// getReleaseChartName extracts and returns the Helm chart name from the chart info in a release state. -// example: chart in release state is "jenkins-0.9.0" and this function will extract "jenkins" from it. -func getReleaseChartName(rs releaseState) string { - - chart := rs.Chart - runes := []rune(chart) - return string(runes[0:strings.LastIndexByte(chart[0:strings.IndexByte(chart, '.')], '-')]) -} - -// getReleaseChartVersion extracts and returns the Helm chart version from the chart info in a release state. -// example: chart in release state is returns "jenkins-0.9.0" and this functions will extract "0.9.0" from it. -// It should also handle semver-valid pre-release/meta information, example: in: jenkins-0.9.0-1, out: 0.9.0-1 -// in the event of an error, an empty string is returned. -func getReleaseChartVersion(rs releaseState) string { - chart := rs.Chart - re := regexp.MustCompile("-(v?[0-9]+\\.[0-9]+\\.[0-9]+.*)") - matches := re.FindStringSubmatch(chart) - if len(matches) > 1 { - return matches[1] - } - return "" -} - -// validateReleaseCharts validates if the charts defined in a release are valid. -// Valid charts are the ones that can be found in the defined repos. -// This function uses Helm search to verify if the chart can be found or not. -func validateReleaseCharts(apps map[string]*release) error { - versionExtractor := regexp.MustCompile(`version:\s?(.*)`) - - wg := sync.WaitGroup{} - c := make(chan string, len(apps)) - for app, r := range apps { - wg.Add(1) - go func(app string, r *release, wg *sync.WaitGroup, c chan string) { - defer wg.Done() - validateCurrentChart := true - if !r.isReleaseConsideredToRun() { - validateCurrentChart = false - } - if validateCurrentChart { - if isLocalChart(r.Chart) { - cmd := command{ - Cmd: helmBin, - Args: []string{"inspect", "chart", r.Chart}, - Description: "Validating [ " + r.Chart + " ] chart's availability", - } - - var output string - var exitCode int - if exitCode, output, _ = cmd.exec(debug, verbose); exitCode != 0 { - maybeRepo := filepath.Base(filepath.Dir(r.Chart)) - c <- "Chart [ " + r.Chart + " ] for app [" + app + "] can't be found. Did you mean to add a repo [ " + maybeRepo + " ]?" - return - } - matches := versionExtractor.FindStringSubmatch(output) - if len(matches) == 2 { - version := matches[1] - if r.Version != version { - c <- "Chart [ " + r.Chart + " ] with version [ " + r.Version + " ] is specified for " + - "app [" + app + "] but the chart found at that path has version [ " + version + " ] which does not match." - return - } - } - - } else { - version := r.Version - if len(version) == 0 { - version = "*" - } - cmd := command{ - Cmd: helmBin, - Args: []string{"search", "repo", r.Chart, "--version", version, "-l"}, - Description: "Validating [ " + r.Chart + " ] chart's version [ " + r.Version + " ] availability", - } - - if exitCode, result, _ := cmd.exec(debug, verbose); exitCode != 0 || strings.Contains(result, "No results found") { - c <- "Chart [ " + r.Chart + " ] with version [ " + r.Version + " ] is specified for " + - "app [" + app + "] but was not found" - return - } - } - } - }(app, r, &wg, c) - } - wg.Wait() - if len(c) > 0 { - err := <-c - if err != "" { - return errors.New(err) - } - } - return nil + return strings.Contains(result, plugin) } -// getChartVersion fetches the lastest chart version matching the semantic versioning constraints. -// If chart is local, returns the given release version -func getChartVersion(r *release) (string, string) { - if isLocalChart(r.Chart) { - return r.Version, "" - } +// updateChartDep updates dependencies for a local chart +func updateChartDep(chartPath string) (bool, string) { cmd := command{ Cmd: helmBin, - Args: []string{"search", "repo", r.Chart, "--version", r.Version, "-o", "json"}, - Description: "Getting latest chart's version " + r.Chart + "-" + r.Version + "", - } - - var ( - exitCode int - result string - ) - - if exitCode, result, _ = cmd.exec(debug, verbose); exitCode != 0 { - return "", "Chart [ " + r.Chart + " ] with version [ " + r.Version + " ] is specified but not found in the helm repositories" + Args: []string{"dependency", "update", chartPath}, + Description: "Updating dependency for local chart [ " + chartPath + " ]", } - chartVersions := make([]chartVersion, 0) - if err := json.Unmarshal([]byte(result), &chartVersions); err != nil { - log.Fatal(fmt.Sprint(err)) - } + exitCode, err, _ := cmd.exec(debug, verbose) - if len(chartVersions) < 1 { - return "", "Chart [ " + r.Chart + " ] with version [ " + r.Version + " ] is specified but not found in the helm repositories" - } else if len(chartVersions) > 1 { - return "", "Multiple versions of chart [ " + r.Chart + " ] with version [ " + r.Version + " ] found in the helm repositories" + if exitCode != 0 { + return false, err } - return chartVersions[0].Version, "" + return true, "" } // addHelmRepos adds repositories to Helm if they don't exist already. @@ -316,133 +108,3 @@ func addHelmRepos(repos map[string]string) (bool, string) { return true, "" } - -// cleanUntrackedReleases checks for any releases that are managed by Helmsman and are no longer tracked by the desired state -// It compares the currently deployed releases labeled with "MANAGED-BY=HELMSMAN" with Apps defined in the desired state -// For all untracked releases found, a decision is made to uninstall them and is added to the Helmsman plan -// NOTE: Untracked releases don't benefit from either namespace or application protection. -// NOTE: Removing/Commenting out an app from the desired state makes it untracked. -func cleanUntrackedReleases() { - toDelete := make(map[string]map[releaseState]bool) - log.Info("Checking if any Helmsman managed releases are no longer tracked by your desired state ...") - for ns, releases := range getHelmsmanReleases() { - for r := range releases { - tracked := false - for _, app := range s.Apps { - if app.Name == r.Name && app.Namespace == r.Namespace { - tracked = true - } - } - if !tracked { - if _, ok := toDelete[ns]; !ok { - toDelete[ns] = make(map[releaseState]bool) - } - toDelete[ns][r] = true - } - } - } - - if len(toDelete) == 0 { - log.Info("No untracked releases found") - } else { - for _, releases := range toDelete { - for r := range releases { - logDecision("Untracked release [ "+r.Name+" ] found and it will be deleted", -800, delete) - uninstallUntrackedRelease(r) - } - } - } -} - -// uninstallUntrackedRelease creates the helm command to uninstall an untracked release -func uninstallUntrackedRelease(release releaseState) { - cmd := command{ - Cmd: helmBin, - Args: concat([]string{"uninstall", release.Name, "--namespace", release.Namespace}, getDryRunFlags()), - Description: "Deleting untracked release [ " + release.Name + " ] in namespace [ " + release.Namespace + " ]", - } - - outcome.addCommand(cmd, -800, nil) -} - -// decrypt a helm secret file -func decryptSecret(name string) error { - cmd := helmBin - args := []string{"secrets", "dec", name} - - if settings.EyamlEnabled { - cmd = "eyaml" - args = []string{"decrypt", "-f", name} - if settings.EyamlPrivateKeyPath != "" && settings.EyamlPublicKeyPath != "" { - args = append(args, []string{"--pkcs7-private-key", settings.EyamlPrivateKeyPath, "--pkcs7-public-key", settings.EyamlPublicKeyPath}...) - } - } - - command := command{ - Cmd: cmd, - Args: args, - Description: "Decrypting " + name, - } - - exitCode, output, stderr := command.exec(debug, false) - if !settings.EyamlEnabled { - _, fileNotFound := os.Stat(name + ".dec") - if fileNotFound != nil && !isOfType(name, []string{".dec"}) { - return errors.New(output) - } - } - - if exitCode != 0 { - return errors.New(output) - } else if stderr != "" { - return errors.New(output) - } - - if settings.EyamlEnabled { - var outfile string - if isOfType(name, []string{".dec"}) { - outfile = name - } else { - outfile = name + ".dec" - } - err := writeStringToFile(outfile, output) - if err != nil { - log.Fatal("Can't write [ " + outfile + " ] file") - } - } - return nil -} - -// updateChartDep updates dependencies for a local chart -func updateChartDep(chartPath string) (bool, string) { - cmd := command{ - Cmd: helmBin, - Args: []string{"dependency", "update", chartPath}, - Description: "Updating dependency for local chart [ " + chartPath + " ]", - } - - exitCode, err, _ := cmd.exec(debug, verbose) - - if exitCode != 0 { - return false, err - } - return true, "" -} - -// helmPluginExists returns true if the plugin is present in the environment and false otherwise. -// It takes as input the plugin's name to check if it is recognizable or not. e.g. diff -func helmPluginExists(plugin string) bool { - cmd := command{ - Cmd: helmBin, - Args: []string{"plugin", "list"}, - Description: "Validating that [ " + plugin + " ] is installed", - } - - exitCode, result, _ := cmd.exec(debug, false) - - if exitCode != 0 { - return false - } - - return strings.Contains(result, plugin) -} diff --git a/internal/app/helm_helpers_test.go b/internal/app/helm_helpers_test.go deleted file mode 100644 index 40861917..00000000 --- a/internal/app/helm_helpers_test.go +++ /dev/null @@ -1,475 +0,0 @@ -package app - -import ( - "fmt" - "os" - "testing" - "time" -) - -func setupTestCase(t *testing.T) func(t *testing.T) { - t.Log("setup test case") - os.MkdirAll(os.TempDir()+"/helmsman-tests/myapp", os.ModePerm) - os.MkdirAll(os.TempDir()+"/helmsman-tests/dir-with space/myapp", os.ModePerm) - cmd := command{ - Cmd: helmBin, - Args: []string{"create", os.TempDir() + "/helmsman-tests/dir-with space/myapp"}, - Description: "creating an empty local chart directory", - } - if exitCode, msg, _ := cmd.exec(debug, verbose); exitCode != 0 { - log.Fatal(fmt.Sprintf("Command returned with exit code: %d. And error message: %s ", exitCode, msg)) - } - - return func(t *testing.T) { - t.Log("teardown test case") - //os.RemoveAll("/tmp/helmsman-tests/") - } -} -func Test_validateReleaseCharts(t *testing.T) { - type args struct { - apps map[string]*release - } - tests := []struct { - name string - targetFlag []string - groupFlag []string - args args - want bool - }{ - { - name: "test case 1: valid local path with no chart", - targetFlag: []string{}, - args: args{ - apps: map[string]*release{ - "app": &release{ - Name: "", - Description: "", - Namespace: "", - Enabled: true, - Chart: os.TempDir() + "/helmsman-tests/myapp", - Version: "", - ValuesFile: "", - ValuesFiles: []string{}, - SecretsFile: "", - SecretsFiles: []string{}, - Test: false, - Protected: false, - Wait: false, - Priority: 0, - Set: make(map[string]string), - SetString: make(map[string]string), - HelmFlags: []string{}, - NoHooks: false, - Timeout: 0, - }, - }, - }, - want: false, - }, { - name: "test case 2: invalid local path", - targetFlag: []string{}, - args: args{ - apps: map[string]*release{ - "app": &release{ - Name: "", - Description: "", - Namespace: "", - Enabled: true, - Chart: os.TempDir() + "/does-not-exist/myapp", - Version: "", - ValuesFile: "", - ValuesFiles: []string{}, - SecretsFile: "", - SecretsFiles: []string{}, - Test: false, - Protected: false, - Wait: false, - Priority: 0, - Set: make(map[string]string), - SetString: make(map[string]string), - HelmFlags: []string{}, - NoHooks: false, - Timeout: 0, - }, - }, - }, - want: false, - }, { - name: "test case 3: valid chart local path with whitespace", - targetFlag: []string{}, - args: args{ - apps: map[string]*release{ - "app": &release{ - Name: "", - Description: "", - Namespace: "", - Enabled: true, - Chart: os.TempDir() + "/helmsman-tests/dir-with space/myapp", - Version: "0.1.0", - ValuesFile: "", - ValuesFiles: []string{}, - SecretsFile: "", - SecretsFiles: []string{}, - Test: false, - Protected: false, - Wait: false, - Priority: 0, - Set: make(map[string]string), - SetString: make(map[string]string), - HelmFlags: []string{}, - NoHooks: false, - Timeout: 0, - }, - }, - }, - want: true, - }, { - name: "test case 4: valid chart from repo", - targetFlag: []string{}, - args: args{ - apps: map[string]*release{ - "app": &release{ - Name: "", - Description: "", - Namespace: "", - Enabled: true, - Chart: "stable/prometheus", - Version: "9.5.2", - ValuesFile: "", - ValuesFiles: []string{}, - SecretsFile: "", - SecretsFiles: []string{}, - Test: false, - Protected: false, - Wait: false, - Priority: 0, - Set: make(map[string]string), - SetString: make(map[string]string), - HelmFlags: []string{}, - NoHooks: false, - Timeout: 0, - }, - }, - }, - want: true, - }, { - name: "test case 5: invalid local path for chart ignored with -target flag, while other app was targeted", - targetFlag: []string{"notThisOne"}, - args: args{ - apps: map[string]*release{ - "app": &release{ - Name: "app", - Description: "", - Namespace: "", - Enabled: true, - Chart: os.TempDir() + "/does-not-exist/myapp", - Version: "", - ValuesFile: "", - ValuesFiles: []string{}, - SecretsFile: "", - SecretsFiles: []string{}, - Test: false, - Protected: false, - Wait: false, - Priority: 0, - Set: make(map[string]string), - SetString: make(map[string]string), - HelmFlags: []string{}, - NoHooks: false, - Timeout: 0, - }, - }, - }, - want: true, - }, { - name: "test case 6: invalid local path for chart included with -target flag", - targetFlag: []string{"app"}, - args: args{ - apps: map[string]*release{ - "app": &release{ - Name: "app", - Description: "", - Namespace: "", - Enabled: true, - Chart: os.TempDir() + "/does-not-exist/myapp", - Version: "", - ValuesFile: "", - ValuesFiles: []string{}, - SecretsFile: "", - SecretsFiles: []string{}, - Test: false, - Protected: false, - Wait: false, - Priority: 0, - Set: make(map[string]string), - SetString: make(map[string]string), - HelmFlags: []string{}, - NoHooks: false, - Timeout: 0, - }, - }, - }, - want: false, - }, - } - - teardownTestCase := setupTestCase(t) - defer teardownTestCase(t) - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - targetMap = make(map[string]bool) - groupMap = make(map[string]bool) - for _, target := range tt.targetFlag { - targetMap[target] = true - } - for _, group := range tt.groupFlag { - groupMap[group] = true - } - err := validateReleaseCharts(tt.args.apps) - switch err.(type) { - case nil: - if tt.want != true { - t.Errorf("validateReleaseCharts() = %v, want error", err) - } - case error: - if tt.want != false { - t.Errorf("validateReleaseCharts() = %v, want nil", err) - } - } - }) - } -} - -func Test_getReleaseChartVersion(t *testing.T) { - // version string = the first semver-valid string after the last hypen in the chart string. - - type args struct { - r releaseState - } - tests := []struct { - name string - args args - want string - }{ - { - name: "test case 1: there is a pre-release version", - args: args{ - r: releaseState{ - Revision: 0, - Updated: time.Now(), - Status: "", - Chart: "elasticsearch-1.3.0-1", - Namespace: "", - }, - }, - want: "1.3.0-1", - }, { - name: "test case 2: normal case", - args: args{ - r: releaseState{ - Revision: 0, - Updated: time.Now(), - Status: "", - Chart: "elasticsearch-1.3.0", - Namespace: "", - }, - }, - want: "1.3.0", - }, { - name: "test case 3: there is a hypen in the name", - args: args{ - r: releaseState{ - Revision: 0, - Updated: time.Now(), - Status: "", - Chart: "elastic-search-1.3.0", - Namespace: "", - }, - }, - want: "1.3.0", - }, { - name: "test case 4: there is meta information", - args: args{ - r: releaseState{ - Revision: 0, - Updated: time.Now(), - Status: "", - Chart: "elastic-search-1.3.0+meta.info", - Namespace: "", - }, - }, - want: "1.3.0+meta.info", - }, { - name: "test case 5: an invalid string", - args: args{ - r: releaseState{ - Revision: 0, - Updated: time.Now(), - Status: "", - Chart: "foo", - Namespace: "", - }, - }, - want: "", - }, { - name: "test case 6: version includes v", - args: args{ - r: releaseState{ - Revision: 0, - Updated: time.Now(), - Status: "", - Chart: "cert-manager-v0.5.2", - Namespace: "", - }, - }, - want: "v0.5.2", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Log(tt.want) - if got := getReleaseChartVersion(tt.args.r); got != tt.want { - t.Errorf("getReleaseChartVersion() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_getChartVersion(t *testing.T) { - // version string = the first semver-valid string after the last hypen in the chart string. - type args struct { - r *release - } - tests := []struct { - name string - args args - want string - }{ - { - name: "getChartVersion - local chart should return given release version", - args: args{ - r: &release{ - Name: "release1", - Namespace: "namespace", - Version: "1.0.0", - Chart: "./../../tests/chart-test", - Enabled: true, - }, - }, - want: "1.0.0", - }, - { - name: "getChartVersion - unknown chart should error", - args: args{ - r: &release{ - Name: "release1", - Namespace: "namespace", - Version: "1.0.0", - Chart: "random-chart-name-1f8147", - Enabled: true, - }, - }, - want: "", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Log(tt.want) - got, _ := getChartVersion(tt.args.r) - if got != tt.want { - t.Errorf("getChartVersion() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_eyamlSecrets(t *testing.T) { - type args struct { - r *release - s *config - } - tests := []struct { - name string - args args - want bool - }{ - { - name: "decryptSecrets - valid eyaml-based secrets decryption", - args: args{ - s: &config{ - EyamlEnabled: true, - EyamlPublicKeyPath: "./../../tests/keys/public_key.pkcs7.pem", - EyamlPrivateKeyPath: "./../../tests/keys/private_key.pkcs7.pem", - }, - r: &release{ - Name: "release1", - Namespace: "namespace", - Version: "1.0.0", - Enabled: true, - SecretsFile: "./../../tests/secrets/valid_eyaml_secrets.yaml", - }, - }, - want: true, - }, - { - name: "decryptSecrets - not existing eyaml-based secrets file", - args: args{ - s: &config{ - EyamlEnabled: true, - EyamlPublicKeyPath: "./../../tests/keys/public_key.pkcs7.pem", - EyamlPrivateKeyPath: "./../../tests/keys/private_key.pkcs7.pem", - }, - r: &release{ - Name: "release1", - Namespace: "namespace", - Version: "1.0.0", - Enabled: true, - SecretsFile: "./../../tests/secrets/invalid_eyaml_secrets.yaml", - }, - }, - want: false, - }, - { - name: "decryptSecrets - not existing eyaml key", - args: args{ - s: &config{ - EyamlEnabled: true, - EyamlPublicKeyPath: "./../../tests/keys/public_key.pkcs7.pem2", - EyamlPrivateKeyPath: "./../../tests/keys/private_key.pkcs7.pem", - }, - r: &release{ - Name: "release1", - Namespace: "namespace", - Version: "1.0.0", - Enabled: true, - SecretsFile: "./../../tests/secrets/valid_eyaml_secrets.yaml", - }, - }, - want: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Log(tt.want) - defaultSettings := settings - defer func() { settings = defaultSettings }() - settings.EyamlEnabled = tt.args.s.EyamlEnabled - settings.EyamlPublicKeyPath = tt.args.s.EyamlPublicKeyPath - settings.EyamlPrivateKeyPath = tt.args.s.EyamlPrivateKeyPath - err := decryptSecret(tt.args.r.SecretsFile) - switch err.(type) { - case nil: - if tt.want != true { - t.Errorf("decryptSecret() = %v, want error", err) - } - case error: - if tt.want != false { - t.Errorf("decryptSecret() = %v, want nil", err) - } - } - if _, err := os.Stat(tt.args.r.SecretsFile + ".dec"); err == nil { - defer deleteFile(tt.args.r.SecretsFile + ".dec") - } - }) - } -} diff --git a/internal/app/helm_release.go b/internal/app/helm_release.go new file mode 100644 index 00000000..27b72dfb --- /dev/null +++ b/internal/app/helm_release.go @@ -0,0 +1,84 @@ +package app + +import ( + "encoding/json" + "fmt" + "regexp" + "strconv" + "strings" +) + +// helmRelease represents the current state of a release +type helmRelease struct { + Name string `json:"Name"` + Namespace string `json:"Namespace"` + Revision int `json:"Revision,string"` + Updated HelmTime `json:"Updated"` + Status string `json:"Status"` + Chart string `json:"Chart"` + AppVersion string `json:"AppVersion,omitempty"` + HelmsmanContext string +} + +// getHelmReleases fetches a list of all releases in a k8s cluster +func getHelmReleases() []helmRelease { + var allReleases []helmRelease + cmd := command{ + Cmd: helmBin, + Args: []string{"list", "--all", "--max", "0", "--output", "json", "--all-namespaces"}, + Description: "Listing all existing releases...", + } + exitCode, result, _ := cmd.exec(debug, verbose) + if exitCode != 0 { + log.Fatal("Failed to list all releases: " + result) + } + if err := json.Unmarshal([]byte(result), &allReleases); err != nil { + log.Fatal(fmt.Sprintf("failed to unmarshal Helm CLI output: %s", err)) + } + return allReleases +} + +// uninstall creates the helm command to uninstall an untracked release +func (r *helmRelease) uninstall() { + cmd := command{ + Cmd: helmBin, + Args: concat([]string{"uninstall", r.Name, "--namespace", r.Namespace}, getDryRunFlags()), + Description: "Deleting untracked release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ]", + } + + outcome.addCommand(cmd, -800, nil) +} + +// getRevision returns the revision number for an existing helm release +func (rs *helmRelease) getRevision() string { + return strconv.Itoa(rs.Revision) +} + +// getChartName extracts and returns the Helm chart name from the chart info in a release state. +// example: chart in release state is "jenkins-0.9.0" and this function will extract "jenkins" from it. +func (rs *helmRelease) getChartName() string { + + chart := rs.Chart + runes := []rune(chart) + return string(runes[0:strings.LastIndexByte(chart[0:strings.IndexByte(chart, '.')], '-')]) +} + +// getChartVersion extracts and returns the Helm chart version from the chart info in a release state. +// example: chart in release state is returns "jenkins-0.9.0" and this functions will extract "0.9.0" from it. +// It should also handle semver-valid pre-release/meta information, example: in: jenkins-0.9.0-1, out: 0.9.0-1 +// in the event of an error, an empty string is returned. +func (rs *helmRelease) getChartVersion() string { + chart := rs.Chart + re := regexp.MustCompile(`-(v?[0-9]+\.[0-9]+\.[0-9]+.*)`) + matches := re.FindStringSubmatch(chart) + if len(matches) > 1 { + return matches[1] + } + return "" +} + +// getCurrentNamespaceProtection returns the protection state for the namespace where a release is currently installed. +// It returns true if a namespace is defined as protected in the desired state file, false otherwise. +func (rs *helmRelease) getCurrentNamespaceProtection() bool { + return s.Namespaces[rs.Namespace].Protected +} diff --git a/internal/app/helm_time.go b/internal/app/helm_time.go new file mode 100644 index 00000000..05cf6ca9 --- /dev/null +++ b/internal/app/helm_time.go @@ -0,0 +1,44 @@ +package app + +import ( + "fmt" + "strings" + "time" +) + +const ctLayout = "2006-01-02 15:04:05.000000000 -0700 MST" + +var nilTime = (time.Time{}).UnixNano() + +type HelmTime struct { + time.Time +} + +func (ht *HelmTime) UnmarshalJSON(b []byte) (err error) { + s := strings.Trim(string(b), "\"") + if s == "null" { + ht.Time = time.Time{} + return + } + // we need to split the time into parts and make sure milliseconds len = 6, it happens to skip trailing zeros + updatedFields := strings.Fields(s) + updatedHour := strings.Split(updatedFields[1], ".") + milliseconds := updatedHour[1] + for i := len(milliseconds); i < 9; i++ { + milliseconds = fmt.Sprintf("%s0", milliseconds) + } + s = fmt.Sprintf("%s %s.%s %s %s", updatedFields[0], updatedHour[0], milliseconds, updatedFields[2], updatedFields[3]) + ht.Time, err = time.Parse(ctLayout, s) + return +} + +func (ht *HelmTime) MarshalJSON() ([]byte, error) { + if ht.Time.UnixNano() == nilTime { + return []byte("null"), nil + } + return []byte(fmt.Sprintf("\"%s\"", ht.Time.Format(ctLayout))), nil +} + +func (ht *HelmTime) IsSet() bool { + return ht.UnixNano() != nilTime +} diff --git a/internal/app/kube_helpers.go b/internal/app/kube_helpers.go index e6a9a458..493a085a 100644 --- a/internal/app/kube_helpers.go +++ b/internal/app/kube_helpers.go @@ -29,7 +29,7 @@ func addNamespaces(namespaces map[string]namespace) { func overrideAppsNamespace(newNs string) { log.Info("Overriding apps namespaces with [ " + newNs + " ] ...") for _, r := range s.Apps { - overrideNamespace(r, newNs) + r.overrideNamespace(newNs) } } @@ -338,44 +338,6 @@ func getReleaseContext(releaseName string, namespace string) string { return strings.TrimSpace(out) } -// getHelmsmanReleases returns a map of all releases that are labeled with "MANAGED-BY=HELMSMAN" -// The releases are categorized by the namespaces in which they are deployed -// The returned map format is: map[:map[:true]] -func getHelmsmanReleases() map[string]map[releaseState]bool { - var lines []string - releases := make(map[string]map[releaseState]bool) - storageBackend := s.Settings.StorageBackend - - for ns := range s.Namespaces { - cmd := command{ - Cmd: "kubectl", - Args: []string{"get", storageBackend, "-n", ns, "-l", "MANAGED-BY=HELMSMAN", "-l", "HELMSMAN_CONTEXT=" + s.Context, "-o", "name"}, - Description: "Getting Helmsman-managed releases in namespace [ " + ns + " ]", - } - - exitCode, output, _ := cmd.exec(debug, verbose) - - if exitCode != 0 { - log.Fatal(output) - } - if strings.ToUpper("No resources found.") != strings.ToUpper(strings.TrimSpace(output)) { - lines = strings.Split(output, "\n") - } - - for _, r := range lines { - if r == "" { - continue - } - if _, ok := releases[ns]; !ok { - releases[ns] = make(map[releaseState]bool) - } - releaseName := strings.Split(strings.Split(r, "/")[1], ".")[4] - releases[ns][currentState[releaseName+"-"+ns]] = true - } - } - return releases -} - // getKubectlClientVersion returns kubectl client version func getKubectlClientVersion() string { cmd := command{ diff --git a/internal/app/main.go b/internal/app/main.go index ad6d9b43..891050d5 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -16,6 +16,12 @@ func (i *stringArray) Set(value string) error { return nil } +const appVersion = "v3.0.0-beta2" +const tempFilesDir = ".helmsman-tmp" +const stableHelmRepo = "https://kubernetes-charts.storage.googleapis.com" +const incubatorHelmRepo = "http://storage.googleapis.com/kubernetes-charts-incubator" +const defaultContextName = "default" + var s state var debug bool var files stringArray @@ -31,7 +37,6 @@ var noNs bool var nsOverride string var skipValidation bool var keepUntrackedReleases bool -var appVersion = "v3.0.0-beta1" var helmBin = "helm" var helmVersion string var kubectlVersion string @@ -52,10 +57,7 @@ var updateDeps bool var forceUpgrades bool var noDefaultRepos bool -const tempFilesDir = ".helmsman-tmp" -const stableHelmRepo = "https://kubernetes-charts.storage.googleapis.com" -const incubatorHelmRepo = "http://storage.googleapis.com/kubernetes-charts-incubator" -const defaultContextName = "default" +var settings config func init() { Cli() @@ -67,8 +69,9 @@ func Main() { defer os.RemoveAll(tempFilesDir) defer cleanup() + settings = s.Settings // set the kubecontext to be used Or create it if it does not exist - if !setKubeContext(s.Settings.KubeContext) { + if !setKubeContext(settings.KubeContext) { if r, msg := createContext(); !r { log.Fatal(msg) } @@ -100,9 +103,10 @@ func Main() { log.Info("--destroy is enabled. Your releases will be deleted!") } - p := makePlan(&s) + cs := buildState() + p := cs.makePlan(&s) if !keepUntrackedReleases { - cleanUntrackedReleases() + cs.cleanUntrackedReleases() } p.sortPlan() diff --git a/internal/app/namespace.go b/internal/app/namespace.go index e45d6cfb..636f4477 100644 --- a/internal/app/namespace.go +++ b/internal/app/namespace.go @@ -31,10 +31,7 @@ type namespace struct { // checkNamespaceDefined checks if a given namespace is defined in the namespaces section of the desired state file func checkNamespaceDefined(ns string, s state) bool { _, ok := s.Namespaces[ns] - if !ok { - return false - } - return true + return ok } // print prints the namespace diff --git a/internal/app/release.go b/internal/app/release.go index 9b9031de..4e7b3c68 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -1,9 +1,15 @@ package app import ( + "encoding/json" + "errors" "fmt" "os" + "path/filepath" + "regexp" + "strconv" "strings" + "sync" ) // release type representing Helm releases which are described in the desired state @@ -30,8 +36,15 @@ type release struct { Timeout int `yaml:"timeout"` } +type chartVersion struct { + Name string `json:"name"` + Version string `json:"version"` + AppVersion string `json:"app_version"` + Description string `json:"description"` +} + // isReleaseConsideredToRun checks if a release is being targeted for operations as specified by user cmd flags (--group or --target) -func (r *release) isReleaseConsideredToRun() bool { +func (r *release) isConsideredToRun() bool { if len(targetMap) > 0 { if _, ok := targetMap[r.Name]; ok { return true @@ -49,7 +62,7 @@ func (r *release) isReleaseConsideredToRun() bool { // validateRelease validates if a release inside a desired state meets the specifications or not. // check the full specification @ https://github.com/Praqma/helmsman/docs/desired_state_spec.md -func validateRelease(appLabel string, r *release, names map[string]map[string]bool, s state) (bool, string) { +func (r *release) validate(appLabel string, names map[string]map[string]bool, s state) (bool, string) { if r.Name == "" { r.Name = appLabel } @@ -120,8 +133,364 @@ func validateRelease(appLabel string, r *release, names map[string]map[string]bo return true, "" } +// validateReleaseCharts validates if the charts defined in a release are valid. +// Valid charts are the ones that can be found in the defined repos. +// This function uses Helm search to verify if the chart can be found or not. +func validateReleaseCharts(apps map[string]*release) error { + wg := sync.WaitGroup{} + c := make(chan string, len(apps)) + for app, r := range apps { + wg.Add(1) + go r.validateChart(app, &wg, c) + } + wg.Wait() + if len(c) > 0 { + err := <-c + if err != "" { + return errors.New(err) + } + } + return nil +} + +var versionExtractor = regexp.MustCompile(`version:\s?(.*)`) + +func (r *release) validateChart(app string, wg *sync.WaitGroup, c chan string) { + + defer wg.Done() + validateCurrentChart := true + if !r.isConsideredToRun() { + validateCurrentChart = false + } + if validateCurrentChart { + if isLocalChart(r.Chart) { + cmd := command{ + Cmd: helmBin, + Args: []string{"inspect", "chart", r.Chart}, + Description: "Validating [ " + r.Chart + " ] chart's availability", + } + + var output string + var exitCode int + if exitCode, output, _ = cmd.exec(debug, verbose); exitCode != 0 { + maybeRepo := filepath.Base(filepath.Dir(r.Chart)) + c <- "Chart [ " + r.Chart + " ] for app [" + app + "] can't be found. Did you mean to add a repo [ " + maybeRepo + " ]?" + return + } + matches := versionExtractor.FindStringSubmatch(output) + if len(matches) == 2 { + version := matches[1] + if r.Version != version { + c <- "Chart [ " + r.Chart + " ] with version [ " + r.Version + " ] is specified for " + + "app [" + app + "] but the chart found at that path has version [ " + version + " ] which does not match." + return + } + } + + } else { + version := r.Version + if len(version) == 0 { + version = "*" + } + cmd := command{ + Cmd: helmBin, + Args: []string{"search", "repo", r.Chart, "--version", version, "-l"}, + Description: "Validating [ " + r.Chart + " ] chart's version [ " + r.Version + " ] availability", + } + + if exitCode, result, _ := cmd.exec(debug, verbose); exitCode != 0 || strings.Contains(result, "No results found") { + c <- "Chart [ " + r.Chart + " ] with version [ " + r.Version + " ] is specified for " + + "app [" + app + "] but was not found" + return + } + } + } +} + +// getChartVersion fetches the lastest chart version matching the semantic versioning constraints. +// If chart is local, returns the given release version +func (r *release) getChartVersion() (string, string) { + if isLocalChart(r.Chart) { + return r.Version, "" + } + cmd := command{ + Cmd: helmBin, + Args: []string{"search", "repo", r.Chart, "--version", r.Version, "-o", "json"}, + Description: "Getting latest chart's version " + r.Chart + "-" + r.Version + "", + } + + var ( + exitCode int + result string + ) + + if exitCode, result, _ = cmd.exec(debug, verbose); exitCode != 0 { + return "", "Chart [ " + r.Chart + " ] with version [ " + r.Version + " ] is specified but not found in the helm repositories" + } + + chartVersions := make([]chartVersion, 0) + if err := json.Unmarshal([]byte(result), &chartVersions); err != nil { + log.Fatal(fmt.Sprint(err)) + } + + if len(chartVersions) < 1 { + return "", "Chart [ " + r.Chart + " ] with version [ " + r.Version + " ] is specified but not found in the helm repositories" + } else if len(chartVersions) > 1 { + return "", "Multiple versions of chart [ " + r.Chart + " ] with version [ " + r.Version + " ] found in the helm repositories" + } + return chartVersions[0].Version, "" +} + +// testRelease creates a Helm command to test a particular release. +func (r *release) test() { + cmd := command{ + Cmd: helmBin, + Args: []string{"test", "--namespace", r.Namespace, r.Name}, + Description: "Running tests for release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ]", + } + outcome.addCommand(cmd, r.Priority, r) + logDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is required to be tested during installation", r.Priority, noop) +} + +// installRelease creates a Helm command to install a particular release in a particular namespace using a particular Tiller. +func (r *release) install() { + cmd := command{ + Cmd: helmBin, + Args: concat([]string{"install", r.Name, r.Chart, "--namespace", r.Namespace}, r.getValuesFiles(), []string{"--version", r.Version}, r.getSetValues(), r.getSetStringValues(), r.getWait(), r.getHelmFlags()), + Description: "Installing release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ]", + } + outcome.addCommand(cmd, r.Priority, r) + logDecision("Release [ "+r.Name+" ] will be installed in [ "+r.Namespace+" ] namespace", r.Priority, create) + + if r.Test { + r.test() + } +} + +// uninstall deletes a release from a particular Tiller in a k8s cluster +func (r *release) uninstall() { + priority := r.Priority + if settings.ReverseDelete { + priority = priority * -1 + } + + cmd := command{ + Cmd: helmBin, + Args: concat([]string{"uninstall", "--namespace", r.Namespace, r.Name}, getDryRunFlags()), + Description: "Deleting release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ]", + } + outcome.addCommand(cmd, priority, r) + logDecision(fmt.Sprintf("release [ %s ] is desired to be DELETED.", r.Name), r.Priority, delete) +} + +// diffRelease diffs an existing release with the specified values.yaml +func (r *release) diff() string { + exitCode := 0 + msg := "" + colorFlag := "" + diffContextFlag := []string{} + suppressDiffSecretsFlag := "" + if noColors { + colorFlag = "--no-color" + } + if diffContext != -1 { + diffContextFlag = []string{"--context", strconv.Itoa(diffContext)} + } + if suppressDiffSecrets { + suppressDiffSecretsFlag = "--suppress-secrets" + } + + cmd := command{ + Cmd: helmBin, + Args: concat([]string{"diff", colorFlag}, diffContextFlag, []string{suppressDiffSecretsFlag, "--namespace", r.Namespace, "upgrade", r.Name, r.Chart}, r.getValuesFiles(), []string{"--version", r.Version}, r.getSetValues(), r.getSetStringValues()), + Description: "Diffing release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ]", + } + + if exitCode, msg, _ = cmd.exec(debug, verbose); exitCode != 0 { + log.Fatal(fmt.Sprintf("Command returned with exit code: %d. And error message: %s ", exitCode, msg)) + } else { + if (verbose || showDiff) && msg != "" { + fmt.Println(msg) + } + } + + return msg +} + +// upgradeRelease upgrades an existing release with the specified values.yaml +func (r *release) upgrade() { + var force string + if forceUpgrades { + force = "--force" + } + cmd := command{ + Cmd: helmBin, + Args: concat([]string{"upgrade", "--namespace", r.Namespace, r.Name, r.Chart}, r.getValuesFiles(), []string{"--version", r.Version, force}, r.getSetValues(), r.getSetStringValues(), r.getWait(), r.getHelmFlags()), + Description: "Upgrading release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ]", + } + + outcome.addCommand(cmd, r.Priority, r) +} + +// reInstall purge deletes a release and reinstalls it. +// This is used when moving a release to another namespace or when changing the chart used for it. +func (r *release) reInstall(rs helmRelease) { + + delCmd := command{ + Cmd: helmBin, + Args: concat([]string{"delete", "--purge", r.Name}, getDryRunFlags()), + Description: "Deleting release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ]", + } + outcome.addCommand(delCmd, r.Priority, r) + + installCmd := command{ + Cmd: helmBin, + Args: concat([]string{"install", r.Chart, "--version", r.Version, "-n", r.Name, "--namespace", r.Namespace}, r.getValuesFiles(), r.getSetValues(), r.getSetStringValues(), r.getWait(), r.getHelmFlags()), + Description: "Installing release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ]", + } + outcome.addCommand(installCmd, r.Priority, r) +} + +// rollbackRelease evaluates if a rollback action needs to be taken for a given release. +// if the release is already deleted but from a different namespace than the one specified in input, +// it purge deletes it and create it in the specified namespace. +func (r *release) rollback(cs *currentState) { + rs, ok := (*cs)[fmt.Sprintf("%s-%s", r.Name, r.Namespace)] + if !ok { + return + } + + if r.Namespace == rs.Namespace { + + cmd := command{ + Cmd: helmBin, + Args: concat([]string{"rollback", r.Name, rs.getRevision()}, r.getWait(), r.getTimeout(), r.getNoHooks(), getDryRunFlags()), + Description: "Rolling back release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ]", + } + outcome.addCommand(cmd, r.Priority, r) + r.upgrade() // this is to reflect any changes in values file(s) + logDecision("Release [ "+r.Name+" ] was deleted and is desired to be rolled back to "+ + "namespace [ "+r.Namespace+" ]", r.Priority, create) + } else { + r.reInstall(rs) + logDecision("Release [ "+r.Name+" ] is deleted BUT from namespace [ "+rs.Namespace+ + " ]. Will purge delete it from there and install it in namespace [ "+r.Namespace+" ]", r.Priority, create) + logDecision("WARNING: rolling back release [ "+r.Name+" ] from [ "+rs.Namespace+" ] to [ "+r.Namespace+ + " ] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/apps/moving_across_namespaces.md"+ + " for details if this release uses PV and PVC.", r.Priority, create) + + } +} + +// getNoHooks returns the no-hooks flag for install/upgrade commands +func (r *release) getNoHooks() []string { + if r.NoHooks { + return []string{"--no-hooks"} + } + return []string{} +} + +// getTimeout returns the timeout flag for install/upgrade commands +func (r *release) getTimeout() []string { + if r.Timeout != 0 { + return []string{"--timeout", strconv.Itoa(r.Timeout) + "s"} + } + return []string{} +} + +// getValuesFiles return partial install/upgrade release command to substitute the -f flag in Helm. +func (r *release) getValuesFiles() []string { + var fileList []string + + if r.ValuesFile != "" { + fileList = append(fileList, r.ValuesFile) + } else if len(r.ValuesFiles) > 0 { + fileList = append(fileList, r.ValuesFiles...) + } + + if r.SecretsFile != "" { + if !helmPluginExists("secrets") { + log.Fatal("helm secrets plugin is not installed/configured correctly. Aborting!") + } + if err := decryptSecret(r.SecretsFile); err != nil { + log.Fatal(err.Error()) + } + fileList = append(fileList, r.SecretsFile+".dec") + } else if len(r.SecretsFiles) > 0 { + if !helmPluginExists("secrets") { + log.Fatal("helm secrets plugin is not installed/configured correctly. Aborting!") + } + for i := 0; i < len(r.SecretsFiles); i++ { + if err := decryptSecret(r.SecretsFiles[i]); err != nil { + log.Fatal(err.Error()) + } + // if .dec extension is added before to the secret filename, don't add it again. + // This happens at upgrade time (where diff and upgrade both call this function) + if !isOfType(r.SecretsFiles[i], []string{".dec"}) { + r.SecretsFiles[i] = r.SecretsFiles[i] + ".dec" + } + } + fileList = append(fileList, r.SecretsFiles...) + } + + fileListArgs := []string{} + for _, file := range fileList { + fileListArgs = append(fileListArgs, "-f", file) + } + return fileListArgs +} + +// getSetValues returns --set params to be used with helm install/upgrade commands +func (r *release) getSetValues() []string { + result := []string{} + for k, v := range r.Set { + result = append(result, "--set", k+"="+strings.Replace(v, ",", "\\,", -1)+"") + } + return result +} + +// getSetStringValues returns --set-string params to be used with helm install/upgrade commands +func (r *release) getSetStringValues() []string { + result := []string{} + for k, v := range r.SetString { + result = append(result, "--set-string", k+"="+strings.Replace(v, ",", "\\,", -1)+"") + } + return result +} + +// getWait returns a partial helm command containing the helm wait flag (--wait) if the wait flag for the release was set to true +// Otherwise, retruns an empty string +func (r *release) getWait() []string { + result := []string{} + if r.Wait { + result = append(result, "--wait") + } + return result +} + +// getDesiredNamespace returns the namespace of a release +func (r *release) getDesiredNamespace() string { + return r.Namespace +} + +// getHelmFlags returns helm flags +func (r *release) getHelmFlags() []string { + var flags []string + + flags = append(flags, r.HelmFlags...) + return concat(r.getNoHooks(), r.getTimeout(), getDryRunFlags(), flags) +} + +func (r *release) checkChartDepUpdate() { + if updateDeps && isLocalChart(r.Chart) { + if ok, err := updateChartDep(r.Chart); !ok { + log.Fatal("helm dependency update failed: " + err) + } + } +} + // overrideNamespace overrides a release defined namespace with a new given one -func overrideNamespace(r *release, newNs string) { +func (r *release) overrideNamespace(newNs string) { log.Info("Overriding namespace for app: " + r.Name) r.Namespace = newNs } diff --git a/internal/app/release_test.go b/internal/app/release_test.go index fd5aad47..1af9d055 100644 --- a/internal/app/release_test.go +++ b/internal/app/release_test.go @@ -1,10 +1,31 @@ package app import ( + "fmt" + "os" "strings" "testing" ) +func setupTestCase(t *testing.T) func(t *testing.T) { + t.Log("setup test case") + os.MkdirAll(os.TempDir()+"/helmsman-tests/myapp", os.ModePerm) + os.MkdirAll(os.TempDir()+"/helmsman-tests/dir-with space/myapp", os.ModePerm) + cmd := command{ + Cmd: helmBin, + Args: []string{"create", os.TempDir() + "/helmsman-tests/dir-with space/myapp"}, + Description: "creating an empty local chart directory", + } + if exitCode, msg, _ := cmd.exec(debug, verbose); exitCode != 0 { + log.Fatal(fmt.Sprintf("Command returned with exit code: %d. And error message: %s ", exitCode, msg)) + } + + return func(t *testing.T) { + t.Log("teardown test case") + //os.RemoveAll("/tmp/helmsman-tests/") + } +} + func Test_validateRelease(t *testing.T) { st := state{ Metadata: make(map[string]string), @@ -270,7 +291,7 @@ func Test_validateRelease(t *testing.T) { names := make(map[string]map[string]bool) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, got1 := validateRelease("testApp", tt.args.r, names, tt.args.s) + got, got1 := tt.args.r.validate("testApp", names, tt.args.s) if got != tt.want { t.Errorf("validateRelease() got = %v, want %v", got, tt.want) } @@ -280,3 +301,361 @@ func Test_validateRelease(t *testing.T) { }) } } + +func Test_validateReleaseCharts(t *testing.T) { + type args struct { + apps map[string]*release + } + tests := []struct { + name string + targetFlag []string + groupFlag []string + args args + want bool + }{ + { + name: "test case 1: valid local path with no chart", + targetFlag: []string{}, + args: args{ + apps: map[string]*release{ + "app": &release{ + Name: "", + Description: "", + Namespace: "", + Enabled: true, + Chart: os.TempDir() + "/helmsman-tests/myapp", + Version: "", + ValuesFile: "", + ValuesFiles: []string{}, + SecretsFile: "", + SecretsFiles: []string{}, + Test: false, + Protected: false, + Wait: false, + Priority: 0, + Set: make(map[string]string), + SetString: make(map[string]string), + HelmFlags: []string{}, + NoHooks: false, + Timeout: 0, + }, + }, + }, + want: false, + }, { + name: "test case 2: invalid local path", + targetFlag: []string{}, + args: args{ + apps: map[string]*release{ + "app": &release{ + Name: "", + Description: "", + Namespace: "", + Enabled: true, + Chart: os.TempDir() + "/does-not-exist/myapp", + Version: "", + ValuesFile: "", + ValuesFiles: []string{}, + SecretsFile: "", + SecretsFiles: []string{}, + Test: false, + Protected: false, + Wait: false, + Priority: 0, + Set: make(map[string]string), + SetString: make(map[string]string), + HelmFlags: []string{}, + NoHooks: false, + Timeout: 0, + }, + }, + }, + want: false, + }, { + name: "test case 3: valid chart local path with whitespace", + targetFlag: []string{}, + args: args{ + apps: map[string]*release{ + "app": &release{ + Name: "", + Description: "", + Namespace: "", + Enabled: true, + Chart: os.TempDir() + "/helmsman-tests/dir-with space/myapp", + Version: "0.1.0", + ValuesFile: "", + ValuesFiles: []string{}, + SecretsFile: "", + SecretsFiles: []string{}, + Test: false, + Protected: false, + Wait: false, + Priority: 0, + Set: make(map[string]string), + SetString: make(map[string]string), + HelmFlags: []string{}, + NoHooks: false, + Timeout: 0, + }, + }, + }, + want: true, + }, { + name: "test case 4: valid chart from repo", + targetFlag: []string{}, + args: args{ + apps: map[string]*release{ + "app": &release{ + Name: "", + Description: "", + Namespace: "", + Enabled: true, + Chart: "stable/prometheus", + Version: "9.5.2", + ValuesFile: "", + ValuesFiles: []string{}, + SecretsFile: "", + SecretsFiles: []string{}, + Test: false, + Protected: false, + Wait: false, + Priority: 0, + Set: make(map[string]string), + SetString: make(map[string]string), + HelmFlags: []string{}, + NoHooks: false, + Timeout: 0, + }, + }, + }, + want: true, + }, { + name: "test case 5: invalid local path for chart ignored with -target flag, while other app was targeted", + targetFlag: []string{"notThisOne"}, + args: args{ + apps: map[string]*release{ + "app": &release{ + Name: "app", + Description: "", + Namespace: "", + Enabled: true, + Chart: os.TempDir() + "/does-not-exist/myapp", + Version: "", + ValuesFile: "", + ValuesFiles: []string{}, + SecretsFile: "", + SecretsFiles: []string{}, + Test: false, + Protected: false, + Wait: false, + Priority: 0, + Set: make(map[string]string), + SetString: make(map[string]string), + HelmFlags: []string{}, + NoHooks: false, + Timeout: 0, + }, + }, + }, + want: true, + }, { + name: "test case 6: invalid local path for chart included with -target flag", + targetFlag: []string{"app"}, + args: args{ + apps: map[string]*release{ + "app": &release{ + Name: "app", + Description: "", + Namespace: "", + Enabled: true, + Chart: os.TempDir() + "/does-not-exist/myapp", + Version: "", + ValuesFile: "", + ValuesFiles: []string{}, + SecretsFile: "", + SecretsFiles: []string{}, + Test: false, + Protected: false, + Wait: false, + Priority: 0, + Set: make(map[string]string), + SetString: make(map[string]string), + HelmFlags: []string{}, + NoHooks: false, + Timeout: 0, + }, + }, + }, + want: false, + }, + } + + teardownTestCase := setupTestCase(t) + defer teardownTestCase(t) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + targetMap = make(map[string]bool) + groupMap = make(map[string]bool) + for _, target := range tt.targetFlag { + targetMap[target] = true + } + for _, group := range tt.groupFlag { + groupMap[group] = true + } + err := validateReleaseCharts(tt.args.apps) + switch err.(type) { + case nil: + if tt.want != true { + t.Errorf("validateReleaseCharts() = %v, want error", err) + } + case error: + if tt.want != false { + t.Errorf("validateReleaseCharts() = %v, want nil", err) + } + } + }) + } +} + +func Test_getReleaseChartVersion(t *testing.T) { + // version string = the first semver-valid string after the last hypen in the chart string. + + type args struct { + r helmRelease + } + tests := []struct { + name string + args args + want string + }{ + { + name: "test case 1: there is a pre-release version", + args: args{ + r: helmRelease{ + Revision: 0, + Updated: HelmTime{}, + Status: "", + Chart: "elasticsearch-1.3.0-1", + Namespace: "", + }, + }, + want: "1.3.0-1", + }, { + name: "test case 2: normal case", + args: args{ + r: helmRelease{ + Revision: 0, + Updated: HelmTime{}, + Status: "", + Chart: "elasticsearch-1.3.0", + Namespace: "", + }, + }, + want: "1.3.0", + }, { + name: "test case 3: there is a hypen in the name", + args: args{ + r: helmRelease{ + Revision: 0, + Updated: HelmTime{}, + Status: "", + Chart: "elastic-search-1.3.0", + Namespace: "", + }, + }, + want: "1.3.0", + }, { + name: "test case 4: there is meta information", + args: args{ + r: helmRelease{ + Revision: 0, + Updated: HelmTime{}, + Status: "", + Chart: "elastic-search-1.3.0+meta.info", + Namespace: "", + }, + }, + want: "1.3.0+meta.info", + }, { + name: "test case 5: an invalid string", + args: args{ + r: helmRelease{ + Revision: 0, + Updated: HelmTime{}, + Status: "", + Chart: "foo", + Namespace: "", + }, + }, + want: "", + }, { + name: "test case 6: version includes v", + args: args{ + r: helmRelease{ + Revision: 0, + Updated: HelmTime{}, + Status: "", + Chart: "cert-manager-v0.5.2", + Namespace: "", + }, + }, + want: "v0.5.2", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Log(tt.want) + if got := tt.args.r.getChartVersion(); got != tt.want { + t.Errorf("getReleaseChartVersion() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_getChartVersion(t *testing.T) { + // version string = the first semver-valid string after the last hypen in the chart string. + type args struct { + r *release + } + tests := []struct { + name string + args args + want string + }{ + { + name: "getChartVersion - local chart should return given release version", + args: args{ + r: &release{ + Name: "release1", + Namespace: "namespace", + Version: "1.0.0", + Chart: "./../../tests/chart-test", + Enabled: true, + }, + }, + want: "1.0.0", + }, + { + name: "getChartVersion - unknown chart should error", + args: args{ + r: &release{ + Name: "release1", + Namespace: "namespace", + Version: "1.0.0", + Chart: "random-chart-name-1f8147", + Enabled: true, + }, + }, + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Log(tt.want) + got, _ := tt.args.r.getChartVersion() + if got != tt.want { + t.Errorf("getChartVersion() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/app/state.go b/internal/app/state.go index 031e8cc8..5f7832b2 100644 --- a/internal/app/state.go +++ b/internal/app/state.go @@ -139,7 +139,7 @@ func (s state) validate() error { names := make(map[string]map[string]bool) for appLabel, r := range s.Apps { - result, errMsg := validateRelease(appLabel, r, names, s) + result, errMsg := r.validate(appLabel, names, s) if !result { return errors.New("apps validation failed -- for app [" + appLabel + " ]. " + errMsg) } diff --git a/internal/app/utils.go b/internal/app/utils.go index 09c93396..c7633124 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -2,6 +2,7 @@ package app import ( "bytes" + "errors" "fmt" "io" "io/ioutil" @@ -450,19 +451,19 @@ func notifySlack(content string, url string, failure bool, executing bool) bool ] }`) req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr)) + if err != nil { + log.Errorf("Failed to send slack message: %w", err) + } req.Header.Set("Content-Type", "application/json") client := &http.Client{} resp, err := client.Do(req) if err != nil { - log.Fatal("while sending notifications to slack" + err.Error()) + log.Errorf("Failed to send notification to slack: %w", err) } defer resp.Body.Close() - if resp.StatusCode == 200 { - return true - } - return false + return resp.StatusCode == 200 } // getBucketElements returns a map containing the bucket name and the file path inside the bucket @@ -508,10 +509,7 @@ func Indent(s, prefix string) string { // isLocalChart checks if a chart specified in the DSF is a local directory or not func isLocalChart(chart string) bool { _, err := os.Stat(chart) - if err == nil { - return true - } - return false + return err == nil } // concat appends all slices to a single slice @@ -536,3 +534,51 @@ func writeStringToFile(filename string, data string) error { } return file.Sync() } + +// decrypt a eyaml secret file +func decryptSecret(name string) error { + cmd := helmBin + args := []string{"secrets", "dec", name} + + if settings.EyamlEnabled { + cmd = "eyaml" + args = []string{"decrypt", "-f", name} + if settings.EyamlPrivateKeyPath != "" && settings.EyamlPublicKeyPath != "" { + args = append(args, []string{"--pkcs7-private-key", settings.EyamlPrivateKeyPath, "--pkcs7-public-key", settings.EyamlPublicKeyPath}...) + } + } + + command := command{ + Cmd: cmd, + Args: args, + Description: "Decrypting " + name, + } + + exitCode, output, stderr := command.exec(debug, false) + if !settings.EyamlEnabled { + _, fileNotFound := os.Stat(name + ".dec") + if fileNotFound != nil && !isOfType(name, []string{".dec"}) { + return errors.New(output) + } + } + + if exitCode != 0 { + return errors.New(output) + } else if stderr != "" { + return errors.New(output) + } + + if settings.EyamlEnabled { + var outfile string + if isOfType(name, []string{".dec"}) { + outfile = name + } else { + outfile = name + ".dec" + } + err := writeStringToFile(outfile, output) + if err != nil { + log.Fatal("Can't write [ " + outfile + " ] file") + } + } + return nil +} diff --git a/internal/app/utils_test.go b/internal/app/utils_test.go index 31def8e6..971abca0 100644 --- a/internal/app/utils_test.go +++ b/internal/app/utils_test.go @@ -369,6 +369,97 @@ func Test_readFile(t *testing.T) { } } +func Test_eyamlSecrets(t *testing.T) { + type args struct { + r *release + s *config + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "decryptSecrets - valid eyaml-based secrets decryption", + args: args{ + s: &config{ + EyamlEnabled: true, + EyamlPublicKeyPath: "./../../tests/keys/public_key.pkcs7.pem", + EyamlPrivateKeyPath: "./../../tests/keys/private_key.pkcs7.pem", + }, + r: &release{ + Name: "release1", + Namespace: "namespace", + Version: "1.0.0", + Enabled: true, + SecretsFile: "./../../tests/secrets/valid_eyaml_secrets.yaml", + }, + }, + want: true, + }, + { + name: "decryptSecrets - not existing eyaml-based secrets file", + args: args{ + s: &config{ + EyamlEnabled: true, + EyamlPublicKeyPath: "./../../tests/keys/public_key.pkcs7.pem", + EyamlPrivateKeyPath: "./../../tests/keys/private_key.pkcs7.pem", + }, + r: &release{ + Name: "release1", + Namespace: "namespace", + Version: "1.0.0", + Enabled: true, + SecretsFile: "./../../tests/secrets/invalid_eyaml_secrets.yaml", + }, + }, + want: false, + }, + { + name: "decryptSecrets - not existing eyaml key", + args: args{ + s: &config{ + EyamlEnabled: true, + EyamlPublicKeyPath: "./../../tests/keys/public_key.pkcs7.pem2", + EyamlPrivateKeyPath: "./../../tests/keys/private_key.pkcs7.pem", + }, + r: &release{ + Name: "release1", + Namespace: "namespace", + Version: "1.0.0", + Enabled: true, + SecretsFile: "./../../tests/secrets/valid_eyaml_secrets.yaml", + }, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Log(tt.want) + defaultSettings := settings + defer func() { settings = defaultSettings }() + settings.EyamlEnabled = tt.args.s.EyamlEnabled + settings.EyamlPublicKeyPath = tt.args.s.EyamlPublicKeyPath + settings.EyamlPrivateKeyPath = tt.args.s.EyamlPrivateKeyPath + err := decryptSecret(tt.args.r.SecretsFile) + switch err.(type) { + case nil: + if tt.want != true { + t.Errorf("decryptSecret() = %v, want error", err) + } + case error: + if tt.want != false { + t.Errorf("decryptSecret() = %v, want nil", err) + } + } + if _, err := os.Stat(tt.args.r.SecretsFile + ".dec"); err == nil { + defer deleteFile(tt.args.r.SecretsFile + ".dec") + } + }) + } +} + // func Test_printHelp(t *testing.T) { // tests := []struct { // name string diff --git a/release-notes.md b/release-notes.md index 174da7e4..66731b64 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,14 +1,15 @@ -# v3.0.0-beta1 +# v3.0.0-beta2 This is a major release to support Helm v3. It is recommended you read the [Helm 3 migration guide](https://helm.sh/docs/topics/v2_v3_migration/) before using this release. > Starting from this release, support for Helmsman v1.x will be limited to bug fixes. -The following are the most important changes: +The following are the most important changes: - A new and improved logger. - Restructuring the code. -- Introducing the `context` stanza to define a context for each DSF. More details [here](docs/misc/merge_desired_state_files). +- Parallelized decision making +- Introducing the `context` stanza to define a context for each DSF. More details [here](docs/misc/merge_desired_state_files). - Deprecating all the DSF stanzas related to Tiller. - Deprecating the `purge` option for releases. - The default value for `storageBackend` is now `secret`. From c8f4dca8035d628e8ab18974f005ddb2cb169870 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Wed, 25 Dec 2019 13:16:07 +0000 Subject: [PATCH 0547/1127] preparing new release --- .version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.version b/.version index fc910349..c2caabd0 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.0.0-beta1 \ No newline at end of file +v3.0.0-beta2 From 370ba6b61f9d22f02f5f1fda63fbf38ec48b0fe3 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Fri, 27 Dec 2019 17:33:39 +0000 Subject: [PATCH 0548/1127] reduce code duplication --- internal/app/cli.go | 6 +- internal/app/cli_test.go | 2 +- internal/app/command.go | 8 +- internal/app/decision_maker.go | 131 ++++++++++++--------------------- internal/app/helm_helpers.go | 54 +++++++------- internal/app/helm_release.go | 32 ++++---- internal/app/kube_helpers.go | 96 ++++++------------------ internal/app/main.go | 13 +++- internal/app/namespace.go | 6 -- internal/app/plan.go | 16 ++-- internal/app/release.go | 104 +++++++++++--------------- internal/app/release_test.go | 8 +- internal/app/state.go | 39 +++++++++- internal/app/utils.go | 23 +----- 14 files changed, 224 insertions(+), 314 deletions(-) diff --git a/internal/app/cli.go b/internal/app/cli.go index 6fcebae8..a28b74db 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -120,11 +120,11 @@ func Cli() { os.Setenv("KUBECONFIG", kubeconfig) } - if !toolExists("kubectl") { + if !toolExists("kubectl", debug) { log.Fatal("kubectl is not installed/configured correctly. Aborting!") } - if !toolExists(helmBin) { + if !toolExists(helmBin, debug) { log.Fatal("" + helmBin + " is not installed/configured correctly. Aborting!") } @@ -169,7 +169,7 @@ func Cli() { var fileState state for _, f := range files { - result, msg := fromFile(f, &fileState) + result, msg := fileState.fromFile(f) if result { log.Info(msg) } else { diff --git a/internal/app/cli_test.go b/internal/app/cli_test.go index fddbdec4..25868c06 100644 --- a/internal/app/cli_test.go +++ b/internal/app/cli_test.go @@ -38,7 +38,7 @@ func Test_toolExists(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := toolExists(tt.args.tool); got != tt.want { + if got := toolExists(tt.args.tool, false); got != tt.want { t.Errorf("toolExists() = %v, want %v", got, tt.want) } }) diff --git a/internal/app/command.go b/internal/app/command.go index 61d5acf2..65fb2f66 100644 --- a/internal/app/command.go +++ b/internal/app/command.go @@ -16,8 +16,12 @@ type command struct { Description string } +func (c *command) String() string { + return c.Cmd + " " + strings.Join(c.Args, " ") +} + // exec executes the executable command and returns the exit code and execution result -func (c command) exec(debug bool, verbose bool) (int, string, string) { +func (c *command) exec(debug bool, verbose bool) (int, string, string) { // Only use non-empty string args args := []string{} for _, str := range c.Args { @@ -55,7 +59,7 @@ func (c command) exec(debug bool, verbose bool) (int, string, string) { // toolExists returns true if the tool is present in the environment and false otherwise. // It takes as input the tool's command to check if it is recognizable or not. e.g. helm or kubectl -func toolExists(tool string) bool { +func toolExists(tool string, debug bool) bool { cmd := command{ Cmd: tool, Args: []string{}, diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index 9127bd3e..849ef7d6 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -1,7 +1,6 @@ package app import ( - "fmt" "regexp" "strings" "sync" @@ -21,18 +20,24 @@ func logDecision(decision string, priority int, decisionType decisionType) { func buildState() currentState { log.Info("Acquiring current Helm state from cluster...") - cs := make(map[string]helmRelease) + cs := currentState(make(map[string]helmRelease)) rel := getHelmReleases() + var wg sync.WaitGroup for _, r := range rel { - r.HelmsmanContext = getReleaseContext(r.Name, r.Namespace) - cs[fmt.Sprintf("%s-%s", r.Name, r.Namespace)] = r + wg.Add(1) + go func(c currentState, r helmRelease, wg *sync.WaitGroup) { + defer wg.Done() + r.HelmsmanContext = getReleaseContext(r.Name, r.Namespace) + c[r.key()] = r + }(cs, r, &wg) } + wg.Wait() return cs } // makePlan creates a plan of the actions needed to make the desired state come true. -func (cs *currentState) makePlan(s *state) *plan { +func (cs currentState) makePlan(s *state) *plan { outcome = createPlan() wg := sync.WaitGroup{} @@ -48,7 +53,7 @@ func (cs *currentState) makePlan(s *state) *plan { // decide makes a decision about what commands (actions) need to be executed // to make a release section of the desired state come true. -func (cs *currentState) decide(r *release, s *state, wg *sync.WaitGroup) { +func (cs currentState) decide(r *release, s *state, wg *sync.WaitGroup) { defer wg.Done() // check for presence in defined targets or groups if !r.isConsideredToRun() { @@ -66,7 +71,7 @@ func (cs *currentState) decide(r *release, s *state, wg *sync.WaitGroup) { if !r.Enabled { if ok := cs.releaseExists(r, ""); ok { - if cs.isProtected(r) { + if r.isProtected(cs) { logDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ "protection is removed.", r.Priority, noop) @@ -81,7 +86,7 @@ func (cs *currentState) decide(r *release, s *state, wg *sync.WaitGroup) { } if ok := cs.releaseExists(r, "deployed"); ok { - if !cs.isProtected(r) { + if !r.isProtected(cs) { cs.inspectUpgradeScenario(r) // upgrade or move } else { @@ -90,7 +95,7 @@ func (cs *currentState) decide(r *release, s *state, wg *sync.WaitGroup) { } } else if ok := cs.releaseExists(r, "deleted"); ok { - if !cs.isProtected(r) { + if !r.isProtected(cs) { r.rollback(cs) // rollback @@ -101,7 +106,7 @@ func (cs *currentState) decide(r *release, s *state, wg *sync.WaitGroup) { } else if ok := cs.releaseExists(r, "failed"); ok { - if !cs.isProtected(r) { + if !r.isProtected(cs) { logDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is in FAILED state. Upgrade is scheduled!", r.Priority, change) r.upgrade() @@ -112,7 +117,7 @@ func (cs *currentState) decide(r *release, s *state, wg *sync.WaitGroup) { } } else { // If there is no release in the cluster with this name and in this namespace, then install it! - if _, ok := (*cs)[fmt.Sprintf("%s-%s", r.Name, r.Namespace)]; !ok { + if _, ok := cs[r.key()]; !ok { r.install() } else { // A release with the same name and in the same namespace exists, but it has a different context label (managed by another DSF) @@ -126,8 +131,8 @@ func (cs *currentState) decide(r *release, s *state, wg *sync.WaitGroup) { // It searches the Current State for releases. // The key format for releases uniqueness is: // If status is provided as an input [deployed, deleted, failed], then the search will verify the release status matches the search status. -func (cs *currentState) releaseExists(r *release, status string) bool { - v, ok := (*cs)[fmt.Sprintf("%s-%s", r.Name, r.Namespace)] +func (cs currentState) releaseExists(r *release, status string) bool { + v, ok := cs[r.key()] if !ok || v.HelmsmanContext != s.Context { return false } @@ -138,39 +143,44 @@ func (cs *currentState) releaseExists(r *release, status string) bool { return true } +var resourceNameExtractor = regexp.MustCompile(`(^\w+\/|\.v\d+$)`) +var releaseNameExtractor = regexp.MustCompile(`sh\.helm\.release\.v\d+\.`) + // getHelmsmanReleases returns a map of all releases that are labeled with "MANAGED-BY=HELMSMAN" // The releases are categorized by the namespaces in which they are deployed // The returned map format is: map[:map[:true]] -func (cs *currentState) getHelmsmanReleases() map[string]map[helmRelease]bool { +func (cs currentState) getHelmsmanReleases() map[string]map[string]bool { var lines []string - releases := make(map[string]map[helmRelease]bool) + releases := make(map[string]map[string]bool) storageBackend := s.Settings.StorageBackend for ns := range s.Namespaces { - cmd := command{ - Cmd: "kubectl", - Args: []string{"get", storageBackend, "-n", ns, "-l", "MANAGED-BY=HELMSMAN", "-l", "HELMSMAN_CONTEXT=" + s.Context, "-o", "name"}, - Description: "Getting Helmsman-managed releases in namespace [ " + ns + " ]", - } + cmd := kubectl([]string{"get", storageBackend, "-n", ns, "-l", "MANAGED-BY=HELMSMAN", "-l", "HELMSMAN_CONTEXT=" + s.Context, "-o", "name"}, "Getting Helmsman-managed releases in namespace [ "+ns+" ]") exitCode, output, _ := cmd.exec(debug, verbose) if exitCode != 0 { log.Fatal(output) } - if strings.EqualFold("No resources found.", strings.TrimSpace(output)) { + if !strings.EqualFold("No resources found.", strings.TrimSpace(output)) { lines = strings.Split(output, "\n") } - for _, r := range lines { - if r == "" { + for _, name := range lines { + if name == "" { continue } + name = resourceNameExtractor.ReplaceAllString(name, "") + name = releaseNameExtractor.ReplaceAllString(name, "") if _, ok := releases[ns]; !ok { - releases[ns] = make(map[helmRelease]bool) + releases[ns] = make(map[string]bool) + } + releases[ns][name] = false + for _, app := range s.Apps { + if app.Name == name && app.Namespace == ns { + releases[ns][name] = true + } } - releaseName := strings.Split(strings.Split(r, "/")[1], ".")[4] - releases[ns][(*cs)[releaseName+"-"+ns]] = true } } return releases @@ -181,36 +191,22 @@ func (cs *currentState) getHelmsmanReleases() map[string]map[helmRelease]bool { // For all untracked releases found, a decision is made to uninstall them and is added to the Helmsman plan // NOTE: Untracked releases don't benefit from either namespace or application protection. // NOTE: Removing/Commenting out an app from the desired state makes it untracked. -func (cs *currentState) cleanUntrackedReleases() { - toDelete := make(map[string]map[helmRelease]bool) +func (cs currentState) cleanUntrackedReleases() { + toDelete := 0 log.Info("Checking if any Helmsman managed releases are no longer tracked by your desired state ...") - for ns, releases := range cs.getHelmsmanReleases() { - for r := range releases { - tracked := false - for _, app := range s.Apps { - if app.Name == r.Name && app.Namespace == r.Namespace { - tracked = true - } - } + for ns, hr := range cs.getHelmsmanReleases() { + for name, tracked := range hr { if !tracked { - if _, ok := toDelete[ns]; !ok { - toDelete[ns] = make(map[helmRelease]bool) - } - toDelete[ns][r] = true - } - } - } - - if len(toDelete) == 0 { - log.Info("No untracked releases found") - } else { - for _, releases := range toDelete { - for r := range releases { + toDelete++ + r := cs[name+"-"+ns] logDecision("Untracked release [ "+r.Name+" ] found and it will be deleted", -800, delete) r.uninstall() } } } + if toDelete == 0 { + log.Info("No untracked releases found") + } } // inspectUpgradeScenario evaluates if a release should be upgraded. @@ -220,9 +216,9 @@ func (cs *currentState) cleanUntrackedReleases() { // it will be purge deleted and installed in the same namespace using the new chart. // - If the release is NOT in the same namespace specified in the input, // it will be purge deleted and installed in the new namespace. -func (cs *currentState) inspectUpgradeScenario(r *release) { +func (cs currentState) inspectUpgradeScenario(r *release) { - rs, ok := (*cs)[fmt.Sprintf("%s-%s", r.Name, r.Namespace)] + rs, ok := cs[r.key()] if !ok { return } @@ -265,36 +261,3 @@ func (cs *currentState) inspectUpgradeScenario(r *release) { " for details if this release uses PV and PVC.", r.Priority, change) } } - -// isProtected checks if a release is protected or not. -// A protected is release is either: a) deployed in a protected namespace b) flagged as protected in the desired state file -// Any release in a protected namespace is protected by default regardless of its flag -// returns true if a release is protected, false otherwise -func (cs *currentState) isProtected(r *release) bool { - - // if the release does not exist in the cluster, it is not protected - if ok := cs.releaseExists(r, ""); !ok { - return false - } - - if s.Namespaces[r.Namespace].Protected || r.Protected { - return true - } - - return false - -} - -var chartNameExtractor = regexp.MustCompile(`[\\/]([^\\/]+)$`) - -// extractChartName extracts the Helm chart name from full chart name in the desired state. -// example: it extracts "chartY" from "repoX/chartY" and "chartZ" from "c:\charts\chartZ" -func extractChartName(releaseChart string) string { - - m := chartNameExtractor.FindStringSubmatch(releaseChart) - if len(m) == 2 { - return m[1] - } - - return "" -} diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index 640a591f..a728333c 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -2,19 +2,39 @@ package app import ( "net/url" + "regexp" "strings" "github.com/Praqma/helmsman/internal/gcs" ) -// getHelmClientVersion returns Helm client Version -func getHelmVersion() string { - cmd := command{ +// helmCmd prepares a helm command to be executed +func helmCmd(args []string, desc string) command { + return command{ Cmd: helmBin, - Args: []string{"version", "--short", "-c"}, - Description: "Checking Helm version", + Args: args, + Description: desc, + } +} + +var chartNameExtractor = regexp.MustCompile(`[\\/]([^\\/]+)$`) + +// extractChartName extracts the Helm chart name from full chart name in the desired state. +// example: it extracts "chartY" from "repoX/chartY" and "chartZ" from "c:\charts\chartZ" +func extractChartName(releaseChart string) string { + + m := chartNameExtractor.FindStringSubmatch(releaseChart) + if len(m) == 2 { + return m[1] } + return "" +} + +// getHelmClientVersion returns Helm client Version +func getHelmVersion() string { + cmd := helmCmd([]string{"version", "--short", "-c"}, "Checking Helm version") + exitCode, result, _ := cmd.exec(debug, false) if exitCode != 0 { log.Fatal("While checking helm version: " + result) @@ -25,11 +45,7 @@ func getHelmVersion() string { // helmPluginExists returns true if the plugin is present in the environment and false otherwise. // It takes as input the plugin's name to check if it is recognizable or not. e.g. diff func helmPluginExists(plugin string) bool { - cmd := command{ - Cmd: helmBin, - Args: []string{"plugin", "list"}, - Description: "Validating that [ " + plugin + " ] is installed", - } + cmd := helmCmd([]string{"plugin", "list"}, "Validating that [ "+plugin+" ] is installed") exitCode, result, _ := cmd.exec(debug, false) @@ -42,11 +58,7 @@ func helmPluginExists(plugin string) bool { // updateChartDep updates dependencies for a local chart func updateChartDep(chartPath string) (bool, string) { - cmd := command{ - Cmd: helmBin, - Args: []string{"dependency", "update", chartPath}, - Description: "Updating dependency for local chart [ " + chartPath + " ]", - } + cmd := helmCmd([]string{"dependency", "update", chartPath}, "Updating dependency for local chart [ "+chartPath+" ]") exitCode, err, _ := cmd.exec(debug, verbose) @@ -84,11 +96,7 @@ func addHelmRepos(repos map[string]string) (bool, string) { } - cmd := command{ - Cmd: helmBin, - Args: concat([]string{"repo", "add", repoName, repoLink}, basicAuthArgs), - Description: "Adding helm repository [ " + repoName + " ]", - } + cmd := helmCmd(concat([]string{"repo", "add", repoName, repoLink}, basicAuthArgs), "Adding helm repository [ "+repoName+" ]") if exitCode, err, _ := cmd.exec(debug, verbose); exitCode != 0 { return false, "While adding helm repository [" + repoName + "]: " + err @@ -96,11 +104,7 @@ func addHelmRepos(repos map[string]string) (bool, string) { } - cmd := command{ - Cmd: helmBin, - Args: []string{"repo", "update"}, - Description: "Updating helm repositories", - } + cmd := helmCmd([]string{"repo", "update"}, "Updating helm repositories") if exitCode, err, _ := cmd.exec(debug, verbose); exitCode != 0 { return false, "While updating helm repos : " + err diff --git a/internal/app/helm_release.go b/internal/app/helm_release.go index 27b72dfb..c6817fc5 100644 --- a/internal/app/helm_release.go +++ b/internal/app/helm_release.go @@ -23,11 +23,7 @@ type helmRelease struct { // getHelmReleases fetches a list of all releases in a k8s cluster func getHelmReleases() []helmRelease { var allReleases []helmRelease - cmd := command{ - Cmd: helmBin, - Args: []string{"list", "--all", "--max", "0", "--output", "json", "--all-namespaces"}, - Description: "Listing all existing releases...", - } + cmd := helmCmd([]string{"list", "--all", "--max", "0", "--output", "json", "--all-namespaces"}, "Listing all existing releases...") exitCode, result, _ := cmd.exec(debug, verbose) if exitCode != 0 { log.Fatal("Failed to list all releases: " + result) @@ -38,27 +34,27 @@ func getHelmReleases() []helmRelease { return allReleases } +func (r *helmRelease) key() string { + return fmt.Sprintf("%s-%s", r.Name, r.Namespace) +} + // uninstall creates the helm command to uninstall an untracked release func (r *helmRelease) uninstall() { - cmd := command{ - Cmd: helmBin, - Args: concat([]string{"uninstall", r.Name, "--namespace", r.Namespace}, getDryRunFlags()), - Description: "Deleting untracked release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ]", - } + cmd := helmCmd(concat([]string{"uninstall", r.Name, "--namespace", r.Namespace}, getDryRunFlags()), "Deleting untracked release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") outcome.addCommand(cmd, -800, nil) } // getRevision returns the revision number for an existing helm release -func (rs *helmRelease) getRevision() string { - return strconv.Itoa(rs.Revision) +func (r *helmRelease) getRevision() string { + return strconv.Itoa(r.Revision) } // getChartName extracts and returns the Helm chart name from the chart info in a release state. // example: chart in release state is "jenkins-0.9.0" and this function will extract "jenkins" from it. -func (rs *helmRelease) getChartName() string { +func (r *helmRelease) getChartName() string { - chart := rs.Chart + chart := r.Chart runes := []rune(chart) return string(runes[0:strings.LastIndexByte(chart[0:strings.IndexByte(chart, '.')], '-')]) } @@ -67,8 +63,8 @@ func (rs *helmRelease) getChartName() string { // example: chart in release state is returns "jenkins-0.9.0" and this functions will extract "0.9.0" from it. // It should also handle semver-valid pre-release/meta information, example: in: jenkins-0.9.0-1, out: 0.9.0-1 // in the event of an error, an empty string is returned. -func (rs *helmRelease) getChartVersion() string { - chart := rs.Chart +func (r *helmRelease) getChartVersion() string { + chart := r.Chart re := regexp.MustCompile(`-(v?[0-9]+\.[0-9]+\.[0-9]+.*)`) matches := re.FindStringSubmatch(chart) if len(matches) > 1 { @@ -79,6 +75,6 @@ func (rs *helmRelease) getChartVersion() string { // getCurrentNamespaceProtection returns the protection state for the namespace where a release is currently installed. // It returns true if a namespace is defined as protected in the desired state file, false otherwise. -func (rs *helmRelease) getCurrentNamespaceProtection() bool { - return s.Namespaces[rs.Namespace].Protected +func (r *helmRelease) getCurrentNamespaceProtection() bool { + return s.Namespaces[r.Namespace].Protected } diff --git a/internal/app/kube_helpers.go b/internal/app/kube_helpers.go index 493a085a..8c804c65 100644 --- a/internal/app/kube_helpers.go +++ b/internal/app/kube_helpers.go @@ -21,35 +21,28 @@ func addNamespaces(namespaces map[string]namespace) { } } else { createNamespace(nsOverride) - overrideAppsNamespace(nsOverride) + s.overrideAppsNamespace(nsOverride) } } -// overrideAppsNamespace replaces all apps namespaces with one specific namespace -func overrideAppsNamespace(newNs string) { - log.Info("Overriding apps namespaces with [ " + newNs + " ] ...") - for _, r := range s.Apps { - r.overrideNamespace(newNs) +// kubectl prepares a kubectl command to be executed +func kubectl(args []string, desc string) command { + return command{ + Cmd: "kubectl", + Args: args, + Description: desc, } } // createNamespace creates a namespace in the k8s cluster func createNamespace(ns string) { - checkCmd := command{ - Cmd: "kubectl", - Args: []string{"get", "namespace", ns}, - Description: "Looking for namespace [ " + ns + " ]", - } + checkCmd := kubectl([]string{"get", "namespace", ns}, "Looking for namespace [ "+ns+" ]") checkExitCode, _, _ := checkCmd.exec(debug, verbose) if checkExitCode == 0 { log.Verbose("Namespace [ " + ns + " ] exists") return } - cmd := command{ - Cmd: "kubectl", - Args: []string{"create", "namespace", ns}, - Description: "Creating namespace [ " + ns + " ]", - } + cmd := kubectl([]string{"create", "namespace", ns}, "Creating namespace [ "+ns+" ]") exitCode, err, _ := cmd.exec(debug, verbose) if exitCode == 0 { log.Info("Namespace [ " + ns + " ] created") @@ -72,11 +65,7 @@ func labelNamespace(ns string, labels map[string]string) { args := []string{"label", "--overwrite", "namespace/" + ns} args = append(args, labelSlice...) - cmd := command{ - Cmd: "kubectl", - Args: args, - Description: "Labeling namespace [ " + ns + " ]", - } + cmd := kubectl(args, "Labeling namespace [ "+ns+" ]") exitCode, errMsg, _ := cmd.exec(debug, verbose) if exitCode != 0 && verbose { @@ -96,11 +85,7 @@ func annotateNamespace(ns string, annotations map[string]string) { } args := []string{"annotate", "--overwrite", "namespace/" + ns} args = append(args, annotationSlice...) - cmd := command{ - Cmd: "kubectl", - Args: args, - Description: "Annotating namespace [ " + ns + " ]", - } + cmd := kubectl(args, "Annotating namespace [ "+ns+" ]") exitCode, errMsg, _ := cmd.exec(debug, verbose) if exitCode != 0 && verbose { @@ -135,12 +120,7 @@ spec: log.Fatal(err.Error()) } - cmd := command{ - Cmd: "kubectl", - Args: []string{"apply", "-f", "temp-LimitRange.yaml", "-n", ns}, - Description: "Creating LimitRange in namespace [ " + ns + " ]", - } - + cmd := kubectl([]string{"apply", "-f", "temp-LimitRange.yaml", "-n", ns}, "Creating LimitRange in namespace [ "+ns+" ]") exitCode, e, _ := cmd.exec(debug, verbose) if exitCode != 0 { @@ -221,31 +201,19 @@ func createContext() (bool, string) { setCredentialsCmdArgs = append(setCredentialsCmdArgs, "--client-certificate="+caClient) } } - cmd := command{ - Cmd: "kubectl", - Args: setCredentialsCmdArgs, - Description: "Creating kubectl context - setting credentials", - } + cmd := kubectl(setCredentialsCmdArgs, "Creating kubectl context - setting credentials") if exitCode, err, _ := cmd.exec(debug, verbose); exitCode != 0 { return false, "failed to create context [ " + s.Settings.KubeContext + " ]: " + err } - cmd = command{ - Cmd: "kubectl", - Args: []string{"config", "set-cluster", s.Settings.KubeContext, "--server=" + s.Settings.ClusterURI, "--certificate-authority=" + caCrt}, - Description: "Creating kubectl context - setting cluster", - } + cmd = kubectl([]string{"config", "set-cluster", s.Settings.KubeContext, "--server=" + s.Settings.ClusterURI, "--certificate-authority=" + caCrt}, "Creating kubectl context - setting cluster") if exitCode, err, _ := cmd.exec(debug, verbose); exitCode != 0 { return false, "failed to create context [ " + s.Settings.KubeContext + " ]: " + err } - cmd = command{ - Cmd: "kubectl", - Args: []string{"config", "set-context", s.Settings.KubeContext, "--cluster=" + s.Settings.KubeContext, "--user=" + s.Settings.Username}, - Description: "Creating kubectl context - setting context", - } + cmd = kubectl([]string{"config", "set-context", s.Settings.KubeContext, "--cluster=" + s.Settings.KubeContext, "--user=" + s.Settings.Username}, "Creating kubectl context - setting context") if exitCode, err, _ := cmd.exec(debug, verbose); exitCode != 0 { return false, "failed to create context [ " + s.Settings.KubeContext + " ]: " + err @@ -265,11 +233,7 @@ func setKubeContext(context string) bool { return getKubeContext() } - cmd := command{ - Cmd: "kubectl", - Args: []string{"config", "use-context", context}, - Description: "Setting kube context to [ " + context + " ]", - } + cmd := kubectl([]string{"config", "use-context", context}, "Setting kube context to [ "+context+" ]") exitCode, _, _ := cmd.exec(debug, verbose) @@ -284,11 +248,7 @@ func setKubeContext(context string) bool { // getKubeContext gets your kubectl context. // It returns false if no context is set. func getKubeContext() bool { - cmd := command{ - Cmd: "kubectl", - Args: []string{"config", "current-context"}, - Description: "Getting kubectl context", - } + cmd := kubectl([]string{"config", "current-context"}, "Getting kubectl context") exitCode, result, _ := cmd.exec(debug, verbose) @@ -305,11 +265,7 @@ func labelResource(r *release) { if r.Enabled { storageBackend := s.Settings.StorageBackend - cmd := command{ - Cmd: "kubectl", - Args: []string{"label", storageBackend, "-n", r.Namespace, "-l", "owner=helm,name=" + r.Name, "MANAGED-BY=HELMSMAN", "NAMESPACE=" + r.Namespace, "HELMSMAN_CONTEXT=" + s.Context, "--overwrite"}, - Description: "Applying Helmsman labels to [ " + r.Name + " ] release", - } + cmd := kubectl([]string{"label", storageBackend, "-n", r.Namespace, "-l", "owner=helm,name=" + r.Name, "MANAGED-BY=HELMSMAN", "NAMESPACE=" + r.Namespace, "HELMSMAN_CONTEXT=" + s.Context, "--overwrite"}, "Applying Helmsman labels to [ "+r.Name+" ] release") exitCode, err, _ := cmd.exec(debug, verbose) @@ -324,27 +280,19 @@ func getReleaseContext(releaseName string, namespace string) string { storageBackend := s.Settings.StorageBackend // kubectl get secrets -n test1 -l MANAGED-BY=HELMSMAN -o=jsonpath='{.items[0].metadata.labels.HELMSMAN_CONTEXT}' // kubectl get secret sh.helm.release.v1.argo.v1 -n test1 -o=jsonpath='{.metadata.labels.HELMSMAN_CONTEXT}' - cmd := command{ - Cmd: "kubectl", - Args: []string{"get", storageBackend, "-n", namespace, "sh.helm.release.v1." + releaseName + ".v1", "-o=jsonpath={.metadata.labels.HELMSMAN_CONTEXT}"}, - Description: "Getting Helmsman context for [ " + releaseName + " ] release", - } + // kubectl get secret -l owner=helm,name=argo -n test1 -o=jsonpath='{.items[-1].metadata.labels.HELMSMAN_CONTEXT}' + cmd := kubectl([]string{"get", storageBackend, "-n", namespace, "-l", "owner=helm", "-l", "name=" + releaseName, "-o", "jsonpath='{.items[-1].metadata.labels.HELMSMAN_CONTEXT}'"}, "Getting Helmsman context for [ "+releaseName+" ] release") exitCode, out, _ := cmd.exec(debug, verbose) - if exitCode != 0 { log.Fatal(out) } - return strings.TrimSpace(out) + return strings.Trim(out, `"' `) } // getKubectlClientVersion returns kubectl client version func getKubectlClientVersion() string { - cmd := command{ - Cmd: "kubectl", - Args: []string{"version", "--client", "--short"}, - Description: "Checking kubectl version", - } + cmd := kubectl([]string{"version", "--client", "--short"}, "Checking kubectl version") exitCode, result, _ := cmd.exec(debug, false) if exitCode != 0 { diff --git a/internal/app/main.go b/internal/app/main.go index 891050d5..b1b6f703 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -60,6 +60,7 @@ var noDefaultRepos bool var settings config func init() { + // Parse cli flags and read config files Cli() } @@ -90,6 +91,7 @@ func Main() { } if !skipValidation { + log.Info("Validating charts...") // validate charts-versions exist in defined repos if err := validateReleaseCharts(s.Apps); err != nil { log.Fatal(err.Error()) @@ -109,12 +111,15 @@ func Main() { cs.cleanUntrackedReleases() } - p.sortPlan() - p.printPlan() - p.sendPlanToSlack() + p.sort() + p.print() + if debug { + p.printCmds() + } + p.sendToSlack() if apply || dryRun || destroy { - p.execPlan() + p.exec() } } diff --git a/internal/app/namespace.go b/internal/app/namespace.go index 636f4477..783d7924 100644 --- a/internal/app/namespace.go +++ b/internal/app/namespace.go @@ -28,12 +28,6 @@ type namespace struct { Annotations map[string]string `yaml:"annotations"` } -// checkNamespaceDefined checks if a given namespace is defined in the namespaces section of the desired state file -func checkNamespaceDefined(ns string, s state) bool { - _, ok := s.Namespaces[ns] - return ok -} - // print prints the namespace func (n namespace) print() { fmt.Println("") diff --git a/internal/app/plan.go b/internal/app/plan.go index 6913d5dd..f316f271 100644 --- a/internal/app/plan.go +++ b/internal/app/plan.go @@ -74,8 +74,8 @@ func (p *plan) addDecision(decision string, priority int, decisionType decisionT } // execPlan executes the commands (actions) which were added to the plan. -func (p plan) execPlan() { - p.sortPlan() +func (p plan) exec() { + p.sort() if len(p.Commands) > 0 { log.Info("Executing plan... ") } else { @@ -108,15 +108,15 @@ func (p plan) execPlan() { } // printPlanCmds prints the actual commands that will be executed as part of a plan. -func (p plan) printPlanCmds() { - fmt.Println("Printing the commands of the current plan ...") +func (p plan) printCmds() { + log.Info("Printing the commands of the current plan ...") for _, cmd := range p.Commands { - fmt.Println(cmd.Command.Args[1]) + fmt.Println(cmd.Command.String()) } } // printPlan prints the decisions made in a plan. -func (p plan) printPlan() { +func (p plan) print() { log.Notice("-------- PLAN starts here --------------") for _, decision := range p.Decisions { if decision.Type == ignored { @@ -133,7 +133,7 @@ func (p plan) printPlan() { } // sendPlanToSlack sends the description of plan commands to slack if a webhook is provided. -func (p plan) sendPlanToSlack() { +func (p plan) sendToSlack() { if _, err := url.ParseRequestURI(s.Settings.SlackWebhook); err == nil { str := "" for _, c := range p.Commands { @@ -147,7 +147,7 @@ func (p plan) sendPlanToSlack() { // sortPlan sorts the slices of commands and decisions based on priorities // the lower the priority value the earlier a command should be attempted -func (p plan) sortPlan() { +func (p plan) sort() { log.Verbose("Sorting the commands in the plan based on priorities (order flags) ... ") sort.SliceStable(p.Commands, func(i, j int) bool { diff --git a/internal/app/release.go b/internal/app/release.go index 4e7b3c68..082d7547 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -43,6 +43,10 @@ type chartVersion struct { Description string `json:"description"` } +func (r *release) key() string { + return fmt.Sprintf("%s-%s", r.Name, r.Namespace) +} + // isReleaseConsideredToRun checks if a release is being targeted for operations as specified by user cmd flags (--group or --target) func (r *release) isConsideredToRun() bool { if len(targetMap) > 0 { @@ -62,7 +66,7 @@ func (r *release) isConsideredToRun() bool { // validateRelease validates if a release inside a desired state meets the specifications or not. // check the full specification @ https://github.com/Praqma/helmsman/docs/desired_state_spec.md -func (r *release) validate(appLabel string, names map[string]map[string]bool, s state) (bool, string) { +func (r *release) validate(appLabel string, names map[string]map[string]bool, s *state) (bool, string) { if r.Name == "" { r.Name = appLabel } @@ -73,7 +77,7 @@ func (r *release) validate(appLabel string, names map[string]map[string]bool, s if nsOverride == "" && r.Namespace == "" { return false, "release targeted namespace can't be empty." - } else if nsOverride == "" && r.Namespace != "" && r.Namespace != "kube-system" && !checkNamespaceDefined(r.Namespace, s) { + } else if nsOverride == "" && r.Namespace != "" && r.Namespace != "kube-system" && !s.checkNamespaceDefined(r.Namespace) { return false, "release " + r.Name + " is using namespace [ " + r.Namespace + " ] which is not defined in the Namespaces section of your desired state file." + " Release [ " + r.Name + " ] can't be installed in that Namespace until its defined." } @@ -164,11 +168,7 @@ func (r *release) validateChart(app string, wg *sync.WaitGroup, c chan string) { } if validateCurrentChart { if isLocalChart(r.Chart) { - cmd := command{ - Cmd: helmBin, - Args: []string{"inspect", "chart", r.Chart}, - Description: "Validating [ " + r.Chart + " ] chart's availability", - } + cmd := helmCmd([]string{"inspect", "chart", r.Chart}, "Validating [ "+r.Chart+" ] chart's availability") var output string var exitCode int @@ -192,11 +192,7 @@ func (r *release) validateChart(app string, wg *sync.WaitGroup, c chan string) { if len(version) == 0 { version = "*" } - cmd := command{ - Cmd: helmBin, - Args: []string{"search", "repo", r.Chart, "--version", version, "-l"}, - Description: "Validating [ " + r.Chart + " ] chart's version [ " + r.Version + " ] availability", - } + cmd := helmCmd([]string{"search", "repo", r.Chart, "--version", version, "-l"}, "Validating [ "+r.Chart+" ] chart's version [ "+r.Version+" ] availability") if exitCode, result, _ := cmd.exec(debug, verbose); exitCode != 0 || strings.Contains(result, "No results found") { c <- "Chart [ " + r.Chart + " ] with version [ " + r.Version + " ] is specified for " + @@ -213,11 +209,7 @@ func (r *release) getChartVersion() (string, string) { if isLocalChart(r.Chart) { return r.Version, "" } - cmd := command{ - Cmd: helmBin, - Args: []string{"search", "repo", r.Chart, "--version", r.Version, "-o", "json"}, - Description: "Getting latest chart's version " + r.Chart + "-" + r.Version + "", - } + cmd := helmCmd([]string{"search", "repo", r.Chart, "--version", r.Version, "-o", "json"}, "Getting latest chart's version "+r.Chart+"-"+r.Version+"") var ( exitCode int @@ -243,22 +235,14 @@ func (r *release) getChartVersion() (string, string) { // testRelease creates a Helm command to test a particular release. func (r *release) test() { - cmd := command{ - Cmd: helmBin, - Args: []string{"test", "--namespace", r.Namespace, r.Name}, - Description: "Running tests for release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ]", - } + cmd := helmCmd(r.getHelmArgsFor("test"), "Running tests for release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") outcome.addCommand(cmd, r.Priority, r) logDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is required to be tested during installation", r.Priority, noop) } // installRelease creates a Helm command to install a particular release in a particular namespace using a particular Tiller. func (r *release) install() { - cmd := command{ - Cmd: helmBin, - Args: concat([]string{"install", r.Name, r.Chart, "--namespace", r.Namespace}, r.getValuesFiles(), []string{"--version", r.Version}, r.getSetValues(), r.getSetStringValues(), r.getWait(), r.getHelmFlags()), - Description: "Installing release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ]", - } + cmd := helmCmd(r.getHelmArgsFor("install"), "Installing release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") outcome.addCommand(cmd, r.Priority, r) logDecision("Release [ "+r.Name+" ] will be installed in [ "+r.Namespace+" ] namespace", r.Priority, create) @@ -274,11 +258,7 @@ func (r *release) uninstall() { priority = priority * -1 } - cmd := command{ - Cmd: helmBin, - Args: concat([]string{"uninstall", "--namespace", r.Namespace, r.Name}, getDryRunFlags()), - Description: "Deleting release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ]", - } + cmd := helmCmd(concat(r.getHelmArgsFor("uninstall"), getDryRunFlags()), "Deleting release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") outcome.addCommand(cmd, priority, r) logDecision(fmt.Sprintf("release [ %s ] is desired to be DELETED.", r.Name), r.Priority, delete) } @@ -300,11 +280,7 @@ func (r *release) diff() string { suppressDiffSecretsFlag = "--suppress-secrets" } - cmd := command{ - Cmd: helmBin, - Args: concat([]string{"diff", colorFlag}, diffContextFlag, []string{suppressDiffSecretsFlag, "--namespace", r.Namespace, "upgrade", r.Name, r.Chart}, r.getValuesFiles(), []string{"--version", r.Version}, r.getSetValues(), r.getSetStringValues()), - Description: "Diffing release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ]", - } + cmd := helmCmd(concat([]string{"diff", colorFlag, suppressDiffSecretsFlag}, diffContextFlag, r.getHelmArgsFor("upgrade")), "Diffing release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") if exitCode, msg, _ = cmd.exec(debug, verbose); exitCode != 0 { log.Fatal(fmt.Sprintf("Command returned with exit code: %d. And error message: %s ", exitCode, msg)) @@ -323,11 +299,7 @@ func (r *release) upgrade() { if forceUpgrades { force = "--force" } - cmd := command{ - Cmd: helmBin, - Args: concat([]string{"upgrade", "--namespace", r.Namespace, r.Name, r.Chart}, r.getValuesFiles(), []string{"--version", r.Version, force}, r.getSetValues(), r.getSetStringValues(), r.getWait(), r.getHelmFlags()), - Description: "Upgrading release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ]", - } + cmd := helmCmd(concat(r.getHelmArgsFor("upgrade"), []string{force}, r.getWait(), r.getHelmFlags()), "Upgrading release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") outcome.addCommand(cmd, r.Priority, r) } @@ -336,37 +308,25 @@ func (r *release) upgrade() { // This is used when moving a release to another namespace or when changing the chart used for it. func (r *release) reInstall(rs helmRelease) { - delCmd := command{ - Cmd: helmBin, - Args: concat([]string{"delete", "--purge", r.Name}, getDryRunFlags()), - Description: "Deleting release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ]", - } + delCmd := helmCmd(concat([]string{"delete", "--purge", r.Name}, getDryRunFlags()), "Deleting release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") outcome.addCommand(delCmd, r.Priority, r) - installCmd := command{ - Cmd: helmBin, - Args: concat([]string{"install", r.Chart, "--version", r.Version, "-n", r.Name, "--namespace", r.Namespace}, r.getValuesFiles(), r.getSetValues(), r.getSetStringValues(), r.getWait(), r.getHelmFlags()), - Description: "Installing release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ]", - } + installCmd := helmCmd(r.getHelmArgsFor("install"), "Installing release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") outcome.addCommand(installCmd, r.Priority, r) } // rollbackRelease evaluates if a rollback action needs to be taken for a given release. // if the release is already deleted but from a different namespace than the one specified in input, // it purge deletes it and create it in the specified namespace. -func (r *release) rollback(cs *currentState) { - rs, ok := (*cs)[fmt.Sprintf("%s-%s", r.Name, r.Namespace)] +func (r *release) rollback(cs currentState) { + rs, ok := cs[r.key()] if !ok { return } if r.Namespace == rs.Namespace { - cmd := command{ - Cmd: helmBin, - Args: concat([]string{"rollback", r.Name, rs.getRevision()}, r.getWait(), r.getTimeout(), r.getNoHooks(), getDryRunFlags()), - Description: "Rolling back release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ]", - } + cmd := helmCmd(concat([]string{"rollback", r.Name, rs.getRevision()}, r.getWait(), r.getTimeout(), r.getNoHooks(), getDryRunFlags()), "Rolling back release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") outcome.addCommand(cmd, r.Priority, r) r.upgrade() // this is to reflect any changes in values file(s) logDecision("Release [ "+r.Name+" ] was deleted and is desired to be rolled back to "+ @@ -382,6 +342,21 @@ func (r *release) rollback(cs *currentState) { } } +// isProtected checks if a release is protected or not. +// A protected is release is either: a) deployed in a protected namespace b) flagged as protected in the desired state file +// Any release in a protected namespace is protected by default regardless of its flag +// returns true if a release is protected, false otherwise +func (r *release) isProtected(cs currentState) bool { + // if the release does not exist in the cluster, it is not protected + if ok := cs.releaseExists(r, ""); !ok { + return false + } + if s.Namespaces[r.Namespace].Protected || r.Protected { + return true + } + return false +} + // getNoHooks returns the no-hooks flag for install/upgrade commands func (r *release) getNoHooks() []string { if r.NoHooks { @@ -481,6 +456,17 @@ func (r *release) getHelmFlags() []string { return concat(r.getNoHooks(), r.getTimeout(), getDryRunFlags(), flags) } +func (r *release) getHelmArgsFor(action string) []string { + switch action { + case "install": + return concat([]string{action, r.Name, r.Chart, "--version", r.Version, "--namespace", r.Namespace}, r.getValuesFiles(), r.getSetValues(), r.getSetStringValues(), r.getWait(), r.getHelmFlags()) + case "upgrade": + return concat([]string{action, "--namespace", r.Namespace, r.Name, r.Chart}, r.getValuesFiles(), []string{"--version", r.Version}, r.getSetValues(), r.getSetStringValues()) + default: + return []string{action, "--namespace", r.Namespace, r.Name} + } +} + func (r *release) checkChartDepUpdate() { if updateDeps && isLocalChart(r.Chart) { if ok, err := updateChartDep(r.Chart); !ok { diff --git a/internal/app/release_test.go b/internal/app/release_test.go index 1af9d055..4cb72e7d 100644 --- a/internal/app/release_test.go +++ b/internal/app/release_test.go @@ -11,11 +11,7 @@ func setupTestCase(t *testing.T) func(t *testing.T) { t.Log("setup test case") os.MkdirAll(os.TempDir()+"/helmsman-tests/myapp", os.ModePerm) os.MkdirAll(os.TempDir()+"/helmsman-tests/dir-with space/myapp", os.ModePerm) - cmd := command{ - Cmd: helmBin, - Args: []string{"create", os.TempDir() + "/helmsman-tests/dir-with space/myapp"}, - Description: "creating an empty local chart directory", - } + cmd := helmCmd([]string{"create", os.TempDir() + "/helmsman-tests/dir-with space/myapp"}, "creating an empty local chart directory") if exitCode, msg, _ := cmd.exec(debug, verbose); exitCode != 0 { log.Fatal(fmt.Sprintf("Command returned with exit code: %d. And error message: %s ", exitCode, msg)) } @@ -291,7 +287,7 @@ func Test_validateRelease(t *testing.T) { names := make(map[string]map[string]bool) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, got1 := tt.args.r.validate("testApp", names, tt.args.s) + got, got1 := tt.args.r.validate("testApp", names, &tt.args.s) if got != tt.want { t.Errorf("validateRelease() got = %v, want %v", got, tt.want) } diff --git a/internal/app/state.go b/internal/app/state.go index 5f7832b2..0992ec12 100644 --- a/internal/app/state.go +++ b/internal/app/state.go @@ -38,9 +38,30 @@ type state struct { AppsTemplates map[string]*release `yaml:"appsTemplates,omitempty"` } +// invokes either yaml or toml parser considering file extension +func (s *state) fromFile(file string) (bool, string) { + if isOfType(file, []string{".toml"}) { + return fromTOML(file, s) + } else if isOfType(file, []string{".yaml", ".yml"}) { + return fromYAML(file, s) + } else { + return false, "State file does not have toml/yaml extension." + } +} + +func (s *state) toFile(file string) { + if isOfType(file, []string{".toml"}) { + toTOML(file, s) + } else if isOfType(file, []string{".yaml", ".yml"}) { + toYAML(file, s) + } else { + log.Fatal("State file does not have toml/yaml extension.") + } +} + // validate validates that the values specified in the desired state are valid according to the desired state spec. // check https://github.com/Praqma/Helmsman/docs/desired_state_spec.md for the detailed specification -func (s state) validate() error { +func (s *state) validate() error { // settings if (s.Settings == (config{}) || s.Settings.KubeContext == "") && !getKubeContext() { @@ -158,8 +179,22 @@ func isValidCert(value string) (bool, string) { return true, value } +// checkNamespaceDefined checks if a given namespace is defined in the namespaces section of the desired state file +func (s *state) checkNamespaceDefined(ns string) bool { + _, ok := s.Namespaces[ns] + return ok +} + +// overrideAppsNamespace replaces all apps namespaces with one specific namespace +func (s *state) overrideAppsNamespace(newNs string) { + log.Info("Overriding apps namespaces with [ " + newNs + " ] ...") + for _, r := range s.Apps { + r.overrideNamespace(newNs) + } +} + // print prints the desired state -func (s state) print() { +func (s *state) print() { fmt.Println("\nMetadata: ") fmt.Println("--------- ") diff --git a/internal/app/utils.go b/internal/app/utils.go index c7633124..26774550 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -189,27 +189,6 @@ func substituteVarsInYaml(file string) string { return outFile } -// invokes either yaml or toml parser considering file extension -func fromFile(file string, s *state) (bool, string) { - if isOfType(file, []string{".toml"}) { - return fromTOML(file, s) - } else if isOfType(file, []string{".yaml", ".yml"}) { - return fromYAML(file, s) - } else { - return false, "State file does not have toml/yaml extension." - } -} - -func toFile(file string, s *state) { - if isOfType(file, []string{".toml"}) { - toTOML(file, s) - } else if isOfType(file, []string{".yaml", ".yml"}) { - toYAML(file, s) - } else { - log.Fatal("State file does not have toml/yaml extension.") - } -} - func stringInSlice(a string, list []string) bool { for _, b := range list { if b == a { @@ -535,7 +514,7 @@ func writeStringToFile(filename string, data string) error { return file.Sync() } -// decrypt a eyaml secret file +// decrypt a eyaml or helm secret file func decryptSecret(name string) error { cmd := helmBin args := []string{"secrets", "dec", name} From de897d90c032b58e1430c5a34da4c55f535e071c Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sun, 29 Dec 2019 13:25:42 +0000 Subject: [PATCH 0549/1127] preparing new release --- README.md | 6 +++--- docs/best_practice.md | 2 +- docs/cmd_reference.md | 2 +- docs/deployment_strategies.md | 4 ++-- docs/desired_state_specification.md | 2 +- docs/how_to/apps/basic.md | 2 +- docs/how_to/apps/destroy.md | 2 +- docs/how_to/apps/helm_tests.md | 2 +- docs/how_to/apps/moving_across_namespaces.md | 2 +- docs/how_to/apps/order.md | 2 +- docs/how_to/apps/override_namespaces.md | 2 +- docs/how_to/apps/secrets.md | 2 +- docs/how_to/deployments/ci.md | 4 ++-- docs/how_to/helm_repos/default.md | 4 ++-- docs/how_to/misc/merge_desired_state_files.md | 4 ++-- examples/example.toml | 2 +- examples/example.yaml | 2 +- internal/app/main.go | 2 +- release-notes.md | 2 +- 19 files changed, 25 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index d9fe7114..567d3255 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.0.0-beta2&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.0.0-beta3&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -61,9 +61,9 @@ If you use private helm repos, you will need either `helm-gcs` or `helm-s3` plug Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. ``` # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.0.0-beta2/helmsman_3.0.0-beta1_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.0.0-beta3/helmsman_3.0.0-beta1_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.0.0-beta2/helmsman_3.0.0-beta1_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.0.0-beta3/helmsman_3.0.0-beta1_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/docs/best_practice.md b/docs/best_practice.md index ec49e90f..faf71be1 100644 --- a/docs/best_practice.md +++ b/docs/best_practice.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta2 +version: v3.0.0-beta3 --- # Best Practice diff --git a/docs/cmd_reference.md b/docs/cmd_reference.md index 87854d48..66d729bf 100644 --- a/docs/cmd_reference.md +++ b/docs/cmd_reference.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta2 +version: v3.0.0-beta3 --- # CMD reference diff --git a/docs/deployment_strategies.md b/docs/deployment_strategies.md index 42d1c148..cfb02f7c 100644 --- a/docs/deployment_strategies.md +++ b/docs/deployment_strategies.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta2 +version: v3.0.0-beta3 --- # Deployment Strategies @@ -135,7 +135,7 @@ If you need supporting applications (charts) for your application (e.g, reverse ## Notes on using multiple Helmsman desired state files for the same cluster -Helmsman v3.0.0-beta2 introduces the `context` stanza. +Helmsman v3.0.0-beta3 introduces the `context` stanza. When having multiple DSFs operating on different releases, it is essential to use the `context` stanza in each DSF to define what context the DSF covers. The user-provided value for `context` is used by Helmsman to label and distinguish which DSF manages which deployed releases in the cluster. This way, each helmsman operation will only operate on releases within the context defined in the DSF. When having multiple DSFs be aware of the following: diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index dba344d3..4e0957fd 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta2 +version: v3.0.0-beta3 --- # Helmsman desired state specification diff --git a/docs/how_to/apps/basic.md b/docs/how_to/apps/basic.md index 677b6782..1aebcdde 100644 --- a/docs/how_to/apps/basic.md +++ b/docs/how_to/apps/basic.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta2 +version: v3.0.0-beta3 --- # Install releases diff --git a/docs/how_to/apps/destroy.md b/docs/how_to/apps/destroy.md index f6d2d94e..8c211d7f 100644 --- a/docs/how_to/apps/destroy.md +++ b/docs/how_to/apps/destroy.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta2 +version: v3.0.0-beta3 --- # Delete all deployed releases diff --git a/docs/how_to/apps/helm_tests.md b/docs/how_to/apps/helm_tests.md index a15a77fa..c9f9b0a4 100644 --- a/docs/how_to/apps/helm_tests.md +++ b/docs/how_to/apps/helm_tests.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta2 +version: v3.0.0-beta3 --- # Test charts diff --git a/docs/how_to/apps/moving_across_namespaces.md b/docs/how_to/apps/moving_across_namespaces.md index 2cae21e5..aeefc664 100644 --- a/docs/how_to/apps/moving_across_namespaces.md +++ b/docs/how_to/apps/moving_across_namespaces.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta2 +version: v3.0.0-beta3 --- # Move charts across namespaces diff --git a/docs/how_to/apps/order.md b/docs/how_to/apps/order.md index c705bc9b..954fb4b3 100644 --- a/docs/how_to/apps/order.md +++ b/docs/how_to/apps/order.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta2 +version: v3.0.0-beta3 --- # Using the priority key for Apps diff --git a/docs/how_to/apps/override_namespaces.md b/docs/how_to/apps/override_namespaces.md index 8ee6a81e..577db438 100644 --- a/docs/how_to/apps/override_namespaces.md +++ b/docs/how_to/apps/override_namespaces.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta2 +version: v3.0.0-beta3 --- # Override defined namespaces from command line diff --git a/docs/how_to/apps/secrets.md b/docs/how_to/apps/secrets.md index d410f577..932b91fa 100644 --- a/docs/how_to/apps/secrets.md +++ b/docs/how_to/apps/secrets.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta2 +version: v3.0.0-beta3 --- # Passing secrets from env variables: diff --git a/docs/how_to/deployments/ci.md b/docs/how_to/deployments/ci.md index 249f0389..a498ada8 100644 --- a/docs/how_to/deployments/ci.md +++ b/docs/how_to/deployments/ci.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta2 +version: v3.0.0-beta3 --- # Run Helmsman in CI @@ -13,7 +13,7 @@ jobs: deploy-apps: docker: - - image: praqma/helmsman:v3.0.0-beta2 + - image: praqma/helmsman:v3.0.0-beta3 steps: - checkout - run: diff --git a/docs/how_to/helm_repos/default.md b/docs/how_to/helm_repos/default.md index 04cc4571..a69de620 100644 --- a/docs/how_to/helm_repos/default.md +++ b/docs/how_to/helm_repos/default.md @@ -1,10 +1,10 @@ --- -version: v3.0.0-beta2 +version: v3.0.0-beta3 --- # Default helm repos -Helm v3 no longer adds the `stable` and `incubator` repos by default. However, Helmsman v3.0.0-beta2 still adds these two repos by default. These two DO NOT need to be defined explicitly in your desired state file (DSF). However, if you would like to configure some repo with the name stable for example, you can override the default repo. +Helm v3 no longer adds the `stable` and `incubator` repos by default. However, Helmsman v3.0.0-beta3 still adds these two repos by default. These two DO NOT need to be defined explicitly in your desired state file (DSF). However, if you would like to configure some repo with the name stable for example, you can override the default repo. > You can disable the automatic addition of these two repos, use the `--no-default-repos` flag. diff --git a/docs/how_to/misc/merge_desired_state_files.md b/docs/how_to/misc/merge_desired_state_files.md index 971b1b99..44e0c939 100644 --- a/docs/how_to/misc/merge_desired_state_files.md +++ b/docs/how_to/misc/merge_desired_state_files.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta2 +version: v3.0.0-beta3 --- # Supply multiple desired state files @@ -47,7 +47,7 @@ $ helmsman -f common.toml -f nonprod.toml ... When using multiple DSFs -and since Helmsman doesn't maintain any external state-, it has been possible for operations from one DSF to cause problems to releases deployed by other DSFs. A typical example is that releases deployed by other DSFs are considered `untracked` and get scheduled for deleting. Workarounds existed (e.g. using the `--keep-untracked-releases`, `--target` and `--group` flags). -Starting from Helmsman v3.0.0-beta2, `context` is introduced to define the context in which a DSF is used. This context is used as the ID of that specific DSF and must be unique across the used DSFs. The context is then used to label the different releases to link them to the DSF they were first deployed from. These labels are then checked by Helmsman on each run to make sure operations are limited to releases from a specific context. +Starting from Helmsman v3.0.0-beta3, `context` is introduced to define the context in which a DSF is used. This context is used as the ID of that specific DSF and must be unique across the used DSFs. The context is then used to label the different releases to link them to the DSF they were first deployed from. These labels are then checked by Helmsman on each run to make sure operations are limited to releases from a specific context. Here is how it is used: diff --git a/examples/example.toml b/examples/example.toml index 3a94c332..6dd02b0e 100644 --- a/examples/example.toml +++ b/examples/example.toml @@ -1,4 +1,4 @@ -# version: v3.0.0-beta2 +# version: v3.0.0-beta3 # context defines the context of this Desired State File. # It is used to allow Helmsman identify which releases are managed by which DSF. diff --git a/examples/example.yaml b/examples/example.yaml index fed8fc7c..4a4830a0 100644 --- a/examples/example.yaml +++ b/examples/example.yaml @@ -1,4 +1,4 @@ -# version: v3.0.0-beta2 +# version: v3.0.0-beta3 # metadata -- add as many key/value pairs as you want metadata: org: "example.com/$ORG_PATH/" diff --git a/internal/app/main.go b/internal/app/main.go index b1b6f703..a1e29391 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -16,7 +16,7 @@ func (i *stringArray) Set(value string) error { return nil } -const appVersion = "v3.0.0-beta2" +const appVersion = "v3.0.0-beta3" const tempFilesDir = ".helmsman-tmp" const stableHelmRepo = "https://kubernetes-charts.storage.googleapis.com" const incubatorHelmRepo = "http://storage.googleapis.com/kubernetes-charts-incubator" diff --git a/release-notes.md b/release-notes.md index 66731b64..bd8dbdf8 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,4 +1,4 @@ -# v3.0.0-beta2 +# v3.0.0-beta3 This is a major release to support Helm v3. It is recommended you read the [Helm 3 migration guide](https://helm.sh/docs/topics/v2_v3_migration/) before using this release. From 88ce10fa24e5aca7384d946d10cd6812240bcdd1 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sun, 29 Dec 2019 13:30:25 +0000 Subject: [PATCH 0550/1127] fix README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 567d3255..a5813a12 100644 --- a/README.md +++ b/README.md @@ -61,9 +61,9 @@ If you use private helm repos, you will need either `helm-gcs` or `helm-s3` plug Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. ``` # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.0.0-beta3/helmsman_3.0.0-beta1_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.0.0-beta3/helmsman_3.0.0-beta3_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.0.0-beta3/helmsman_3.0.0-beta1_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.0.0-beta3/helmsman_3.0.0-beta3_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` From c2091de3fccac12bf3036d809789838761763b80 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Mon, 30 Dec 2019 13:05:21 +0000 Subject: [PATCH 0551/1127] code cleanup --- README.md | 2 +- internal/app/cli.go | 219 +++++++++++++++++----------- internal/app/cli_test.go | 95 +----------- internal/app/command.go | 36 +++-- internal/app/command_test.go | 65 +-------- internal/app/decision_maker.go | 171 ++++++++++++---------- internal/app/decision_maker_test.go | 29 ++-- internal/app/helm_helpers.go | 39 ++--- internal/app/helm_release.go | 16 +- internal/app/kube_helpers.go | 136 ++++++++--------- internal/app/logging.go | 13 +- internal/app/main.go | 116 ++++++--------- internal/app/plan.go | 44 +++--- internal/app/plan_test.go | 6 +- internal/app/release.go | 167 ++++++++++----------- internal/app/release_test.go | 74 ++++------ internal/app/state.go | 44 ++++-- internal/app/utils.go | 36 ++--- 18 files changed, 600 insertions(+), 708 deletions(-) diff --git a/README.md b/README.md index a5813a12..07c2f107 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ To limit execution to specific application: - **Built for CD**: Helmsman can be used as a docker image or a binary. - **Applications as code**: describe your desired applications and manage them from a single version-controlled declarative file. -- **Suitable for Multitenant Clusters**: deploy Tiller in different namespaces with service accounts and TLS. +- **Suitable for Multitenant Clusters**: deploy Tiller in different namespaces with service accounts and TLS (versions 1.x). - **Easy to use**: deep knowledge of Helm CLI and Kubectl is NOT mandatory to use Helmsman. - **Plan, View, apply**: you can run Helmsman to generate and view a plan with/without executing it. - **Portable**: Helmsman can be used to manage charts deployments on any k8s cluster. diff --git a/internal/app/cli.go b/internal/app/cli.go index a28b74db..ac71a538 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -22,6 +22,49 @@ const ( slogan = "A Helm-Charts-as-Code tool.\n\n" ) +// Allow parsing of multiple string command line options into an array of strings +type stringArray []string + +func (i *stringArray) String() string { + return strings.Join(*i, " ") +} + +func (i *stringArray) Set(value string) error { + *i = append(*i, value) + return nil +} + +type cli struct { + debug bool + files stringArray + envFiles stringArray + target stringArray + group stringArray + kubeconfig string + apply bool + destroy bool + dryRun bool + verbose bool + noBanner bool + noColors bool + noFancy bool + noNs bool + nsOverride string + skipValidation bool + keepUntrackedReleases bool + showDiff bool + suppressDiffSecrets bool + diffContext int + noEnvSubst bool + noEnvValuesSubst bool + noSSMSubst bool + noSSMValuesSubst bool + updateDeps bool + forceUpgrades bool + noDefaultRepos bool + version bool +} + func printUsage() { fmt.Print(banner) fmt.Printf("Helmsman version: " + appVersion + "\n") @@ -32,71 +75,68 @@ func printUsage() { } // Cli parses cmd flags, validates them and performs some initializations -func Cli() { +func (c *cli) parse() { //parsing command line flags - flag.Var(&files, "f", "desired state file name(s), may be supplied more than once to merge state files") - flag.Var(&envFiles, "e", "file(s) to load environment variables from (default .env), may be supplied more than once") - flag.StringVar(&kubeconfig, "kubeconfig", "", "path to the kubeconfig file to use for CLI requests") - flag.BoolVar(&apply, "apply", false, "apply the plan directly") - flag.BoolVar(&debug, "debug", false, "show the execution logs") - flag.BoolVar(&dryRun, "dry-run", false, "apply the dry-run option for helm commands.") - flag.Var(&target, "target", "limit execution to specific app.") - flag.Var(&group, "group", "limit execution to specific group of apps.") - flag.BoolVar(&destroy, "destroy", false, "delete all deployed releases.") - flag.BoolVar(&v, "v", false, "show the version") - flag.BoolVar(&verbose, "verbose", false, "show verbose execution logs") - flag.BoolVar(&noBanner, "no-banner", false, "don't show the banner") - flag.BoolVar(&noColors, "no-color", false, "don't use colors") - flag.BoolVar(&noFancy, "no-fancy", false, "don't display the banner and don't use colors") - flag.BoolVar(&noNs, "no-ns", false, "don't create namespaces") - flag.StringVar(&nsOverride, "ns-override", "", "override defined namespaces with this one") - flag.BoolVar(&skipValidation, "skip-validation", false, "skip desired state validation") - flag.BoolVar(&keepUntrackedReleases, "keep-untracked-releases", false, "keep releases that are managed by Helmsman from the used DSFs in the command, and are no longer tracked in your desired state.") - flag.BoolVar(&showDiff, "show-diff", false, "show helm diff results. Can expose sensitive information.") - flag.BoolVar(&suppressDiffSecrets, "suppress-diff-secrets", true, "don't show secrets in helm diff output. (default true).") - flag.IntVar(&diffContext, "diff-context", -1, "number of lines of context to show around changes in helm diff output") - flag.BoolVar(&noEnvSubst, "no-env-subst", false, "turn off environment substitution globally") - flag.BoolVar(&noEnvValuesSubst, "no-env-values-subst", true, "turn off environment substitution in values files only. (default true).") - flag.BoolVar(&noSSMSubst, "no-ssm-subst", false, "turn off SSM parameter substitution globally") - flag.BoolVar(&noSSMValuesSubst, "no-ssm-values-subst", true, "turn off SSM parameter substitution in values files only") - flag.BoolVar(&updateDeps, "update-deps", false, "run 'helm dep up' for local chart") - flag.BoolVar(&forceUpgrades, "force-upgrades", false, "use --force when upgrading helm releases. May cause resources to be recreated.") - flag.BoolVar(&noDefaultRepos, "no-default-repos", false, "don't set default Helm repos from Google for 'stable' and 'incubator'") + flag.Var(&c.files, "f", "desired state file name(s), may be supplied more than once to merge state files") + flag.Var(&c.envFiles, "e", "file(s) to load environment variables from (default .env), may be supplied more than once") + flag.Var(&c.target, "target", "limit execution to specific app.") + flag.Var(&c.group, "group", "limit execution to specific group of apps.") + flag.IntVar(&c.diffContext, "diff-context", -1, "number of lines of context to show around changes in helm diff output") + flag.StringVar(&c.kubeconfig, "kubeconfig", "", "path to the kubeconfig file to use for CLI requests") + flag.StringVar(&c.nsOverride, "ns-override", "", "override defined namespaces with this one") + flag.BoolVar(&c.apply, "apply", false, "apply the plan directly") + flag.BoolVar(&c.dryRun, "dry-run", false, "apply the dry-run option for helm commands.") + flag.BoolVar(&c.destroy, "destroy", false, "delete all deployed releases.") + flag.BoolVar(&c.version, "v", false, "show the version") + flag.BoolVar(&c.debug, "debug", false, "show the execution logs") + flag.BoolVar(&c.verbose, "verbose", false, "show verbose execution logs") + flag.BoolVar(&c.noBanner, "no-banner", false, "don't show the banner") + flag.BoolVar(&c.noColors, "no-color", false, "don't use colors") + flag.BoolVar(&c.noFancy, "no-fancy", false, "don't display the banner and don't use colors") + flag.BoolVar(&c.noNs, "no-ns", false, "don't create namespaces") + flag.BoolVar(&c.skipValidation, "skip-validation", false, "skip desired state validation") + flag.BoolVar(&c.keepUntrackedReleases, "keep-untracked-releases", false, "keep releases that are managed by Helmsman from the used DSFs in the command, and are no longer tracked in your desired state.") + flag.BoolVar(&c.showDiff, "show-diff", false, "show helm diff results. Can expose sensitive information.") + flag.BoolVar(&c.suppressDiffSecrets, "suppress-diff-secrets", true, "don't show secrets in helm diff output. (default true).") + flag.BoolVar(&c.noEnvSubst, "no-env-subst", false, "turn off environment substitution globally") + flag.BoolVar(&c.noEnvValuesSubst, "no-env-values-subst", true, "turn off environment substitution in values files only. (default true).") + flag.BoolVar(&c.noSSMSubst, "no-ssm-subst", false, "turn off SSM parameter substitution globally") + flag.BoolVar(&c.noSSMValuesSubst, "no-ssm-values-subst", true, "turn off SSM parameter substitution in values files only") + flag.BoolVar(&c.updateDeps, "update-deps", false, "run 'helm dep up' for local chart") + flag.BoolVar(&c.forceUpgrades, "force-upgrades", false, "use --force when upgrading helm releases. May cause resources to be recreated.") + flag.BoolVar(&c.noDefaultRepos, "no-default-repos", false, "don't set default Helm repos from Google for 'stable' and 'incubator'") flag.Usage = printUsage flag.Parse() - if v { + if c.version { fmt.Println("Helmsman version: " + appVersion) os.Exit(0) } - if noFancy { - noColors = true - noBanner = true + if c.noFancy { + c.noColors = true + c.noBanner = true } - initLogs(verbose, noColors) + verbose := c.verbose || c.debug + initLogs(verbose, c.noColors) - if !noBanner { + if !c.noBanner { fmt.Printf("%s version: %s\n%s", banner, appVersion, slogan) } - if dryRun && apply { + if c.dryRun && c.apply { log.Fatal("--apply and --dry-run can't be used together.") } - if destroy && apply { + if c.destroy && c.apply { log.Fatal("--destroy and --apply can't be used together.") } - if len(target) > 0 && len(group) > 0 { + if len(c.target) > 0 && len(c.group) > 0 { log.Fatal("--target and --group can't be used together.") } - if (settings.EyamlPrivateKeyPath != "" && settings.EyamlPublicKeyPath == "") || (settings.EyamlPrivateKeyPath == "" && settings.EyamlPublicKeyPath != "") { - log.Fatal("both EyamlPrivateKeyPath and EyamlPublicKeyPath are required") - } - - helmVersion = strings.TrimSpace(getHelmVersion()) + helmVersion := strings.TrimSpace(getHelmVersion()) extractedHelmVersion := helmVersion if !strings.HasPrefix(helmVersion, "v") { extractedHelmVersion = strings.TrimSpace(strings.Split(helmVersion, ":")[1]) @@ -108,23 +148,23 @@ func Cli() { log.Fatal("this version of Helmsman does not work with helm releases older than 3.0.0") } - kubectlVersion = strings.TrimSpace(strings.SplitN(getKubectlClientVersion(), ": ", 2)[1]) + kubectlVersion := strings.TrimSpace(strings.SplitN(getKubectlClientVersion(), ": ", 2)[1]) log.Verbose("kubectl client version: " + kubectlVersion) - if len(files) == 0 { + if len(c.files) == 0 { log.Info("No desired state files provided.") os.Exit(0) } - if kubeconfig != "" { - os.Setenv("KUBECONFIG", kubeconfig) + if c.kubeconfig != "" { + os.Setenv("KUBECONFIG", c.kubeconfig) } - if !toolExists("kubectl", debug) { + if !toolExists("kubectl") { log.Fatal("kubectl is not installed/configured correctly. Aborting!") } - if !toolExists(helmBin, debug) { + if !toolExists(helmBin) { log.Fatal("" + helmBin + " is not installed/configured correctly. Aborting!") } @@ -132,8 +172,24 @@ func Cli() { log.Fatal("helm diff plugin is not installed/configured correctly. Aborting!") } + if !c.noEnvSubst { + log.Verbose("Substitution of env variables enabled") + if !c.noEnvValuesSubst { + log.Verbose("Substitution of env variables in values enabled") + } + } + if !c.noSSMSubst { + log.Verbose("Substitution of SSM variables enabled") + if !c.noSSMValuesSubst { + log.Verbose("Substitution of SSM variables in values enabled") + } + } +} + +// readState gets the desired state from files +func (c *cli) readState(s *state) { // read the env file - if len(envFiles) == 0 { + if len(c.envFiles) == 0 { if _, err := os.Stat(".env"); err == nil { err = godotenv.Load() if err != nil { @@ -142,7 +198,7 @@ func Cli() { } } - for _, e := range envFiles { + for _, e := range c.envFiles { err := godotenv.Load(e) if err != nil { log.Fatal("Error loading " + e + " env file") @@ -154,20 +210,8 @@ func Cli() { _ = os.MkdirAll(tempFilesDir, 0755) // read the TOML/YAML desired state file - if !noEnvSubst { - log.Verbose("Substitution of env variables enabled") - if !noEnvValuesSubst { - log.Verbose("Substitution of env variables in values enabled") - } - } - if !noSSMSubst { - log.Verbose("Substitution of SSM variables enabled") - if !noSSMValuesSubst { - log.Verbose("Substitution of SSM variables in values enabled") - } - } var fileState state - for _, f := range files { + for _, f := range c.files { result, msg := fileState.fromFile(f) if result { @@ -191,18 +235,33 @@ func Cli() { // All the apps are already merged, make fileState.Apps empty to avoid conflicts in the final merge fileState.Apps = make(map[string]*release) - if err := mergo.Merge(&s, &fileState, mergo.WithAppendSlice, mergo.WithOverride); err != nil { + if err := mergo.Merge(s, &fileState, mergo.WithAppendSlice, mergo.WithOverride); err != nil { log.Fatal("Failed to merge desired state file" + f) } } - if debug { + if len(c.target) > 0 { + s.TargetMap = map[string]bool{} + for _, v := range c.target { + s.TargetMap[v] = true + } + } + + if len(c.group) > 0 { + s.GroupMap = map[string]bool{} + for _, v := range c.group { + s.GroupMap[v] = true + } + } + + if c.debug { s.print() } - if !skipValidation { + if !c.skipValidation { // validate the desired state content - if len(files) > 0 { + if len(c.files) > 0 { + log.Info("Validating desired state definition...") if err := s.validate(); err != nil { // syntax validation log.Fatal(err.Error()) } @@ -213,10 +272,8 @@ func Cli() { if s.Settings.StorageBackend != "" { os.Setenv("HELM_DRIVER", s.Settings.StorageBackend) - } - - // set default storage background to secret if not set by user - if s.Settings.StorageBackend == "" { + } else { + // set default storage background to secret if not set by user s.Settings.StorageBackend = "secret" } @@ -224,25 +281,11 @@ func Cli() { if s.Context == "" { s.Context = defaultContextName } - - if len(target) > 0 { - targetMap = map[string]bool{} - for _, v := range target { - targetMap[v] = true - } - } - - if len(group) > 0 { - groupMap = map[string]bool{} - for _, v := range group { - groupMap[v] = true - } - } } // getDryRunFlags returns dry-run flag -func getDryRunFlags() []string { - if dryRun { +func (c *cli) getDryRunFlags() []string { + if c.dryRun { return []string{"--dry-run", "--debug"} } return []string{} diff --git a/internal/app/cli_test.go b/internal/app/cli_test.go index 25868c06..3e0f6c7d 100644 --- a/internal/app/cli_test.go +++ b/internal/app/cli_test.go @@ -38,102 +38,9 @@ func Test_toolExists(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := toolExists(tt.args.tool, false); got != tt.want { + if got := toolExists(tt.args.tool); got != tt.want { t.Errorf("toolExists() = %v, want %v", got, tt.want) } }) } } - -// func Test_addNamespaces(t *testing.T) { -// type args struct { -// namespaces map[string]string -// } -// tests := []struct { -// name string -// args args -// }{ -// // TODO: Add test cases. -// } -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// addNamespaces(tt.args.namespaces) -// }) -// } -// } - -// func Test_validateReleaseCharts(t *testing.T) { -// type args struct { -// apps map[string]release -// } -// tests := []struct { -// name string -// args args -// want bool -// }{ -// // TODO: Add test cases. -// } -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// if got := validateReleaseCharts(tt.args.apps); got != tt.want { -// t.Errorf("validateReleaseCharts() = %v, want %v", got, tt.want) -// } -// }) -// } -// } - -// func Test_addHelmRepos(t *testing.T) { -// type args struct { -// repos map[string]string -// } -// tests := []struct { -// name string -// args args -// want bool -// }{ -// // TODO: Add test cases. -// } -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// if got := addHelmRepos(tt.args.repos); got != tt.want { -// t.Errorf("addHelmRepos() = %v, want %v", got, tt.want) -// } -// }) -// } -// } - -// func Test_setKubeContext(t *testing.T) { -// type args struct { -// context string -// } -// tests := []struct { -// name string -// args args -// want bool -// }{ -// // TODO: Add test cases. -// } -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// if got := setKubeContext(tt.args.context); got != tt.want { -// t.Errorf("setKubeContext() = %v, want %v", got, tt.want) -// } -// }) -// } -// } - -// func Test_createContext(t *testing.T) { -// tests := []struct { -// name string -// want bool -// }{ -// // TODO: Add test cases. -// } -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// if got := createContext(); got != tt.want { -// t.Errorf("createContext() = %v, want %v", got, tt.want) -// } -// }) -// } -// } diff --git a/internal/app/command.go b/internal/app/command.go index 65fb2f66..9ddd6edc 100644 --- a/internal/app/command.go +++ b/internal/app/command.go @@ -2,7 +2,6 @@ package app import ( "bytes" - "fmt" "os/exec" "strings" "syscall" @@ -16,12 +15,18 @@ type command struct { Description string } +type exitStatus struct { + code int + errors string + output string +} + func (c *command) String() string { return c.Cmd + " " + strings.Join(c.Args, " ") } // exec executes the executable command and returns the exit code and execution result -func (c *command) exec(debug bool, verbose bool) (int, string, string) { +func (c *command) exec() exitStatus { // Only use non-empty string args args := []string{} for _, str := range c.Args { @@ -31,10 +36,8 @@ func (c *command) exec(debug bool, verbose bool) (int, string, string) { } log.Verbose(c.Description) + log.Debug(c.String()) - if debug { - log.Debug(fmt.Sprintf("%s %s", c.Cmd, strings.Join(args, " "))) - } cmd := exec.Command(c.Cmd, args...) var stdout, stderr bytes.Buffer cmd.Stdout = &stdout @@ -42,31 +45,42 @@ func (c *command) exec(debug bool, verbose bool) (int, string, string) { if err := cmd.Start(); err != nil { log.Info("cmd.Start: " + err.Error()) - return 1, err.Error(), "" + return exitStatus{ + code: 1, + errors: err.Error(), + } } if err := cmd.Wait(); err != nil { if exiterr, ok := err.(*exec.ExitError); ok { if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { - return status.ExitStatus(), stderr.String(), "" + return exitStatus{ + code: status.ExitStatus(), + output: stdout.String(), + errors: stderr.String(), + } } } else { log.Fatal("cmd.Wait: " + err.Error()) } } - return 0, stdout.String(), stderr.String() + return exitStatus{ + code: 0, + output: stdout.String(), + errors: stderr.String(), + } } // toolExists returns true if the tool is present in the environment and false otherwise. // It takes as input the tool's command to check if it is recognizable or not. e.g. helm or kubectl -func toolExists(tool string, debug bool) bool { +func toolExists(tool string) bool { cmd := command{ Cmd: tool, Args: []string{}, Description: "Validating that [ " + tool + " ] is installed", } - exitCode, _, _ := cmd.exec(debug, false) + result := cmd.exec() - return exitCode == 0 + return result.code == 0 } diff --git a/internal/app/command_test.go b/internal/app/command_test.go index 0820d90d..e4c0d0f5 100644 --- a/internal/app/command_test.go +++ b/internal/app/command_test.go @@ -5,68 +5,15 @@ import ( "testing" ) -// func Test_command_printDescription(t *testing.T) { -// type fields struct { -// Cmd string -// Args []string -// Description string -// } -// tests := []struct { -// name string -// fields fields -// }{ -// // TODO: Add test cases. -// } -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// c := command{ -// Cmd: tt.fields.Cmd, -// Args: tt.fields.Args, -// Description: tt.fields.Description, -// } -// c.printDescription() -// }) -// } -// } - -// func Test_command_printFullCommand(t *testing.T) { -// type fields struct { -// Cmd string -// Args []string -// Description string -// } -// tests := []struct { -// name string -// fields fields -// }{ -// // TODO: Add test cases. -// } -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// c := command{ -// Cmd: tt.fields.Cmd, -// Args: tt.fields.Args, -// Description: tt.fields.Description, -// } -// c.printFullCommand() -// }) -// } -// } - func Test_command_exec(t *testing.T) { type fields struct { Cmd string Args []string Description string } - type args struct { - debug bool - verbose bool - } tests := []struct { name string fields fields - args args want int want1 string }{ @@ -77,7 +24,6 @@ func Test_command_exec(t *testing.T) { Args: []string{"-c", "echo this is fun"}, Description: "A bash command execution test with echo.", }, - args: args{debug: false, verbose: false}, want: 0, want1: "this is fun", }, { @@ -87,7 +33,6 @@ func Test_command_exec(t *testing.T) { Args: []string{"-c", "echo $?"}, Description: "A bash command execution test with exitCode.", }, - args: args{debug: false}, want: 0, want1: "0", }, @@ -99,12 +44,12 @@ func Test_command_exec(t *testing.T) { Args: tt.fields.Args, Description: tt.fields.Description, } - got, got1, _ := c.exec(tt.args.debug, tt.args.verbose) - if got != tt.want { - t.Errorf("command.exec() got = %v, want %v", got, tt.want) + got := c.exec() + if got.code != tt.want { + t.Errorf("command.exec() got = %v, want %v", got.code, tt.want) } - if strings.TrimSpace(got1) != tt.want1 { - t.Errorf("command.exec() got1 = %v, want %v", got1, tt.want1) + if strings.TrimSpace(got.output) != tt.want1 { + t.Errorf("command.exec() got1 = %v, want %v", got.output, tt.want1) } }) } diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index 849ef7d6..46793af8 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -6,30 +6,34 @@ import ( "sync" ) -var outcome plan - -type currentState map[string]helmRelease +type currentState struct { + sync.Mutex + releases map[string]helmRelease + plan *plan +} -// logDecision adds the decisions made to the plan. -// Depending on the debug flag being set or not, it will either log the the decision to output or not. -func logDecision(decision string, priority int, decisionType decisionType) { - outcome.addDecision(decision, priority, decisionType) +func newCurrentState() *currentState { + return ¤tState{ + releases: map[string]helmRelease{}, + } } // buildState builds the currentState map containing information about all releases existing in a k8s cluster -func buildState() currentState { +func buildState() *currentState { log.Info("Acquiring current Helm state from cluster...") - cs := currentState(make(map[string]helmRelease)) + cs := newCurrentState() rel := getHelmReleases() var wg sync.WaitGroup for _, r := range rel { wg.Add(1) - go func(c currentState, r helmRelease, wg *sync.WaitGroup) { + go func(c *currentState, r helmRelease, wg *sync.WaitGroup) { + c.Lock() + defer c.Unlock() defer wg.Done() r.HelmsmanContext = getReleaseContext(r.Name, r.Namespace) - c[r.key()] = r + c.releases[r.key()] = r }(cs, r, &wg) } wg.Wait() @@ -37,33 +41,33 @@ func buildState() currentState { } // makePlan creates a plan of the actions needed to make the desired state come true. -func (cs currentState) makePlan(s *state) *plan { - outcome = createPlan() +func (cs *currentState) makePlan(s *state) *plan { + p := createPlan() wg := sync.WaitGroup{} for _, r := range s.Apps { r.checkChartDepUpdate() wg.Add(1) - go cs.decide(r, s, &wg) + go cs.decide(r, s, p, &wg) } wg.Wait() - return &outcome + return p } // decide makes a decision about what commands (actions) need to be executed // to make a release section of the desired state come true. -func (cs currentState) decide(r *release, s *state, wg *sync.WaitGroup) { +func (cs *currentState) decide(r *release, s *state, p *plan, wg *sync.WaitGroup) { defer wg.Done() // check for presence in defined targets or groups - if !r.isConsideredToRun() { - logDecision("Release [ "+r.Name+" ] ignored", r.Priority, ignored) + if !r.isConsideredToRun(s) { + p.addDecision("Release [ "+r.Name+" ] ignored", r.Priority, ignored) return } - if destroy { + if flags.destroy { if ok := cs.releaseExists(r, ""); ok { - r.uninstall() + r.uninstall(p) } return } @@ -71,54 +75,54 @@ func (cs currentState) decide(r *release, s *state, wg *sync.WaitGroup) { if !r.Enabled { if ok := cs.releaseExists(r, ""); ok { - if r.isProtected(cs) { + if r.isProtected(cs, s) { - logDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ + p.addDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ "protection is removed.", r.Priority, noop) return } - r.uninstall() + r.uninstall(p) return } - logDecision("Release [ "+r.Name+" ] disabled", r.Priority, noop) + p.addDecision("Release [ "+r.Name+" ] disabled", r.Priority, noop) return } if ok := cs.releaseExists(r, "deployed"); ok { - if !r.isProtected(cs) { - cs.inspectUpgradeScenario(r) // upgrade or move + if !r.isProtected(cs, s) { + cs.inspectUpgradeScenario(r, p) // upgrade or move } else { - logDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ + p.addDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ "you remove its protection.", r.Priority, noop) } } else if ok := cs.releaseExists(r, "deleted"); ok { - if !r.isProtected(cs) { + if !r.isProtected(cs, s) { - r.rollback(cs) // rollback + r.rollback(cs, p) // rollback } else { - logDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ + p.addDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ "you remove its protection.", r.Priority, noop) } } else if ok := cs.releaseExists(r, "failed"); ok { - if !r.isProtected(cs) { + if !r.isProtected(cs, s) { - logDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is in FAILED state. Upgrade is scheduled!", r.Priority, change) - r.upgrade() + p.addDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is in FAILED state. Upgrade is scheduled!", r.Priority, change) + r.upgrade(p) } else { - logDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ + p.addDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ "you remove its protection.", r.Priority, noop) } } else { // If there is no release in the cluster with this name and in this namespace, then install it! - if _, ok := cs[r.key()]; !ok { - r.install() + if _, ok := cs.releases[r.key()]; !ok { + r.install(p) } else { // A release with the same name and in the same namespace exists, but it has a different context label (managed by another DSF) log.Fatal("Release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ] already exists but is not managed by the" + @@ -131,9 +135,9 @@ func (cs currentState) decide(r *release, s *state, wg *sync.WaitGroup) { // It searches the Current State for releases. // The key format for releases uniqueness is: // If status is provided as an input [deployed, deleted, failed], then the search will verify the release status matches the search status. -func (cs currentState) releaseExists(r *release, status string) bool { - v, ok := cs[r.key()] - if !ok || v.HelmsmanContext != s.Context { +func (cs *currentState) releaseExists(r *release, status string) bool { + v, ok := cs.releases[r.key()] + if !ok || v.HelmsmanContext != curContext { return false } @@ -149,37 +153,46 @@ var releaseNameExtractor = regexp.MustCompile(`sh\.helm\.release\.v\d+\.`) // getHelmsmanReleases returns a map of all releases that are labeled with "MANAGED-BY=HELMSMAN" // The releases are categorized by the namespaces in which they are deployed // The returned map format is: map[:map[:true]] -func (cs currentState) getHelmsmanReleases() map[string]map[string]bool { +func (cs *currentState) getHelmsmanReleases(s *state) map[string]map[string]bool { var lines []string + const outputFmt = "custom-columns=NAME:.metadata.name,NS:.metadata.namespace,CTX:.metadata.labels.HELMSMAN_CONTEXT" releases := make(map[string]map[string]bool) storageBackend := s.Settings.StorageBackend - for ns := range s.Namespaces { - cmd := kubectl([]string{"get", storageBackend, "-n", ns, "-l", "MANAGED-BY=HELMSMAN", "-l", "HELMSMAN_CONTEXT=" + s.Context, "-o", "name"}, "Getting Helmsman-managed releases in namespace [ "+ns+" ]") + cmd := kubectl([]string{"get", storageBackend, "--all-namespaces", "-l", "MANAGED-BY=HELMSMAN", "-o", outputFmt, "--no-headers"}, "Getting Helmsman-managed releases") + result := cmd.exec() - exitCode, output, _ := cmd.exec(debug, verbose) + if result.code != 0 { + log.Fatal(result.errors) + } + if !strings.EqualFold("No resources found.", strings.TrimSpace(result.output)) { + lines = strings.Split(result.output, "\n") + } - if exitCode != 0 { - log.Fatal(output) + for _, line := range lines { + if line == "" { + continue } - if !strings.EqualFold("No resources found.", strings.TrimSpace(output)) { - lines = strings.Split(output, "\n") + flds := strings.Fields(line) + name := resourceNameExtractor.ReplaceAllString(flds[0], "") + name = releaseNameExtractor.ReplaceAllString(name, "") + ns := flds[1] + rctx := defaultContextName + if len(flds) > 2 { + rctx = flds[2] } - - for _, name := range lines { - if name == "" { - continue - } - name = resourceNameExtractor.ReplaceAllString(name, "") - name = releaseNameExtractor.ReplaceAllString(name, "") - if _, ok := releases[ns]; !ok { - releases[ns] = make(map[string]bool) - } - releases[ns][name] = false - for _, app := range s.Apps { - if app.Name == name && app.Namespace == ns { - releases[ns][name] = true - } + if _, ok := releases[ns]; !ok { + releases[ns] = make(map[string]bool) + } + if rctx != s.Context { + // if the release is not related to the current context we assume it's tracked + releases[ns][name] = true + continue + } + releases[ns][name] = false + for _, app := range s.Apps { + if app.Name == name && app.Namespace == ns { + releases[ns][name] = true } } } @@ -191,16 +204,16 @@ func (cs currentState) getHelmsmanReleases() map[string]map[string]bool { // For all untracked releases found, a decision is made to uninstall them and is added to the Helmsman plan // NOTE: Untracked releases don't benefit from either namespace or application protection. // NOTE: Removing/Commenting out an app from the desired state makes it untracked. -func (cs currentState) cleanUntrackedReleases() { +func (cs *currentState) cleanUntrackedReleases(s *state, p *plan) { toDelete := 0 log.Info("Checking if any Helmsman managed releases are no longer tracked by your desired state ...") - for ns, hr := range cs.getHelmsmanReleases() { + for ns, hr := range cs.getHelmsmanReleases(s) { for name, tracked := range hr { if !tracked { toDelete++ - r := cs[name+"-"+ns] - logDecision("Untracked release [ "+r.Name+" ] found and it will be deleted", -800, delete) - r.uninstall() + r := cs.releases[name+"-"+ns] + p.addDecision("Untracked release [ "+r.Name+" ] found and it will be deleted", -800, delete) + r.uninstall(p) } } } @@ -216,9 +229,9 @@ func (cs currentState) cleanUntrackedReleases() { // it will be purge deleted and installed in the same namespace using the new chart. // - If the release is NOT in the same namespace specified in the input, // it will be purge deleted and installed in the new namespace. -func (cs currentState) inspectUpgradeScenario(r *release) { +func (cs *currentState) inspectUpgradeScenario(r *release, p *plan) { - rs, ok := cs[r.key()] + rs, ok := cs.releases[r.key()] if !ok { return } @@ -235,28 +248,28 @@ func (cs currentState) inspectUpgradeScenario(r *release) { if extractChartName(r.Chart) == rs.getChartName() && r.Version != rs.getChartVersion() { // upgrade r.diff() - r.upgrade() - logDecision("Release [ "+r.Name+" ] will be updated", r.Priority, change) + r.upgrade(p) + p.addDecision("Release [ "+r.Name+" ] will be updated", r.Priority, change) } else if extractChartName(r.Chart) != rs.getChartName() { - r.reInstall(rs) - logDecision("Release [ "+r.Name+" ] is desired to use a new chart [ "+r.Chart+ + r.reInstall(rs, p) + p.addDecision("Release [ "+r.Name+" ] is desired to use a new chart [ "+r.Chart+ " ]. Delete of the current release will be planned and new chart will be installed in namespace [ "+ r.Namespace+" ]", r.Priority, change) } else { if diff := r.diff(); diff != "" { - r.upgrade() - logDecision("Release [ "+r.Name+" ] will be updated", r.Priority, change) + r.upgrade(p) + p.addDecision("Release [ "+r.Name+" ] will be updated", r.Priority, change) } else { - logDecision("Release [ "+r.Name+" ] installed and up-to-date", r.Priority, noop) + p.addDecision("Release [ "+r.Name+" ] installed and up-to-date", r.Priority, noop) } } } else { - r.reInstall(rs) - logDecision("Release [ "+r.Name+" ] is desired to be enabled in a new namespace [ "+r.Namespace+ + r.reInstall(rs, p) + p.addDecision("Release [ "+r.Name+" ] is desired to be enabled in a new namespace [ "+r.Namespace+ " ]. Uninstall of the current release from namespace [ "+rs.Namespace+" ] will be performed "+ "and then installation in namespace [ "+r.Namespace+" ] will take place", r.Priority, change) - logDecision("WARNING: moving release [ "+r.Name+" ] from [ "+rs.Namespace+" ] to [ "+r.Namespace+ + p.addDecision("WARNING: moving release [ "+r.Name+" ] from [ "+rs.Namespace+" ] to [ "+r.Namespace+ " ] might not correctly connect existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md"+ " for details if this release uses PV and PVC.", r.Priority, change) } diff --git a/internal/app/decision_maker_test.go b/internal/app/decision_maker_test.go index efdc298b..30c30094 100644 --- a/internal/app/decision_maker_test.go +++ b/internal/app/decision_maker_test.go @@ -108,11 +108,11 @@ func Test_inspectUpgradeScenario(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - outcome = plan{} - cs := currentState(*tt.args.s) + outcome := plan{} + cs := currentState{releases: *tt.args.s} // Act - cs.inspectUpgradeScenario(tt.args.r) + cs.inspectUpgradeScenario(tt.args.r, &outcome) got := outcome.Decisions[0].Type t.Log(outcome.Decisions[0].Description) @@ -204,17 +204,17 @@ func Test_decide(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - targetMap = make(map[string]bool) - cs := currentState(make(map[string]helmRelease)) + cs := newCurrentState() + tt.args.s.TargetMap = make(map[string]bool) for _, target := range tt.targetFlag { - targetMap[target] = true + tt.args.s.TargetMap[target] = true } - outcome = plan{} + outcome := plan{} wg := sync.WaitGroup{} wg.Add(1) // Act - cs.decide(tt.args.r, tt.args.s, &wg) + cs.decide(tt.args.r, tt.args.s, &outcome, &wg) wg.Wait() got := outcome.Decisions[0].Type t.Log(outcome.Decisions[0].Description) @@ -287,20 +287,19 @@ func Test_decide_group(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - groupMap = make(map[string]bool) - targetMap = make(map[string]bool) - cs := currentState(*tt.args.currentState) + tt.args.s.GroupMap = make(map[string]bool) + cs := currentState{releases: *tt.args.currentState} for _, target := range tt.targetFlag { - groupMap[target] = true + tt.args.s.GroupMap[target] = true } for _, group := range tt.groupFlag { - groupMap[group] = true + tt.args.s.GroupMap[group] = true } - outcome = plan{} + outcome := plan{} wg := sync.WaitGroup{} wg.Add(1) - cs.decide(tt.args.r, tt.args.s, &wg) + cs.decide(tt.args.r, tt.args.s, &outcome, &wg) wg.Wait() got := outcome.Decisions[0].Type t.Log(outcome.Decisions[0].Description) diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index a728333c..8f676753 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -1,6 +1,8 @@ package app import ( + "errors" + "fmt" "net/url" "regexp" "strings" @@ -35,11 +37,11 @@ func extractChartName(releaseChart string) string { func getHelmVersion() string { cmd := helmCmd([]string{"version", "--short", "-c"}, "Checking Helm version") - exitCode, result, _ := cmd.exec(debug, false) - if exitCode != 0 { - log.Fatal("While checking helm version: " + result) + result := cmd.exec() + if result.code != 0 { + log.Fatal("While checking helm version: " + result.errors) } - return result + return result.output } // helmPluginExists returns true if the plugin is present in the environment and false otherwise. @@ -47,30 +49,29 @@ func getHelmVersion() string { func helmPluginExists(plugin string) bool { cmd := helmCmd([]string{"plugin", "list"}, "Validating that [ "+plugin+" ] is installed") - exitCode, result, _ := cmd.exec(debug, false) + result := cmd.exec() - if exitCode != 0 { + if result.code != 0 { return false } - return strings.Contains(result, plugin) + return strings.Contains(result.output, plugin) } // updateChartDep updates dependencies for a local chart -func updateChartDep(chartPath string) (bool, string) { +func updateChartDep(chartPath string) error { cmd := helmCmd([]string{"dependency", "update", chartPath}, "Updating dependency for local chart [ "+chartPath+" ]") - exitCode, err, _ := cmd.exec(debug, verbose) - - if exitCode != 0 { - return false, err + result := cmd.exec() + if result.code != 0 { + return errors.New(result.errors) } - return true, "" + return nil } // addHelmRepos adds repositories to Helm if they don't exist already. // Helm does not mind if a repo with the same name exists. It treats it as an update. -func addHelmRepos(repos map[string]string) (bool, string) { +func addHelmRepos(repos map[string]string) error { for repoName, repoLink := range repos { basicAuthArgs := []string{} @@ -98,17 +99,17 @@ func addHelmRepos(repos map[string]string) (bool, string) { cmd := helmCmd(concat([]string{"repo", "add", repoName, repoLink}, basicAuthArgs), "Adding helm repository [ "+repoName+" ]") - if exitCode, err, _ := cmd.exec(debug, verbose); exitCode != 0 { - return false, "While adding helm repository [" + repoName + "]: " + err + if result := cmd.exec(); result.code != 0 { + return fmt.Errorf("While adding helm repository ["+repoName+"]: %w", err) } } cmd := helmCmd([]string{"repo", "update"}, "Updating helm repositories") - if exitCode, err, _ := cmd.exec(debug, verbose); exitCode != 0 { - return false, "While updating helm repos : " + err + if result := cmd.exec(); result.code != 0 { + return errors.New("While updating helm repos : " + result.errors) } - return true, "" + return nil } diff --git a/internal/app/helm_release.go b/internal/app/helm_release.go index c6817fc5..24e98940 100644 --- a/internal/app/helm_release.go +++ b/internal/app/helm_release.go @@ -24,11 +24,11 @@ type helmRelease struct { func getHelmReleases() []helmRelease { var allReleases []helmRelease cmd := helmCmd([]string{"list", "--all", "--max", "0", "--output", "json", "--all-namespaces"}, "Listing all existing releases...") - exitCode, result, _ := cmd.exec(debug, verbose) - if exitCode != 0 { - log.Fatal("Failed to list all releases: " + result) + result := cmd.exec() + if result.code != 0 { + log.Fatal("Failed to list all releases: " + result.errors) } - if err := json.Unmarshal([]byte(result), &allReleases); err != nil { + if err := json.Unmarshal([]byte(result.output), &allReleases); err != nil { log.Fatal(fmt.Sprintf("failed to unmarshal Helm CLI output: %s", err)) } return allReleases @@ -39,10 +39,10 @@ func (r *helmRelease) key() string { } // uninstall creates the helm command to uninstall an untracked release -func (r *helmRelease) uninstall() { - cmd := helmCmd(concat([]string{"uninstall", r.Name, "--namespace", r.Namespace}, getDryRunFlags()), "Deleting untracked release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") +func (r *helmRelease) uninstall(p *plan) { + cmd := helmCmd(concat([]string{"uninstall", r.Name, "--namespace", r.Namespace}, flags.getDryRunFlags()), "Deleting untracked release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") - outcome.addCommand(cmd, -800, nil) + p.addCommand(cmd, -800, nil) } // getRevision returns the revision number for an existing helm release @@ -75,6 +75,6 @@ func (r *helmRelease) getChartVersion() string { // getCurrentNamespaceProtection returns the protection state for the namespace where a release is currently installed. // It returns true if a namespace is defined as protected in the desired state file, false otherwise. -func (r *helmRelease) getCurrentNamespaceProtection() bool { +func (r *helmRelease) getCurrentNamespaceProtection(s *state) bool { return s.Namespaces[r.Namespace].Protected } diff --git a/internal/app/kube_helpers.go b/internal/app/kube_helpers.go index 8c804c65..80c63c3d 100644 --- a/internal/app/kube_helpers.go +++ b/internal/app/kube_helpers.go @@ -1,9 +1,11 @@ package app import ( + "errors" "fmt" "io/ioutil" "strings" + "sync" "gopkg.in/yaml.v2" ) @@ -12,17 +14,18 @@ import ( // If a namespace with the same name exists, it will skip it. // If --ns-override flag is used, it only creates the provided namespace in that flag func addNamespaces(namespaces map[string]namespace) { - if nsOverride == "" { - for nsName, ns := range namespaces { - createNamespace(nsName) - labelNamespace(nsName, ns.Labels) - annotateNamespace(nsName, ns.Annotations) - setLimits(nsName, ns.Limits) - } - } else { - createNamespace(nsOverride) - s.overrideAppsNamespace(nsOverride) + var wg sync.WaitGroup + for nsName, ns := range namespaces { + wg.Add(1) + go func(name string, cfg namespace, wg *sync.WaitGroup) { + defer wg.Done() + createNamespace(name) + labelNamespace(name, cfg.Labels) + annotateNamespace(name, cfg.Annotations) + setLimits(name, cfg.Limits) + }(nsName, ns, &wg) } + wg.Wait() } // kubectl prepares a kubectl command to be executed @@ -37,17 +40,17 @@ func kubectl(args []string, desc string) command { // createNamespace creates a namespace in the k8s cluster func createNamespace(ns string) { checkCmd := kubectl([]string{"get", "namespace", ns}, "Looking for namespace [ "+ns+" ]") - checkExitCode, _, _ := checkCmd.exec(debug, verbose) - if checkExitCode == 0 { + checkResult := checkCmd.exec() + if checkResult.code == 0 { log.Verbose("Namespace [ " + ns + " ] exists") return } cmd := kubectl([]string{"create", "namespace", ns}, "Creating namespace [ "+ns+" ]") - exitCode, err, _ := cmd.exec(debug, verbose) - if exitCode == 0 { + result := cmd.exec() + if result.code == 0 { log.Info("Namespace [ " + ns + " ] created") } else { - log.Fatal("Failed creating namespace [ " + ns + " ] with error: " + err) + log.Fatal("Failed creating namespace [ " + ns + " ] with error: " + result.errors) } } @@ -67,9 +70,9 @@ func labelNamespace(ns string, labels map[string]string) { cmd := kubectl(args, "Labeling namespace [ "+ns+" ]") - exitCode, errMsg, _ := cmd.exec(debug, verbose) - if exitCode != 0 && verbose { - log.Warning(fmt.Sprintf("Could not label namespace [ %s with %v ]. Error message: %s", ns, labelSlice, errMsg)) + result := cmd.exec() + if result.code != 0 && flags.verbose { + log.Warning(fmt.Sprintf("Could not label namespace [ %s with %v ]. Error message: %s", ns, labelSlice, result.errors)) } } @@ -87,9 +90,9 @@ func annotateNamespace(ns string, annotations map[string]string) { args = append(args, annotationSlice...) cmd := kubectl(args, "Annotating namespace [ "+ns+" ]") - exitCode, errMsg, _ := cmd.exec(debug, verbose) - if exitCode != 0 && verbose { - log.Info(fmt.Sprintf("Could not annotate namespace [ %s with %v ]. Error message: %s", ns, annotationSlice, errMsg)) + result := cmd.exec() + if result.code != 0 && flags.verbose { + log.Info(fmt.Sprintf("Could not annotate namespace [ %s with %v ]. Error message: %s", ns, annotationSlice, result.errors)) } } @@ -121,10 +124,10 @@ spec: } cmd := kubectl([]string{"apply", "-f", "temp-LimitRange.yaml", "-n", ns}, "Creating LimitRange in namespace [ "+ns+" ]") - exitCode, e, _ := cmd.exec(debug, verbose) + result := cmd.exec() - if exitCode != 0 { - log.Fatal("Failed to create LimitRange in namespace [ " + ns + " ] with error: " + e) + if result.code != 0 { + log.Fatal("Failed to create LimitRange in namespace [ " + ns + " ] with error: " + result.errors) } deleteFile("temp-LimitRange.yaml") @@ -133,21 +136,21 @@ spec: // createContext creates a context -connecting to a k8s cluster- in kubectl config. // It returns true if successful, false otherwise -func createContext() (bool, string) { +func createContext(s *state) error { if s.Settings.BearerToken && s.Settings.BearerTokenPath == "" { log.Info("Creating kube context with bearer token from K8S service account.") s.Settings.BearerTokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token" } else if s.Settings.BearerToken && s.Settings.BearerTokenPath != "" { log.Info("Creating kube context with bearer token from " + s.Settings.BearerTokenPath) } else if s.Settings.Password == "" || s.Settings.Username == "" || s.Settings.ClusterURI == "" { - return false, "missing information to create context [ " + s.Settings.KubeContext + " ] " + - "you are either missing PASSWORD, USERNAME or CLUSTERURI in the Settings section of your desired state file." + return errors.New("missing information to create context [ " + s.Settings.KubeContext + " ] " + + "you are either missing PASSWORD, USERNAME or CLUSTERURI in the Settings section of your desired state file.") } else if !s.Settings.BearerToken && (s.Certificates == nil || s.Certificates["caCrt"] == "" || s.Certificates["caKey"] == "") { - return false, "missing information to create context [ " + s.Settings.KubeContext + " ] " + - "you are either missing caCrt or caKey or both in the Certifications section of your desired state file." + return errors.New("missing information to create context [ " + s.Settings.KubeContext + " ] " + + "you are either missing caCrt or caKey or both in the Certifications section of your desired state file.") } else if s.Settings.BearerToken && (s.Certificates == nil || s.Certificates["caCrt"] == "") { - return false, "missing information to create context [ " + s.Settings.KubeContext + " ] " + - "caCrt is missing in the Certifications section of your desired state file." + return errors.New("missing information to create context [ " + s.Settings.KubeContext + " ] " + + "caCrt is missing in the Certifications section of your desired state file.") } // set certs locations (relative filepath, GCS bucket, AWS bucket) @@ -161,23 +164,17 @@ func createContext() (bool, string) { // CA cert if caCrt != "" { - caCrt = downloadFile(caCrt, "ca.crt") - } // CA key if caKey != "" { - caKey = downloadFile(caKey, "ca.key") - } // client certificate if caClient != "" { - caClient = downloadFile(caClient, "client.crt") - } // bearer token @@ -203,42 +200,42 @@ func createContext() (bool, string) { } cmd := kubectl(setCredentialsCmdArgs, "Creating kubectl context - setting credentials") - if exitCode, err, _ := cmd.exec(debug, verbose); exitCode != 0 { - return false, "failed to create context [ " + s.Settings.KubeContext + " ]: " + err + if result := cmd.exec(); result.code != 0 { + return errors.New("failed to create context [ " + s.Settings.KubeContext + " ]: " + result.errors) } cmd = kubectl([]string{"config", "set-cluster", s.Settings.KubeContext, "--server=" + s.Settings.ClusterURI, "--certificate-authority=" + caCrt}, "Creating kubectl context - setting cluster") - if exitCode, err, _ := cmd.exec(debug, verbose); exitCode != 0 { - return false, "failed to create context [ " + s.Settings.KubeContext + " ]: " + err + if result := cmd.exec(); result.code != 0 { + return errors.New("failed to create context [ " + s.Settings.KubeContext + " ]: " + result.errors) } cmd = kubectl([]string{"config", "set-context", s.Settings.KubeContext, "--cluster=" + s.Settings.KubeContext, "--user=" + s.Settings.Username}, "Creating kubectl context - setting context") - if exitCode, err, _ := cmd.exec(debug, verbose); exitCode != 0 { - return false, "failed to create context [ " + s.Settings.KubeContext + " ]: " + err + if result := cmd.exec(); result.code != 0 { + return errors.New("failed to create context [ " + s.Settings.KubeContext + " ]: " + result.errors) } if setKubeContext(s.Settings.KubeContext) { - return true, "" + return nil } - return false, "something went wrong while setting the kube context to the newly created one." + return errors.New("something went wrong while setting the kube context to the newly created one.") } // setKubeContext sets your kubectl context to the one specified in the desired state file. // It returns false if it fails to set the context. This means the context does not exist. -func setKubeContext(context string) bool { - if context == "" { +func setKubeContext(kctx string) bool { + if kctx == "" { return getKubeContext() } - cmd := kubectl([]string{"config", "use-context", context}, "Setting kube context to [ "+context+" ]") + cmd := kubectl([]string{"config", "use-context", kctx}, "Setting kube context to [ "+kctx+" ]") - exitCode, _, _ := cmd.exec(debug, verbose) + result := cmd.exec() - if exitCode != 0 { - log.Info("Kubectl context [ " + context + " ] does not exist. Attempting to create it...") + if result.code != 0 { + log.Info("Kubectl context [ " + kctx + " ] does not exist. Attempting to create it...") return false } @@ -250,9 +247,9 @@ func setKubeContext(context string) bool { func getKubeContext() bool { cmd := kubectl([]string{"config", "current-context"}, "Getting kubectl context") - exitCode, result, _ := cmd.exec(debug, verbose) + result := cmd.exec() - if exitCode != 0 || result == "" { + if result.code != 0 || result.output == "" { log.Info("Kubectl context is not set") return false } @@ -263,40 +260,43 @@ func getKubeContext() bool { // labelResource applies Helmsman specific labels to Helm's state resources (secrets/configmaps) func labelResource(r *release) { if r.Enabled { - storageBackend := s.Settings.StorageBackend + storageBackend := settings.StorageBackend - cmd := kubectl([]string{"label", storageBackend, "-n", r.Namespace, "-l", "owner=helm,name=" + r.Name, "MANAGED-BY=HELMSMAN", "NAMESPACE=" + r.Namespace, "HELMSMAN_CONTEXT=" + s.Context, "--overwrite"}, "Applying Helmsman labels to [ "+r.Name+" ] release") + cmd := kubectl([]string{"label", storageBackend, "-n", r.Namespace, "-l", "owner=helm,name=" + r.Name, "MANAGED-BY=HELMSMAN", "NAMESPACE=" + r.Namespace, "HELMSMAN_CONTEXT=" + curContext, "--overwrite"}, "Applying Helmsman labels to [ "+r.Name+" ] release") - exitCode, err, _ := cmd.exec(debug, verbose) - - if exitCode != 0 { - log.Fatal(err) + result := cmd.exec() + if result.code != 0 { + log.Fatal(result.errors) } } } // getReleaseContext extracts the Helmsman release context from the helm storage driver objects (secret or configmap) labels func getReleaseContext(releaseName string, namespace string) string { - storageBackend := s.Settings.StorageBackend + storageBackend := settings.StorageBackend // kubectl get secrets -n test1 -l MANAGED-BY=HELMSMAN -o=jsonpath='{.items[0].metadata.labels.HELMSMAN_CONTEXT}' // kubectl get secret sh.helm.release.v1.argo.v1 -n test1 -o=jsonpath='{.metadata.labels.HELMSMAN_CONTEXT}' // kubectl get secret -l owner=helm,name=argo -n test1 -o=jsonpath='{.items[-1].metadata.labels.HELMSMAN_CONTEXT}' cmd := kubectl([]string{"get", storageBackend, "-n", namespace, "-l", "owner=helm", "-l", "name=" + releaseName, "-o", "jsonpath='{.items[-1].metadata.labels.HELMSMAN_CONTEXT}'"}, "Getting Helmsman context for [ "+releaseName+" ] release") - exitCode, out, _ := cmd.exec(debug, verbose) - if exitCode != 0 { - log.Fatal(out) + result := cmd.exec() + if result.code != 0 { + log.Fatal(result.errors) + } + rctx := strings.Trim(result.output, `"' `) + if rctx == "" { + rctx = defaultContextName } - return strings.Trim(out, `"' `) + return rctx } // getKubectlClientVersion returns kubectl client version func getKubectlClientVersion() string { cmd := kubectl([]string{"version", "--client", "--short"}, "Checking kubectl version") - exitCode, result, _ := cmd.exec(debug, false) - if exitCode != 0 { - log.Fatal("While checking kubectl version: " + result) + result := cmd.exec() + if result.code != 0 { + log.Fatal("While checking kubectl version: " + result.errors) } - return result + return result.output } diff --git a/internal/app/logging.go b/internal/app/logging.go index 57a3d602..3ed88239 100644 --- a/internal/app/logging.go +++ b/internal/app/logging.go @@ -1,9 +1,10 @@ package app import ( - "github.com/apsdehal/go-logger" "net/url" "os" + + "github.com/apsdehal/go-logger" ) type Logger struct { @@ -18,11 +19,13 @@ func (l *Logger) Info(message string) { } func (l *Logger) Debug(message string) { - baseLogger.Debug(message) + if flags.debug { + baseLogger.Debug(message) + } } func (l *Logger) Verbose(message string) { - if verbose { + if flags.verbose { baseLogger.Info(message) } } @@ -40,8 +43,8 @@ func (l *Logger) Notice(message string) { } func (l *Logger) Fatal(message string) { - if _, err := url.ParseRequestURI(s.Settings.SlackWebhook); err == nil { - notifySlack(message, s.Settings.SlackWebhook, true, apply) + if _, err := url.ParseRequestURI(settings.SlackWebhook); err == nil { + notifySlack(message, settings.SlackWebhook, true, flags.apply) } baseLogger.Fatal(message) } diff --git a/internal/app/main.go b/internal/app/main.go index a1e29391..a148cbc0 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -4,96 +4,71 @@ import ( "os" ) -// Allow parsing of multiple string command line options into an array of strings -type stringArray []string - -func (i *stringArray) String() string { - return "my string representation" -} - -func (i *stringArray) Set(value string) error { - *i = append(*i, value) - return nil -} +const ( + helmBin = "helm" + appVersion = "v3.0.0-beta3" + tempFilesDir = ".helmsman-tmp" + stableHelmRepo = "https://kubernetes-charts.storage.googleapis.com" + incubatorHelmRepo = "http://storage.googleapis.com/kubernetes-charts-incubator" + defaultContextName = "default" +) -const appVersion = "v3.0.0-beta3" -const tempFilesDir = ".helmsman-tmp" -const stableHelmRepo = "https://kubernetes-charts.storage.googleapis.com" -const incubatorHelmRepo = "http://storage.googleapis.com/kubernetes-charts-incubator" -const defaultContextName = "default" - -var s state -var debug bool -var files stringArray -var envFiles stringArray -var kubeconfig string -var apply bool -var v bool -var verbose bool -var noBanner bool -var noColors bool -var noFancy bool -var noNs bool -var nsOverride string -var skipValidation bool -var keepUntrackedReleases bool -var helmBin = "helm" -var helmVersion string -var kubectlVersion string -var dryRun bool -var target stringArray -var group stringArray -var targetMap map[string]bool -var groupMap map[string]bool -var destroy bool -var showDiff bool -var suppressDiffSecrets bool -var diffContext int -var noEnvSubst bool -var noEnvValuesSubst bool -var noSSMSubst bool -var noSSMValuesSubst bool -var updateDeps bool -var forceUpgrades bool -var noDefaultRepos bool - -var settings config +var ( + flags cli + settings config + curContext string +) func init() { // Parse cli flags and read config files - Cli() + flags.parse() } // Main is the app main function func Main() { + var s state + // delete temp files with substituted env vars when the program terminates defer os.RemoveAll(tempFilesDir) - defer cleanup() + defer s.cleanup() + flags.readState(&s) settings = s.Settings + curContext = s.Context + // set the kubecontext to be used Or create it if it does not exist + log.Info("Setting up kubectl...") if !setKubeContext(settings.KubeContext) { - if r, msg := createContext(); !r { - log.Fatal(msg) + if err := createContext(&s); err != nil { + log.Fatal(err.Error()) } } // add repos -- fails if they are not valid - if r, msg := addHelmRepos(s.HelmRepos); !r { - log.Fatal(msg) + if !flags.destroy { + log.Info("Setting up helm...") + if err := addHelmRepos(s.HelmRepos); err != nil { + log.Fatal(err.Error()) + } } - if apply || dryRun || destroy { + if flags.apply || flags.dryRun || flags.destroy { // add/validate namespaces - if !noNs { - addNamespaces(s.Namespaces) + if !flags.noNs { + log.Info("Setting up namespaces...") + if flags.nsOverride == "" { + addNamespaces(s.Namespaces) + } else { + createNamespace(flags.nsOverride) + s.overrideAppsNamespace(flags.nsOverride) + } } } - if !skipValidation { + if !flags.skipValidation { log.Info("Validating charts...") // validate charts-versions exist in defined repos - if err := validateReleaseCharts(s.Apps); err != nil { + if err := validateReleaseCharts(&s); err != nil { log.Fatal(err.Error()) } } else { @@ -101,24 +76,24 @@ func Main() { } log.Info("Preparing plan...") - if destroy { + if flags.destroy { log.Info("--destroy is enabled. Your releases will be deleted!") } cs := buildState() p := cs.makePlan(&s) - if !keepUntrackedReleases { - cs.cleanUntrackedReleases() + if !flags.keepUntrackedReleases { + cs.cleanUntrackedReleases(&s, p) } p.sort() p.print() - if debug { + if flags.debug { p.printCmds() } p.sendToSlack() - if apply || dryRun || destroy { + if flags.apply || flags.dryRun || flags.destroy { p.exec() } } @@ -126,7 +101,7 @@ func Main() { // cleanup deletes the k8s certificates and keys files // It also deletes any Tiller TLS certs and keys // and secret files -func cleanup() { +func (s *state) cleanup() { log.Verbose("Cleaning up sensitive and temp files") if _, err := os.Stat("ca.crt"); err == nil { deleteFile("ca.crt") @@ -154,5 +129,4 @@ func cleanup() { } } } - } diff --git a/internal/app/plan.go b/internal/app/plan.go index f316f271..faa4d92d 100644 --- a/internal/app/plan.go +++ b/internal/app/plan.go @@ -6,6 +6,7 @@ import ( "sort" "strconv" "strings" + "sync" "time" ) @@ -36,24 +37,25 @@ type orderedCommand struct { // plan type representing the plan of actions to make the desired state come true. type plan struct { + sync.Mutex Commands []orderedCommand Decisions []orderedDecision Created time.Time } // createPlan initializes an empty plan -func createPlan() plan { - - p := plan{ +func createPlan() *plan { + return &plan{ Commands: []orderedCommand{}, Decisions: []orderedDecision{}, Created: time.Now().UTC(), } - return p } // addCommand adds a command type to the plan func (p *plan) addCommand(cmd command, priority int, r *release) { + p.Lock() + defer p.Unlock() oc := orderedCommand{ Command: cmd, Priority: priority, @@ -65,6 +67,8 @@ func (p *plan) addCommand(cmd command, priority int, r *release) { // addDecision adds a decision type to the plan func (p *plan) addDecision(decision string, priority int, decisionType decisionType) { + p.Lock() + defer p.Unlock() od := orderedDecision{ Description: decision, Priority: priority, @@ -74,7 +78,7 @@ func (p *plan) addDecision(decision string, priority int, decisionType decisionT } // execPlan executes the commands (actions) which were added to the plan. -func (p plan) exec() { +func (p *plan) exec() { p.sort() if len(p.Commands) > 0 { log.Info("Executing plan... ") @@ -84,20 +88,20 @@ func (p plan) exec() { for _, cmd := range p.Commands { log.Notice(cmd.Command.Description) - if exitCode, msg, _ := cmd.Command.exec(debug, verbose); exitCode != 0 { + if result := cmd.Command.exec(); result.code != 0 { var errorMsg string - if errorMsg = msg; !verbose { - errorMsg = strings.Split(msg, "---")[0] + if errorMsg = result.errors; !flags.verbose { + errorMsg = strings.Split(result.errors, "---")[0] } - log.Fatal(fmt.Sprintf("Command returned [ %d ] exit code and error message [ %s ]", exitCode, errorMsg)) + log.Fatal(fmt.Sprintf("Command returned [ %d ] exit code and error message [ %s ]", result.code, errorMsg)) } else { - log.Notice(msg) - if cmd.targetRelease != nil && !dryRun { + log.Notice(result.output) + if cmd.targetRelease != nil && !flags.dryRun { labelResource(cmd.targetRelease) } log.Notice("Finished: " + cmd.Command.Description) - if _, err := url.ParseRequestURI(s.Settings.SlackWebhook); err == nil { - notifySlack(cmd.Command.Description+" ... SUCCESS!", s.Settings.SlackWebhook, false, true) + if _, err := url.ParseRequestURI(settings.SlackWebhook); err == nil { + notifySlack(cmd.Command.Description+" ... SUCCESS!", settings.SlackWebhook, false, true) } } } @@ -108,7 +112,7 @@ func (p plan) exec() { } // printPlanCmds prints the actual commands that will be executed as part of a plan. -func (p plan) printCmds() { +func (p *plan) printCmds() { log.Info("Printing the commands of the current plan ...") for _, cmd := range p.Commands { fmt.Println(cmd.Command.String()) @@ -116,7 +120,7 @@ func (p plan) printCmds() { } // printPlan prints the decisions made in a plan. -func (p plan) print() { +func (p *plan) print() { log.Notice("-------- PLAN starts here --------------") for _, decision := range p.Decisions { if decision.Type == ignored { @@ -133,21 +137,19 @@ func (p plan) print() { } // sendPlanToSlack sends the description of plan commands to slack if a webhook is provided. -func (p plan) sendToSlack() { - if _, err := url.ParseRequestURI(s.Settings.SlackWebhook); err == nil { +func (p *plan) sendToSlack() { + if _, err := url.ParseRequestURI(settings.SlackWebhook); err == nil { str := "" for _, c := range p.Commands { str = str + c.Command.Description + "\n" } - - notifySlack(strings.TrimRight(str, "\n"), s.Settings.SlackWebhook, false, false) + notifySlack(strings.TrimRight(str, "\n"), settings.SlackWebhook, false, false) } - } // sortPlan sorts the slices of commands and decisions based on priorities // the lower the priority value the earlier a command should be attempted -func (p plan) sort() { +func (p *plan) sort() { log.Verbose("Sorting the commands in the plan based on priorities (order flags) ... ") sort.SliceStable(p.Commands, func(i, j int) bool { diff --git a/internal/app/plan_test.go b/internal/app/plan_test.go index fcd92289..70295773 100644 --- a/internal/app/plan_test.go +++ b/internal/app/plan_test.go @@ -9,11 +9,11 @@ import ( func Test_createPlan(t *testing.T) { tests := []struct { name string - want plan + want *plan }{ { name: "test creating a plan", - want: createPlan(), + want: &plan{}, }, } for _, tt := range tests { @@ -154,7 +154,7 @@ func Test_plan_addDecision(t *testing.T) { // Args: []string{"-c", "ls | grep hello.world | wc -l"}, // Description: "", // } -// if _, got := c.exec(false, false); strings.TrimSpace(got) != "2" { +// if _, got := c.exec(); strings.TrimSpace(got) != "2" { // t.Errorf("execPlan(): got %v, want hello world, again!", got) // } // }) diff --git a/internal/app/release.go b/internal/app/release.go index 082d7547..5d51dff7 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -48,15 +48,15 @@ func (r *release) key() string { } // isReleaseConsideredToRun checks if a release is being targeted for operations as specified by user cmd flags (--group or --target) -func (r *release) isConsideredToRun() bool { - if len(targetMap) > 0 { - if _, ok := targetMap[r.Name]; ok { +func (r *release) isConsideredToRun(s *state) bool { + if len(s.TargetMap) > 0 { + if _, ok := s.TargetMap[r.Name]; ok { return true } return false } - if len(groupMap) > 0 { - if _, ok := groupMap[r.Group]; ok { + if len(s.GroupMap) > 0 { + if _, ok := s.GroupMap[r.Group]; ok { return true } return false @@ -66,57 +66,57 @@ func (r *release) isConsideredToRun() bool { // validateRelease validates if a release inside a desired state meets the specifications or not. // check the full specification @ https://github.com/Praqma/helmsman/docs/desired_state_spec.md -func (r *release) validate(appLabel string, names map[string]map[string]bool, s *state) (bool, string) { +func (r *release) validate(appLabel string, names map[string]map[string]bool, s *state) error { if r.Name == "" { r.Name = appLabel } if names[r.Name][r.Namespace] { - return false, "release name must be unique within a given Tiller." + return errors.New("release name must be unique within a given namespace.") } - if nsOverride == "" && r.Namespace == "" { - return false, "release targeted namespace can't be empty." - } else if nsOverride == "" && r.Namespace != "" && r.Namespace != "kube-system" && !s.checkNamespaceDefined(r.Namespace) { - return false, "release " + r.Name + " is using namespace [ " + r.Namespace + " ] which is not defined in the Namespaces section of your desired state file." + - " Release [ " + r.Name + " ] can't be installed in that Namespace until its defined." + if flags.nsOverride == "" && r.Namespace == "" { + return errors.New("release targeted namespace can't be empty.") + } else if flags.nsOverride == "" && r.Namespace != "" && r.Namespace != "kube-system" && !s.checkNamespaceDefined(r.Namespace) { + return errors.New("release " + r.Name + " is using namespace [ " + r.Namespace + " ] which is not defined in the Namespaces section of your desired state file." + + " Release [ " + r.Name + " ] can't be installed in that Namespace until its defined.") } _, err := os.Stat(r.Chart) if r.Chart == "" || os.IsNotExist(err) && !strings.ContainsAny(r.Chart, "/") { - return false, "chart can't be empty and must be of the format: repo/chart." + return errors.New("chart can't be empty and must be of the format: repo/chart.") } if r.Version == "" { - return false, "version can't be empty." + return errors.New("version can't be empty.") } _, err = os.Stat(r.ValuesFile) if r.ValuesFile != "" && (!isOfType(r.ValuesFile, []string{".yaml", ".yml", ".json"}) || err != nil) { - return false, fmt.Sprintf("valuesFile must be a valid relative (from dsf file) file path for a yaml file, or can be left empty (provided path resolved to %q).", r.ValuesFile) + return errors.New(fmt.Sprintf("valuesFile must be a valid relative (from dsf file) file path for a yaml file, or can be left empty (provided path resolved to %q).", r.ValuesFile)) } else if r.ValuesFile != "" && len(r.ValuesFiles) > 0 { - return false, "valuesFile and valuesFiles should not be used together." + return errors.New("valuesFile and valuesFiles should not be used together.") } else if len(r.ValuesFiles) > 0 { for i, filePath := range r.ValuesFiles { if _, pathErr := os.Stat(filePath); !isOfType(filePath, []string{".yaml", ".yml", ".json"}) || pathErr != nil { - return false, fmt.Sprintf("valuesFiles must be valid relative (from dsf file) file paths for a yaml file; path at index %d provided path resolved to %q.", i, filePath) + return errors.New(fmt.Sprintf("valuesFiles must be valid relative (from dsf file) file paths for a yaml file; path at index %d provided path resolved to %q.", i, filePath)) } } } _, err = os.Stat(r.SecretsFile) if r.SecretsFile != "" && (!isOfType(r.SecretsFile, []string{".yaml", ".yml", ".json"}) || err != nil) { - return false, fmt.Sprintf("secretsFile must be a valid relative (from dsf file) file path for a yaml file, or can be left empty (provided path resolved to %q).", r.SecretsFile) + return errors.New(fmt.Sprintf("secretsFile must be a valid relative (from dsf file) file path for a yaml file, or can be left empty (provided path resolved to %q).", r.SecretsFile)) } else if r.SecretsFile != "" && len(r.SecretsFiles) > 0 { - return false, "secretsFile and secretsFiles should not be used together." + return errors.New("secretsFile and secretsFiles should not be used together.") } else if len(r.SecretsFiles) > 0 { for _, filePath := range r.SecretsFiles { if i, pathErr := os.Stat(filePath); !isOfType(filePath, []string{".yaml", ".yml", ".json"}) || pathErr != nil { - return false, fmt.Sprintf("secretsFiles must be valid relative (from dsf file) file paths for a yaml file; path at index %d provided path resolved to %q.", i, filePath) + return errors.New(fmt.Sprintf("secretsFiles must be valid relative (from dsf file) file paths for a yaml file; path at index %d provided path resolved to %q.", i, filePath)) } } } if r.Priority != 0 && r.Priority > 0 { - return false, "priority can only be 0 or negative value, positive values are not allowed." + return errors.New("priority can only be 0 or negative value, positive values are not allowed.") } if names[r.Name] == nil { @@ -129,23 +129,23 @@ func (r *release) validate(appLabel string, names map[string]map[string]bool, s for k, v := range r.Set { if strings.Contains(v, "$") { if os.ExpandEnv(strings.Replace(v, "$$", "${HELMSMAN_DOLLAR}", -1)) == "" { - return false, "env var [ " + v + " ] is not set, but is wanted to be passed for [ " + k + " ] in [[ " + r.Name + " ]]" + return errors.New("env var [ " + v + " ] is not set, but is wanted to be passed for [ " + k + " ] in [[ " + r.Name + " ]]") } } } - return true, "" + return nil } // validateReleaseCharts validates if the charts defined in a release are valid. // Valid charts are the ones that can be found in the defined repos. // This function uses Helm search to verify if the chart can be found or not. -func validateReleaseCharts(apps map[string]*release) error { +func validateReleaseCharts(s *state) error { wg := sync.WaitGroup{} - c := make(chan string, len(apps)) - for app, r := range apps { + c := make(chan string, len(s.Apps)) + for app, r := range s.Apps { wg.Add(1) - go r.validateChart(app, &wg, c) + go r.validateChart(app, s, &wg, c) } wg.Wait() if len(c) > 0 { @@ -159,25 +159,24 @@ func validateReleaseCharts(apps map[string]*release) error { var versionExtractor = regexp.MustCompile(`version:\s?(.*)`) -func (r *release) validateChart(app string, wg *sync.WaitGroup, c chan string) { +func (r *release) validateChart(app string, s *state, wg *sync.WaitGroup, c chan string) { defer wg.Done() validateCurrentChart := true - if !r.isConsideredToRun() { + if !r.isConsideredToRun(s) { validateCurrentChart = false } if validateCurrentChart { if isLocalChart(r.Chart) { cmd := helmCmd([]string{"inspect", "chart", r.Chart}, "Validating [ "+r.Chart+" ] chart's availability") - var output string - var exitCode int - if exitCode, output, _ = cmd.exec(debug, verbose); exitCode != 0 { + result := cmd.exec() + if result.code != 0 { maybeRepo := filepath.Base(filepath.Dir(r.Chart)) c <- "Chart [ " + r.Chart + " ] for app [" + app + "] can't be found. Did you mean to add a repo [ " + maybeRepo + " ]?" return } - matches := versionExtractor.FindStringSubmatch(output) + matches := versionExtractor.FindStringSubmatch(result.output) if len(matches) == 2 { version := matches[1] if r.Version != version { @@ -194,7 +193,7 @@ func (r *release) validateChart(app string, wg *sync.WaitGroup, c chan string) { } cmd := helmCmd([]string{"search", "repo", r.Chart, "--version", version, "-l"}, "Validating [ "+r.Chart+" ] chart's version [ "+r.Version+" ] availability") - if exitCode, result, _ := cmd.exec(debug, verbose); exitCode != 0 || strings.Contains(result, "No results found") { + if result := cmd.exec(); result.code != 0 || strings.Contains(result.output, "No results found") { c <- "Chart [ " + r.Chart + " ] with version [ " + r.Version + " ] is specified for " + "app [" + app + "] but was not found" return @@ -211,17 +210,13 @@ func (r *release) getChartVersion() (string, string) { } cmd := helmCmd([]string{"search", "repo", r.Chart, "--version", r.Version, "-o", "json"}, "Getting latest chart's version "+r.Chart+"-"+r.Version+"") - var ( - exitCode int - result string - ) - - if exitCode, result, _ = cmd.exec(debug, verbose); exitCode != 0 { + result := cmd.exec() + if result.code != 0 { return "", "Chart [ " + r.Chart + " ] with version [ " + r.Version + " ] is specified but not found in the helm repositories" } chartVersions := make([]chartVersion, 0) - if err := json.Unmarshal([]byte(result), &chartVersions); err != nil { + if err := json.Unmarshal([]byte(result.output), &chartVersions); err != nil { log.Fatal(fmt.Sprint(err)) } @@ -234,111 +229,109 @@ func (r *release) getChartVersion() (string, string) { } // testRelease creates a Helm command to test a particular release. -func (r *release) test() { +func (r *release) test(p *plan) { cmd := helmCmd(r.getHelmArgsFor("test"), "Running tests for release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") - outcome.addCommand(cmd, r.Priority, r) - logDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is required to be tested during installation", r.Priority, noop) + p.addCommand(cmd, r.Priority, r) + p.addDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is required to be tested during installation", r.Priority, noop) } // installRelease creates a Helm command to install a particular release in a particular namespace using a particular Tiller. -func (r *release) install() { +func (r *release) install(p *plan) { cmd := helmCmd(r.getHelmArgsFor("install"), "Installing release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") - outcome.addCommand(cmd, r.Priority, r) - logDecision("Release [ "+r.Name+" ] will be installed in [ "+r.Namespace+" ] namespace", r.Priority, create) + p.addCommand(cmd, r.Priority, r) + p.addDecision("Release [ "+r.Name+" ] will be installed in [ "+r.Namespace+" ] namespace", r.Priority, create) if r.Test { - r.test() + r.test(p) } } // uninstall deletes a release from a particular Tiller in a k8s cluster -func (r *release) uninstall() { +func (r *release) uninstall(p *plan) { priority := r.Priority if settings.ReverseDelete { priority = priority * -1 } - cmd := helmCmd(concat(r.getHelmArgsFor("uninstall"), getDryRunFlags()), "Deleting release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") - outcome.addCommand(cmd, priority, r) - logDecision(fmt.Sprintf("release [ %s ] is desired to be DELETED.", r.Name), r.Priority, delete) + cmd := helmCmd(concat(r.getHelmArgsFor("uninstall"), flags.getDryRunFlags()), "Deleting release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") + p.addCommand(cmd, priority, r) + p.addDecision(fmt.Sprintf("release [ %s ] is desired to be DELETED.", r.Name), r.Priority, delete) } // diffRelease diffs an existing release with the specified values.yaml func (r *release) diff() string { - exitCode := 0 - msg := "" colorFlag := "" diffContextFlag := []string{} suppressDiffSecretsFlag := "" - if noColors { + if flags.noColors { colorFlag = "--no-color" } - if diffContext != -1 { - diffContextFlag = []string{"--context", strconv.Itoa(diffContext)} + if flags.diffContext != -1 { + diffContextFlag = []string{"--context", strconv.Itoa(flags.diffContext)} } - if suppressDiffSecrets { + if flags.suppressDiffSecrets { suppressDiffSecretsFlag = "--suppress-secrets" } cmd := helmCmd(concat([]string{"diff", colorFlag, suppressDiffSecretsFlag}, diffContextFlag, r.getHelmArgsFor("upgrade")), "Diffing release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") - if exitCode, msg, _ = cmd.exec(debug, verbose); exitCode != 0 { - log.Fatal(fmt.Sprintf("Command returned with exit code: %d. And error message: %s ", exitCode, msg)) + result := cmd.exec() + if result.code != 0 { + log.Fatal(fmt.Sprintf("Command returned with exit code: %d. And error message: %s ", result.code, result.errors)) } else { - if (verbose || showDiff) && msg != "" { - fmt.Println(msg) + if (flags.verbose || flags.showDiff) && result.output != "" { + fmt.Println(result.output) } } - return msg + return result.output } // upgradeRelease upgrades an existing release with the specified values.yaml -func (r *release) upgrade() { +func (r *release) upgrade(p *plan) { var force string - if forceUpgrades { + if flags.forceUpgrades { force = "--force" } cmd := helmCmd(concat(r.getHelmArgsFor("upgrade"), []string{force}, r.getWait(), r.getHelmFlags()), "Upgrading release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") - outcome.addCommand(cmd, r.Priority, r) + p.addCommand(cmd, r.Priority, r) } // reInstall purge deletes a release and reinstalls it. // This is used when moving a release to another namespace or when changing the chart used for it. -func (r *release) reInstall(rs helmRelease) { +func (r *release) reInstall(rs helmRelease, p *plan) { - delCmd := helmCmd(concat([]string{"delete", "--purge", r.Name}, getDryRunFlags()), "Deleting release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") - outcome.addCommand(delCmd, r.Priority, r) + delCmd := helmCmd(concat([]string{"delete", "--purge", r.Name}, flags.getDryRunFlags()), "Deleting release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") + p.addCommand(delCmd, r.Priority, r) installCmd := helmCmd(r.getHelmArgsFor("install"), "Installing release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") - outcome.addCommand(installCmd, r.Priority, r) + p.addCommand(installCmd, r.Priority, r) } // rollbackRelease evaluates if a rollback action needs to be taken for a given release. // if the release is already deleted but from a different namespace than the one specified in input, // it purge deletes it and create it in the specified namespace. -func (r *release) rollback(cs currentState) { - rs, ok := cs[r.key()] +func (r *release) rollback(cs *currentState, p *plan) { + rs, ok := cs.releases[r.key()] if !ok { return } if r.Namespace == rs.Namespace { - cmd := helmCmd(concat([]string{"rollback", r.Name, rs.getRevision()}, r.getWait(), r.getTimeout(), r.getNoHooks(), getDryRunFlags()), "Rolling back release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") - outcome.addCommand(cmd, r.Priority, r) - r.upgrade() // this is to reflect any changes in values file(s) - logDecision("Release [ "+r.Name+" ] was deleted and is desired to be rolled back to "+ + cmd := helmCmd(concat([]string{"rollback", r.Name, rs.getRevision()}, r.getWait(), r.getTimeout(), r.getNoHooks(), flags.getDryRunFlags()), "Rolling back release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") + p.addCommand(cmd, r.Priority, r) + r.upgrade(p) // this is to reflect any changes in values file(s) + p.addDecision("Release [ "+r.Name+" ] was deleted and is desired to be rolled back to "+ "namespace [ "+r.Namespace+" ]", r.Priority, create) } else { - r.reInstall(rs) - logDecision("Release [ "+r.Name+" ] is deleted BUT from namespace [ "+rs.Namespace+ + r.reInstall(rs, p) + p.addDecision("Release [ "+r.Name+" ] is deleted BUT from namespace [ "+rs.Namespace+ " ]. Will purge delete it from there and install it in namespace [ "+r.Namespace+" ]", r.Priority, create) - logDecision("WARNING: rolling back release [ "+r.Name+" ] from [ "+rs.Namespace+" ] to [ "+r.Namespace+ + p.addDecision("WARNING: rolling back release [ "+r.Name+" ] from [ "+rs.Namespace+" ] to [ "+r.Namespace+ " ] might not correctly connect to existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/apps/moving_across_namespaces.md"+ " for details if this release uses PV and PVC.", r.Priority, create) - } } @@ -346,7 +339,7 @@ func (r *release) rollback(cs currentState) { // A protected is release is either: a) deployed in a protected namespace b) flagged as protected in the desired state file // Any release in a protected namespace is protected by default regardless of its flag // returns true if a release is protected, false otherwise -func (r *release) isProtected(cs currentState) bool { +func (r *release) isProtected(cs *currentState, s *state) bool { // if the release does not exist in the cluster, it is not protected if ok := cs.releaseExists(r, ""); !ok { return false @@ -450,10 +443,10 @@ func (r *release) getDesiredNamespace() string { // getHelmFlags returns helm flags func (r *release) getHelmFlags() []string { - var flags []string + var flgs []string - flags = append(flags, r.HelmFlags...) - return concat(r.getNoHooks(), r.getTimeout(), getDryRunFlags(), flags) + flgs = append(flgs, r.HelmFlags...) + return concat(r.getNoHooks(), r.getTimeout(), flags.getDryRunFlags(), flgs) } func (r *release) getHelmArgsFor(action string) []string { @@ -468,9 +461,9 @@ func (r *release) getHelmArgsFor(action string) []string { } func (r *release) checkChartDepUpdate() { - if updateDeps && isLocalChart(r.Chart) { - if ok, err := updateChartDep(r.Chart); !ok { - log.Fatal("helm dependency update failed: " + err) + if flags.updateDeps && isLocalChart(r.Chart) { + if err := updateChartDep(r.Chart); err != nil { + log.Fatal("helm dependency update failed: " + err.Error()) } } } diff --git a/internal/app/release_test.go b/internal/app/release_test.go index 4cb72e7d..38aebece 100644 --- a/internal/app/release_test.go +++ b/internal/app/release_test.go @@ -3,7 +3,6 @@ package app import ( "fmt" "os" - "strings" "testing" ) @@ -12,8 +11,8 @@ func setupTestCase(t *testing.T) func(t *testing.T) { os.MkdirAll(os.TempDir()+"/helmsman-tests/myapp", os.ModePerm) os.MkdirAll(os.TempDir()+"/helmsman-tests/dir-with space/myapp", os.ModePerm) cmd := helmCmd([]string{"create", os.TempDir() + "/helmsman-tests/dir-with space/myapp"}, "creating an empty local chart directory") - if exitCode, msg, _ := cmd.exec(debug, verbose); exitCode != 0 { - log.Fatal(fmt.Sprintf("Command returned with exit code: %d. And error message: %s ", exitCode, msg)) + if result := cmd.exec(); result.code != 0 { + log.Fatal(fmt.Sprintf("Command returned with exit code: %d. And error message: %s ", result.code, result.errors)) } return func(t *testing.T) { @@ -37,10 +36,9 @@ func Test_validateRelease(t *testing.T) { r *release } tests := []struct { - name string - args args - want bool - want1 string + name string + args args + want string }{ { name: "test case 1", @@ -57,8 +55,7 @@ func Test_validateRelease(t *testing.T) { }, s: st, }, - want: true, - want1: "", + want: "", }, { name: "test case 2", args: args{ @@ -74,8 +71,7 @@ func Test_validateRelease(t *testing.T) { }, s: st, }, - want: false, - want1: "valuesFile must be a valid relative (from dsf file) file path for a yaml file, or can be left empty (provided path resolved to \"xyz.yaml\").", + want: "valuesFile must be a valid relative (from dsf file) file path for a yaml file, or can be left empty (provided path resolved to \"xyz.yaml\").", }, { name: "test case 3", args: args{ @@ -91,8 +87,7 @@ func Test_validateRelease(t *testing.T) { }, s: st, }, - want: false, - want1: "valuesFile must be a valid relative (from dsf file) file path for a yaml file, or can be left empty (provided path resolved to \"../../tests/values.xml\").", + want: "valuesFile must be a valid relative (from dsf file) file path for a yaml file, or can be left empty (provided path resolved to \"../../tests/values.xml\").", }, { name: "test case 4", args: args{ @@ -108,8 +103,7 @@ func Test_validateRelease(t *testing.T) { }, s: st, }, - want: false, - want1: "release name must be unique within a given Tiller.", + want: "release name must be unique within a given namespace.", }, { name: "test case 5", args: args{ @@ -125,8 +119,7 @@ func Test_validateRelease(t *testing.T) { }, s: st, }, - want: true, - want1: "", + want: "", }, { name: "test case 6", args: args{ @@ -142,8 +135,7 @@ func Test_validateRelease(t *testing.T) { }, s: st, }, - want: false, - want1: "release targeted namespace can't be empty.", + want: "release targeted namespace can't be empty.", }, { name: "test case 7", args: args{ @@ -159,8 +151,7 @@ func Test_validateRelease(t *testing.T) { }, s: st, }, - want: false, - want1: "chart can't be empty and must be of the format: repo/chart.", + want: "chart can't be empty and must be of the format: repo/chart.", }, { name: "test case 8", args: args{ @@ -176,8 +167,7 @@ func Test_validateRelease(t *testing.T) { }, s: st, }, - want: false, - want1: "chart can't be empty and must be of the format: repo/chart.", + want: "chart can't be empty and must be of the format: repo/chart.", }, { name: "test case 9", args: args{ @@ -193,8 +183,7 @@ func Test_validateRelease(t *testing.T) { }, s: st, }, - want: false, - want1: "version can't be empty.", + want: "version can't be empty.", }, { name: "test case 10", args: args{ @@ -210,8 +199,7 @@ func Test_validateRelease(t *testing.T) { }, s: st, }, - want: true, - want1: "", + want: "", }, { name: "test case 11", args: args{ @@ -228,8 +216,7 @@ func Test_validateRelease(t *testing.T) { }, s: st, }, - want: false, - want1: "valuesFile and valuesFiles should not be used together.", + want: "valuesFile and valuesFiles should not be used together.", }, { name: "test case 12", args: args{ @@ -245,8 +232,7 @@ func Test_validateRelease(t *testing.T) { }, s: st, }, - want: false, - want1: "valuesFiles must be valid relative (from dsf file) file paths for a yaml file; path at index 0 provided path resolved to \"xyz.yaml\".", + want: "valuesFiles must be valid relative (from dsf file) file paths for a yaml file; path at index 0 provided path resolved to \"xyz.yaml\".", }, { name: "test case 13", args: args{ @@ -262,8 +248,7 @@ func Test_validateRelease(t *testing.T) { }, s: st, }, - want: true, - want1: "", + want: "", }, { name: "test case 14", args: args{ @@ -280,20 +265,19 @@ func Test_validateRelease(t *testing.T) { }, s: st, }, - want: false, - want1: "env var [ $SOME_VAR ] is not set, but is wanted to be passed for [ some_var ] in [[ release14 ]]", + want: "env var [ $SOME_VAR ] is not set, but is wanted to be passed for [ some_var ] in [[ release14 ]]", }, } names := make(map[string]map[string]bool) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, got1 := tt.args.r.validate("testApp", names, &tt.args.s) + got := "" + if r := tt.args.r.validate("testApp", names, &tt.args.s); r != nil { + got = r.Error() + } if got != tt.want { t.Errorf("validateRelease() got = %v, want %v", got, tt.want) } - if strings.TrimSpace(got1) != tt.want1 { - t.Errorf("validateRelease() got1 = %v, want %v", got1, tt.want1) - } }) } } @@ -490,15 +474,17 @@ func Test_validateReleaseCharts(t *testing.T) { defer teardownTestCase(t) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - targetMap = make(map[string]bool) - groupMap = make(map[string]bool) + stt := &state{} + stt.Apps = tt.args.apps + stt.TargetMap = make(map[string]bool) + stt.GroupMap = make(map[string]bool) for _, target := range tt.targetFlag { - targetMap[target] = true + stt.TargetMap[target] = true } for _, group := range tt.groupFlag { - groupMap[group] = true + stt.GroupMap[group] = true } - err := validateReleaseCharts(tt.args.apps) + err := validateReleaseCharts(stt) switch err.(type) { case nil: if tt.want != true { diff --git a/internal/app/state.go b/internal/app/state.go index 0992ec12..8fd130b1 100644 --- a/internal/app/state.go +++ b/internal/app/state.go @@ -36,6 +36,8 @@ type state struct { PreconfiguredHelmRepos []string `yaml:"preconfiguredHelmRepos"` Apps map[string]*release `yaml:"apps"` AppsTemplates map[string]*release `yaml:"appsTemplates,omitempty"` + TargetMap map[string]bool + GroupMap map[string]bool } // invokes either yaml or toml parser considering file extension @@ -63,12 +65,19 @@ func (s *state) toFile(file string) { // check https://github.com/Praqma/Helmsman/docs/desired_state_spec.md for the detailed specification func (s *state) validate() error { + // apps + if s.Apps == nil { + log.Info("No apps specified. Nothing to be executed.") + os.Exit(0) + } + // settings if (s.Settings == (config{}) || s.Settings.KubeContext == "") && !getKubeContext() { return errors.New("settings validation failed -- you have not defined a " + "kubeContext to use. Either define it in the desired state file or pass a kubeconfig with --kubeconfig to use an existing context") - } else if s.Settings.ClusterURI != "" { + } + if s.Settings.ClusterURI != "" { if _, err := url.ParseRequestURI(s.Settings.ClusterURI); err != nil { return errors.New("settings validation failed -- clusterURI must have a valid URL set in an env variable or passed directly. Either the env var is missing/empty or the URL is invalid") } @@ -130,14 +139,17 @@ func (s *state) validate() error { } } + if (s.Settings.EyamlPrivateKeyPath != "" && s.Settings.EyamlPublicKeyPath == "") || (s.Settings.EyamlPrivateKeyPath == "" && s.Settings.EyamlPublicKeyPath != "") { + return errors.New("both EyamlPrivateKeyPath and EyamlPublicKeyPath are required") + } + // namespaces - if nsOverride == "" { + if flags.nsOverride == "" { if s.Namespaces == nil || len(s.Namespaces) == 0 { return errors.New("namespaces validation failed -- at least one namespace is required") } - } else { - log.Info("ns-override is used to override all namespaces with [ " + nsOverride + " ] Skipping defined namespaces validation.") + log.Info("ns-override is used to override all namespaces with [ " + flags.nsOverride + " ] Skipping defined namespaces validation.") } // repos @@ -147,22 +159,12 @@ func (s *state) validate() error { return errors.New("repos validation failed -- repo [" + k + " ] " + "must have a valid URL") } - - continue - - } - - // apps - if s.Apps == nil { - log.Info("No apps specified. Nothing to be executed.") - os.Exit(0) } names := make(map[string]map[string]bool) for appLabel, r := range s.Apps { - result, errMsg := r.validate(appLabel, names, s) - if !result { - return errors.New("apps validation failed -- for app [" + appLabel + " ]. " + errMsg) + if err := r.validate(appLabel, names, s); err != nil { + return fmt.Errorf("apps validation failed -- for app ["+appLabel+" ]. %w", err) } } @@ -219,4 +221,14 @@ func (s *state) print() { for _, r := range s.Apps { r.print() } + fmt.Println("\nTargets: ") + fmt.Println("--------------- ") + for t := range s.TargetMap { + fmt.Println(t) + } + fmt.Println("\nGroups: ") + fmt.Println("--------------- ") + for g := range s.GroupMap { + fmt.Println(g) + } } diff --git a/internal/app/utils.go b/internal/app/utils.go index 26774550..0b24620e 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -47,10 +47,10 @@ func fromTOML(file string, s *state) (bool, string) { } tomlFile := string(rawTomlFile) - if !noEnvSubst { + if !flags.noEnvSubst { tomlFile = substituteEnv(tomlFile) } - if !noSSMSubst { + if !flags.noSSMSubst { tomlFile = substituteSSM(tomlFile) } @@ -99,10 +99,10 @@ func fromYAML(file string, s *state) (bool, string) { } yamlFile := string(rawYamlFile) - if !noEnvSubst { + if !flags.noEnvSubst { yamlFile = substituteEnv(yamlFile) } - if !noSSMSubst { + if !flags.noSSMSubst { yamlFile = substituteSSM(yamlFile) } @@ -168,10 +168,10 @@ func substituteVarsInYaml(file string) string { } yamlFile := string(rawYamlFile) - if !noEnvSubst && !noEnvValuesSubst { + if !flags.noEnvSubst && !flags.noEnvValuesSubst { yamlFile = substituteEnv(yamlFile) } - if !noSSMSubst && !noSSMValuesSubst { + if !flags.noSSMSubst && !flags.noSSMValuesSubst { yamlFile = substituteSSM(yamlFile) } @@ -200,7 +200,7 @@ func stringInSlice(a string, list []string) bool { // addDefaultHelmRepos adds stable and incubator helm repos to the state if they are not already defined func addDefaultHelmRepos(s *state) { - if noDefaultRepos { + if flags.noDefaultRepos { log.Info("Default helm repo set disabled, 'stable' and 'incubator' repos unset.") return } @@ -318,7 +318,7 @@ func substituteSSM(name string) string { if err != nil { fmt.Printf("Invalid decryption argument %T \n", string(match[3])) } - value := aws.ReadSSMParam(paramPath, withDecryption, noColors) + value := aws.ReadSSMParam(paramPath, withDecryption, flags.noColors) name = strings.ReplaceAll(name, placeholder, value) } } @@ -341,12 +341,12 @@ func downloadFile(path string, outfile string) string { if strings.HasPrefix(path, "s3") { tmp := getBucketElements(path) - aws.ReadFile(tmp["bucketName"], tmp["filePath"], outfile, noColors) + aws.ReadFile(tmp["bucketName"], tmp["filePath"], outfile, flags.noColors) } else if strings.HasPrefix(path, "gs") { tmp := getBucketElements(path) - msg, err := gcs.ReadFile(tmp["bucketName"], tmp["filePath"], outfile, noColors) + msg, err := gcs.ReadFile(tmp["bucketName"], tmp["filePath"], outfile, flags.noColors) if err != nil { log.Fatal(msg) } @@ -354,7 +354,7 @@ func downloadFile(path string, outfile string) string { } else if strings.HasPrefix(path, "az") { tmp := getBucketElements(path) - azure.ReadFile(tmp["bucketName"], tmp["filePath"], outfile, noColors) + azure.ReadFile(tmp["bucketName"], tmp["filePath"], outfile, flags.noColors) } else { @@ -533,18 +533,18 @@ func decryptSecret(name string) error { Description: "Decrypting " + name, } - exitCode, output, stderr := command.exec(debug, false) + result := command.exec() if !settings.EyamlEnabled { _, fileNotFound := os.Stat(name + ".dec") if fileNotFound != nil && !isOfType(name, []string{".dec"}) { - return errors.New(output) + return errors.New(result.errors) } } - if exitCode != 0 { - return errors.New(output) - } else if stderr != "" { - return errors.New(output) + if result.code != 0 { + return errors.New(result.errors) + } else if result.errors != "" { + return errors.New(result.errors) } if settings.EyamlEnabled { @@ -554,7 +554,7 @@ func decryptSecret(name string) error { } else { outfile = name + ".dec" } - err := writeStringToFile(outfile, output) + err := writeStringToFile(outfile, result.output) if err != nil { log.Fatal("Can't write [ " + outfile + " ] file") } From 1d2098f1523078517c3ed33f029b02dfa7700d91 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 31 Dec 2019 12:20:07 +0000 Subject: [PATCH 0552/1127] Preparing new release --- README.md | 6 +++--- docs/best_practice.md | 2 +- docs/cmd_reference.md | 2 +- docs/deployment_strategies.md | 4 ++-- docs/desired_state_specification.md | 2 +- docs/how_to/apps/basic.md | 2 +- docs/how_to/apps/destroy.md | 2 +- docs/how_to/apps/helm_tests.md | 2 +- docs/how_to/apps/moving_across_namespaces.md | 2 +- docs/how_to/apps/order.md | 2 +- docs/how_to/apps/override_namespaces.md | 2 +- docs/how_to/apps/secrets.md | 2 +- docs/how_to/deployments/ci.md | 4 ++-- docs/how_to/helm_repos/default.md | 4 ++-- docs/how_to/misc/merge_desired_state_files.md | 4 ++-- examples/example.toml | 2 +- examples/example.yaml | 2 +- internal/app/main.go | 2 +- release-notes.md | 2 +- 19 files changed, 25 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 07c2f107..138790dd 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.0.0-beta3&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.0.0-beta4&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -61,9 +61,9 @@ If you use private helm repos, you will need either `helm-gcs` or `helm-s3` plug Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. ``` # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.0.0-beta3/helmsman_3.0.0-beta3_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.0.0-beta4/helmsman_3.0.0-beta4_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.0.0-beta3/helmsman_3.0.0-beta3_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.0.0-beta4/helmsman_3.0.0-beta4_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/docs/best_practice.md b/docs/best_practice.md index faf71be1..73ada3da 100644 --- a/docs/best_practice.md +++ b/docs/best_practice.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta3 +version: v3.0.0-beta4 --- # Best Practice diff --git a/docs/cmd_reference.md b/docs/cmd_reference.md index 66d729bf..a27af4de 100644 --- a/docs/cmd_reference.md +++ b/docs/cmd_reference.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta3 +version: v3.0.0-beta4 --- # CMD reference diff --git a/docs/deployment_strategies.md b/docs/deployment_strategies.md index cfb02f7c..a35b0988 100644 --- a/docs/deployment_strategies.md +++ b/docs/deployment_strategies.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta3 +version: v3.0.0-beta4 --- # Deployment Strategies @@ -135,7 +135,7 @@ If you need supporting applications (charts) for your application (e.g, reverse ## Notes on using multiple Helmsman desired state files for the same cluster -Helmsman v3.0.0-beta3 introduces the `context` stanza. +Helmsman v3.0.0-beta4 introduces the `context` stanza. When having multiple DSFs operating on different releases, it is essential to use the `context` stanza in each DSF to define what context the DSF covers. The user-provided value for `context` is used by Helmsman to label and distinguish which DSF manages which deployed releases in the cluster. This way, each helmsman operation will only operate on releases within the context defined in the DSF. When having multiple DSFs be aware of the following: diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 4e0957fd..78153b84 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta3 +version: v3.0.0-beta4 --- # Helmsman desired state specification diff --git a/docs/how_to/apps/basic.md b/docs/how_to/apps/basic.md index 1aebcdde..47aeef5d 100644 --- a/docs/how_to/apps/basic.md +++ b/docs/how_to/apps/basic.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta3 +version: v3.0.0-beta4 --- # Install releases diff --git a/docs/how_to/apps/destroy.md b/docs/how_to/apps/destroy.md index 8c211d7f..84e319d0 100644 --- a/docs/how_to/apps/destroy.md +++ b/docs/how_to/apps/destroy.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta3 +version: v3.0.0-beta4 --- # Delete all deployed releases diff --git a/docs/how_to/apps/helm_tests.md b/docs/how_to/apps/helm_tests.md index c9f9b0a4..bd8a12d6 100644 --- a/docs/how_to/apps/helm_tests.md +++ b/docs/how_to/apps/helm_tests.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta3 +version: v3.0.0-beta4 --- # Test charts diff --git a/docs/how_to/apps/moving_across_namespaces.md b/docs/how_to/apps/moving_across_namespaces.md index aeefc664..9c578b1e 100644 --- a/docs/how_to/apps/moving_across_namespaces.md +++ b/docs/how_to/apps/moving_across_namespaces.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta3 +version: v3.0.0-beta4 --- # Move charts across namespaces diff --git a/docs/how_to/apps/order.md b/docs/how_to/apps/order.md index 954fb4b3..5daeb9f1 100644 --- a/docs/how_to/apps/order.md +++ b/docs/how_to/apps/order.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta3 +version: v3.0.0-beta4 --- # Using the priority key for Apps diff --git a/docs/how_to/apps/override_namespaces.md b/docs/how_to/apps/override_namespaces.md index 577db438..1609f82e 100644 --- a/docs/how_to/apps/override_namespaces.md +++ b/docs/how_to/apps/override_namespaces.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta3 +version: v3.0.0-beta4 --- # Override defined namespaces from command line diff --git a/docs/how_to/apps/secrets.md b/docs/how_to/apps/secrets.md index 932b91fa..7ed42eab 100644 --- a/docs/how_to/apps/secrets.md +++ b/docs/how_to/apps/secrets.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta3 +version: v3.0.0-beta4 --- # Passing secrets from env variables: diff --git a/docs/how_to/deployments/ci.md b/docs/how_to/deployments/ci.md index a498ada8..66954a74 100644 --- a/docs/how_to/deployments/ci.md +++ b/docs/how_to/deployments/ci.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta3 +version: v3.0.0-beta4 --- # Run Helmsman in CI @@ -13,7 +13,7 @@ jobs: deploy-apps: docker: - - image: praqma/helmsman:v3.0.0-beta3 + - image: praqma/helmsman:v3.0.0-beta4 steps: - checkout - run: diff --git a/docs/how_to/helm_repos/default.md b/docs/how_to/helm_repos/default.md index a69de620..3ba240aa 100644 --- a/docs/how_to/helm_repos/default.md +++ b/docs/how_to/helm_repos/default.md @@ -1,10 +1,10 @@ --- -version: v3.0.0-beta3 +version: v3.0.0-beta4 --- # Default helm repos -Helm v3 no longer adds the `stable` and `incubator` repos by default. However, Helmsman v3.0.0-beta3 still adds these two repos by default. These two DO NOT need to be defined explicitly in your desired state file (DSF). However, if you would like to configure some repo with the name stable for example, you can override the default repo. +Helm v3 no longer adds the `stable` and `incubator` repos by default. However, Helmsman v3.0.0-beta4 still adds these two repos by default. These two DO NOT need to be defined explicitly in your desired state file (DSF). However, if you would like to configure some repo with the name stable for example, you can override the default repo. > You can disable the automatic addition of these two repos, use the `--no-default-repos` flag. diff --git a/docs/how_to/misc/merge_desired_state_files.md b/docs/how_to/misc/merge_desired_state_files.md index 44e0c939..2343a8a4 100644 --- a/docs/how_to/misc/merge_desired_state_files.md +++ b/docs/how_to/misc/merge_desired_state_files.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta3 +version: v3.0.0-beta4 --- # Supply multiple desired state files @@ -47,7 +47,7 @@ $ helmsman -f common.toml -f nonprod.toml ... When using multiple DSFs -and since Helmsman doesn't maintain any external state-, it has been possible for operations from one DSF to cause problems to releases deployed by other DSFs. A typical example is that releases deployed by other DSFs are considered `untracked` and get scheduled for deleting. Workarounds existed (e.g. using the `--keep-untracked-releases`, `--target` and `--group` flags). -Starting from Helmsman v3.0.0-beta3, `context` is introduced to define the context in which a DSF is used. This context is used as the ID of that specific DSF and must be unique across the used DSFs. The context is then used to label the different releases to link them to the DSF they were first deployed from. These labels are then checked by Helmsman on each run to make sure operations are limited to releases from a specific context. +Starting from Helmsman v3.0.0-beta4, `context` is introduced to define the context in which a DSF is used. This context is used as the ID of that specific DSF and must be unique across the used DSFs. The context is then used to label the different releases to link them to the DSF they were first deployed from. These labels are then checked by Helmsman on each run to make sure operations are limited to releases from a specific context. Here is how it is used: diff --git a/examples/example.toml b/examples/example.toml index 6dd02b0e..8ca92b94 100644 --- a/examples/example.toml +++ b/examples/example.toml @@ -1,4 +1,4 @@ -# version: v3.0.0-beta3 +# version: v3.0.0-beta4 # context defines the context of this Desired State File. # It is used to allow Helmsman identify which releases are managed by which DSF. diff --git a/examples/example.yaml b/examples/example.yaml index 4a4830a0..d843efd5 100644 --- a/examples/example.yaml +++ b/examples/example.yaml @@ -1,4 +1,4 @@ -# version: v3.0.0-beta3 +# version: v3.0.0-beta4 # metadata -- add as many key/value pairs as you want metadata: org: "example.com/$ORG_PATH/" diff --git a/internal/app/main.go b/internal/app/main.go index a148cbc0..5c1e644c 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -6,7 +6,7 @@ import ( const ( helmBin = "helm" - appVersion = "v3.0.0-beta3" + appVersion = "v3.0.0-beta4" tempFilesDir = ".helmsman-tmp" stableHelmRepo = "https://kubernetes-charts.storage.googleapis.com" incubatorHelmRepo = "http://storage.googleapis.com/kubernetes-charts-incubator" diff --git a/release-notes.md b/release-notes.md index bd8dbdf8..9a0ab25c 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,4 +1,4 @@ -# v3.0.0-beta3 +# v3.0.0-beta4 This is a major release to support Helm v3. It is recommended you read the [Helm 3 migration guide](https://helm.sh/docs/topics/v2_v3_migration/) before using this release. From 7407cfb8e3699a0aafddb92625fd76b9002e19bb Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 31 Dec 2019 19:02:12 +0000 Subject: [PATCH 0553/1127] Don't remove untracked releasees from namespaces not tracked by the current desired state --- internal/app/decision_maker.go | 6 ++++++ internal/app/release.go | 2 +- internal/app/state.go | 4 ++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index 46793af8..5a4e1cd2 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -184,6 +184,11 @@ func (cs *currentState) getHelmsmanReleases(s *state) map[string]map[string]bool if _, ok := releases[ns]; !ok { releases[ns] = make(map[string]bool) } + if !s.isNamespaceDefined(ns) { + // if the namespace is not managed by this desired state we assume it's tracked + releases[ns][name] = true + continue + } if rctx != s.Context { // if the release is not related to the current context we assume it's tracked releases[ns][name] = true @@ -193,6 +198,7 @@ func (cs *currentState) getHelmsmanReleases(s *state) map[string]map[string]bool for _, app := range s.Apps { if app.Name == name && app.Namespace == ns { releases[ns][name] = true + break } } } diff --git a/internal/app/release.go b/internal/app/release.go index 5d51dff7..88a2294c 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -77,7 +77,7 @@ func (r *release) validate(appLabel string, names map[string]map[string]bool, s if flags.nsOverride == "" && r.Namespace == "" { return errors.New("release targeted namespace can't be empty.") - } else if flags.nsOverride == "" && r.Namespace != "" && r.Namespace != "kube-system" && !s.checkNamespaceDefined(r.Namespace) { + } else if flags.nsOverride == "" && r.Namespace != "" && r.Namespace != "kube-system" && !s.isNamespaceDefined(r.Namespace) { return errors.New("release " + r.Name + " is using namespace [ " + r.Namespace + " ] which is not defined in the Namespaces section of your desired state file." + " Release [ " + r.Name + " ] can't be installed in that Namespace until its defined.") } diff --git a/internal/app/state.go b/internal/app/state.go index 8fd130b1..7c392f98 100644 --- a/internal/app/state.go +++ b/internal/app/state.go @@ -181,8 +181,8 @@ func isValidCert(value string) (bool, string) { return true, value } -// checkNamespaceDefined checks if a given namespace is defined in the namespaces section of the desired state file -func (s *state) checkNamespaceDefined(ns string) bool { +// isNamespaceDefined checks if a given namespace is defined in the namespaces section of the desired state file +func (s *state) isNamespaceDefined(ns string) bool { _, ok := s.Namespaces[ns] return ok } From 96694a3f7ede621f75634313a3c21101a0e2123b Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Wed, 1 Jan 2020 23:40:57 +0000 Subject: [PATCH 0554/1127] label releases regardless of the helm command result --- internal/app/kube_helpers.go | 14 -------------- internal/app/plan.go | 17 ++++++++--------- internal/app/release.go | 14 ++++++++++++++ 3 files changed, 22 insertions(+), 23 deletions(-) diff --git a/internal/app/kube_helpers.go b/internal/app/kube_helpers.go index 80c63c3d..87f993a6 100644 --- a/internal/app/kube_helpers.go +++ b/internal/app/kube_helpers.go @@ -257,20 +257,6 @@ func getKubeContext() bool { return true } -// labelResource applies Helmsman specific labels to Helm's state resources (secrets/configmaps) -func labelResource(r *release) { - if r.Enabled { - storageBackend := settings.StorageBackend - - cmd := kubectl([]string{"label", storageBackend, "-n", r.Namespace, "-l", "owner=helm,name=" + r.Name, "MANAGED-BY=HELMSMAN", "NAMESPACE=" + r.Namespace, "HELMSMAN_CONTEXT=" + curContext, "--overwrite"}, "Applying Helmsman labels to [ "+r.Name+" ] release") - - result := cmd.exec() - if result.code != 0 { - log.Fatal(result.errors) - } - } -} - // getReleaseContext extracts the Helmsman release context from the helm storage driver objects (secret or configmap) labels func getReleaseContext(releaseName string, namespace string) string { storageBackend := settings.StorageBackend diff --git a/internal/app/plan.go b/internal/app/plan.go index faa4d92d..f98d8cf7 100644 --- a/internal/app/plan.go +++ b/internal/app/plan.go @@ -88,17 +88,18 @@ func (p *plan) exec() { for _, cmd := range p.Commands { log.Notice(cmd.Command.Description) - if result := cmd.Command.exec(); result.code != 0 { - var errorMsg string - if errorMsg = result.errors; !flags.verbose { + result := cmd.Command.exec() + if cmd.targetRelease != nil && !flags.dryRun && !flags.destroy { + cmd.targetRelease.label() + } + if result.code != 0 { + errorMsg := result.errors + if !flags.verbose { errorMsg = strings.Split(result.errors, "---")[0] } log.Fatal(fmt.Sprintf("Command returned [ %d ] exit code and error message [ %s ]", result.code, errorMsg)) } else { log.Notice(result.output) - if cmd.targetRelease != nil && !flags.dryRun { - labelResource(cmd.targetRelease) - } log.Notice("Finished: " + cmd.Command.Description) if _, err := url.ParseRequestURI(settings.SlackWebhook); err == nil { notifySlack(cmd.Command.Description+" ... SUCCESS!", settings.SlackWebhook, false, true) @@ -123,9 +124,7 @@ func (p *plan) printCmds() { func (p *plan) print() { log.Notice("-------- PLAN starts here --------------") for _, decision := range p.Decisions { - if decision.Type == ignored { - log.Info(decision.Description + " -- priority: " + strconv.Itoa(decision.Priority)) - } else if decision.Type == noop { + if decision.Type == ignored || decision.Type == noop { log.Info(decision.Description + " -- priority: " + strconv.Itoa(decision.Priority)) } else if decision.Type == delete { log.Warning(decision.Description + " -- priority: " + strconv.Itoa(decision.Priority)) diff --git a/internal/app/release.go b/internal/app/release.go index 88a2294c..428022cc 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -335,6 +335,20 @@ func (r *release) rollback(cs *currentState, p *plan) { } } +// label applies Helmsman specific labels to Helm's state resources (secrets/configmaps) +func (r *release) label() { + if r.Enabled { + storageBackend := settings.StorageBackend + + cmd := kubectl([]string{"label", storageBackend, "-n", r.Namespace, "-l", "owner=helm,name=" + r.Name, "MANAGED-BY=HELMSMAN", "NAMESPACE=" + r.Namespace, "HELMSMAN_CONTEXT=" + curContext, "--overwrite"}, "Applying Helmsman labels to [ "+r.Name+" ] release") + + result := cmd.exec() + if result.code != 0 { + log.Fatal(result.errors) + } + } +} + // isProtected checks if a release is protected or not. // A protected is release is either: a) deployed in a protected namespace b) flagged as protected in the desired state file // Any release in a protected namespace is protected by default regardless of its flag From 32da43a4e144c39ed1c8374217c4be51c1b829f0 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Wed, 1 Jan 2020 23:53:27 +0000 Subject: [PATCH 0555/1127] code linting --- internal/app/kube_helpers.go | 2 +- internal/app/release.go | 22 +++++++++++----------- internal/app/release_test.go | 18 +++++++++--------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/internal/app/kube_helpers.go b/internal/app/kube_helpers.go index 87f993a6..f8895b43 100644 --- a/internal/app/kube_helpers.go +++ b/internal/app/kube_helpers.go @@ -220,7 +220,7 @@ func createContext(s *state) error { return nil } - return errors.New("something went wrong while setting the kube context to the newly created one.") + return errors.New("something went wrong while setting the kube context to the newly created one") } // setKubeContext sets your kubectl context to the one specified in the desired state file. diff --git a/internal/app/release.go b/internal/app/release.go index 428022cc..558eb3ff 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -72,51 +72,51 @@ func (r *release) validate(appLabel string, names map[string]map[string]bool, s } if names[r.Name][r.Namespace] { - return errors.New("release name must be unique within a given namespace.") + return errors.New("release name must be unique within a given namespace") } if flags.nsOverride == "" && r.Namespace == "" { - return errors.New("release targeted namespace can't be empty.") + return errors.New("release targeted namespace can't be empty") } else if flags.nsOverride == "" && r.Namespace != "" && r.Namespace != "kube-system" && !s.isNamespaceDefined(r.Namespace) { return errors.New("release " + r.Name + " is using namespace [ " + r.Namespace + " ] which is not defined in the Namespaces section of your desired state file." + " Release [ " + r.Name + " ] can't be installed in that Namespace until its defined.") } _, err := os.Stat(r.Chart) if r.Chart == "" || os.IsNotExist(err) && !strings.ContainsAny(r.Chart, "/") { - return errors.New("chart can't be empty and must be of the format: repo/chart.") + return errors.New("chart can't be empty and must be of the format: repo/chart") } if r.Version == "" { - return errors.New("version can't be empty.") + return errors.New("version can't be empty") } _, err = os.Stat(r.ValuesFile) if r.ValuesFile != "" && (!isOfType(r.ValuesFile, []string{".yaml", ".yml", ".json"}) || err != nil) { - return errors.New(fmt.Sprintf("valuesFile must be a valid relative (from dsf file) file path for a yaml file, or can be left empty (provided path resolved to %q).", r.ValuesFile)) + return fmt.Errorf("valuesFile must be a valid relative (from dsf file) file path for a yaml file, or can be left empty (provided path resolved to %q)", r.ValuesFile) } else if r.ValuesFile != "" && len(r.ValuesFiles) > 0 { - return errors.New("valuesFile and valuesFiles should not be used together.") + return errors.New("valuesFile and valuesFiles should not be used together") } else if len(r.ValuesFiles) > 0 { for i, filePath := range r.ValuesFiles { if _, pathErr := os.Stat(filePath); !isOfType(filePath, []string{".yaml", ".yml", ".json"}) || pathErr != nil { - return errors.New(fmt.Sprintf("valuesFiles must be valid relative (from dsf file) file paths for a yaml file; path at index %d provided path resolved to %q.", i, filePath)) + return fmt.Errorf("valuesFiles must be valid relative (from dsf file) file paths for a yaml file; path at index %d provided path resolved to %q", i, filePath) } } } _, err = os.Stat(r.SecretsFile) if r.SecretsFile != "" && (!isOfType(r.SecretsFile, []string{".yaml", ".yml", ".json"}) || err != nil) { - return errors.New(fmt.Sprintf("secretsFile must be a valid relative (from dsf file) file path for a yaml file, or can be left empty (provided path resolved to %q).", r.SecretsFile)) + return fmt.Errorf("secretsFile must be a valid relative (from dsf file) file path for a yaml file, or can be left empty (provided path resolved to %q)", r.SecretsFile) } else if r.SecretsFile != "" && len(r.SecretsFiles) > 0 { - return errors.New("secretsFile and secretsFiles should not be used together.") + return errors.New("secretsFile and secretsFiles should not be used together") } else if len(r.SecretsFiles) > 0 { for _, filePath := range r.SecretsFiles { if i, pathErr := os.Stat(filePath); !isOfType(filePath, []string{".yaml", ".yml", ".json"}) || pathErr != nil { - return errors.New(fmt.Sprintf("secretsFiles must be valid relative (from dsf file) file paths for a yaml file; path at index %d provided path resolved to %q.", i, filePath)) + return fmt.Errorf("secretsFiles must be valid relative (from dsf file) file paths for a yaml file; path at index %d provided path resolved to %q", i, filePath) } } } if r.Priority != 0 && r.Priority > 0 { - return errors.New("priority can only be 0 or negative value, positive values are not allowed.") + return errors.New("priority can only be 0 or negative value, positive values are not allowed") } if names[r.Name] == nil { diff --git a/internal/app/release_test.go b/internal/app/release_test.go index 38aebece..e104c971 100644 --- a/internal/app/release_test.go +++ b/internal/app/release_test.go @@ -71,7 +71,7 @@ func Test_validateRelease(t *testing.T) { }, s: st, }, - want: "valuesFile must be a valid relative (from dsf file) file path for a yaml file, or can be left empty (provided path resolved to \"xyz.yaml\").", + want: "valuesFile must be a valid relative (from dsf file) file path for a yaml file, or can be left empty (provided path resolved to \"xyz.yaml\")", }, { name: "test case 3", args: args{ @@ -87,7 +87,7 @@ func Test_validateRelease(t *testing.T) { }, s: st, }, - want: "valuesFile must be a valid relative (from dsf file) file path for a yaml file, or can be left empty (provided path resolved to \"../../tests/values.xml\").", + want: "valuesFile must be a valid relative (from dsf file) file path for a yaml file, or can be left empty (provided path resolved to \"../../tests/values.xml\")", }, { name: "test case 4", args: args{ @@ -103,7 +103,7 @@ func Test_validateRelease(t *testing.T) { }, s: st, }, - want: "release name must be unique within a given namespace.", + want: "release name must be unique within a given namespace", }, { name: "test case 5", args: args{ @@ -135,7 +135,7 @@ func Test_validateRelease(t *testing.T) { }, s: st, }, - want: "release targeted namespace can't be empty.", + want: "release targeted namespace can't be empty", }, { name: "test case 7", args: args{ @@ -151,7 +151,7 @@ func Test_validateRelease(t *testing.T) { }, s: st, }, - want: "chart can't be empty and must be of the format: repo/chart.", + want: "chart can't be empty and must be of the format: repo/chart", }, { name: "test case 8", args: args{ @@ -167,7 +167,7 @@ func Test_validateRelease(t *testing.T) { }, s: st, }, - want: "chart can't be empty and must be of the format: repo/chart.", + want: "chart can't be empty and must be of the format: repo/chart", }, { name: "test case 9", args: args{ @@ -183,7 +183,7 @@ func Test_validateRelease(t *testing.T) { }, s: st, }, - want: "version can't be empty.", + want: "version can't be empty", }, { name: "test case 10", args: args{ @@ -216,7 +216,7 @@ func Test_validateRelease(t *testing.T) { }, s: st, }, - want: "valuesFile and valuesFiles should not be used together.", + want: "valuesFile and valuesFiles should not be used together", }, { name: "test case 12", args: args{ @@ -232,7 +232,7 @@ func Test_validateRelease(t *testing.T) { }, s: st, }, - want: "valuesFiles must be valid relative (from dsf file) file paths for a yaml file; path at index 0 provided path resolved to \"xyz.yaml\".", + want: "valuesFiles must be valid relative (from dsf file) file paths for a yaml file; path at index 0 provided path resolved to \"xyz.yaml\"", }, { name: "test case 13", args: args{ From 00035aea9ca6e98746b39b1c153a2cb6e55e8a82 Mon Sep 17 00:00:00 2001 From: Nathan Flynn Date: Thu, 2 Jan 2020 14:08:07 +0000 Subject: [PATCH 0556/1127] Fix for matching wrong version on local charts https://github.com/Praqma/helmsman/issues/362 --- internal/app/release.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/app/release.go b/internal/app/release.go index 558eb3ff..1d937564 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -157,7 +157,7 @@ func validateReleaseCharts(s *state) error { return nil } -var versionExtractor = regexp.MustCompile(`version:\s?(.*)`) +var versionExtractor = regexp.MustCompile(`[\n]version:\s?(.*)`) func (r *release) validateChart(app string, s *state, wg *sync.WaitGroup, c chan string) { From b26f3338598ffa18987be09e2ba251b00d41525f Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Thu, 2 Jan 2020 18:49:49 +0000 Subject: [PATCH 0557/1127] preparing new release --- README.md | 6 +++--- docs/best_practice.md | 2 +- docs/cmd_reference.md | 2 +- docs/deployment_strategies.md | 4 ++-- docs/desired_state_specification.md | 2 +- docs/how_to/apps/basic.md | 2 +- docs/how_to/apps/destroy.md | 2 +- docs/how_to/apps/helm_tests.md | 2 +- docs/how_to/apps/moving_across_namespaces.md | 2 +- docs/how_to/apps/order.md | 2 +- docs/how_to/apps/override_namespaces.md | 2 +- docs/how_to/apps/secrets.md | 2 +- docs/how_to/deployments/ci.md | 4 ++-- docs/how_to/helm_repos/default.md | 4 ++-- docs/how_to/misc/merge_desired_state_files.md | 4 ++-- examples/example.toml | 2 +- examples/example.yaml | 2 +- internal/app/main.go | 2 +- release-notes.md | 2 +- 19 files changed, 25 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 138790dd..ab24ea7c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.0.0-beta4&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.0.0-beta5&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -61,9 +61,9 @@ If you use private helm repos, you will need either `helm-gcs` or `helm-s3` plug Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. ``` # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.0.0-beta4/helmsman_3.0.0-beta4_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.0.0-beta5/helmsman_3.0.0-beta5_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.0.0-beta4/helmsman_3.0.0-beta4_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.0.0-beta5/helmsman_3.0.0-beta5_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/docs/best_practice.md b/docs/best_practice.md index 73ada3da..425b3a6b 100644 --- a/docs/best_practice.md +++ b/docs/best_practice.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta4 +version: v3.0.0-beta5 --- # Best Practice diff --git a/docs/cmd_reference.md b/docs/cmd_reference.md index a27af4de..e5fe3d0d 100644 --- a/docs/cmd_reference.md +++ b/docs/cmd_reference.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta4 +version: v3.0.0-beta5 --- # CMD reference diff --git a/docs/deployment_strategies.md b/docs/deployment_strategies.md index a35b0988..89341023 100644 --- a/docs/deployment_strategies.md +++ b/docs/deployment_strategies.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta4 +version: v3.0.0-beta5 --- # Deployment Strategies @@ -135,7 +135,7 @@ If you need supporting applications (charts) for your application (e.g, reverse ## Notes on using multiple Helmsman desired state files for the same cluster -Helmsman v3.0.0-beta4 introduces the `context` stanza. +Helmsman v3.0.0-beta5 introduces the `context` stanza. When having multiple DSFs operating on different releases, it is essential to use the `context` stanza in each DSF to define what context the DSF covers. The user-provided value for `context` is used by Helmsman to label and distinguish which DSF manages which deployed releases in the cluster. This way, each helmsman operation will only operate on releases within the context defined in the DSF. When having multiple DSFs be aware of the following: diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 78153b84..d78088eb 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta4 +version: v3.0.0-beta5 --- # Helmsman desired state specification diff --git a/docs/how_to/apps/basic.md b/docs/how_to/apps/basic.md index 47aeef5d..e7e6a5c3 100644 --- a/docs/how_to/apps/basic.md +++ b/docs/how_to/apps/basic.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta4 +version: v3.0.0-beta5 --- # Install releases diff --git a/docs/how_to/apps/destroy.md b/docs/how_to/apps/destroy.md index 84e319d0..99687a94 100644 --- a/docs/how_to/apps/destroy.md +++ b/docs/how_to/apps/destroy.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta4 +version: v3.0.0-beta5 --- # Delete all deployed releases diff --git a/docs/how_to/apps/helm_tests.md b/docs/how_to/apps/helm_tests.md index bd8a12d6..c59629c1 100644 --- a/docs/how_to/apps/helm_tests.md +++ b/docs/how_to/apps/helm_tests.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta4 +version: v3.0.0-beta5 --- # Test charts diff --git a/docs/how_to/apps/moving_across_namespaces.md b/docs/how_to/apps/moving_across_namespaces.md index 9c578b1e..c6ab4f61 100644 --- a/docs/how_to/apps/moving_across_namespaces.md +++ b/docs/how_to/apps/moving_across_namespaces.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta4 +version: v3.0.0-beta5 --- # Move charts across namespaces diff --git a/docs/how_to/apps/order.md b/docs/how_to/apps/order.md index 5daeb9f1..185b4a4f 100644 --- a/docs/how_to/apps/order.md +++ b/docs/how_to/apps/order.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta4 +version: v3.0.0-beta5 --- # Using the priority key for Apps diff --git a/docs/how_to/apps/override_namespaces.md b/docs/how_to/apps/override_namespaces.md index 1609f82e..a9eca736 100644 --- a/docs/how_to/apps/override_namespaces.md +++ b/docs/how_to/apps/override_namespaces.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta4 +version: v3.0.0-beta5 --- # Override defined namespaces from command line diff --git a/docs/how_to/apps/secrets.md b/docs/how_to/apps/secrets.md index 7ed42eab..2c36aa46 100644 --- a/docs/how_to/apps/secrets.md +++ b/docs/how_to/apps/secrets.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta4 +version: v3.0.0-beta5 --- # Passing secrets from env variables: diff --git a/docs/how_to/deployments/ci.md b/docs/how_to/deployments/ci.md index 66954a74..02423345 100644 --- a/docs/how_to/deployments/ci.md +++ b/docs/how_to/deployments/ci.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta4 +version: v3.0.0-beta5 --- # Run Helmsman in CI @@ -13,7 +13,7 @@ jobs: deploy-apps: docker: - - image: praqma/helmsman:v3.0.0-beta4 + - image: praqma/helmsman:v3.0.0-beta5 steps: - checkout - run: diff --git a/docs/how_to/helm_repos/default.md b/docs/how_to/helm_repos/default.md index 3ba240aa..4098310c 100644 --- a/docs/how_to/helm_repos/default.md +++ b/docs/how_to/helm_repos/default.md @@ -1,10 +1,10 @@ --- -version: v3.0.0-beta4 +version: v3.0.0-beta5 --- # Default helm repos -Helm v3 no longer adds the `stable` and `incubator` repos by default. However, Helmsman v3.0.0-beta4 still adds these two repos by default. These two DO NOT need to be defined explicitly in your desired state file (DSF). However, if you would like to configure some repo with the name stable for example, you can override the default repo. +Helm v3 no longer adds the `stable` and `incubator` repos by default. However, Helmsman v3.0.0-beta5 still adds these two repos by default. These two DO NOT need to be defined explicitly in your desired state file (DSF). However, if you would like to configure some repo with the name stable for example, you can override the default repo. > You can disable the automatic addition of these two repos, use the `--no-default-repos` flag. diff --git a/docs/how_to/misc/merge_desired_state_files.md b/docs/how_to/misc/merge_desired_state_files.md index 2343a8a4..5ee0214d 100644 --- a/docs/how_to/misc/merge_desired_state_files.md +++ b/docs/how_to/misc/merge_desired_state_files.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta4 +version: v3.0.0-beta5 --- # Supply multiple desired state files @@ -47,7 +47,7 @@ $ helmsman -f common.toml -f nonprod.toml ... When using multiple DSFs -and since Helmsman doesn't maintain any external state-, it has been possible for operations from one DSF to cause problems to releases deployed by other DSFs. A typical example is that releases deployed by other DSFs are considered `untracked` and get scheduled for deleting. Workarounds existed (e.g. using the `--keep-untracked-releases`, `--target` and `--group` flags). -Starting from Helmsman v3.0.0-beta4, `context` is introduced to define the context in which a DSF is used. This context is used as the ID of that specific DSF and must be unique across the used DSFs. The context is then used to label the different releases to link them to the DSF they were first deployed from. These labels are then checked by Helmsman on each run to make sure operations are limited to releases from a specific context. +Starting from Helmsman v3.0.0-beta5, `context` is introduced to define the context in which a DSF is used. This context is used as the ID of that specific DSF and must be unique across the used DSFs. The context is then used to label the different releases to link them to the DSF they were first deployed from. These labels are then checked by Helmsman on each run to make sure operations are limited to releases from a specific context. Here is how it is used: diff --git a/examples/example.toml b/examples/example.toml index 8ca92b94..8cfdfb01 100644 --- a/examples/example.toml +++ b/examples/example.toml @@ -1,4 +1,4 @@ -# version: v3.0.0-beta4 +# version: v3.0.0-beta5 # context defines the context of this Desired State File. # It is used to allow Helmsman identify which releases are managed by which DSF. diff --git a/examples/example.yaml b/examples/example.yaml index d843efd5..43dc83ff 100644 --- a/examples/example.yaml +++ b/examples/example.yaml @@ -1,4 +1,4 @@ -# version: v3.0.0-beta4 +# version: v3.0.0-beta5 # metadata -- add as many key/value pairs as you want metadata: org: "example.com/$ORG_PATH/" diff --git a/internal/app/main.go b/internal/app/main.go index 5c1e644c..47298c39 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -6,7 +6,7 @@ import ( const ( helmBin = "helm" - appVersion = "v3.0.0-beta4" + appVersion = "v3.0.0-beta5" tempFilesDir = ".helmsman-tmp" stableHelmRepo = "https://kubernetes-charts.storage.googleapis.com" incubatorHelmRepo = "http://storage.googleapis.com/kubernetes-charts-incubator" diff --git a/release-notes.md b/release-notes.md index 9a0ab25c..76d71ed8 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,4 +1,4 @@ -# v3.0.0-beta4 +# v3.0.0-beta5 This is a major release to support Helm v3. It is recommended you read the [Helm 3 migration guide](https://helm.sh/docs/topics/v2_v3_migration/) before using this release. From dea444866413c698d60c77102267934f215908d5 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Fri, 3 Jan 2020 11:32:47 +0100 Subject: [PATCH 0558/1127] stop adding stable helm repos by default --- docs/cmd_reference.md | 3 --- docs/how_to/helm_repos/default.md | 29 +++++++++++++++++++++++++---- examples/example.toml | 2 ++ examples/example.yaml | 2 ++ internal/app/cli.go | 2 -- internal/app/main.go | 2 -- internal/app/release.go | 2 +- internal/app/utils.go | 25 ++----------------------- release-notes.md | 3 ++- 9 files changed, 34 insertions(+), 36 deletions(-) diff --git a/docs/cmd_reference.md b/docs/cmd_reference.md index e5fe3d0d..955fdc6b 100644 --- a/docs/cmd_reference.md +++ b/docs/cmd_reference.md @@ -44,9 +44,6 @@ This lists available CMD options in Helmsman: `--no-color` don't use colors. - `--no-default-repos` - don't set default Helm repos from Google for 'stable' and 'incubator'. - `--no-env-subst` turn off environment substitution globally. diff --git a/docs/how_to/helm_repos/default.md b/docs/how_to/helm_repos/default.md index 4098310c..86514597 100644 --- a/docs/how_to/helm_repos/default.md +++ b/docs/how_to/helm_repos/default.md @@ -4,11 +4,12 @@ version: v3.0.0-beta5 # Default helm repos -Helm v3 no longer adds the `stable` and `incubator` repos by default. However, Helmsman v3.0.0-beta5 still adds these two repos by default. These two DO NOT need to be defined explicitly in your desired state file (DSF). However, if you would like to configure some repo with the name stable for example, you can override the default repo. +Helm v3 no longer adds the `stable` and `incubator` repos by default. Up to Helmsman v3.0.0-beta5, Helmsman adds these two repos by default. And you can disable the automatic addition of these two repos, use the `--no-default-repos` flag. -> You can disable the automatic addition of these two repos, use the `--no-default-repos` flag. +Starting from `v3.0.0-beta6`, Helmsman complies with the Helm v3 behavior and DOES NOT add `stable` nor `incubator` by default. The `--no-default-repos` is also deprecated. + -This example would have `stable` and `incubator` added by default and another `custom` repo defined explicitly: +This example would have only the `custom` repo defined explicitly: ```toml @@ -26,7 +27,7 @@ helmRepos: ``` -This example would have `stable` overriden with a custom repo: +This example would have `stable` defined with a custom repo: ```toml ... @@ -45,3 +46,23 @@ helmRepos: # ... ``` + +This example would have `stable` defined with a Google stable repo: + +```toml +... + +[helmRepos] +stable = "https://kubernetes-charts.storage.googleapis.com" +... + +``` + +```yaml +# ... + +helmRepos: + stable: "https://kubernetes-charts.storage.googleapis.com" +# ... + +``` diff --git a/examples/example.toml b/examples/example.toml index 8cfdfb01..97583790 100644 --- a/examples/example.toml +++ b/examples/example.toml @@ -66,6 +66,8 @@ context= "test-infra" # defaults to "default" if not provided [helmRepos] argo = "https://argoproj.github.io/argo-helm" jfrog = "https://charts.jfrog.io" + # stable = "https://kubernetes-charts.storage.googleapis.com" + # incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" # myS3repo = "s3://my-S3-private-repo/charts" # myGCSrepo = "gs://my-GCS-private-repo/charts" # custom = "https://user:pass@mycustomrepo.org" diff --git a/examples/example.yaml b/examples/example.yaml index 43dc83ff..a56948f6 100644 --- a/examples/example.yaml +++ b/examples/example.yaml @@ -58,6 +58,8 @@ namespaces: helmRepos: argo: "https://argoproj.github.io/argo-helm" jfrog: "https://charts.jfrog.io" + #stable: "https://kubernetes-charts.storage.googleapis.com" + #incubator: "http://storage.googleapis.com/kubernetes-charts-incubator" #myS3repo: "s3://my-S3-private-repo/charts" #myGCSrepo: "gs://my-GCS-private-repo/charts" #custom: "https://user:pass@mycustomrepo.org" diff --git a/internal/app/cli.go b/internal/app/cli.go index ac71a538..46749355 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -61,7 +61,6 @@ type cli struct { noSSMValuesSubst bool updateDeps bool forceUpgrades bool - noDefaultRepos bool version bool } @@ -104,7 +103,6 @@ func (c *cli) parse() { flag.BoolVar(&c.noSSMValuesSubst, "no-ssm-values-subst", true, "turn off SSM parameter substitution in values files only") flag.BoolVar(&c.updateDeps, "update-deps", false, "run 'helm dep up' for local chart") flag.BoolVar(&c.forceUpgrades, "force-upgrades", false, "use --force when upgrading helm releases. May cause resources to be recreated.") - flag.BoolVar(&c.noDefaultRepos, "no-default-repos", false, "don't set default Helm repos from Google for 'stable' and 'incubator'") flag.Usage = printUsage flag.Parse() diff --git a/internal/app/main.go b/internal/app/main.go index 47298c39..0c31a983 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,8 +8,6 @@ const ( helmBin = "helm" appVersion = "v3.0.0-beta5" tempFilesDir = ".helmsman-tmp" - stableHelmRepo = "https://kubernetes-charts.storage.googleapis.com" - incubatorHelmRepo = "http://storage.googleapis.com/kubernetes-charts-incubator" defaultContextName = "default" ) diff --git a/internal/app/release.go b/internal/app/release.go index 1d937564..6a30d2af 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -195,7 +195,7 @@ func (r *release) validateChart(app string, s *state, wg *sync.WaitGroup, c chan if result := cmd.exec(); result.code != 0 || strings.Contains(result.output, "No results found") { c <- "Chart [ " + r.Chart + " ] with version [ " + r.Version + " ] is specified for " + - "app [" + app + "] but was not found" + "app [" + app + "] but was not found. If this is not a local chart, define its helm repo in your DSF." return } } diff --git a/internal/app/utils.go b/internal/app/utils.go index 0b24620e..40c879b3 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -57,7 +57,7 @@ func fromTOML(file string, s *state) (bool, string) { if _, err := toml.Decode(tomlFile, s); err != nil { return false, err.Error() } - addDefaultHelmRepos(s) + //addDefaultHelmRepos(s) resolvePaths(file, s) substituteVarsInValuesFiles(s) @@ -109,7 +109,7 @@ func fromYAML(file string, s *state) (bool, string) { if err = yaml.UnmarshalStrict([]byte(yamlFile), s); err != nil { return false, err.Error() } - addDefaultHelmRepos(s) + //addDefaultHelmRepos(s) resolvePaths(file, s) substituteVarsInValuesFiles(s) @@ -198,27 +198,6 @@ func stringInSlice(a string, list []string) bool { return false } -// addDefaultHelmRepos adds stable and incubator helm repos to the state if they are not already defined -func addDefaultHelmRepos(s *state) { - if flags.noDefaultRepos { - log.Info("Default helm repo set disabled, 'stable' and 'incubator' repos unset.") - return - } - if s.HelmRepos == nil || len(s.HelmRepos) == 0 { - s.HelmRepos = map[string]string{ - "stable": stableHelmRepo, - "incubator": incubatorHelmRepo, - } - log.Info("No helm repos provided, using the default 'stable' and 'incubator' repos.") - } - if _, ok := s.HelmRepos["stable"]; !ok { - s.HelmRepos["stable"] = stableHelmRepo - } - if _, ok := s.HelmRepos["incubator"]; !ok { - s.HelmRepos["incubator"] = incubatorHelmRepo - } -} - // resolvePaths resolves relative paths of certs/keys/chart and replace them with a absolute paths func resolvePaths(relativeToFile string, s *state) { dir := filepath.Dir(relativeToFile) diff --git a/release-notes.md b/release-notes.md index 76d71ed8..771f5faf 100644 --- a/release-notes.md +++ b/release-notes.md @@ -3,7 +3,7 @@ This is a major release to support Helm v3. It is recommended you read the [Helm 3 migration guide](https://helm.sh/docs/topics/v2_v3_migration/) before using this release. -> Starting from this release, support for Helmsman v1.x will be limited to bug fixes. +> Starting from Helmsman v3.0.0 GA release, support for Helmsman v1.x will be limited to bug fixes. The following are the most important changes: - A new and improved logger. @@ -13,6 +13,7 @@ The following are the most important changes: - Deprecating all the DSF stanzas related to Tiller. - Deprecating the `purge` option for releases. - The default value for `storageBackend` is now `secret`. +- The `stable` and `incubator` repos are no longer added by default and the `--no-default-repos` flag is deprecated. - The `--suppress-diff-secrets` cmd flag is enabled by default. - The `--no-env-values-subst` cmd flag is enabled by default. - The `--no-ssm-values-subst` cmd flag is enabled by default. From 165cb38b84844772c399982d832356326cbd3383 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Fri, 3 Jan 2020 11:58:27 +0100 Subject: [PATCH 0559/1127] deprecate some cmd flags --- docs/cmd_reference.md | 23 ++++++++++------------- internal/app/cli.go | 18 ++++++++---------- internal/app/release.go | 5 +---- internal/app/utils.go | 4 ++-- release-notes.md | 10 +++++----- 5 files changed, 26 insertions(+), 34 deletions(-) diff --git a/docs/cmd_reference.md b/docs/cmd_reference.md index e5fe3d0d..6b5b21a6 100644 --- a/docs/cmd_reference.md +++ b/docs/cmd_reference.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta5 +version: v3.0.0-beta6 --- # CMD reference @@ -12,7 +12,10 @@ This lists available CMD options in Helmsman: apply the plan directly. `--debug` - show execution logs. + show the debug execution logs and actual helm/kubectl commands. This can log secrets and should only be used for debugging purposes. + + `--verbose` + show verbose execution logs. `--destroy` delete all deployed releases. @@ -50,8 +53,8 @@ This lists available CMD options in Helmsman: `--no-env-subst` turn off environment substitution globally. - `--no-env-values-subst` - turn off environment substitution in values files only. (default true). + `--subst-env-values` + turn on environment substitution in values files. `--no-fancy` don't display the banner and don't use colors. @@ -59,11 +62,11 @@ This lists available CMD options in Helmsman: `--no-ns` don't create namespaces. - `-no-ssm-subst` + `--no-ssm-subst` turn off SSM parameter substitution globally. - `-no-ssm-values-subst` - turn off SSM parameter substitution in values files only (default true). + `--subst-ssm-values` + turn on SSM parameter substitution in values files. `--ns-override string` override defined namespaces with this one. @@ -74,9 +77,6 @@ This lists available CMD options in Helmsman: `--skip-validation` skip desired state validation. - `--suppress-diff-secrets` - don't show secrets in helm diff output. (default true). - `--target` limit execution to specific app. @@ -87,6 +87,3 @@ This lists available CMD options in Helmsman: run 'helm dep up' for local chart `--v` show the version. - - `--verbose` - show verbose execution logs. diff --git a/internal/app/cli.go b/internal/app/cli.go index ac71a538..ac2ddebf 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -53,12 +53,11 @@ type cli struct { skipValidation bool keepUntrackedReleases bool showDiff bool - suppressDiffSecrets bool diffContext int noEnvSubst bool - noEnvValuesSubst bool + substEnvValues bool noSSMSubst bool - noSSMValuesSubst bool + substSSMValues bool updateDeps bool forceUpgrades bool noDefaultRepos bool @@ -88,8 +87,8 @@ func (c *cli) parse() { flag.BoolVar(&c.dryRun, "dry-run", false, "apply the dry-run option for helm commands.") flag.BoolVar(&c.destroy, "destroy", false, "delete all deployed releases.") flag.BoolVar(&c.version, "v", false, "show the version") - flag.BoolVar(&c.debug, "debug", false, "show the execution logs") - flag.BoolVar(&c.verbose, "verbose", false, "show verbose execution logs") + flag.BoolVar(&c.debug, "debug", false, "show the debug execution logs and actual helm/kubectl commands. This can log secrets and should only be used for debugging purposes.") + flag.BoolVar(&c.verbose, "verbose", false, "show verbose execution logs.") flag.BoolVar(&c.noBanner, "no-banner", false, "don't show the banner") flag.BoolVar(&c.noColors, "no-color", false, "don't use colors") flag.BoolVar(&c.noFancy, "no-fancy", false, "don't display the banner and don't use colors") @@ -97,11 +96,10 @@ func (c *cli) parse() { flag.BoolVar(&c.skipValidation, "skip-validation", false, "skip desired state validation") flag.BoolVar(&c.keepUntrackedReleases, "keep-untracked-releases", false, "keep releases that are managed by Helmsman from the used DSFs in the command, and are no longer tracked in your desired state.") flag.BoolVar(&c.showDiff, "show-diff", false, "show helm diff results. Can expose sensitive information.") - flag.BoolVar(&c.suppressDiffSecrets, "suppress-diff-secrets", true, "don't show secrets in helm diff output. (default true).") flag.BoolVar(&c.noEnvSubst, "no-env-subst", false, "turn off environment substitution globally") - flag.BoolVar(&c.noEnvValuesSubst, "no-env-values-subst", true, "turn off environment substitution in values files only. (default true).") + flag.BoolVar(&c.substEnvValues, "subst-env-values", false, "turn on environment substitution in values files.") flag.BoolVar(&c.noSSMSubst, "no-ssm-subst", false, "turn off SSM parameter substitution globally") - flag.BoolVar(&c.noSSMValuesSubst, "no-ssm-values-subst", true, "turn off SSM parameter substitution in values files only") + flag.BoolVar(&c.substSSMValues, "subst-ssm-values", false, "turn on SSM parameter substitution in values files.") flag.BoolVar(&c.updateDeps, "update-deps", false, "run 'helm dep up' for local chart") flag.BoolVar(&c.forceUpgrades, "force-upgrades", false, "use --force when upgrading helm releases. May cause resources to be recreated.") flag.BoolVar(&c.noDefaultRepos, "no-default-repos", false, "don't set default Helm repos from Google for 'stable' and 'incubator'") @@ -174,13 +172,13 @@ func (c *cli) parse() { if !c.noEnvSubst { log.Verbose("Substitution of env variables enabled") - if !c.noEnvValuesSubst { + if c.substEnvValues { log.Verbose("Substitution of env variables in values enabled") } } if !c.noSSMSubst { log.Verbose("Substitution of SSM variables enabled") - if !c.noSSMValuesSubst { + if c.substSSMValues { log.Verbose("Substitution of SSM variables in values enabled") } } diff --git a/internal/app/release.go b/internal/app/release.go index 1d937564..86eff126 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -262,16 +262,13 @@ func (r *release) uninstall(p *plan) { func (r *release) diff() string { colorFlag := "" diffContextFlag := []string{} - suppressDiffSecretsFlag := "" + suppressDiffSecretsFlag := "--suppress-secrets" if flags.noColors { colorFlag = "--no-color" } if flags.diffContext != -1 { diffContextFlag = []string{"--context", strconv.Itoa(flags.diffContext)} } - if flags.suppressDiffSecrets { - suppressDiffSecretsFlag = "--suppress-secrets" - } cmd := helmCmd(concat([]string{"diff", colorFlag, suppressDiffSecretsFlag}, diffContextFlag, r.getHelmArgsFor("upgrade")), "Diffing release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") diff --git a/internal/app/utils.go b/internal/app/utils.go index 0b24620e..d6d8c595 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -168,10 +168,10 @@ func substituteVarsInYaml(file string) string { } yamlFile := string(rawYamlFile) - if !flags.noEnvSubst && !flags.noEnvValuesSubst { + if !flags.noEnvSubst && flags.substEnvValues { yamlFile = substituteEnv(yamlFile) } - if !flags.noSSMSubst && !flags.noSSMValuesSubst { + if !flags.noSSMSubst && flags.substSSMValues { yamlFile = substituteSSM(yamlFile) } diff --git a/release-notes.md b/release-notes.md index 76d71ed8..1b912500 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,4 +1,4 @@ -# v3.0.0-beta5 +# v3.0.0-beta6 This is a major release to support Helm v3. It is recommended you read the [Helm 3 migration guide](https://helm.sh/docs/topics/v2_v3_migration/) before using this release. @@ -8,12 +8,12 @@ It is recommended you read the [Helm 3 migration guide](https://helm.sh/docs/top The following are the most important changes: - A new and improved logger. - Restructuring the code. -- Parallelized decision making +- Parallelized decision making. - Introducing the `context` stanza to define a context for each DSF. More details [here](docs/misc/merge_desired_state_files). - Deprecating all the DSF stanzas related to Tiller. - Deprecating the `purge` option for releases. - The default value for `storageBackend` is now `secret`. -- The `--suppress-diff-secrets` cmd flag is enabled by default. -- The `--no-env-values-subst` cmd flag is enabled by default. -- The `--no-ssm-values-subst` cmd flag is enabled by default. +- The `--suppress-diff-secrets` is deprecated. Diff secrets are suppressed by default. +- The `--no-env-values-subst` cmd flag is deprecated. Env vars substitution in values files is disabled by default. `--subst-env-values` is introduced to enable it when needed. +- The `--no-ssm-values-subst` cmd flag is deprecated. SSM vars substitution in values files is disabled by default. `--subst-ssm-values` is introduced to enable it when needed. From 79eba6adf20672a65c7394c66ca025dd24a58b6b Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Fri, 3 Jan 2020 12:04:36 +0100 Subject: [PATCH 0560/1127] cleanup --- examples/example.toml | 8 ++++---- internal/app/utils.go | 2 -- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/examples/example.toml b/examples/example.toml index 97583790..2e7edea9 100644 --- a/examples/example.toml +++ b/examples/example.toml @@ -67,10 +67,10 @@ context= "test-infra" # defaults to "default" if not provided argo = "https://argoproj.github.io/argo-helm" jfrog = "https://charts.jfrog.io" # stable = "https://kubernetes-charts.storage.googleapis.com" - # incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" -# myS3repo = "s3://my-S3-private-repo/charts" -# myGCSrepo = "gs://my-GCS-private-repo/charts" -# custom = "https://user:pass@mycustomrepo.org" + # incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" + # myS3repo = "s3://my-S3-private-repo/charts" + # myGCSrepo = "gs://my-GCS-private-repo/charts" + # custom = "https://user:pass@mycustomrepo.org" # define the desired state of your applications helm charts diff --git a/internal/app/utils.go b/internal/app/utils.go index 40c879b3..e4d79b2b 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -57,7 +57,6 @@ func fromTOML(file string, s *state) (bool, string) { if _, err := toml.Decode(tomlFile, s); err != nil { return false, err.Error() } - //addDefaultHelmRepos(s) resolvePaths(file, s) substituteVarsInValuesFiles(s) @@ -109,7 +108,6 @@ func fromYAML(file string, s *state) (bool, string) { if err = yaml.UnmarshalStrict([]byte(yamlFile), s); err != nil { return false, err.Error() } - //addDefaultHelmRepos(s) resolvePaths(file, s) substituteVarsInValuesFiles(s) From 130544eb07d9b222b8f5a038721a11f7ba8e275c Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Fri, 3 Jan 2020 12:37:10 +0100 Subject: [PATCH 0561/1127] release v3.0.0-beta6 --- .version | 2 +- README.md | 6 +++--- docs/desired_state_specification.md | 10 +--------- examples/example.toml | 2 +- examples/example.yaml | 2 +- internal/app/main.go | 2 +- 6 files changed, 8 insertions(+), 16 deletions(-) diff --git a/.version b/.version index c2caabd0..f590c647 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.0.0-beta2 +v3.0.0-beta6 diff --git a/README.md b/README.md index ab24ea7c..4e8eb2ea 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.0.0-beta5&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.0.0-beta6&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -61,9 +61,9 @@ If you use private helm repos, you will need either `helm-gcs` or `helm-s3` plug Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. ``` # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.0.0-beta5/helmsman_3.0.0-beta5_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.0.0-beta6/helmsman_3.0.0-beta6_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.0.0-beta5/helmsman_3.0.0-beta5_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.0.0-beta6/helmsman_3.0.0-beta6_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index d78088eb..1a7934bb 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta5 +version: v3.0.0-beta6 --- # Helmsman desired state specification @@ -121,11 +121,9 @@ kubeContext = "minikube" # password = "$K8S_PASSWORD" # clusterURI = "https://192.168.99.100:8443" ## clusterURI= "$K8S_URI" -# serviceAccount = "my-service-account" # storageBackend = "secret" # slackWebhook = $MY_SLACK_WEBHOOK # reverseDelete = false -# tilerless = true # eyamlEnabled = true # eyamlPrivateKeyPath = "../keys/custom-key.pem" # eyamlPublicKeyPath = "../keys/custom-key.pub" @@ -138,11 +136,9 @@ settings: #password: "$K8S_PASSWORD" #clusterURI: "https://192.168.99.100:8443" ##clusterURI: "$K8S_URI" - #serviceAccount: "my-service-account" #storageBackend: "secret" #slackWebhook: "$MY_SLACK_WEBHOOK" #reverseDelete: false - #tilerless: true # eyamlEnabled: true # eyamlPrivateKeyPath: ../keys/custom-key.pem # eyamlPublicKeyPath: ../keys/custom-key.pub @@ -295,7 +291,6 @@ appsTemplates: default: &template valuesFile: "" - purge: false test: true protected: false wait: true @@ -303,7 +298,6 @@ appsTemplates: custom: &template_custom valuesFile: "" - purge: true test: true protected: false wait: false @@ -380,7 +374,6 @@ Example: chart = "stable/jenkins" version = "0.9.0" valuesFile = "" - purge = false test = true protected = false wait = true @@ -407,7 +400,6 @@ apps: chart: "stable/jenkins" version: "0.9.0" valuesFile: "" - purge: false test: true protected: false wait: true diff --git a/examples/example.toml b/examples/example.toml index 2e7edea9..242e2fea 100644 --- a/examples/example.toml +++ b/examples/example.toml @@ -1,4 +1,4 @@ -# version: v3.0.0-beta5 +# version: v3.0.0-beta6 # context defines the context of this Desired State File. # It is used to allow Helmsman identify which releases are managed by which DSF. diff --git a/examples/example.yaml b/examples/example.yaml index a56948f6..9c42f4bf 100644 --- a/examples/example.yaml +++ b/examples/example.yaml @@ -1,4 +1,4 @@ -# version: v3.0.0-beta5 +# version: v3.0.0-beta6 # metadata -- add as many key/value pairs as you want metadata: org: "example.com/$ORG_PATH/" diff --git a/internal/app/main.go b/internal/app/main.go index 0c31a983..259d24a9 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -6,7 +6,7 @@ import ( const ( helmBin = "helm" - appVersion = "v3.0.0-beta5" + appVersion = "v3.0.0-beta6" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" ) From 587ccbdaf83a95acec1dead37ea1a4a615a5eb5c Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Fri, 3 Jan 2020 22:21:21 +0000 Subject: [PATCH 0562/1127] don't use --all-namespaces to find helmsman releases --- examples/example.toml | 2 +- examples/minimal-example.toml | 8 ++- examples/minimal-example.yaml | 6 +- internal/app/decision_maker.go | 104 ++++++++++++++++----------------- internal/app/main.go | 2 +- 5 files changed, 63 insertions(+), 59 deletions(-) diff --git a/examples/example.toml b/examples/example.toml index 242e2fea..47d5ba0c 100644 --- a/examples/example.toml +++ b/examples/example.toml @@ -67,7 +67,7 @@ context= "test-infra" # defaults to "default" if not provided argo = "https://argoproj.github.io/argo-helm" jfrog = "https://charts.jfrog.io" # stable = "https://kubernetes-charts.storage.googleapis.com" - # incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" + # incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" # myS3repo = "s3://my-S3-private-repo/charts" # myGCSrepo = "gs://my-GCS-private-repo/charts" # custom = "https://user:pass@mycustomrepo.org" diff --git a/examples/minimal-example.toml b/examples/minimal-example.toml index 1f55dcc0..5bc101ed 100644 --- a/examples/minimal-example.toml +++ b/examples/minimal-example.toml @@ -2,19 +2,21 @@ ## It will use your current kube context and will deploy Tiller without RBAC service account. ## For the full config spec and options, check https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md +[helmRepos] + stable = "https://kubernetes-charts.storage.googleapis.com" + [namespaces] [namespaces.staging] [apps] - [apps.jenkins] namespace = "staging" enabled = true chart = "stable/jenkins" - version = "0.14.3" + version = "1.9.12" [apps.artifactory] namespace = "staging" enabled = true chart = "stable/artifactory" - version = "7.0.6" + version = "7.3.1" diff --git a/examples/minimal-example.yaml b/examples/minimal-example.yaml index 29208b88..532d084a 100644 --- a/examples/minimal-example.yaml +++ b/examples/minimal-example.yaml @@ -1,6 +1,8 @@ ## This is a minimal example. ## It will use your current kube context and will deploy Tiller without RBAC service account. ## For the full config spec and options, check https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md +helmRepos: + stable: https://kubernetes-charts.storage.googleapis.com namespaces: staging: @@ -10,10 +12,10 @@ apps: namespace: staging enabled: true chart: stable/jenkins - version: 0.14.3 + version: 1.9.12 artifactory: namespace: staging enabled: true chart: stable/artifactory - version: 7.0.6 + version: 7.3.1 diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index 5a4e1cd2..35edf85a 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -74,9 +74,7 @@ func (cs *currentState) decide(r *release, s *state, p *plan, wg *sync.WaitGroup if !r.Enabled { if ok := cs.releaseExists(r, ""); ok { - if r.isProtected(cs, s) { - p.addDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ "protection is removed.", r.Priority, noop) return @@ -86,35 +84,26 @@ func (cs *currentState) decide(r *release, s *state, p *plan, wg *sync.WaitGroup } p.addDecision("Release [ "+r.Name+" ] disabled", r.Priority, noop) return - } if ok := cs.releaseExists(r, "deployed"); ok { if !r.isProtected(cs, s) { cs.inspectUpgradeScenario(r, p) // upgrade or move - } else { p.addDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ "you remove its protection.", r.Priority, noop) } - } else if ok := cs.releaseExists(r, "deleted"); ok { if !r.isProtected(cs, s) { - r.rollback(cs, p) // rollback - } else { p.addDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ "you remove its protection.", r.Priority, noop) } - } else if ok := cs.releaseExists(r, "failed"); ok { - if !r.isProtected(cs, s) { - p.addDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is in FAILED state. Upgrade is scheduled!", r.Priority, change) r.upgrade(p) - } else { p.addDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ "you remove its protection.", r.Priority, noop) @@ -154,54 +143,65 @@ var releaseNameExtractor = regexp.MustCompile(`sh\.helm\.release\.v\d+\.`) // The releases are categorized by the namespaces in which they are deployed // The returned map format is: map[:map[:true]] func (cs *currentState) getHelmsmanReleases(s *state) map[string]map[string]bool { - var lines []string - const outputFmt = "custom-columns=NAME:.metadata.name,NS:.metadata.namespace,CTX:.metadata.labels.HELMSMAN_CONTEXT" + var ( + wg sync.WaitGroup + mutex = &sync.Mutex{} + ) + const outputFmt = "custom-columns=NAME:.metadata.name,CTX:.metadata.labels.HELMSMAN_CONTEXT" releases := make(map[string]map[string]bool) storageBackend := s.Settings.StorageBackend - cmd := kubectl([]string{"get", storageBackend, "--all-namespaces", "-l", "MANAGED-BY=HELMSMAN", "-o", outputFmt, "--no-headers"}, "Getting Helmsman-managed releases") - result := cmd.exec() + for ns := range s.Namespaces { + wg.Add(1) + go func(ns string, releases map[string]map[string]bool, s *state, wg *sync.WaitGroup, mutex *sync.Mutex) { + var lines []string + defer wg.Done() + + cmd := kubectl([]string{"get", storageBackend, "-n", ns, "-l", "MANAGED-BY=HELMSMAN", "-o", outputFmt, "--no-headers"}, "Getting Helmsman-managed releases") + result := cmd.exec() + if result.code != 0 { + log.Fatal(result.errors) + } - if result.code != 0 { - log.Fatal(result.errors) - } - if !strings.EqualFold("No resources found.", strings.TrimSpace(result.output)) { - lines = strings.Split(result.output, "\n") - } + if !strings.EqualFold("No resources found.", strings.TrimSpace(result.output)) { + lines = strings.Split(result.output, "\n") + } - for _, line := range lines { - if line == "" { - continue - } - flds := strings.Fields(line) - name := resourceNameExtractor.ReplaceAllString(flds[0], "") - name = releaseNameExtractor.ReplaceAllString(name, "") - ns := flds[1] - rctx := defaultContextName - if len(flds) > 2 { - rctx = flds[2] - } - if _, ok := releases[ns]; !ok { - releases[ns] = make(map[string]bool) - } - if !s.isNamespaceDefined(ns) { - // if the namespace is not managed by this desired state we assume it's tracked - releases[ns][name] = true - continue - } - if rctx != s.Context { - // if the release is not related to the current context we assume it's tracked - releases[ns][name] = true - continue - } - releases[ns][name] = false - for _, app := range s.Apps { - if app.Name == name && app.Namespace == ns { - releases[ns][name] = true - break + for _, line := range lines { + if line == "" { + continue + } + flds := strings.Fields(line) + name := resourceNameExtractor.ReplaceAllString(flds[0], "") + name = releaseNameExtractor.ReplaceAllString(name, "") + rctx := defaultContextName + if len(flds) > 1 { + rctx = flds[1] + } + + mutex.Lock() + if _, ok := releases[ns]; !ok { + releases[ns] = make(map[string]bool) + } + if !s.isNamespaceDefined(ns) || rctx != s.Context { + // if the namespace is not managed by this desired state + // or the release is not related to the current context we assume it's tracked + releases[ns][name] = true + continue + } + releases[ns][name] = false + for _, app := range s.Apps { + if app.Name == name && app.Namespace == ns { + releases[ns][name] = true + break + } + } + mutex.Unlock() } - } + + }(ns, releases, s, &wg, mutex) } + wg.Wait() return releases } diff --git a/internal/app/main.go b/internal/app/main.go index 259d24a9..769332c4 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -75,7 +75,7 @@ func Main() { log.Info("Preparing plan...") if flags.destroy { - log.Info("--destroy is enabled. Your releases will be deleted!") + log.Warning("Destroy flag is enabled. Your releases will be deleted!") } cs := buildState() From ab8b98f1735c2b736cff28516be986340abb5fb2 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Tue, 7 Jan 2020 15:11:27 +0100 Subject: [PATCH 0563/1127] don't run helm repo update when repos are empty --- internal/app/helm_helpers.go | 8 +++++--- internal/app/release.go | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index 8f676753..0705418a 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -105,10 +105,12 @@ func addHelmRepos(repos map[string]string) error { } - cmd := helmCmd([]string{"repo", "update"}, "Updating helm repositories") + if len(repos) > 0 { + cmd := helmCmd([]string{"repo", "update"}, "Updating helm repositories") - if result := cmd.exec(); result.code != 0 { - return errors.New("While updating helm repos : " + result.errors) + if result := cmd.exec(); result.code != 0 { + return errors.New("While updating helm repos : " + result.errors) + } } return nil diff --git a/internal/app/release.go b/internal/app/release.go index f926b26e..cfed9f9e 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -195,7 +195,7 @@ func (r *release) validateChart(app string, s *state, wg *sync.WaitGroup, c chan if result := cmd.exec(); result.code != 0 || strings.Contains(result.output, "No results found") { c <- "Chart [ " + r.Chart + " ] with version [ " + r.Version + " ] is specified for " + - "app [" + app + "] but was not found. If this is not a local chart, define its helm repo in your DSF." + "app [" + app + "] but was not found. If this is not a local chart, define its helm repo in the helmRepo stanza in your DSF." return } } From 110fb8e70749cfeccf5480c6e27cbda3b5abaa74 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Tue, 7 Jan 2020 15:11:43 +0100 Subject: [PATCH 0564/1127] cleanup --- internal/app/utils_test.go | 66 -------------------------------------- 1 file changed, 66 deletions(-) diff --git a/internal/app/utils_test.go b/internal/app/utils_test.go index 971abca0..b06b9c49 100644 --- a/internal/app/utils_test.go +++ b/internal/app/utils_test.go @@ -6,23 +6,6 @@ import ( "testing" ) -// func Test_printMap(t *testing.T) { -// type args struct { -// m map[string]string -// } -// tests := []struct { -// name string -// args args -// }{ -// // TODO: Add test cases. -// } -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// printMap(tt.args.m) -// }) -// } -// } - func Test_fromTOML(t *testing.T) { type args struct { file string @@ -140,24 +123,6 @@ func Test_fromTOML_Expand(t *testing.T) { } } -// func Test_toTOML(t *testing.T) { -// type args struct { -// file string -// s *state -// } -// tests := []struct { -// name string -// args args -// }{ -// // TODO: Add test cases. -// } -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// toTOML(tt.args.file, tt.args.s) -// }) -// } -// } - func Test_fromYAML(t *testing.T) { type args struct { file string @@ -276,24 +241,6 @@ func Test_fromYAML_Expand(t *testing.T) { } } -// func Test_toYAML(t *testing.T) { -// type args struct { -// file string -// s *state -// } -// tests := []struct { -// name string -// args args -// }{ -// // TODO: Add test cases. -// } -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// toYAML(tt.args.file, tt.args.s) -// }) -// } -// } - func Test_isOfType(t *testing.T) { type args struct { filename string @@ -459,16 +406,3 @@ func Test_eyamlSecrets(t *testing.T) { }) } } - -// func Test_printHelp(t *testing.T) { -// tests := []struct { -// name string -// }{ -// // TODO: Add test cases. -// } -// for range tests { -// t.Run(tt.name, func(t *testing.T) { -// printHelp() -// }) -// } -// } From 54ceee84d092655ded65f7fe7b38f6521faa2153 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Tue, 7 Jan 2020 15:25:24 +0100 Subject: [PATCH 0565/1127] fix #373 --- internal/app/release.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/app/release.go b/internal/app/release.go index cfed9f9e..0c7b0f37 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -173,7 +173,8 @@ func (r *release) validateChart(app string, s *state, wg *sync.WaitGroup, c chan result := cmd.exec() if result.code != 0 { maybeRepo := filepath.Base(filepath.Dir(r.Chart)) - c <- "Chart [ " + r.Chart + " ] for app [" + app + "] can't be found. Did you mean to add a repo [ " + maybeRepo + " ]?" + c <- "Chart [ " + r.Chart + " ] for app [" + app + "] can't be found. Inspection returned error: \"" + + strings.TrimSpace(result.errors) + "\" -- If this is not a local chart, add the repo [ " + maybeRepo + " ] in your helmRepos stanza." return } matches := versionExtractor.FindStringSubmatch(result.output) From 90f102a46870ac4a8dd36c4145970b00513d1e14 Mon Sep 17 00:00:00 2001 From: sami-alajrami Date: Tue, 7 Jan 2020 15:58:18 +0100 Subject: [PATCH 0566/1127] releasing v3.0.0 GA --- .version | 2 +- README.md | 8 ++++---- docs/cmd_reference.md | 2 +- docs/desired_state_specification.md | 2 +- examples/example.toml | 2 +- examples/example.yaml | 2 +- internal/app/main.go | 2 +- release-notes.md | 2 +- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.version b/.version index f590c647..ad55eb85 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.0.0-beta6 +v3.0.0 diff --git a/README.md b/README.md index 4e8eb2ea..bb926b28 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.0.0-beta6&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.0.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -52,7 +52,7 @@ To limit execution to specific application: Please make sure the following are installed prior to using `helmsman` as a binary (the docker image contains all of them): - [kubectl](https://github.com/kubernetes/kubectl) -- [helm](https://github.com/helm/helm) (for `helmsman` >= 1.6.0) +- [helm](https://github.com/helm/helm) (helm >=v2.10.0 for `helmsman` >= 1.6.0, helm >=v3.0.0 for `helmsman` >=v3.0.0) - [helm-diff](https://github.com/databus23/helm-diff) (`helmsman` >= 1.6.0) If you use private helm repos, you will need either `helm-gcs` or `helm-s3` plugin or you can use basic auth to authenticate to your repos. See the [docs](https://github.com/Praqma/helmsman/blob/master/docs/how_to/helm_repos) for details. @@ -61,9 +61,9 @@ If you use private helm repos, you will need either `helm-gcs` or `helm-s3` plug Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. ``` # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.0.0-beta6/helmsman_3.0.0-beta6_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.0.0/helmsman_3.0.0_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.0.0-beta6/helmsman_3.0.0-beta6_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.0.0/helmsman_3.0.0_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/docs/cmd_reference.md b/docs/cmd_reference.md index f301b23d..707c98a3 100644 --- a/docs/cmd_reference.md +++ b/docs/cmd_reference.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta6 +version: v3.0.0 --- # CMD reference diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 1a7934bb..ab115a53 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -1,5 +1,5 @@ --- -version: v3.0.0-beta6 +version: v3.0.0 --- # Helmsman desired state specification diff --git a/examples/example.toml b/examples/example.toml index 47d5ba0c..bef218c5 100644 --- a/examples/example.toml +++ b/examples/example.toml @@ -1,4 +1,4 @@ -# version: v3.0.0-beta6 +# version: v3.0.0 # context defines the context of this Desired State File. # It is used to allow Helmsman identify which releases are managed by which DSF. diff --git a/examples/example.yaml b/examples/example.yaml index 9c42f4bf..704d7a51 100644 --- a/examples/example.yaml +++ b/examples/example.yaml @@ -1,4 +1,4 @@ -# version: v3.0.0-beta6 +# version: v3.0.0 # metadata -- add as many key/value pairs as you want metadata: org: "example.com/$ORG_PATH/" diff --git a/internal/app/main.go b/internal/app/main.go index 769332c4..43c50173 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -6,7 +6,7 @@ import ( const ( helmBin = "helm" - appVersion = "v3.0.0-beta6" + appVersion = "v3.0.0" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" ) diff --git a/release-notes.md b/release-notes.md index 6e2fc6fd..4d921a59 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,4 +1,4 @@ -# v3.0.0-beta6 +# v3.0.0 This is a major release to support Helm v3. It is recommended you read the [Helm 3 migration guide](https://helm.sh/docs/topics/v2_v3_migration/) before using this release. From a2dcf00b78dd3b3f54d366c1caf7400debd1502d Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Thu, 9 Jan 2020 16:18:49 +0000 Subject: [PATCH 0567/1127] fix overlocking issue when looking for untracked releases --- internal/app/decision_maker.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index 35edf85a..b9586a56 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -187,6 +187,7 @@ func (cs *currentState) getHelmsmanReleases(s *state) map[string]map[string]bool // if the namespace is not managed by this desired state // or the release is not related to the current context we assume it's tracked releases[ns][name] = true + mutex.Unlock() continue } releases[ns][name] = false From 07ca39bf097f08a81681c2ff0d3bd6479caf0820 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Fri, 10 Jan 2020 00:48:41 +0000 Subject: [PATCH 0568/1127] don't use --all-namespaces to find helm releases --- internal/app/decision_maker.go | 8 ++++---- internal/app/helm_release.go | 35 +++++++++++++++++++++++++--------- internal/app/main.go | 2 +- internal/app/release.go | 11 ++++++++--- 4 files changed, 39 insertions(+), 17 deletions(-) diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index b9586a56..57621a71 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -19,11 +19,11 @@ func newCurrentState() *currentState { } // buildState builds the currentState map containing information about all releases existing in a k8s cluster -func buildState() *currentState { +func buildState(s *state) *currentState { log.Info("Acquiring current Helm state from cluster...") cs := newCurrentState() - rel := getHelmReleases() + rel := getHelmReleases(s) var wg sync.WaitGroup for _, r := range rel { @@ -153,7 +153,7 @@ func (cs *currentState) getHelmsmanReleases(s *state) map[string]map[string]bool for ns := range s.Namespaces { wg.Add(1) - go func(ns string, releases map[string]map[string]bool, s *state, wg *sync.WaitGroup, mutex *sync.Mutex) { + go func(ns string) { var lines []string defer wg.Done() @@ -200,7 +200,7 @@ func (cs *currentState) getHelmsmanReleases(s *state) map[string]map[string]bool mutex.Unlock() } - }(ns, releases, s, &wg, mutex) + }(ns) } wg.Wait() return releases diff --git a/internal/app/helm_release.go b/internal/app/helm_release.go index 24e98940..07350dbc 100644 --- a/internal/app/helm_release.go +++ b/internal/app/helm_release.go @@ -6,6 +6,7 @@ import ( "regexp" "strconv" "strings" + "sync" ) // helmRelease represents the current state of a release @@ -21,16 +22,32 @@ type helmRelease struct { } // getHelmReleases fetches a list of all releases in a k8s cluster -func getHelmReleases() []helmRelease { - var allReleases []helmRelease - cmd := helmCmd([]string{"list", "--all", "--max", "0", "--output", "json", "--all-namespaces"}, "Listing all existing releases...") - result := cmd.exec() - if result.code != 0 { - log.Fatal("Failed to list all releases: " + result.errors) - } - if err := json.Unmarshal([]byte(result.output), &allReleases); err != nil { - log.Fatal(fmt.Sprintf("failed to unmarshal Helm CLI output: %s", err)) +func getHelmReleases(s *state) []helmRelease { + var ( + allReleases []helmRelease + wg sync.WaitGroup + mutex = &sync.Mutex{} + ) + + for ns := range s.Namespaces { + wg.Add(1) + go func(ns string) { + var releases []helmRelease + defer wg.Done() + cmd := helmCmd([]string{"list", "--all", "--max", "0", "--output", "json", "-n", ns}, "Listing all existing releases...") + result := cmd.exec() + if result.code != 0 { + log.Fatal("Failed to list all releases: " + result.errors) + } + if err := json.Unmarshal([]byte(result.output), &releases); err != nil { + log.Fatal(fmt.Sprintf("failed to unmarshal Helm CLI output: %s", err)) + } + mutex.Lock() + allReleases = append(allReleases, releases...) + mutex.Unlock() + }(ns) } + wg.Wait() return allReleases } diff --git a/internal/app/main.go b/internal/app/main.go index 43c50173..14b277a8 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -78,7 +78,7 @@ func Main() { log.Warning("Destroy flag is enabled. Your releases will be deleted!") } - cs := buildState() + cs := buildState(&s) p := cs.makePlan(&s) if !flags.keepUntrackedReleases { cs.cleanUntrackedReleases(&s, p) diff --git a/internal/app/release.go b/internal/app/release.go index 0c7b0f37..cba0918a 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -141,6 +141,7 @@ func (r *release) validate(appLabel string, names map[string]map[string]bool, s // Valid charts are the ones that can be found in the defined repos. // This function uses Helm search to verify if the chart can be found or not. func validateReleaseCharts(s *state) error { + var fail bool wg := sync.WaitGroup{} c := make(chan string, len(s.Apps)) for app, r := range s.Apps { @@ -148,12 +149,16 @@ func validateReleaseCharts(s *state) error { go r.validateChart(app, s, &wg, c) } wg.Wait() - if len(c) > 0 { - err := <-c + close(c) + for err := range c { if err != "" { - return errors.New(err) + fail = true + log.Error(err) } } + if fail { + return errors.New("chart validation failed") + } return nil } From 96714d451b71ab1d31f23435401693d0fc47b627 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Fri, 10 Jan 2020 21:37:24 +0100 Subject: [PATCH 0569/1127] Remove --purge from reInstall func --- internal/app/decision_maker.go | 4 ++-- internal/app/release.go | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index b9586a56..42d3930e 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -259,7 +259,7 @@ func (cs *currentState) inspectUpgradeScenario(r *release, p *plan) { p.addDecision("Release [ "+r.Name+" ] will be updated", r.Priority, change) } else if extractChartName(r.Chart) != rs.getChartName() { - r.reInstall(rs, p) + r.reInstall(p) p.addDecision("Release [ "+r.Name+" ] is desired to use a new chart [ "+r.Chart+ " ]. Delete of the current release will be planned and new chart will be installed in namespace [ "+ r.Namespace+" ]", r.Priority, change) @@ -272,7 +272,7 @@ func (cs *currentState) inspectUpgradeScenario(r *release, p *plan) { } } } else { - r.reInstall(rs, p) + r.reInstall(p) p.addDecision("Release [ "+r.Name+" ] is desired to be enabled in a new namespace [ "+r.Namespace+ " ]. Uninstall of the current release from namespace [ "+rs.Namespace+" ] will be performed "+ "and then installation in namespace [ "+r.Namespace+" ] will take place", r.Priority, change) diff --git a/internal/app/release.go b/internal/app/release.go index 0c7b0f37..ece87aa3 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -298,9 +298,8 @@ func (r *release) upgrade(p *plan) { // reInstall purge deletes a release and reinstalls it. // This is used when moving a release to another namespace or when changing the chart used for it. -func (r *release) reInstall(rs helmRelease, p *plan) { - - delCmd := helmCmd(concat([]string{"delete", "--purge", r.Name}, flags.getDryRunFlags()), "Deleting release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") +func (r *release) reInstall(p *plan) { + delCmd := helmCmd(concat(r.getHelmArgsFor("uninstall"), flags.getDryRunFlags()), "Deleting release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") p.addCommand(delCmd, r.Priority, r) installCmd := helmCmd(r.getHelmArgsFor("install"), "Installing release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") @@ -324,7 +323,7 @@ func (r *release) rollback(cs *currentState, p *plan) { p.addDecision("Release [ "+r.Name+" ] was deleted and is desired to be rolled back to "+ "namespace [ "+r.Namespace+" ]", r.Priority, create) } else { - r.reInstall(rs, p) + r.reInstall(p) p.addDecision("Release [ "+r.Name+" ] is deleted BUT from namespace [ "+rs.Namespace+ " ]. Will purge delete it from there and install it in namespace [ "+r.Namespace+" ]", r.Priority, create) p.addDecision("WARNING: rolling back release [ "+r.Name+" ] from [ "+rs.Namespace+" ] to [ "+r.Namespace+ From 2c7a5ba2a100546dee02b9061b274bc95b9e8bff Mon Sep 17 00:00:00 2001 From: Evgenii Terechkov Date: Fri, 17 Jan 2020 23:38:27 +0700 Subject: [PATCH 0570/1127] Update README.md: fix references to examples --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bb926b28..9cbd9642 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,8 @@ Helmsman is a Helm Charts (k8s applications) as Code tool which allows you to au # How does it work? -Helmsman uses a simple declarative [TOML](https://github.com/toml-lang/toml) file to allow you to describe a desired state for your k8s applications as in the [example toml file](https://github.com/Praqma/helmsman/blob/master/example.toml). -Alternatively YAML declaration is also acceptable [example yaml file](https://github.com/Praqma/helmsman/blob/master/example.yaml). +Helmsman uses a simple declarative [TOML](https://github.com/toml-lang/toml) file to allow you to describe a desired state for your k8s applications as in the [example toml file](https://github.com/Praqma/helmsman/blob/master/examples/example.toml). +Alternatively YAML declaration is also acceptable [example yaml file](https://github.com/Praqma/helmsman/blob/master/examples/example.yaml). The desired state file (DSF) follows the [desired state specification](https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md). From 01f2661cd8ae7f92b1946bc70977a231ab96eb96 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Mon, 20 Jan 2020 11:35:19 +0100 Subject: [PATCH 0571/1127] Release v3.0.1 --- .version | 2 +- internal/app/main.go | 2 +- release-notes.md | 22 ++++++++-------------- 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/.version b/.version index ad55eb85..b105cea1 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.0.0 +v3.0.1 diff --git a/internal/app/main.go b/internal/app/main.go index 14b277a8..c3e954f5 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -6,7 +6,7 @@ import ( const ( helmBin = "helm" - appVersion = "v3.0.0" + appVersion = "v3.0.1" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" ) diff --git a/release-notes.md b/release-notes.md index 4d921a59..67436652 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,20 +1,14 @@ -# v3.0.0 +# v3.0.1 -This is a major release to support Helm v3. +This is a bugfix release to support Helm v3. It is recommended you read the [Helm 3 migration guide](https://helm.sh/docs/topics/v2_v3_migration/) before using this release. > Starting from Helmsman v3.0.0 GA release, support for Helmsman v1.x will be limited to bug fixes. -The following are the most important changes: -- A new and improved logger. -- Restructuring the code. -- Parallelized decision making. -- Introducing the `context` stanza to define a context for each DSF. More details [here](docs/misc/merge_desired_state_files). -- Deprecating all the DSF stanzas related to Tiller. -- Deprecating the `purge` option for releases. -- The default value for `storageBackend` is now `secret`. -- The `stable` and `incubator` repos are no longer added by default and the `--no-default-repos` flag is deprecated. -- The `--suppress-diff-secrets` is deprecated. Diff secrets are suppressed by default. -- The `--no-env-values-subst` cmd flag is deprecated. Env vars substitution in values files is disabled by default. `--subst-env-values` is introduced to enable it when needed. -- The `--no-ssm-values-subst` cmd flag is deprecated. SSM vars substitution in values files is disabled by default. `--subst-ssm-values` is introduced to enable it when needed. +# Fixes and improvements: +- Fix overlocking issue when looking for untracked releases; PR #378 +- Do not use --all-namespaces to find helm releases; PR #379 +- Remove --purge from reInstall function; PR #381 +# New features: +None, bug fix release From 4049dd67e2a9b996954e4fd2631a776364fb5b72 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 21 Jan 2020 08:05:33 -0500 Subject: [PATCH 0572/1127] fixes #385 --- internal/app/release.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/app/release.go b/internal/app/release.go index dd39cf01..52b973dc 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -184,8 +184,8 @@ func (r *release) validateChart(app string, s *state, wg *sync.WaitGroup, c chan } matches := versionExtractor.FindStringSubmatch(result.output) if len(matches) == 2 { - version := matches[1] - if r.Version != version { + version := strings.Trim(matches[1], `'"`) + if strings.Trim(r.Version, `'"`) != version { c <- "Chart [ " + r.Chart + " ] with version [ " + r.Version + " ] is specified for " + "app [" + app + "] but the chart found at that path has version [ " + version + " ] which does not match." return From 697f1cae00cb1f4981da406ebad6acae30d0b27c Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Tue, 21 Jan 2020 17:36:24 +0100 Subject: [PATCH 0573/1127] Add backup helmTime parse layout with doubled timezone offset --- internal/app/helm_time.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/internal/app/helm_time.go b/internal/app/helm_time.go index 05cf6ca9..6c53a950 100644 --- a/internal/app/helm_time.go +++ b/internal/app/helm_time.go @@ -7,7 +7,7 @@ import ( ) const ctLayout = "2006-01-02 15:04:05.000000000 -0700 MST" - +const ctLayout2 = "2006-01-02 15:04:05.000000000 -0700 -0700" var nilTime = (time.Time{}).UnixNano() type HelmTime struct { @@ -29,6 +29,9 @@ func (ht *HelmTime) UnmarshalJSON(b []byte) (err error) { } s = fmt.Sprintf("%s %s.%s %s %s", updatedFields[0], updatedHour[0], milliseconds, updatedFields[2], updatedFields[3]) ht.Time, err = time.Parse(ctLayout, s) + if err != nil { + ht.Time, err = time.Parse(ctLayout2, s) + } return } From 6a35dab712599b055471d975c00e2fe0e8bcbe42 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 21 Jan 2020 21:50:43 -0500 Subject: [PATCH 0574/1127] limit the number of parallel go routines, fixes #391 Signed-off-by: Luis Davim --- internal/app/decision_maker.go | 45 ++++++++++++++++++++++------- internal/app/decision_maker_test.go | 11 ++----- internal/app/helm_time.go | 1 + internal/app/main.go | 1 + internal/app/release.go | 13 +++++++-- 5 files changed, 48 insertions(+), 23 deletions(-) diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index d677057b..d57700d3 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -25,16 +25,24 @@ func buildState(s *state) *currentState { cs := newCurrentState() rel := getHelmReleases(s) - var wg sync.WaitGroup + wg := sync.WaitGroup{} + sem := make(chan struct{}, resourcePool) + for _, r := range rel { + // aquire + sem <- struct{}{} wg.Add(1) - go func(c *currentState, r helmRelease, wg *sync.WaitGroup) { - c.Lock() - defer c.Unlock() - defer wg.Done() + go func(r helmRelease) { + cs.Lock() + defer func() { + cs.Unlock() + wg.Done() + // release + <-sem + }() r.HelmsmanContext = getReleaseContext(r.Name, r.Namespace) - c.releases[r.key()] = r - }(cs, r, &wg) + cs.releases[r.key()] = r + }(r) } wg.Wait() return cs @@ -45,10 +53,19 @@ func (cs *currentState) makePlan(s *state) *plan { p := createPlan() wg := sync.WaitGroup{} + sem := make(chan struct{}, resourcePool) + for _, r := range s.Apps { r.checkChartDepUpdate() + sem <- struct{}{} wg.Add(1) - go cs.decide(r, s, p, &wg) + go func(r *release) { + defer func() { + wg.Done() + <-sem + }() + cs.decide(r, s, p) + }(r) } wg.Wait() @@ -57,8 +74,7 @@ func (cs *currentState) makePlan(s *state) *plan { // decide makes a decision about what commands (actions) need to be executed // to make a release section of the desired state come true. -func (cs *currentState) decide(r *release, s *state, p *plan, wg *sync.WaitGroup) { - defer wg.Done() +func (cs *currentState) decide(r *release, s *state, p *plan) { // check for presence in defined targets or groups if !r.isConsideredToRun(s) { p.addDecision("Release [ "+r.Name+" ] ignored", r.Priority, ignored) @@ -149,13 +165,20 @@ func (cs *currentState) getHelmsmanReleases(s *state) map[string]map[string]bool ) const outputFmt = "custom-columns=NAME:.metadata.name,CTX:.metadata.labels.HELMSMAN_CONTEXT" releases := make(map[string]map[string]bool) + sem := make(chan struct{}, resourcePool) storageBackend := s.Settings.StorageBackend for ns := range s.Namespaces { + // aquire + sem <- struct{}{} wg.Add(1) go func(ns string) { var lines []string - defer wg.Done() + defer func() { + wg.Done() + // release + <-sem + }() cmd := kubectl([]string{"get", storageBackend, "-n", ns, "-l", "MANAGED-BY=HELMSMAN", "-o", outputFmt, "--no-headers"}, "Getting Helmsman-managed releases") result := cmd.exec() diff --git a/internal/app/decision_maker_test.go b/internal/app/decision_maker_test.go index 30c30094..5345f94b 100644 --- a/internal/app/decision_maker_test.go +++ b/internal/app/decision_maker_test.go @@ -2,7 +2,6 @@ package app import ( "reflect" - "sync" "testing" ) @@ -211,11 +210,8 @@ func Test_decide(t *testing.T) { tt.args.s.TargetMap[target] = true } outcome := plan{} - wg := sync.WaitGroup{} - wg.Add(1) // Act - cs.decide(tt.args.r, tt.args.s, &outcome, &wg) - wg.Wait() + cs.decide(tt.args.r, tt.args.s, &outcome) got := outcome.Decisions[0].Type t.Log(outcome.Decisions[0].Description) @@ -297,10 +293,7 @@ func Test_decide_group(t *testing.T) { tt.args.s.GroupMap[group] = true } outcome := plan{} - wg := sync.WaitGroup{} - wg.Add(1) - cs.decide(tt.args.r, tt.args.s, &outcome, &wg) - wg.Wait() + cs.decide(tt.args.r, tt.args.s, &outcome) got := outcome.Decisions[0].Type t.Log(outcome.Decisions[0].Description) diff --git a/internal/app/helm_time.go b/internal/app/helm_time.go index 6c53a950..1254a860 100644 --- a/internal/app/helm_time.go +++ b/internal/app/helm_time.go @@ -8,6 +8,7 @@ import ( const ctLayout = "2006-01-02 15:04:05.000000000 -0700 MST" const ctLayout2 = "2006-01-02 15:04:05.000000000 -0700 -0700" + var nilTime = (time.Time{}).UnixNano() type HelmTime struct { diff --git a/internal/app/main.go b/internal/app/main.go index c3e954f5..e0422b85 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -9,6 +9,7 @@ const ( appVersion = "v3.0.1" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" + resourcePool = 10 ) var ( diff --git a/internal/app/release.go b/internal/app/release.go index 52b973dc..23a57de6 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -143,10 +143,18 @@ func (r *release) validate(appLabel string, names map[string]map[string]bool, s func validateReleaseCharts(s *state) error { var fail bool wg := sync.WaitGroup{} + sem := make(chan struct{}, resourcePool) c := make(chan string, len(s.Apps)) for app, r := range s.Apps { + sem <- struct{}{} wg.Add(1) - go r.validateChart(app, s, &wg, c) + go func(r *release, app string) { + defer func() { + wg.Done() + <-sem + }() + r.validateChart(app, s, c) + }(r, app) } wg.Wait() close(c) @@ -164,9 +172,8 @@ func validateReleaseCharts(s *state) error { var versionExtractor = regexp.MustCompile(`[\n]version:\s?(.*)`) -func (r *release) validateChart(app string, s *state, wg *sync.WaitGroup, c chan string) { +func (r *release) validateChart(app string, s *state, c chan string) { - defer wg.Done() validateCurrentChart := true if !r.isConsideredToRun(s) { validateCurrentChart = false From 3caa72b7757b2d3c21f3851cea1263ee3a41cd19 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Wed, 22 Jan 2020 13:56:52 +0100 Subject: [PATCH 0575/1127] Release v3.0.2 --- .version | 2 +- internal/app/main.go | 2 +- release-notes.md | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.version b/.version index b105cea1..96506fd2 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.0.1 +v3.0.2 diff --git a/internal/app/main.go b/internal/app/main.go index e0422b85..ca1bb0e6 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -6,7 +6,7 @@ import ( const ( helmBin = "helm" - appVersion = "v3.0.1" + appVersion = "v3.0.2" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index 67436652..df341552 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,4 +1,4 @@ -# v3.0.1 +# v3.0.2 This is a bugfix release to support Helm v3. It is recommended you read the [Helm 3 migration guide](https://helm.sh/docs/topics/v2_v3_migration/) before using this release. @@ -6,9 +6,9 @@ It is recommended you read the [Helm 3 migration guide](https://helm.sh/docs/top > Starting from Helmsman v3.0.0 GA release, support for Helmsman v1.x will be limited to bug fixes. # Fixes and improvements: -- Fix overlocking issue when looking for untracked releases; PR #378 -- Do not use --all-namespaces to find helm releases; PR #379 -- Remove --purge from reInstall function; PR #381 +- Add concurrency limit on goroutines for getting release state and decision makers; PR #395 +- Add second 'Updated' time layout to parser for release state; PR #393 +- Fix unmarshal issue for quoted version in Chart.yaml; PR #389 # New features: None, bug fix release From 2be12852f090c20ea0e14b452003492d23c0f788 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Schmitz=20von=20H=C3=BClst?= Date: Wed, 22 Jan 2020 18:14:32 +0100 Subject: [PATCH 0576/1127] filter returned charts for actual required chart MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Moritz Schmitz von Hülst --- internal/app/release.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/internal/app/release.go b/internal/app/release.go index 23a57de6..494df148 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -233,12 +233,19 @@ func (r *release) getChartVersion() (string, string) { log.Fatal(fmt.Sprint(err)) } - if len(chartVersions) < 1 { + filteredChartVersions := make([]chartVersion, 0) + for _, chart := range chartVersions { + if chart.Name == r.Chart { + append(filteredChartVersions, chart) + } + } + + if len(filteredChartVersions) < 1 { return "", "Chart [ " + r.Chart + " ] with version [ " + r.Version + " ] is specified but not found in the helm repositories" - } else if len(chartVersions) > 1 { + } else if len(filteredChartVersions) > 1 { return "", "Multiple versions of chart [ " + r.Chart + " ] with version [ " + r.Version + " ] found in the helm repositories" } - return chartVersions[0].Version, "" + return filteredChartVersions[0].Version, "" } // testRelease creates a Helm command to test a particular release. From ff3bca0ff72d3907a96db20ac15ef483e3b54d9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Schmitz=20von=20H=C3=BClst?= Date: Wed, 22 Jan 2020 18:21:58 +0100 Subject: [PATCH 0577/1127] fix code, golang beginner here MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Moritz Schmitz von Hülst --- internal/app/release.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/app/release.go b/internal/app/release.go index 494df148..c473e8c8 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -236,7 +236,7 @@ func (r *release) getChartVersion() (string, string) { filteredChartVersions := make([]chartVersion, 0) for _, chart := range chartVersions { if chart.Name == r.Chart { - append(filteredChartVersions, chart) + filteredChartVersions = append(filteredChartVersions, chart) } } From 48e0836715bf56f3b31237d4be6509919164bc22 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Thu, 30 Jan 2020 18:00:41 +0100 Subject: [PATCH 0578/1127] Get existing helm repos before adding those from DSF in order to limit actions to take --- internal/app/helm_helpers.go | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index 0705418a..942dd149 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -1,6 +1,7 @@ package app import ( + "encoding/json" "errors" "fmt" "net/url" @@ -10,6 +11,12 @@ import ( "github.com/Praqma/helmsman/internal/gcs" ) +type helmRepo struct { + Name string `json:"name"` + Url string `json:"url"` +} + + // helmCmd prepares a helm command to be executed func helmCmd(args []string, desc string) command { return command{ @@ -72,6 +79,24 @@ func updateChartDep(chartPath string) error { // addHelmRepos adds repositories to Helm if they don't exist already. // Helm does not mind if a repo with the same name exists. It treats it as an update. func addHelmRepos(repos map[string]string) error { + var helmRepos []helmRepo + existingRepos := make(map[string]string) + + // get existing helm repositories + cmdList := helmCmd(concat([]string{"repo", "list", "--output", "json"}), "Listing helm repositories") + if reposResult := cmdList.exec(); reposResult.code == 0 { + if err := json.Unmarshal([]byte(reposResult.output), &helmRepos); err != nil { + log.Fatal(fmt.Sprintf("failed to unmarshal Helm CLI output: %s", err)) + } + // create map of existing repositories + for _, repo := range helmRepos { + existingRepos[repo.Name] = repo.Url + } + } else { + if !strings.Contains(reposResult.errors,"no repositories to show") { + return fmt.Errorf("while listing helm repositories: %s", reposResult.errors) + } + } for repoName, repoLink := range repos { basicAuthArgs := []string{} @@ -98,11 +123,15 @@ func addHelmRepos(repos map[string]string) error { } cmd := helmCmd(concat([]string{"repo", "add", repoName, repoLink}, basicAuthArgs), "Adding helm repository [ "+repoName+" ]") - + // check current repository against existing repositories map in order to make sure it's missing and needs to be added + if existingRepoUrl, ok := existingRepos[repoName]; ok { + if repoLink == existingRepoUrl { + continue + } + } if result := cmd.exec(); result.code != 0 { - return fmt.Errorf("While adding helm repository ["+repoName+"]: %w", err) + return fmt.Errorf("While adding helm repository ["+repoName+"]: %s", result.errors) } - } if len(repos) > 0 { From cabbc7362f5e139f79052153f86de1584d82a00a Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Thu, 30 Jan 2020 20:29:52 +0100 Subject: [PATCH 0579/1127] Enhance -target flag to ignore whole cluster state acquiring --- internal/app/decision_maker.go | 16 +++++++++++++--- internal/app/helm_release.go | 17 ++++++++++++++--- internal/app/kube_helpers.go | 8 +++++++- internal/app/main.go | 6 +++++- internal/app/release.go | 10 ++++++++-- internal/app/state.go | 27 +++++++++++++++++++++++++++ 6 files changed, 74 insertions(+), 10 deletions(-) diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index d57700d3..3a765d10 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -166,10 +166,16 @@ func (cs *currentState) getHelmsmanReleases(s *state) map[string]map[string]bool const outputFmt = "custom-columns=NAME:.metadata.name,CTX:.metadata.labels.HELMSMAN_CONTEXT" releases := make(map[string]map[string]bool) sem := make(chan struct{}, resourcePool) + namespaces := make(map[string]namespace) + if len(s.TargetMap) > 0 { + namespaces = s.TargetNamespaces + } else { + namespaces = s.Namespaces + } storageBackend := s.Settings.StorageBackend - for ns := range s.Namespaces { - // aquire + for ns := range namespaces { + // acquire sem <- struct{}{} wg.Add(1) go func(ns string) { @@ -201,7 +207,11 @@ func (cs *currentState) getHelmsmanReleases(s *state) map[string]map[string]bool if len(flds) > 1 { rctx = flds[1] } - + if len(s.TargetMap) > 0 { + if use, ok := s.TargetMap[name]; !ok || !use { + continue + } + } mutex.Lock() if _, ok := releases[ns]; !ok { releases[ns] = make(map[string]bool) diff --git a/internal/app/helm_release.go b/internal/app/helm_release.go index 07350dbc..245f9a18 100644 --- a/internal/app/helm_release.go +++ b/internal/app/helm_release.go @@ -27,12 +27,18 @@ func getHelmReleases(s *state) []helmRelease { allReleases []helmRelease wg sync.WaitGroup mutex = &sync.Mutex{} + namespaces map[string]namespace ) - - for ns := range s.Namespaces { + if len(s.TargetMap) > 0 { + namespaces = s.TargetNamespaces + } else { + namespaces = s.Namespaces + } + for ns := range namespaces { wg.Add(1) go func(ns string) { var releases []helmRelease + var targetReleases []helmRelease defer wg.Done() cmd := helmCmd([]string{"list", "--all", "--max", "0", "--output", "json", "-n", ns}, "Listing all existing releases...") result := cmd.exec() @@ -42,8 +48,13 @@ func getHelmReleases(s *state) []helmRelease { if err := json.Unmarshal([]byte(result.output), &releases); err != nil { log.Fatal(fmt.Sprintf("failed to unmarshal Helm CLI output: %s", err)) } + for _, r := range releases { + if use, ok := s.TargetMap[r.Name]; ok && use { + targetReleases = append(targetReleases, r) + } + } mutex.Lock() - allReleases = append(allReleases, releases...) + allReleases = append(allReleases, targetReleases...) mutex.Unlock() }(ns) } diff --git a/internal/app/kube_helpers.go b/internal/app/kube_helpers.go index f8895b43..d6a813ff 100644 --- a/internal/app/kube_helpers.go +++ b/internal/app/kube_helpers.go @@ -13,8 +13,14 @@ import ( // addNamespaces creates a set of namespaces in your k8s cluster. // If a namespace with the same name exists, it will skip it. // If --ns-override flag is used, it only creates the provided namespace in that flag -func addNamespaces(namespaces map[string]namespace) { +func addNamespaces(s state) { var wg sync.WaitGroup + var namespaces map[string]namespace + if len(s.TargetMap) > 0 { + namespaces = s.TargetNamespaces + } else { + namespaces = s.Namespaces + } for nsName, ns := range namespaces { wg.Add(1) go func(name string, cfg namespace, wg *sync.WaitGroup) { diff --git a/internal/app/main.go b/internal/app/main.go index ca1bb0e6..4121027c 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -32,6 +32,10 @@ func Main() { defer s.cleanup() flags.readState(&s) + if len(s.TargetMap) > 0 { + s.TargetApps = s.getAppsInTargetsOnly() + s.TargetNamespaces = s.getNamespacesInTargetsOnly() + } settings = s.Settings curContext = s.Context @@ -56,7 +60,7 @@ func Main() { if !flags.noNs { log.Info("Setting up namespaces...") if flags.nsOverride == "" { - addNamespaces(s.Namespaces) + addNamespaces(s) } else { createNamespace(flags.nsOverride) s.overrideAppsNamespace(flags.nsOverride) diff --git a/internal/app/release.go b/internal/app/release.go index c473e8c8..234e20ce 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -142,10 +142,16 @@ func (r *release) validate(appLabel string, names map[string]map[string]bool, s // This function uses Helm search to verify if the chart can be found or not. func validateReleaseCharts(s *state) error { var fail bool + var apps map[string]*release wg := sync.WaitGroup{} sem := make(chan struct{}, resourcePool) - c := make(chan string, len(s.Apps)) - for app, r := range s.Apps { + if len(s.TargetMap) > 0 { + apps = s.TargetApps + } else { + apps = s.Apps + } + c := make(chan string, len(apps)) + for app, r := range apps { sem <- struct{}{} wg.Add(1) go func(r *release, app string) { diff --git a/internal/app/state.go b/internal/app/state.go index 7c392f98..24daa741 100644 --- a/internal/app/state.go +++ b/internal/app/state.go @@ -38,6 +38,8 @@ type state struct { AppsTemplates map[string]*release `yaml:"appsTemplates,omitempty"` TargetMap map[string]bool GroupMap map[string]bool + TargetApps map[string]*release + TargetNamespaces map[string]namespace } // invokes either yaml or toml parser considering file extension @@ -195,6 +197,31 @@ func (s *state) overrideAppsNamespace(newNs string) { } } +// get only those Apps that exist in TargetMap +func (s *state) getAppsInTargetsOnly() map[string]*release { + targetApps := make(map[string]*release) + for appName, use := range s.TargetMap { + if use { + if value, ok := s.Apps[appName]; ok { + targetApps[appName] = value + } + } + } + return targetApps +} + +func (s *state) getNamespacesInTargetsOnly() map[string]namespace { + targetNamespaces := make(map[string]namespace) + for appName, use := range s.TargetMap { + if use { + if value, ok := s.Apps[appName]; ok { + targetNamespaces[value.Namespace] = s.Namespaces[value.Namespace] + } + } + } + return targetNamespaces +} + // print prints the desired state func (s *state) print() { From 1f32de72e5a2af7f42b32ef8bdc8aebe404c955c Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Thu, 30 Jan 2020 20:53:00 +0100 Subject: [PATCH 0580/1127] Fix -target ignoring current helm releases states when no flag defined --- internal/app/helm_release.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/internal/app/helm_release.go b/internal/app/helm_release.go index 245f9a18..58e4b7c3 100644 --- a/internal/app/helm_release.go +++ b/internal/app/helm_release.go @@ -40,7 +40,7 @@ func getHelmReleases(s *state) []helmRelease { var releases []helmRelease var targetReleases []helmRelease defer wg.Done() - cmd := helmCmd([]string{"list", "--all", "--max", "0", "--output", "json", "-n", ns}, "Listing all existing releases...") + cmd := helmCmd([]string{"list", "--all", "--max", "0", "--output", "json", "-n", ns}, "Listing all existing releases in [ "+ns+" ] namespace...") result := cmd.exec() if result.code != 0 { log.Fatal("Failed to list all releases: " + result.errors) @@ -48,10 +48,14 @@ func getHelmReleases(s *state) []helmRelease { if err := json.Unmarshal([]byte(result.output), &releases); err != nil { log.Fatal(fmt.Sprintf("failed to unmarshal Helm CLI output: %s", err)) } - for _, r := range releases { - if use, ok := s.TargetMap[r.Name]; ok && use { - targetReleases = append(targetReleases, r) + if len(s.TargetMap) > 0 { + for _, r := range releases { + if use, ok := s.TargetMap[r.Name]; ok && use { + targetReleases = append(targetReleases, r) + } } + } else { + targetReleases = releases } mutex.Lock() allReleases = append(allReleases, targetReleases...) From 61b000f3463f5248176b66b996101f9643c36ce1 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Thu, 30 Jan 2020 21:14:41 +0100 Subject: [PATCH 0581/1127] Fix tests --- internal/app/release_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/app/release_test.go b/internal/app/release_test.go index e104c971..99ce5b4f 100644 --- a/internal/app/release_test.go +++ b/internal/app/release_test.go @@ -478,9 +478,15 @@ func Test_validateReleaseCharts(t *testing.T) { stt.Apps = tt.args.apps stt.TargetMap = make(map[string]bool) stt.GroupMap = make(map[string]bool) + stt.TargetApps = make(map[string]*release) for _, target := range tt.targetFlag { stt.TargetMap[target] = true } + for name, use := range stt.TargetMap { + if value, ok := stt.Apps[name]; ok && use { + stt.TargetApps[name] = value + } + } for _, group := range tt.groupFlag { stt.GroupMap[group] = true } From 6bdbbf8591b596cda3b2a5d922c471fd6bb965a2 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Sat, 1 Feb 2020 12:37:25 +0100 Subject: [PATCH 0582/1127] Make addNamespaces taking pointer of state --- internal/app/kube_helpers.go | 2 +- internal/app/main.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/app/kube_helpers.go b/internal/app/kube_helpers.go index d6a813ff..9c720d1c 100644 --- a/internal/app/kube_helpers.go +++ b/internal/app/kube_helpers.go @@ -13,7 +13,7 @@ import ( // addNamespaces creates a set of namespaces in your k8s cluster. // If a namespace with the same name exists, it will skip it. // If --ns-override flag is used, it only creates the provided namespace in that flag -func addNamespaces(s state) { +func addNamespaces(s *state) { var wg sync.WaitGroup var namespaces map[string]namespace if len(s.TargetMap) > 0 { diff --git a/internal/app/main.go b/internal/app/main.go index 4121027c..eb67d9ea 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -60,7 +60,7 @@ func Main() { if !flags.noNs { log.Info("Setting up namespaces...") if flags.nsOverride == "" { - addNamespaces(s) + addNamespaces(&s) } else { createNamespace(flags.nsOverride) s.overrideAppsNamespace(flags.nsOverride) From 677e5284e88d1d9e1941a829b26c3902e67f6387 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Sat, 1 Feb 2020 16:18:57 +0100 Subject: [PATCH 0583/1127] Reuse -target flag flow for -group passed --- internal/app/decision_maker_test.go | 40 ++++++++++++++++++++++------- internal/app/helm_helpers.go | 7 +++-- internal/app/main.go | 3 +++ internal/app/release.go | 6 ----- internal/app/state.go | 10 ++++++++ 5 files changed, 47 insertions(+), 19 deletions(-) diff --git a/internal/app/decision_maker_test.go b/internal/app/decision_maker_test.go index 5345f94b..b88b2de3 100644 --- a/internal/app/decision_maker_test.go +++ b/internal/app/decision_maker_test.go @@ -228,6 +228,7 @@ func Test_decide_group(t *testing.T) { r *release s *state currentState *map[string]helmRelease + curContext string } tests := []struct { name string @@ -245,13 +246,26 @@ func Test_decide_group(t *testing.T) { Namespace: "namespace", Enabled: true, }, - s: &state{}, + s: &state{ + Apps: map[string]*release{ + "app": { + Name: "release1", + Namespace: "namespace", + Group: "run-me", + Enabled: true, + }, + }, + }, currentState: &map[string]helmRelease{ - "release1-namespace": { - Namespace: "namespace", - Chart: "chart-1.0.0", + "release2-namespace": { + Name: "release2", + Namespace: "namespace", + Chart: "chart-1.0.0", + Status: "deployed", + HelmsmanContext: "some-other-context", }, }, + curContext: "some-other-context", }, want: ignored, }, @@ -266,16 +280,25 @@ func Test_decide_group(t *testing.T) { Group: "run-me", }, s: &state{ - Context: "default", + Apps: map[string]*release{ + "app": { + Name: "release1", + Namespace: "namespace", + Group: "run-me", + Enabled: true, + }, + }, }, currentState: &map[string]helmRelease{ "release2-namespace": { Name: "release2", Namespace: "namespace", Chart: "chart-1.0.0", + Status: "deployed", HelmsmanContext: "some-other-context", }, }, + curContext: "some-other-context", }, want: create, }, @@ -285,13 +308,12 @@ func Test_decide_group(t *testing.T) { t.Run(tt.name, func(t *testing.T) { tt.args.s.GroupMap = make(map[string]bool) cs := currentState{releases: *tt.args.currentState} - + curContext = tt.args.curContext + tt.args.s.Context = tt.args.curContext for _, target := range tt.targetFlag { tt.args.s.GroupMap[target] = true } - for _, group := range tt.groupFlag { - tt.args.s.GroupMap[group] = true - } + tt.args.s.GroupMap = tt.args.s.getAppsInGroupsAsTargetMap() outcome := plan{} cs.decide(tt.args.r, tt.args.s, &outcome) got := outcome.Decisions[0].Type diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index 942dd149..4cc79bc1 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -12,11 +12,10 @@ import ( ) type helmRepo struct { - Name string `json:"name"` - Url string `json:"url"` + Name string `json:"name"` + Url string `json:"url"` } - // helmCmd prepares a helm command to be executed func helmCmd(args []string, desc string) command { return command{ @@ -93,7 +92,7 @@ func addHelmRepos(repos map[string]string) error { existingRepos[repo.Name] = repo.Url } } else { - if !strings.Contains(reposResult.errors,"no repositories to show") { + if !strings.Contains(reposResult.errors, "no repositories to show") { return fmt.Errorf("while listing helm repositories: %s", reposResult.errors) } } diff --git a/internal/app/main.go b/internal/app/main.go index eb67d9ea..4974c125 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -32,6 +32,9 @@ func Main() { defer s.cleanup() flags.readState(&s) + if len(s.GroupMap) > 0 { + s.TargetMap = s.getAppsInGroupsAsTargetMap() + } if len(s.TargetMap) > 0 { s.TargetApps = s.getAppsInTargetsOnly() s.TargetNamespaces = s.getNamespacesInTargetsOnly() diff --git a/internal/app/release.go b/internal/app/release.go index 234e20ce..4f4fe5a3 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -55,12 +55,6 @@ func (r *release) isConsideredToRun(s *state) bool { } return false } - if len(s.GroupMap) > 0 { - if _, ok := s.GroupMap[r.Group]; ok { - return true - } - return false - } return true } diff --git a/internal/app/state.go b/internal/app/state.go index 24daa741..30baa04a 100644 --- a/internal/app/state.go +++ b/internal/app/state.go @@ -197,6 +197,16 @@ func (s *state) overrideAppsNamespace(newNs string) { } } +func (s *state) getAppsInGroupsAsTargetMap() map[string]bool { + targetApps := make(map[string]bool) + for appName, data := range s.Apps { + if use, ok := s.GroupMap[data.Group]; ok && use { + targetApps[appName] = true + } + } + return targetApps +} + // get only those Apps that exist in TargetMap func (s *state) getAppsInTargetsOnly() map[string]*release { targetApps := make(map[string]*release) From 5d1eda8998e6689524b289798fadf2cad3e4f8b0 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Sat, 1 Feb 2020 16:44:16 +0100 Subject: [PATCH 0584/1127] Fix -group flag tests --- internal/app/decision_maker_test.go | 74 +++++++---------------------- 1 file changed, 16 insertions(+), 58 deletions(-) diff --git a/internal/app/decision_maker_test.go b/internal/app/decision_maker_test.go index b88b2de3..87813e07 100644 --- a/internal/app/decision_maker_test.go +++ b/internal/app/decision_maker_test.go @@ -225,30 +225,21 @@ func Test_decide(t *testing.T) { func Test_decide_group(t *testing.T) { type args struct { - r *release - s *state - currentState *map[string]helmRelease - curContext string + s *state } tests := []struct { - name string - groupFlag []string - targetFlag []string - args args - want decisionType + name string + groupFlag []string + args args + want map[string]bool }{ { name: "decide() - groupMap does not contain this service - skip", groupFlag: []string{"some-group"}, args: args{ - r: &release{ - Name: "release1", - Namespace: "namespace", - Enabled: true, - }, s: &state{ Apps: map[string]*release{ - "app": { + "release1": { Name: "release1", Namespace: "namespace", Group: "run-me", @@ -256,32 +247,16 @@ func Test_decide_group(t *testing.T) { }, }, }, - currentState: &map[string]helmRelease{ - "release2-namespace": { - Name: "release2", - Namespace: "namespace", - Chart: "chart-1.0.0", - Status: "deployed", - HelmsmanContext: "some-other-context", - }, - }, - curContext: "some-other-context", }, - want: ignored, + want: map[string]bool{}, }, { name: "decide() - groupMap contains this service - proceed", groupFlag: []string{"run-me"}, args: args{ - r: &release{ - Name: "release1", - Namespace: "namespace", - Enabled: true, - Group: "run-me", - }, s: &state{ Apps: map[string]*release{ - "app": { + "release1": { Name: "release1", Namespace: "namespace", Group: "run-me", @@ -289,39 +264,22 @@ func Test_decide_group(t *testing.T) { }, }, }, - currentState: &map[string]helmRelease{ - "release2-namespace": { - Name: "release2", - Namespace: "namespace", - Chart: "chart-1.0.0", - Status: "deployed", - HelmsmanContext: "some-other-context", - }, - }, - curContext: "some-other-context", }, - want: create, + want: map[string]bool{ + "release1": true, + }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.args.s.GroupMap = make(map[string]bool) - cs := currentState{releases: *tt.args.currentState} - curContext = tt.args.curContext - tt.args.s.Context = tt.args.curContext - for _, target := range tt.targetFlag { - tt.args.s.GroupMap[target] = true + for _, group := range tt.groupFlag { + tt.args.s.GroupMap[group] = true } - tt.args.s.GroupMap = tt.args.s.getAppsInGroupsAsTargetMap() - outcome := plan{} - cs.decide(tt.args.r, tt.args.s, &outcome) - got := outcome.Decisions[0].Type - t.Log(outcome.Decisions[0].Description) - - // Assert - if got != tt.want { - t.Errorf("decide() = %s, want %s", got, tt.want) + tt.args.s.TargetMap = tt.args.s.getAppsInGroupsAsTargetMap() + if len(tt.args.s.TargetMap) != len(tt.want) { + t.Errorf("decide() = %d, want %d", len(tt.args.s.TargetMap), len(tt.want)) } }) } From 5d76e609ad5ab5e19d9aa9586361bf72085542b8 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Sat, 1 Feb 2020 16:44:31 +0100 Subject: [PATCH 0585/1127] Add exiting on empty apps list for -target or -group flag --- internal/app/main.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/internal/app/main.go b/internal/app/main.go index 4974c125..f710195e 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -34,10 +34,18 @@ func Main() { flags.readState(&s) if len(s.GroupMap) > 0 { s.TargetMap = s.getAppsInGroupsAsTargetMap() + if len(s.TargetMap) == 0 { + log.Info("No apps defined with -group flag were found, exiting...") + os.Exit(0) + } } if len(s.TargetMap) > 0 { s.TargetApps = s.getAppsInTargetsOnly() s.TargetNamespaces = s.getNamespacesInTargetsOnly() + if len(s.TargetApps) == 0 { + log.Info("No apps defined with -target flag were found, exiting...") + os.Exit(0) + } } settings = s.Settings curContext = s.Context From 165248cceae0c90ceb49b4dc337cee1f46cf9748 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Mon, 3 Feb 2020 13:07:32 +0100 Subject: [PATCH 0586/1127] Release v3.1.0 --- .circleci/config.yml | 3 +++ .version | 2 +- internal/app/main.go | 2 +- release-notes.md | 14 ++++++++------ 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c8037cbf..8a09624f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -49,6 +49,9 @@ jobs: docker build -t praqma/helmsman:$TAG-helm-v3.0.2 --build-arg HELM_VERSION=v3.0.2 . --no-cache docker push praqma/helmsman:$TAG-helm-v3.0.2 + docker build -t praqma/helmsman:$TAG-helm-v3.0.3 --build-arg HELM_VERSION=v3.0.3 . --no-cache + docker push praqma/helmsman:$TAG-helm-v3.0.3 + workflows: version: 2 build-test-push-release: diff --git a/.version b/.version index 96506fd2..6c8dc7eb 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.0.2 +v3.1.0 diff --git a/internal/app/main.go b/internal/app/main.go index f710195e..f3ee921d 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -6,7 +6,7 @@ import ( const ( helmBin = "helm" - appVersion = "v3.0.2" + appVersion = "v3.1.0" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index df341552..d2ff4166 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,14 +1,16 @@ -# v3.0.2 +# v3.1.0 -This is a bugfix release to support Helm v3. +This is a minor release. It is recommended you read the [Helm 3 migration guide](https://helm.sh/docs/topics/v2_v3_migration/) before using this release. > Starting from Helmsman v3.0.0 GA release, support for Helmsman v1.x will be limited to bug fixes. # Fixes and improvements: -- Add concurrency limit on goroutines for getting release state and decision makers; PR #395 -- Add second 'Updated' time layout to parser for release state; PR #393 -- Fix unmarshal issue for quoted version in Chart.yaml; PR #389 +- Add helm v3.0.3 to Docker images built +- Fix multiple versions of chart found in the helm repositories; PR #397 +- Get existing helm repos first before adding new ones from DSF in order to limit actions to be taken; PR #403 +- Enhance the way -target flag checks releases states; PR #405 +- Take advantage of enhancements in -target flag flow for -group flags; PR #407 # New features: -None, bug fix release +None From f3b4bcc2a0861c6da6e19d4352154f9e58347cc1 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Tue, 4 Feb 2020 12:49:33 +0100 Subject: [PATCH 0587/1127] Add helm-gcs plugin's existence check when repository with prefix 'gs://' is defined --- internal/app/helm_helpers.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index 4cc79bc1..3cacb82b 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -102,6 +102,9 @@ func addHelmRepos(repos map[string]string) error { // check if repo is in GCS, then perform GCS auth -- needed for private GCS helm repos // failed auth would not throw an error here, as it is possible that the repo is public and does not need authentication if strings.HasPrefix(repoLink, "gs://") { + if !helmPluginExists("gcs") { + log.Fatal(fmt.Sprintf("repository %s can't be used: helm-gcs plugin is missing", repoLink)) + } msg, err := gcs.Auth() if err != nil { log.Fatal(msg) From 27429c37a3ebb8f1d9d04b45588f956a3f2cf93b Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Tue, 4 Feb 2020 13:25:05 +0100 Subject: [PATCH 0588/1127] Disable checking helm-secrets plugin installed when hiera-eyaml used for secrets --- internal/app/release.go | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/internal/app/release.go b/internal/app/release.go index 4f4fe5a3..94e11470 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -406,18 +406,23 @@ func (r *release) getValuesFiles() []string { fileList = append(fileList, r.ValuesFiles...) } - if r.SecretsFile != "" { - if !helmPluginExists("secrets") { - log.Fatal("helm secrets plugin is not installed/configured correctly. Aborting!") + if r.SecretsFile != "" || len(r.SecretsFiles) > 0 { + if settings.EyamlEnabled { + if !toolExists("eyaml") { + log.Fatal("hiera-eyaml is not installed/configured correctly. Aborting!") + } + } else { + if !helmPluginExists("secrets") { + log.Fatal("helm secrets plugin is not installed/configured correctly. Aborting!") + } } + } + if r.SecretsFile != "" { if err := decryptSecret(r.SecretsFile); err != nil { log.Fatal(err.Error()) } fileList = append(fileList, r.SecretsFile+".dec") } else if len(r.SecretsFiles) > 0 { - if !helmPluginExists("secrets") { - log.Fatal("helm secrets plugin is not installed/configured correctly. Aborting!") - } for i := 0; i < len(r.SecretsFiles); i++ { if err := decryptSecret(r.SecretsFiles[i]); err != nil { log.Fatal(err.Error()) From e5a8933cb2ce3200dd7dfeb0ef4f6a5359a2c98f Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Tue, 4 Feb 2020 15:44:02 +0100 Subject: [PATCH 0589/1127] Make decide exiting on pending-upgrade chart state --- internal/app/decision_maker.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index 3a765d10..476da788 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -1,6 +1,7 @@ package app import ( + "os" "regexp" "strings" "sync" @@ -124,6 +125,11 @@ func (cs *currentState) decide(r *release, s *state, p *plan) { p.addDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ "you remove its protection.", r.Priority, noop) } + } else if ok := cs.releaseExists(r, "pending-upgrade"); ok { + log.Error("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is in pending-upgrade state. " + + "This means application is being upgraded outside of this Helmsman invocation's scope." + + "Exiting, as this may cause issues when continuing...") + os.Exit(1) } else { // If there is no release in the cluster with this name and in this namespace, then install it! if _, ok := cs.releases[r.key()]; !ok { From 2090bc49fcb33e6e9c990995d22573fabecccb62 Mon Sep 17 00:00:00 2001 From: bha Date: Wed, 5 Feb 2020 06:52:23 +0100 Subject: [PATCH 0590/1127] add slack output for error messages --- internal/app/logging.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/app/logging.go b/internal/app/logging.go index 3ed88239..3322c27b 100644 --- a/internal/app/logging.go +++ b/internal/app/logging.go @@ -31,6 +31,9 @@ func (l *Logger) Verbose(message string) { } func (l *Logger) Error(message string) { + if _, err := url.ParseRequestURI(settings.SlackWebhook); err == nil { + notifySlack(message, settings.SlackWebhook, true, flags.apply) + } baseLogger.Error(message) } From 4ce598a97590354ff3dac4965d915070653be16c Mon Sep 17 00:00:00 2001 From: bha Date: Wed, 5 Feb 2020 07:54:53 +0100 Subject: [PATCH 0591/1127] add version to upgrade and install cmds for better output --- internal/app/release.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/app/release.go b/internal/app/release.go index 94e11470..1d7ba48f 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -257,9 +257,9 @@ func (r *release) test(p *plan) { // installRelease creates a Helm command to install a particular release in a particular namespace using a particular Tiller. func (r *release) install(p *plan) { - cmd := helmCmd(r.getHelmArgsFor("install"), "Installing release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") + cmd := helmCmd(r.getHelmArgsFor("install"), "Installing release [ "+r.Name+" ] version [ "+r.Version+" ] in namespace [ "+r.Namespace+" ]") p.addCommand(cmd, r.Priority, r) - p.addDecision("Release [ "+r.Name+" ] will be installed in [ "+r.Namespace+" ] namespace", r.Priority, create) + p.addDecision("Release [ "+r.Name+" ] version [ "+r.Version+" ] will be installed in [ "+r.Namespace+" ] namespace", r.Priority, create) if r.Test { r.test(p) @@ -310,7 +310,7 @@ func (r *release) upgrade(p *plan) { if flags.forceUpgrades { force = "--force" } - cmd := helmCmd(concat(r.getHelmArgsFor("upgrade"), []string{force}, r.getWait(), r.getHelmFlags()), "Upgrading release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") + cmd := helmCmd(concat(r.getHelmArgsFor("upgrade"), []string{force}, r.getWait(), r.getHelmFlags()), "Upgrading release [ "+r.Name+" ] to version [ "+r.Version+" ] in namespace [ "+r.Namespace+" ]") p.addCommand(cmd, r.Priority, r) } @@ -321,7 +321,7 @@ func (r *release) reInstall(p *plan) { delCmd := helmCmd(concat(r.getHelmArgsFor("uninstall"), flags.getDryRunFlags()), "Deleting release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") p.addCommand(delCmd, r.Priority, r) - installCmd := helmCmd(r.getHelmArgsFor("install"), "Installing release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") + installCmd := helmCmd(r.getHelmArgsFor("install"), "Installing release [ "+r.Name+" ] version [ "+r.Version+" ] in namespace [ "+r.Namespace+" ]") p.addCommand(installCmd, r.Priority, r) } From e09f9130c5ea3e985ca8a2ec07dec41a2f8e74ca Mon Sep 17 00:00:00 2001 From: bha Date: Wed, 5 Feb 2020 11:27:27 +0100 Subject: [PATCH 0592/1127] bold message and change type to text --- internal/app/utils.go | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/internal/app/utils.go b/internal/app/utils.go index 4b78d058..3ac9a648 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -374,7 +374,7 @@ func deleteFile(path string) { // and the webhook URL as well as a flag specifying if this is a failure message or not // It returns true if the sending of the message is successful, otherwise returns false func notifySlack(content string, url string, failure bool, executing bool) bool { - log.Info("Posting notifications to slack ... ") + log.Info("Posting notifications to Slack ... ") color := "#36a64f" // green if failure { @@ -383,16 +383,23 @@ func notifySlack(content string, url string, failure bool, executing bool) bool var pretext string if content == "" { - pretext = "No actions to perform!" + pretext = "*No actions to perform!*" } else if failure { - pretext = "Failed to generate/execute a plan: " + pretext = "*Failed to generate/execute a plan: *" } else if executing && !failure { - pretext = "Here is what I have done: " + pretext = "*Here is what I have done: *" } else { - pretext = "Here is what I am going to do:" + pretext = "*Here is what I am going to do: *" } t := time.Now().UTC() + content_split := strings.Split(content, "\n") + + for i := range content_split { + content_split[i] = "*" + content_split[i] + "*" + } + + content_bold := strings.Join(content_split, "\n") var jsonStr = []byte(`{ "attachments": [ @@ -400,7 +407,7 @@ func notifySlack(content string, url string, failure bool, executing bool) bool "fallback": "Helmsman results.", "color": "` + color + `" , "pretext": "` + pretext + `", - "title": "` + content + `", + "text": "` + content_bold + `", "footer": "Helmsman ` + appVersion + `", "ts": ` + strconv.FormatInt(t.Unix(), 10) + ` } From d543c68ad7983524201c2acf1ee0762ad38fd070 Mon Sep 17 00:00:00 2001 From: bha Date: Wed, 5 Feb 2020 13:05:04 +0100 Subject: [PATCH 0593/1127] add logo only file to use for slack --- docs/images/helmsman_logo.png | Bin 0 -> 66218 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/images/helmsman_logo.png diff --git a/docs/images/helmsman_logo.png b/docs/images/helmsman_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..63b0fe240356e4d1e506dc1425f296ba416ae7ff GIT binary patch literal 66218 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 literal 0 HcmV?d00001 From 41f675a63cf7737dd16f30c38ee789ff8ed54f75 Mon Sep 17 00:00:00 2001 From: bha Date: Wed, 5 Feb 2020 13:21:15 +0100 Subject: [PATCH 0594/1127] convert png to svg image for logo --- docs/images/helmsman_logo.png | Bin 66218 -> 0 bytes docs/images/helmsman_logo.svg | 1 + 2 files changed, 1 insertion(+) delete mode 100644 docs/images/helmsman_logo.png create mode 100644 docs/images/helmsman_logo.svg diff --git a/docs/images/helmsman_logo.png b/docs/images/helmsman_logo.png deleted file mode 100644 index 63b0fe240356e4d1e506dc1425f296ba416ae7ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 66218 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 diff --git a/docs/images/helmsman_logo.svg b/docs/images/helmsman_logo.svg new file mode 100644 index 00000000..ea3b1a7a --- /dev/null +++ b/docs/images/helmsman_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file From e6d89a34b7dfa65af52c17292563f944c5c016bc Mon Sep 17 00:00:00 2001 From: bha Date: Wed, 5 Feb 2020 13:34:15 +0100 Subject: [PATCH 0595/1127] remove svg because slack doesnt support and create new slack sized logo --- docs/images/helmsman_logo.png | Bin 0 -> 23005 bytes docs/images/helmsman_logo.svg | 1 - 2 files changed, 1 deletion(-) create mode 100644 docs/images/helmsman_logo.png delete mode 100644 docs/images/helmsman_logo.svg diff --git a/docs/images/helmsman_logo.png b/docs/images/helmsman_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..48bd25c32fb80550a17542390905bd04294e7eb5 GIT binary patch literal 23005 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 literal 0 HcmV?d00001 diff --git a/docs/images/helmsman_logo.svg b/docs/images/helmsman_logo.svg deleted file mode 100644 index ea3b1a7a..00000000 --- a/docs/images/helmsman_logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file From 17667e782d1ce580da0f76a107688cbac4709f48 Mon Sep 17 00:00:00 2001 From: bha Date: Wed, 5 Feb 2020 13:40:42 +0100 Subject: [PATCH 0596/1127] give up on logo for now because it is not proper dimensions to work well for slack footer --- docs/images/helmsman_logo.png | Bin 23005 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 docs/images/helmsman_logo.png diff --git a/docs/images/helmsman_logo.png b/docs/images/helmsman_logo.png deleted file mode 100644 index 48bd25c32fb80550a17542390905bd04294e7eb5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23005 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 From 36f1726bf638b7885be67bf83a4788abc7cd933f Mon Sep 17 00:00:00 2001 From: bha Date: Wed, 5 Feb 2020 14:45:01 +0100 Subject: [PATCH 0597/1127] fix spacing --- internal/app/logging.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/app/logging.go b/internal/app/logging.go index 3322c27b..9c850726 100644 --- a/internal/app/logging.go +++ b/internal/app/logging.go @@ -31,9 +31,9 @@ func (l *Logger) Verbose(message string) { } func (l *Logger) Error(message string) { - if _, err := url.ParseRequestURI(settings.SlackWebhook); err == nil { - notifySlack(message, settings.SlackWebhook, true, flags.apply) - } + if _, err := url.ParseRequestURI(settings.SlackWebhook); err == nil { + notifySlack(message, settings.SlackWebhook, true, flags.apply) + } baseLogger.Error(message) } From cc497d7d43cdcb40dfffbd9d60ec23add6faa49c Mon Sep 17 00:00:00 2001 From: bha Date: Wed, 5 Feb 2020 14:51:26 +0100 Subject: [PATCH 0598/1127] add extra asterisk for easier reading --- internal/app/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/app/utils.go b/internal/app/utils.go index 3ac9a648..6ba6b82e 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -396,7 +396,7 @@ func notifySlack(content string, url string, failure bool, executing bool) bool content_split := strings.Split(content, "\n") for i := range content_split { - content_split[i] = "*" + content_split[i] + "*" + content_split[i] = "* *" + content_split[i] + "*" } content_bold := strings.Join(content_split, "\n") From eec2556acc127fab697b795a6ea24ac430ed9ac3 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Wed, 5 Feb 2020 15:22:30 +0100 Subject: [PATCH 0599/1127] Define helm release status as const for decision making --- internal/app/decision_maker.go | 8 ++++---- internal/app/helm_release.go | 7 +++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index 476da788..1194e060 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -103,21 +103,21 @@ func (cs *currentState) decide(r *release, s *state, p *plan) { return } - if ok := cs.releaseExists(r, "deployed"); ok { + if ok := cs.releaseExists(r, helmStatusDeployed); ok { if !r.isProtected(cs, s) { cs.inspectUpgradeScenario(r, p) // upgrade or move } else { p.addDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ "you remove its protection.", r.Priority, noop) } - } else if ok := cs.releaseExists(r, "deleted"); ok { + } else if ok := cs.releaseExists(r, helmStatusDeleted); ok { if !r.isProtected(cs, s) { r.rollback(cs, p) // rollback } else { p.addDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ "you remove its protection.", r.Priority, noop) } - } else if ok := cs.releaseExists(r, "failed"); ok { + } else if ok := cs.releaseExists(r, helmStatusFailed); ok { if !r.isProtected(cs, s) { p.addDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is in FAILED state. Upgrade is scheduled!", r.Priority, change) r.upgrade(p) @@ -125,7 +125,7 @@ func (cs *currentState) decide(r *release, s *state, p *plan) { p.addDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ "you remove its protection.", r.Priority, noop) } - } else if ok := cs.releaseExists(r, "pending-upgrade"); ok { + } else if ok := cs.releaseExists(r, helmStatusPending); ok { log.Error("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is in pending-upgrade state. " + "This means application is being upgraded outside of this Helmsman invocation's scope." + "Exiting, as this may cause issues when continuing...") diff --git a/internal/app/helm_release.go b/internal/app/helm_release.go index 58e4b7c3..dff01453 100644 --- a/internal/app/helm_release.go +++ b/internal/app/helm_release.go @@ -9,6 +9,13 @@ import ( "sync" ) +const ( + helmStatusDeployed = "deployed" + helmStatusDeleted = "deleted" + helmStatusFailed = "failed" + helmStatusPending = "pending-upgrade" +) + // helmRelease represents the current state of a release type helmRelease struct { Name string `json:"Name"` From f9e4909ed09aa2b03fd6e5f03202322e4b28c321 Mon Sep 17 00:00:00 2001 From: bha Date: Wed, 5 Feb 2020 19:58:10 +0100 Subject: [PATCH 0600/1127] add mrkdwn_in to fix formatting on mobile --- internal/app/utils.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/app/utils.go b/internal/app/utils.go index 6ba6b82e..dcd1fd92 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -409,7 +409,8 @@ func notifySlack(content string, url string, failure bool, executing bool) bool "pretext": "` + pretext + `", "text": "` + content_bold + `", "footer": "Helmsman ` + appVersion + `", - "ts": ` + strconv.FormatInt(t.Unix(), 10) + ` + "ts": ` + strconv.FormatInt(t.Unix(), 10) + `, + "mrkdwn_in": ["text","pretext"] } ] }`) From 3d4695674e0b70b80e7f20e754b91bb8849c5939 Mon Sep 17 00:00:00 2001 From: bha Date: Wed, 5 Feb 2020 20:50:53 +0100 Subject: [PATCH 0601/1127] fix error message output formatting --- internal/app/utils.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/internal/app/utils.go b/internal/app/utils.go index dcd1fd92..be9cb2d2 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -401,6 +401,11 @@ func notifySlack(content string, url string, failure bool, executing bool) bool content_bold := strings.Join(content_split, "\n") + if failure { + content_trimmed := strings.TrimSuffix(content, "\n") + content_bold = "*" + content_trimmed + "*" + } + var jsonStr = []byte(`{ "attachments": [ { From a6acc80aae9e2af00a56350f67b28dabbfff49f4 Mon Sep 17 00:00:00 2001 From: bha Date: Thu, 6 Feb 2020 06:18:12 +0100 Subject: [PATCH 0602/1127] refactor --- internal/app/utils.go | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/internal/app/utils.go b/internal/app/utils.go index be9cb2d2..23f8d380 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -381,30 +381,29 @@ func notifySlack(content string, url string, failure bool, executing bool) bool color = "#FF0000" // red } + var contentBold string var pretext string + if content == "" { pretext = "*No actions to perform!*" + contentBold = content } else if failure { pretext = "*Failed to generate/execute a plan: *" + contentTrimmed := strings.TrimSuffix(content, "\n") + contentBold = "*" + contentTrimmed + "*" } else if executing && !failure { pretext = "*Here is what I have done: *" + contentBold = "*" + content + "*" } else { pretext = "*Here is what I am going to do: *" + contentSplit := strings.Split(content, "\n") + for i := range contentSplit { + contentSplit[i] = "* *" + contentSplit[i] + "*" + } + contentBold = strings.Join(contentSplit, "\n") } t := time.Now().UTC() - content_split := strings.Split(content, "\n") - - for i := range content_split { - content_split[i] = "* *" + content_split[i] + "*" - } - - content_bold := strings.Join(content_split, "\n") - - if failure { - content_trimmed := strings.TrimSuffix(content, "\n") - content_bold = "*" + content_trimmed + "*" - } var jsonStr = []byte(`{ "attachments": [ @@ -412,7 +411,7 @@ func notifySlack(content string, url string, failure bool, executing bool) bool "fallback": "Helmsman results.", "color": "` + color + `" , "pretext": "` + pretext + `", - "text": "` + content_bold + `", + "text": "` + contentBold + `", "footer": "Helmsman ` + appVersion + `", "ts": ` + strconv.FormatInt(t.Unix(), 10) + `, "mrkdwn_in": ["text","pretext"] From 246f28c7967dd3a1f8d8ff872100d489137a29e1 Mon Sep 17 00:00:00 2001 From: bha Date: Thu, 6 Feb 2020 06:23:41 +0100 Subject: [PATCH 0603/1127] change verb tense in output to user --- internal/app/helm_release.go | 2 +- internal/app/release.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/app/helm_release.go b/internal/app/helm_release.go index 58e4b7c3..fb679bbe 100644 --- a/internal/app/helm_release.go +++ b/internal/app/helm_release.go @@ -72,7 +72,7 @@ func (r *helmRelease) key() string { // uninstall creates the helm command to uninstall an untracked release func (r *helmRelease) uninstall(p *plan) { - cmd := helmCmd(concat([]string{"uninstall", r.Name, "--namespace", r.Namespace}, flags.getDryRunFlags()), "Deleting untracked release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") + cmd := helmCmd(concat([]string{"uninstall", r.Name, "--namespace", r.Namespace}, flags.getDryRunFlags()), "Delete untracked release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") p.addCommand(cmd, -800, nil) } diff --git a/internal/app/release.go b/internal/app/release.go index 1d7ba48f..05ef1213 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -257,7 +257,7 @@ func (r *release) test(p *plan) { // installRelease creates a Helm command to install a particular release in a particular namespace using a particular Tiller. func (r *release) install(p *plan) { - cmd := helmCmd(r.getHelmArgsFor("install"), "Installing release [ "+r.Name+" ] version [ "+r.Version+" ] in namespace [ "+r.Namespace+" ]") + cmd := helmCmd(r.getHelmArgsFor("install"), "Install release [ "+r.Name+" ] version [ "+r.Version+" ] in namespace [ "+r.Namespace+" ]") p.addCommand(cmd, r.Priority, r) p.addDecision("Release [ "+r.Name+" ] version [ "+r.Version+" ] will be installed in [ "+r.Namespace+" ] namespace", r.Priority, create) @@ -310,7 +310,7 @@ func (r *release) upgrade(p *plan) { if flags.forceUpgrades { force = "--force" } - cmd := helmCmd(concat(r.getHelmArgsFor("upgrade"), []string{force}, r.getWait(), r.getHelmFlags()), "Upgrading release [ "+r.Name+" ] to version [ "+r.Version+" ] in namespace [ "+r.Namespace+" ]") + cmd := helmCmd(concat(r.getHelmArgsFor("upgrade"), []string{force}, r.getWait(), r.getHelmFlags()), "Upgrade release [ "+r.Name+" ] to version [ "+r.Version+" ] in namespace [ "+r.Namespace+" ]") p.addCommand(cmd, r.Priority, r) } @@ -318,10 +318,10 @@ func (r *release) upgrade(p *plan) { // reInstall purge deletes a release and reinstalls it. // This is used when moving a release to another namespace or when changing the chart used for it. func (r *release) reInstall(p *plan) { - delCmd := helmCmd(concat(r.getHelmArgsFor("uninstall"), flags.getDryRunFlags()), "Deleting release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") + delCmd := helmCmd(concat(r.getHelmArgsFor("uninstall"), flags.getDryRunFlags()), "Delete release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") p.addCommand(delCmd, r.Priority, r) - installCmd := helmCmd(r.getHelmArgsFor("install"), "Installing release [ "+r.Name+" ] version [ "+r.Version+" ] in namespace [ "+r.Namespace+" ]") + installCmd := helmCmd(r.getHelmArgsFor("install"), "Install release [ "+r.Name+" ] version [ "+r.Version+" ] in namespace [ "+r.Namespace+" ]") p.addCommand(installCmd, r.Priority, r) } From ec9d72132b74c784e62884c3f555e73598e78696 Mon Sep 17 00:00:00 2001 From: bha Date: Thu, 6 Feb 2020 12:33:32 +0100 Subject: [PATCH 0604/1127] trim spaces around error message --- internal/app/plan.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/app/plan.go b/internal/app/plan.go index f98d8cf7..14ad2ead 100644 --- a/internal/app/plan.go +++ b/internal/app/plan.go @@ -97,7 +97,7 @@ func (p *plan) exec() { if !flags.verbose { errorMsg = strings.Split(result.errors, "---")[0] } - log.Fatal(fmt.Sprintf("Command returned [ %d ] exit code and error message [ %s ]", result.code, errorMsg)) + log.Fatal(fmt.Sprintf("Command returned [ %d ] exit code and error message [ %s ]", result.code, strings.TrimSpace(errorMsg))) } else { log.Notice(result.output) log.Notice("Finished: " + cmd.Command.Description) From 6975641228e592377daeeee370cfbdb107cd1872 Mon Sep 17 00:00:00 2001 From: "bha@marketlogicsoftware.com" Date: Sun, 9 Feb 2020 21:20:05 +0100 Subject: [PATCH 0605/1127] remove redundant bit --- internal/app/utils.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/app/utils.go b/internal/app/utils.go index 23f8d380..c380b83d 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -386,7 +386,6 @@ func notifySlack(content string, url string, failure bool, executing bool) bool if content == "" { pretext = "*No actions to perform!*" - contentBold = content } else if failure { pretext = "*Failed to generate/execute a plan: *" contentTrimmed := strings.TrimSuffix(content, "\n") From d0f60466d53ce30dce36d4511864bb66fc92bdb5 Mon Sep 17 00:00:00 2001 From: Christophe-Alexandre 'Surian' Ferriol Date: Mon, 10 Feb 2020 16:33:16 +0100 Subject: [PATCH 0606/1127] get chart name from helm show chart command --- internal/app/helm_helpers.go | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index 3cacb82b..ed58986a 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "net/url" - "regexp" "strings" "github.com/Praqma/helmsman/internal/gcs" @@ -25,18 +24,25 @@ func helmCmd(args []string, desc string) command { } } -var chartNameExtractor = regexp.MustCompile(`[\\/]([^\\/]+)$`) - // extractChartName extracts the Helm chart name from full chart name in the desired state. -// example: it extracts "chartY" from "repoX/chartY" and "chartZ" from "c:\charts\chartZ" func extractChartName(releaseChart string) string { + cmd := helmCmd([]string{"show", "chart", releaseChart}, "Show chart information") + + result := cmd.exec() + if result.code != 0 { + log.Fatal("While getting chart information: " + result.errors) + } - m := chartNameExtractor.FindStringSubmatch(releaseChart) - if len(m) == 2 { - return m[1] + name := "" + for _, v := range strings.Split(result.output, "\n") { + split := strings.Split(v, ":") + if len(split) == 2 && split[0] == "name" { + name = strings.TrimSpace(split[1]) + break + } } - return "" + return name } // getHelmClientVersion returns Helm client Version From 7e5d5e0c7f385f2e3cf9b4da40c01e289e4b077b Mon Sep 17 00:00:00 2001 From: Christophe-Alexandre 'Surian' Ferriol Date: Tue, 11 Feb 2020 13:24:37 +0100 Subject: [PATCH 0607/1127] trim space and quotes --- internal/app/helm_helpers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index ed58986a..2666c4b0 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -37,7 +37,7 @@ func extractChartName(releaseChart string) string { for _, v := range strings.Split(result.output, "\n") { split := strings.Split(v, ":") if len(split) == 2 && split[0] == "name" { - name = strings.TrimSpace(split[1]) + name = strings.Trim(split[1], `"' `) break } } From 428de773d5faab3640c0b667b45108c5e298ff3b Mon Sep 17 00:00:00 2001 From: Nicolas Degory Date: Mon, 2 Mar 2020 18:45:12 -0800 Subject: [PATCH 0608/1127] Errorf call has error-wrapping directive %w Signed-off-by: Nicolas Degory --- internal/app/utils.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/app/utils.go b/internal/app/utils.go index c380b83d..c627fc60 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -419,14 +419,14 @@ func notifySlack(content string, url string, failure bool, executing bool) bool }`) req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr)) if err != nil { - log.Errorf("Failed to send slack message: %w", err) + log.Errorf("Failed to send slack message: %v", err) } req.Header.Set("Content-Type", "application/json") client := &http.Client{} resp, err := client.Do(req) if err != nil { - log.Errorf("Failed to send notification to slack: %w", err) + log.Errorf("Failed to send notification to slack: %v", err) } defer resp.Body.Close() From d02f9cc6158e61ad8312dd607a0222363e68c3a4 Mon Sep 17 00:00:00 2001 From: Nicolas Degory Date: Mon, 2 Mar 2020 18:45:38 -0800 Subject: [PATCH 0609/1127] update repos also on destroy Signed-off-by: Nicolas Degory --- internal/app/main.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/internal/app/main.go b/internal/app/main.go index f3ee921d..812af544 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -59,11 +59,9 @@ func Main() { } // add repos -- fails if they are not valid - if !flags.destroy { - log.Info("Setting up helm...") - if err := addHelmRepos(s.HelmRepos); err != nil { - log.Fatal(err.Error()) - } + log.Info("Setting up helm...") + if err := addHelmRepos(s.HelmRepos); err != nil && !flags.destroy { + log.Fatal(err.Error()) } if flags.apply || flags.dryRun || flags.destroy { From fa0ce4c95933991cc187c10dec7ee47705db1f3e Mon Sep 17 00:00:00 2001 From: Nicolas Degory Date: Mon, 2 Mar 2020 19:02:03 -0800 Subject: [PATCH 0610/1127] bump go, alpine, helm and helm-diff versions Signed-off-by: Nicolas Degory --- Dockerfile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2061bb27..4476c618 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ -ARG GO_VERSION="1.13.5" -ARG ALPINE_VERSION="3.10" -ARG GLOBAL_KUBE_VERSION="v1.14.8" -ARG GLOBAL_HELM_VERSION="v3.0.2" -ARG GLOBAL_HELM_DIFF_VERSION="v3.0.0-rc.7" +ARG GO_VERSION="1.13.8" +ARG ALPINE_VERSION="3.11" +ARG GLOBAL_KUBE_VERSION="v1.14.10" +ARG GLOBAL_HELM_VERSION="v3.1.1" +ARG GLOBAL_HELM_DIFF_VERSION="v3.1.1" FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} as builder From 4a172fbdc724bf92575b6b043ed79647087e55c9 Mon Sep 17 00:00:00 2001 From: Spinnaker Date: Thu, 12 Mar 2020 23:11:04 +0100 Subject: [PATCH 0611/1127] Override getContext operation for faster deployments --- internal/app/cli.go | 2 ++ internal/app/decision_maker.go | 9 +++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/internal/app/cli.go b/internal/app/cli.go index e372c572..ee713585 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -50,6 +50,7 @@ type cli struct { noFancy bool noNs bool nsOverride string + contextOverride string skipValidation bool keepUntrackedReleases bool showDiff bool @@ -82,6 +83,7 @@ func (c *cli) parse() { flag.IntVar(&c.diffContext, "diff-context", -1, "number of lines of context to show around changes in helm diff output") flag.StringVar(&c.kubeconfig, "kubeconfig", "", "path to the kubeconfig file to use for CLI requests") flag.StringVar(&c.nsOverride, "ns-override", "", "override defined namespaces with this one") + flag.StringVar(&c.contextOverride, "context-override", "", "override releases context with this one") flag.BoolVar(&c.apply, "apply", false, "apply the plan directly") flag.BoolVar(&c.dryRun, "dry-run", false, "apply the dry-run option for helm commands.") flag.BoolVar(&c.destroy, "destroy", false, "delete all deployed releases.") diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index 1194e060..9535dba3 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -41,7 +41,12 @@ func buildState(s *state) *currentState { // release <-sem }() - r.HelmsmanContext = getReleaseContext(r.Name, r.Namespace) + if flags.contextOverride == "" { + r.HelmsmanContext = getReleaseContext(r.Name, r.Namespace) + } else { + log.Info("Overriding Helmsman context for " + r.Name + " as " + flags.contextOverride) + r.HelmsmanContext = flags.contextOverride + } cs.releases[r.key()] = r }(r) } @@ -126,7 +131,7 @@ func (cs *currentState) decide(r *release, s *state, p *plan) { "you remove its protection.", r.Priority, noop) } } else if ok := cs.releaseExists(r, helmStatusPending); ok { - log.Error("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is in pending-upgrade state. " + + log.Error("Release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ] is in pending-upgrade state. " + "This means application is being upgraded outside of this Helmsman invocation's scope." + "Exiting, as this may cause issues when continuing...") os.Exit(1) From f635086a6a74e6177cfab67c8bee1e9befa643f2 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Fri, 20 Mar 2020 12:08:44 +0100 Subject: [PATCH 0612/1127] support helmsman lifecycle hooks --- examples/example.toml | 78 ++++++---- examples/example.yaml | 51 +++--- examples/job.yaml | 14 ++ internal/app/decision_maker.go | 14 +- internal/app/helm_release.go | 8 +- internal/app/kube_helpers.go | 8 +- internal/app/plan.go | 66 +++++--- internal/app/plan_test.go | 2 +- internal/app/release.go | 275 ++++++++++++++++++++++++++++----- internal/app/release_test.go | 159 ++++++++++++++++++- internal/app/state.go | 8 + internal/app/utils.go | 202 +++++++++++++++++++----- 12 files changed, 727 insertions(+), 158 deletions(-) create mode 100644 examples/job.yaml diff --git a/examples/example.toml b/examples/example.toml index bef218c5..4bc07f5e 100644 --- a/examples/example.toml +++ b/examples/example.toml @@ -24,7 +24,7 @@ context= "test-infra" # defaults to "default" if not provided kubeContext = "minikube" # will try connect to this context first, if it does not exist, it will be created using the details below # username = "admin" # password = "$K8S_PASSWORD" # the name of an environment variable containing the k8s password - clusterURI = "${SET_URI}" # the name of an environment variable containing the cluster API + # clusterURI = "${SET_URI}" # the name of an environment variable containing the cluster API # #clusterURI = "https://192.168.99.100:8443" # equivalent to the above # storageBackend = "secret" # default is secret # slackWebhook = "$slack" # or "your slack webhook url" @@ -32,6 +32,11 @@ context= "test-infra" # defaults to "default" if not provided #### to use bearer token: # bearerToken = true # clusterURI = "https://kubernetes.default" + [settings.globalHooks] + successCondition= "Initialized" + deleteOnSuccess= true + postInstall= "job.yaml" + @@ -41,23 +46,23 @@ context= "test-infra" # defaults to "default" if not provided # protected = -- default to false [namespaces] [namespaces.production] - protected = true - [[namespaces.production.limits]] - type = "Container" - [namespaces.production.limits.default] - cpu = "300m" - memory = "200Mi" - [namespaces.production.limits.defaultRequest] - cpu = "200m" - memory = "100Mi" - [[namespaces.production.limits]] - type = "Pod" - [namespaces.production.limits.max] - memory = "300Mi" + # protected = true + # [[namespaces.production.limits]] + # type = "Container" + # [namespaces.production.limits.default] + # cpu = "300m" + # memory = "200Mi" + # [namespaces.production.limits.defaultRequest] + # cpu = "200m" + # memory = "100Mi" + # [[namespaces.production.limits]] + # type = "Pod" + # [namespaces.production.limits.max] + # memory = "300Mi" [namespaces.staging] - protected = false - [namespaces.staging.labels] - env = "staging" + # protected = false + # [namespaces.staging.labels] + # env = "staging" # define any private/public helm charts repos you would like to get charts from @@ -78,7 +83,7 @@ context= "test-infra" # defaults to "default" if not provided [apps] [apps.argo] - namespace = "staging" # maps to the namespace as defined in namespaces above + namespace = "production" # maps to the namespace as defined in namespaces above enabled = true # change to false if you want to delete this app release [default = false] chart = "argo/argo" # changing the chart name means delete and recreate this release version = "0.6.4" # chart version @@ -88,24 +93,35 @@ context= "test-infra" # defaults to "default" if not provided protected = true priority= -3 wait = true + [apps.argo.hooks] + successCondition= "Complete" + successTimeout= "90s" + # deleteOnSuccess= true + preInstall="job.yaml" + # preInstall="https://github.com/jetstack/cert-manager/releases/download/v0.14.0/cert-manager.crds.yaml" + # postInstall="https://raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml" + # preUpgrade="job.yaml" + # postUpgrade="job.yaml" + # preDelete="job.yaml" + # postDelete="job.yaml" # [apps.argo.setString] # values to override values from values.yaml with values from env vars or directly entered-- useful for passing secrets to charts - # AdminPassword="$SOME_PASSWORD" # $JENKINS_PASSWORD must exist in the environment + # AdminPassword="$SOME_PASSWORD" # $SOME_PASSWORD must exist in the environment # MyLongIntVar="1234567890" # [apps.argo.set] # installCRD="true" - [apps.artifactory] - namespace = "production" # maps to the namespace as defined in namespaces above - enabled = true # change to false if you want to delete this app release [default = false] - chart = "jfrog/artifactory" # changing the chart name means delete and recreate this release - version = "8.3.2" # chart version - ### Optional values below - valuesFile = "" # leaving it empty uses the default chart values - test = false # run the tests when this release is installed for the first time only - priority= -2 - noHooks= false - timeout= 300 - helmFlags= [] # additional helm flags for this release + # [apps.artifactory] + # namespace = "production" # maps to the namespace as defined in namespaces above + # enabled = true # change to false if you want to delete this app release [default = false] + # chart = "jfrog/artifactory" # changing the chart name means delete and recreate this release + # version = "8.3.2" # chart version + # ### Optional values below + # valuesFile = "" # leaving it empty uses the default chart values + # test = false # run the tests when this release is installed for the first time only + # priority= -2 + # noHooks= false + # timeout= 300 + # helmFlags= [] # additional helm flags for this release # See https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md#apps for more apps options diff --git a/examples/example.yaml b/examples/example.yaml index 704d7a51..521d3624 100644 --- a/examples/example.yaml +++ b/examples/example.yaml @@ -30,26 +30,30 @@ settings: #### to use bearer token: # bearerToken: true # clusterURI: "https://kubernetes.default" + # globalHooks: + # successCondition: "Initialized" + # deleteOnSuccess: true + # postInstall: "job.yaml" # define your environments and their k8s namespaces namespaces: production: - protected: true - limits: - - type: Container - default: - cpu: "300m" - memory: "200Mi" - defaultRequest: - cpu: "200m" - memory: "100Mi" - - type: Pod - max: - memory: "300Mi" + # protected: true + # limits: + # - type: Container + # default: + # cpu: "300m" + # memory: "200Mi" + # defaultRequest: + # cpu: "200m" + # memory: "100Mi" + # - type: Pod + # max: + # memory: "300Mi" staging: - protected: false - labels: - env: "staging" + # protected: false + # labels: + # env: "staging" # define any private/public helm charts repos you would like to get charts from @@ -73,14 +77,25 @@ apps: namespace: "staging" # maps to the namespace as defined in namespaces above enabled: true # change to false if you want to delete this app release empty: false: chart: "argo/argo" # changing the chart name means delete and recreate this chart - version: "0.6.4" # chart version + version: "0.6.5" # chart version ### Optional values below valuesFile: "" # leaving it empty uses the default chart values test: false - protected: true + #protected: true priority: -3 wait: true - + # hooks: + # successCondition: "Complete" + # successTimeout: "90s" + # deleteOnSuccess: true + # preInstall: "job.yaml" + # preInstall: "https://github.com/jetstack/cert-manager/releases/download/v0.14.0/cert-manager.crds.yaml" + # postInstall: "https://raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml" + # postInstall: "job.yaml" + # preUpgrade: "job.yaml" + # postUpgrade: "job.yaml" + # preDelete: "job.yaml" + # postDelete: "job.yaml" artifactory: namespace: "production" # maps to the namespace as defined in namespaces above diff --git a/examples/job.yaml b/examples/job.yaml new file mode 100644 index 00000000..b448f2eb --- /dev/null +++ b/examples/job.yaml @@ -0,0 +1,14 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: pi +spec: + template: + spec: + containers: + - name: pi + image: perl + command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"] + restartPolicy: Never + backoffLimit: 4 + diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index 1194e060..129643cb 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -82,8 +82,12 @@ func (cs *currentState) decide(r *release, s *state, p *plan) { return } + // inherit globalHooks if local ones are not set + r.inheritHooks(s) + if flags.destroy { if ok := cs.releaseExists(r, ""); ok { + p.addDecision("Release [ "+r.Name+" ] will be DELETED (destroy flag enabled).", r.Priority, delete) r.uninstall(p) } return @@ -96,6 +100,7 @@ func (cs *currentState) decide(r *release, s *state, p *plan) { "protection is removed.", r.Priority, noop) return } + p.addDecision("Release [ "+r.Name+" ] is desired to be DELETED.", r.Priority, delete) r.uninstall(p) return } @@ -126,13 +131,14 @@ func (cs *currentState) decide(r *release, s *state, p *plan) { "you remove its protection.", r.Priority, noop) } } else if ok := cs.releaseExists(r, helmStatusPending); ok { - log.Error("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is in pending-upgrade state. " + + log.Error("Release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ] is in pending-upgrade state. " + "This means application is being upgraded outside of this Helmsman invocation's scope." + "Exiting, as this may cause issues when continuing...") os.Exit(1) } else { // If there is no release in the cluster with this name and in this namespace, then install it! if _, ok := cs.releases[r.key()]; !ok { + p.addDecision("Release [ "+r.Name+" ] version [ "+r.Version+" ] will be installed in [ "+r.Namespace+" ] namespace", r.Priority, create) r.install(p) } else { // A release with the same name and in the same namespace exists, but it has a different context label (managed by another DSF) @@ -258,7 +264,7 @@ func (cs *currentState) cleanUntrackedReleases(s *state, p *plan) { if !tracked { toDelete++ r := cs.releases[name+"-"+ns] - p.addDecision("Untracked release [ "+r.Name+" ] found and it will be deleted", -800, delete) + p.addDecision("Untracked release [ "+r.Name+" ] found and it will be deleted", -1000, delete) r.uninstall(p) } } @@ -311,12 +317,12 @@ func (cs *currentState) inspectUpgradeScenario(r *release, p *plan) { } } } else { - r.reInstall(p) + r.reInstall(p, rs.Namespace) p.addDecision("Release [ "+r.Name+" ] is desired to be enabled in a new namespace [ "+r.Namespace+ " ]. Uninstall of the current release from namespace [ "+rs.Namespace+" ] will be performed "+ "and then installation in namespace [ "+r.Namespace+" ] will take place", r.Priority, change) p.addDecision("WARNING: moving release [ "+r.Name+" ] from [ "+rs.Namespace+" ] to [ "+r.Namespace+ - " ] might not correctly connect existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/move_charts_across_namespaces.md"+ + " ] might not correctly connect existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/apps/moving_across_namespaces.md#note-on-persistent-volumes"+ " for details if this release uses PV and PVC.", r.Priority, change) } } diff --git a/internal/app/helm_release.go b/internal/app/helm_release.go index 2f39b4f2..b9968e43 100644 --- a/internal/app/helm_release.go +++ b/internal/app/helm_release.go @@ -11,9 +11,9 @@ import ( const ( helmStatusDeployed = "deployed" - helmStatusDeleted = "deleted" - helmStatusFailed = "failed" - helmStatusPending = "pending-upgrade" + helmStatusDeleted = "deleted" + helmStatusFailed = "failed" + helmStatusPending = "pending-upgrade" ) // helmRelease represents the current state of a release @@ -81,7 +81,7 @@ func (r *helmRelease) key() string { func (r *helmRelease) uninstall(p *plan) { cmd := helmCmd(concat([]string{"uninstall", r.Name, "--namespace", r.Namespace}, flags.getDryRunFlags()), "Delete untracked release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") - p.addCommand(cmd, -800, nil) + p.addCommand(cmd, -800, nil, []command{}, []command{}) } // getRevision returns the revision number for an existing helm release diff --git a/internal/app/kube_helpers.go b/internal/app/kube_helpers.go index 9c720d1c..9d4d5b29 100644 --- a/internal/app/kube_helpers.go +++ b/internal/app/kube_helpers.go @@ -170,23 +170,23 @@ func createContext(s *state) error { // CA cert if caCrt != "" { - caCrt = downloadFile(caCrt, "ca.crt") + caCrt = downloadFile(caCrt, "", "ca.crt") } // CA key if caKey != "" { - caKey = downloadFile(caKey, "ca.key") + caKey = downloadFile(caKey, "", "ca.key") } // client certificate if caClient != "" { - caClient = downloadFile(caClient, "client.crt") + caClient = downloadFile(caClient, "", "client.crt") } // bearer token tokenPath := "bearer.token" if s.Settings.BearerToken && s.Settings.BearerTokenPath != "" { - downloadFile(s.Settings.BearerTokenPath, tokenPath) + downloadFile(s.Settings.BearerTokenPath, "", tokenPath) } // connecting to the cluster diff --git a/internal/app/plan.go b/internal/app/plan.go index 14ad2ead..570f0989 100644 --- a/internal/app/plan.go +++ b/internal/app/plan.go @@ -30,9 +30,11 @@ type orderedDecision struct { // orderedCommand type representing a Command and it's priority weight and the targeted release from the desired state type orderedCommand struct { - Command command - Priority int - targetRelease *release + Command command + Priority int + targetRelease *release + beforeCommands []command + afterCommands []command } // plan type representing the plan of actions to make the desired state come true. @@ -53,13 +55,15 @@ func createPlan() *plan { } // addCommand adds a command type to the plan -func (p *plan) addCommand(cmd command, priority int, r *release) { +func (p *plan) addCommand(cmd command, priority int, r *release, beforeCommands []command, afterCommands []command) { p.Lock() defer p.Unlock() oc := orderedCommand{ - Command: cmd, - Priority: priority, - targetRelease: r, + Command: cmd, + Priority: priority, + targetRelease: r, + beforeCommands: beforeCommands, + afterCommands: afterCommands, } p.Commands = append(p.Commands, oc) @@ -77,7 +81,7 @@ func (p *plan) addDecision(decision string, priority int, decisionType decisionT p.Decisions = append(p.Decisions, od) } -// execPlan executes the commands (actions) which were added to the plan. +// exec executes the commands (actions) which were added to the plan. func (p *plan) exec() { p.sort() if len(p.Commands) > 0 { @@ -87,23 +91,15 @@ func (p *plan) exec() { } for _, cmd := range p.Commands { - log.Notice(cmd.Command.Description) - result := cmd.Command.exec() + for _, c := range cmd.beforeCommands { + execOne(c) + } + execOne(cmd.Command) if cmd.targetRelease != nil && !flags.dryRun && !flags.destroy { cmd.targetRelease.label() } - if result.code != 0 { - errorMsg := result.errors - if !flags.verbose { - errorMsg = strings.Split(result.errors, "---")[0] - } - log.Fatal(fmt.Sprintf("Command returned [ %d ] exit code and error message [ %s ]", result.code, strings.TrimSpace(errorMsg))) - } else { - log.Notice(result.output) - log.Notice("Finished: " + cmd.Command.Description) - if _, err := url.ParseRequestURI(settings.SlackWebhook); err == nil { - notifySlack(cmd.Command.Description+" ... SUCCESS!", settings.SlackWebhook, false, true) - } + for _, c := range cmd.afterCommands { + execOne(c) } } @@ -112,11 +108,37 @@ func (p *plan) exec() { } } +// execOne executes a single ordered command +func execOne(cmd command) { + log.Notice(cmd.Description) + result := cmd.exec() + + if result.code != 0 { + errorMsg := result.errors + if !flags.verbose { + errorMsg = strings.Split(result.errors, "---")[0] + } + log.Fatal(fmt.Sprintf("Command returned [ %d ] exit code and error message [ %s ]", result.code, strings.TrimSpace(errorMsg))) + } else { + log.Notice(result.output) + log.Notice("Finished: " + cmd.Description) + if _, err := url.ParseRequestURI(settings.SlackWebhook); err == nil { + notifySlack(cmd.Description+" ... SUCCESS!", settings.SlackWebhook, false, true) + } + } +} + // printPlanCmds prints the actual commands that will be executed as part of a plan. func (p *plan) printCmds() { log.Info("Printing the commands of the current plan ...") for _, cmd := range p.Commands { + for _, c := range cmd.beforeCommands { + fmt.Println(c.String()) + } fmt.Println(cmd.Command.String()) + for _, c := range cmd.afterCommands { + fmt.Println(c.String()) + } } } diff --git a/internal/app/plan_test.go b/internal/app/plan_test.go index 70295773..6fb247ab 100644 --- a/internal/app/plan_test.go +++ b/internal/app/plan_test.go @@ -63,7 +63,7 @@ func Test_plan_addCommand(t *testing.T) { Created: tt.fields.Created, } r := &release{} - p.addCommand(tt.args.c, 0, r) + p.addCommand(tt.args.c, 0, r, []command{}, []command{}) if got := len(p.Commands); got != 1 { t.Errorf("addCommand(): got %v, want 1", got) } diff --git a/internal/app/release.go b/internal/app/release.go index 05ef1213..c73bfd2f 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -34,6 +34,20 @@ type release struct { HelmFlags []string `yaml:"helmFlags"` NoHooks bool `yaml:"noHooks"` Timeout int `yaml:"timeout"` + Hooks hooks `yaml:"hooks"` +} + +// hooks type which defines life-cycle hooks +type hooks struct { + SuccessCondition string `yaml:"successCondition"` + SuccessTimeout string `yaml:"successTimeout"` + DeleteOnSuccess bool `yaml:"deleteOnSuccess"` + PreInstall string `yaml:"preInstall"` + PostInstall string `yaml:"postInstall"` + PreUpgrade string `yaml:"preUpgrade"` + PostUpgrade string `yaml:"postUpgrade"` + PreDelete string `yaml:"preDelete"` + PostDelete string `yaml:"postDelete"` } type chartVersion struct { @@ -58,7 +72,7 @@ func (r *release) isConsideredToRun(s *state) bool { return true } -// validateRelease validates if a release inside a desired state meets the specifications or not. +// validate validates if a release inside a desired state meets the specifications or not. // check the full specification @ https://github.com/Praqma/helmsman/docs/desired_state_spec.md func (r *release) validate(appLabel string, names map[string]map[string]bool, s *state) error { if r.Name == "" { @@ -83,28 +97,30 @@ func (r *release) validate(appLabel string, names map[string]map[string]bool, s return errors.New("version can't be empty") } - _, err = os.Stat(r.ValuesFile) - if r.ValuesFile != "" && (!isOfType(r.ValuesFile, []string{".yaml", ".yml", ".json"}) || err != nil) { - return fmt.Errorf("valuesFile must be a valid relative (from dsf file) file path for a yaml file, or can be left empty (provided path resolved to %q)", r.ValuesFile) - } else if r.ValuesFile != "" && len(r.ValuesFiles) > 0 { + if r.ValuesFile != "" && len(r.ValuesFiles) > 0 { return errors.New("valuesFile and valuesFiles should not be used together") + } else if r.ValuesFile != "" { + if err := isValidFile(r.ValuesFile, []string{".yaml", ".yml", ".json"}); err != nil { + return fmt.Errorf(err.Error()) + } } else if len(r.ValuesFiles) > 0 { - for i, filePath := range r.ValuesFiles { - if _, pathErr := os.Stat(filePath); !isOfType(filePath, []string{".yaml", ".yml", ".json"}) || pathErr != nil { - return fmt.Errorf("valuesFiles must be valid relative (from dsf file) file paths for a yaml file; path at index %d provided path resolved to %q", i, filePath) + for _, filePath := range r.ValuesFiles { + if err := isValidFile(filePath, []string{".yaml", ".yml", ".json"}); err != nil { + return fmt.Errorf(err.Error()) } } } - _, err = os.Stat(r.SecretsFile) - if r.SecretsFile != "" && (!isOfType(r.SecretsFile, []string{".yaml", ".yml", ".json"}) || err != nil) { - return fmt.Errorf("secretsFile must be a valid relative (from dsf file) file path for a yaml file, or can be left empty (provided path resolved to %q)", r.SecretsFile) - } else if r.SecretsFile != "" && len(r.SecretsFiles) > 0 { + if r.SecretsFile != "" && len(r.SecretsFiles) > 0 { return errors.New("secretsFile and secretsFiles should not be used together") + } else if r.SecretsFile != "" { + if err := isValidFile(r.SecretsFile, []string{".yaml", ".yml", ".json"}); err != nil { + return fmt.Errorf(err.Error()) + } } else if len(r.SecretsFiles) > 0 { for _, filePath := range r.SecretsFiles { - if i, pathErr := os.Stat(filePath); !isOfType(filePath, []string{".yaml", ".yml", ".json"}) || pathErr != nil { - return fmt.Errorf("secretsFiles must be valid relative (from dsf file) file paths for a yaml file; path at index %d provided path resolved to %q", i, filePath) + if err := isValidFile(filePath, []string{".yaml", ".yml", ".json"}); err != nil { + return fmt.Errorf(err.Error()) } } } @@ -113,6 +129,12 @@ func (r *release) validate(appLabel string, names map[string]map[string]bool, s return errors.New("priority can only be 0 or negative value, positive values are not allowed") } + if (hooks{}) != r.Hooks { + if ok, errorMsg := validateHooks(r.Hooks); !ok { + return fmt.Errorf(errorMsg) + } + } + if names[r.Name] == nil { names[r.Name] = make(map[string]bool) } @@ -131,6 +153,46 @@ func (r *release) validate(appLabel string, names map[string]map[string]bool, s return nil } +// validateHooks validates that hook files exist and of YAML type +func validateHooks(hks hooks) (bool, string) { + if hks.PreInstall != "" { + if err := isValidFile(hks.PreInstall, []string{".yaml", ".yml"}); err != nil { + return false, err.Error() + } + } + + if hks.PostInstall != "" { + if err := isValidFile(hks.PostInstall, []string{".yaml", ".yml"}); err != nil { + return false, err.Error() + } + } + + if hks.PreUpgrade != "" { + if err := isValidFile(hks.PreUpgrade, []string{".yaml", ".yml"}); err != nil { + return false, err.Error() + } + } + + if hks.PostUpgrade != "" { + if err := isValidFile(hks.PostUpgrade, []string{".yaml", ".yml"}); err != nil { + return false, err.Error() + } + } + + if hks.PreDelete != "" { + if err := isValidFile(hks.PreDelete, []string{".yaml", ".yml"}); err != nil { + return false, err.Error() + } + } + + if hks.PostDelete != "" { + if err := isValidFile(hks.PostDelete, []string{".yaml", ".yml"}); err != nil { + return false, err.Error() + } + } + return true, "" +} + // validateReleaseCharts validates if the charts defined in a release are valid. // Valid charts are the ones that can be found in the defined repos. // This function uses Helm search to verify if the chart can be found or not. @@ -172,6 +234,7 @@ func validateReleaseCharts(s *state) error { var versionExtractor = regexp.MustCompile(`[\n]version:\s?(.*)`) +// validateChart validates if chart with the same name and version as specified in the DSF exists func (r *release) validateChart(app string, s *state, c chan string) { validateCurrentChart := true @@ -251,31 +314,37 @@ func (r *release) getChartVersion() (string, string) { // testRelease creates a Helm command to test a particular release. func (r *release) test(p *plan) { cmd := helmCmd(r.getHelmArgsFor("test"), "Running tests for release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") - p.addCommand(cmd, r.Priority, r) - p.addDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is required to be tested during installation", r.Priority, noop) + p.addCommand(cmd, r.Priority, r, []command{}, []command{}) } // installRelease creates a Helm command to install a particular release in a particular namespace using a particular Tiller. func (r *release) install(p *plan) { + + before, after := r.checkHooks("install", p) cmd := helmCmd(r.getHelmArgsFor("install"), "Install release [ "+r.Name+" ] version [ "+r.Version+" ] in namespace [ "+r.Namespace+" ]") - p.addCommand(cmd, r.Priority, r) - p.addDecision("Release [ "+r.Name+" ] version [ "+r.Version+" ] will be installed in [ "+r.Namespace+" ] namespace", r.Priority, create) + p.addCommand(cmd, r.Priority, r, before, after) if r.Test { r.test(p) } } -// uninstall deletes a release from a particular Tiller in a k8s cluster -func (r *release) uninstall(p *plan) { +// uninstall uninstalls a release +func (r *release) uninstall(p *plan, optionalNamespace ...string) { + ns := r.Namespace + if len(optionalNamespace) > 0 { + ns = optionalNamespace[0] + } priority := r.Priority if settings.ReverseDelete { priority = priority * -1 } - cmd := helmCmd(concat(r.getHelmArgsFor("uninstall"), flags.getDryRunFlags()), "Deleting release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") - p.addCommand(cmd, priority, r) - p.addDecision(fmt.Sprintf("release [ %s ] is desired to be DELETED.", r.Name), r.Priority, delete) + before, after := r.checkHooks("delete", p, ns) + + cmd := helmCmd(concat(r.getHelmArgsFor("uninstall", ns), flags.getDryRunFlags()), "Delete release [ "+r.Name+" ] in namespace [ "+ns+" ]") + p.addCommand(cmd, priority, r, before, after) + } // diffRelease diffs an existing release with the specified values.yaml @@ -310,19 +379,26 @@ func (r *release) upgrade(p *plan) { if flags.forceUpgrades { force = "--force" } + + before, after := r.checkHooks("upgrade", p) + cmd := helmCmd(concat(r.getHelmArgsFor("upgrade"), []string{force}, r.getWait(), r.getHelmFlags()), "Upgrade release [ "+r.Name+" ] to version [ "+r.Version+" ] in namespace [ "+r.Namespace+" ]") - p.addCommand(cmd, r.Priority, r) + p.addCommand(cmd, r.Priority, r, before, after) + } -// reInstall purge deletes a release and reinstalls it. +// reInstall uninstalls a release and reinstalls it. // This is used when moving a release to another namespace or when changing the chart used for it. -func (r *release) reInstall(p *plan) { - delCmd := helmCmd(concat(r.getHelmArgsFor("uninstall"), flags.getDryRunFlags()), "Delete release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") - p.addCommand(delCmd, r.Priority, r) - - installCmd := helmCmd(r.getHelmArgsFor("install"), "Install release [ "+r.Name+" ] version [ "+r.Version+" ] in namespace [ "+r.Namespace+" ]") - p.addCommand(installCmd, r.Priority, r) +// When the release is being moved to another namespace, the optionalOldNamespace is used to provide +// the namespace from which the release is deleted. +func (r *release) reInstall(p *plan, optionalOldNamespace ...string) { + oldNamespace := "" + if len(optionalOldNamespace) > 0 { + oldNamespace = optionalOldNamespace[0] + } + r.uninstall(p, oldNamespace) + r.install(p) } // rollbackRelease evaluates if a rollback action needs to be taken for a given release. @@ -337,7 +413,7 @@ func (r *release) rollback(cs *currentState, p *plan) { if r.Namespace == rs.Namespace { cmd := helmCmd(concat([]string{"rollback", r.Name, rs.getRevision()}, r.getWait(), r.getTimeout(), r.getNoHooks(), flags.getDryRunFlags()), "Rolling back release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") - p.addCommand(cmd, r.Priority, r) + p.addCommand(cmd, r.Priority, r, []command{}, []command{}) r.upgrade(p) // this is to reflect any changes in values file(s) p.addDecision("Release [ "+r.Name+" ] was deleted and is desired to be rolled back to "+ "namespace [ "+r.Namespace+" ]", r.Priority, create) @@ -484,14 +560,19 @@ func (r *release) getHelmFlags() []string { return concat(r.getNoHooks(), r.getTimeout(), flags.getDryRunFlags(), flgs) } -func (r *release) getHelmArgsFor(action string) []string { +// getHelmArgsFor returns helm arguments for a specific helm operation +func (r *release) getHelmArgsFor(action string, optionalNamespaceOverride ...string) []string { + ns := r.Namespace + if len(optionalNamespaceOverride) > 0 { + ns = optionalNamespaceOverride[0] + } switch action { case "install": - return concat([]string{action, r.Name, r.Chart, "--version", r.Version, "--namespace", r.Namespace}, r.getValuesFiles(), r.getSetValues(), r.getSetStringValues(), r.getWait(), r.getHelmFlags()) + return concat([]string{action, r.Name, r.Chart, "--version", r.Version, "--namespace", ns}, r.getValuesFiles(), r.getSetValues(), r.getSetStringValues(), r.getWait(), r.getHelmFlags()) case "upgrade": - return concat([]string{action, "--namespace", r.Namespace, r.Name, r.Chart}, r.getValuesFiles(), []string{"--version", r.Version}, r.getSetValues(), r.getSetStringValues()) + return concat([]string{action, "--namespace", ns, r.Name, r.Chart}, r.getValuesFiles(), []string{"--version", r.Version}, r.getSetValues(), r.getSetStringValues()) default: - return []string{action, "--namespace", r.Namespace, r.Name} + return []string{action, "--namespace", ns, r.Name} } } @@ -509,6 +590,119 @@ func (r *release) overrideNamespace(newNs string) { r.Namespace = newNs } +// inheritHooks passes global hooks config from the state to the release hooks if they are unset +// release hooks override the global ones +func (r *release) inheritHooks(s *state) { + if (hooks{}) != s.Settings.GlobalHooks { + if (hooks{}) == r.Hooks { + r.Hooks = s.Settings.GlobalHooks + } else { + if r.Hooks.PreInstall == "" { + r.Hooks.PreInstall = s.Settings.GlobalHooks.PreInstall + } + if r.Hooks.PostInstall == "" { + r.Hooks.PostInstall = s.Settings.GlobalHooks.PostInstall + } + if r.Hooks.PreUpgrade == "" { + r.Hooks.PreUpgrade = s.Settings.GlobalHooks.PreUpgrade + } + if r.Hooks.PostUpgrade == "" { + r.Hooks.PostUpgrade = s.Settings.GlobalHooks.PostUpgrade + } + if r.Hooks.PreDelete == "" { + r.Hooks.PreDelete = s.Settings.GlobalHooks.PreDelete + } + if r.Hooks.SuccessCondition == "" { + r.Hooks.SuccessCondition = s.Settings.GlobalHooks.SuccessCondition + } + if r.Hooks.SuccessTimeout == "" { + r.Hooks.SuccessTimeout = s.Settings.GlobalHooks.SuccessTimeout + } + if !r.Hooks.DeleteOnSuccess { + r.Hooks.DeleteOnSuccess = s.Settings.GlobalHooks.DeleteOnSuccess + } + } + } +} + +// checkHooks checks if a hook of certain type exists and creates its command +// if success condition for the hook is defined, a "kubectl wait" command is created +// returns two slices of before and after commands +func (r *release) checkHooks(hookType string, p *plan, optionalNamespace ...string) ([]command, []command) { + ns := r.Namespace + if len(optionalNamespace) > 0 { + ns = optionalNamespace[0] + } + var beforeCommands []command + var afterCommands []command + switch hookType { + case "install": + { + if r.Hooks.PreInstall != "" { + beforeCommands = append(beforeCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks.PreInstall}, "Apply pre-install manifest "+r.Hooks.PreInstall)) + if wait, cmds := r.shouldWaitForHook(r.Hooks.PreInstall, "pre-install", ns); wait { + beforeCommands = append(beforeCommands, cmds...) + } + } + if r.Hooks.PostInstall != "" { + afterCommands = append(afterCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks.PostInstall}, "Apply post-install manifest "+r.Hooks.PostInstall)) + if wait, cmds := r.shouldWaitForHook(r.Hooks.PostInstall, "post-install", ns); wait { + afterCommands = append(afterCommands, cmds...) + } + } + } + case "upgrade": + { + if r.Hooks.PreUpgrade != "" { + beforeCommands = append(beforeCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks.PreUpgrade}, "Apply pre-upgrade manifest "+r.Hooks.PreUpgrade)) + if wait, cmds := r.shouldWaitForHook(r.Hooks.PreUpgrade, "pre-upgrade", ns); wait { + beforeCommands = append(beforeCommands, cmds...) + } + } + if r.Hooks.PostUpgrade != "" { + afterCommands = append(afterCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks.PostUpgrade}, "Apply post-upgrade manifest "+r.Hooks.PostUpgrade)) + if wait, cmds := r.shouldWaitForHook(r.Hooks.PostUpgrade, "post-upgrade", ns); wait { + afterCommands = append(afterCommands, cmds...) + } + } + } + case "delete": + { + if r.Hooks.PreDelete != "" { + beforeCommands = append(beforeCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks.PreDelete}, "Apply pre-delete manifest "+r.Hooks.PreDelete)) + if wait, cmds := r.shouldWaitForHook(r.Hooks.PreDelete, "pre-delete", ns); wait { + beforeCommands = append(beforeCommands, cmds...) + } + } + if r.Hooks.PostDelete != "" { + afterCommands = append(afterCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks.PostDelete}, "Apply post-delete manifest "+r.Hooks.PostDelete)) + if wait, cmds := r.shouldWaitForHook(r.Hooks.PostDelete, "post-delete", ns); wait { + afterCommands = append(afterCommands, cmds...) + } + } + } + } + return beforeCommands, afterCommands +} + +// shouldWaitForHook checks if there is a success condition to wait for after applying a hook +// returns a boolean and the wait command if applicable +func (r *release) shouldWaitForHook(hookFile string, hookType string, namespace string) (bool, []command) { + var cmds []command + if r.Hooks.SuccessCondition != "" { + timeoutFlag := "" + if r.Hooks.SuccessTimeout != "" { + timeoutFlag = "--timeout=" + r.Hooks.SuccessTimeout + } + cmds = append(cmds, kubectl([]string{"wait", "-n", namespace, "-f", hookFile, "--for=condition=" + r.Hooks.SuccessCondition, timeoutFlag}, "Wait for "+hookType+" : "+hookFile)) + if r.Hooks.DeleteOnSuccess { + cmds = append(cmds, kubectl([]string{"delete", "-n", namespace, "-f", hookFile}, "Delete "+hookType+" : "+hookFile)) + } + return true, cmds + } + return false, cmds +} + // print prints the details of the release func (r release) print() { fmt.Println("") @@ -524,6 +718,15 @@ func (r release) print() { fmt.Println("\tprotected : ", r.Protected) fmt.Println("\twait : ", r.Wait) fmt.Println("\tpriority : ", r.Priority) + fmt.Println("\tSuccessCondition : ", r.Hooks.SuccessCondition) + fmt.Println("\tSuccessTimeout : ", r.Hooks.SuccessTimeout) + fmt.Println("\tDeleteOnSuccess : ", r.Hooks.DeleteOnSuccess) + fmt.Println("\tpreInstall : ", r.Hooks.PreInstall) + fmt.Println("\tpostInstall : ", r.Hooks.PostInstall) + fmt.Println("\tpreUpgrade : ", r.Hooks.PreUpgrade) + fmt.Println("\tpostUpgrade : ", r.Hooks.PostUpgrade) + fmt.Println("\tpreDelete : ", r.Hooks.PreDelete) + fmt.Println("\tpostDelete : ", r.Hooks.PostDelete) fmt.Println("\tno-hooks : ", r.NoHooks) fmt.Println("\ttimeout : ", r.Timeout) fmt.Println("\tvalues to override from env:") diff --git a/internal/app/release_test.go b/internal/app/release_test.go index 99ce5b4f..3b0303da 100644 --- a/internal/app/release_test.go +++ b/internal/app/release_test.go @@ -8,6 +8,7 @@ import ( func setupTestCase(t *testing.T) func(t *testing.T) { t.Log("setup test case") + os.MkdirAll(tempFilesDir, 0755) os.MkdirAll(os.TempDir()+"/helmsman-tests/myapp", os.ModePerm) os.MkdirAll(os.TempDir()+"/helmsman-tests/dir-with space/myapp", os.ModePerm) cmd := helmCmd([]string{"create", os.TempDir() + "/helmsman-tests/dir-with space/myapp"}, "creating an empty local chart directory") @@ -71,7 +72,7 @@ func Test_validateRelease(t *testing.T) { }, s: st, }, - want: "valuesFile must be a valid relative (from dsf file) file path for a yaml file, or can be left empty (provided path resolved to \"xyz.yaml\")", + want: "xyz.yaml must be valid relative (from dsf file) file path.", }, { name: "test case 3", args: args{ @@ -87,7 +88,7 @@ func Test_validateRelease(t *testing.T) { }, s: st, }, - want: "valuesFile must be a valid relative (from dsf file) file path for a yaml file, or can be left empty (provided path resolved to \"../../tests/values.xml\")", + want: "../../tests/values.xml must be of one the following file formats: .yaml, .yml, .json", }, { name: "test case 4", args: args{ @@ -232,7 +233,7 @@ func Test_validateRelease(t *testing.T) { }, s: st, }, - want: "valuesFiles must be valid relative (from dsf file) file paths for a yaml file; path at index 0 provided path resolved to \"xyz.yaml\"", + want: "xyz.yaml must be valid relative (from dsf file) file path.", }, { name: "test case 13", args: args{ @@ -266,6 +267,96 @@ func Test_validateRelease(t *testing.T) { s: st, }, want: "env var [ $SOME_VAR ] is not set, but is wanted to be passed for [ some_var ] in [[ release14 ]]", + }, { + name: "test case 15 - non-existing hook file", + args: args{ + r: &release{ + Name: "release15", + Description: "", + Namespace: "namespace", + Enabled: true, + Chart: "repo/chartX", + Version: "1.0", + ValuesFile: "../../tests/values.yaml", + Hooks: hooks{ + PreInstall: "xyz.fake", + }, + }, + s: st, + }, + want: "xyz.fake must be valid relative (from dsf file) file path.", + }, { + name: "test case 16 - invalid hook file type", + args: args{ + r: &release{ + Name: "release16", + Description: "", + Namespace: "namespace", + Enabled: true, + Chart: "repo/chartX", + Version: "1.0", + ValuesFile: "../../tests/values.yaml", + Hooks: hooks{ + PreInstall: "../../tests/values.xml", + }, + }, + s: st, + }, + want: "../../tests/values.xml must be of one the following file formats: .yaml, .yml", + }, { + name: "test case 17 - valid hook file type", + args: args{ + r: &release{ + Name: "release17", + Description: "", + Namespace: "namespace", + Enabled: true, + Chart: "repo/chartX", + Version: "1.0", + ValuesFile: "../../tests/values.yaml", + Hooks: hooks{ + PreDelete: "../../tests/values.yaml", + }, + }, + s: st, + }, + want: "", + }, { + name: "test case 18 - valid hook file URL", + args: args{ + r: &release{ + Name: "release18", + Description: "", + Namespace: "namespace", + Enabled: true, + Chart: "repo/chartX", + Version: "1.0", + ValuesFile: "../../tests/values.yaml", + Hooks: hooks{ + PostUpgrade: "https://raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml", + }, + }, + s: st, + }, + want: "", + }, { + name: "test case 19 - invalid hook file URL", + args: args{ + r: &release{ + Name: "release19", + Description: "", + Namespace: "namespace", + Enabled: true, + Chart: "repo/chartX", + Version: "1.0", + ValuesFile: "../../tests/values.yaml", + Hooks: hooks{ + PreDelete: "https//raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml", + }, + }, + s: st, + }, + want: "https//raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml must be valid URL path to a raw file.", }, } names := make(map[string]map[string]bool) @@ -282,6 +373,68 @@ func Test_validateRelease(t *testing.T) { } } +func Test_inheritHooks(t *testing.T) { + st := state{ + Metadata: make(map[string]string), + Certificates: make(map[string]string), + Settings: config{ + GlobalHooks: hooks{ + PreInstall: "https//raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml", + PostInstall: "https//raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml", + SuccessCondition: "Complete", + SuccessTimeout: "60s", + }, + }, + Namespaces: map[string]namespace{"namespace": namespace{false, limits{}, make(map[string]string), make(map[string]string)}}, + HelmRepos: make(map[string]string), + Apps: make(map[string]*release), + } + + type args struct { + s state + r *release + } + tests := []struct { + name string + args args + want string + }{ + { + name: "test case 1", + args: args{ + r: &release{ + Name: "release1 - Global hooks correctly inherited", + Description: "", + Namespace: "namespace", + Enabled: true, + Chart: "repo/chartX", + Version: "1.0", + ValuesFile: "../../tests/values.yaml", + Hooks: hooks{ + PostInstall: "../../tests/values.yaml", + PreDelete: "../../tests/values.yaml", + SuccessTimeout: "360s", + }, + }, + s: st, + }, + want: "https//raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml -- " + + "../../tests/values.yaml -- " + + "../../tests/values.yaml -- " + + "Complete -- 360s", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.args.r.inheritHooks(&tt.args.s) + got := tt.args.r.Hooks.PreInstall + " -- " + tt.args.r.Hooks.PostInstall + " -- " + tt.args.r.Hooks.PreDelete + + " -- " + tt.args.r.Hooks.SuccessCondition + " -- " + tt.args.r.Hooks.SuccessTimeout + if got != tt.want { + t.Errorf("inheritHooks() got = %v, want %v", got, tt.want) + } + }) + } +} func Test_validateReleaseCharts(t *testing.T) { type args struct { apps map[string]*release diff --git a/internal/app/state.go b/internal/app/state.go index 30baa04a..cdc5025d 100644 --- a/internal/app/state.go +++ b/internal/app/state.go @@ -23,6 +23,7 @@ type config struct { EyamlEnabled bool `yaml:"eyamlEnabled"` EyamlPrivateKeyPath string `yaml:"eyamlPrivateKeyPath"` EyamlPublicKeyPath string `yaml:"eyamlPublicKeyPath"` + GlobalHooks hooks `yaml:"globalHooks"` } // state type represents the desired state of applications on a k8s cluster. @@ -101,6 +102,13 @@ func (s *state) validate() error { return errors.New("settings validation failed -- bearer token is enabled but no cluster URI provided") } + // lifecycle hooks validation + if (hooks{}) != s.Settings.GlobalHooks { + if ok, errorMsg := validateHooks(s.Settings.GlobalHooks); !ok { + return fmt.Errorf(errorMsg) + } + } + // slack webhook validation (if provided) if s.Settings.SlackWebhook != "" { if _, err := url.ParseRequestURI(s.Settings.SlackWebhook); err != nil { diff --git a/internal/app/utils.go b/internal/app/utils.go index c380b83d..43a3f4a5 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -53,12 +53,11 @@ func fromTOML(file string, s *state) (bool, string) { if !flags.noSSMSubst { tomlFile = substituteSSM(tomlFile) } - if _, err := toml.Decode(tomlFile, s); err != nil { return false, err.Error() } resolvePaths(file, s) - substituteVarsInValuesFiles(s) + substituteVarsInStaticFiles(s) return true, "Parsed TOML [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps" } @@ -109,7 +108,7 @@ func fromYAML(file string, s *state) (bool, string) { return false, err.Error() } resolvePaths(file, s) - substituteVarsInValuesFiles(s) + substituteVarsInStaticFiles(s) return true, "Parsed YAML [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps" } @@ -139,8 +138,8 @@ func toYAML(file string, s *state) { newFile.Close() } -// substituteVarsInValuesFiles loops through the values/secrets files and substitutes variables into them. -func substituteVarsInValuesFiles(s *state) { +// substituteVarsInStaticFiles loops through the values/secrets files and substitutes variables into them. +func substituteVarsInStaticFiles(s *state) { for _, v := range s.Apps { if v.ValuesFile != "" { v.ValuesFile = substituteVarsInYaml(v.ValuesFile) @@ -148,6 +147,46 @@ func substituteVarsInValuesFiles(s *state) { if v.SecretsFile != "" { v.SecretsFile = substituteVarsInYaml(v.SecretsFile) } + if (hooks{}) != v.Hooks { + if v.Hooks.PreInstall != "" { + v.Hooks.PreInstall = substituteVarsInYaml(v.Hooks.PreInstall) + } + if v.Hooks.PostInstall != "" { + v.Hooks.PostInstall = substituteVarsInYaml(v.Hooks.PostInstall) + } + if v.Hooks.PreUpgrade != "" { + v.Hooks.PreUpgrade = substituteVarsInYaml(v.Hooks.PreUpgrade) + } + if v.Hooks.PostUpgrade != "" { + v.Hooks.PostUpgrade = substituteVarsInYaml(v.Hooks.PostUpgrade) + } + if v.Hooks.PreDelete != "" { + v.Hooks.PreDelete = substituteVarsInYaml(v.Hooks.PreDelete) + } + if v.Hooks.PostDelete != "" { + v.Hooks.PostDelete = substituteVarsInYaml(v.Hooks.PostDelete) + } + } + if (hooks{}) != s.Settings.GlobalHooks { + if s.Settings.GlobalHooks.PreInstall != "" { + s.Settings.GlobalHooks.PreInstall = substituteVarsInYaml(s.Settings.GlobalHooks.PreInstall) + } + if s.Settings.GlobalHooks.PostInstall != "" { + s.Settings.GlobalHooks.PostInstall = substituteVarsInYaml(s.Settings.GlobalHooks.PostInstall) + } + if s.Settings.GlobalHooks.PreUpgrade != "" { + s.Settings.GlobalHooks.PreUpgrade = substituteVarsInYaml(s.Settings.GlobalHooks.PreUpgrade) + } + if s.Settings.GlobalHooks.PostUpgrade != "" { + s.Settings.GlobalHooks.PostUpgrade = substituteVarsInYaml(s.Settings.GlobalHooks.PostUpgrade) + } + if s.Settings.GlobalHooks.PreDelete != "" { + s.Settings.GlobalHooks.PreDelete = substituteVarsInYaml(s.Settings.GlobalHooks.PreDelete) + } + if s.Settings.GlobalHooks.PostDelete != "" { + s.Settings.GlobalHooks.PostDelete = substituteVarsInYaml(s.Settings.GlobalHooks.PostDelete) + } + } for i := range v.ValuesFiles { v.ValuesFiles[i] = substituteVarsInYaml(v.ValuesFiles[i]) } @@ -173,10 +212,7 @@ func substituteVarsInYaml(file string) string { yamlFile = substituteSSM(yamlFile) } - dir, err := ioutil.TempDir(tempFilesDir, "tmp") - if err != nil { - log.Fatal(err.Error()) - } + dir := createTempDir(tempFilesDir, "tmp") // output file contents with env variables substituted into temp files outFile := path.Join(dir, filepath.Base(file)) @@ -187,6 +223,7 @@ func substituteVarsInYaml(file string) string { return outFile } +// func stringInSlice checks if a string is in a slice func stringInSlice(a string, list []string) bool { for _, b := range list { if b == a { @@ -196,24 +233,43 @@ func stringInSlice(a string, list []string) bool { return false } -// resolvePaths resolves relative paths of certs/keys/chart and replace them with a absolute paths +// resolvePaths resolves relative paths of certs/keys/chart/value file/secret files/etc and replace them with a absolute paths func resolvePaths(relativeToFile string, s *state) { dir := filepath.Dir(relativeToFile) - for ns, v := range s.Namespaces { - s.Namespaces[ns] = v - } + downloadDest, _ := filepath.Abs(createTempDir(tempFilesDir, "tmp")) for k, v := range s.Apps { if v.ValuesFile != "" { - v.ValuesFile, _ = filepath.Abs(filepath.Join(dir, v.ValuesFile)) + v.ValuesFile, _ = resolveOnePath(v.ValuesFile, dir, downloadDest) } if v.SecretsFile != "" { - v.SecretsFile, _ = filepath.Abs(filepath.Join(dir, v.SecretsFile)) + v.SecretsFile, _ = resolveOnePath(v.SecretsFile, dir, downloadDest) } - for i, f := range v.ValuesFiles { - v.ValuesFiles[i], _ = filepath.Abs(filepath.Join(dir, f)) + if (hooks{}) != v.Hooks { + if v.Hooks.PreInstall != "" { + v.Hooks.PreInstall, _ = resolveOnePath(v.Hooks.PreInstall, dir, downloadDest) + } + + if v.Hooks.PostInstall != "" { + v.Hooks.PostInstall, _ = resolveOnePath(v.Hooks.PostInstall, dir, downloadDest) + } + if v.Hooks.PreUpgrade != "" { + v.Hooks.PreUpgrade, _ = resolveOnePath(v.Hooks.PreUpgrade, dir, downloadDest) + } + if v.Hooks.PostUpgrade != "" { + v.Hooks.PostUpgrade, _ = resolveOnePath(v.Hooks.PostUpgrade, dir, downloadDest) + } + if v.Hooks.PreDelete != "" { + v.Hooks.PreDelete, _ = resolveOnePath(v.Hooks.PreDelete, dir, downloadDest) + } + if v.Hooks.PostDelete != "" { + v.Hooks.PostDelete, _ = resolveOnePath(v.Hooks.PostDelete, dir, downloadDest) + } } - for i, f := range v.SecretsFiles { - v.SecretsFiles[i], _ = filepath.Abs(filepath.Join(dir, f)) + for i := range v.ValuesFiles { + v.ValuesFiles[i], _ = resolveOnePath(v.ValuesFiles[i], dir, downloadDest) + } + for i := range v.SecretsFiles { + v.SecretsFiles[i], _ = resolveOnePath(v.SecretsFiles[i], dir, downloadDest) } if v.Chart != "" { @@ -235,17 +291,52 @@ func resolvePaths(relativeToFile string, s *state) { } // resolving paths for Bearer Token path in settings if s.Settings.BearerTokenPath != "" { - if _, err := url.ParseRequestURI(s.Settings.BearerTokenPath); err != nil { - s.Settings.BearerTokenPath, _ = filepath.Abs(filepath.Join(dir, s.Settings.BearerTokenPath)) + s.Settings.BearerTokenPath, _ = resolveOnePath(s.Settings.BearerTokenPath, dir, downloadDest) + } + // resolve paths for global hooks + if (hooks{}) != s.Settings.GlobalHooks { + if s.Settings.GlobalHooks.PreInstall != "" { + s.Settings.GlobalHooks.PreInstall, _ = resolveOnePath(s.Settings.GlobalHooks.PreInstall, dir, downloadDest) + } + if s.Settings.GlobalHooks.PostInstall != "" { + s.Settings.GlobalHooks.PostInstall, _ = resolveOnePath(s.Settings.GlobalHooks.PostInstall, dir, downloadDest) + } + if s.Settings.GlobalHooks.PreUpgrade != "" { + s.Settings.GlobalHooks.PreUpgrade, _ = resolveOnePath(s.Settings.GlobalHooks.PreUpgrade, dir, downloadDest) + } + if s.Settings.GlobalHooks.PostUpgrade != "" { + s.Settings.GlobalHooks.PostUpgrade, _ = resolveOnePath(s.Settings.GlobalHooks.PostUpgrade, dir, downloadDest) + } + if s.Settings.GlobalHooks.PreDelete != "" { + s.Settings.GlobalHooks.PreDelete, _ = resolveOnePath(s.Settings.GlobalHooks.PreDelete, dir, downloadDest) + } + if s.Settings.GlobalHooks.PostDelete != "" { + s.Settings.GlobalHooks.PostDelete, _ = resolveOnePath(s.Settings.GlobalHooks.PostDelete, dir, downloadDest) } } // resolving paths for k8s certificate files - for k, v := range s.Certificates { - if _, err := url.ParseRequestURI(v); err != nil { - v, _ = filepath.Abs(filepath.Join(dir, v)) - } - s.Certificates[k] = v + for k := range s.Certificates { + s.Certificates[k], _ = resolveOnePath(s.Certificates[k], "", downloadDest) } + +} + +// resolveOnePath takes the input file (URL, cloud bucket, or local file relative path), +// the directory containing the DSF and the temp directory where files will be fetched to +// and downloads/fetches the file locally into helmsman temp directory and returns +// its absolute path +func resolveOnePath(file string, dir string, downloadDest string) (string, error) { + destFileName := filepath.Join(downloadDest, path.Base(file)) + return filepath.Abs(downloadFile(file, dir, destFileName)) +} + +// createTempDir creates a temp directory in a specific location with a pattern +func createTempDir(parent string, pattern string) string { + dir, err := ioutil.TempDir(parent, pattern) + if err != nil { + log.Fatal(err.Error()) + } + return dir } // isOfType checks if the file extension of a filename/path is the same as "filetype". @@ -312,35 +403,62 @@ func sliceContains(slice []string, s string) bool { return false } -// downloadFile downloads a file from GCS or AWS buckets and name it with a given outfile +// downloadFile downloads a file from a URL, GCS, Azure or AWS buckets or local file system +// and saves it with a given outfile name and in a given dir // if downloaded, returns the outfile name. If the file path is local file system path, it is copied to current directory. -func downloadFile(path string, outfile string) string { - if strings.HasPrefix(path, "s3") { +func downloadFile(file string, dir string, outfile string) string { + if strings.HasPrefix(file, "http") { + if err := downloadFileFromURL(file, outfile); err != nil { + log.Fatal(err.Error()) + } + } else if strings.HasPrefix(file, "s3") { - tmp := getBucketElements(path) + tmp := getBucketElements(file) aws.ReadFile(tmp["bucketName"], tmp["filePath"], outfile, flags.noColors) - } else if strings.HasPrefix(path, "gs") { + } else if strings.HasPrefix(file, "gs") { - tmp := getBucketElements(path) + tmp := getBucketElements(file) msg, err := gcs.ReadFile(tmp["bucketName"], tmp["filePath"], outfile, flags.noColors) if err != nil { log.Fatal(msg) } - } else if strings.HasPrefix(path, "az") { + } else if strings.HasPrefix(file, "az") { - tmp := getBucketElements(path) + tmp := getBucketElements(file) azure.ReadFile(tmp["bucketName"], tmp["filePath"], outfile, flags.noColors) } else { log.Info("" + outfile + " will be used from local file system.") - copyFile(path, outfile) + toCopy, _ := filepath.Abs(filepath.Join(dir, file)) + copyFile(toCopy, outfile) } return outfile } +// downloadFileFromURL will download a url to a local file. It writes to the file as it downloads +func downloadFileFromURL(url string, filepath string) error { + // Get the data + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + + // Create the file + out, err := os.Create(filepath) + if err != nil { + return err + } + defer out.Close() + + // Write the body to file + _, err = io.Copy(out, resp.Body) + return err +} + // copyFile copies a file from source to destination func copyFile(source string, destination string) { from, err := os.Open(source) @@ -549,3 +667,17 @@ func decryptSecret(name string) error { } return nil } + +// isValidFile checks if the file exists in the given path or accessible via http and is of allowed file extension (e.g. yaml, json ...) +func isValidFile(filePath string, allowedFileTypes []string) error { + if strings.HasPrefix(filePath, "http") { + if _, err := url.ParseRequestURI(filePath); err != nil { + return errors.New(filePath + " must be valid URL path to a raw file.") + } + } else if _, pathErr := os.Stat(filePath); pathErr != nil { + return errors.New(filePath + " must be valid relative (from dsf file) file path.") + } else if !isOfType(filePath, allowedFileTypes) { + return errors.New(filePath + " must be of one the following file formats: " + strings.Join(allowedFileTypes, ", ")) + } + return nil +} From 9a0917373ed6c302d9aa9874320bd79bc36f62d9 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Fri, 20 Mar 2020 12:09:34 +0100 Subject: [PATCH 0613/1127] mention the test command and required tools --- CONTRIBUTION.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md index 8d132853..75bfee63 100644 --- a/CONTRIBUTION.md +++ b/CONTRIBUTION.md @@ -8,7 +8,9 @@ To build helmsman from source, you need go:1.13+. Follow the steps below: ``` git clone https://github.com/Praqma/helmsman.git +make tools # installs few tools for testing, building, releasing make build +make test ``` ## The branches and tags From 6bd43881bf4edfac79730114e0d87bc83b0ee724 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Fri, 20 Mar 2020 14:31:45 +0100 Subject: [PATCH 0614/1127] update docs for lifecycle hooks --- docs/desired_state_specification.md | 38 ++++++-- docs/how_to/apps/lifecycle_hooks.md | 129 ++++++++++++++++++++++++++++ 2 files changed, 159 insertions(+), 8 deletions(-) create mode 100644 docs/how_to/apps/lifecycle_hooks.md diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index ab115a53..891cfa2e 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -49,9 +49,9 @@ Optional : Yes, only needed if you want Helmsman to connect kubectl to your clus Synopsis: defines where to find the certificates needed for connecting kubectl to a k8s cluster. If connection settings (username/password/clusterAPI) are provided in the Settings section below, then you need **AT LEAST** to provide caCrt and caKey. You can optionally provide a client certificate (caClient) depending on your cluster connection setup. Options: -- **caCrt** : a valid S3/GCS/Azure bucket or local relative file path to a certificate file. -- **caKey** : a valid S3/GCS/Azure bucket or local relative file path to a client key file. -- **caClient**: a valid S3/GCS/Azure bucket or local relative file path to a client certificate file. +- **caCrt** : a valid URL/S3/GCS/Azure bucket or local relative file path to a certificate file. +- **caKey** : a valid URL/S3/GCS/Azure bucket or local relative file path to a client key file. +- **caClient**: a valid URL/S3/GCS/Azure bucket or local relative file path to a client certificate file. > bucket format is: [s3 or gs or az]://bucket-name/dir1/dir2/.../file.extension @@ -103,13 +103,14 @@ The following options can be skipped if your kubectl context is already created - **password** : an environment variable name (starting with `$`) where your password is stored. Get the password from your k8s admin or consult k8s docs on how to get/set it. - **clusterURI** : the URI for your cluster API or the name of an environment variable (starting with `$`) containing the URI. - **bearerToken**: whether you want helmsman to connect to the cluster using a bearer token. Default is `false` -- **bearerTokenPath**: optional. If bearer token is used, you can specify a custom location for the token file. +- **bearerTokenPath**: optional. If bearer token is used, you can specify a custom location (URL, cloud bucket, local file path) for the token file. - **storageBackend** : by default Helm v3 stores release information in secrets, using secrets for storage is recommended for security. - **slackWebhook** : a [Slack](http://slack.com) Webhook URL to receive Helmsman notifications. This can be passed directly or in an environment variable. - **reverseDelete** : if set to `true` it will reverse the priority order whilst deleting. - **eyamlEnabled** : if set to `true' it will use [hiera-eyaml](https://github.com/voxpupuli/hiera-eyaml) to decrypt secret files instead of using default helm-secrets based on sops - **eyamlPrivateKeyPath** : if set with path to the eyaml private key file, it will use it instead of looking for default one in ./keys directory relative to where Helmsman were run. It needs to be defined in conjunction with eyamlPublicKeyPath. - **eyamlPublicKeyPath** : if set with path to the eyaml public key file, it will use it instead of looking for default one in ./keys directory relative to where Helmsman were run. It needs to be defined in conjunction with eyamlPrivateKeyPath. +- **globalHooks** : defines global lifecycle hooks to apply yaml manifest before and/or after different helmsman operations. Check [here](how_to/apps/lifecycle_hooks.md) for more details. Example: @@ -127,6 +128,10 @@ kubeContext = "minikube" # eyamlEnabled = true # eyamlPrivateKeyPath = "../keys/custom-key.pem" # eyamlPublicKeyPath = "../keys/custom-key.pub" +# [settings.globalHooks] +# successCondition= "Complete" +# deleteOnSuccess= true +# postInstall= "job.yaml" ``` ```yaml @@ -142,6 +147,10 @@ settings: # eyamlEnabled: true # eyamlPrivateKeyPath: ../keys/custom-key.pem # eyamlPublicKeyPath: ../keys/custom-key.pub + # globalHooks: + # successCondition: "Complete" + # deleteOnSuccess: true + # preInstall: "job.yaml" ``` ## Namespaces @@ -341,11 +350,11 @@ Options: **Optional** - **group** : group name this apps belongs to. It has no effect until Helmsman's flag `-group` is passed. Check this [doc](how_to/misc/limit-deployment-to-specific-group-of-apps.md) for more details. - **description** : a release metadata for human readers. -- **valuesFile** : a valid path to custom Helm values.yaml file. File extension must be `yaml`. Cannot be used with valuesFiles together. Leaving it empty uses the default chart values. -- **valuesFiles** : array of valid paths to custom Helm values.yaml file. File extension must be `yaml`. Cannot be used with valuesFile together. Leaving it empty uses the default chart values. +- **valuesFile** : a valid path (URL, cloud bucket, local file path) to custom Helm values.yaml file. File extension must be `yaml`. Cannot be used with valuesFiles together. Leaving it empty uses the default chart values. +- **valuesFiles** : array of valid paths (URL, cloud bucket, local file path) to custom Helm values.yaml file. File extension must be `yaml`. Cannot be used with valuesFile together. Leaving it empty uses the default chart values. > The values file(s) path is resolved when the DSF yaml/toml file is loaded, relative to the path that the dsf was loaded from. -- **secretsFile** : a valid path to custom Helm secrets.yaml file. File extension must be `yaml`. Cannot be used with secretsFiles together. Leaving it empty uses the default chart secrets. -- **secretsFiles** : array of valid paths to custom Helm secrets.yaml file. File extension must be `yaml`. Cannot be used with secretsFile together. Leaving it empty uses the default chart secrets. +- **secretsFile** : a valid path (URL, cloud bucket, local file path) to custom Helm secrets.yaml file. File extension must be `yaml`. Cannot be used with secretsFiles together. Leaving it empty uses the default chart secrets. +- **secretsFiles** : array of valid paths (URL, cloud bucket, local file path) to custom Helm secrets.yaml file. File extension must be `yaml`. Cannot be used with secretsFile together. Leaving it empty uses the default chart secrets. > The secrets file(s) path is resolved when the DSF yaml/toml file is loaded, relative to the path that the dsf was loaded from. > To use the secrets files you must have the helm-secrets plugin - **test** : defines whether to run the chart tests whenever the release is installed. Default is false. @@ -357,6 +366,7 @@ Options: - **set** : is used to override certain values from values.yaml with values from environment variables (or ,starting from v1.3.0-rc, directly provided in the Desired State File). This is particularly useful for passing secrets to charts. If the an environment variable with the same name as the provided value exists, the environment variable value will be used, otherwise, the provided value will be used as is. The TOML stanza for this is `[apps..set]` - **setString** : is used to override String values from values.yaml or chart's defaults. This uses the `--set-string` flag in helm which is available only in helm >v2.9.0. This option is useful for image tags and the like. The TOML stanza for this is `[apps..setString]` - **helmFlags** : array of `helm` flags, is used to pass flags to helm install/upgrade commands +- **hooks** : defines global lifecycle hooks to apply yaml manifest before and/or after different helmsman operations. Check [here](how_to/apps/lifecycle_hooks.md) for more details. Unset hooks for a release are inherited from `globalHooks` in the [settings](#Settings) stanza. Example: @@ -387,6 +397,12 @@ Example: [apps.jenkins.setString] longInt = "1234567890" "image.tag" = "1.0.0" + [apps.jenkins.hooks] + successCondition= "Complete" + successTimeout= "90s" + deleteOnSuccess= true + postInstall="job.yaml" + preInstall="https://github.com/jetstack/cert-manager/releases/download/v0.14.0/cert-manager.crds.yaml" ``` ```yaml @@ -414,4 +430,10 @@ apps: longInt: "1234567890" image: tag: "1.0.0" + hooks: + successCondition: "Complete" + successTimeout: "90s" + deleteOnSuccess: true + postInstall: "job.yaml" + preInstall: "https://github.com/jetstack/cert-manager/releases/download/v0.14.0/cert-manager.crds.yaml" ``` diff --git a/docs/how_to/apps/lifecycle_hooks.md b/docs/how_to/apps/lifecycle_hooks.md new file mode 100644 index 00000000..cd9a236e --- /dev/null +++ b/docs/how_to/apps/lifecycle_hooks.md @@ -0,0 +1,129 @@ +--- +version: v3.2.0 +--- + +# Helmsman Lifecycle hooks + +With lifecycle hooks, you can declaratively define certain operations to perform before and/or after helmsman operations. +These operations can be running installing dependencies (e.g. CRDs), executing certain tests, sending custom notifications, etc. +Another useful use-case is if you are using a 3rd party chart which does not define native helm lifecycle hooks that you wish to have. + +## Prerequisites + +- Hook operations must be defined in a Kubernetes manifest. They can be any kubernetes resource(s) (jobs, cron jobs, deployments, pods, etc). +- You can only define one manifest file for each lifecycle hook. So make sure all your needed resources are in this manifest. + +## Supported lifecycle stages + +- pre-install : before installing a release. +- post-install: after installing a release. +- pre-upgrade: before upgrading a release. +- post-upgrade: after upgrading a release. +- pre-delete: before uninstalling a release. +- post-delete: after uninstalling a release. + +## Hooks stanza details + +The following items can be defined in the hooks stanza: +- pre/postInstall, pre/postUpgrade, pre/postDelete : a valid path (URL, cloud bucket, local file path) to your hook's k8s manifest. +- `successCondition` the Kubernetes status condition that indicates that your resources have finished their job successfully. You can find out what the status conditions are for different k8s resources with a kubectl command similar to: `kubectl get job -o=jsonpath='{range .items[*]}{.status.conditions[0].type}{"\n"}{end}'` + +For jobs, it is `Complete` +For pods, it is `Initialized` +For deployments, it is `Available` + +- `successTimeout` (default 30s) how much time to wait for the `successCondition` +- `deleteOnSuccess` (true/false) indicates if you wish to delete the hook's manifest after the hook succeeds. This is only used if you define `successCondition` + +## Global vs App-specific hooks + +You can define two types of hooks in your desired state file: + +- **Gloabl** hooks: are defined in the `settings` stanza and are inherited by all releases in the DSF if they haven't defined their own. + +These are defined as follows: +```toml +[settings] + ... + [settings.globalHooks] + successCondition= "Initialized" + deleteOnSuccess= true + postInstall= "job.yaml" +``` + +```yaml +settings: + ... + globalHooks: + successCondition: "Initialized" + deleteOnSuccess: true + postInstall: "job.yaml" + ... +``` + +- **App-specific** hooks: each app (release) can define its own hooks which **override any global ones**. + +These are defined as follows: + +```toml +[apps] + [apps.argo] + namespace = "production" # maps to the namespace as defined in namespaces above + enabled = true # change to false if you want to delete this app release [default = false] + chart = "argo/argo" # changing the chart name means delete and recreate this release + version = "0.6.4" # chart version + [apps.argo.hooks] + successCondition= "Complete" + successTimeout= "90s" + deleteOnSuccess= true + preInstall="job.yaml" + preInstall="https://github.com/jetstack/cert-manager/releases/download/v0.14.0/cert-manager.crds.yaml" + postInstall="https://raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml" + preUpgrade="job.yaml" + postUpgrade="job.yaml" + preDelete="job.yaml" + postDelete="job.yaml" +``` + +```yaml +apps: + argo: + namespace: "staging" # maps to the namespace as defined in namespaces above + enabled: true # change to false if you want to delete this app release empty: false: + chart: "argo/argo" # changing the chart name means delete and recreate this chart + version: "0.6.5" # chart version + hooks: + successCondition: "Complete" + successTimeout: "90s" + deleteOnSuccess: true + preInstall: "job.yaml" + preInstall: "https://github.com/jetstack/cert-manager/releases/download/v0.14.0/cert-manager.crds.yaml" + postInstall: "https://raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml" + postInstall: "job.yaml" + preUpgrade: "job.yaml" + postUpgrade: "job.yaml" + preDelete: "job.yaml" + postDelete: "job.yaml" +``` + +## Enforcing hook manifests deletion on all apps + +You can do that by setting `deleteOnSuccess` to true in the `globalHooks` stanza under `settings`. If you need to make an exception for some app, you can set it to `false` in the `hooks` stanza of this app. This overrides the global hooks. + +## Expanding variables in hook manifests + +You can expand variables/parameters in the hook manifests at run time in one of the following ways: + +- use env variables (defined as `$MY_VAR` in your manifests) and run helmsman with `--subst-env-values`. Environment variables can be read from the environment or you can [load them from an env file](https://github.com/Praqma/helmsman/blob/master/docs/how_to/apps/secrets.md#passing-secrets-from-env-files) + +- use [AWS SSM parameters](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html) (defined as `{{ssm: MY_PARAM }}` in your manifests) and run helmsman with `--subst-ssm-values`. + +- Pass encrypted values with [hiera-eyaml](https://github.com/Praqma/helmsman/blob/master/docs/how_to/settings/use-hiera-eyaml-as-secrets-encryption.md) + + +## Limitations + +- You can only have one manifest file per lifecycle. +- Your arbitrary tasks must run within a k8s resource (example inside a pod). You can't use a plain bash script as a hook manifest for example. +- If you have multiple k8s resources in your hook manifest file, `successCondition` may not work. +- pre/postDelete hooks are not respected before/after deleting untracked releases (releases which are no longer defined in your desired state file). From aee03e2ad70e41cf7e1430fbe056bc2b33c12a73 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Fri, 20 Mar 2020 14:50:33 +0100 Subject: [PATCH 0615/1127] fix namespace limits from toml dsf --- internal/app/namespace.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/app/namespace.go b/internal/app/namespace.go index 783d7924..37cb7d5b 100644 --- a/internal/app/namespace.go +++ b/internal/app/namespace.go @@ -17,7 +17,7 @@ type limits []struct { Default resources `yaml:"default,omitempty"` DefaultRequest resources `yaml:"defaultRequest,omitempty"` MaxLimitRequestRatio resources `yaml:"maxLimitRequestRatio,omitempty"` - LimitType string `yaml:"type"` + Type string `yaml:"type"` } // namespace type represents the fields of a namespace From 251a5f3a01dd1c6e3d6af83b2fe1a08ae6ed851a Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Fri, 20 Mar 2020 15:03:21 +0100 Subject: [PATCH 0616/1127] add --no-cleanup flag to optionally keep k8s crednetials on host --- internal/app/cli.go | 2 ++ internal/app/main.go | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/internal/app/cli.go b/internal/app/cli.go index e372c572..91917e57 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -61,6 +61,7 @@ type cli struct { updateDeps bool forceUpgrades bool version bool + noCleanup bool } func printUsage() { @@ -101,6 +102,7 @@ func (c *cli) parse() { flag.BoolVar(&c.substSSMValues, "subst-ssm-values", false, "turn on SSM parameter substitution in values files.") flag.BoolVar(&c.updateDeps, "update-deps", false, "run 'helm dep up' for local chart") flag.BoolVar(&c.forceUpgrades, "force-upgrades", false, "use --force when upgrading helm releases. May cause resources to be recreated.") + flag.BoolVar(&c.noCleanup, "no-cleanup", false, "keeps any credentials files that has been downloaded on the host where helmsman runs.") flag.Usage = printUsage flag.Parse() diff --git a/internal/app/main.go b/internal/app/main.go index 812af544..04d4fa30 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -29,7 +29,9 @@ func Main() { // delete temp files with substituted env vars when the program terminates defer os.RemoveAll(tempFilesDir) - defer s.cleanup() + if !flags.noCleanup { + defer s.cleanup() + } flags.readState(&s) if len(s.GroupMap) > 0 { From dfced9b0ab1d945e377c36db62a90c69110b3cc4 Mon Sep 17 00:00:00 2001 From: Miroslav Hadzhiev Date: Sat, 28 Mar 2020 21:59:02 +0200 Subject: [PATCH 0617/1127] Add Complete DRY-ed Example Signed-off-by: Miroslav Hadzhiev --- .../apps/puppetserver/common-values.yaml | 2 + .../apps/puppetserver/development-values.yaml | 4 + .../apps/puppetserver/testing-values.yaml | 4 + .../apps/tomcat/common-values.yaml | 7 ++ .../apps/tomcat/development-values.yaml | 1 + .../apps/tomcat/testing-values.yaml | 1 + examples/dried-example/config/.gitignore | 2 + examples/dried-example/config/helmsman.yaml | 105 ++++++++++++++++++ 8 files changed, 126 insertions(+) create mode 100644 examples/dried-example/apps/puppetserver/common-values.yaml create mode 100644 examples/dried-example/apps/puppetserver/development-values.yaml create mode 100644 examples/dried-example/apps/puppetserver/testing-values.yaml create mode 100644 examples/dried-example/apps/tomcat/common-values.yaml create mode 100644 examples/dried-example/apps/tomcat/development-values.yaml create mode 100644 examples/dried-example/apps/tomcat/testing-values.yaml create mode 100644 examples/dried-example/config/.gitignore create mode 100644 examples/dried-example/config/helmsman.yaml diff --git a/examples/dried-example/apps/puppetserver/common-values.yaml b/examples/dried-example/apps/puppetserver/common-values.yaml new file mode 100644 index 00000000..9741903f --- /dev/null +++ b/examples/dried-example/apps/puppetserver/common-values.yaml @@ -0,0 +1,2 @@ +puppetserver: + puppeturl: 'https://github.com/puppetlabs/control-repo.git' diff --git a/examples/dried-example/apps/puppetserver/development-values.yaml b/examples/dried-example/apps/puppetserver/development-values.yaml new file mode 100644 index 00000000..b5c17bbd --- /dev/null +++ b/examples/dried-example/apps/puppetserver/development-values.yaml @@ -0,0 +1,4 @@ +r10k: + code: + cronJob: + schedule: "*/2 * * * *" diff --git a/examples/dried-example/apps/puppetserver/testing-values.yaml b/examples/dried-example/apps/puppetserver/testing-values.yaml new file mode 100644 index 00000000..2568bc74 --- /dev/null +++ b/examples/dried-example/apps/puppetserver/testing-values.yaml @@ -0,0 +1,4 @@ +r10k: + code: + cronJob: + schedule: "*/1 * * * *" diff --git a/examples/dried-example/apps/tomcat/common-values.yaml b/examples/dried-example/apps/tomcat/common-values.yaml new file mode 100644 index 00000000..69759188 --- /dev/null +++ b/examples/dried-example/apps/tomcat/common-values.yaml @@ -0,0 +1,7 @@ +resources: + limits: + cpu: 100m + memory: 256Mi + requests: + cpu: 100m + memory: 256Mi diff --git a/examples/dried-example/apps/tomcat/development-values.yaml b/examples/dried-example/apps/tomcat/development-values.yaml new file mode 100644 index 00000000..91f27227 --- /dev/null +++ b/examples/dried-example/apps/tomcat/development-values.yaml @@ -0,0 +1 @@ +hostPort: 8009 diff --git a/examples/dried-example/apps/tomcat/testing-values.yaml b/examples/dried-example/apps/tomcat/testing-values.yaml new file mode 100644 index 00000000..46d83182 --- /dev/null +++ b/examples/dried-example/apps/tomcat/testing-values.yaml @@ -0,0 +1 @@ +hostPort: 8010 diff --git a/examples/dried-example/config/.gitignore b/examples/dried-example/config/.gitignore new file mode 100644 index 00000000..e9fed779 --- /dev/null +++ b/examples/dried-example/config/.gitignore @@ -0,0 +1,2 @@ +*crt* +*key* diff --git a/examples/dried-example/config/helmsman.yaml b/examples/dried-example/config/helmsman.yaml new file mode 100644 index 00000000..ab3cd8a2 --- /dev/null +++ b/examples/dried-example/config/helmsman.yaml @@ -0,0 +1,105 @@ +metadata: + scope: "K8s Cluster kind-1" + maintainer: "devops" + +settings: + kubeContext: "kind-kind-1" # the name of the context to be created + slackWebhook: "$MY_SLACK_WEBHOOK" + +namespaces: + testing: + labels: + env: "testing" + limits: + - type: Container + default: + cpu: "200m" + memory: "250Mi" + defaultRequest: + cpu: "100m" + memory: "150Mi" + - type: Pod + max: + memory: "300Mi" + development: + labels: + env: "development" + limits: + - type: Container + default: + cpu: "300m" + memory: "300Mi" + defaultRequest: + cpu: "200m" + memory: "200Mi" + - type: Pod + max: + memory: "400Mi" + +helmRepos: + stable: "https://kubernetes-charts.storage.googleapis.com" + +appsTemplates: + common: &common + test: true + + testing: &testing + namespace: "testing" + protected: false # defining all "testing" releases to be protected. + wait: true + + development: &development + namespace: "development" + protected: true # defining all "development" releases to be protected. + wait: false + + puppetserver: &puppetserver + enabled: true + priority: -1 + chart: "../../../../puppetserver-helm-chart-1.8.2.tgz" + version: "1.8.2" # chart version + valuesFiles: [ + "../apps/puppetserver/common-values.yaml", + ] + + tomcat: &tomcat + enabled: true + priority: -2 + chart: "stable/tomcat" + version: "0.4.0" # chart version + valuesFiles: [ + "../apps/tomcat/common-values.yaml", + ] + +apps: + testing-puppetserver: + <<: *common + <<: *testing + <<: *puppetserver + valuesFiles: [ + "../apps/puppetserver/testing-values.yaml", + ] + + testing-tomcat: + <<: *common + <<: *testing + <<: *tomcat + valuesFiles: [ + "../apps/tomcat/testing-values.yaml", + ] + + development-puppetserver: + <<: *common + <<: *development + <<: *puppetserver + valuesFiles: [ + "../apps/puppetserver/development-values.yaml", + ] + + development-tomcat: + <<: *common + <<: *development + <<: *tomcat + valuesFiles: [ + "../apps/tomcat/development-values.yaml", + ] From 354500da634402544a1b5685e875b1c37cf95081 Mon Sep 17 00:00:00 2001 From: Miroslav Hadzhiev Date: Sat, 28 Mar 2020 22:03:14 +0200 Subject: [PATCH 0618/1127] Update helmsman.yaml --- examples/dried-example/config/helmsman.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/dried-example/config/helmsman.yaml b/examples/dried-example/config/helmsman.yaml index ab3cd8a2..8ac2d03b 100644 --- a/examples/dried-example/config/helmsman.yaml +++ b/examples/dried-example/config/helmsman.yaml @@ -56,7 +56,7 @@ appsTemplates: puppetserver: &puppetserver enabled: true priority: -1 - chart: "../../../../puppetserver-helm-chart-1.8.2.tgz" + chart: "../../../../puppetserver-helm-chart-1.8.2.tgz" # https://github.com/puppetlabs/pupperware/tree/master/k8s version: "1.8.2" # chart version valuesFiles: [ "../apps/puppetserver/common-values.yaml", From 11f1ff82152e886f09a08d92d9ae69018d38c7dc Mon Sep 17 00:00:00 2001 From: Miroslav Hadzhiev Date: Sat, 28 Mar 2020 22:22:25 +0200 Subject: [PATCH 0619/1127] Create README.md --- examples/dried-example/README.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 examples/dried-example/README.md diff --git a/examples/dried-example/README.md b/examples/dried-example/README.md new file mode 100644 index 00000000..85a3aa97 --- /dev/null +++ b/examples/dried-example/README.md @@ -0,0 +1,9 @@ +# DRY-ed Example + +## Execution Plan + +To deploy the DRY-ed example - please run: + +```bash +helmsman -apply -no-default-repos -f config/helmsman.yaml +``` From b6b68e88e1a85be6507ba7cf8dbdd0156ec51290 Mon Sep 17 00:00:00 2001 From: Miroslav Hadzhiev Date: Sat, 28 Mar 2020 22:23:32 +0200 Subject: [PATCH 0620/1127] Delete .gitignore --- examples/dried-example/config/.gitignore | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 examples/dried-example/config/.gitignore diff --git a/examples/dried-example/config/.gitignore b/examples/dried-example/config/.gitignore deleted file mode 100644 index e9fed779..00000000 --- a/examples/dried-example/config/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*crt* -*key* From cf32f708c435e7e5119691ad4f4e3d80ed7cca9b Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Tue, 31 Mar 2020 13:06:39 +0200 Subject: [PATCH 0621/1127] minor formatting --- internal/app/decision_maker.go | 2 +- internal/app/helm_release.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index 1194e060..e5671a43 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -126,7 +126,7 @@ func (cs *currentState) decide(r *release, s *state, p *plan) { "you remove its protection.", r.Priority, noop) } } else if ok := cs.releaseExists(r, helmStatusPending); ok { - log.Error("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is in pending-upgrade state. " + + log.Error("Release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ] is in pending-upgrade state. " + "This means application is being upgraded outside of this Helmsman invocation's scope." + "Exiting, as this may cause issues when continuing...") os.Exit(1) diff --git a/internal/app/helm_release.go b/internal/app/helm_release.go index 2f39b4f2..d5730f94 100644 --- a/internal/app/helm_release.go +++ b/internal/app/helm_release.go @@ -11,9 +11,9 @@ import ( const ( helmStatusDeployed = "deployed" - helmStatusDeleted = "deleted" - helmStatusFailed = "failed" - helmStatusPending = "pending-upgrade" + helmStatusDeleted = "deleted" + helmStatusFailed = "failed" + helmStatusPending = "pending-upgrade" ) // helmRelease represents the current state of a release From de49aab47e23b1596967060ccc2b9f0bb292830f Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Tue, 31 Mar 2020 13:07:10 +0200 Subject: [PATCH 0622/1127] add --migrate-context flag --- internal/app/cli.go | 2 ++ internal/app/main.go | 7 ++++++- internal/app/state.go | 12 ++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/internal/app/cli.go b/internal/app/cli.go index 91917e57..7c976977 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -62,6 +62,7 @@ type cli struct { forceUpgrades bool version bool noCleanup bool + migrateContext bool } func printUsage() { @@ -103,6 +104,7 @@ func (c *cli) parse() { flag.BoolVar(&c.updateDeps, "update-deps", false, "run 'helm dep up' for local chart") flag.BoolVar(&c.forceUpgrades, "force-upgrades", false, "use --force when upgrading helm releases. May cause resources to be recreated.") flag.BoolVar(&c.noCleanup, "no-cleanup", false, "keeps any credentials files that has been downloaded on the host where helmsman runs.") + flag.BoolVar(&c.migrateContext, "migrate-context", false, "Updates the context name for all apps defined in the DSF and applies Helmsman labels. Using this flag is required if you want to change context name after it has been set.") flag.Usage = printUsage flag.Parse() diff --git a/internal/app/main.go b/internal/app/main.go index 04d4fa30..f7e57c54 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -89,11 +89,16 @@ func Main() { log.Info("Skipping charts' validation.") } - log.Info("Preparing plan...") if flags.destroy { log.Warning("Destroy flag is enabled. Your releases will be deleted!") } + if flags.migrateContext { + log.Warning("migrate-context flag is enabled. Context will be changed to [ " + s.Context + " ] and Helmsman labels will be applied.") + s.updateContextLabels() + } + + log.Info("Preparing plan...") cs := buildState(&s) p := cs.makePlan(&s) if !flags.keepUntrackedReleases { diff --git a/internal/app/state.go b/internal/app/state.go index 30baa04a..9a04c813 100644 --- a/internal/app/state.go +++ b/internal/app/state.go @@ -232,6 +232,18 @@ func (s *state) getNamespacesInTargetsOnly() map[string]namespace { return targetNamespaces } +// updateContextLabels applies Helmsman labels including overriding any previously-set context with the one found in the DSF +func (s *state) updateContextLabels() { + for _, r := range s.Apps { + if r.isConsideredToRun(s) { + log.Info("Updating context and reapplying Helmsman labels for release [ " + r.Name + " ]") + r.label() + } else { + log.Warning(r.Name + " is not in the target group and therefore context and labels are not changed.") + } + } +} + // print prints the desired state func (s *state) print() { From 81369a9468d07c57f0335d37a4004e5f52665255 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Tue, 31 Mar 2020 14:11:16 +0200 Subject: [PATCH 0623/1127] add docs for the migrate-context flag --- docs/cmd_reference.md | 3 ++ docs/desired_state_specification.md | 4 ++- docs/how_to/README.md | 5 +++ docs/how_to/apps/migrate_contexts.md | 15 ++++++++ docs/how_to/misc/merge_desired_state_files.md | 4 ++- docs/how_to/misc/migrate_to_3.md | 35 +++++++++++++++++++ 6 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 docs/how_to/apps/migrate_contexts.md create mode 100644 docs/how_to/misc/migrate_to_3.md diff --git a/docs/cmd_reference.md b/docs/cmd_reference.md index 707c98a3..a9825e5a 100644 --- a/docs/cmd_reference.md +++ b/docs/cmd_reference.md @@ -41,6 +41,9 @@ This lists available CMD options in Helmsman: `--kubeconfig` path to the kubeconfig file to use for CLI requests. + `--migrate-context` + Updates the context name for all apps defined in the DSF and applies Helmsman labels. Using this flag is required if you want to change context name after it has been set. + `--no-banner` don't show the banner. diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index ab115a53..ce34a8c4 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -1,5 +1,5 @@ --- -version: v3.0.0 +version: v3.2.0 --- # Helmsman desired state specification @@ -81,6 +81,8 @@ Optional : Yes. Synopsis: defines the context in which a DSF is used. This context is used as the ID of that specific DSF and must be unique across the used DSFs. If not defined, `default` is used. Check [here](how_to/misc/merge_desired_state_files.md) for more details on the limitations. +> Renaming the Helmsman context can be done from v3.2.0 using the `--migrate-context` flag. Check [this guide](how_to/apps/migrate_contexts.md) for details. + ```yaml context: prod-apps ... diff --git a/docs/how_to/README.md b/docs/how_to/README.md index f6409c02..4bd3f178 100644 --- a/docs/how_to/README.md +++ b/docs/how_to/README.md @@ -5,6 +5,8 @@ This page contains a list of guides on how to use Helmsman. It is recommended that you also check the [DSF spec](../desired_state_specification.md), [cmd reference](../cmd_reference.md), and the [best practice guide](../best_practice.md). +- [Migrating from Helm 2 (Helmsman v1.x) to Helm 3 (Helmsman v3.x)](misc/migrate_to_3.md) + - Connecting to Kubernetes clusters - [Using an existing kube context](settings/existing_kube_context.md) - [Using the current kube context](settings/current_kube_context.md) @@ -32,6 +34,9 @@ It is recommended that you also check the [DSF spec](../desired_state_specificat - [Run helm tests for deployed releases (apps)](apps/helm_tests.md) - [Define the order of apps operations](apps/order.md) - [Delete all releases (apps)](apps/destroy.md) + - [Distinguish releases deployed from different DSF files using Helmsman's contexts](misc/merge_desired_state_files.md#distinguishing-releases-deployed-from-different-desired-state-files) + - [Migrating releases from Helmsman context to another](apps/migrate_contexts.md) + - [Rename Helmsman's contexts](apps/migrate_contexts.md) - Running Helmsman in different environments - [Running Helmsman in CI](deployments/ci.md) - [Running Helmsman inside your k8s cluster](deployments/inside_k8s.md) diff --git a/docs/how_to/apps/migrate_contexts.md b/docs/how_to/apps/migrate_contexts.md new file mode 100644 index 00000000..c1f42982 --- /dev/null +++ b/docs/how_to/apps/migrate_contexts.md @@ -0,0 +1,15 @@ +--- +version: v3.2.0 +--- + +# Migrating releases from Helmsman context to another + +The `context` stanza has been introduced in v3.0.0 to allow you to [distinguish releases managed by different Helmsman's files](misc/merge_desired_state_files.md#distinguishing-releases-deployed-from-different-desired-state-files). However, once a context is defined, it couldn't be modified. + +From v.3.2.0, you can migrate releases in a DSF to another context (or rename the context) using the `--migrate-context`. This option can be combined with any other Helmsman flags. Behind the scenes, it will just update the Helmsman labels that contain the context name on the release secrets/configmaps before proceeding with the regular execution. + +# Remember +- It is safe to run the `--migrate-context` flag multiple times. +- It can be used in conjunction with other cmd flags. +- It will respect `--target` & `--group` flags if specified (i.e. context migration will only be applied to the selected releases). +- The flag introduces an extra operation done before any other operations are done. So to reduce execution time, don't use it when it's not needed. \ No newline at end of file diff --git a/docs/how_to/misc/merge_desired_state_files.md b/docs/how_to/misc/merge_desired_state_files.md index 5ee0214d..c12f9cf5 100644 --- a/docs/how_to/misc/merge_desired_state_files.md +++ b/docs/how_to/misc/merge_desired_state_files.md @@ -94,10 +94,12 @@ apps: ... ``` +> If you need to migrate releases from one Helmsman's context to another, check this [guide](../apps/migrate_contexts.md). + ### Limitations - If no context is provided in DSF (or merged DSFs), `default` is applied as a default context. This means any set of DSFs that don't define custom contexts can still operate on each other's releases (same behavior as in Helmsman 1.x). - When merging multiple DSFs, context from the firs DSF in the list gets overridden by the context in the last DSF. -- If multiple DSFs use the same context name, they will mess up each other's releases. +- If multiple DSFs use the same context name, they will mess up each other's releases. You can use `--keep-untracked-releases` to avoid that. However, it is recommended to avoid having multiple DSFs using the same context name. diff --git a/docs/how_to/misc/migrate_to_3.md b/docs/how_to/misc/migrate_to_3.md new file mode 100644 index 00000000..3a4c4f86 --- /dev/null +++ b/docs/how_to/misc/migrate_to_3.md @@ -0,0 +1,35 @@ +--- +version: v3.2.0 +--- + +# Migrate from Helm2 (Helmsman v1.x) to Helm3 (Helmsman v3.x) + +This guide describes the process of migrating your Helmsman managed releases from Helm v2 to v3. +Helmsman v3.x is Helm v3-compatible, while Helmsman v1.x is Helm v2-compatible. + +The migration process can go as follows: + +## Migrate Helm v2 release state to Helm v3 +- Go through the [Helm's v2 to v3 migration guide](https://helm.sh/docs/topics/v2_v3_migration/) +- Manually migrate your releases state/history with the [helm3 2to3 plugin](https://helm.sh/blog/migrate-from-helm-v2-to-helm-v3/) (e.g. usage helm3 2to3 convert ). + +> At this stage, Helm v3 can see and operate on your releases, but Helmsman can't. This is because Helmsman defined labels haven't been migrated to the Helm v3 releases state. + +## Migrate to Helmsman v3.x +- Download the latest Helmsman v3.x release from [Github releases](https://github.com/Praqma/helmsman/releases) +- Modify your Helmsman's TOML/YAML desired state files (DSFs) to be Helmsman v3.x compatible. You can check [v3.0.0 release notes](https://github.com/Praqma/helmsman/blob/v3.0.0/release-notes.md) for what's changed and verify from the [Desired State Spec](https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md) that your DSF files are compatible. + +> Everything related to Tiller will be removed from your DSFs at this stage. + +- Helmsman v3.x introduces the [`context` stanza](../../desired_state_specification.md#context) to logically group different groups of applications managed by Helmsman. It is highly recommended that you define a unique `context` for each of your DSFs at this stage. + +- In order for Helmsman to recognize the Helm v3 releases state, you need to use the `--migrate-context` flag on your first Helmsman v3.x run. This flag will recreate the Helmsman labels needed to recognize the Helm v3 releases state. + - Make sure that `helm` binary points to Helm v3 in your environment before you run this command. + - The `--migrate-context` flag is only available in Helmsman v.3.2.0 and above. If you are using Helmsman v3.0.x or v3.1.x, you can recreate the labels manually or with a script, but we highly recommend using Helmsman v3.2.0 or above. + - You only need to use `--migrate-context` once. However, the flag is safe to use multiple times. + + ```bash + $ helmsman --debug --migrate-context -f .yaml + ``` + +At this stage, you should have your release migrate to Helm v3 and Helmsman v3.x is able to see and manage those releases as usual. The next step would be to clean up your Helm v2 state and remove Tiller deployment which you can do with the [Helm v3 2to3 plugin](https://helm.sh/blog/migrate-from-helm-v2-to-helm-v3/). \ No newline at end of file From ce147f1159956408e9869475fb8d38794d312059 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Tue, 31 Mar 2020 14:56:20 +0200 Subject: [PATCH 0624/1127] document --context-override fixes #418 --- docs/cmd_reference.md | 3 +++ docs/how_to/README.md | 2 ++ docs/how_to/apps/override_context_from_cmd.md | 14 ++++++++++++++ internal/app/cli.go | 2 +- 4 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 docs/how_to/apps/override_context_from_cmd.md diff --git a/docs/cmd_reference.md b/docs/cmd_reference.md index a9825e5a..ccd80442 100644 --- a/docs/cmd_reference.md +++ b/docs/cmd_reference.md @@ -11,6 +11,9 @@ This lists available CMD options in Helmsman: `--apply` apply the plan directly. + `--context-override string` + override releases context defined in release state with this one. + `--debug` show the debug execution logs and actual helm/kubectl commands. This can log secrets and should only be used for debugging purposes. diff --git a/docs/how_to/README.md b/docs/how_to/README.md index 4bd3f178..7eb49fb7 100644 --- a/docs/how_to/README.md +++ b/docs/how_to/README.md @@ -37,6 +37,8 @@ It is recommended that you also check the [DSF spec](../desired_state_specificat - [Distinguish releases deployed from different DSF files using Helmsman's contexts](misc/merge_desired_state_files.md#distinguishing-releases-deployed-from-different-desired-state-files) - [Migrating releases from Helmsman context to another](apps/migrate_contexts.md) - [Rename Helmsman's contexts](apps/migrate_contexts.md) + - [Speed up Helmsman execution by skipping context fetching](apps/override_context_from_cmd.md) + - [Override context from cmd flags](apps/override_context_from_cmd.md) - Running Helmsman in different environments - [Running Helmsman in CI](deployments/ci.md) - [Running Helmsman inside your k8s cluster](deployments/inside_k8s.md) diff --git a/docs/how_to/apps/override_context_from_cmd.md b/docs/how_to/apps/override_context_from_cmd.md new file mode 100644 index 00000000..de6e9720 --- /dev/null +++ b/docs/how_to/apps/override_context_from_cmd.md @@ -0,0 +1,14 @@ +--- +version: v3.2.0 +--- + +# Override Helmsman context name from CMD flags using `--context-override` + +There are two main use cases for this flag: + +1. To speed up Helmsman's execution when you have too many release (see [issue #418](https://github.com/Praqma/helmsman/issues/418)) + This flag works by skipping the search for the context information which Helmsman adds in the form of labels to the helm release state (secrets/configmaps). + + > Use this option with caution. You must be sure that this won't cause conflicts. + +2. [Not recommended] If ,for whatever reason, you want to temporarily override the context defined on the release state (in labels on secrets/configmaps) with something. **Use [`--migrate-context`](migrate_contexts.md) instead to permanently rename your context** \ No newline at end of file diff --git a/internal/app/cli.go b/internal/app/cli.go index de6d66f5..a462726b 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -85,7 +85,7 @@ func (c *cli) parse() { flag.IntVar(&c.diffContext, "diff-context", -1, "number of lines of context to show around changes in helm diff output") flag.StringVar(&c.kubeconfig, "kubeconfig", "", "path to the kubeconfig file to use for CLI requests") flag.StringVar(&c.nsOverride, "ns-override", "", "override defined namespaces with this one") - flag.StringVar(&c.contextOverride, "context-override", "", "override releases context with this one") + flag.StringVar(&c.contextOverride, "context-override", "", "override releases context defined in release state with this one") flag.BoolVar(&c.apply, "apply", false, "apply the plan directly") flag.BoolVar(&c.dryRun, "dry-run", false, "apply the dry-run option for helm commands.") flag.BoolVar(&c.destroy, "destroy", false, "delete all deployed releases.") From 956ad1dd118d62b9136527a6a7b50b77b8c1b4b3 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Tue, 31 Mar 2020 16:05:58 +0200 Subject: [PATCH 0625/1127] fix race condition when creating multiple namespace limit ranges fixes #426 --- internal/app/kube_helpers.go | 9 +++++---- internal/app/utils.go | 9 +++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/internal/app/kube_helpers.go b/internal/app/kube_helpers.go index 9c720d1c..493a9f9b 100644 --- a/internal/app/kube_helpers.go +++ b/internal/app/kube_helpers.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "io/ioutil" + "path" "strings" "sync" @@ -124,19 +125,19 @@ spec: } definition = definition + Indent(string(d), strings.Repeat(" ", 4)) - - if err := ioutil.WriteFile("temp-LimitRange.yaml", []byte(definition), 0666); err != nil { + targetFile := path.Join(createTempDir(tempFilesDir, "tmp"), "temp-LimitRange.yaml") + if err := ioutil.WriteFile(targetFile, []byte(definition), 0666); err != nil { log.Fatal(err.Error()) } - cmd := kubectl([]string{"apply", "-f", "temp-LimitRange.yaml", "-n", ns}, "Creating LimitRange in namespace [ "+ns+" ]") + cmd := kubectl([]string{"apply", "-f", targetFile, "-n", ns}, "Creating LimitRange in namespace [ "+ns+" ]") result := cmd.exec() if result.code != 0 { log.Fatal("Failed to create LimitRange in namespace [ " + ns + " ] with error: " + result.errors) } - deleteFile("temp-LimitRange.yaml") + deleteFile(targetFile) } diff --git a/internal/app/utils.go b/internal/app/utils.go index c627fc60..6b231e00 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -341,6 +341,15 @@ func downloadFile(path string, outfile string) string { return outfile } +// createTempDir creates a temp directory in a specific location with a pattern +func createTempDir(parent string, pattern string) string { + dir, err := ioutil.TempDir(parent, pattern) + if err != nil { + log.Fatal(err.Error()) + } + return dir +} + // copyFile copies a file from source to destination func copyFile(source string, destination string) { from, err := os.Open(source) From e5449c4a5b20717b8f8265670d64bfb3ef5485bb Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Tue, 31 Mar 2020 16:32:27 +0200 Subject: [PATCH 0626/1127] add release step to update download instructions --- CONTRIBUTION.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md index 8d132853..a1e88e19 100644 --- a/CONTRIBUTION.md +++ b/CONTRIBUTION.md @@ -42,9 +42,10 @@ Release is automated from CicrcleCI based on Git tags. [Goreleaser](goreleaser.c The following steps are needed to cut a release (They assume that you are on master and the code is up to date): 1. Change the version variable in [main.go](internal/app/main.go) and in [.version](.version) 2. Update the [release-notes.md](release-notes.md) file with new version and changelog. -3. (Optional), if new helm versions are required, update the [circleci config](.circleci/config.yml) and add more docker commands. -4. Commit your changes locally. -5. Create a git tag with the following command: `git tag -a -m "" ` -6. Push your commit and tag with `git push --follow-tags` -7. This should trigger the [pipeline on circleci](https://circleci.com/gh/Praqma/workflows/helmsman) which eventually releases to Github and dockerhub. +3. Update the installation section in the [README.md](README.md) file to point to the latest version. +4. (Optional), if new helm versions are required, update the [circleci config](.circleci/config.yml) and add more docker commands. +5. Commit your changes locally. +6. Create a git tag with the following command: `git tag -a -m "" ` +7. Push your commit and tag with `git push --follow-tags` +8. This should trigger the [pipeline on circleci](https://circleci.com/gh/Praqma/workflows/helmsman) which eventually releases to Github and dockerhub. From 6def7102bc7c58c3876b8bb07f0759db435aac2e Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Tue, 31 Mar 2020 16:33:17 +0200 Subject: [PATCH 0627/1127] add image with the latest helm release --- .circleci/config.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8a09624f..60984bd6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -46,12 +46,13 @@ jobs: command: | TAG=$(git describe --abbrev=0 --tags) docker login -u $DOCKER_USER -p $DOCKER_PASS - docker build -t praqma/helmsman:$TAG-helm-v3.0.2 --build-arg HELM_VERSION=v3.0.2 . --no-cache - docker push praqma/helmsman:$TAG-helm-v3.0.2 docker build -t praqma/helmsman:$TAG-helm-v3.0.3 --build-arg HELM_VERSION=v3.0.3 . --no-cache docker push praqma/helmsman:$TAG-helm-v3.0.3 + docker build -t praqma/helmsman:$TAG-helm-v3.1.2 --build-arg HELM_VERSION=v3.1.2 . --no-cache + docker push praqma/helmsman:$TAG-helm-v3.1.2 + workflows: version: 2 build-test-push-release: From 7aa13932c0595bec8f860b299a4017c8d056ed17 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Tue, 31 Mar 2020 16:33:44 +0200 Subject: [PATCH 0628/1127] releasing v3.2.0 --- .version | 2 +- README.md | 6 +++--- internal/app/main.go | 2 +- release-notes.md | 22 +++++++++++++--------- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/.version b/.version index 6c8dc7eb..6d260c3a 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.1.0 +v3.2.0 diff --git a/README.md b/README.md index 9cbd9642..9fb60a5e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.0.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.2.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -61,9 +61,9 @@ If you use private helm repos, you will need either `helm-gcs` or `helm-s3` plug Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. ``` # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.0.0/helmsman_3.0.0_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.2.0/helmsman_3.2.0_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.0.0/helmsman_3.0.0_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.2.0/helmsman_3.2.0_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/internal/app/main.go b/internal/app/main.go index f7e57c54..8a9f74cb 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -6,7 +6,7 @@ import ( const ( helmBin = "helm" - appVersion = "v3.1.0" + appVersion = "v3.2.0" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index d2ff4166..03f20902 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,16 +1,20 @@ -# v3.1.0 +# v3.2.0 -This is a minor release. -It is recommended you read the [Helm 3 migration guide](https://helm.sh/docs/topics/v2_v3_migration/) before using this release. +If you migrating from Helmsman v1.x, it is recommended you read the [migration guide](https://github.com/Praqma/helmsman/blob/master/docs/how_to/misc/migrate_to_3.md) before using this release. > Starting from Helmsman v3.0.0 GA release, support for Helmsman v1.x will be limited to bug fixes. # Fixes and improvements: -- Add helm v3.0.3 to Docker images built -- Fix multiple versions of chart found in the helm repositories; PR #397 -- Get existing helm repos first before adding new ones from DSF in order to limit actions to be taken; PR #403 -- Enhance the way -target flag checks releases states; PR #405 -- Take advantage of enhancements in -target flag flow for -group flags; PR #407 +- Disable checking helm-secrets plugin installed when hiera-eyaml used for secrets; PR #408 +- Make decide exiting on pending-upgrade chart state; PR #409 +- Changes to standard output and Slack messages; PR #410 +- Fix local charts installation not being idempotent; PR #411 +- Fix destroy command when repo cache is not up to date; PR #416 +- Fix namespace limits defined in TOML not properly parsed; PR #422 +- Fix race condition when applying limit ranges to multiple namespaces; PR #429 + # New features: -None +- Add `--context-override` flag to skip fetching context name from releases state; PR #420 +- Add `no-cleanup` flag to optionally keep k8s credentials on host; PR #423 +- Add `--migrate-context` flag support renaming helmsman contexts and enable smooth migration of helm 2 releases to 3; PR #427 From 4ce7244b16aef43a5189a6a9384fb3857e0670ca Mon Sep 17 00:00:00 2001 From: ivelichkovich Date: Fri, 17 Jan 2020 14:45:10 -0600 Subject: [PATCH 0629/1127] add resource quotas --- docs/how_to/README.md | 1 + docs/how_to/namespaces/quotas.md | 44 ++++++++++++++++++++++++++++++++ examples/example.toml | 9 +++++++ internal/app/kube_helpers.go | 44 ++++++++++++++++++++++++++++++++ internal/app/namespace.go | 17 ++++++++++++ internal/app/release_test.go | 2 +- internal/app/state_test.go | 28 ++++++++++---------- 7 files changed, 130 insertions(+), 15 deletions(-) create mode 100644 docs/how_to/namespaces/quotas.md diff --git a/docs/how_to/README.md b/docs/how_to/README.md index 7eb49fb7..1f24bfbf 100644 --- a/docs/how_to/README.md +++ b/docs/how_to/README.md @@ -17,6 +17,7 @@ It is recommended that you also check the [DSF spec](../desired_state_specificat - [Label namespaces](namespaces/labels_and_annotations.md) - [Set resource limits for namespaces](namespaces/limits.md) - [Protecting namespaces](namespaces/protection.md) + - [Namespace resource quotas](namespaces/quotas.md) - Defining Helm repositories - [Using default helm repos](helm_repos/default.md) - [Using private repos in Google GCS](helm_repos/gcs.md) diff --git a/docs/how_to/namespaces/quotas.md b/docs/how_to/namespaces/quotas.md new file mode 100644 index 00000000..348c9213 --- /dev/null +++ b/docs/how_to/namespaces/quotas.md @@ -0,0 +1,44 @@ +--- +version: 3.3.0 +--- + +# Define resource quotas for namespaces + +You can define namespaces to be used in your cluster. If they don't exist, Helmsman will create them for you. You can also define how much resource limits to set for each namespace. + +You can read more about the `Quotas` specification [here](https://kubernetes.io/docs/tasks/administer-cluster/manage-resources/quota-memory-cpu-namespace/#create-a-resourcequota). + +```toml +#... +[namespaces] + + [namespaces.helmsman1] + + [namespaces.helmsman1.quotas] + "limits.cpu" = "10" + "limits.memory" = "30Gi" + pods = "25" + "requests.cpu" = "10" + "requests.memory" = "30Gi" + + [[namespaces.helmsman1.quotas.customQuotas]] + name = "requests.nvidia.com/gpu" + value = "2" +#... +``` + +```yaml +namespaces: + helmsman1: + quotas: + limits.cpu: '10' + limits.memory: '30Gi' + pods: '25' + requests.cpu: '10' + requests.memory: '30Gi' + customQuotas: + - name: 'requests.nvidia.com/gpu' + value: '2' +``` + +The example above will create one namespace - helmsman1 - with resource quotas defined for the helmsman1 namespace. \ No newline at end of file diff --git a/examples/example.toml b/examples/example.toml index bef218c5..04c99ab3 100644 --- a/examples/example.toml +++ b/examples/example.toml @@ -58,6 +58,15 @@ context= "test-infra" # defaults to "default" if not provided protected = false [namespaces.staging.labels] env = "staging" + [namespaces.staging.quotas] + "limits.cpu" = "10" + "limits.memory" = "30Gi" + pods = "25" + "requests.cpu" = "10" + "requests.memory" = "30Gi" + [[namespaces.helmsman1.quotas.customQuotas]] + name = "requests.nvidia.com/gpu" + value = "2" # define any private/public helm charts repos you would like to get charts from diff --git a/internal/app/kube_helpers.go b/internal/app/kube_helpers.go index 9c720d1c..4e2f729e 100644 --- a/internal/app/kube_helpers.go +++ b/internal/app/kube_helpers.go @@ -29,6 +29,7 @@ func addNamespaces(s *state) { labelNamespace(name, cfg.Labels) annotateNamespace(name, cfg.Annotations) setLimits(name, cfg.Limits) + setQuotas(name, cfg.Quotas) }(nsName, ns, &wg) } wg.Wait() @@ -140,6 +141,49 @@ spec: } +func setQuotas(ns string, quotas *quotas) { + if quotas == nil { + return + } + + definition := ` +--- +apiVersion: v1 +kind: ResourceQuota +metadata: + name: resource-quota +spec: + hard: +` + + for _, customQuota := range quotas.CustomQuotas { + definition = definition + Indent(customQuota.Name+": '"+customQuota.Value+"'\n", strings.Repeat(" ", 4)) + } + + //Special formatting for custom quotas so manually write these and then set to nil for marshalling + quotas.CustomQuotas = nil + + d, err := yaml.Marshal("as) + if err != nil { + log.Fatal(err.Error()) + } + + definition = definition + Indent(string(d), strings.Repeat(" ", 4)) + + if err := ioutil.WriteFile("temp-ResourceQuota.yaml", []byte(definition), 0666); err != nil { + log.Fatal(err.Error()) + } + + cmd := kubectl([]string{"apply", "-f", "temp-ResourceQuota.yaml", "-n", ns}, "Creating ResourceQuota in namespace [ "+ns+" ]") + result := cmd.exec() + + deleteFile("temp-ResourceQuota.yaml") + + if result.code != 0 { + log.Fatal("ERROR: failed to create ResourceQuota in namespace [ " + ns + " ]: " + result.errors) + } +} + // createContext creates a context -connecting to a k8s cluster- in kubectl config. // It returns true if successful, false otherwise func createContext(s *state) error { diff --git a/internal/app/namespace.go b/internal/app/namespace.go index 37cb7d5b..9cbc918a 100644 --- a/internal/app/namespace.go +++ b/internal/app/namespace.go @@ -10,6 +10,12 @@ type resources struct { Memory string `yaml:"memory,omitempty"` } +// custom resource type +type customResource struct { + Name string `yaml:"name,omitempty"` + Value string `yaml:"value,omitempty"` +} + // limits type type limits []struct { Max resources `yaml:"max,omitempty"` @@ -20,12 +26,23 @@ type limits []struct { Type string `yaml:"type"` } +// quota type +type quotas struct { + Pods string `yaml:"pods,omitempty"` + CPULimits string `yaml:"limits.cpu,omitempty"` + CPURequests string `yaml:"requests.cpu,omitempty"` + MemoryLimits string `yaml:"limits.memory,omitempty"` + MemoryRequests string `yaml:"requests.memory,omitempty"` + CustomQuotas []customResource `yaml:"customQuotas,omitempty"` +} + // namespace type represents the fields of a namespace type namespace struct { Protected bool `yaml:"protected"` Limits limits `yaml:"limits,omitempty"` Labels map[string]string `yaml:"labels"` Annotations map[string]string `yaml:"annotations"` + Quotas *quotas `yaml:"quotas,omitempty"` } // print prints the namespace diff --git a/internal/app/release_test.go b/internal/app/release_test.go index 99ce5b4f..88ea74cb 100644 --- a/internal/app/release_test.go +++ b/internal/app/release_test.go @@ -26,7 +26,7 @@ func Test_validateRelease(t *testing.T) { Metadata: make(map[string]string), Certificates: make(map[string]string), Settings: (config{}), - Namespaces: map[string]namespace{"namespace": namespace{false, limits{}, make(map[string]string), make(map[string]string)}}, + Namespaces: map[string]namespace{"namespace": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}}, HelmRepos: make(map[string]string), Apps: make(map[string]*release), } diff --git a/internal/app/state_test.go b/internal/app/state_test.go index 3599950b..c919a099 100644 --- a/internal/app/state_test.go +++ b/internal/app/state_test.go @@ -34,7 +34,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string)}, + "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -58,7 +58,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string)}, + "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -79,7 +79,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string)}, + "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -103,7 +103,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string)}, + "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -127,7 +127,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "$URI", // unset env }, Namespaces: map[string]namespace{ - "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string)}, + "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -151,7 +151,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https//192.168.99.100:8443", // invalid url }, Namespaces: map[string]namespace{ - "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string)}, + "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -174,7 +174,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string)}, + "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -195,7 +195,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string)}, + "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -219,7 +219,7 @@ func Test_state_validate(t *testing.T) { ClusterURI: "https://192.168.99.100:8443", }, Namespaces: map[string]namespace{ - "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string)}, + "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -237,7 +237,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string)}, + "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -287,7 +287,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string)}, + "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, }, HelmRepos: nil, Apps: make(map[string]*release), @@ -302,7 +302,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string)}, + "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, }, HelmRepos: map[string]string{}, Apps: make(map[string]*release), @@ -317,7 +317,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string)}, + "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", @@ -335,7 +335,7 @@ func Test_state_validate(t *testing.T) { KubeContext: "minikube", }, Namespaces: map[string]namespace{ - "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string)}, + "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, }, HelmRepos: map[string]string{ "stable": "https://kubernetes-charts.storage.googleapis.com", From e6bff1d0d004553f9bbc92ef4b867f8e53246085 Mon Sep 17 00:00:00 2001 From: Miroslav Hadzhiev Date: Sat, 4 Apr 2020 17:19:24 +0300 Subject: [PATCH 0630/1127] Small Dir Restructure --- docs/how_to/misc/use-dry-code.md | 103 ++++++++++++++++++ examples/appsTemplates/README.md | 11 ++ .../apps/puppetserver/common-values.yaml | 0 .../apps/puppetserver/development-values.yaml | 0 .../apps/puppetserver/testing-values.yaml | 0 .../apps/tomcat/common-values.yaml | 0 .../apps/tomcat/development-values.yaml | 0 .../apps/tomcat/testing-values.yaml | 0 .../config/helmsman.yaml | 0 examples/dried-example/README.md | 9 -- 10 files changed, 114 insertions(+), 9 deletions(-) create mode 100644 docs/how_to/misc/use-dry-code.md create mode 100644 examples/appsTemplates/README.md rename examples/{dried-example => appsTemplates}/apps/puppetserver/common-values.yaml (100%) rename examples/{dried-example => appsTemplates}/apps/puppetserver/development-values.yaml (100%) rename examples/{dried-example => appsTemplates}/apps/puppetserver/testing-values.yaml (100%) rename examples/{dried-example => appsTemplates}/apps/tomcat/common-values.yaml (100%) rename examples/{dried-example => appsTemplates}/apps/tomcat/development-values.yaml (100%) rename examples/{dried-example => appsTemplates}/apps/tomcat/testing-values.yaml (100%) rename examples/{dried-example => appsTemplates}/config/helmsman.yaml (100%) delete mode 100644 examples/dried-example/README.md diff --git a/docs/how_to/misc/use-dry-code.md b/docs/how_to/misc/use-dry-code.md new file mode 100644 index 00000000..5ee0214d --- /dev/null +++ b/docs/how_to/misc/use-dry-code.md @@ -0,0 +1,103 @@ +--- +version: v3.0.0-beta5 +--- + +# Supply multiple desired state files + +Starting from v1.5.0, Helmsman allows you to pass the `-f` flag multiple times to specify multiple desired state files +that should be merged. This allows us to do things like specify our non-environment-specific config in a `common.toml` file +and environment specific info in a `nonprod.toml` or `prod.toml` file. This process uses [this library](https://github.com/imdario/mergo) +to do the merging, and is subject to the limitations described there. + +For example: + +* `common.toml`: +```toml +[metadata] +org = "Organization Name" +maintainer = "project-owners@example.com" +description = "Project charts" + +[settings] +serviceAccount = "tiller" +storageBackend = "secret" +... +``` + +* `nonprod.toml`: +```toml +[settings] +kubeContext = "cluster-nonprod" + +[apps] + [apps.external-dns] + valuesFiles = ["./external-dns/values.yaml", "./external-dns/nonprod.yaml"] + + [apps.cert-issuer] + valuesFile = "./cert-issuer/nonprod.yaml" +... +``` + +One can then run the following to use the merged config of the above files, with later files override values of earlier ones: +```shell +$ helmsman -f common.toml -f nonprod.toml ... +``` + +## Distinguishing releases deployed from different Desired State Files + +When using multiple DSFs -and since Helmsman doesn't maintain any external state-, it has been possible for operations from one DSF to cause problems to releases deployed by other DSFs. A typical example is that releases deployed by other DSFs are considered `untracked` and get scheduled for deleting. Workarounds existed (e.g. using the `--keep-untracked-releases`, `--target` and `--group` flags). + +Starting from Helmsman v3.0.0-beta5, `context` is introduced to define the context in which a DSF is used. This context is used as the ID of that specific DSF and must be unique across the used DSFs. The context is then used to label the different releases to link them to the DSF they were first deployed from. These labels are then checked by Helmsman on each run to make sure operations are limited to releases from a specific context. + +Here is how it is used: + +* `infra.yaml`: +```yaml +context: infra-apps +settings: + kubeContext: "cluster" + storageBackend: "secret" + +namespaces: + infra: + protected: true + +apps: + external-dns: + namespace: infra + valuesFile: "./external-dns/values.yaml" + ... + + cert-issuer: + namespace: infra + valuesFile: "./cert-issuer/nonprod.yaml" + ... +... +``` + +* `prod.yaml`: +```yaml +context: prod-apps +settings: + kubeContext: "cluster" + storageBackend: "secret" + +namespaces: + prod: + protected: true + +apps: + my-prod-app: + namespace: prod + valuesFile: "./my-prod-app/values.yaml" + ... +... +``` + +### Limitations + +- If no context is provided in DSF (or merged DSFs), `default` is applied as a default context. This means any set of DSFs that don't define custom contexts can still operate on each other's releases (same behavior as in Helmsman 1.x). + +- When merging multiple DSFs, context from the firs DSF in the list gets overridden by the context in the last DSF. + +- If multiple DSFs use the same context name, they will mess up each other's releases. diff --git a/examples/appsTemplates/README.md b/examples/appsTemplates/README.md new file mode 100644 index 00000000..d46f976f --- /dev/null +++ b/examples/appsTemplates/README.md @@ -0,0 +1,11 @@ +# DRY-ed Example + +## Execution Plan + +To deploy the DRY-ed example, the app templates placed in [config](config) and [apps](apps) need to be used. To do so - please run: + +```bash +helmsman -apply -f config/helmsman.yaml +``` + +> **Tip**: That kind of DRY-ed code can be achieved only using YAML desired state files. diff --git a/examples/dried-example/apps/puppetserver/common-values.yaml b/examples/appsTemplates/apps/puppetserver/common-values.yaml similarity index 100% rename from examples/dried-example/apps/puppetserver/common-values.yaml rename to examples/appsTemplates/apps/puppetserver/common-values.yaml diff --git a/examples/dried-example/apps/puppetserver/development-values.yaml b/examples/appsTemplates/apps/puppetserver/development-values.yaml similarity index 100% rename from examples/dried-example/apps/puppetserver/development-values.yaml rename to examples/appsTemplates/apps/puppetserver/development-values.yaml diff --git a/examples/dried-example/apps/puppetserver/testing-values.yaml b/examples/appsTemplates/apps/puppetserver/testing-values.yaml similarity index 100% rename from examples/dried-example/apps/puppetserver/testing-values.yaml rename to examples/appsTemplates/apps/puppetserver/testing-values.yaml diff --git a/examples/dried-example/apps/tomcat/common-values.yaml b/examples/appsTemplates/apps/tomcat/common-values.yaml similarity index 100% rename from examples/dried-example/apps/tomcat/common-values.yaml rename to examples/appsTemplates/apps/tomcat/common-values.yaml diff --git a/examples/dried-example/apps/tomcat/development-values.yaml b/examples/appsTemplates/apps/tomcat/development-values.yaml similarity index 100% rename from examples/dried-example/apps/tomcat/development-values.yaml rename to examples/appsTemplates/apps/tomcat/development-values.yaml diff --git a/examples/dried-example/apps/tomcat/testing-values.yaml b/examples/appsTemplates/apps/tomcat/testing-values.yaml similarity index 100% rename from examples/dried-example/apps/tomcat/testing-values.yaml rename to examples/appsTemplates/apps/tomcat/testing-values.yaml diff --git a/examples/dried-example/config/helmsman.yaml b/examples/appsTemplates/config/helmsman.yaml similarity index 100% rename from examples/dried-example/config/helmsman.yaml rename to examples/appsTemplates/config/helmsman.yaml diff --git a/examples/dried-example/README.md b/examples/dried-example/README.md deleted file mode 100644 index 85a3aa97..00000000 --- a/examples/dried-example/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# DRY-ed Example - -## Execution Plan - -To deploy the DRY-ed example - please run: - -```bash -helmsman -apply -no-default-repos -f config/helmsman.yaml -``` From f8ef47d51a5b6143eef399f8b1831e742d6d4ac8 Mon Sep 17 00:00:00 2001 From: Miroslav Hadzhiev Date: Sat, 4 Apr 2020 17:29:16 +0300 Subject: [PATCH 0631/1127] Update use-dry-code.md --- docs/how_to/misc/use-dry-code.md | 102 +------------------------------ 1 file changed, 3 insertions(+), 99 deletions(-) diff --git a/docs/how_to/misc/use-dry-code.md b/docs/how_to/misc/use-dry-code.md index 5ee0214d..c3091c3c 100644 --- a/docs/how_to/misc/use-dry-code.md +++ b/docs/how_to/misc/use-dry-code.md @@ -1,103 +1,7 @@ --- -version: v3.0.0-beta5 +version: v3.0.0 --- -# Supply multiple desired state files +# Use DRY-ed code in YAML -Starting from v1.5.0, Helmsman allows you to pass the `-f` flag multiple times to specify multiple desired state files -that should be merged. This allows us to do things like specify our non-environment-specific config in a `common.toml` file -and environment specific info in a `nonprod.toml` or `prod.toml` file. This process uses [this library](https://github.com/imdario/mergo) -to do the merging, and is subject to the limitations described there. - -For example: - -* `common.toml`: -```toml -[metadata] -org = "Organization Name" -maintainer = "project-owners@example.com" -description = "Project charts" - -[settings] -serviceAccount = "tiller" -storageBackend = "secret" -... -``` - -* `nonprod.toml`: -```toml -[settings] -kubeContext = "cluster-nonprod" - -[apps] - [apps.external-dns] - valuesFiles = ["./external-dns/values.yaml", "./external-dns/nonprod.yaml"] - - [apps.cert-issuer] - valuesFile = "./cert-issuer/nonprod.yaml" -... -``` - -One can then run the following to use the merged config of the above files, with later files override values of earlier ones: -```shell -$ helmsman -f common.toml -f nonprod.toml ... -``` - -## Distinguishing releases deployed from different Desired State Files - -When using multiple DSFs -and since Helmsman doesn't maintain any external state-, it has been possible for operations from one DSF to cause problems to releases deployed by other DSFs. A typical example is that releases deployed by other DSFs are considered `untracked` and get scheduled for deleting. Workarounds existed (e.g. using the `--keep-untracked-releases`, `--target` and `--group` flags). - -Starting from Helmsman v3.0.0-beta5, `context` is introduced to define the context in which a DSF is used. This context is used as the ID of that specific DSF and must be unique across the used DSFs. The context is then used to label the different releases to link them to the DSF they were first deployed from. These labels are then checked by Helmsman on each run to make sure operations are limited to releases from a specific context. - -Here is how it is used: - -* `infra.yaml`: -```yaml -context: infra-apps -settings: - kubeContext: "cluster" - storageBackend: "secret" - -namespaces: - infra: - protected: true - -apps: - external-dns: - namespace: infra - valuesFile: "./external-dns/values.yaml" - ... - - cert-issuer: - namespace: infra - valuesFile: "./cert-issuer/nonprod.yaml" - ... -... -``` - -* `prod.yaml`: -```yaml -context: prod-apps -settings: - kubeContext: "cluster" - storageBackend: "secret" - -namespaces: - prod: - protected: true - -apps: - my-prod-app: - namespace: prod - valuesFile: "./my-prod-app/values.yaml" - ... -... -``` - -### Limitations - -- If no context is provided in DSF (or merged DSFs), `default` is applied as a default context. This means any set of DSFs that don't define custom contexts can still operate on each other's releases (same behavior as in Helmsman 1.x). - -- When merging multiple DSFs, context from the firs DSF in the list gets overridden by the context in the last DSF. - -- If multiple DSFs use the same context name, they will mess up each other's releases. +If you want to deploy the DRY-ed example, please refer to the provided [app templates](../../examples/appsTemplates) From 9b6a5359c68ddb52d026429b1dc2a252582ce6c4 Mon Sep 17 00:00:00 2001 From: Miroslav Hadzhiev Date: Sat, 4 Apr 2020 17:32:13 +0300 Subject: [PATCH 0632/1127] Update use-dry-code.md --- docs/how_to/misc/use-dry-code.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how_to/misc/use-dry-code.md b/docs/how_to/misc/use-dry-code.md index c3091c3c..cb735267 100644 --- a/docs/how_to/misc/use-dry-code.md +++ b/docs/how_to/misc/use-dry-code.md @@ -4,4 +4,4 @@ version: v3.0.0 # Use DRY-ed code in YAML -If you want to deploy the DRY-ed example, please refer to the provided [app templates](../../examples/appsTemplates) +If you want to use as a baseline or deploy the DRY-ed example, please refer to the provided [app templates](../../../examples/appsTemplates) From 856f84c25845eae884d295e74d1fe36e70d0c2b7 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Mon, 6 Apr 2020 12:05:04 +0100 Subject: [PATCH 0633/1127] feat: Parallelise releases with the same priority, fixes: #361 Signed-off-by: Luis Davim --- docs/cmd_reference.md | 9 +++-- internal/app/cli.go | 6 ++++ internal/app/decision_maker.go | 9 ++--- internal/app/plan.go | 65 ++++++++++++++++++++++++++-------- 4 files changed, 67 insertions(+), 22 deletions(-) diff --git a/docs/cmd_reference.md b/docs/cmd_reference.md index ccd80442..e2e1e214 100644 --- a/docs/cmd_reference.md +++ b/docs/cmd_reference.md @@ -12,13 +12,13 @@ This lists available CMD options in Helmsman: apply the plan directly. `--context-override string` - override releases context defined in release state with this one. + override releases context defined in release state with this one. `--debug` show the debug execution logs and actual helm/kubectl commands. This can log secrets and should only be used for debugging purposes. `--verbose` - show verbose execution logs. + show verbose execution logs. `--destroy` delete all deployed releases. @@ -26,6 +26,9 @@ This lists available CMD options in Helmsman: `--diff-context num` number of lines of context to show around changes in helm diff output. + `-p` + max number of concurrent helm releases to run + `--dry-run` apply the dry-run (do not update) option for helm commands. @@ -45,7 +48,7 @@ This lists available CMD options in Helmsman: path to the kubeconfig file to use for CLI requests. `--migrate-context` - Updates the context name for all apps defined in the DSF and applies Helmsman labels. Using this flag is required if you want to change context name after it has been set. + Updates the context name for all apps defined in the DSF and applies Helmsman labels. Using this flag is required if you want to change context name after it has been set. `--no-banner` don't show the banner. diff --git a/internal/app/cli.go b/internal/app/cli.go index a462726b..1e8b5315 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -64,6 +64,7 @@ type cli struct { version bool noCleanup bool migrateContext bool + parallel int } func printUsage() { @@ -83,6 +84,7 @@ func (c *cli) parse() { flag.Var(&c.target, "target", "limit execution to specific app.") flag.Var(&c.group, "group", "limit execution to specific group of apps.") flag.IntVar(&c.diffContext, "diff-context", -1, "number of lines of context to show around changes in helm diff output") + flag.IntVar(&c.parallel, "p", 1, "max number of concurrent helm releases to run") flag.StringVar(&c.kubeconfig, "kubeconfig", "", "path to the kubeconfig file to use for CLI requests") flag.StringVar(&c.nsOverride, "ns-override", "", "override defined namespaces with this one") flag.StringVar(&c.contextOverride, "context-override", "", "override releases context defined in release state with this one") @@ -138,6 +140,10 @@ func (c *cli) parse() { log.Fatal("--target and --group can't be used together.") } + if c.parallel < 1 { + c.parallel = 1 + } + helmVersion := strings.TrimSpace(getHelmVersion()) extractedHelmVersion := helmVersion if !strings.HasPrefix(helmVersion, "v") { diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index 9535dba3..bf739c2b 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -170,14 +170,15 @@ var releaseNameExtractor = regexp.MustCompile(`sh\.helm\.release\.v\d+\.`) // The releases are categorized by the namespaces in which they are deployed // The returned map format is: map[:map[:true]] func (cs *currentState) getHelmsmanReleases(s *state) map[string]map[string]bool { + const outputFmt = "custom-columns=NAME:.metadata.name,CTX:.metadata.labels.HELMSMAN_CONTEXT" var ( - wg sync.WaitGroup - mutex = &sync.Mutex{} + wg sync.WaitGroup + mutex = &sync.Mutex{} + namespaces map[string]namespace ) - const outputFmt = "custom-columns=NAME:.metadata.name,CTX:.metadata.labels.HELMSMAN_CONTEXT" releases := make(map[string]map[string]bool) sem := make(chan struct{}, resourcePool) - namespaces := make(map[string]namespace) + if len(s.TargetMap) > 0 { namespaces = s.TargetNamespaces } else { diff --git a/internal/app/plan.go b/internal/app/plan.go index 14ad2ead..f075ae74 100644 --- a/internal/app/plan.go +++ b/internal/app/plan.go @@ -86,25 +86,60 @@ func (p *plan) exec() { log.Info("Nothing to execute") } + wg := sync.WaitGroup{} + sem := make(chan struct{}, flags.parallel) + var fail bool + var priorities []int + pl := make(map[int][]orderedCommand) for _, cmd := range p.Commands { - log.Notice(cmd.Command.Description) - result := cmd.Command.exec() - if cmd.targetRelease != nil && !flags.dryRun && !flags.destroy { - cmd.targetRelease.label() + pl[cmd.Priority] = append(pl[cmd.Priority], cmd) + } + for priority := range pl { + priorities = append(priorities, priority) + } + sort.Ints(priorities) + + for _, priority := range priorities { + c := make(chan string, len(pl[priority])) + for _, cmd := range pl[priority] { + sem <- struct{}{} + wg.Add(1) + go func(cmd orderedCommand) { + defer func() { + wg.Done() + <-sem + }() + log.Notice(cmd.Command.Description) + result := cmd.Command.exec() + if cmd.targetRelease != nil && !flags.dryRun && !flags.destroy { + cmd.targetRelease.label() + } + if result.code != 0 { + errorMsg := result.errors + if !flags.verbose { + errorMsg = strings.Split(result.errors, "---")[0] + } + c <- fmt.Sprintf("Command for release [%s] returned [ %d ] exit code and error message [ %s ]", cmd.targetRelease.Name, result.code, strings.TrimSpace(errorMsg)) + } else { + log.Notice(result.output) + log.Notice("Finished: " + cmd.Command.Description) + if _, err := url.ParseRequestURI(settings.SlackWebhook); err == nil { + notifySlack(cmd.Command.Description+" ... SUCCESS!", settings.SlackWebhook, false, true) + } + } + }(cmd) } - if result.code != 0 { - errorMsg := result.errors - if !flags.verbose { - errorMsg = strings.Split(result.errors, "---")[0] - } - log.Fatal(fmt.Sprintf("Command returned [ %d ] exit code and error message [ %s ]", result.code, strings.TrimSpace(errorMsg))) - } else { - log.Notice(result.output) - log.Notice("Finished: " + cmd.Command.Description) - if _, err := url.ParseRequestURI(settings.SlackWebhook); err == nil { - notifySlack(cmd.Command.Description+" ... SUCCESS!", settings.SlackWebhook, false, true) + wg.Wait() + close(c) + for err := range c { + if err != "" { + fail = true + log.Error(err) } } + if fail { + log.Fatal("Plan execution failed") + } } if len(p.Commands) > 0 { From 509f39df171e374fe286e6a5b205c2c72bb159c0 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Wed, 15 Apr 2020 09:08:07 +0200 Subject: [PATCH 0634/1127] Release v3.3.0 --- .version | 2 +- README.md | 2 +- internal/app/main.go | 2 +- release-notes.md | 16 ++++------------ 4 files changed, 7 insertions(+), 15 deletions(-) diff --git a/.version b/.version index 6d260c3a..b299be97 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.2.0 +v3.3.0 diff --git a/README.md b/README.md index 9fb60a5e..82f95826 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.2.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.3.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) diff --git a/internal/app/main.go b/internal/app/main.go index 8a9f74cb..4891d56b 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -6,7 +6,7 @@ import ( const ( helmBin = "helm" - appVersion = "v3.2.0" + appVersion = "v3.3.0" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index 03f20902..c5d5e1b0 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,20 +1,12 @@ -# v3.2.0 +# v3.3.0 If you migrating from Helmsman v1.x, it is recommended you read the [migration guide](https://github.com/Praqma/helmsman/blob/master/docs/how_to/misc/migrate_to_3.md) before using this release. > Starting from Helmsman v3.0.0 GA release, support for Helmsman v1.x will be limited to bug fixes. # Fixes and improvements: -- Disable checking helm-secrets plugin installed when hiera-eyaml used for secrets; PR #408 -- Make decide exiting on pending-upgrade chart state; PR #409 -- Changes to standard output and Slack messages; PR #410 -- Fix local charts installation not being idempotent; PR #411 -- Fix destroy command when repo cache is not up to date; PR #416 -- Fix namespace limits defined in TOML not properly parsed; PR #422 -- Fix race condition when applying limit ranges to multiple namespaces; PR #429 - +- Add DRY-ed examples using appsTemplates; PR #425 # New features: -- Add `--context-override` flag to skip fetching context name from releases state; PR #420 -- Add `no-cleanup` flag to optionally keep k8s credentials on host; PR #423 -- Add `--migrate-context` flag support renaming helmsman contexts and enable smooth migration of helm 2 releases to 3; PR #427 +- Add `--p` flag to define parallel apps installation/upgrade for those with the same priority defined in DSF; PR #431 +- Add Namespace's resource quotas to DSF; PR #384 From d85b5ad799ea6c304dd1b8426f90f4599f200631 Mon Sep 17 00:00:00 2001 From: Mehdi Yedes Date: Wed, 29 Apr 2020 17:35:17 +0200 Subject: [PATCH 0635/1127] Support the Helm --set-file flag Signed-off-by: Mehdi Yedes --- docs/desired_state_specification.md | 1 + internal/app/release.go | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index ce34a8c4..fc75ed0b 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -358,6 +358,7 @@ Options: - **priority** : defines the priority of applying operations on this release. Only negative values allowed and the lower the value, the higher the priority. Default priority is 0. Apps with equal priorities will be applied in the order they were added in your state file (DSF). - **set** : is used to override certain values from values.yaml with values from environment variables (or ,starting from v1.3.0-rc, directly provided in the Desired State File). This is particularly useful for passing secrets to charts. If the an environment variable with the same name as the provided value exists, the environment variable value will be used, otherwise, the provided value will be used as is. The TOML stanza for this is `[apps..set]` - **setString** : is used to override String values from values.yaml or chart's defaults. This uses the `--set-string` flag in helm which is available only in helm >v2.9.0. This option is useful for image tags and the like. The TOML stanza for this is `[apps..setString]` +- **setFile** : is used to override values from values.yaml or chart's defaults from provided file. This uses the `--set-file` flag in helm. This option is useful for embedding file contents in the values. The TOML stanza for this is `[apps..setFile]` - **helmFlags** : array of `helm` flags, is used to pass flags to helm install/upgrade commands Example: diff --git a/internal/app/release.go b/internal/app/release.go index 05ef1213..048c939a 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -31,6 +31,7 @@ type release struct { Priority int `yaml:"priority"` Set map[string]string `yaml:"set"` SetString map[string]string `yaml:"setString"` + SetFile map[string]string `yaml:"setFile"` HelmFlags []string `yaml:"helmFlags"` NoHooks bool `yaml:"noHooks"` Timeout int `yaml:"timeout"` @@ -461,6 +462,15 @@ func (r *release) getSetStringValues() []string { return result } +// getSetFileValues returns --set-file params to be used with helm install/upgrade commands +func (r *release) getSetFileValues() []string { + result := []string{} + for k, v := range r.SetFile { + result = append(result, "--set-file", k+"="+strings.Replace(v, ",", "\\,", -1)+"") + } + return result +} + // getWait returns a partial helm command containing the helm wait flag (--wait) if the wait flag for the release was set to true // Otherwise, retruns an empty string func (r *release) getWait() []string { @@ -487,9 +497,9 @@ func (r *release) getHelmFlags() []string { func (r *release) getHelmArgsFor(action string) []string { switch action { case "install": - return concat([]string{action, r.Name, r.Chart, "--version", r.Version, "--namespace", r.Namespace}, r.getValuesFiles(), r.getSetValues(), r.getSetStringValues(), r.getWait(), r.getHelmFlags()) + return concat([]string{action, r.Name, r.Chart, "--version", r.Version, "--namespace", r.Namespace}, r.getValuesFiles(), r.getSetValues(), r.getSetStringValues(), r.getSetFileValues(), r.getWait(), r.getHelmFlags()) case "upgrade": - return concat([]string{action, "--namespace", r.Namespace, r.Name, r.Chart}, r.getValuesFiles(), []string{"--version", r.Version}, r.getSetValues(), r.getSetStringValues()) + return concat([]string{action, "--namespace", r.Namespace, r.Name, r.Chart}, r.getValuesFiles(), []string{"--version", r.Version}, r.getSetValues(), r.getSetStringValues(), r.getSetFileValues()) default: return []string{action, "--namespace", r.Namespace, r.Name} } From ed3978c3bbb6f340d91b0c603f736a6d2f377974 Mon Sep 17 00:00:00 2001 From: Mehdi Yedes Date: Thu, 7 May 2020 11:54:49 +0200 Subject: [PATCH 0636/1127] Add gnupg to the Docker image Signed-off-by: Mehdi Yedes --- scripts/setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/setup.sh b/scripts/setup.sh index 4799fa3d..686e08a0 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -2,7 +2,7 @@ set -e -apk add --update --no-cache ca-certificates git openssh ruby curl tar gzip make bash +apk add --update --no-cache ca-certificates git openssh ruby curl tar gzip make bash gnupg curl -L https://storage.googleapis.com/kubernetes-release/release/${KUBE_VERSION}/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl chmod +x /usr/local/bin/kubectl From 128be5b254210b060c97fedcca190c173b14bb65 Mon Sep 17 00:00:00 2001 From: zanderhavgaard Date: Wed, 22 Apr 2020 21:32:51 +0200 Subject: [PATCH 0637/1127] refactored Hooks from struct to map[string]string --- Makefile | 6 +- internal/app/release.go | 134 +++++++++++------------------------ internal/app/release_test.go | 42 +++++------ internal/app/state.go | 35 ++++----- internal/app/utils.go | 92 +++++------------------- 5 files changed, 98 insertions(+), 211 deletions(-) diff --git a/Makefile b/Makefile index 581839af..bc42ca57 100644 --- a/Makefile +++ b/Makefile @@ -79,9 +79,9 @@ release: ## Generate a new release @goreleaser --release-notes release-notes.md --rm-dist tools: ## Get extra tools used by this makefile - @go get -u github.com/golang/dep/cmd/dep - @go get -u github.com/mitchellh/gox - @go get -u github.com/goreleaser/goreleaser + @go get -d -u github.com/golang/dep/cmd/dep + @go get -d -u github.com/mitchellh/gox + @go get -d -u github.com/goreleaser/goreleaser @gem install hiera-eyaml .PHONY: tools diff --git a/internal/app/release.go b/internal/app/release.go index c73bfd2f..e253ea3b 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -34,7 +34,7 @@ type release struct { HelmFlags []string `yaml:"helmFlags"` NoHooks bool `yaml:"noHooks"` Timeout int `yaml:"timeout"` - Hooks hooks `yaml:"hooks"` + Hooks map[string]string `yaml:"hooks"` } // hooks type which defines life-cycle hooks @@ -129,7 +129,7 @@ func (r *release) validate(appLabel string, names map[string]map[string]bool, s return errors.New("priority can only be 0 or negative value, positive values are not allowed") } - if (hooks{}) != r.Hooks { + if (len(r.Hooks)) != 0 { if ok, errorMsg := validateHooks(r.Hooks); !ok { return fmt.Errorf(errorMsg) } @@ -154,39 +154,9 @@ func (r *release) validate(appLabel string, names map[string]map[string]bool, s } // validateHooks validates that hook files exist and of YAML type -func validateHooks(hks hooks) (bool, string) { - if hks.PreInstall != "" { - if err := isValidFile(hks.PreInstall, []string{".yaml", ".yml"}); err != nil { - return false, err.Error() - } - } - - if hks.PostInstall != "" { - if err := isValidFile(hks.PostInstall, []string{".yaml", ".yml"}); err != nil { - return false, err.Error() - } - } - - if hks.PreUpgrade != "" { - if err := isValidFile(hks.PreUpgrade, []string{".yaml", ".yml"}); err != nil { - return false, err.Error() - } - } - - if hks.PostUpgrade != "" { - if err := isValidFile(hks.PostUpgrade, []string{".yaml", ".yml"}); err != nil { - return false, err.Error() - } - } - - if hks.PreDelete != "" { - if err := isValidFile(hks.PreDelete, []string{".yaml", ".yml"}); err != nil { - return false, err.Error() - } - } - - if hks.PostDelete != "" { - if err := isValidFile(hks.PostDelete, []string{".yaml", ".yml"}); err != nil { +func validateHooks(hooks map[string]string) (bool, string) { + for _, value := range hooks { + if err := isValidFile(value, []string{".yaml", ".yml"}); err != nil { return false, err.Error() } } @@ -593,33 +563,12 @@ func (r *release) overrideNamespace(newNs string) { // inheritHooks passes global hooks config from the state to the release hooks if they are unset // release hooks override the global ones func (r *release) inheritHooks(s *state) { - if (hooks{}) != s.Settings.GlobalHooks { - if (hooks{}) == r.Hooks { + if len(s.Settings.GlobalHooks) != 0 { + if len(r.Hooks) == 0 { r.Hooks = s.Settings.GlobalHooks } else { - if r.Hooks.PreInstall == "" { - r.Hooks.PreInstall = s.Settings.GlobalHooks.PreInstall - } - if r.Hooks.PostInstall == "" { - r.Hooks.PostInstall = s.Settings.GlobalHooks.PostInstall - } - if r.Hooks.PreUpgrade == "" { - r.Hooks.PreUpgrade = s.Settings.GlobalHooks.PreUpgrade - } - if r.Hooks.PostUpgrade == "" { - r.Hooks.PostUpgrade = s.Settings.GlobalHooks.PostUpgrade - } - if r.Hooks.PreDelete == "" { - r.Hooks.PreDelete = s.Settings.GlobalHooks.PreDelete - } - if r.Hooks.SuccessCondition == "" { - r.Hooks.SuccessCondition = s.Settings.GlobalHooks.SuccessCondition - } - if r.Hooks.SuccessTimeout == "" { - r.Hooks.SuccessTimeout = s.Settings.GlobalHooks.SuccessTimeout - } - if !r.Hooks.DeleteOnSuccess { - r.Hooks.DeleteOnSuccess = s.Settings.GlobalHooks.DeleteOnSuccess + for key, _ := range s.Settings.GlobalHooks { + r.Hooks[key] = s.Settings.GlobalHooks[key] } } } @@ -638,45 +587,45 @@ func (r *release) checkHooks(hookType string, p *plan, optionalNamespace ...stri switch hookType { case "install": { - if r.Hooks.PreInstall != "" { - beforeCommands = append(beforeCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks.PreInstall}, "Apply pre-install manifest "+r.Hooks.PreInstall)) - if wait, cmds := r.shouldWaitForHook(r.Hooks.PreInstall, "pre-install", ns); wait { + if _, ok := r.Hooks["preInstall"]; ok { + beforeCommands = append(beforeCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks["preInstall"]}, "Apply pre-install manifest "+r.Hooks["preInstall"])) + if wait, cmds := r.shouldWaitForHook(r.Hooks["preInstall"], "pre-install", ns); wait { beforeCommands = append(beforeCommands, cmds...) } } - if r.Hooks.PostInstall != "" { - afterCommands = append(afterCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks.PostInstall}, "Apply post-install manifest "+r.Hooks.PostInstall)) - if wait, cmds := r.shouldWaitForHook(r.Hooks.PostInstall, "post-install", ns); wait { + if _, ok := r.Hooks["postInstall"]; ok { + afterCommands = append(afterCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks["postInstall"]}, "Apply post-install manifest "+r.Hooks["postInstall"])) + if wait, cmds := r.shouldWaitForHook(r.Hooks["postIntall"], "post-install", ns); wait { afterCommands = append(afterCommands, cmds...) } } } case "upgrade": { - if r.Hooks.PreUpgrade != "" { - beforeCommands = append(beforeCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks.PreUpgrade}, "Apply pre-upgrade manifest "+r.Hooks.PreUpgrade)) - if wait, cmds := r.shouldWaitForHook(r.Hooks.PreUpgrade, "pre-upgrade", ns); wait { + if _, ok := r.Hooks["PreUpgrade"]; ok { + beforeCommands = append(beforeCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks["PreUpgrade"]}, "Apply pre-upgrade manifest "+r.Hooks["PreUpgrade"])) + if wait, cmds := r.shouldWaitForHook(r.Hooks["PreUpgrade"], "pre-upgrade", ns); wait { beforeCommands = append(beforeCommands, cmds...) } } - if r.Hooks.PostUpgrade != "" { - afterCommands = append(afterCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks.PostUpgrade}, "Apply post-upgrade manifest "+r.Hooks.PostUpgrade)) - if wait, cmds := r.shouldWaitForHook(r.Hooks.PostUpgrade, "post-upgrade", ns); wait { + if _, ok := r.Hooks["postUpgrade"]; ok { + afterCommands = append(afterCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks["postUpgrade"]}, "Apply post-upgrade manifest "+r.Hooks["postUpgrade"])) + if wait, cmds := r.shouldWaitForHook(r.Hooks["postUpgrade"], "post-upgrade", ns); wait { afterCommands = append(afterCommands, cmds...) } } } case "delete": { - if r.Hooks.PreDelete != "" { - beforeCommands = append(beforeCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks.PreDelete}, "Apply pre-delete manifest "+r.Hooks.PreDelete)) - if wait, cmds := r.shouldWaitForHook(r.Hooks.PreDelete, "pre-delete", ns); wait { + if _, ok := r.Hooks["preDelete"]; ok { + beforeCommands = append(beforeCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks["preDelete"]}, "Apply pre-delete manifest "+r.Hooks["preDelete"])) + if wait, cmds := r.shouldWaitForHook(r.Hooks["preDelete"], "pre-delete", ns); wait { beforeCommands = append(beforeCommands, cmds...) } } - if r.Hooks.PostDelete != "" { - afterCommands = append(afterCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks.PostDelete}, "Apply post-delete manifest "+r.Hooks.PostDelete)) - if wait, cmds := r.shouldWaitForHook(r.Hooks.PostDelete, "post-delete", ns); wait { + if _, ok := r.Hooks["postDelete"]; ok { + afterCommands = append(afterCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks["postDelete"]}, "Apply post-delete manifest "+r.Hooks["postDelete"])) + if wait, cmds := r.shouldWaitForHook(r.Hooks["postDelete"], "post-delete", ns); wait { afterCommands = append(afterCommands, cmds...) } } @@ -689,13 +638,14 @@ func (r *release) checkHooks(hookType string, p *plan, optionalNamespace ...stri // returns a boolean and the wait command if applicable func (r *release) shouldWaitForHook(hookFile string, hookType string, namespace string) (bool, []command) { var cmds []command - if r.Hooks.SuccessCondition != "" { + // if r.Hooks.SuccessCondition != "" { + if _, ok := r.Hooks["successCondition"]; ok { timeoutFlag := "" - if r.Hooks.SuccessTimeout != "" { - timeoutFlag = "--timeout=" + r.Hooks.SuccessTimeout + if _, ok := r.Hooks["successTimeout"]; ok { + timeoutFlag = "--timeout=" + r.Hooks["successTimeout"] } - cmds = append(cmds, kubectl([]string{"wait", "-n", namespace, "-f", hookFile, "--for=condition=" + r.Hooks.SuccessCondition, timeoutFlag}, "Wait for "+hookType+" : "+hookFile)) - if r.Hooks.DeleteOnSuccess { + cmds = append(cmds, kubectl([]string{"wait", "-n", namespace, "-f", hookFile, "--for=condition=" + r.Hooks["successCondition"], timeoutFlag}, "Wait for "+hookType+" : "+hookFile)) + if _, ok := r.Hooks["deleteOnSuccess"]; ok { cmds = append(cmds, kubectl([]string{"delete", "-n", namespace, "-f", hookFile}, "Delete "+hookType+" : "+hookFile)) } return true, cmds @@ -718,15 +668,15 @@ func (r release) print() { fmt.Println("\tprotected : ", r.Protected) fmt.Println("\twait : ", r.Wait) fmt.Println("\tpriority : ", r.Priority) - fmt.Println("\tSuccessCondition : ", r.Hooks.SuccessCondition) - fmt.Println("\tSuccessTimeout : ", r.Hooks.SuccessTimeout) - fmt.Println("\tDeleteOnSuccess : ", r.Hooks.DeleteOnSuccess) - fmt.Println("\tpreInstall : ", r.Hooks.PreInstall) - fmt.Println("\tpostInstall : ", r.Hooks.PostInstall) - fmt.Println("\tpreUpgrade : ", r.Hooks.PreUpgrade) - fmt.Println("\tpostUpgrade : ", r.Hooks.PostUpgrade) - fmt.Println("\tpreDelete : ", r.Hooks.PreDelete) - fmt.Println("\tpostDelete : ", r.Hooks.PostDelete) + fmt.Println("\tSuccessCondition : ", r.Hooks["successCondition"]) + fmt.Println("\tSuccessTimeout : ", r.Hooks["successTimeout"]) + fmt.Println("\tDeleteOnSuccess : ", r.Hooks["deleteOnSuccess"]) + fmt.Println("\tpreInstall : ", r.Hooks["preInstall"]) + fmt.Println("\tpostInstall : ", r.Hooks["postInstall"]) + fmt.Println("\tpreUpgrade : ", r.Hooks["preUpgrade"]) + fmt.Println("\tpostUpgrade : ", r.Hooks["postUpgrade"]) + fmt.Println("\tpreDelete : ", r.Hooks["preDelete"]) + fmt.Println("\tpostDelete : ", r.Hooks["postDelete"]) fmt.Println("\tno-hooks : ", r.NoHooks) fmt.Println("\ttimeout : ", r.Timeout) fmt.Println("\tvalues to override from env:") diff --git a/internal/app/release_test.go b/internal/app/release_test.go index 3b0303da..83d0292a 100644 --- a/internal/app/release_test.go +++ b/internal/app/release_test.go @@ -278,9 +278,7 @@ func Test_validateRelease(t *testing.T) { Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", - Hooks: hooks{ - PreInstall: "xyz.fake", - }, + Hooks: map[string]string{"preInstall": "xyz.fake"}, }, s: st, }, @@ -296,9 +294,7 @@ func Test_validateRelease(t *testing.T) { Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", - Hooks: hooks{ - PreInstall: "../../tests/values.xml", - }, + Hooks: map[string]string{"preInstall": "../../tests/values.xml"}, }, s: st, }, @@ -314,9 +310,7 @@ func Test_validateRelease(t *testing.T) { Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", - Hooks: hooks{ - PreDelete: "../../tests/values.yaml", - }, + Hooks: map[string]string{"preDelete": "../../tests/values.xml"}, }, s: st, }, @@ -332,9 +326,7 @@ func Test_validateRelease(t *testing.T) { Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", - Hooks: hooks{ - PostUpgrade: "https://raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml", - }, + Hooks: map[string]string{"postUpgrade": "https://raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml"}, }, s: st, }, @@ -350,9 +342,7 @@ func Test_validateRelease(t *testing.T) { Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", - Hooks: hooks{ - PreDelete: "https//raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml", - }, + Hooks: map[string]string{"preDelete": "https//raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml"}, }, s: st, }, @@ -378,11 +368,11 @@ func Test_inheritHooks(t *testing.T) { Metadata: make(map[string]string), Certificates: make(map[string]string), Settings: config{ - GlobalHooks: hooks{ - PreInstall: "https//raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml", - PostInstall: "https//raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml", - SuccessCondition: "Complete", - SuccessTimeout: "60s", + GlobalHooks: map[string]string{ + "preInstall": "https//raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml", + "postInstall": "https//raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml", + "successCondition": "Complete", + "successTimeout": "60s", }, }, Namespaces: map[string]namespace{"namespace": namespace{false, limits{}, make(map[string]string), make(map[string]string)}}, @@ -410,10 +400,10 @@ func Test_inheritHooks(t *testing.T) { Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", - Hooks: hooks{ - PostInstall: "../../tests/values.yaml", - PreDelete: "../../tests/values.yaml", - SuccessTimeout: "360s", + Hooks: map[string]string{ + "postInstall": "../../tests/values.yaml", + "preDelete": "../../tests/values.yaml", + "successTimeout": "360s", }, }, s: st, @@ -427,8 +417,8 @@ func Test_inheritHooks(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.args.r.inheritHooks(&tt.args.s) - got := tt.args.r.Hooks.PreInstall + " -- " + tt.args.r.Hooks.PostInstall + " -- " + tt.args.r.Hooks.PreDelete + - " -- " + tt.args.r.Hooks.SuccessCondition + " -- " + tt.args.r.Hooks.SuccessTimeout + got := tt.args.r.Hooks["preInstall"] + " -- " + tt.args.r.Hooks["postInstall"] + " -- " + tt.args.r.Hooks["preDelete"] + + " -- " + tt.args.r.Hooks["successCondition"] + " -- " + tt.args.r.Hooks["successTimeout"] if got != tt.want { t.Errorf("inheritHooks() got = %v, want %v", got, tt.want) } diff --git a/internal/app/state.go b/internal/app/state.go index cdc5025d..3ec731ca 100644 --- a/internal/app/state.go +++ b/internal/app/state.go @@ -5,25 +5,26 @@ import ( "fmt" "net/url" "os" + "reflect" "strings" ) // config type represents the settings fields type config struct { - KubeContext string `yaml:"kubeContext"` - Username string `yaml:"username"` - Password string `yaml:"password"` - ClusterURI string `yaml:"clusterURI"` - ServiceAccount string `yaml:"serviceAccount"` - StorageBackend string `yaml:"storageBackend"` - SlackWebhook string `yaml:"slackWebhook"` - ReverseDelete bool `yaml:"reverseDelete"` - BearerToken bool `yaml:"bearerToken"` - BearerTokenPath string `yaml:"bearerTokenPath"` - EyamlEnabled bool `yaml:"eyamlEnabled"` - EyamlPrivateKeyPath string `yaml:"eyamlPrivateKeyPath"` - EyamlPublicKeyPath string `yaml:"eyamlPublicKeyPath"` - GlobalHooks hooks `yaml:"globalHooks"` + KubeContext string `yaml:"kubeContext"` + Username string `yaml:"username"` + Password string `yaml:"password"` + ClusterURI string `yaml:"clusterURI"` + ServiceAccount string `yaml:"serviceAccount"` + StorageBackend string `yaml:"storageBackend"` + SlackWebhook string `yaml:"slackWebhook"` + ReverseDelete bool `yaml:"reverseDelete"` + BearerToken bool `yaml:"bearerToken"` + BearerTokenPath string `yaml:"bearerTokenPath"` + EyamlEnabled bool `yaml:"eyamlEnabled"` + EyamlPrivateKeyPath string `yaml:"eyamlPrivateKeyPath"` + EyamlPublicKeyPath string `yaml:"eyamlPublicKeyPath"` + GlobalHooks map[string]string `yaml:"globalHooks"` } // state type represents the desired state of applications on a k8s cluster. @@ -75,7 +76,9 @@ func (s *state) validate() error { } // settings - if (s.Settings == (config{}) || s.Settings.KubeContext == "") && !getKubeContext() { + // if (s.Settings == (config{}) || s.Settings.KubeContext == "") && !getKubeContext() { + // use reflect.DeepEqual to compare Settings are empty, since it now contains a map + if (reflect.DeepEqual(s.Settings, config{}) || s.Settings.KubeContext == "") && !getKubeContext() { return errors.New("settings validation failed -- you have not defined a " + "kubeContext to use. Either define it in the desired state file or pass a kubeconfig with --kubeconfig to use an existing context") } @@ -103,7 +106,7 @@ func (s *state) validate() error { } // lifecycle hooks validation - if (hooks{}) != s.Settings.GlobalHooks { + if len(s.Settings.GlobalHooks) != 0 { if ok, errorMsg := validateHooks(s.Settings.GlobalHooks); !ok { return fmt.Errorf(errorMsg) } diff --git a/internal/app/utils.go b/internal/app/utils.go index 43a3f4a5..44fdb92f 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -147,46 +147,19 @@ func substituteVarsInStaticFiles(s *state) { if v.SecretsFile != "" { v.SecretsFile = substituteVarsInYaml(v.SecretsFile) } - if (hooks{}) != v.Hooks { - if v.Hooks.PreInstall != "" { - v.Hooks.PreInstall = substituteVarsInYaml(v.Hooks.PreInstall) - } - if v.Hooks.PostInstall != "" { - v.Hooks.PostInstall = substituteVarsInYaml(v.Hooks.PostInstall) - } - if v.Hooks.PreUpgrade != "" { - v.Hooks.PreUpgrade = substituteVarsInYaml(v.Hooks.PreUpgrade) - } - if v.Hooks.PostUpgrade != "" { - v.Hooks.PostUpgrade = substituteVarsInYaml(v.Hooks.PostUpgrade) - } - if v.Hooks.PreDelete != "" { - v.Hooks.PreDelete = substituteVarsInYaml(v.Hooks.PreDelete) - } - if v.Hooks.PostDelete != "" { - v.Hooks.PostDelete = substituteVarsInYaml(v.Hooks.PostDelete) + + if len(v.Hooks) != 0 { + for key, val := range v.Hooks { + v.Hooks[key] = substituteVarsInYaml(val) } } - if (hooks{}) != s.Settings.GlobalHooks { - if s.Settings.GlobalHooks.PreInstall != "" { - s.Settings.GlobalHooks.PreInstall = substituteVarsInYaml(s.Settings.GlobalHooks.PreInstall) - } - if s.Settings.GlobalHooks.PostInstall != "" { - s.Settings.GlobalHooks.PostInstall = substituteVarsInYaml(s.Settings.GlobalHooks.PostInstall) - } - if s.Settings.GlobalHooks.PreUpgrade != "" { - s.Settings.GlobalHooks.PreUpgrade = substituteVarsInYaml(s.Settings.GlobalHooks.PreUpgrade) - } - if s.Settings.GlobalHooks.PostUpgrade != "" { - s.Settings.GlobalHooks.PostUpgrade = substituteVarsInYaml(s.Settings.GlobalHooks.PostUpgrade) - } - if s.Settings.GlobalHooks.PreDelete != "" { - s.Settings.GlobalHooks.PreDelete = substituteVarsInYaml(s.Settings.GlobalHooks.PreDelete) - } - if s.Settings.GlobalHooks.PostDelete != "" { - s.Settings.GlobalHooks.PostDelete = substituteVarsInYaml(s.Settings.GlobalHooks.PostDelete) + + if len(s.Settings.GlobalHooks) != 0 { + for key, val := range s.Settings.GlobalHooks { + s.Settings.GlobalHooks[key] = substituteVarsInYaml(val) } } + for i := range v.ValuesFiles { v.ValuesFiles[i] = substituteVarsInYaml(v.ValuesFiles[i]) } @@ -244,27 +217,13 @@ func resolvePaths(relativeToFile string, s *state) { if v.SecretsFile != "" { v.SecretsFile, _ = resolveOnePath(v.SecretsFile, dir, downloadDest) } - if (hooks{}) != v.Hooks { - if v.Hooks.PreInstall != "" { - v.Hooks.PreInstall, _ = resolveOnePath(v.Hooks.PreInstall, dir, downloadDest) - } - if v.Hooks.PostInstall != "" { - v.Hooks.PostInstall, _ = resolveOnePath(v.Hooks.PostInstall, dir, downloadDest) - } - if v.Hooks.PreUpgrade != "" { - v.Hooks.PreUpgrade, _ = resolveOnePath(v.Hooks.PreUpgrade, dir, downloadDest) - } - if v.Hooks.PostUpgrade != "" { - v.Hooks.PostUpgrade, _ = resolveOnePath(v.Hooks.PostUpgrade, dir, downloadDest) - } - if v.Hooks.PreDelete != "" { - v.Hooks.PreDelete, _ = resolveOnePath(v.Hooks.PreDelete, dir, downloadDest) - } - if v.Hooks.PostDelete != "" { - v.Hooks.PostDelete, _ = resolveOnePath(v.Hooks.PostDelete, dir, downloadDest) + if len(v.Hooks) != 0 { + for key, val := range v.Hooks { + v.Hooks[key], _ = resolveOnePath(val, dir, downloadDest) } } + for i := range v.ValuesFiles { v.ValuesFiles[i], _ = resolveOnePath(v.ValuesFiles[i], dir, downloadDest) } @@ -294,24 +253,9 @@ func resolvePaths(relativeToFile string, s *state) { s.Settings.BearerTokenPath, _ = resolveOnePath(s.Settings.BearerTokenPath, dir, downloadDest) } // resolve paths for global hooks - if (hooks{}) != s.Settings.GlobalHooks { - if s.Settings.GlobalHooks.PreInstall != "" { - s.Settings.GlobalHooks.PreInstall, _ = resolveOnePath(s.Settings.GlobalHooks.PreInstall, dir, downloadDest) - } - if s.Settings.GlobalHooks.PostInstall != "" { - s.Settings.GlobalHooks.PostInstall, _ = resolveOnePath(s.Settings.GlobalHooks.PostInstall, dir, downloadDest) - } - if s.Settings.GlobalHooks.PreUpgrade != "" { - s.Settings.GlobalHooks.PreUpgrade, _ = resolveOnePath(s.Settings.GlobalHooks.PreUpgrade, dir, downloadDest) - } - if s.Settings.GlobalHooks.PostUpgrade != "" { - s.Settings.GlobalHooks.PostUpgrade, _ = resolveOnePath(s.Settings.GlobalHooks.PostUpgrade, dir, downloadDest) - } - if s.Settings.GlobalHooks.PreDelete != "" { - s.Settings.GlobalHooks.PreDelete, _ = resolveOnePath(s.Settings.GlobalHooks.PreDelete, dir, downloadDest) - } - if s.Settings.GlobalHooks.PostDelete != "" { - s.Settings.GlobalHooks.PostDelete, _ = resolveOnePath(s.Settings.GlobalHooks.PostDelete, dir, downloadDest) + if len(s.Settings.GlobalHooks) != 0 { + for key, val := range s.Settings.GlobalHooks { + s.Settings.GlobalHooks[key], _ = resolveOnePath(val, dir, downloadDest) } } // resolving paths for k8s certificate files @@ -537,14 +481,14 @@ func notifySlack(content string, url string, failure bool, executing bool) bool }`) req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr)) if err != nil { - log.Errorf("Failed to send slack message: %w", err) + log.Errorf("Failed to send slack message: %v", err) } req.Header.Set("Content-Type", "application/json") client := &http.Client{} resp, err := client.Do(req) if err != nil { - log.Errorf("Failed to send notification to slack: %w", err) + log.Errorf("Failed to send notification to slack: %v", err) } defer resp.Body.Close() From 8e7e9f8693eb4ed21b459a61f6b49d861ad51d2a Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Fri, 8 May 2020 18:34:19 +0200 Subject: [PATCH 0638/1127] fix tests and other improvments --- docs/how_to/apps/lifecycle_hooks.md | 18 +++-- examples/example.toml | 36 ++++----- examples/example.yaml | 2 +- internal/app/cli.go | 13 +++- internal/app/decision_maker.go | 3 - internal/app/kube_helpers.go | 8 ++ internal/app/release.go | 113 ++++++++++++++-------------- internal/app/release_test.go | 56 +++++++++++--- internal/app/state.go | 31 ++++---- internal/app/utils.go | 16 +++- 10 files changed, 173 insertions(+), 123 deletions(-) diff --git a/docs/how_to/apps/lifecycle_hooks.md b/docs/how_to/apps/lifecycle_hooks.md index cd9a236e..0134819d 100644 --- a/docs/how_to/apps/lifecycle_hooks.md +++ b/docs/how_to/apps/lifecycle_hooks.md @@ -1,5 +1,5 @@ --- -version: v3.2.0 +version: v3.4.0 --- # Helmsman Lifecycle hooks @@ -15,12 +15,14 @@ Another useful use-case is if you are using a 3rd party chart which does not def ## Supported lifecycle stages -- pre-install : before installing a release. -- post-install: after installing a release. -- pre-upgrade: before upgrading a release. -- post-upgrade: after upgrading a release. -- pre-delete: before uninstalling a release. -- post-delete: after uninstalling a release. +> hook types are case sensitive. Also, note the camleCase. + +- `preInstall` : before installing a release. +- `postInstall`: after installing a release. +- `preUpgrade`: before upgrading a release. +- `postUpgrade`: after upgrading a release. +- `preDelete`: before uninstalling a release. +- `postDelete`: after uninstalling a release. ## Hooks stanza details @@ -35,6 +37,8 @@ For deployments, it is `Available` - `successTimeout` (default 30s) how much time to wait for the `successCondition` - `deleteOnSuccess` (true/false) indicates if you wish to delete the hook's manifest after the hook succeeds. This is only used if you define `successCondition` +> Note: successCondition, deleteOnSuccess and successTimeout are ignored when the `--dry-run` flag is used. + ## Global vs App-specific hooks You can define two types of hooks in your desired state file: diff --git a/examples/example.toml b/examples/example.toml index 4bc07f5e..d5a167af 100644 --- a/examples/example.toml +++ b/examples/example.toml @@ -32,10 +32,10 @@ context= "test-infra" # defaults to "default" if not provided #### to use bearer token: # bearerToken = true # clusterURI = "https://kubernetes.default" - [settings.globalHooks] - successCondition= "Initialized" - deleteOnSuccess= true - postInstall= "job.yaml" +# [settings.globalHooks] +# successCondition= "Initialized" +# deleteOnSuccess= true +# postInstall= "job.yaml" @@ -86,7 +86,7 @@ context= "test-infra" # defaults to "default" if not provided namespace = "production" # maps to the namespace as defined in namespaces above enabled = true # change to false if you want to delete this app release [default = false] chart = "argo/argo" # changing the chart name means delete and recreate this release - version = "0.6.4" # chart version + version = "0.8.5" # chart version ### Optional values below valuesFile = "" # leaving it empty uses the default chart values test = false # run the tests when this release is installed for the first time only @@ -96,7 +96,7 @@ context= "test-infra" # defaults to "default" if not provided [apps.argo.hooks] successCondition= "Complete" successTimeout= "90s" - # deleteOnSuccess= true + deleteOnSuccess= true preInstall="job.yaml" # preInstall="https://github.com/jetstack/cert-manager/releases/download/v0.14.0/cert-manager.crds.yaml" # postInstall="https://raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml" @@ -111,17 +111,17 @@ context= "test-infra" # defaults to "default" if not provided # installCRD="true" - # [apps.artifactory] - # namespace = "production" # maps to the namespace as defined in namespaces above - # enabled = true # change to false if you want to delete this app release [default = false] - # chart = "jfrog/artifactory" # changing the chart name means delete and recreate this release - # version = "8.3.2" # chart version - # ### Optional values below - # valuesFile = "" # leaving it empty uses the default chart values - # test = false # run the tests when this release is installed for the first time only - # priority= -2 - # noHooks= false - # timeout= 300 - # helmFlags= [] # additional helm flags for this release + [apps.artifactory] + namespace = "production" # maps to the namespace as defined in namespaces above + enabled = true # change to false if you want to delete this app release [default = false] + chart = "jfrog/artifactory" # changing the chart name means delete and recreate this release + version = "8.3.2" # chart version + ### Optional values below + valuesFile = "" # leaving it empty uses the default chart values + test = false # run the tests when this release is installed for the first time only + priority= -2 + noHooks= false + timeout= 300 + helmFlags= [] # additional helm flags for this release # See https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md#apps for more apps options diff --git a/examples/example.yaml b/examples/example.yaml index 521d3624..80c78d12 100644 --- a/examples/example.yaml +++ b/examples/example.yaml @@ -81,7 +81,7 @@ apps: ### Optional values below valuesFile: "" # leaving it empty uses the default chart values test: false - #protected: true + protected: true priority: -3 wait: true # hooks: diff --git a/internal/app/cli.go b/internal/app/cli.go index e372c572..958180a1 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -250,10 +250,6 @@ func (c *cli) readState(s *state) { } } - if c.debug { - s.print() - } - if !c.skipValidation { // validate the desired state content if len(c.files) > 0 { @@ -277,6 +273,15 @@ func (c *cli) readState(s *state) { if s.Context == "" { s.Context = defaultContextName } + + // inherit globalHooks if local ones are not set + for _, r := range s.Apps { + r.inheritHooks(s) + } + + if c.debug { + s.print() + } } // getDryRunFlags returns dry-run flag diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index 129643cb..766e3cc2 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -82,9 +82,6 @@ func (cs *currentState) decide(r *release, s *state, p *plan) { return } - // inherit globalHooks if local ones are not set - r.inheritHooks(s) - if flags.destroy { if ok := cs.releaseExists(r, ""); ok { p.addDecision("Release [ "+r.Name+" ] will be DELETED (destroy flag enabled).", r.Priority, delete) diff --git a/internal/app/kube_helpers.go b/internal/app/kube_helpers.go index 9d4d5b29..7e0ff71f 100644 --- a/internal/app/kube_helpers.go +++ b/internal/app/kube_helpers.go @@ -292,3 +292,11 @@ func getKubectlClientVersion() string { } return result.output } + +// getKubeDryRunFlag returns kubectl dry-run flag if helmsman --dry-run flag is enabled +func (c *cli) getKubeDryRunFlag() string { + if c.dryRun { + return "--server-dry-run" + } + return "" +} diff --git a/internal/app/release.go b/internal/app/release.go index e253ea3b..87b763b6 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -14,40 +14,27 @@ import ( // release type representing Helm releases which are described in the desired state type release struct { - Name string `yaml:"name"` - Description string `yaml:"description"` - Namespace string `yaml:"namespace"` - Enabled bool `yaml:"enabled"` - Group string `yaml:"group"` - Chart string `yaml:"chart"` - Version string `yaml:"version"` - ValuesFile string `yaml:"valuesFile"` - ValuesFiles []string `yaml:"valuesFiles"` - SecretsFile string `yaml:"secretsFile"` - SecretsFiles []string `yaml:"secretsFiles"` - Test bool `yaml:"test"` - Protected bool `yaml:"protected"` - Wait bool `yaml:"wait"` - Priority int `yaml:"priority"` - Set map[string]string `yaml:"set"` - SetString map[string]string `yaml:"setString"` - HelmFlags []string `yaml:"helmFlags"` - NoHooks bool `yaml:"noHooks"` - Timeout int `yaml:"timeout"` - Hooks map[string]string `yaml:"hooks"` -} - -// hooks type which defines life-cycle hooks -type hooks struct { - SuccessCondition string `yaml:"successCondition"` - SuccessTimeout string `yaml:"successTimeout"` - DeleteOnSuccess bool `yaml:"deleteOnSuccess"` - PreInstall string `yaml:"preInstall"` - PostInstall string `yaml:"postInstall"` - PreUpgrade string `yaml:"preUpgrade"` - PostUpgrade string `yaml:"postUpgrade"` - PreDelete string `yaml:"preDelete"` - PostDelete string `yaml:"postDelete"` + Name string `yaml:"name"` + Description string `yaml:"description"` + Namespace string `yaml:"namespace"` + Enabled bool `yaml:"enabled"` + Group string `yaml:"group"` + Chart string `yaml:"chart"` + Version string `yaml:"version"` + ValuesFile string `yaml:"valuesFile"` + ValuesFiles []string `yaml:"valuesFiles"` + SecretsFile string `yaml:"secretsFile"` + SecretsFiles []string `yaml:"secretsFiles"` + Test bool `yaml:"test"` + Protected bool `yaml:"protected"` + Wait bool `yaml:"wait"` + Priority int `yaml:"priority"` + Set map[string]string `yaml:"set"` + SetString map[string]string `yaml:"setString"` + HelmFlags []string `yaml:"helmFlags"` + NoHooks bool `yaml:"noHooks"` + Timeout int `yaml:"timeout"` + Hooks map[string]interface{} `yaml:"hooks"` } type chartVersion struct { @@ -154,10 +141,17 @@ func (r *release) validate(appLabel string, names map[string]map[string]bool, s } // validateHooks validates that hook files exist and of YAML type -func validateHooks(hooks map[string]string) (bool, string) { - for _, value := range hooks { - if err := isValidFile(value, []string{".yaml", ".yml"}); err != nil { - return false, err.Error() +func validateHooks(hooks map[string]interface{}) (bool, string) { + for key, value := range hooks { + switch key { + case "preInstall", "postInstall", "preUpgrade", "postUpgrade", "preDelete", "postDelete": + if err := isValidFile(value.(string), []string{".yaml", ".yml"}); err != nil { + return false, err.Error() + } + case "successCondition", "successTimeout", "deleteOnSuccess": + continue + default: + return false, key + " is an Invalid hook type." } } return true, "" @@ -567,8 +561,10 @@ func (r *release) inheritHooks(s *state) { if len(r.Hooks) == 0 { r.Hooks = s.Settings.GlobalHooks } else { - for key, _ := range s.Settings.GlobalHooks { - r.Hooks[key] = s.Settings.GlobalHooks[key] + for key := range s.Settings.GlobalHooks { + if _, ok := r.Hooks[key]; !ok { + r.Hooks[key] = s.Settings.GlobalHooks[key] + } } } } @@ -588,29 +584,29 @@ func (r *release) checkHooks(hookType string, p *plan, optionalNamespace ...stri case "install": { if _, ok := r.Hooks["preInstall"]; ok { - beforeCommands = append(beforeCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks["preInstall"]}, "Apply pre-install manifest "+r.Hooks["preInstall"])) - if wait, cmds := r.shouldWaitForHook(r.Hooks["preInstall"], "pre-install", ns); wait { + beforeCommands = append(beforeCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks["preInstall"].(string), flags.getKubeDryRunFlag()}, "Apply pre-install manifest "+r.Hooks["preInstall"].(string))) + if wait, cmds := r.shouldWaitForHook(r.Hooks["preInstall"].(string), "pre-install", ns); wait { beforeCommands = append(beforeCommands, cmds...) } } if _, ok := r.Hooks["postInstall"]; ok { - afterCommands = append(afterCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks["postInstall"]}, "Apply post-install manifest "+r.Hooks["postInstall"])) - if wait, cmds := r.shouldWaitForHook(r.Hooks["postIntall"], "post-install", ns); wait { + afterCommands = append(afterCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks["postInstall"].(string), flags.getKubeDryRunFlag()}, "Apply post-install manifest "+r.Hooks["postInstall"].(string))) + if wait, cmds := r.shouldWaitForHook(r.Hooks["postInstall"].(string), "post-install", ns); wait { afterCommands = append(afterCommands, cmds...) } } } case "upgrade": { - if _, ok := r.Hooks["PreUpgrade"]; ok { - beforeCommands = append(beforeCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks["PreUpgrade"]}, "Apply pre-upgrade manifest "+r.Hooks["PreUpgrade"])) - if wait, cmds := r.shouldWaitForHook(r.Hooks["PreUpgrade"], "pre-upgrade", ns); wait { + if _, ok := r.Hooks["preUpgrade"]; ok { + beforeCommands = append(beforeCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks["preUpgrade"].(string), flags.getKubeDryRunFlag()}, "Apply pre-upgrade manifest "+r.Hooks["preUpgrade"].(string))) + if wait, cmds := r.shouldWaitForHook(r.Hooks["preUpgrade"].(string), "pre-upgrade", ns); wait { beforeCommands = append(beforeCommands, cmds...) } } if _, ok := r.Hooks["postUpgrade"]; ok { - afterCommands = append(afterCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks["postUpgrade"]}, "Apply post-upgrade manifest "+r.Hooks["postUpgrade"])) - if wait, cmds := r.shouldWaitForHook(r.Hooks["postUpgrade"], "post-upgrade", ns); wait { + afterCommands = append(afterCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks["postUpgrade"].(string), flags.getKubeDryRunFlag()}, "Apply post-upgrade manifest "+r.Hooks["postUpgrade"].(string))) + if wait, cmds := r.shouldWaitForHook(r.Hooks["postUpgrade"].(string), "post-upgrade", ns); wait { afterCommands = append(afterCommands, cmds...) } } @@ -618,14 +614,14 @@ func (r *release) checkHooks(hookType string, p *plan, optionalNamespace ...stri case "delete": { if _, ok := r.Hooks["preDelete"]; ok { - beforeCommands = append(beforeCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks["preDelete"]}, "Apply pre-delete manifest "+r.Hooks["preDelete"])) - if wait, cmds := r.shouldWaitForHook(r.Hooks["preDelete"], "pre-delete", ns); wait { + beforeCommands = append(beforeCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks["preDelete"].(string), flags.getKubeDryRunFlag()}, "Apply pre-delete manifest "+r.Hooks["preDelete"].(string))) + if wait, cmds := r.shouldWaitForHook(r.Hooks["preDelete"].(string), "pre-delete", ns); wait { beforeCommands = append(beforeCommands, cmds...) } } if _, ok := r.Hooks["postDelete"]; ok { - afterCommands = append(afterCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks["postDelete"]}, "Apply post-delete manifest "+r.Hooks["postDelete"])) - if wait, cmds := r.shouldWaitForHook(r.Hooks["postDelete"], "post-delete", ns); wait { + afterCommands = append(afterCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks["postDelete"].(string), flags.getKubeDryRunFlag()}, "Apply post-delete manifest "+r.Hooks["postDelete"].(string))) + if wait, cmds := r.shouldWaitForHook(r.Hooks["postDelete"].(string), "post-delete", ns); wait { afterCommands = append(afterCommands, cmds...) } } @@ -638,14 +634,15 @@ func (r *release) checkHooks(hookType string, p *plan, optionalNamespace ...stri // returns a boolean and the wait command if applicable func (r *release) shouldWaitForHook(hookFile string, hookType string, namespace string) (bool, []command) { var cmds []command - // if r.Hooks.SuccessCondition != "" { - if _, ok := r.Hooks["successCondition"]; ok { + if flags.dryRun { + return false, cmds + } else if _, ok := r.Hooks["successCondition"]; ok { timeoutFlag := "" if _, ok := r.Hooks["successTimeout"]; ok { - timeoutFlag = "--timeout=" + r.Hooks["successTimeout"] + timeoutFlag = "--timeout=" + r.Hooks["successTimeout"].(string) } - cmds = append(cmds, kubectl([]string{"wait", "-n", namespace, "-f", hookFile, "--for=condition=" + r.Hooks["successCondition"], timeoutFlag}, "Wait for "+hookType+" : "+hookFile)) - if _, ok := r.Hooks["deleteOnSuccess"]; ok { + cmds = append(cmds, kubectl([]string{"wait", "-n", namespace, "-f", hookFile, "--for=condition=" + r.Hooks["successCondition"].(string), timeoutFlag}, "Wait for "+hookType+" : "+hookFile)) + if _, ok := r.Hooks["deleteOnSuccess"]; ok && r.Hooks["deleteOnSuccess"].(bool) { cmds = append(cmds, kubectl([]string{"delete", "-n", namespace, "-f", hookFile}, "Delete "+hookType+" : "+hookFile)) } return true, cmds diff --git a/internal/app/release_test.go b/internal/app/release_test.go index 83d0292a..9c127f02 100644 --- a/internal/app/release_test.go +++ b/internal/app/release_test.go @@ -278,7 +278,7 @@ func Test_validateRelease(t *testing.T) { Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", - Hooks: map[string]string{"preInstall": "xyz.fake"}, + Hooks: map[string]interface{}{"preInstall": "xyz.fake"}, }, s: st, }, @@ -294,7 +294,7 @@ func Test_validateRelease(t *testing.T) { Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", - Hooks: map[string]string{"preInstall": "../../tests/values.xml"}, + Hooks: map[string]interface{}{"preInstall": "../../tests/values.xml"}, }, s: st, }, @@ -310,7 +310,7 @@ func Test_validateRelease(t *testing.T) { Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", - Hooks: map[string]string{"preDelete": "../../tests/values.xml"}, + Hooks: map[string]interface{}{"preDelete": "../../tests/values.yaml"}, }, s: st, }, @@ -326,7 +326,7 @@ func Test_validateRelease(t *testing.T) { Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", - Hooks: map[string]string{"postUpgrade": "https://raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml"}, + Hooks: map[string]interface{}{"postUpgrade": "https://raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml"}, }, s: st, }, @@ -342,11 +342,43 @@ func Test_validateRelease(t *testing.T) { Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", - Hooks: map[string]string{"preDelete": "https//raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml"}, + Hooks: map[string]interface{}{"preDelete": "https//raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml"}, }, s: st, }, want: "https//raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml must be valid URL path to a raw file.", + }, { + name: "test case 20 - invalid hook type 1", + args: args{ + r: &release{ + Name: "release20", + Description: "", + Namespace: "namespace", + Enabled: true, + Chart: "repo/chartX", + Version: "1.0", + ValuesFile: "../../tests/values.yaml", + Hooks: map[string]interface{}{"afterDelete": "https://raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml"}, + }, + s: st, + }, + want: "afterDelete is an Invalid hook type.", + }, { + name: "test case 21 - invalid hook type 2", + args: args{ + r: &release{ + Name: "release21", + Description: "", + Namespace: "namespace", + Enabled: true, + Chart: "repo/chartX", + Version: "1.0", + ValuesFile: "../../tests/values.yaml", + Hooks: map[string]interface{}{"PreDelete": "https://raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml"}, + }, + s: st, + }, + want: "PreDelete is an Invalid hook type.", }, } names := make(map[string]map[string]bool) @@ -368,9 +400,9 @@ func Test_inheritHooks(t *testing.T) { Metadata: make(map[string]string), Certificates: make(map[string]string), Settings: config{ - GlobalHooks: map[string]string{ - "preInstall": "https//raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml", - "postInstall": "https//raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml", + GlobalHooks: map[string]interface{}{ + "preInstall": "https://raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml", + "postInstall": "https://raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml", "successCondition": "Complete", "successTimeout": "60s", }, @@ -400,7 +432,7 @@ func Test_inheritHooks(t *testing.T) { Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", - Hooks: map[string]string{ + Hooks: map[string]interface{}{ "postInstall": "../../tests/values.yaml", "preDelete": "../../tests/values.yaml", "successTimeout": "360s", @@ -408,7 +440,7 @@ func Test_inheritHooks(t *testing.T) { }, s: st, }, - want: "https//raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml -- " + + want: "https://raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml -- " + "../../tests/values.yaml -- " + "../../tests/values.yaml -- " + "Complete -- 360s", @@ -417,8 +449,8 @@ func Test_inheritHooks(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.args.r.inheritHooks(&tt.args.s) - got := tt.args.r.Hooks["preInstall"] + " -- " + tt.args.r.Hooks["postInstall"] + " -- " + tt.args.r.Hooks["preDelete"] + - " -- " + tt.args.r.Hooks["successCondition"] + " -- " + tt.args.r.Hooks["successTimeout"] + got := tt.args.r.Hooks["preInstall"].(string) + " -- " + tt.args.r.Hooks["postInstall"].(string) + " -- " + tt.args.r.Hooks["preDelete"].(string) + + " -- " + tt.args.r.Hooks["successCondition"].(string) + " -- " + tt.args.r.Hooks["successTimeout"].(string) if got != tt.want { t.Errorf("inheritHooks() got = %v, want %v", got, tt.want) } diff --git a/internal/app/state.go b/internal/app/state.go index 3ec731ca..6dfc8713 100644 --- a/internal/app/state.go +++ b/internal/app/state.go @@ -11,20 +11,20 @@ import ( // config type represents the settings fields type config struct { - KubeContext string `yaml:"kubeContext"` - Username string `yaml:"username"` - Password string `yaml:"password"` - ClusterURI string `yaml:"clusterURI"` - ServiceAccount string `yaml:"serviceAccount"` - StorageBackend string `yaml:"storageBackend"` - SlackWebhook string `yaml:"slackWebhook"` - ReverseDelete bool `yaml:"reverseDelete"` - BearerToken bool `yaml:"bearerToken"` - BearerTokenPath string `yaml:"bearerTokenPath"` - EyamlEnabled bool `yaml:"eyamlEnabled"` - EyamlPrivateKeyPath string `yaml:"eyamlPrivateKeyPath"` - EyamlPublicKeyPath string `yaml:"eyamlPublicKeyPath"` - GlobalHooks map[string]string `yaml:"globalHooks"` + KubeContext string `yaml:"kubeContext"` + Username string `yaml:"username"` + Password string `yaml:"password"` + ClusterURI string `yaml:"clusterURI"` + ServiceAccount string `yaml:"serviceAccount"` + StorageBackend string `yaml:"storageBackend"` + SlackWebhook string `yaml:"slackWebhook"` + ReverseDelete bool `yaml:"reverseDelete"` + BearerToken bool `yaml:"bearerToken"` + BearerTokenPath string `yaml:"bearerTokenPath"` + EyamlEnabled bool `yaml:"eyamlEnabled"` + EyamlPrivateKeyPath string `yaml:"eyamlPrivateKeyPath"` + EyamlPublicKeyPath string `yaml:"eyamlPublicKeyPath"` + GlobalHooks map[string]interface{} `yaml:"globalHooks"` } // state type represents the desired state of applications on a k8s cluster. @@ -76,8 +76,7 @@ func (s *state) validate() error { } // settings - // if (s.Settings == (config{}) || s.Settings.KubeContext == "") && !getKubeContext() { - // use reflect.DeepEqual to compare Settings are empty, since it now contains a map + // use reflect.DeepEqual to compare Settings are empty, since it contains a map if (reflect.DeepEqual(s.Settings, config{}) || s.Settings.KubeContext == "") && !getKubeContext() { return errors.New("settings validation failed -- you have not defined a " + "kubeContext to use. Either define it in the desired state file or pass a kubeconfig with --kubeconfig to use an existing context") diff --git a/internal/app/utils.go b/internal/app/utils.go index 44fdb92f..e2845c8f 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -150,13 +150,17 @@ func substituteVarsInStaticFiles(s *state) { if len(v.Hooks) != 0 { for key, val := range v.Hooks { - v.Hooks[key] = substituteVarsInYaml(val) + if key != "deleteOnSuccess" && key != "successTimeout" && key != "successCondition" { + v.Hooks[key] = substituteVarsInYaml(val.(string)) + } } } if len(s.Settings.GlobalHooks) != 0 { for key, val := range s.Settings.GlobalHooks { - s.Settings.GlobalHooks[key] = substituteVarsInYaml(val) + if key != "deleteOnSuccess" && key != "successTimeout" && key != "successCondition" { + s.Settings.GlobalHooks[key] = substituteVarsInYaml(val.(string)) + } } } @@ -220,7 +224,9 @@ func resolvePaths(relativeToFile string, s *state) { if len(v.Hooks) != 0 { for key, val := range v.Hooks { - v.Hooks[key], _ = resolveOnePath(val, dir, downloadDest) + if key != "deleteOnSuccess" && key != "successTimeout" && key != "successCondition" { + v.Hooks[key], _ = resolveOnePath(val.(string), dir, downloadDest) + } } } @@ -255,7 +261,9 @@ func resolvePaths(relativeToFile string, s *state) { // resolve paths for global hooks if len(s.Settings.GlobalHooks) != 0 { for key, val := range s.Settings.GlobalHooks { - s.Settings.GlobalHooks[key], _ = resolveOnePath(val, dir, downloadDest) + if key != "deleteOnSuccess" && key != "successTimeout" && key != "successCondition" { + s.Settings.GlobalHooks[key], _ = resolveOnePath(val.(string), dir, downloadDest) + } } } // resolving paths for k8s certificate files From 041d5e836a15f44e4ad6a6bc13bdf7556ab62c9c Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Fri, 8 May 2020 18:42:42 +0200 Subject: [PATCH 0639/1127] uncomment namespace config --- examples/example.toml | 26 +++++++++++++------------- examples/example.yaml | 24 ++++++++++++------------ 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/examples/example.toml b/examples/example.toml index d5a167af..20f21a16 100644 --- a/examples/example.toml +++ b/examples/example.toml @@ -46,19 +46,19 @@ context= "test-infra" # defaults to "default" if not provided # protected = -- default to false [namespaces] [namespaces.production] - # protected = true - # [[namespaces.production.limits]] - # type = "Container" - # [namespaces.production.limits.default] - # cpu = "300m" - # memory = "200Mi" - # [namespaces.production.limits.defaultRequest] - # cpu = "200m" - # memory = "100Mi" - # [[namespaces.production.limits]] - # type = "Pod" - # [namespaces.production.limits.max] - # memory = "300Mi" + protected = true + [[namespaces.production.limits]] + type = "Container" + [namespaces.production.limits.default] + cpu = "300m" + memory = "200Mi" + [namespaces.production.limits.defaultRequest] + cpu = "200m" + memory = "100Mi" + [[namespaces.production.limits]] + type = "Pod" + [namespaces.production.limits.max] + memory = "300Mi" [namespaces.staging] # protected = false # [namespaces.staging.labels] diff --git a/examples/example.yaml b/examples/example.yaml index 80c78d12..a1a1261b 100644 --- a/examples/example.yaml +++ b/examples/example.yaml @@ -38,18 +38,18 @@ settings: # define your environments and their k8s namespaces namespaces: production: - # protected: true - # limits: - # - type: Container - # default: - # cpu: "300m" - # memory: "200Mi" - # defaultRequest: - # cpu: "200m" - # memory: "100Mi" - # - type: Pod - # max: - # memory: "300Mi" + protected: true + limits: + - type: Container + default: + cpu: "300m" + memory: "200Mi" + defaultRequest: + cpu: "200m" + memory: "100Mi" + - type: Pod + max: + memory: "300Mi" staging: # protected: false # labels: From 93be432ad58d500183d4cdbbbcf65126c7f214f3 Mon Sep 17 00:00:00 2001 From: Xtigyro Date: Sun, 10 May 2020 19:44:32 +0300 Subject: [PATCH 0640/1127] small fixes for dry-ed code example --- docs/how_to/README.md | 1 + examples/appsTemplates/config/helmsman.yaml | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/how_to/README.md b/docs/how_to/README.md index 1f24bfbf..d2b73326 100644 --- a/docs/how_to/README.md +++ b/docs/how_to/README.md @@ -51,3 +51,4 @@ It is recommended that you also check the [DSF spec](../desired_state_specificat - [Limit Helmsman deployment to specific apps](misc/limit-deployment-to-specific-apps.md) - [Limit Helmsman deployment to specific group of apps](misc/limit-deployment-to-specific-group-of-apps.md) - [Use hiera-eyaml as secrets encryption backend](settings/use-hiera-eyaml-as-secrets-encryption.md) + - [Use DRY-ed code](misc/use-dry-code.md) diff --git a/examples/appsTemplates/config/helmsman.yaml b/examples/appsTemplates/config/helmsman.yaml index 8ac2d03b..ef9f6198 100644 --- a/examples/appsTemplates/config/helmsman.yaml +++ b/examples/appsTemplates/config/helmsman.yaml @@ -38,6 +38,7 @@ namespaces: helmRepos: stable: "https://kubernetes-charts.storage.googleapis.com" + puppet: "https://puppetlabs.github.io/puppetserver-helm-chart" appsTemplates: common: &common @@ -56,8 +57,8 @@ appsTemplates: puppetserver: &puppetserver enabled: true priority: -1 - chart: "../../../../puppetserver-helm-chart-1.8.2.tgz" # https://github.com/puppetlabs/pupperware/tree/master/k8s - version: "1.8.2" # chart version + chart: "puppet/puppetserver-helm-chart" + version: "3.0.2" # chart version valuesFiles: [ "../apps/puppetserver/common-values.yaml", ] From d8b592995e14a8dcb231ee885ca5b1ee281594e2 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Mon, 11 May 2020 12:38:56 +0200 Subject: [PATCH 0641/1127] fix inconsistent helm args between install and upgrade. fixes #451 --- docs/desired_state_specification.md | 2 +- internal/app/release.go | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 47381df4..4c56f5fb 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -368,7 +368,7 @@ Options: - **set** : is used to override certain values from values.yaml with values from environment variables (or ,starting from v1.3.0-rc, directly provided in the Desired State File). This is particularly useful for passing secrets to charts. If the an environment variable with the same name as the provided value exists, the environment variable value will be used, otherwise, the provided value will be used as is. The TOML stanza for this is `[apps..set]` - **setString** : is used to override String values from values.yaml or chart's defaults. This uses the `--set-string` flag in helm which is available only in helm >v2.9.0. This option is useful for image tags and the like. The TOML stanza for this is `[apps..setString]` - **setFile** : is used to override values from values.yaml or chart's defaults from provided file. This uses the `--set-file` flag in helm. This option is useful for embedding file contents in the values. The TOML stanza for this is `[apps..setFile]` -- **helmFlags** : array of `helm` flags, is used to pass flags to helm install/upgrade commands +- **helmFlags** : array of `helm` upgrade flags, is used to pass flags to helm install/upgrade commands. **These flags are not passed to helm diff**. For setting values, use **set**, **setString** or **setFile** instead. - **hooks** : defines global lifecycle hooks to apply yaml manifest before and/or after different helmsman operations. Check [here](how_to/apps/lifecycle_hooks.md) for more details. Unset hooks for a release are inherited from `globalHooks` in the [settings](#Settings) stanza. Example: diff --git a/internal/app/release.go b/internal/app/release.go index 6212c1bd..04da9647 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -324,7 +324,7 @@ func (r *release) diff() string { diffContextFlag = []string{"--context", strconv.Itoa(flags.diffContext)} } - cmd := helmCmd(concat([]string{"diff", colorFlag, suppressDiffSecretsFlag}, diffContextFlag, r.getHelmArgsFor("upgrade")), "Diffing release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") + cmd := helmCmd(concat([]string{"diff", colorFlag, suppressDiffSecretsFlag}, diffContextFlag, r.getHelmArgsFor("diff")), "Diffing release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") result := cmd.exec() if result.code != 0 { @@ -541,10 +541,10 @@ func (r *release) getHelmArgsFor(action string, optionalNamespaceOverride ...str ns = optionalNamespaceOverride[0] } switch action { - case "install": - return concat([]string{action, r.Name, r.Chart, "--version", r.Version, "--namespace", r.Namespace}, r.getValuesFiles(), r.getSetValues(), r.getSetStringValues(), r.getSetFileValues(), r.getWait(), r.getHelmFlags()) - case "upgrade": - return concat([]string{action, "--namespace", r.Namespace, r.Name, r.Chart}, r.getValuesFiles(), []string{"--version", r.Version}, r.getSetValues(), r.getSetStringValues(), r.getSetFileValues()) + case "install", "upgrade": + return concat([]string{"upgrade", r.Name, r.Chart, "--install", "--version", r.Version, "--namespace", r.Namespace}, r.getValuesFiles(), r.getSetValues(), r.getSetStringValues(), r.getSetFileValues(), r.getWait(), r.getHelmFlags()) + case "diff": + return concat([]string{"upgrade", r.Name, r.Chart, "--version", r.Version, "--namespace", r.Namespace}, r.getValuesFiles(), r.getSetValues(), r.getSetStringValues(), r.getSetFileValues()) default: return []string{action, "--namespace", ns, r.Name} } From 13f6a0cfecb780b4e283bbb348f86fa61a1bf6e5 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Mon, 11 May 2020 13:23:37 +0200 Subject: [PATCH 0642/1127] allow absolute paths for values/secrets/etc files. Fixes #437 --- docs/desired_state_specification.md | 8 ++++---- docs/how_to/apps/multiple_values_files.md | 2 ++ internal/app/utils.go | 7 +++++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 4c56f5fb..5e11a93d 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -352,11 +352,11 @@ Options: **Optional** - **group** : group name this apps belongs to. It has no effect until Helmsman's flag `-group` is passed. Check this [doc](how_to/misc/limit-deployment-to-specific-group-of-apps.md) for more details. - **description** : a release metadata for human readers. -- **valuesFile** : a valid path (URL, cloud bucket, local file path) to custom Helm values.yaml file. File extension must be `yaml`. Cannot be used with valuesFiles together. Leaving it empty uses the default chart values. -- **valuesFiles** : array of valid paths (URL, cloud bucket, local file path) to custom Helm values.yaml file. File extension must be `yaml`. Cannot be used with valuesFile together. Leaving it empty uses the default chart values. +- **valuesFile** : a valid path (URL, cloud bucket, local absolute/relative file path) to custom Helm values.yaml file. File extension must be `yaml`. Cannot be used with valuesFiles together. Leaving it empty uses the default chart values. +- **valuesFiles** : array of valid paths (URL, cloud bucket, local absolute/relative file path) to custom Helm values.yaml file. File extension must be `yaml`. Cannot be used with valuesFile together. Leaving it empty uses the default chart values. > The values file(s) path is resolved when the DSF yaml/toml file is loaded, relative to the path that the dsf was loaded from. -- **secretsFile** : a valid path (URL, cloud bucket, local file path) to custom Helm secrets.yaml file. File extension must be `yaml`. Cannot be used with secretsFiles together. Leaving it empty uses the default chart secrets. -- **secretsFiles** : array of valid paths (URL, cloud bucket, local file path) to custom Helm secrets.yaml file. File extension must be `yaml`. Cannot be used with secretsFile together. Leaving it empty uses the default chart secrets. +- **secretsFile** : a valid path (URL, cloud bucket, local absolute/relative file path) to custom Helm secrets.yaml file. File extension must be `yaml`. Cannot be used with secretsFiles together. Leaving it empty uses the default chart secrets. +- **secretsFiles** : array of valid paths (URL, cloud bucket, local absolute/relative file path) to custom Helm secrets.yaml file. File extension must be `yaml`. Cannot be used with secretsFile together. Leaving it empty uses the default chart secrets. > The secrets file(s) path is resolved when the DSF yaml/toml file is loaded, relative to the path that the dsf was loaded from. > To use the secrets files you must have the helm-secrets plugin - **test** : defines whether to run the chart tests whenever the release is installed. Default is false. diff --git a/docs/how_to/apps/multiple_values_files.md b/docs/how_to/apps/multiple_values_files.md index 5c9f63b0..d821d2e6 100644 --- a/docs/how_to/apps/multiple_values_files.md +++ b/docs/how_to/apps/multiple_values_files.md @@ -6,6 +6,8 @@ version: v3.3.0-beta1 You can include multiple yaml value files to separate configuration for different environments. +> file paths can be a URL (e.g. to a public git repo) , cloud bucket, local absolute/relative file path. + ```toml ... [apps] diff --git a/internal/app/utils.go b/internal/app/utils.go index e2845c8f..8ec4663c 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -383,8 +383,11 @@ func downloadFile(file string, dir string, outfile string) string { } else { - log.Info("" + outfile + " will be used from local file system.") - toCopy, _ := filepath.Abs(filepath.Join(dir, file)) + log.Info("" + file + " will be used from local file system.") + toCopy := file + if !filepath.IsAbs(file) { + toCopy, _ = filepath.Abs(filepath.Join(dir, file)) + } copyFile(toCopy, outfile) } return outfile From b942c7a0b047d74e12b7351d62f4a859302e2aee Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Mon, 11 May 2020 15:06:40 +0200 Subject: [PATCH 0643/1127] add global and per-release max-history options. fixes #443 --- docs/desired_state_specification.md | 4 +++ examples/example.toml | 11 +++++-- examples/example.yaml | 48 +++++++++++++++++++---------- internal/app/cli.go | 1 + internal/app/release.go | 36 +++++++++++++++++----- internal/app/state.go | 1 + 6 files changed, 73 insertions(+), 28 deletions(-) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 5e11a93d..96e61f47 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -113,6 +113,7 @@ The following options can be skipped if your kubectl context is already created - **eyamlPrivateKeyPath** : if set with path to the eyaml private key file, it will use it instead of looking for default one in ./keys directory relative to where Helmsman were run. It needs to be defined in conjunction with eyamlPublicKeyPath. - **eyamlPublicKeyPath** : if set with path to the eyaml public key file, it will use it instead of looking for default one in ./keys directory relative to where Helmsman were run. It needs to be defined in conjunction with eyamlPrivateKeyPath. - **globalHooks** : defines global lifecycle hooks to apply yaml manifest before and/or after different helmsman operations. Check [here](how_to/apps/lifecycle_hooks.md) for more details. +- **globalMaxHistory** : defines the **global** maximum number of helm revisions state (secrets/configmap) to keep. Releases can override this global value by setting `maxHistory`. If both are not set or are set to `0`, it is defaulted to 10. Example: @@ -370,6 +371,7 @@ Options: - **setFile** : is used to override values from values.yaml or chart's defaults from provided file. This uses the `--set-file` flag in helm. This option is useful for embedding file contents in the values. The TOML stanza for this is `[apps..setFile]` - **helmFlags** : array of `helm` upgrade flags, is used to pass flags to helm install/upgrade commands. **These flags are not passed to helm diff**. For setting values, use **set**, **setString** or **setFile** instead. - **hooks** : defines global lifecycle hooks to apply yaml manifest before and/or after different helmsman operations. Check [here](how_to/apps/lifecycle_hooks.md) for more details. Unset hooks for a release are inherited from `globalHooks` in the [settings](#Settings) stanza. +- **maxHistory** : defines the maximum number of helm revisions state (secrets/configmap) to keep. If unset, it will inherit the value of `settings.globalMaxHistory`, if that's also unset, it defaults to 10. Example: @@ -388,6 +390,7 @@ Example: version = "0.9.0" valuesFile = "" test = true + maxHistory = 4 protected = false wait = true priority = -3 @@ -420,6 +423,7 @@ apps: version: "0.9.0" valuesFile: "" test: true + maxHistory: 4 protected: false wait: true priority: -3 diff --git a/examples/example.toml b/examples/example.toml index 690c2f13..a8326aef 100644 --- a/examples/example.toml +++ b/examples/example.toml @@ -36,6 +36,7 @@ context= "test-infra" # defaults to "default" if not provided # successCondition= "Initialized" # deleteOnSuccess= true # postInstall= "job.yaml" + globalMaxHistory= 5 @@ -116,8 +117,8 @@ context= "test-infra" # defaults to "default" if not provided # [apps.argo.setString] # values to override values from values.yaml with values from env vars or directly entered-- useful for passing secrets to charts # AdminPassword="$SOME_PASSWORD" # $SOME_PASSWORD must exist in the environment # MyLongIntVar="1234567890" - # [apps.argo.set] - # installCRD="true" + [apps.argo.setString] + "images.tag"="v2.7.5" [apps.artifactory] @@ -131,6 +132,10 @@ context= "test-infra" # defaults to "default" if not provided priority= -2 noHooks= false timeout= 300 - helmFlags= [] # additional helm flags for this release + maxHistory = 4 + # additional helm flags for this release + helmFlags= [ + "--devel", + ] # See https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md#apps for more apps options diff --git a/examples/example.yaml b/examples/example.yaml index a1a1261b..ab249b19 100644 --- a/examples/example.yaml +++ b/examples/example.yaml @@ -1,4 +1,10 @@ # version: v3.0.0 + +# context defines the context of this Desired State File. +# It is used to allow Helmsman identify which releases are managed by which DSF. +# Therefore, it is important that each DSF uses a unique context. +context: test-infra # defaults to "default" if not provided + # metadata -- add as many key/value pairs as you want metadata: org: "example.com/$ORG_PATH/" @@ -13,18 +19,13 @@ metadata: #caCrt: "s3://mybucket/ca.crt" # S3 bucket path #caKey: "../ca.key" # valid local file relative path -# context defines the context of this Desired State File. -# It is used to allow Helmsman identify which releases are managed by which DSF. -# Therefore, it is important that each DSF uses a unique context. -context: test-infra # defaults to "default" if not provided - settings: kubeContext: "minikube" # will try connect to this context first, if it does not exist, it will be created using the details below #username: "admin" #password: "$K8S_PASSWORD" # the name of an environment variable containing the k8s password #clusterURI: "$SET_URI" # the name of an environment variable containing the cluster API #clusterURI: "https://192.168.99.100:8443" # equivalent to the above - storageBackend: "secret" + #storageBackend: "secret" #slackWebhook: "$slack" # or your slack webhook url #reverseDelete: false # reverse the priorities on delete #### to use bearer token: @@ -34,6 +35,7 @@ settings: # successCondition: "Initialized" # deleteOnSuccess: true # postInstall: "job.yaml" + globalMaxHistory: 5 # define your environments and their k8s namespaces namespaces: @@ -51,9 +53,19 @@ namespaces: max: memory: "300Mi" staging: - # protected: false - # labels: - # env: "staging" + protected: false + labels: + env: "staging" + quotas: + limits.cpu: "10" + limits.memory: "20Gi" + pods: 25 + requests.cpu: "10" + requests.memory: "30Gi" + customQuotas: + - name: "requests.nvidia.com/gpu" + value: "2" + # define any private/public helm charts repos you would like to get charts from @@ -72,23 +84,22 @@ helmRepos: # each contains the following: apps: - argo: namespace: "staging" # maps to the namespace as defined in namespaces above enabled: true # change to false if you want to delete this app release empty: false: chart: "argo/argo" # changing the chart name means delete and recreate this chart - version: "0.6.5" # chart version + version: "0.8.5" # chart version ### Optional values below valuesFile: "" # leaving it empty uses the default chart values test: false protected: true priority: -3 wait: true - # hooks: - # successCondition: "Complete" - # successTimeout: "90s" - # deleteOnSuccess: true - # preInstall: "job.yaml" + hooks: + successCondition: "Complete" + successTimeout: "90s" + deleteOnSuccess: true + preInstall: "job.yaml" # preInstall: "https://github.com/jetstack/cert-manager/releases/download/v0.14.0/cert-manager.crds.yaml" # postInstall: "https://raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml" # postInstall: "job.yaml" @@ -108,6 +119,9 @@ apps: priority: -2 noHooks: false timeout: 300 - helmFlags: [] # additional helm flags for this release + maxHistory: 4 + # additional helm flags for this release + helmFlags: + - "--devel" # See https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md#apps for more apps options diff --git a/internal/app/cli.go b/internal/app/cli.go index 4d34a8b1..92d2de6e 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -289,6 +289,7 @@ func (c *cli) readState(s *state) { // inherit globalHooks if local ones are not set for _, r := range s.Apps { r.inheritHooks(s) + r.inheritMaxHistory(s) } if c.debug { diff --git a/internal/app/release.go b/internal/app/release.go index 04da9647..c1434633 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -36,6 +36,7 @@ type release struct { NoHooks bool `yaml:"noHooks"` Timeout int `yaml:"timeout"` Hooks map[string]interface{} `yaml:"hooks"` + MaxHistory int `yaml:"maxHistory"` } type chartVersion struct { @@ -307,7 +308,7 @@ func (r *release) uninstall(p *plan, optionalNamespace ...string) { before, after := r.checkHooks("delete", p, ns) - cmd := helmCmd(concat(r.getHelmArgsFor("uninstall", ns), flags.getDryRunFlags()), "Delete release [ "+r.Name+" ] in namespace [ "+ns+" ]") + cmd := helmCmd(r.getHelmArgsFor("uninstall", ns), "Delete release [ "+r.Name+" ] in namespace [ "+ns+" ]") p.addCommand(cmd, priority, r, before, after) } @@ -340,14 +341,10 @@ func (r *release) diff() string { // upgradeRelease upgrades an existing release with the specified values.yaml func (r *release) upgrade(p *plan) { - var force string - if flags.forceUpgrades { - force = "--force" - } before, after := r.checkHooks("upgrade", p) - cmd := helmCmd(concat(r.getHelmArgsFor("upgrade"), []string{force}, r.getWait(), r.getHelmFlags()), "Upgrade release [ "+r.Name+" ] to version [ "+r.Version+" ] in namespace [ "+r.Namespace+" ]") + cmd := helmCmd(r.getHelmArgsFor("upgrade"), "Upgrade release [ "+r.Name+" ] to version [ "+r.Version+" ] in namespace [ "+r.Namespace+" ]") p.addCommand(cmd, r.Priority, r, before, after) @@ -526,12 +523,24 @@ func (r *release) getDesiredNamespace() string { return r.Namespace } +// getMaxHistory returns the max-history flag for upgrade commands +func (r *release) getMaxHistory() []string { + if r.MaxHistory != 0 { + return []string{"--history-max", strconv.Itoa(r.MaxHistory)} + } + return []string{} +} + // getHelmFlags returns helm flags func (r *release) getHelmFlags() []string { var flgs []string + var force string + if flags.forceUpgrades { + force = "--force" + } flgs = append(flgs, r.HelmFlags...) - return concat(r.getNoHooks(), r.getTimeout(), flags.getDryRunFlags(), flgs) + return concat(r.getNoHooks(), r.getWait(), r.getTimeout(), r.getMaxHistory(), flags.getDryRunFlags(), []string{force}, flgs) } // getHelmArgsFor returns helm arguments for a specific helm operation @@ -542,9 +551,11 @@ func (r *release) getHelmArgsFor(action string, optionalNamespaceOverride ...str } switch action { case "install", "upgrade": - return concat([]string{"upgrade", r.Name, r.Chart, "--install", "--version", r.Version, "--namespace", r.Namespace}, r.getValuesFiles(), r.getSetValues(), r.getSetStringValues(), r.getSetFileValues(), r.getWait(), r.getHelmFlags()) + return concat([]string{"upgrade", r.Name, r.Chart, "--install", "--version", r.Version, "--namespace", r.Namespace}, r.getValuesFiles(), r.getSetValues(), r.getSetStringValues(), r.getSetFileValues(), r.getHelmFlags()) case "diff": return concat([]string{"upgrade", r.Name, r.Chart, "--version", r.Version, "--namespace", r.Namespace}, r.getValuesFiles(), r.getSetValues(), r.getSetStringValues(), r.getSetFileValues()) + case "uninstall": + return concat([]string{action, "--namespace", ns, r.Name}, flags.getDryRunFlags()) default: return []string{action, "--namespace", ns, r.Name} } @@ -580,6 +591,15 @@ func (r *release) inheritHooks(s *state) { } } +// inheritMaxHistory passes global max history from the state to the release if it is unset +func (r *release) inheritMaxHistory(s *state) { + if s.Settings.GlobalMaxHistory != 0 { + if r.MaxHistory == 0 { + r.MaxHistory = s.Settings.GlobalMaxHistory + } + } +} + // checkHooks checks if a hook of certain type exists and creates its command // if success condition for the hook is defined, a "kubectl wait" command is created // returns two slices of before and after commands diff --git a/internal/app/state.go b/internal/app/state.go index a38eea10..9cdf6690 100644 --- a/internal/app/state.go +++ b/internal/app/state.go @@ -25,6 +25,7 @@ type config struct { EyamlPrivateKeyPath string `yaml:"eyamlPrivateKeyPath"` EyamlPublicKeyPath string `yaml:"eyamlPublicKeyPath"` GlobalHooks map[string]interface{} `yaml:"globalHooks"` + GlobalMaxHistory int `yaml:"globalMaxHistory"` } // state type represents the desired state of applications on a k8s cluster. From c80e79994932a0abb69a3b3036a17d9e62d20fae Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Mon, 11 May 2020 15:08:39 +0200 Subject: [PATCH 0644/1127] add globalMaxHistory example --- docs/desired_state_specification.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 96e61f47..c969b3e4 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -134,7 +134,8 @@ kubeContext = "minikube" # [settings.globalHooks] # successCondition= "Complete" # deleteOnSuccess= true -# postInstall= "job.yaml" +# postInstall= "job.yaml" +globalMaxHistory= 10 ``` ```yaml @@ -153,7 +154,8 @@ settings: # globalHooks: # successCondition: "Complete" # deleteOnSuccess: true - # preInstall: "job.yaml" + # preInstall: "job.yaml" + globalMaxHistory: 10 ``` ## Namespaces From dd10a9c33154a3341f22e6032d6dd97f00d70a0b Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Mon, 11 May 2020 19:15:24 +0200 Subject: [PATCH 0645/1127] respect dry-run flag with kubectl commands. fixes #424 --- internal/app/kube_helpers.go | 24 ++++++++++++++++-------- internal/app/release.go | 12 ++++++------ 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/internal/app/kube_helpers.go b/internal/app/kube_helpers.go index 216395fe..1b827571 100644 --- a/internal/app/kube_helpers.go +++ b/internal/app/kube_helpers.go @@ -29,8 +29,10 @@ func addNamespaces(s *state) { createNamespace(name) labelNamespace(name, cfg.Labels) annotateNamespace(name, cfg.Annotations) - setLimits(name, cfg.Limits) - setQuotas(name, cfg.Quotas) + if !flags.dryRun { + setLimits(name, cfg.Limits) + setQuotas(name, cfg.Quotas) + } }(nsName, ns, &wg) } wg.Wait() @@ -53,6 +55,7 @@ func createNamespace(ns string) { log.Verbose("Namespace [ " + ns + " ] exists") return } + cmd := kubectl([]string{"create", "namespace", ns}, "Creating namespace [ "+ns+" ]") result := cmd.exec() if result.code == 0 { @@ -73,7 +76,7 @@ func labelNamespace(ns string, labels map[string]string) { labelSlice = append(labelSlice, k+"="+v) } - args := []string{"label", "--overwrite", "namespace/" + ns} + args := []string{"label", "--overwrite", "namespace/" + ns, flags.getKubeDryRunFlag("label")} args = append(args, labelSlice...) cmd := kubectl(args, "Labeling namespace [ "+ns+" ]") @@ -94,7 +97,7 @@ func annotateNamespace(ns string, annotations map[string]string) { for k, v := range annotations { annotationSlice = append(annotationSlice, k+"="+v) } - args := []string{"annotate", "--overwrite", "namespace/" + ns} + args := []string{"annotate", "--overwrite", "namespace/" + ns, flags.getKubeDryRunFlag("annotate")} args = append(args, annotationSlice...) cmd := kubectl(args, "Annotating namespace [ "+ns+" ]") @@ -131,7 +134,7 @@ spec: log.Fatal(err.Error()) } - cmd := kubectl([]string{"apply", "-f", targetFile, "-n", ns}, "Creating LimitRange in namespace [ "+ns+" ]") + cmd := kubectl([]string{"apply", "-f", targetFile, "-n", ns, flags.getKubeDryRunFlag("apply")}, "Creating LimitRange in namespace [ "+ns+" ]") result := cmd.exec() if result.code != 0 { @@ -175,7 +178,7 @@ spec: log.Fatal(err.Error()) } - cmd := kubectl([]string{"apply", "-f", "temp-ResourceQuota.yaml", "-n", ns}, "Creating ResourceQuota in namespace [ "+ns+" ]") + cmd := kubectl([]string{"apply", "-f", "temp-ResourceQuota.yaml", "-n", ns, flags.getKubeDryRunFlag("apply")}, "Creating ResourceQuota in namespace [ "+ns+" ]") result := cmd.exec() deleteFile("temp-ResourceQuota.yaml") @@ -339,9 +342,14 @@ func getKubectlClientVersion() string { } // getKubeDryRunFlag returns kubectl dry-run flag if helmsman --dry-run flag is enabled -func (c *cli) getKubeDryRunFlag() string { +func (c *cli) getKubeDryRunFlag(action string) string { if c.dryRun { - return "--server-dry-run" + switch action { + case "apply": + return "--server-dry-run" + default: + return "--dry-run" + } } return "" } diff --git a/internal/app/release.go b/internal/app/release.go index c1434633..d5a2ebe6 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -614,13 +614,13 @@ func (r *release) checkHooks(hookType string, p *plan, optionalNamespace ...stri case "install": { if _, ok := r.Hooks["preInstall"]; ok { - beforeCommands = append(beforeCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks["preInstall"].(string), flags.getKubeDryRunFlag()}, "Apply pre-install manifest "+r.Hooks["preInstall"].(string))) + beforeCommands = append(beforeCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks["preInstall"].(string), flags.getKubeDryRunFlag("apply")}, "Apply pre-install manifest "+r.Hooks["preInstall"].(string))) if wait, cmds := r.shouldWaitForHook(r.Hooks["preInstall"].(string), "pre-install", ns); wait { beforeCommands = append(beforeCommands, cmds...) } } if _, ok := r.Hooks["postInstall"]; ok { - afterCommands = append(afterCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks["postInstall"].(string), flags.getKubeDryRunFlag()}, "Apply post-install manifest "+r.Hooks["postInstall"].(string))) + afterCommands = append(afterCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks["postInstall"].(string), flags.getKubeDryRunFlag("apply")}, "Apply post-install manifest "+r.Hooks["postInstall"].(string))) if wait, cmds := r.shouldWaitForHook(r.Hooks["postInstall"].(string), "post-install", ns); wait { afterCommands = append(afterCommands, cmds...) } @@ -629,13 +629,13 @@ func (r *release) checkHooks(hookType string, p *plan, optionalNamespace ...stri case "upgrade": { if _, ok := r.Hooks["preUpgrade"]; ok { - beforeCommands = append(beforeCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks["preUpgrade"].(string), flags.getKubeDryRunFlag()}, "Apply pre-upgrade manifest "+r.Hooks["preUpgrade"].(string))) + beforeCommands = append(beforeCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks["preUpgrade"].(string), flags.getKubeDryRunFlag("apply")}, "Apply pre-upgrade manifest "+r.Hooks["preUpgrade"].(string))) if wait, cmds := r.shouldWaitForHook(r.Hooks["preUpgrade"].(string), "pre-upgrade", ns); wait { beforeCommands = append(beforeCommands, cmds...) } } if _, ok := r.Hooks["postUpgrade"]; ok { - afterCommands = append(afterCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks["postUpgrade"].(string), flags.getKubeDryRunFlag()}, "Apply post-upgrade manifest "+r.Hooks["postUpgrade"].(string))) + afterCommands = append(afterCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks["postUpgrade"].(string), flags.getKubeDryRunFlag("apply")}, "Apply post-upgrade manifest "+r.Hooks["postUpgrade"].(string))) if wait, cmds := r.shouldWaitForHook(r.Hooks["postUpgrade"].(string), "post-upgrade", ns); wait { afterCommands = append(afterCommands, cmds...) } @@ -644,13 +644,13 @@ func (r *release) checkHooks(hookType string, p *plan, optionalNamespace ...stri case "delete": { if _, ok := r.Hooks["preDelete"]; ok { - beforeCommands = append(beforeCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks["preDelete"].(string), flags.getKubeDryRunFlag()}, "Apply pre-delete manifest "+r.Hooks["preDelete"].(string))) + beforeCommands = append(beforeCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks["preDelete"].(string), flags.getKubeDryRunFlag("apply")}, "Apply pre-delete manifest "+r.Hooks["preDelete"].(string))) if wait, cmds := r.shouldWaitForHook(r.Hooks["preDelete"].(string), "pre-delete", ns); wait { beforeCommands = append(beforeCommands, cmds...) } } if _, ok := r.Hooks["postDelete"]; ok { - afterCommands = append(afterCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks["postDelete"].(string), flags.getKubeDryRunFlag()}, "Apply post-delete manifest "+r.Hooks["postDelete"].(string))) + afterCommands = append(afterCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks["postDelete"].(string), flags.getKubeDryRunFlag("apply")}, "Apply post-delete manifest "+r.Hooks["postDelete"].(string))) if wait, cmds := r.shouldWaitForHook(r.Hooks["postDelete"].(string), "post-delete", ns); wait { afterCommands = append(afterCommands, cmds...) } From 9d80057912586f775b1504e42147f9f4a7c5170c Mon Sep 17 00:00:00 2001 From: Fareed Dudhia Date: Mon, 27 Apr 2020 12:43:08 +0100 Subject: [PATCH 0646/1127] reduce number of helm commands for validation to number of chart/version pairs to validate --- internal/app/release.go | 102 +++++++++++++++++++++++----------------- 1 file changed, 58 insertions(+), 44 deletions(-) diff --git a/internal/app/release.go b/internal/app/release.go index d5a2ebe6..8f411db7 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -173,17 +173,39 @@ func validateReleaseCharts(s *state) error { apps = s.Apps } c := make(chan string, len(apps)) + + charts := make(map[string]map[string][]string) for app, r := range apps { - sem <- struct{}{} - wg.Add(1) - go func(r *release, app string) { - defer func() { - wg.Done() - <-sem - }() - r.validateChart(app, s, c) - }(r, app) + if charts[r.Chart] == nil { + charts[r.Chart] = make(map[string][]string) + } + + if charts[r.Chart][r.Version] == nil { + charts[r.Chart][r.Version] = make([]string, 0) + } + + if r.isConsideredToRun(s) { + charts[r.Chart][r.Version] = append(charts[r.Chart][r.Version], app) + } } + + for chart, versions := range charts { + for version, apps := range versions { + concattedApps := strings.Join(apps, ", ") + ch := chart + v := version + sem <- struct{}{} + wg.Add(1) + go func(apps, chart, version string) { + defer func() { + wg.Done() + <-sem + }() + validateChart(concattedApps, chart, version, s, c) + }(concattedApps, ch, v) + } + } + wg.Wait() close(c) for err := range c { @@ -201,45 +223,37 @@ func validateReleaseCharts(s *state) error { var versionExtractor = regexp.MustCompile(`[\n]version:\s?(.*)`) // validateChart validates if chart with the same name and version as specified in the DSF exists -func (r *release) validateChart(app string, s *state, c chan string) { +func validateChart(apps, chart, version string, s *state, c chan string) { + if isLocalChart(chart) { + cmd := helmCmd([]string{"inspect", "chart", chart}, "Validating [ "+chart+" ] chart's availability") - validateCurrentChart := true - if !r.isConsideredToRun(s) { - validateCurrentChart = false - } - if validateCurrentChart { - if isLocalChart(r.Chart) { - cmd := helmCmd([]string{"inspect", "chart", r.Chart}, "Validating [ "+r.Chart+" ] chart's availability") - - result := cmd.exec() - if result.code != 0 { - maybeRepo := filepath.Base(filepath.Dir(r.Chart)) - c <- "Chart [ " + r.Chart + " ] for app [" + app + "] can't be found. Inspection returned error: \"" + - strings.TrimSpace(result.errors) + "\" -- If this is not a local chart, add the repo [ " + maybeRepo + " ] in your helmRepos stanza." + result := cmd.exec() + if result.code != 0 { + maybeRepo := filepath.Base(filepath.Dir(chart)) + c <- "Chart [ " + chart + " ] for apps [" + apps + "] can't be found. Inspection returned error: \"" + + strings.TrimSpace(result.errors) + "\" -- If this is not a local chart, add the repo [ " + maybeRepo + " ] in your helmRepos stanza." + return + } + matches := versionExtractor.FindStringSubmatch(result.output) + if len(matches) == 2 { + v := strings.Trim(matches[1], `'"`) + if strings.Trim(version, `'"`) != v { + c <- "Chart [ " + chart + " ] with version [ " + version + " ] is specified for " + + "apps [" + apps + "] but the chart found at that path has version [ " + v + " ] which does not match." return } - matches := versionExtractor.FindStringSubmatch(result.output) - if len(matches) == 2 { - version := strings.Trim(matches[1], `'"`) - if strings.Trim(r.Version, `'"`) != version { - c <- "Chart [ " + r.Chart + " ] with version [ " + r.Version + " ] is specified for " + - "app [" + app + "] but the chart found at that path has version [ " + version + " ] which does not match." - return - } - } - - } else { - version := r.Version - if len(version) == 0 { - version = "*" - } - cmd := helmCmd([]string{"search", "repo", r.Chart, "--version", version, "-l"}, "Validating [ "+r.Chart+" ] chart's version [ "+r.Version+" ] availability") + } + } else { + v := version + if len(v) == 0 { + v = "*" + } + cmd := helmCmd([]string{"search", "repo", chart, "--version", v, "-l"}, "Validating [ "+chart+" ] chart's version [ "+version+" ] availability") - if result := cmd.exec(); result.code != 0 || strings.Contains(result.output, "No results found") { - c <- "Chart [ " + r.Chart + " ] with version [ " + r.Version + " ] is specified for " + - "app [" + app + "] but was not found. If this is not a local chart, define its helm repo in the helmRepo stanza in your DSF." - return - } + if result := cmd.exec(); result.code != 0 || strings.Contains(result.output, "No results found") { + c <- "Chart [ " + chart + " ] with version [ " + version + " ] is specified for " + + "apps [" + apps + "] but was not found. If this is not a local chart, define its helm repo in the helmRepo stanza in your DSF." + return } } } From 1482c3160af1eed919dd3d7cfa9cfe9c10d64452 Mon Sep 17 00:00:00 2001 From: Fareed Dudhia Date: Mon, 27 Apr 2020 13:19:25 +0100 Subject: [PATCH 0647/1127] cache some helm command results --- internal/app/helm_helpers.go | 9 +++++++++ internal/app/release.go | 14 ++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index 2666c4b0..1bf8692d 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -6,6 +6,7 @@ import ( "fmt" "net/url" "strings" + "sync" "github.com/Praqma/helmsman/internal/gcs" ) @@ -15,6 +16,8 @@ type helmRepo struct { Url string `json:"url"` } +var chartNameCache sync.Map + // helmCmd prepares a helm command to be executed func helmCmd(args []string, desc string) command { return command{ @@ -26,6 +29,11 @@ func helmCmd(args []string, desc string) command { // extractChartName extracts the Helm chart name from full chart name in the desired state. func extractChartName(releaseChart string) string { + chart, ok := chartNameCache.Load(releaseChart) + if ok { + return chart.(string) + } + cmd := helmCmd([]string{"show", "chart", releaseChart}, "Show chart information") result := cmd.exec() @@ -42,6 +50,7 @@ func extractChartName(releaseChart string) string { } } + chartNameCache.Store(releaseChart, name) return name } diff --git a/internal/app/release.go b/internal/app/release.go index 8f411db7..1966327b 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -262,9 +262,16 @@ func validateChart(apps, chart, version string, s *state, c chan string) { // If chart is local, returns the given release version func (r *release) getChartVersion() (string, string) { if isLocalChart(r.Chart) { + log.Info("Chart [ " + r.Chart + "] with version [ " + r.Version + " ] was found locally.") return r.Version, "" } - cmd := helmCmd([]string{"search", "repo", r.Chart, "--version", r.Version, "-o", "json"}, "Getting latest chart's version "+r.Chart+"-"+r.Version+"") + + if len(r.cachedVersion) > 0 { + log.Info("Non-local chart [ " + r.Chart + "] with version [ " + r.Version + " ] was found in cache.") + return r.cachedVersion, "" + } + + cmd := helmCmd([]string{"search", "repo", r.Chart, "--version", r.Version, "-o", "json"}, "Getting latest non-local chart's version "+r.Chart+"-"+r.Version+"") result := cmd.exec() if result.code != 0 { @@ -288,7 +295,10 @@ func (r *release) getChartVersion() (string, string) { } else if len(filteredChartVersions) > 1 { return "", "Multiple versions of chart [ " + r.Chart + " ] with version [ " + r.Version + " ] found in the helm repositories" } - return filteredChartVersions[0].Version, "" + + v := filteredChartVersions[0].Version + r.cachedVersion = v + return v, "" } // testRelease creates a Helm command to test a particular release. From 4d5749def371d572e012cdd8a5cc1e754e09ad50 Mon Sep 17 00:00:00 2001 From: Fareed Dudhia Date: Mon, 27 Apr 2020 15:01:26 +0100 Subject: [PATCH 0648/1127] fix appVersion --- internal/app/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/app/main.go b/internal/app/main.go index 4891d56b..baf8f2fe 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -6,7 +6,7 @@ import ( const ( helmBin = "helm" - appVersion = "v3.3.0" + appVersion = "v3.3.1-perf" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 From 1c7aedde9004359a177851d86992c2503ed6c0dc Mon Sep 17 00:00:00 2001 From: Fareed Dudhia Date: Tue, 28 Apr 2020 00:32:00 +0100 Subject: [PATCH 0649/1127] fix release performancce improvements --- Dockerfile | 56 ++++++--- internal/app/decision_maker.go | 126 +++++++++++++++++--- internal/app/decision_maker_test.go | 8 +- internal/app/helm_helpers.go | 13 +-- internal/app/main.go | 2 +- internal/app/release.go | 39 +++---- internal/app/release_test.go | 173 ++++++++-------------------- 7 files changed, 223 insertions(+), 194 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4476c618..fda61e68 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,18 +4,44 @@ ARG GLOBAL_KUBE_VERSION="v1.14.10" ARG GLOBAL_HELM_VERSION="v3.1.1" ARG GLOBAL_HELM_DIFF_VERSION="v3.1.1" - -FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} as builder +### Helm Installer ### +FROM alpine:${ALPINE_VERSION} as helm-installer ARG GLOBAL_KUBE_VERSION ARG GLOBAL_HELM_VERSION ARG GLOBAL_HELM_DIFF_VERSION ENV KUBE_VERSION=$GLOBAL_KUBE_VERSION ENV HELM_VERSION=$GLOBAL_HELM_VERSION ENV HELM_DIFF_VERSION=$GLOBAL_HELM_DIFF_VERSION + +RUN apk add --update --no-cache ca-certificates git openssh ruby curl tar gzip make bash + +RUN curl -L https://storage.googleapis.com/kubernetes-release/release/${KUBE_VERSION}/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl +RUN chmod +x /usr/local/bin/kubectl + +RUN curl -Lk https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz | tar zxv -C /tmp +RUN mv /tmp/linux-amd64/helm /usr/local/bin/helm && rm -rf /tmp/linux-amd64 +RUN chmod +x /usr/local/bin/helm + +RUN helm plugin install https://github.com/hypnoglow/helm-s3.git +RUN helm plugin install https://github.com/nouney/helm-gcs +RUN helm plugin install https://github.com/databus23/helm-diff --version ${HELM_DIFF_VERSION} +RUN helm plugin install https://github.com/futuresimple/helm-secrets +RUN rm -r /tmp/helm-diff /tmp/helm-diff.tgz + +### Go Builder & Tester ### +FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} as builder + +RUN apk add --update --no-cache ca-certificates git openssh ruby bash make +RUN gem install hiera-eyaml --no-doc +RUN update-ca-certificates + +COPY --from=helm-installer /usr/local/bin/kubectl /usr/local/bin/kubectl +COPY --from=helm-installer /usr/local/bin/helm /usr/local/bin/helm +COPY --from=helm-installer /root/.cache/helm/plugins/ /root/.cache/helm/plugins/ +COPY --from=helm-installer /root/.local/share/helm/plugins/ /root/.local/share/helm/plugins/ + WORKDIR /go/src/github.com/Praqma/helmsman -COPY scripts/ /tmp/ -RUN sh /tmp/setup.sh \ - && apk --no-cache add dep + COPY . . RUN make test \ && LastTag=$(git describe --abbrev=0 --tags) \ @@ -25,14 +51,16 @@ RUN make test \ && if [ ${LT_SHA} != ${LC_SHA} ]; then TAG=latest-$(date +"%d%m%y"); fi \ && make build - +### Final Image ### FROM alpine:${ALPINE_VERSION} as base -ARG GLOBAL_KUBE_VERSION -ARG GLOBAL_HELM_VERSION -ARG GLOBAL_HELM_DIFF_VERSION -ENV KUBE_VERSION=$GLOBAL_KUBE_VERSION -ENV HELM_VERSION=$GLOBAL_HELM_VERSION -ENV HELM_DIFF_VERSION=$GLOBAL_HELM_DIFF_VERSION -COPY scripts/ /tmp/ -RUN sh /tmp/setup.sh + +RUN apk add --update --no-cache ca-certificates git openssh ruby curl bash +RUN gem install hiera-eyaml --no-doc +RUN update-ca-certificates + +COPY --from=helm-installer /usr/local/bin/kubectl /usr/local/bin/kubectl +COPY --from=helm-installer /usr/local/bin/helm /usr/local/bin/helm +COPY --from=helm-installer /root/.cache/helm/plugins/ /root/.cache/helm/plugins/ +COPY --from=helm-installer /root/.local/share/helm/plugins/ /root/.local/share/helm/plugins/ + COPY --from=builder /go/src/github.com/Praqma/helmsman/helmsman /bin/helmsman diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index 5254a707..30203ec2 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -44,8 +44,8 @@ func buildState(s *state) *currentState { if flags.contextOverride == "" { r.HelmsmanContext = getReleaseContext(r.Name, r.Namespace) } else { - log.Info("Overriding Helmsman context for " + r.Name + " as " + flags.contextOverride) r.HelmsmanContext = flags.contextOverride + log.Info("Overwrote Helmsman context for release [ " + r.Name + " ] to " + flags.contextOverride) } cs.releases[r.key()] = r }(r) @@ -60,18 +60,115 @@ func (cs *currentState) makePlan(s *state) *plan { wg := sync.WaitGroup{} sem := make(chan struct{}, resourcePool) + namesC := make(chan [2]string, len(s.Apps)) + versionsC := make(chan [4]string, len(s.Apps)) + + // We store the results of the helm commands + extractedChartNames := make(map[string]string) + extractedChartVersions := make(map[string]map[string]string) + + // We get the charts and versions with the expensive helm commands first. + // We can probably DRY this concurrency stuff up somehow. + // We can also definitely DRY this with validateReleaseChart. + // We should probably have a data structure earlier on that sorts this out properly. + // Ideally we'd have a pipeline of helm command tasks with several stages that can all come home if one of them fails. + // Is it better to fail early here? I am not sure. + + // Initialize the rejigged data structures + charts := make(map[string]map[string]bool) + for _, r := range s.Apps { + if charts[r.Chart] == nil { + charts[r.Chart] = make(map[string]bool) + } + + if extractedChartVersions[r.Chart] == nil { + extractedChartVersions[r.Chart] = make(map[string]string) + } + + if r.isConsideredToRun(s) { + charts[r.Chart][r.Version] = true + } + } + + // Concurrently extract chart names and versions + // I'm not fond of this concurrency pattern, it's just a continuation of what's already there. + // This seems like overkill somehow. Is it necessary to run all the helm commands concurrently? Does that speed it up? (Quick sanity check says: yes, it does) + for chart, versions := range charts { + sem <- struct{}{} + wg.Add(1) + go func(chart string) { + defer func() { + wg.Done() + <-sem + }() + namesC <- [2]string{chart, extractChartName(chart)} + }(chart) + + for version, shouldRun := range versions { + if !shouldRun { + continue + } + + sem <- struct{}{} + wg.Add(1) + go func(chart, version string) { + defer func() { + wg.Done() + <-sem + }() + + // even though I wrote it, lines like this are a code smell to me -- we need to rethink the concurrency as i mentioned above. + // ideally you just process all helm commands "in a background pool", they return channels, + // and we would be looping over select statements until we had the results of the helm commands we wanted. + n, m := getChartVersion(chart, version) + versionsC <- [4]string{chart, version, n, m} + }(chart, version) + } + } + + wg.Wait() + close(namesC) + close(versionsC) + + // One thing we could do here instead would be: + // instead of waiting for everything to come back, just start deciding for releases as their chart names and versions come through. + + for nameResult := range namesC { + // Is there a ... that can do this? I forget + c, n := nameResult[0], nameResult[1] + log.Info("Extracted chart name [ " + c + " ].") + extractedChartNames[c] = n + } + + for versionResult := range versionsC { + // Is there a ... that can do this? I forget + c, v, n, m := versionResult[0], versionResult[1], versionResult[2], versionResult[3] + if m != "" { + // Better to fail early and return here? + log.Error(m) + } else { + log.Info("Extracted chart version from chart [ " + c + " ] with version [ " + v + " ]: '" + n + "'") + extractedChartVersions[c][v] = n + } + } + + // Pass the extracted names and versions back to the apps to decide. + // We still have to run decide on all the apps, even the ones we previously filtered out when extracting names and versions. + // We can now proceed without trying lots of identical helm commands at the same time. for _, r := range s.Apps { + // To be honest, the helmCmd function should probably pass back a channel at this point, making the resource pool "global", for all helm commands. + // It would make more sense than parallelising *some of the workload* like we do here with r.checkChartDepUpdate(), leaving some helm commands outside the concurrent part. r.checkChartDepUpdate() sem <- struct{}{} wg.Add(1) - go func(r *release) { + go func(r *release, chartName, chartVersion string) { defer func() { wg.Done() <-sem }() - cs.decide(r, s, p) - }(r) + cs.decide(r, s, p, chartName, chartVersion) + }(r, extractedChartNames[r.Chart], extractedChartVersions[r.Chart][r.Version]) } wg.Wait() @@ -80,7 +177,7 @@ func (cs *currentState) makePlan(s *state) *plan { // decide makes a decision about what commands (actions) need to be executed // to make a release section of the desired state come true. -func (cs *currentState) decide(r *release, s *state, p *plan) { +func (cs *currentState) decide(r *release, s *state, p *plan, chartName, chartVersion string) { // check for presence in defined targets or groups if !r.isConsideredToRun(s) { p.addDecision("Release [ "+r.Name+" ] ignored", r.Priority, ignored) @@ -112,7 +209,7 @@ func (cs *currentState) decide(r *release, s *state, p *plan) { if ok := cs.releaseExists(r, helmStatusDeployed); ok { if !r.isProtected(cs, s) { - cs.inspectUpgradeScenario(r, p) // upgrade or move + cs.inspectUpgradeScenario(r, p, chartName, chartVersion) // upgrade or move } else { p.addDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ "you remove its protection.", r.Priority, noop) @@ -284,7 +381,10 @@ func (cs *currentState) cleanUntrackedReleases(s *state, p *plan) { // it will be purge deleted and installed in the same namespace using the new chart. // - If the release is NOT in the same namespace specified in the input, // it will be purge deleted and installed in the new namespace. -func (cs *currentState) inspectUpgradeScenario(r *release, p *plan) { +func (cs *currentState) inspectUpgradeScenario(r *release, p *plan, chartName, chartVersion string) { + if chartName == "" || chartVersion == "" { + return + } rs, ok := cs.releases[r.key()] if !ok { @@ -292,21 +392,15 @@ func (cs *currentState) inspectUpgradeScenario(r *release, p *plan) { } if r.Namespace == rs.Namespace { + r.Version = chartVersion - version, msg := r.getChartVersion() - if msg != "" { - log.Fatal(msg) - return - } - r.Version = version - - if extractChartName(r.Chart) == rs.getChartName() && r.Version != rs.getChartVersion() { + if chartName == rs.getChartName() && r.Version != rs.getChartVersion() { // upgrade r.diff() r.upgrade(p) p.addDecision("Release [ "+r.Name+" ] will be updated", r.Priority, change) - } else if extractChartName(r.Chart) != rs.getChartName() { + } else if chartName != rs.getChartName() { r.reInstall(p) p.addDecision("Release [ "+r.Name+" ] is desired to use a new chart [ "+r.Chart+ " ]. Delete of the current release will be planned and new chart will be installed in namespace [ "+ diff --git a/internal/app/decision_maker_test.go b/internal/app/decision_maker_test.go index 87813e07..13eae616 100644 --- a/internal/app/decision_maker_test.go +++ b/internal/app/decision_maker_test.go @@ -86,7 +86,7 @@ func Test_inspectUpgradeScenario(t *testing.T) { want decisionType }{ { - name: "inspectUpgradeScenario() - local chart with different chart name should change", + name: "makePlan() - local chart with different chart name should change", args: args{ r: &release{ Name: "release1", @@ -111,7 +111,9 @@ func Test_inspectUpgradeScenario(t *testing.T) { cs := currentState{releases: *tt.args.s} // Act - cs.inspectUpgradeScenario(tt.args.r, &outcome) + chartName := extractChartName(tt.args.r.Chart) + chartVersion, _ := getChartVersion(tt.args.r.Chart, tt.args.r.Version) + cs.inspectUpgradeScenario(tt.args.r, &outcome, chartName, chartVersion) got := outcome.Decisions[0].Type t.Log(outcome.Decisions[0].Description) @@ -211,7 +213,7 @@ func Test_decide(t *testing.T) { } outcome := plan{} // Act - cs.decide(tt.args.r, tt.args.s, &outcome) + cs.decide(tt.args.r, tt.args.s, &outcome, "", "") got := outcome.Decisions[0].Type t.Log(outcome.Decisions[0].Description) diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index 1bf8692d..56817f66 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -6,7 +6,6 @@ import ( "fmt" "net/url" "strings" - "sync" "github.com/Praqma/helmsman/internal/gcs" ) @@ -16,8 +15,6 @@ type helmRepo struct { Url string `json:"url"` } -var chartNameCache sync.Map - // helmCmd prepares a helm command to be executed func helmCmd(args []string, desc string) command { return command{ @@ -29,12 +26,7 @@ func helmCmd(args []string, desc string) command { // extractChartName extracts the Helm chart name from full chart name in the desired state. func extractChartName(releaseChart string) string { - chart, ok := chartNameCache.Load(releaseChart) - if ok { - return chart.(string) - } - - cmd := helmCmd([]string{"show", "chart", releaseChart}, "Show chart information") + cmd := helmCmd([]string{"show", "chart", releaseChart}, "Caching chart information for [ "+releaseChart+" ].") result := cmd.exec() if result.code != 0 { @@ -50,7 +42,6 @@ func extractChartName(releaseChart string) string { } } - chartNameCache.Store(releaseChart, name) return name } @@ -62,6 +53,8 @@ func getHelmVersion() string { if result.code != 0 { log.Fatal("While checking helm version: " + result.errors) } + + log.Verbose("Helm version " + result.output) return result.output } diff --git a/internal/app/main.go b/internal/app/main.go index baf8f2fe..4891d56b 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -6,7 +6,7 @@ import ( const ( helmBin = "helm" - appVersion = "v3.3.1-perf" + appVersion = "v3.3.0" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/internal/app/release.go b/internal/app/release.go index 1966327b..40965cf0 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -201,7 +201,7 @@ func validateReleaseCharts(s *state) error { wg.Done() <-sem }() - validateChart(concattedApps, chart, version, s, c) + validateChart(concattedApps, chart, version, c) }(concattedApps, ch, v) } } @@ -223,7 +223,7 @@ func validateReleaseCharts(s *state) error { var versionExtractor = regexp.MustCompile(`[\n]version:\s?(.*)`) // validateChart validates if chart with the same name and version as specified in the DSF exists -func validateChart(apps, chart, version string, s *state, c chan string) { +func validateChart(apps, chart, version string, c chan string) { if isLocalChart(chart) { cmd := helmCmd([]string{"inspect", "chart", chart}, "Validating [ "+chart+" ] chart's availability") @@ -260,45 +260,38 @@ func validateChart(apps, chart, version string, s *state, c chan string) { // getChartVersion fetches the lastest chart version matching the semantic versioning constraints. // If chart is local, returns the given release version -func (r *release) getChartVersion() (string, string) { - if isLocalChart(r.Chart) { - log.Info("Chart [ " + r.Chart + "] with version [ " + r.Version + " ] was found locally.") - return r.Version, "" - } - - if len(r.cachedVersion) > 0 { - log.Info("Non-local chart [ " + r.Chart + "] with version [ " + r.Version + " ] was found in cache.") - return r.cachedVersion, "" +func getChartVersion(chart, version string) (string, string) { + if isLocalChart(chart) { + log.Info("Chart [ " + chart + "] with version [ " + version + " ] was found locally.") + return version, "" } - cmd := helmCmd([]string{"search", "repo", r.Chart, "--version", r.Version, "-o", "json"}, "Getting latest non-local chart's version "+r.Chart+"-"+r.Version+"") + cmd := helmCmd([]string{"search", "repo", chart, "--version", version, "-o", "json"}, "Getting latest non-local chart's version "+chart+"-"+version+"") result := cmd.exec() if result.code != 0 { - return "", "Chart [ " + r.Chart + " ] with version [ " + r.Version + " ] is specified but not found in the helm repositories" + return "", "Chart [ " + chart + " ] with version [ " + version + " ] is specified but not found in the helm repositories" } - chartVersions := make([]chartVersion, 0) - if err := json.Unmarshal([]byte(result.output), &chartVersions); err != nil { + cv := make([]chartVersion, 0) + if err := json.Unmarshal([]byte(result.output), &cv); err != nil { log.Fatal(fmt.Sprint(err)) } filteredChartVersions := make([]chartVersion, 0) - for _, chart := range chartVersions { - if chart.Name == r.Chart { - filteredChartVersions = append(filteredChartVersions, chart) + for _, c := range cv { + if c.Name == chart { + filteredChartVersions = append(filteredChartVersions, c) } } if len(filteredChartVersions) < 1 { - return "", "Chart [ " + r.Chart + " ] with version [ " + r.Version + " ] is specified but not found in the helm repositories" + return "", "Chart [ " + chart + " ] with version [ " + version + " ] is specified but not found in the helm repositories" } else if len(filteredChartVersions) > 1 { - return "", "Multiple versions of chart [ " + r.Chart + " ] with version [ " + r.Version + " ] found in the helm repositories" + return "", "Multiple versions of chart [ " + chart + " ] with version [ " + version + " ] found in the helm repositories" } - v := filteredChartVersions[0].Version - r.cachedVersion = v - return v, "" + return filteredChartVersions[0].Version, "" } // testRelease creates a Helm command to test a particular release. diff --git a/internal/app/release_test.go b/internal/app/release_test.go index 8742ca63..34859542 100644 --- a/internal/app/release_test.go +++ b/internal/app/release_test.go @@ -457,10 +457,36 @@ func Test_inheritHooks(t *testing.T) { }) } } + +func createFullReleasePointer(chart, version string) *release { + return &release{ + Name: "", + Description: "", + Namespace: "", + Enabled: true, + Chart: chart, + Version: version, + ValuesFile: "", + ValuesFiles: []string{}, + SecretsFile: "", + SecretsFiles: []string{}, + Test: false, + Protected: false, + Wait: false, + Priority: 0, + Set: make(map[string]string), + SetString: make(map[string]string), + HelmFlags: []string{}, + NoHooks: false, + Timeout: 0, + } +} + func Test_validateReleaseCharts(t *testing.T) { type args struct { apps map[string]*release } + tests := []struct { name string targetFlag []string @@ -473,27 +499,7 @@ func Test_validateReleaseCharts(t *testing.T) { targetFlag: []string{}, args: args{ apps: map[string]*release{ - "app": &release{ - Name: "", - Description: "", - Namespace: "", - Enabled: true, - Chart: os.TempDir() + "/helmsman-tests/myapp", - Version: "", - ValuesFile: "", - ValuesFiles: []string{}, - SecretsFile: "", - SecretsFiles: []string{}, - Test: false, - Protected: false, - Wait: false, - Priority: 0, - Set: make(map[string]string), - SetString: make(map[string]string), - HelmFlags: []string{}, - NoHooks: false, - Timeout: 0, - }, + "app": createFullReleasePointer(os.TempDir()+"/helmsman-tests/myapp", ""), }, }, want: false, @@ -502,27 +508,7 @@ func Test_validateReleaseCharts(t *testing.T) { targetFlag: []string{}, args: args{ apps: map[string]*release{ - "app": &release{ - Name: "", - Description: "", - Namespace: "", - Enabled: true, - Chart: os.TempDir() + "/does-not-exist/myapp", - Version: "", - ValuesFile: "", - ValuesFiles: []string{}, - SecretsFile: "", - SecretsFiles: []string{}, - Test: false, - Protected: false, - Wait: false, - Priority: 0, - Set: make(map[string]string), - SetString: make(map[string]string), - HelmFlags: []string{}, - NoHooks: false, - Timeout: 0, - }, + "app": createFullReleasePointer(os.TempDir()+"/does-not-exist/myapp", ""), }, }, want: false, @@ -531,27 +517,7 @@ func Test_validateReleaseCharts(t *testing.T) { targetFlag: []string{}, args: args{ apps: map[string]*release{ - "app": &release{ - Name: "", - Description: "", - Namespace: "", - Enabled: true, - Chart: os.TempDir() + "/helmsman-tests/dir-with space/myapp", - Version: "0.1.0", - ValuesFile: "", - ValuesFiles: []string{}, - SecretsFile: "", - SecretsFiles: []string{}, - Test: false, - Protected: false, - Wait: false, - Priority: 0, - Set: make(map[string]string), - SetString: make(map[string]string), - HelmFlags: []string{}, - NoHooks: false, - Timeout: 0, - }, + "app": createFullReleasePointer(os.TempDir()+"/helmsman-tests/dir-with space/myapp", "0.1.0"), }, }, want: true, @@ -560,27 +526,7 @@ func Test_validateReleaseCharts(t *testing.T) { targetFlag: []string{}, args: args{ apps: map[string]*release{ - "app": &release{ - Name: "", - Description: "", - Namespace: "", - Enabled: true, - Chart: "stable/prometheus", - Version: "9.5.2", - ValuesFile: "", - ValuesFiles: []string{}, - SecretsFile: "", - SecretsFiles: []string{}, - Test: false, - Protected: false, - Wait: false, - Priority: 0, - Set: make(map[string]string), - SetString: make(map[string]string), - HelmFlags: []string{}, - NoHooks: false, - Timeout: 0, - }, + "app": createFullReleasePointer("stable/prometheus", "9.5.2"), }, }, want: true, @@ -589,27 +535,7 @@ func Test_validateReleaseCharts(t *testing.T) { targetFlag: []string{"notThisOne"}, args: args{ apps: map[string]*release{ - "app": &release{ - Name: "app", - Description: "", - Namespace: "", - Enabled: true, - Chart: os.TempDir() + "/does-not-exist/myapp", - Version: "", - ValuesFile: "", - ValuesFiles: []string{}, - SecretsFile: "", - SecretsFiles: []string{}, - Test: false, - Protected: false, - Wait: false, - Priority: 0, - Set: make(map[string]string), - SetString: make(map[string]string), - HelmFlags: []string{}, - NoHooks: false, - Timeout: 0, - }, + "app": createFullReleasePointer(os.TempDir()+"/does-not-exist/myapp", ""), }, }, want: true, @@ -618,30 +544,23 @@ func Test_validateReleaseCharts(t *testing.T) { targetFlag: []string{"app"}, args: args{ apps: map[string]*release{ - "app": &release{ - Name: "app", - Description: "", - Namespace: "", - Enabled: true, - Chart: os.TempDir() + "/does-not-exist/myapp", - Version: "", - ValuesFile: "", - ValuesFiles: []string{}, - SecretsFile: "", - SecretsFiles: []string{}, - Test: false, - Protected: false, - Wait: false, - Priority: 0, - Set: make(map[string]string), - SetString: make(map[string]string), - HelmFlags: []string{}, - NoHooks: false, - Timeout: 0, - }, + "app": createFullReleasePointer(os.TempDir()+"/does-not-exist/myapp", ""), }, }, want: false, + }, { + name: "test case 7: multiple valid local apps with the same chart version", + targetFlag: []string{"app"}, + args: args{ + apps: map[string]*release{ + "app1": createFullReleasePointer(os.TempDir()+"/helmsman-tests/dir-with space/myapp", "0.1.0"), + "app2": createFullReleasePointer(os.TempDir()+"/helmsman-tests/dir-with space/myapp", "0.1.0"), + "app3": createFullReleasePointer(os.TempDir()+"/helmsman-tests/dir-with space/myapp", "0.1.0"), + "app4": createFullReleasePointer(os.TempDir()+"/helmsman-tests/dir-with space/myapp", "0.1.0"), + "app5": createFullReleasePointer(os.TempDir()+"/helmsman-tests/dir-with space/myapp", "0.1.0"), + }, + }, + want: true, }, } @@ -815,7 +734,7 @@ func Test_getChartVersion(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Log(tt.want) - got, _ := tt.args.r.getChartVersion() + got, _ := getChartVersion(tt.args.r.Chart, tt.args.r.Version) if got != tt.want { t.Errorf("getChartVersion() = %v, want %v", got, tt.want) } From 3459a23ae2fb8142483b2dfd98bc00a68a88a108 Mon Sep 17 00:00:00 2001 From: Fareed Dudhia Date: Tue, 12 May 2020 14:35:21 +0100 Subject: [PATCH 0650/1127] fix pr feedback and restrict plan to targetted apps --- internal/app/decision_maker.go | 15 +++++++++++---- internal/app/decision_maker_test.go | 2 +- internal/app/helm_helpers.go | 3 +-- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index 30203ec2..56c1af4d 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -58,10 +58,17 @@ func buildState(s *state) *currentState { func (cs *currentState) makePlan(s *state) *plan { p := createPlan() + var apps map[string]*release + if len(s.TargetMap) > 0 { + apps = s.TargetApps + } else { + apps = s.Apps + } + wg := sync.WaitGroup{} sem := make(chan struct{}, resourcePool) - namesC := make(chan [2]string, len(s.Apps)) - versionsC := make(chan [4]string, len(s.Apps)) + namesC := make(chan [2]string, len(apps)) + versionsC := make(chan [4]string, len(apps)) // We store the results of the helm commands extractedChartNames := make(map[string]string) @@ -76,7 +83,7 @@ func (cs *currentState) makePlan(s *state) *plan { // Initialize the rejigged data structures charts := make(map[string]map[string]bool) - for _, r := range s.Apps { + for _, r := range apps { if charts[r.Chart] == nil { charts[r.Chart] = make(map[string]bool) } @@ -156,7 +163,7 @@ func (cs *currentState) makePlan(s *state) *plan { // Pass the extracted names and versions back to the apps to decide. // We still have to run decide on all the apps, even the ones we previously filtered out when extracting names and versions. // We can now proceed without trying lots of identical helm commands at the same time. - for _, r := range s.Apps { + for _, r := range apps { // To be honest, the helmCmd function should probably pass back a channel at this point, making the resource pool "global", for all helm commands. // It would make more sense than parallelising *some of the workload* like we do here with r.checkChartDepUpdate(), leaving some helm commands outside the concurrent part. r.checkChartDepUpdate() diff --git a/internal/app/decision_maker_test.go b/internal/app/decision_maker_test.go index 13eae616..d1f6a54d 100644 --- a/internal/app/decision_maker_test.go +++ b/internal/app/decision_maker_test.go @@ -86,7 +86,7 @@ func Test_inspectUpgradeScenario(t *testing.T) { want decisionType }{ { - name: "makePlan() - local chart with different chart name should change", + name: "inspectUpgradeScenario() - local chart with different chart name should change", args: args{ r: &release{ Name: "release1", diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index 56817f66..e0e90a4e 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -26,7 +26,7 @@ func helmCmd(args []string, desc string) command { // extractChartName extracts the Helm chart name from full chart name in the desired state. func extractChartName(releaseChart string) string { - cmd := helmCmd([]string{"show", "chart", releaseChart}, "Caching chart information for [ "+releaseChart+" ].") + cmd := helmCmd([]string{"show", "chart", releaseChart}, "Extracting chart information for [ "+releaseChart+" ].") result := cmd.exec() if result.code != 0 { @@ -54,7 +54,6 @@ func getHelmVersion() string { log.Fatal("While checking helm version: " + result.errors) } - log.Verbose("Helm version " + result.output) return result.output } From 850aed927a18de6f06de649aae0f0c7102c9ca50 Mon Sep 17 00:00:00 2001 From: Fareed Dudhia Date: Tue, 12 May 2020 14:44:20 +0100 Subject: [PATCH 0651/1127] fix variable name --- internal/app/release.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/app/release.go b/internal/app/release.go index 40965cf0..6579f65b 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -273,13 +273,13 @@ func getChartVersion(chart, version string) (string, string) { return "", "Chart [ " + chart + " ] with version [ " + version + " ] is specified but not found in the helm repositories" } - cv := make([]chartVersion, 0) - if err := json.Unmarshal([]byte(result.output), &cv); err != nil { + chartVersions := make([]chartVersion, 0) + if err := json.Unmarshal([]byte(result.output), &chartVersions); err != nil { log.Fatal(fmt.Sprint(err)) } filteredChartVersions := make([]chartVersion, 0) - for _, c := range cv { + for _, c := range chartVersions { if c.Name == chart { filteredChartVersions = append(filteredChartVersions, c) } From 3525dcc24d550f93db200f86fd4fb7a8f867c75b Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Wed, 13 May 2020 12:36:06 +0200 Subject: [PATCH 0652/1127] Replace downloadFile's func log.Info with log.Verbose --- internal/app/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/app/utils.go b/internal/app/utils.go index 8ec4663c..92f5ac7c 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -383,7 +383,7 @@ func downloadFile(file string, dir string, outfile string) string { } else { - log.Info("" + file + " will be used from local file system.") + log.Verbose("" + file + " will be used from local file system.") toCopy := file if !filepath.IsAbs(file) { toCopy, _ = filepath.Abs(filepath.Join(dir, file)) From cffc46e7a35780b5618bb2060002afed91f6fef6 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Wed, 13 May 2020 12:44:39 +0200 Subject: [PATCH 0653/1127] Release v3.4.0 --- README.md | 2 +- internal/app/main.go | 2 +- release-notes.md | 12 ++++++++---- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 82f95826..9f425fc5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.3.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.4.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) diff --git a/internal/app/main.go b/internal/app/main.go index 4891d56b..fc17574b 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -6,7 +6,7 @@ import ( const ( helmBin = "helm" - appVersion = "v3.3.0" + appVersion = "v3.4.0" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index c5d5e1b0..1dc536c2 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,12 +1,16 @@ -# v3.3.0 +# v3.4.0 If you migrating from Helmsman v1.x, it is recommended you read the [migration guide](https://github.com/Praqma/helmsman/blob/master/docs/how_to/misc/migrate_to_3.md) before using this release. > Starting from Helmsman v3.0.0 GA release, support for Helmsman v1.x will be limited to bug fixes. # Fixes and improvements: -- Add DRY-ed examples using appsTemplates; PR #425 +- Respect dry-run flag with kubectl commands; PR #462 +- Add gnupg to Docker image; PR #449 +- Allow absolute paths for values/secrets/etc files; PR #459 +- Fix inconsistent helm args between install and upgrade; PR #458 # New features: -- Add `--p` flag to define parallel apps installation/upgrade for those with the same priority defined in DSF; PR #431 -- Add Namespace's resource quotas to DSF; PR #384 +- Add lifecycle hooks into Helmsman; PR #421 +- Support for using --history-max helm upgrade flag; PR #460 +- Support the Helm --set-file flag; PR #444 From d2f6b6b5313daf214323231a1a11e6e620546871 Mon Sep 17 00:00:00 2001 From: Fareed Dudhia Date: Wed, 13 May 2020 12:09:06 +0100 Subject: [PATCH 0654/1127] revert target for plan --- internal/app/decision_maker.go | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index 56c1af4d..30203ec2 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -58,17 +58,10 @@ func buildState(s *state) *currentState { func (cs *currentState) makePlan(s *state) *plan { p := createPlan() - var apps map[string]*release - if len(s.TargetMap) > 0 { - apps = s.TargetApps - } else { - apps = s.Apps - } - wg := sync.WaitGroup{} sem := make(chan struct{}, resourcePool) - namesC := make(chan [2]string, len(apps)) - versionsC := make(chan [4]string, len(apps)) + namesC := make(chan [2]string, len(s.Apps)) + versionsC := make(chan [4]string, len(s.Apps)) // We store the results of the helm commands extractedChartNames := make(map[string]string) @@ -83,7 +76,7 @@ func (cs *currentState) makePlan(s *state) *plan { // Initialize the rejigged data structures charts := make(map[string]map[string]bool) - for _, r := range apps { + for _, r := range s.Apps { if charts[r.Chart] == nil { charts[r.Chart] = make(map[string]bool) } @@ -163,7 +156,7 @@ func (cs *currentState) makePlan(s *state) *plan { // Pass the extracted names and versions back to the apps to decide. // We still have to run decide on all the apps, even the ones we previously filtered out when extracting names and versions. // We can now proceed without trying lots of identical helm commands at the same time. - for _, r := range apps { + for _, r := range s.Apps { // To be honest, the helmCmd function should probably pass back a channel at this point, making the resource pool "global", for all helm commands. // It would make more sense than parallelising *some of the workload* like we do here with r.checkChartDepUpdate(), leaving some helm commands outside the concurrent part. r.checkChartDepUpdate() From 98033af0bec8f2242aa06e75056a34d260bd79c8 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Wed, 13 May 2020 13:37:27 +0200 Subject: [PATCH 0655/1127] Update helm version for docker images in release v3.4.0 --- .circleci/config.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 60984bd6..e752e473 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -47,11 +47,11 @@ jobs: TAG=$(git describe --abbrev=0 --tags) docker login -u $DOCKER_USER -p $DOCKER_PASS - docker build -t praqma/helmsman:$TAG-helm-v3.0.3 --build-arg HELM_VERSION=v3.0.3 . --no-cache - docker push praqma/helmsman:$TAG-helm-v3.0.3 + docker build -t praqma/helmsman:$TAG-helm-v3.1.3 --build-arg HELM_VERSION=v3.1.3 . --no-cache + docker push praqma/helmsman:$TAG-helm-v3.1.3 - docker build -t praqma/helmsman:$TAG-helm-v3.1.2 --build-arg HELM_VERSION=v3.1.2 . --no-cache - docker push praqma/helmsman:$TAG-helm-v3.1.2 + docker build -t praqma/helmsman:$TAG-helm-v3.2.1 --build-arg HELM_VERSION=v3.2.1 . --no-cache + docker push praqma/helmsman:$TAG-helm-v3.2.1 workflows: version: 2 From 455bc8bc8015980dacd6c00a2f2b111b869edbe3 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Wed, 13 May 2020 16:39:03 +0200 Subject: [PATCH 0656/1127] validate env vars are no unset. fixes #453 --- docs/how_to/README.md | 2 + docs/how_to/apps/environment_vars.md | 35 ++++++++++++++++ examples/example.toml | 7 ++-- examples/example.yaml | 7 +++- internal/app/release.go | 10 ----- internal/app/release_test.go | 43 ++++++------------- internal/app/utils.go | 40 ++++++++++++++++++ internal/app/utils_test.go | 62 ++++++++++++++++++++++++++++ 8 files changed, 161 insertions(+), 45 deletions(-) create mode 100644 docs/how_to/apps/environment_vars.md diff --git a/docs/how_to/README.md b/docs/how_to/README.md index d2b73326..20331880 100644 --- a/docs/how_to/README.md +++ b/docs/how_to/README.md @@ -28,6 +28,8 @@ It is recommended that you also check the [DSF spec](../desired_state_specificat - Manipulating Apps - [Basic operations](apps/basic.md) - [Passing secrets to releases](apps/secrets.md) + - [Using environment variables in helmsman file and helm values files](apps/environment_vars.md) + - [Apply K8S manifest before/after Helmsman operations](apps/lifecycle_hooks.md) - [Use multiple values files for apps](apps/multiple_values_files.md) - [Protect releases (apps)](apps/protection.md) - [Moving releases (apps) across namespaces](apps/moving_across_namespaces.md) diff --git a/docs/how_to/apps/environment_vars.md b/docs/how_to/apps/environment_vars.md new file mode 100644 index 00000000..ec2f3062 --- /dev/null +++ b/docs/how_to/apps/environment_vars.md @@ -0,0 +1,35 @@ +--- +version: v3.4.0 +--- + +# Using Environment Variables in Helmsman DSF and Helm values files + +You can use environment variables in any Helmsman desired state file or helm values files or [lifecycle hooks](lifecycle_hooks.md) files (K8S manifests). Both formats `${MY_VAR}` and `$MY_VAR` are accepted. + +> To expand environment variables in helm values files and lifecycle hooks files, you have to enable the `--subst-env-values`. + +## How does it work? + +Helmsman will expand those variables at run time. For helm values files and Helmsman lifecycle hooks files, the variables are expanded into temporary files which are used during runtime and removed at the end of execution. + +## Validating against unset env variables + +By default, Helmsman will validate that your environment variables are set before using them. If they are unset, an error will be produced. +The validation will parse Helmsman DSF files and other files (values files, lifecycle hooks files) line-by-line. This maybe become slow if you have very large files. + +## Skipping env variables validation + +Validation of environment variables being set is skipped in the following cases: +- If `--skip-validation` flag is used, no env variables validation is performed on any file. +- If `--no-env-subst` flag is used, no env variables validation is performed on Helmsman desired state files. +- If `--subst-env-values` flag is NOT used, no env variables validation is performed on helm values files and lifecycle hooks files. + +## Escaping the `$` sign + +### In Helmsman desired state files + +If you want to pass the `$` as is, you can escape it like so: `$$` + +### In Helm values files and lifecycle hooks files + +If you don't enable `--subst-env-values`, the `$` is passed as is without the need to escape it. However, if you enable `--subst-env-values` and want to pass the `$` as is, you have to escape it like so `$$` \ No newline at end of file diff --git a/examples/example.toml b/examples/example.toml index a8326aef..2275a357 100644 --- a/examples/example.toml +++ b/examples/example.toml @@ -10,6 +10,7 @@ context= "test-infra" # defaults to "default" if not provided org = "example.com/${ORG_PATH}/" maintainer = "k8s-admin (me@example.com)" description = "example Desired State File for demo purposes." + key= "${VALUE}" # paths to the certificate for connecting to the cluster @@ -85,7 +86,7 @@ context= "test-infra" # defaults to "default" if not provided # incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" # myS3repo = "s3://my-S3-private-repo/charts" # myGCSrepo = "gs://my-GCS-private-repo/charts" - # custom = "https://user:pass@mycustomrepo.org" + # custom = "https://$user:$pass@mycustomrepo.org" # define the desired state of your applications helm charts @@ -117,8 +118,8 @@ context= "test-infra" # defaults to "default" if not provided # [apps.argo.setString] # values to override values from values.yaml with values from env vars or directly entered-- useful for passing secrets to charts # AdminPassword="$SOME_PASSWORD" # $SOME_PASSWORD must exist in the environment # MyLongIntVar="1234567890" - [apps.argo.setString] - "images.tag"="v2.7.5" + [apps.argo.set] + "images.tag"="$$TAG" # $$ is escaped and $TAG is passed literally to images.tag (no env variable expansion) [apps.artifactory] diff --git a/examples/example.yaml b/examples/example.yaml index ab249b19..294491b8 100644 --- a/examples/example.yaml +++ b/examples/example.yaml @@ -1,4 +1,4 @@ -# version: v3.0.0 +# version: v3.4.0 # context defines the context of this Desired State File. # It is used to allow Helmsman identify which releases are managed by which DSF. @@ -10,6 +10,7 @@ metadata: org: "example.com/$ORG_PATH/" maintainer: "k8s-admin (me@example.com)" description: "example Desired State File for demo purposes." + key: ${VALUE} # paths to the certificate for connecting to the cluster # You can skip this if you use Helmsman on a machine with kubectl already connected to your k8s cluster. @@ -78,7 +79,7 @@ helmRepos: #incubator: "http://storage.googleapis.com/kubernetes-charts-incubator" #myS3repo: "s3://my-S3-private-repo/charts" #myGCSrepo: "gs://my-GCS-private-repo/charts" - #custom: "https://user:pass@mycustomrepo.org" + #custom: "https://$user:$pass@mycustomrepo.org" # define the desired state of your applications helm charts # each contains the following: @@ -107,6 +108,8 @@ apps: # postUpgrade: "job.yaml" # preDelete: "job.yaml" # postDelete: "job.yaml" + set: + "images.tag": $$TAG # $$ is escaped and $TAG is passed literally to images.tag (no env variable expansion) artifactory: namespace: "production" # maps to the namespace as defined in namespaces above diff --git a/internal/app/release.go b/internal/app/release.go index d5a2ebe6..603ea3ed 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -129,16 +129,6 @@ func (r *release) validate(appLabel string, names map[string]map[string]bool, s } names[r.Name][r.Namespace] = true - // add $$ escaping for $ strings - os.Setenv("HELMSMAN_DOLLAR", "$") - for k, v := range r.Set { - if strings.Contains(v, "$") { - if os.ExpandEnv(strings.Replace(v, "$$", "${HELMSMAN_DOLLAR}", -1)) == "" { - return errors.New("env var [ " + v + " ] is not set, but is wanted to be passed for [ " + k + " ] in [[ " + r.Name + " ]]") - } - } - } - return nil } diff --git a/internal/app/release_test.go b/internal/app/release_test.go index 8742ca63..94819bbd 100644 --- a/internal/app/release_test.go +++ b/internal/app/release_test.go @@ -251,7 +251,7 @@ func Test_validateRelease(t *testing.T) { }, want: "", }, { - name: "test case 14", + name: "test case 14 - non-existing hook file", args: args{ r: &release{ Name: "release14", @@ -260,23 +260,6 @@ func Test_validateRelease(t *testing.T) { Enabled: true, Chart: "repo/chartX", Version: "1.0", - ValuesFiles: []string{"./../../tests/values.yaml", "../../tests/values2.yaml"}, - Test: true, - Set: map[string]string{"some_var": "$SOME_VAR"}, - }, - s: st, - }, - want: "env var [ $SOME_VAR ] is not set, but is wanted to be passed for [ some_var ] in [[ release14 ]]", - }, { - name: "test case 15 - non-existing hook file", - args: args{ - r: &release{ - Name: "release15", - Description: "", - Namespace: "namespace", - Enabled: true, - Chart: "repo/chartX", - Version: "1.0", ValuesFile: "../../tests/values.yaml", Hooks: map[string]interface{}{"preInstall": "xyz.fake"}, }, @@ -284,10 +267,10 @@ func Test_validateRelease(t *testing.T) { }, want: "xyz.fake must be valid relative (from dsf file) file path.", }, { - name: "test case 16 - invalid hook file type", + name: "test case 15 - invalid hook file type", args: args{ r: &release{ - Name: "release16", + Name: "release15", Description: "", Namespace: "namespace", Enabled: true, @@ -300,10 +283,10 @@ func Test_validateRelease(t *testing.T) { }, want: "../../tests/values.xml must be of one the following file formats: .yaml, .yml", }, { - name: "test case 17 - valid hook file type", + name: "test case 16 - valid hook file type", args: args{ r: &release{ - Name: "release17", + Name: "release16", Description: "", Namespace: "namespace", Enabled: true, @@ -316,10 +299,10 @@ func Test_validateRelease(t *testing.T) { }, want: "", }, { - name: "test case 18 - valid hook file URL", + name: "test case 17 - valid hook file URL", args: args{ r: &release{ - Name: "release18", + Name: "release17", Description: "", Namespace: "namespace", Enabled: true, @@ -332,10 +315,10 @@ func Test_validateRelease(t *testing.T) { }, want: "", }, { - name: "test case 19 - invalid hook file URL", + name: "test case 18 - invalid hook file URL", args: args{ r: &release{ - Name: "release19", + Name: "release18", Description: "", Namespace: "namespace", Enabled: true, @@ -348,10 +331,10 @@ func Test_validateRelease(t *testing.T) { }, want: "https//raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml must be valid URL path to a raw file.", }, { - name: "test case 20 - invalid hook type 1", + name: "test case 19 - invalid hook type 1", args: args{ r: &release{ - Name: "release20", + Name: "release19", Description: "", Namespace: "namespace", Enabled: true, @@ -364,10 +347,10 @@ func Test_validateRelease(t *testing.T) { }, want: "afterDelete is an Invalid hook type.", }, { - name: "test case 21 - invalid hook type 2", + name: "test case 20 - invalid hook type 2", args: args{ r: &release{ - Name: "release21", + Name: "release20", Description: "", Namespace: "namespace", Enabled: true, diff --git a/internal/app/utils.go b/internal/app/utils.go index 8ec4663c..d4376bf8 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -1,6 +1,7 @@ package app import ( + "bufio" "bytes" "errors" "fmt" @@ -48,6 +49,9 @@ func fromTOML(file string, s *state) (bool, string) { tomlFile := string(rawTomlFile) if !flags.noEnvSubst { + if ok, err := validateEnvVars(tomlFile, file); !ok { + return false, err + } tomlFile = substituteEnv(tomlFile) } if !flags.noSSMSubst { @@ -98,6 +102,9 @@ func fromYAML(file string, s *state) (bool, string) { yamlFile := string(rawYamlFile) if !flags.noEnvSubst { + if ok, err := validateEnvVars(yamlFile, file); !ok { + return false, err + } yamlFile = substituteEnv(yamlFile) } if !flags.noSSMSubst { @@ -183,6 +190,9 @@ func substituteVarsInYaml(file string) string { yamlFile := string(rawYamlFile) if !flags.noEnvSubst && flags.substEnvValues { + if ok, err := validateEnvVars(yamlFile, file); !ok { + log.Critical(err) + } yamlFile = substituteEnv(yamlFile) } if !flags.noSSMSubst && flags.substSSMValues { @@ -324,6 +334,36 @@ func substituteEnv(name string) string { return name } +// validateEnvVars parses a string line-by-line and detect env variables in +// non-comment lines. It then checks that each env var found has a value. +func validateEnvVars(s string, filename string) (bool, string) { + if !flags.skipValidation { + log.Info("validating environment variables in " + filename) + var key string + r, _ := regexp.Compile("\\${([a-zA-Z_][a-zA-Z0-9_-]*)}|\\$([a-zA-Z_][a-zA-Z0-9_-]*)") + scanner := bufio.NewScanner(strings.NewReader(s)) + for scanner.Scan() { + text := strings.TrimSpace(scanner.Text()) + if !strings.HasPrefix(text, "#") { + for _, v := range r.FindAllStringSubmatch(strings.ReplaceAll(text, "$$", "!?"), -1) { + if v[1] != "" { + key = v[1] + } else { + key = v[2] + } + if _, ok := os.LookupEnv(key); !ok { + return false, v[0] + " is used as an env variable but is currently unset. Either set it or escape it like so: $" + v[0] + } + } + } + } + if err := scanner.Err(); err != nil { + log.Critical(err.Error()) + } + } + return true, "" +} + // substituteSSM checks if a string has an SSM parameter variable (contains '{{ssm: '), then it returns its value // if the env variable is empty or unset, an empty string is returned // if the string does not contain '$', it is returned as is. diff --git a/internal/app/utils_test.go b/internal/app/utils_test.go index b06b9c49..f47014a3 100644 --- a/internal/app/utils_test.go +++ b/internal/app/utils_test.go @@ -32,6 +32,8 @@ func Test_fromTOML(t *testing.T) { want: true, }, } + os.Setenv("ORG_PATH", "sample") + os.Setenv("VALUE", "sample") for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got, _ := fromTOML(tt.args.file, tt.args.s); got != tt.want { @@ -39,6 +41,8 @@ func Test_fromTOML(t *testing.T) { } }) } + os.Unsetenv("ORG_PATH") + os.Unsetenv("VALUE") } func Test_fromTOML_Expand(t *testing.T) { type args struct { @@ -75,6 +79,7 @@ func Test_fromTOML_Expand(t *testing.T) { } os.Setenv("SET_URI", "https://192.168.99.100:8443") os.Setenv("ORG_PATH", "sample") + os.Setenv("VALUE", "sample") for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err, msg := fromTOML(tt.args.file, tt.args.s) @@ -121,6 +126,9 @@ func Test_fromTOML_Expand(t *testing.T) { } }) } + os.Unsetenv("ORG_PATH") + os.Unsetenv("SET_URI") + os.Unsetenv("VALUE") } func Test_fromYAML(t *testing.T) { @@ -149,6 +157,8 @@ func Test_fromYAML(t *testing.T) { want: true, }, } + os.Setenv("VALUE", "sample") + os.Setenv("ORG_PATH", "sample") for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got, _ := fromYAML(tt.args.file, tt.args.s); got != tt.want { @@ -156,6 +166,54 @@ func Test_fromYAML(t *testing.T) { } }) } + os.Unsetenv("ORG_PATH") + os.Unsetenv("VALUE") +} + +func Test_fromYAML_UnsetVars(t *testing.T) { + type args struct { + file string + s *state + } + tests := []struct { + name string + args args + targetVar string + want bool + }{ + { + name: "test case 1 -- unset ORG_PATH env var", + args: args{ + file: "../../examples/example.yaml", + s: new(state), + }, + targetVar: "ORG_PATH", + want: false, + }, + { + name: "test case 2 -- unset VALUE var", + args: args{ + file: "../../examples/example.yaml", + s: new(state), + }, + targetVar: "VALUE", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.targetVar == "ORG_PATH" { + os.Setenv("VALUE", "sample") + } else if tt.targetVar == "VALUE" { + os.Setenv("ORG_PATH", "sample") + } + if got, _ := fromYAML(tt.args.file, tt.args.s); got != tt.want { + t.Errorf("fromYaml() = %v, want %v", got, tt.want) + } + }) + os.Unsetenv("ORG_PATH") + os.Unsetenv("VALUE") + } } func Test_fromYAML_Expand(t *testing.T) { @@ -193,6 +251,7 @@ func Test_fromYAML_Expand(t *testing.T) { } os.Setenv("SET_URI", "https://192.168.99.100:8443") os.Setenv("ORG_PATH", "sample") + os.Setenv("VALUE", "sample") for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err, msg := fromYAML(tt.args.file, tt.args.s) @@ -239,6 +298,9 @@ func Test_fromYAML_Expand(t *testing.T) { } }) } + os.Unsetenv("ORG_PATH") + os.Unsetenv("SET_URI") + os.Unsetenv("VALUE") } func Test_isOfType(t *testing.T) { From a9a6a239a5e002bcb46d046f76a85324114e2ba0 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Wed, 13 May 2020 17:08:24 +0200 Subject: [PATCH 0657/1127] fix #456 --- internal/app/decision_maker.go | 10 +++++----- internal/app/helm_helpers.go | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index 5254a707..b7f8b5d6 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -281,9 +281,9 @@ func (cs *currentState) cleanUntrackedReleases(s *state, p *plan) { // - If the release is already in the same namespace specified in the input, // it will be upgraded using the values file specified in the release info. // - If the release is already in the same namespace specified in the input but is using a different chart, -// it will be purge deleted and installed in the same namespace using the new chart. +// it will be uninstalled and installed in the same namespace using the new chart. // - If the release is NOT in the same namespace specified in the input, -// it will be purge deleted and installed in the new namespace. +// it will be uninstalled and installed in the new namespace. func (cs *currentState) inspectUpgradeScenario(r *release, p *plan) { rs, ok := cs.releases[r.key()] @@ -300,13 +300,13 @@ func (cs *currentState) inspectUpgradeScenario(r *release, p *plan) { } r.Version = version - if extractChartName(r.Chart) == rs.getChartName() && r.Version != rs.getChartVersion() { + if extractChartName(r.Chart, r.Version) == rs.getChartName() && r.Version != rs.getChartVersion() { // upgrade r.diff() r.upgrade(p) - p.addDecision("Release [ "+r.Name+" ] will be updated", r.Priority, change) + p.addDecision("Release [ "+r.Name+" ] will be upgraded", r.Priority, change) - } else if extractChartName(r.Chart) != rs.getChartName() { + } else if extractChartName(r.Chart, r.Version) != rs.getChartName() { r.reInstall(p) p.addDecision("Release [ "+r.Name+" ] is desired to use a new chart [ "+r.Chart+ " ]. Delete of the current release will be planned and new chart will be installed in namespace [ "+ diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index 2666c4b0..7fd99cdf 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -25,8 +25,8 @@ func helmCmd(args []string, desc string) command { } // extractChartName extracts the Helm chart name from full chart name in the desired state. -func extractChartName(releaseChart string) string { - cmd := helmCmd([]string{"show", "chart", releaseChart}, "Show chart information") +func extractChartName(releaseChart string, version string) string { + cmd := helmCmd([]string{"show", "chart", releaseChart, "--version", version}, "Show chart information for version "+version) result := cmd.exec() if result.code != 0 { From 2b83776f900bc813519cf64b5e0c2ab55507c588 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Wed, 13 May 2020 17:47:23 +0200 Subject: [PATCH 0658/1127] ignore inline comments from env vars regex evaluation --- internal/app/utils.go | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/internal/app/utils.go b/internal/app/utils.go index d4376bf8..d120fe9d 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -340,20 +340,22 @@ func validateEnvVars(s string, filename string) (bool, string) { if !flags.skipValidation { log.Info("validating environment variables in " + filename) var key string - r, _ := regexp.Compile("\\${([a-zA-Z_][a-zA-Z0-9_-]*)}|\\$([a-zA-Z_][a-zA-Z0-9_-]*)") + comment, _ := regexp.Compile("#(.*)$") + envVar, _ := regexp.Compile("\\${([a-zA-Z_][a-zA-Z0-9_-]*)}|\\$([a-zA-Z_][a-zA-Z0-9_-]*)") scanner := bufio.NewScanner(strings.NewReader(s)) for scanner.Scan() { - text := strings.TrimSpace(scanner.Text()) - if !strings.HasPrefix(text, "#") { - for _, v := range r.FindAllStringSubmatch(strings.ReplaceAll(text, "$$", "!?"), -1) { - if v[1] != "" { - key = v[1] - } else { - key = v[2] - } - if _, ok := os.LookupEnv(key); !ok { - return false, v[0] + " is used as an env variable but is currently unset. Either set it or escape it like so: $" + v[0] - } + // remove spaces from the single line, then replace $$ with !? to prevent it from matching the regex, + // then remove new line and inline comments from the text + text := comment.ReplaceAllString(strings.ReplaceAll(strings.TrimSpace(scanner.Text()), "$$", "!?"), "") + for _, v := range envVar.FindAllStringSubmatch(text, -1) { + // FindAllStringSubmatch may match the first (${MY_VAR}) or the second ($MY_VAR) group from the regex + if v[1] != "" { + key = v[1] + } else { + key = v[2] + } + if _, ok := os.LookupEnv(key); !ok { + return false, v[0] + " is used as an env variable but is currently unset. Either set it or escape it like so: $" + v[0] } } } From b79bba77cba37b1573c28b930962c77a67c57d3b Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Wed, 13 May 2020 18:04:34 +0200 Subject: [PATCH 0659/1127] report the right error message for releases that are in pending state --- internal/app/decision_maker.go | 8 ++++---- internal/app/helm_release.go | 11 +++++++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index b7f8b5d6..f5e96d83 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -117,7 +117,7 @@ func (cs *currentState) decide(r *release, s *state, p *plan) { p.addDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ "you remove its protection.", r.Priority, noop) } - } else if ok := cs.releaseExists(r, helmStatusDeleted); ok { + } else if ok := cs.releaseExists(r, helmStatusUninstalled); ok { if !r.isProtected(cs, s) { r.rollback(cs, p) // rollback } else { @@ -132,9 +132,9 @@ func (cs *currentState) decide(r *release, s *state, p *plan) { p.addDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ "you remove its protection.", r.Priority, noop) } - } else if ok := cs.releaseExists(r, helmStatusPending); ok { - log.Error("Release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ] is in pending-upgrade state. " + - "This means application is being upgraded outside of this Helmsman invocation's scope." + + } else if cs.releaseExists(r, helmStatusPendingInstall) || cs.releaseExists(r, helmStatusPendingUpgrade) || cs.releaseExists(r, helmStatusPendingRollback) || cs.releaseExists(r, helmStatusUninstalling) { + log.Error("Release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ] is in a pending (install/upgrade/rollback or uninstalling) state. " + + "This means application is being operated on outside of this Helmsman invocation's scope." + "Exiting, as this may cause issues when continuing...") os.Exit(1) } else { diff --git a/internal/app/helm_release.go b/internal/app/helm_release.go index b9968e43..ceeb5cfc 100644 --- a/internal/app/helm_release.go +++ b/internal/app/helm_release.go @@ -10,10 +10,13 @@ import ( ) const ( - helmStatusDeployed = "deployed" - helmStatusDeleted = "deleted" - helmStatusFailed = "failed" - helmStatusPending = "pending-upgrade" + helmStatusDeployed = "deployed" + helmStatusUninstalled = "uninstalled" + helmStatusFailed = "failed" + helmStatusPendingUpgrade = "pending-upgrade" + helmStatusPendingInstall = "pending-install" + helmStatusPendingRollback = "pending-rollback" + helmStatusUninstalling = "uninstalling" ) // helmRelease represents the current state of a release From e7352166baa05c8c6fee9e2f6e9d35591c0d9634 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Thu, 14 May 2020 13:17:06 +0200 Subject: [PATCH 0660/1127] add a note on set options format and fix wrong example. fixes #351 --- docs/desired_state_specification.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index c969b3e4..15483e81 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -371,6 +371,7 @@ Options: - **set** : is used to override certain values from values.yaml with values from environment variables (or ,starting from v1.3.0-rc, directly provided in the Desired State File). This is particularly useful for passing secrets to charts. If the an environment variable with the same name as the provided value exists, the environment variable value will be used, otherwise, the provided value will be used as is. The TOML stanza for this is `[apps..set]` - **setString** : is used to override String values from values.yaml or chart's defaults. This uses the `--set-string` flag in helm which is available only in helm >v2.9.0. This option is useful for image tags and the like. The TOML stanza for this is `[apps..setString]` - **setFile** : is used to override values from values.yaml or chart's defaults from provided file. This uses the `--set-file` flag in helm. This option is useful for embedding file contents in the values. The TOML stanza for this is `[apps..setFile]` +> set, setString and setFile can't take nested elements. If you need to provide nested values, you can combine them in one line with dots e.g. `TOML: "image.tag"=some_value` `YAML: "image.tag": some_value` - **helmFlags** : array of `helm` upgrade flags, is used to pass flags to helm install/upgrade commands. **These flags are not passed to helm diff**. For setting values, use **set**, **setString** or **setFile** instead. - **hooks** : defines global lifecycle hooks to apply yaml manifest before and/or after different helmsman operations. Check [here](how_to/apps/lifecycle_hooks.md) for more details. Unset hooks for a release are inherited from `globalHooks` in the [settings](#Settings) stanza. - **maxHistory** : defines the maximum number of helm revisions state (secrets/configmap) to keep. If unset, it will inherit the value of `settings.globalMaxHistory`, if that's also unset, it defaults to 10. @@ -437,8 +438,7 @@ apps: secret2: "$SECRET_ENV_VAR2" setString: longInt: "1234567890" - image: - tag: "1.0.0" + "image.tag": "1.0.0" hooks: successCondition: "Complete" successTimeout: "90s" From 5550676403f0bf5fb79aac7340d226e9b479ca16 Mon Sep 17 00:00:00 2001 From: Fareed Dudhia Date: Thu, 14 May 2020 15:57:00 +0100 Subject: [PATCH 0661/1127] fix missing quote --- internal/app/helm_helpers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index f7aa6053..975ccaa0 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -26,7 +26,7 @@ func helmCmd(args []string, desc string) command { // extractChartName extracts the Helm chart name from full chart name in the desired state. func extractChartName(releaseChart string) string { - cmd := helmCmd([]string{"show", "chart", releaseChart}, "Extracting chart information for [ "+releaseChart+" ]) + cmd := helmCmd([]string{"show", "chart", releaseChart}, "Extracting chart information for [ "+releaseChart+" ]") result := cmd.exec() if result.code != 0 { From 64180642e947f068562825a0a38a0627e527880d Mon Sep 17 00:00:00 2001 From: Mehdi Yedes Date: Thu, 14 May 2020 17:31:21 +0200 Subject: [PATCH 0662/1127] Fix typo in docs Signed-off-by: Mehdi Yedes --- docs/how_to/apps/lifecycle_hooks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how_to/apps/lifecycle_hooks.md b/docs/how_to/apps/lifecycle_hooks.md index 0134819d..06ef239a 100644 --- a/docs/how_to/apps/lifecycle_hooks.md +++ b/docs/how_to/apps/lifecycle_hooks.md @@ -43,7 +43,7 @@ For deployments, it is `Available` You can define two types of hooks in your desired state file: -- **Gloabl** hooks: are defined in the `settings` stanza and are inherited by all releases in the DSF if they haven't defined their own. +- **Global** hooks: are defined in the `settings` stanza and are inherited by all releases in the DSF if they haven't defined their own. These are defined as follows: ```toml From 03a258157874bc394220a06594a364bd7032ee1d Mon Sep 17 00:00:00 2001 From: Nicolas Degory Date: Thu, 14 May 2020 15:32:21 -0700 Subject: [PATCH 0663/1127] randomize temporary file names Signed-off-by: Nicolas Degory --- internal/app/utils.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/app/utils.go b/internal/app/utils.go index 177a3a06..9b3cd07b 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -288,8 +288,12 @@ func resolvePaths(relativeToFile string, s *state) { // and downloads/fetches the file locally into helmsman temp directory and returns // its absolute path func resolveOnePath(file string, dir string, downloadDest string) (string, error) { - destFileName := filepath.Join(downloadDest, path.Base(file)) - return filepath.Abs(downloadFile(file, dir, destFileName)) + if destFile, err := ioutil.TempFile(downloadDest, fmt.Sprintf("*%s", path.Base(file))); err != nil { + return "", err + } else { + _ = destFile.Close() + return filepath.Abs(downloadFile(file, dir, destFile.Name())) + } } // createTempDir creates a temp directory in a specific location with a pattern From 6772fb373294e82af2fc048a397a59403cb1443d Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Fri, 15 May 2020 16:20:53 +0200 Subject: [PATCH 0664/1127] don't validate env vars when files don't contain dollar signs --- internal/app/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/app/utils.go b/internal/app/utils.go index 9b3cd07b..80456093 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -341,7 +341,7 @@ func substituteEnv(name string) string { // validateEnvVars parses a string line-by-line and detect env variables in // non-comment lines. It then checks that each env var found has a value. func validateEnvVars(s string, filename string) (bool, string) { - if !flags.skipValidation { + if !flags.skipValidation && strings.Contains(s, "$") { log.Info("validating environment variables in " + filename) var key string comment, _ := regexp.Compile("#(.*)$") From e02eac75f706cf03f644dccf3fac79fb0faf0549 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Fri, 15 May 2020 16:34:35 +0200 Subject: [PATCH 0665/1127] releasing v3.4.1 --- .version | 2 +- README.md | 6 +++--- internal/app/main.go | 2 +- release-notes.md | 14 ++++++-------- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/.version b/.version index b299be97..3d67a549 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.3.0 +v3.4.1 diff --git a/README.md b/README.md index 9f425fc5..ec351f68 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.4.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.4.1&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -61,9 +61,9 @@ If you use private helm repos, you will need either `helm-gcs` or `helm-s3` plug Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. ``` # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.2.0/helmsman_3.2.0_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.4.1/helmsman_3.4.1_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.2.0/helmsman_3.2.0_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.4.1/helmsman_3.4.1_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/internal/app/main.go b/internal/app/main.go index fc17574b..6acad813 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -6,7 +6,7 @@ import ( const ( helmBin = "helm" - appVersion = "v3.4.0" + appVersion = "v3.4.1" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index 1dc536c2..ae9a03c4 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,16 +1,14 @@ -# v3.4.0 +# v3.4.1 If you migrating from Helmsman v1.x, it is recommended you read the [migration guide](https://github.com/Praqma/helmsman/blob/master/docs/how_to/misc/migrate_to_3.md) before using this release. > Starting from Helmsman v3.0.0 GA release, support for Helmsman v1.x will be limited to bug fixes. # Fixes and improvements: -- Respect dry-run flag with kubectl commands; PR #462 -- Add gnupg to Docker image; PR #449 -- Allow absolute paths for values/secrets/etc files; PR #459 -- Fix inconsistent helm args between install and upgrade; PR #458 +- Validate env vars used in DSF (and other) files are set in th environment before expanding them. PR #463 +- Pass chart version to helm show commands to avoid errors with develop chart versions. PR #464 +- Report the right error messages when releases are in pending state. PR #465 +- Randomize Helmsman's temporary file names to avoid errors when using the same file basename multiple times. PR #470 # New features: -- Add lifecycle hooks into Helmsman; PR #421 -- Support for using --history-max helm upgrade flag; PR #460 -- Support the Helm --set-file flag; PR #444 +None. From a5b2b41cd396e82a02298a36659ac717a1d6bf63 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Tue, 19 May 2020 10:16:10 +0200 Subject: [PATCH 0666/1127] Set two lines to Verbose after performance fixes were merged --- internal/app/decision_maker.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index 61c5a7a1..49be67e9 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -137,7 +137,7 @@ func (cs *currentState) makePlan(s *state) *plan { for nameResult := range namesC { // Is there a ... that can do this? I forget c, n := nameResult[0], nameResult[1] - log.Info("Extracted chart name [ " + c + " ].") + log.Verbose("Extracted chart name [ " + c + " ].") extractedChartNames[c] = n } @@ -148,7 +148,7 @@ func (cs *currentState) makePlan(s *state) *plan { // Better to fail early and return here? log.Error(m) } else { - log.Info("Extracted chart version from chart [ " + c + " ] with version [ " + v + " ]: '" + n + "'") + log.Verbose("Extracted chart version from chart [ " + c + " ] with version [ " + v + " ]: '" + n + "'") extractedChartVersions[c][v] = n } } From 3ad56c3e5596e9cea642779a73467ffced9724ae Mon Sep 17 00:00:00 2001 From: Nicolas Degory Date: Tue, 19 May 2020 09:46:28 -0700 Subject: [PATCH 0667/1127] Add the Critical(string) function to Logger --- internal/app/logging.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/internal/app/logging.go b/internal/app/logging.go index 9c850726..04eb4a6d 100644 --- a/internal/app/logging.go +++ b/internal/app/logging.go @@ -9,6 +9,18 @@ import ( type Logger struct { *logger.Logger + LoggerInterface +} + +type LoggerInterface interface { + Info(string) + Debug(string) + Verbose(string) + Error(string) + Warning(string) + Notice(string) + Critical(string) + Fatal(string) } var log *Logger @@ -45,6 +57,13 @@ func (l *Logger) Notice(message string) { baseLogger.Notice(message) } +func (l *Logger) Critical(message string) { + if _, err := url.ParseRequestURI(settings.SlackWebhook); err == nil { + notifySlack(message, settings.SlackWebhook, true, flags.apply) + } + baseLogger.Critical(message) +} + func (l *Logger) Fatal(message string) { if _, err := url.ParseRequestURI(settings.SlackWebhook); err == nil { notifySlack(message, settings.SlackWebhook, true, flags.apply) From 72d05e3b73d261bd4a7646bfa44e18eeee05c437 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Tue, 26 May 2020 10:18:24 +0200 Subject: [PATCH 0668/1127] releasing v3.4.2 --- .version | 2 +- README.md | 6 +++--- internal/app/main.go | 2 +- release-notes.md | 7 ++----- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/.version b/.version index 3d67a549..56bde7a6 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.4.1 +v3.4.2 diff --git a/README.md b/README.md index ec351f68..4f5c74b3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.4.1&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.4.2&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -61,9 +61,9 @@ If you use private helm repos, you will need either `helm-gcs` or `helm-s3` plug Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. ``` # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.4.1/helmsman_3.4.1_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.4.2/helmsman_3.4.2_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.4.1/helmsman_3.4.1_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.4.2/helmsman_3.4.2_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/internal/app/main.go b/internal/app/main.go index 6acad813..8fd8729b 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -6,7 +6,7 @@ import ( const ( helmBin = "helm" - appVersion = "v3.4.1" + appVersion = "v3.4.2" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index ae9a03c4..b12cabb6 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,14 +1,11 @@ -# v3.4.1 +# v3.4.2 If you migrating from Helmsman v1.x, it is recommended you read the [migration guide](https://github.com/Praqma/helmsman/blob/master/docs/how_to/misc/migrate_to_3.md) before using this release. > Starting from Helmsman v3.0.0 GA release, support for Helmsman v1.x will be limited to bug fixes. # Fixes and improvements: -- Validate env vars used in DSF (and other) files are set in th environment before expanding them. PR #463 -- Pass chart version to helm show commands to avoid errors with develop chart versions. PR #464 -- Report the right error messages when releases are in pending state. PR #465 -- Randomize Helmsman's temporary file names to avoid errors when using the same file basename multiple times. PR #470 +- Fix panic error when env vars are not set. PR #477 # New features: None. From f908fd7c442a5841defaaef205b357cf3fd5c7be Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Tue, 2 Jun 2020 10:13:55 +0200 Subject: [PATCH 0669/1127] Run helm tests also when upgrading chart --- internal/app/release.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/app/release.go b/internal/app/release.go index f0fb6f2a..8256f51b 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -355,6 +355,10 @@ func (r *release) upgrade(p *plan) { p.addCommand(cmd, r.Priority, r, before, after) + if r.Test { + r.test(p) + } + } // reInstall uninstalls a release and reinstalls it. From ea11b76daa2627d701285317608385b53aac646a Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Tue, 2 Jun 2020 15:31:00 +0200 Subject: [PATCH 0670/1127] fix docker builds. Fixes #482 use the correct docker build argument for helm version --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e752e473..bc624b2b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -47,10 +47,10 @@ jobs: TAG=$(git describe --abbrev=0 --tags) docker login -u $DOCKER_USER -p $DOCKER_PASS - docker build -t praqma/helmsman:$TAG-helm-v3.1.3 --build-arg HELM_VERSION=v3.1.3 . --no-cache + docker build -t praqma/helmsman:$TAG-helm-v3.1.3 --build-arg GLOBAL_HELM_VERSION=v3.1.3 . --no-cache docker push praqma/helmsman:$TAG-helm-v3.1.3 - docker build -t praqma/helmsman:$TAG-helm-v3.2.1 --build-arg HELM_VERSION=v3.2.1 . --no-cache + docker build -t praqma/helmsman:$TAG-helm-v3.2.1 --build-arg GLOBAL_HELM_VERSION=v3.2.1 . --no-cache docker push praqma/helmsman:$TAG-helm-v3.2.1 workflows: From dacefeed3d7858c5268773a356e23ae994d80f91 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Wed, 3 Jun 2020 08:46:22 +0200 Subject: [PATCH 0671/1127] Release v3.4.3 --- README.md | 2 +- internal/app/main.go | 2 +- release-notes.md | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4f5c74b3..1ddec987 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.4.2&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.4.3&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) diff --git a/internal/app/main.go b/internal/app/main.go index 8fd8729b..7a7384f4 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -6,7 +6,7 @@ import ( const ( helmBin = "helm" - appVersion = "v3.4.2" + appVersion = "v3.4.3" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index b12cabb6..37ef6778 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,11 +1,11 @@ -# v3.4.2 +# v3.4.3 If you migrating from Helmsman v1.x, it is recommended you read the [migration guide](https://github.com/Praqma/helmsman/blob/master/docs/how_to/misc/migrate_to_3.md) before using this release. > Starting from Helmsman v3.0.0 GA release, support for Helmsman v1.x will be limited to bug fixes. # Fixes and improvements: -- Fix panic error when env vars are not set. PR #477 +- Run helm tests also when upgrading chart . PR #484 # New features: None. From 17a6e56233dc0ce910ceb6941d627f1337ef4310 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Tue, 9 Jun 2020 09:50:58 +0200 Subject: [PATCH 0672/1127] Fix issue when nil is passed to execCommand for deletion of an untracked release --- internal/app/plan.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/internal/app/plan.go b/internal/app/plan.go index ceb9f4ea..3503713b 100644 --- a/internal/app/plan.go +++ b/internal/app/plan.go @@ -114,14 +114,14 @@ func (p *plan) exec() { <-sem }() for _, c := range cmd.beforeCommands { - execOne(c, cmd.targetRelease.Name) + execOne(c, cmd.targetRelease) } - execOne(cmd.Command, cmd.targetRelease.Name) + execOne(cmd.Command, cmd.targetRelease) if cmd.targetRelease != nil && !flags.dryRun && !flags.destroy { cmd.targetRelease.label() } for _, c := range cmd.afterCommands { - execOne(c, cmd.targetRelease.Name) + execOne(c, cmd.targetRelease) } // if result.code != 0 { // errorMsg := result.errors @@ -157,7 +157,7 @@ func (p *plan) exec() { } // execOne executes a single ordered command -func execOne(cmd command, targetRelease string) { +func execOne(cmd command, targetRelease *release) { log.Notice(cmd.Description) result := cmd.exec() @@ -166,7 +166,12 @@ func execOne(cmd command, targetRelease string) { if !flags.verbose { errorMsg = strings.Split(result.errors, "---")[0] } - log.Fatal(fmt.Sprintf("Command for release [%s] returned [ %d ] exit code and error message [ %s ]", targetRelease, result.code, strings.TrimSpace(errorMsg))) + if targetRelease != nil { + log.Fatal(fmt.Sprintf("Command for release [%s] returned [ %d ] exit code and error message [ %s ]", targetRelease.Name, result.code, strings.TrimSpace(errorMsg))) + } else { + log.Fatal(fmt.Sprintf("%s returned [ %d ] exit code and error message [ %s ]", cmd.Description, result.code, strings.TrimSpace(errorMsg))) + } + } else { log.Notice(result.output) log.Notice("Finished: " + cmd.Description) From 918e5e01798755a3455c7e1df5895d4d7de4fe96 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Tue, 9 Jun 2020 20:52:13 +0200 Subject: [PATCH 0673/1127] Fix missing parallel run for cmd exec after hooks were introduced --- internal/app/plan.go | 85 ++++++++++++++++++++++++-------------------- 1 file changed, 46 insertions(+), 39 deletions(-) diff --git a/internal/app/plan.go b/internal/app/plan.go index 3503713b..e85d1f2e 100644 --- a/internal/app/plan.go +++ b/internal/app/plan.go @@ -1,6 +1,7 @@ package app import ( + "errors" "fmt" "net/url" "sort" @@ -104,46 +105,18 @@ func (p *plan) exec() { sort.Ints(priorities) for _, priority := range priorities { - c := make(chan string, len(pl[priority])) + errorsChan := make(chan error, len(pl[priority])) for _, cmd := range pl[priority] { sem <- struct{}{} wg.Add(1) - go func(cmd orderedCommand) { - defer func() { - wg.Done() - <-sem - }() - for _, c := range cmd.beforeCommands { - execOne(c, cmd.targetRelease) - } - execOne(cmd.Command, cmd.targetRelease) - if cmd.targetRelease != nil && !flags.dryRun && !flags.destroy { - cmd.targetRelease.label() - } - for _, c := range cmd.afterCommands { - execOne(c, cmd.targetRelease) - } - // if result.code != 0 { - // errorMsg := result.errors - // if !flags.verbose { - // errorMsg = strings.Split(result.errors, "---")[0] - // } - // c <- fmt.Sprintf("Command for release [%s] returned [ %d ] exit code and error message [ %s ]", cmd.targetRelease.Name, result.code, strings.TrimSpace(errorMsg)) - // } else { - // log.Notice(result.output) - // log.Notice("Finished: " + cmd.Command.Description) - // if _, err := url.ParseRequestURI(settings.SlackWebhook); err == nil { - // notifySlack(cmd.Command.Description+" ... SUCCESS!", settings.SlackWebhook, false, true) - // } - // } - }(cmd) + go releaseWithHooks(cmd, &wg, sem, errorsChan) } wg.Wait() - close(c) - for err := range c { - if err != "" { + close(errorsChan) + for err := range errorsChan { + if err != nil { fail = true - log.Error(err) + log.Error(err.Error()) } } if fail { @@ -156,28 +129,62 @@ func (p *plan) exec() { } } +func releaseWithHooks(cmd orderedCommand, wg *sync.WaitGroup, sem chan struct{}, errors chan error) { + defer func() { + wg.Done() + <-sem + }() + for _, c := range cmd.beforeCommands { + if err := execOne(c, cmd.targetRelease); err != nil { + errors <- err + log.Verbose(err.Error()) + return + } + } + if err:= execOne(cmd.Command, cmd.targetRelease); err != nil { + errors <- err + log.Verbose(err.Error()) + return + } + if cmd.targetRelease != nil && !flags.dryRun && !flags.destroy { + cmd.targetRelease.label() + } + for _, c := range cmd.afterCommands { + if err := execOne(c, cmd.targetRelease); err != nil { + errors <- err + log.Verbose(err.Error()) + return + } + } +} + // execOne executes a single ordered command -func execOne(cmd command, targetRelease *release) { +func execOne(cmd command, targetRelease *release) error { log.Notice(cmd.Description) result := cmd.exec() - if result.code != 0 { errorMsg := result.errors if !flags.verbose { errorMsg = strings.Split(result.errors, "---")[0] } if targetRelease != nil { - log.Fatal(fmt.Sprintf("Command for release [%s] returned [ %d ] exit code and error message [ %s ]", targetRelease.Name, result.code, strings.TrimSpace(errorMsg))) + return errors.New( + fmt.Sprintf("Command for release [%s] returned [ %d ] exit code and error message [ %s ]", + targetRelease.Name, result.code, strings.TrimSpace(errorMsg))) } else { - log.Fatal(fmt.Sprintf("%s returned [ %d ] exit code and error message [ %s ]", cmd.Description, result.code, strings.TrimSpace(errorMsg))) + return errors.New( + fmt.Sprintf("%s returned [ %d ] exit code and error message [ %s ]", + cmd.Description, result.code, strings.TrimSpace(errorMsg))) } } else { log.Notice(result.output) - log.Notice("Finished: " + cmd.Description) + successMsg := "Finished: " + cmd.Description + log.Notice(successMsg) if _, err := url.ParseRequestURI(settings.SlackWebhook); err == nil { notifySlack(cmd.Description+" ... SUCCESS!", settings.SlackWebhook, false, true) } + return nil } } From 5eccc16534de7d1e00beea560d9a449fd6ec1fe5 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Tue, 16 Jun 2020 08:18:22 +0200 Subject: [PATCH 0674/1127] Use fmt.Errorf in place of errors.New --- internal/app/plan.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/internal/app/plan.go b/internal/app/plan.go index e85d1f2e..481b3ccc 100644 --- a/internal/app/plan.go +++ b/internal/app/plan.go @@ -1,7 +1,6 @@ package app import ( - "errors" "fmt" "net/url" "sort" @@ -168,13 +167,11 @@ func execOne(cmd command, targetRelease *release) error { errorMsg = strings.Split(result.errors, "---")[0] } if targetRelease != nil { - return errors.New( - fmt.Sprintf("Command for release [%s] returned [ %d ] exit code and error message [ %s ]", - targetRelease.Name, result.code, strings.TrimSpace(errorMsg))) + return fmt.Errorf("command for release [%s] returned [ %d ] exit code and error message [ %s ]", + targetRelease.Name, result.code, strings.TrimSpace(errorMsg)) } else { - return errors.New( - fmt.Sprintf("%s returned [ %d ] exit code and error message [ %s ]", - cmd.Description, result.code, strings.TrimSpace(errorMsg))) + return fmt.Errorf("%s returned [ %d ] exit code and error message [ %s ]", + cmd.Description, result.code, strings.TrimSpace(errorMsg)) } } else { From f52253cc99be0dcba538601c33cdd61b48f6d12e Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Tue, 16 Jun 2020 08:34:02 +0200 Subject: [PATCH 0675/1127] Release v3.4.4 --- README.md | 2 +- internal/app/main.go | 2 +- release-notes.md | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1ddec987..226cdc91 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.4.3&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.4.4&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) diff --git a/internal/app/main.go b/internal/app/main.go index 7a7384f4..fcd4fe8e 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -6,7 +6,7 @@ import ( const ( helmBin = "helm" - appVersion = "v3.4.3" + appVersion = "v3.4.4" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index 37ef6778..cd50e389 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,11 +1,12 @@ -# v3.4.3 +# v3.4.4 If you migrating from Helmsman v1.x, it is recommended you read the [migration guide](https://github.com/Praqma/helmsman/blob/master/docs/how_to/misc/migrate_to_3.md) before using this release. > Starting from Helmsman v3.0.0 GA release, support for Helmsman v1.x will be limited to bug fixes. # Fixes and improvements: -- Run helm tests also when upgrading chart . PR #484 +- Fix missing valid parallel run for cmd exec after hooks were introduced; PR #491 +- Fix issue when nil is passed to execCommand for deletion of an untracked release; PR #489 # New features: None. From 9b587bd400289ddafc42450d9832a8b13d66164c Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Tue, 16 Jun 2020 08:44:53 +0200 Subject: [PATCH 0676/1127] Release v3.4.4 again with helm v3.2.4 --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index bc624b2b..564488bb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -50,8 +50,8 @@ jobs: docker build -t praqma/helmsman:$TAG-helm-v3.1.3 --build-arg GLOBAL_HELM_VERSION=v3.1.3 . --no-cache docker push praqma/helmsman:$TAG-helm-v3.1.3 - docker build -t praqma/helmsman:$TAG-helm-v3.2.1 --build-arg GLOBAL_HELM_VERSION=v3.2.1 . --no-cache - docker push praqma/helmsman:$TAG-helm-v3.2.1 + docker build -t praqma/helmsman:$TAG-helm-v3.2.4 --build-arg GLOBAL_HELM_VERSION=v3.2.4 . --no-cache + docker push praqma/helmsman:$TAG-helm-v3.2.4 workflows: version: 2 From 985b8f11249592200db230113db8838d0c14bf9b Mon Sep 17 00:00:00 2001 From: Mehdi Yedes Date: Mon, 6 Jul 2020 16:01:11 +0200 Subject: [PATCH 0677/1127] Add gnupg and sops to the final Docker image Signed-off-by: Mehdi Yedes --- Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index fda61e68..5e63c5c9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -41,7 +41,7 @@ COPY --from=helm-installer /root/.cache/helm/plugins/ /root/.cache/helm/plugins/ COPY --from=helm-installer /root/.local/share/helm/plugins/ /root/.local/share/helm/plugins/ WORKDIR /go/src/github.com/Praqma/helmsman - + COPY . . RUN make test \ && LastTag=$(git describe --abbrev=0 --tags) \ @@ -54,12 +54,13 @@ RUN make test \ ### Final Image ### FROM alpine:${ALPINE_VERSION} as base -RUN apk add --update --no-cache ca-certificates git openssh ruby curl bash +RUN apk add --update --no-cache ca-certificates git openssh ruby curl bash gnupg RUN gem install hiera-eyaml --no-doc RUN update-ca-certificates COPY --from=helm-installer /usr/local/bin/kubectl /usr/local/bin/kubectl COPY --from=helm-installer /usr/local/bin/helm /usr/local/bin/helm +COPY --from=helm-installer /usr/local/bin/sops /usr/local/bin/sops COPY --from=helm-installer /root/.cache/helm/plugins/ /root/.cache/helm/plugins/ COPY --from=helm-installer /root/.local/share/helm/plugins/ /root/.local/share/helm/plugins/ From f8b2adb014b48f9526cf40b3d6452aee75db063d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Desch=C3=AAnes?= Date: Tue, 11 Aug 2020 13:30:16 -0400 Subject: [PATCH 0678/1127] helmRepos: remove "user:pass@" from url when adding repo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Antoine Deschênes --- internal/app/helm_helpers.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index 975ccaa0..98680ecc 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -128,7 +128,8 @@ func addHelmRepos(repos map[string]string) error { log.Fatal("helm repo " + repoName + " has incomplete basic auth info. Missing the password!") } basicAuthArgs = append(basicAuthArgs, "--username", u.User.Username(), "--password", p) - + u.User = nil + repoLink = u.String() } cmd := helmCmd(concat([]string{"repo", "add", repoName, repoLink}, basicAuthArgs), "Adding helm repository [ "+repoName+" ]") From af93177150cf3af65af10722bf6513210eaa80a3 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Mon, 21 Sep 2020 09:06:35 +0200 Subject: [PATCH 0679/1127] Add --force-update to helm repo add command (breaking change in 3.3.2 helm) --- internal/app/cli.go | 12 ++---------- internal/app/helm_helpers.go | 21 ++++++++++++++++++++- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/internal/app/cli.go b/internal/app/cli.go index 92d2de6e..79c75b30 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -6,7 +6,6 @@ import ( "os" "strings" - version "github.com/hashicorp/go-version" "github.com/imdario/mergo" "github.com/joho/godotenv" ) @@ -144,15 +143,8 @@ func (c *cli) parse() { c.parallel = 1 } - helmVersion := strings.TrimSpace(getHelmVersion()) - extractedHelmVersion := helmVersion - if !strings.HasPrefix(helmVersion, "v") { - extractedHelmVersion = strings.TrimSpace(strings.Split(helmVersion, ":")[1]) - } - log.Verbose("Helm client version: " + extractedHelmVersion) - v1, _ := version.NewVersion(extractedHelmVersion) - jsonConstraint, _ := version.NewConstraint(">=3.0.0") - if !jsonConstraint.Check(v1) { + log.Verbose("Helm client version: " + strings.TrimSpace(getHelmVersion())) + if checkHelmVersion("<3.0.0") { log.Fatal("this version of Helmsman does not work with helm releases older than 3.0.0") } diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index 98680ecc..7d2b4a10 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/hashicorp/go-version" "net/url" "strings" @@ -57,6 +58,20 @@ func getHelmVersion() string { return result.output } +func checkHelmVersion(constraint string) bool { + helmVersion := strings.TrimSpace(getHelmVersion()) + extractedHelmVersion := helmVersion + if !strings.HasPrefix(helmVersion, "v") { + extractedHelmVersion = strings.TrimSpace(strings.Split(helmVersion, ":")[1]) + } + v, _ := version.NewVersion(extractedHelmVersion) + jsonConstraint, _ := version.NewConstraint(constraint) + if jsonConstraint.Check(v) { + return true + } + return false +} + // helmPluginExists returns true if the plugin is present in the environment and false otherwise. // It takes as input the plugin's name to check if it is recognizable or not. e.g. diff func helmPluginExists(plugin string) bool { @@ -132,7 +147,11 @@ func addHelmRepos(repos map[string]string) error { repoLink = u.String() } - cmd := helmCmd(concat([]string{"repo", "add", repoName, repoLink}, basicAuthArgs), "Adding helm repository [ "+repoName+" ]") + repoAddFlags := "" + if checkHelmVersion(">=3.3.2") { + repoAddFlags += "--force-update" + } + cmd := helmCmd(concat([]string{"repo", "add", repoAddFlags, repoName, repoLink}, basicAuthArgs), "Adding helm repository [ "+repoName+" ]") // check current repository against existing repositories map in order to make sure it's missing and needs to be added if existingRepoUrl, ok := existingRepos[repoName]; ok { if repoLink == existingRepoUrl { From e69ecb0b0e3c0d105c8698bea8cba711c5c57734 Mon Sep 17 00:00:00 2001 From: Nicolas Degory Date: Thu, 14 May 2020 08:48:25 -0700 Subject: [PATCH 0680/1127] post-renderer option Signed-off-by: Nicolas Degory --- Makefile | 2 +- docs/desired_state_specification.md | 41 ++++++++--------- internal/app/plan.go | 4 +- internal/app/release.go | 69 ++++++++++++++++++----------- internal/app/release_test.go | 35 +++++++++++++++ tests/overlay.sample.yaml | 13 ++++++ tests/post-renderer.sh | 3 ++ 7 files changed, 118 insertions(+), 49 deletions(-) create mode 100644 tests/overlay.sample.yaml create mode 100755 tests/post-renderer.sh diff --git a/Makefile b/Makefile index bc42ca57..0ab5e68b 100644 --- a/Makefile +++ b/Makefile @@ -64,7 +64,7 @@ generate: .PHONY: generate repo: - @helm repo add stable https://kubernetes-charts.storage.googleapis.com + @helm repo list | grep -q "^stable " || helm repo add stable https://kubernetes-charts.storage.googleapis.com .PHONY: repo test: deps vet repo ## Run unit tests diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 15483e81..10bf2bce 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -347,34 +347,35 @@ Releases must have unique names which are defined under `apps`. Example: in `[ap Options: **Required** -- **namespace** : the namespace where the release should be deployed. The namespace should map to one of the ones defined in [namespaces](#namespaces). -- **enabled** : describes the required state of the release (true for enabled, false for disabled). Once a release is deployed, you can change it to false if you want to delete this release [default is false]. -- **chart** : the chart name. It should contain the repo name as well. Example: repoName/chartName. Changing the chart name means delete and reinstall this release using the new Chart. -- **version** : the chart version. +- **namespace** : the namespace where the release should be deployed. The namespace should map to one of the ones defined in [namespaces](#namespaces). +- **enabled** : describes the required state of the release (true for enabled, false for disabled). Once a release is deployed, you can change it to false if you want to delete this release [default is false]. +- **chart** : the chart name. It should contain the repo name as well. Example: repoName/chartName. Changing the chart name means delete and reinstall this release using the new Chart. +- **version** : the chart version. **Optional** -- **group** : group name this apps belongs to. It has no effect until Helmsman's flag `-group` is passed. Check this [doc](how_to/misc/limit-deployment-to-specific-group-of-apps.md) for more details. -- **description** : a release metadata for human readers. -- **valuesFile** : a valid path (URL, cloud bucket, local absolute/relative file path) to custom Helm values.yaml file. File extension must be `yaml`. Cannot be used with valuesFiles together. Leaving it empty uses the default chart values. -- **valuesFiles** : array of valid paths (URL, cloud bucket, local absolute/relative file path) to custom Helm values.yaml file. File extension must be `yaml`. Cannot be used with valuesFile together. Leaving it empty uses the default chart values. +- **group** : group name this apps belongs to. It has no effect until Helmsman's flag `-group` is passed. Check this [doc](how_to/misc/limit-deployment-to-specific-group-of-apps.md) for more details. +- **description** : a release metadata for human readers. +- **valuesFile** : a valid path (URL, cloud bucket, local absolute/relative file path) to custom Helm values.yaml file. File extension must be `yaml`. Cannot be used with valuesFiles together. Leaving it empty uses the default chart values. +- **valuesFiles** : array of valid paths (URL, cloud bucket, local absolute/relative file path) to custom Helm values.yaml file. File extension must be `yaml`. Cannot be used with valuesFile together. Leaving it empty uses the default chart values. > The values file(s) path is resolved when the DSF yaml/toml file is loaded, relative to the path that the dsf was loaded from. - **secretsFile** : a valid path (URL, cloud bucket, local absolute/relative file path) to custom Helm secrets.yaml file. File extension must be `yaml`. Cannot be used with secretsFiles together. Leaving it empty uses the default chart secrets. - **secretsFiles** : array of valid paths (URL, cloud bucket, local absolute/relative file path) to custom Helm secrets.yaml file. File extension must be `yaml`. Cannot be used with secretsFile together. Leaving it empty uses the default chart secrets. > The secrets file(s) path is resolved when the DSF yaml/toml file is loaded, relative to the path that the dsf was loaded from. > To use the secrets files you must have the helm-secrets plugin -- **test** : defines whether to run the chart tests whenever the release is installed. Default is false. -- **protected** : defines if the release should be protected against changes. Namespace-level protection has higher priority than this flag. Check the [protection guide](how_to/misc/protect_namespaces_and_releases.md) for more details. Default is false. -- **wait** : defines whether Helmsman should block execution until all k8s resources are in a ready state. Default is false. -- **timeout** : helm timeout in seconds. Default 300 seconds. -- **noHooks** : helm noHooks option. If true, it will disable pre/post upgrade hooks. Default is false. -- **priority** : defines the priority of applying operations on this release. Only negative values allowed and the lower the value, the higher the priority. Default priority is 0. Apps with equal priorities will be applied in the order they were added in your state file (DSF). -- **set** : is used to override certain values from values.yaml with values from environment variables (or ,starting from v1.3.0-rc, directly provided in the Desired State File). This is particularly useful for passing secrets to charts. If the an environment variable with the same name as the provided value exists, the environment variable value will be used, otherwise, the provided value will be used as is. The TOML stanza for this is `[apps..set]` -- **setString** : is used to override String values from values.yaml or chart's defaults. This uses the `--set-string` flag in helm which is available only in helm >v2.9.0. This option is useful for image tags and the like. The TOML stanza for this is `[apps..setString]` -- **setFile** : is used to override values from values.yaml or chart's defaults from provided file. This uses the `--set-file` flag in helm. This option is useful for embedding file contents in the values. The TOML stanza for this is `[apps..setFile]` -> set, setString and setFile can't take nested elements. If you need to provide nested values, you can combine them in one line with dots e.g. `TOML: "image.tag"=some_value` `YAML: "image.tag": some_value` -- **helmFlags** : array of `helm` upgrade flags, is used to pass flags to helm install/upgrade commands. **These flags are not passed to helm diff**. For setting values, use **set**, **setString** or **setFile** instead. +- **test** : defines whether to run the chart tests whenever the release is installed. Default is false. +- **protected** : defines if the release should be protected against changes. Namespace-level protection has higher priority than this flag. Check the [protection guide](how_to/misc/protect_namespaces_and_releases.md) for more details. Default is false. +- **wait** : defines whether Helmsman should block execution until all k8s resources are in a ready state. Default is false. +- **timeout** : helm timeout in seconds. Default 300 seconds. +- **noHooks** : helm noHooks option. If true, it will disable pre/post upgrade hooks. Default is false. +- **priority** : defines the priority of applying operations on this release. Only negative values allowed and the lower the value, the higher the priority. Default priority is 0. Apps with equal priorities will be applied in the order they were added in your state file (DSF). +- **set** : is used to override certain values from values.yaml with values from environment variables (or ,starting from v1.3.0-rc, directly provided in the Desired State File). This is particularly useful for passing secrets to charts. If the an environment variable with the same name as the provided value exists, the environment variable value will be used, otherwise, the provided value will be used as is. The TOML stanza for this is `[apps..set]` +- **setString** : is used to override String values from values.yaml or chart's defaults. This uses the `--set-string` flag in helm which is available only in helm >v2.9.0. This option is useful for image tags and the like. The TOML stanza for this is `[apps..setString]` +- **setFile** : is used to override values from values.yaml or chart's defaults from provided file. This uses the `--set-file` flag in helm. This option is useful for embedding file contents in the values. The TOML stanza for this is `[apps..setFile]` +> set, setString and setFile can't take nested elements. If you need to provide nested values, you can combine them in one line with dots e.g. `TOML: "image.tag"=some\_value` `YAML: "image.tag": some\_value` +- **helmFlags** : array of `helm` upgrade flags, is used to pass flags to helm install/upgrade commands. **These flags are not passed to helm diff**. For setting values, use **set**, **setString** or **setFile** instead. - **hooks** : defines global lifecycle hooks to apply yaml manifest before and/or after different helmsman operations. Check [here](how_to/apps/lifecycle_hooks.md) for more details. Unset hooks for a release are inherited from `globalHooks` in the [settings](#Settings) stanza. -- **maxHistory** : defines the maximum number of helm revisions state (secrets/configmap) to keep. If unset, it will inherit the value of `settings.globalMaxHistory`, if that's also unset, it defaults to 10. +- **maxHistory** : defines the maximum number of helm revisions state (secrets/configmap) to keep. If unset, it will inherit the value of `settings.globalMaxHistory`, if that's also unset, it defaults to 10. +- **postRenderer** : the path to an executable to be used for post rendering (requires Helm 3.1+ and helm-diff v3.1.2+) Example: diff --git a/internal/app/plan.go b/internal/app/plan.go index 481b3ccc..b3f4a57f 100644 --- a/internal/app/plan.go +++ b/internal/app/plan.go @@ -140,7 +140,7 @@ func releaseWithHooks(cmd orderedCommand, wg *sync.WaitGroup, sem chan struct{}, return } } - if err:= execOne(cmd.Command, cmd.targetRelease); err != nil { + if err := execOne(cmd.Command, cmd.targetRelease); err != nil { errors <- err log.Verbose(err.Error()) return @@ -171,7 +171,7 @@ func execOne(cmd command, targetRelease *release) error { targetRelease.Name, result.code, strings.TrimSpace(errorMsg)) } else { return fmt.Errorf("%s returned [ %d ] exit code and error message [ %s ]", - cmd.Description, result.code, strings.TrimSpace(errorMsg)) + cmd.Description, result.code, strings.TrimSpace(errorMsg)) } } else { diff --git a/internal/app/release.go b/internal/app/release.go index 8256f51b..0385f612 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -25,6 +25,7 @@ type release struct { ValuesFiles []string `yaml:"valuesFiles"` SecretsFile string `yaml:"secretsFile"` SecretsFiles []string `yaml:"secretsFiles"` + PostRenderer string `yaml:"postRenderer"` Test bool `yaml:"test"` Protected bool `yaml:"protected"` Wait bool `yaml:"wait"` @@ -114,6 +115,12 @@ func (r *release) validate(appLabel string, names map[string]map[string]bool, s } } + if r.PostRenderer != "" { + if _, err := os.Stat(r.PostRenderer); err != nil { + return fmt.Errorf(r.PostRenderer + " must be valid relative (from dsf file) file path.") + } + } + if r.Priority != 0 && r.Priority > 0 { return errors.New("priority can only be 0 or negative value, positive values are not allowed") } @@ -252,7 +259,7 @@ func validateChart(apps, chart, version string, c chan string) { // If chart is local, returns the given release version func getChartVersion(chart, version string) (string, string) { if isLocalChart(chart) { - log.Info("Chart [ " + chart + "] with version [ " + version + " ] was found locally.") + log.Info("Chart [ " + chart + " ] with version [ " + version + " ] was found locally.") return version, "" } @@ -554,6 +561,15 @@ func (r *release) getHelmFlags() []string { return concat(r.getNoHooks(), r.getWait(), r.getTimeout(), r.getMaxHistory(), flags.getDryRunFlags(), []string{force}, flgs) } +// getPostRenderer returns the post-renderer Helm flag +func (r *release) getPostRenderer() []string { + result := []string{} + if r.PostRenderer != "" { + result = append(result, "--post-renderer", r.PostRenderer) + } + return result +} + // getHelmArgsFor returns helm arguments for a specific helm operation func (r *release) getHelmArgsFor(action string, optionalNamespaceOverride ...string) []string { ns := r.Namespace @@ -562,9 +578,9 @@ func (r *release) getHelmArgsFor(action string, optionalNamespaceOverride ...str } switch action { case "install", "upgrade": - return concat([]string{"upgrade", r.Name, r.Chart, "--install", "--version", r.Version, "--namespace", r.Namespace}, r.getValuesFiles(), r.getSetValues(), r.getSetStringValues(), r.getSetFileValues(), r.getHelmFlags()) + return concat([]string{"upgrade", r.Name, r.Chart, "--install", "--version", r.Version, "--namespace", r.Namespace}, r.getValuesFiles(), r.getSetValues(), r.getSetStringValues(), r.getSetFileValues(), r.getHelmFlags(), r.getPostRenderer()) case "diff": - return concat([]string{"upgrade", r.Name, r.Chart, "--version", r.Version, "--namespace", r.Namespace}, r.getValuesFiles(), r.getSetValues(), r.getSetStringValues(), r.getSetFileValues()) + return concat([]string{"upgrade", r.Name, r.Chart, "--version", r.Version, "--namespace", r.Namespace}, r.getValuesFiles(), r.getSetValues(), r.getSetStringValues(), r.getSetFileValues(), r.getPostRenderer()) case "uninstall": return concat([]string{action, "--namespace", ns, r.Name}, flags.getDryRunFlags()) default: @@ -694,29 +710,30 @@ func (r *release) shouldWaitForHook(hookFile string, hookType string, namespace // print prints the details of the release func (r release) print() { fmt.Println("") - fmt.Println("\tname : ", r.Name) - fmt.Println("\tdescription : ", r.Description) - fmt.Println("\tnamespace : ", r.Namespace) - fmt.Println("\tenabled : ", r.Enabled) - fmt.Println("\tchart : ", r.Chart) - fmt.Println("\tversion : ", r.Version) - fmt.Println("\tvaluesFile : ", r.ValuesFile) - fmt.Println("\tvaluesFiles : ", strings.Join(r.ValuesFiles, ",")) - fmt.Println("\ttest : ", r.Test) - fmt.Println("\tprotected : ", r.Protected) - fmt.Println("\twait : ", r.Wait) - fmt.Println("\tpriority : ", r.Priority) - fmt.Println("\tSuccessCondition : ", r.Hooks["successCondition"]) - fmt.Println("\tSuccessTimeout : ", r.Hooks["successTimeout"]) - fmt.Println("\tDeleteOnSuccess : ", r.Hooks["deleteOnSuccess"]) - fmt.Println("\tpreInstall : ", r.Hooks["preInstall"]) - fmt.Println("\tpostInstall : ", r.Hooks["postInstall"]) - fmt.Println("\tpreUpgrade : ", r.Hooks["preUpgrade"]) - fmt.Println("\tpostUpgrade : ", r.Hooks["postUpgrade"]) - fmt.Println("\tpreDelete : ", r.Hooks["preDelete"]) - fmt.Println("\tpostDelete : ", r.Hooks["postDelete"]) - fmt.Println("\tno-hooks : ", r.NoHooks) - fmt.Println("\ttimeout : ", r.Timeout) + fmt.Println("\tname: ", r.Name) + fmt.Println("\tdescription: ", r.Description) + fmt.Println("\tnamespace: ", r.Namespace) + fmt.Println("\tenabled: ", r.Enabled) + fmt.Println("\tchart: ", r.Chart) + fmt.Println("\tversion: ", r.Version) + fmt.Println("\tvaluesFile: ", r.ValuesFile) + fmt.Println("\tvaluesFiles: ", strings.Join(r.ValuesFiles, ",")) + fmt.Println("\tpostRenderer: ", r.PostRenderer) + fmt.Println("\ttest: ", r.Test) + fmt.Println("\tprotected: ", r.Protected) + fmt.Println("\twait: ", r.Wait) + fmt.Println("\tpriority: ", r.Priority) + fmt.Println("\tSuccessCondition: ", r.Hooks["successCondition"]) + fmt.Println("\tSuccessTimeout: ", r.Hooks["successTimeout"]) + fmt.Println("\tDeleteOnSuccess: ", r.Hooks["deleteOnSuccess"]) + fmt.Println("\tpreInstall: ", r.Hooks["preInstall"]) + fmt.Println("\tpostInstall: ", r.Hooks["postInstall"]) + fmt.Println("\tpreUpgrade: ", r.Hooks["preUpgrade"]) + fmt.Println("\tpostUpgrade: ", r.Hooks["postUpgrade"]) + fmt.Println("\tpreDelete: ", r.Hooks["preDelete"]) + fmt.Println("\tpostDelete: ", r.Hooks["postDelete"]) + fmt.Println("\tno-hooks: ", r.NoHooks) + fmt.Println("\ttimeout: ", r.Timeout) fmt.Println("\tvalues to override from env:") printMap(r.Set, 2) fmt.Println("------------------- ") diff --git a/internal/app/release_test.go b/internal/app/release_test.go index d47fddfb..454ac978 100644 --- a/internal/app/release_test.go +++ b/internal/app/release_test.go @@ -362,6 +362,40 @@ func Test_validateRelease(t *testing.T) { s: st, }, want: "PreDelete is an Invalid hook type.", + }, { + name: "test case 21", + args: args{ + r: &release{ + Name: "release21", + Description: "", + Namespace: "namespace", + Enabled: true, + Chart: "repo/chartX", + Version: "1.0", + ValuesFile: "../../tests/values.yaml", + PostRenderer: "../../tests/post-renderer.sh", + Test: true, + }, + s: st, + }, + want: "", + }, { + name: "test case 22", + args: args{ + r: &release{ + Name: "release22", + Description: "", + Namespace: "namespace", + Enabled: true, + Chart: "repo/chartX", + Version: "1.0", + ValuesFile: "../../tests/values.yaml", + PostRenderer: "doesnt-exist.sh", + Test: true, + }, + s: st, + }, + want: "doesnt-exist.sh must be valid relative (from dsf file) file path.", }, } names := make(map[string]map[string]bool) @@ -462,6 +496,7 @@ func createFullReleasePointer(chart, version string) *release { HelmFlags: []string{}, NoHooks: false, Timeout: 0, + PostRenderer: "", } } diff --git a/tests/overlay.sample.yaml b/tests/overlay.sample.yaml new file mode 100644 index 00000000..6f763836 --- /dev/null +++ b/tests/overlay.sample.yaml @@ -0,0 +1,13 @@ +#@ load("@ytt:overlay", "overlay") +#@overlay/remove +#@overlay/match by=overlay.subset({"kind":"ServiceAccount"}),expects="1+" +--- +#@overlay/merge +#@overlay/match by=overlay.subset({"kind":"Deployment"}),expects="1+" +--- +apiVersion: apps/v1 +kind: Deployment +spec: + template: + spec: + serviceAccountName: default diff --git a/tests/post-renderer.sh b/tests/post-renderer.sh new file mode 100755 index 00000000..c2f06558 --- /dev/null +++ b/tests/post-renderer.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +ytt --ignore-unknown-comments -f - -f $(dirname $0)/overlay.sample.yaml From 69589c31a68f0480e5911e754626482c52bfdb7c Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Fri, 2 Oct 2020 10:47:40 +0200 Subject: [PATCH 0681/1127] Run helm tests as a last command of after commands from hooks --- internal/app/release.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/internal/app/release.go b/internal/app/release.go index 0385f612..38000b03 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -292,21 +292,21 @@ func getChartVersion(chart, version string) (string, string) { } // testRelease creates a Helm command to test a particular release. -func (r *release) test(p *plan) { +func (r *release) test(afterCommands *[]command) { cmd := helmCmd(r.getHelmArgsFor("test"), "Running tests for release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") - p.addCommand(cmd, r.Priority, r, []command{}, []command{}) + *afterCommands = append(*afterCommands, cmd) } // installRelease creates a Helm command to install a particular release in a particular namespace using a particular Tiller. func (r *release) install(p *plan) { - before, after := r.checkHooks("install", p) - cmd := helmCmd(r.getHelmArgsFor("install"), "Install release [ "+r.Name+" ] version [ "+r.Version+" ] in namespace [ "+r.Namespace+" ]") - p.addCommand(cmd, r.Priority, r, before, after) if r.Test { - r.test(p) + r.test(&after) } + + cmd := helmCmd(r.getHelmArgsFor("install"), "Install release [ "+r.Name+" ] version [ "+r.Version+" ] in namespace [ "+r.Namespace+" ]") + p.addCommand(cmd, r.Priority, r, before, after) } // uninstall uninstalls a release @@ -358,14 +358,14 @@ func (r *release) upgrade(p *plan) { before, after := r.checkHooks("upgrade", p) + if r.Test { + r.test(&after) + } + cmd := helmCmd(r.getHelmArgsFor("upgrade"), "Upgrade release [ "+r.Name+" ] to version [ "+r.Version+" ] in namespace [ "+r.Namespace+" ]") p.addCommand(cmd, r.Priority, r, before, after) - if r.Test { - r.test(p) - } - } // reInstall uninstalls a release and reinstalls it. From 4dd3f76f98e1477ec7e31493fd15a87c990ddc8c Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Fri, 2 Oct 2020 11:32:00 +0200 Subject: [PATCH 0682/1127] Run CI docker build in a loop; bump up golang, alpine, helm-diff version in Dockerfile --- .circleci/config.yml | 14 ++++++++------ Dockerfile | 10 +++++----- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 564488bb..47a99c53 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,7 +2,7 @@ version: 2 jobs: build: docker: - - image: docker:19.03.5-git + - image: docker:19.03.13-git steps: - checkout - setup_remote_docker @@ -44,14 +44,16 @@ jobs: - run: name: build docker images and push them to dockerhub command: | + helm_versions=( "v3.3.4" "v.3.3.3" "v3.3.2" "v3.3.1" "v3.3.0" "v3.2.4" ) + TAG=$(git describe --abbrev=0 --tags) docker login -u $DOCKER_USER -p $DOCKER_PASS - docker build -t praqma/helmsman:$TAG-helm-v3.1.3 --build-arg GLOBAL_HELM_VERSION=v3.1.3 . --no-cache - docker push praqma/helmsman:$TAG-helm-v3.1.3 - - docker build -t praqma/helmsman:$TAG-helm-v3.2.4 --build-arg GLOBAL_HELM_VERSION=v3.2.4 . --no-cache - docker push praqma/helmsman:$TAG-helm-v3.2.4 + for HELM_VERSION in "${helm_versions[@]}" + do + docker build -t praqma/helmsman:$TAG-helm-$HELM_VERSION --build-arg GLOBAL_HELM_VERSION=$HELM_VERSION . --no-cache + docker push praqma/helmsman:$TAG-helm-$HELM_VERSION + done workflows: version: 2 diff --git a/Dockerfile b/Dockerfile index 5e63c5c9..b6fcb88f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ -ARG GO_VERSION="1.13.8" -ARG ALPINE_VERSION="3.11" -ARG GLOBAL_KUBE_VERSION="v1.14.10" -ARG GLOBAL_HELM_VERSION="v3.1.1" -ARG GLOBAL_HELM_DIFF_VERSION="v3.1.1" +ARG GO_VERSION="1.15.2" +ARG ALPINE_VERSION="3.12" +ARG GLOBAL_KUBE_VERSION="v1.19.0" +ARG GLOBAL_HELM_VERSION="v3.3.4" +ARG GLOBAL_HELM_DIFF_VERSION="v3.1.3" ### Helm Installer ### FROM alpine:${ALPINE_VERSION} as helm-installer From 608d7ed0bc18dd59e8be1db754a111c8bdb6eaa7 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 6 Oct 2020 10:24:41 +0100 Subject: [PATCH 0683/1127] update release notes --- README.md | 2 +- internal/app/main.go | 2 +- release-notes.md | 13 ++++++++++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 226cdc91..6c1bf102 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.4.4&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.4.5&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) diff --git a/internal/app/main.go b/internal/app/main.go index fcd4fe8e..18284852 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -6,7 +6,7 @@ import ( const ( helmBin = "helm" - appVersion = "v3.4.4" + appVersion = "v3.4.5" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index cd50e389..8d0453ba 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,12 +1,19 @@ -# v3.4.4 +# v3.4.5 If you migrating from Helmsman v1.x, it is recommended you read the [migration guide](https://github.com/Praqma/helmsman/blob/master/docs/how_to/misc/migrate_to_3.md) before using this release. > Starting from Helmsman v3.0.0 GA release, support for Helmsman v1.x will be limited to bug fixes. # Fixes and improvements: -- Fix missing valid parallel run for cmd exec after hooks were introduced; PR #491 -- Fix issue when nil is passed to execCommand for deletion of an untracked release; PR #489 + +- Added `--force-update` to the `helm repo add` command to support helm v3.3.3+ +- Removed duplicated user and password when adding helm repos. +- Added missing packages to the final docker image. +- Updated some dependencies versions. +- Helm tests are now execurted as the last command after the hooks. # New features: + +- Added post-render option. + None. From 296f5ce6754e35a3b365054f5ae9bc1774979a1d Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 6 Oct 2020 10:30:18 +0100 Subject: [PATCH 0684/1127] update .version --- .version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.version b/.version index 56bde7a6..f119207e 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.4.2 +v3.4.5 From 38822ef3c129d35ca4390eddadfe2a384e370511 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Wed, 7 Oct 2020 18:34:12 +0100 Subject: [PATCH 0685/1127] fix: helm test resets release labels Signed-off-by: Luis Davim --- Makefile | 3 ++- internal/app/plan.go | 13 +++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 0ab5e68b..ee3b65ba 100644 --- a/Makefile +++ b/Makefile @@ -56,7 +56,7 @@ update-deps: ## Update depdendencies. Runs `go get -u` internally. @GOFLAGS="" go mod tidy @GOFLAGS="" go mod vendor -build: vet deps ## Build the package +build: deps vet ## Build the package @go build -o helmsman -ldflags '-X main.version="${TAG}-${DATE}" -extldflags "-static"' cmd/helmsman/main.go generate: @@ -65,6 +65,7 @@ generate: repo: @helm repo list | grep -q "^stable " || helm repo add stable https://kubernetes-charts.storage.googleapis.com + @helm repo update .PHONY: repo test: deps vet repo ## Run unit tests diff --git a/internal/app/plan.go b/internal/app/plan.go index b3f4a57f..0e0d9891 100644 --- a/internal/app/plan.go +++ b/internal/app/plan.go @@ -133,6 +133,12 @@ func releaseWithHooks(cmd orderedCommand, wg *sync.WaitGroup, sem chan struct{}, wg.Done() <-sem }() + if cmd.targetRelease == nil && !flags.destroy { + err := fmt.Errorf("nil target release") + errors <- err + log.Verbose(err.Error()) + return + } for _, c := range cmd.beforeCommands { if err := execOne(c, cmd.targetRelease); err != nil { errors <- err @@ -145,16 +151,15 @@ func releaseWithHooks(cmd orderedCommand, wg *sync.WaitGroup, sem chan struct{}, log.Verbose(err.Error()) return } - if cmd.targetRelease != nil && !flags.dryRun && !flags.destroy { - cmd.targetRelease.label() - } for _, c := range cmd.afterCommands { if err := execOne(c, cmd.targetRelease); err != nil { errors <- err log.Verbose(err.Error()) - return } } + if !flags.dryRun && !flags.destroy { + cmd.targetRelease.label() + } } // execOne executes a single ordered command From d687efdf59524a4ae000422c819b607a01def4d8 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Thu, 8 Oct 2020 16:13:08 +0100 Subject: [PATCH 0686/1127] prepare new release --- .version | 2 +- README.md | 2 +- internal/app/main.go | 2 +- release-notes.md | 10 +++------- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/.version b/.version index f119207e..d0ec26c7 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.4.5 +v3.4.6 diff --git a/README.md b/README.md index 6c1bf102..2ac05b6e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.4.5&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.4.6&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) diff --git a/internal/app/main.go b/internal/app/main.go index 18284852..7b4198d8 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -6,7 +6,7 @@ import ( const ( helmBin = "helm" - appVersion = "v3.4.5" + appVersion = "v3.4.6" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index 8d0453ba..86945bd3 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,4 +1,4 @@ -# v3.4.5 +# v3.4.6 If you migrating from Helmsman v1.x, it is recommended you read the [migration guide](https://github.com/Praqma/helmsman/blob/master/docs/how_to/misc/migrate_to_3.md) before using this release. @@ -6,14 +6,10 @@ If you migrating from Helmsman v1.x, it is recommended you read the [migration g # Fixes and improvements: -- Added `--force-update` to the `helm repo add` command to support helm v3.3.3+ -- Removed duplicated user and password when adding helm repos. -- Added missing packages to the final docker image. -- Updated some dependencies versions. -- Helm tests are now execurted as the last command after the hooks. +- Fixed an issue that would prevent Helmsman from recognizing releases after helm tests were executed. # New features: -- Added post-render option. +Bug fix release, not new features None. From 7cc1f223fa3d70c9f6e349a0b7e4be5c699ffa2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Desch=C3=AAnes?= Date: Fri, 9 Oct 2020 09:30:58 -0400 Subject: [PATCH 0687/1127] fix: handle default app name even when --skip-validation is used --- internal/app/cli.go | 7 +++++++ internal/app/release.go | 6 +----- internal/app/state.go | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/internal/app/cli.go b/internal/app/cli.go index 79c75b30..5f16ec91 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -240,6 +240,13 @@ func (c *cli) readState(s *state) { } } + // Default app.Name to state name when unset + for appName, app := range s.Apps { + if app.Name == "" { + app.Name = appName + } + } + if len(c.target) > 0 { s.TargetMap = map[string]bool{} for _, v := range c.target { diff --git a/internal/app/release.go b/internal/app/release.go index 38000b03..05dcfd9c 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -63,12 +63,8 @@ func (r *release) isConsideredToRun(s *state) bool { } // validate validates if a release inside a desired state meets the specifications or not. -// check the full specification @ https://github.com/Praqma/helmsman/docs/desired_state_spec.md +// check the full specification @ https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md func (r *release) validate(appLabel string, names map[string]map[string]bool, s *state) error { - if r.Name == "" { - r.Name = appLabel - } - if names[r.Name][r.Namespace] { return errors.New("release name must be unique within a given namespace") } diff --git a/internal/app/state.go b/internal/app/state.go index 9cdf6690..e1853163 100644 --- a/internal/app/state.go +++ b/internal/app/state.go @@ -67,7 +67,7 @@ func (s *state) toFile(file string) { } // validate validates that the values specified in the desired state are valid according to the desired state spec. -// check https://github.com/Praqma/Helmsman/docs/desired_state_spec.md for the detailed specification +// check https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md for the detailed specification func (s *state) validate() error { // apps From fccae22d8a6e5d278e159c60347538169a0a08ab Mon Sep 17 00:00:00 2001 From: Nicolas Degory Date: Mon, 26 Oct 2020 18:36:17 -0700 Subject: [PATCH 0688/1127] extractChartName works even when only dev versions are published Signed-off-by: Nicolas Degory --- internal/app/helm_helpers.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index 7d2b4a10..32ccc814 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -4,10 +4,11 @@ import ( "encoding/json" "errors" "fmt" - "github.com/hashicorp/go-version" "net/url" "strings" + "github.com/hashicorp/go-version" + "github.com/Praqma/helmsman/internal/gcs" ) @@ -27,7 +28,7 @@ func helmCmd(args []string, desc string) command { // extractChartName extracts the Helm chart name from full chart name in the desired state. func extractChartName(releaseChart string) string { - cmd := helmCmd([]string{"show", "chart", releaseChart}, "Extracting chart information for [ "+releaseChart+" ]") + cmd := helmCmd([]string{"show", "chart", "--devel", releaseChart}, "Extracting chart information for [ "+releaseChart+" ]") result := cmd.exec() if result.code != 0 { From da3d4f1d2d430a52e15e88c26d817813e6b2c46a Mon Sep 17 00:00:00 2001 From: Nicolas Degory Date: Mon, 26 Oct 2020 21:43:39 -0700 Subject: [PATCH 0689/1127] remove references to the deprecated stable repository Signed-off-by: Nicolas Degory --- Makefile | 2 +- README.md | 4 +- docs/deployment_strategies.md | 32 +++++------ docs/desired_state_specification.md | 23 ++++---- docs/how_to/apps/basic.md | 4 +- docs/how_to/apps/helm_tests.md | 8 +-- docs/how_to/apps/moving_across_namespaces.md | 16 +++--- docs/how_to/apps/multiple_values_files.md | 16 +++--- docs/how_to/apps/order.md | 20 +++---- docs/how_to/apps/override_namespaces.md | 24 ++++---- docs/how_to/apps/protection.md | 8 +-- docs/how_to/helm_repos/default.md | 2 +- .../misc/limit-deployment-to-specific-apps.md | 8 +-- ...it-deployment-to-specific-group-of-apps.md | 8 +-- .../misc/protect_namespaces_and_releases.md | 8 +-- examples/appsTemplates/config/helmsman.yaml | 8 ++- examples/example.toml | 2 - examples/example.yaml | 2 - examples/minimal-example.toml | 11 ++-- examples/minimal-example.yaml | 11 ++-- internal/app/release_test.go | 2 +- internal/app/state_test.go | 56 +++++++++---------- tests/invalid_example.toml | 4 +- tests/invalid_example.yaml | 4 +- 24 files changed, 143 insertions(+), 140 deletions(-) diff --git a/Makefile b/Makefile index ee3b65ba..5350afb9 100644 --- a/Makefile +++ b/Makefile @@ -64,7 +64,7 @@ generate: .PHONY: generate repo: - @helm repo list | grep -q "^stable " || helm repo add stable https://kubernetes-charts.storage.googleapis.com + @helm repo list | grep -q "^prometheus-community " || helm repo add prometheus-community https://prometheus-community.github.io/helm-charts @helm repo update .PHONY: repo diff --git a/README.md b/README.md index 2ac05b6e..4a46c6cd 100644 --- a/README.md +++ b/README.md @@ -61,9 +61,9 @@ If you use private helm repos, you will need either `helm-gcs` or `helm-s3` plug Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. ``` # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.4.2/helmsman_3.4.2_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.4.2/helmsman\_3.4.2\_linux\_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.4.2/helmsman_3.4.2_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.4.2/helmsman\_3.4.2\_darwin\_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/docs/deployment_strategies.md b/docs/deployment_strategies.md index 89341023..82bfb8c9 100644 --- a/docs/deployment_strategies.md +++ b/docs/deployment_strategies.md @@ -27,8 +27,8 @@ You can test 3rd party charts in designated namespaces (e.g, staging) within the protected = true [helmRepos] - stable = "https://kubernetes-charts.storage.googleapis.com" - incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" + jenkins = https://charts.jenkins.io + center = https://repo.chartcenter.io [apps] @@ -37,8 +37,8 @@ You can test 3rd party charts in designated namespaces (e.g, staging) within the description = "production jenkins" namespace = "production" enabled = true - chart = "stable/jenkins" - version = "0.9.1" # chart version + chart = "jenkins/jenkins" + version = "2.15.1" # chart version valuesFiles = [ "../my-jenkins-common-values.yaml", "../my-jenkins-production-values.yaml" ] @@ -47,8 +47,8 @@ You can test 3rd party charts in designated namespaces (e.g, staging) within the description = "production artifactory" namespace = "production" enabled = true - chart = "stable/artifactory" - version = "6.2.0" # chart version + chart = "center/jfrog/artifactory" + version = "11.4.2" # chart version valuesFile = "../my-artificatory-production-values.yaml" @@ -58,8 +58,8 @@ You can test 3rd party charts in designated namespaces (e.g, staging) within the description = "test release of jenkins, testing xyz feature" namespace = "staging" enabled = true - chart = "stable/jenkins" - version = "0.9.1" # chart version + chart = "jenkins/jenkins" + version = "2.15.1" # chart version valuesFiles = [ "../my-jenkins-common-values.yaml", "../my-jenkins-testing-values.yaml" ] ``` @@ -78,8 +78,8 @@ namespaces: protected: true helmRepos: - stable: "https://kubernetes-charts.storage.googleapis.com" - incubator: "http://storage.googleapis.com/kubernetes-charts-incubator" + jenkins: https://charts.jenkins.io + center: https://repo.chartcenter.io apps: jenkins: @@ -87,8 +87,8 @@ apps: description: "production jenkins" namespace: "production" enabled: true - chart: "stable/jenkins" - version: "0.9.1" # chart version + chart: "jenkins/jenkins" + version: "2.15.1" # chart version valuesFile: "../my-jenkins-production-values.yaml" artifactory: @@ -96,8 +96,8 @@ apps: description: "production artifactory" namespace: "production" enabled: true - chart: "stable/artifactory" - version: "6.2.0" # chart version + chart: "center/jfrog/artifactory" + version: "11.4.2" # chart version valuesFile: "../my-artifactory-production-values.yaml" # the jenkins release below is being tested in the staging namespace @@ -106,8 +106,8 @@ apps: description: "test release of jenkins, testing xyz feature" namespace: "staging" enabled: true - chart: "stable/jenkins" - version: "0.9.1" # chart version + chart: "jenkins/jenkins" + version: "2.15.1" # chart version valuesFile: "../my-jenkins-testing-values.yaml" ``` diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 10bf2bce..f4992652 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -240,8 +240,8 @@ Synopsis: defines the Helm repos where your charts can be found. You can add as Authenticating to private cloud helm repos: - **For S3 repos**: you need to have valid AWS access keys in your environment variables. See [here](https://github.com/hypnoglow/helm-s3#note-on-aws-authentication) for more details. - **For GCS repos**: check [here](https://www.terraform.io/docs/providers/google/index.html#authentication-json-file) for getting the required authentication file. Once you have the file, you have two options, either: - - set `GOOGLE_APPLICATION_CREDENTIALS` environment variable to contain the absolute path to your Google cloud credentials.json file. - - Or, set `GCLOUD_CREDENTIALS` environment variable to contain the content of the credentials.json file. + - set `GOOGLE\_APPLICATION\_CREDENTIALS` environment variable to contain the absolute path to your Google cloud credentials.json file. + - Or, set `GCLOUD\_CREDENTIALS` environment variable to contain the content of the credentials.json file. > You can also provide basic auth to access private repos that support basic auth. See the example below. @@ -252,8 +252,8 @@ Example: ```toml [helmRepos] -stable = "https://kubernetes-charts.storage.googleapis.com" -incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" +autoscaler = https://kubernetes.github.io/autoscaler +grafana = https://grafana.github.io/helm-charts myS3repo = "s3://my-S3-private-repo/charts" myGCSrepo = "gs://my-GCS-private-repo/charts" myPrivateRepo = "https://user:$TOP_SECRET_PASSWORD@mycustomprivaterepo.org" @@ -261,8 +261,8 @@ myPrivateRepo = "https://user:$TOP_SECRET_PASSWORD@mycustomprivaterepo.org" ```yaml helmRepos: - stable: "https://kubernetes-charts.storage.googleapis.com" - incubator: "http://storage.googleapis.com/kubernetes-charts-incubator" + autoscaler: https://kubernetes.github.io/autoscaler + grafana: https://grafana.github.io/helm-charts myS3repo: "s3://my-S3-private-repo/charts" myGCSrepo: "gs://my-GCS-private-repo/charts" myPrivateRepo: "https://user:$TOP_SECRET_PASSWORD@mycustomprivaterepo.org" @@ -301,6 +301,9 @@ Synopsis: allows for YAML (TOML has no variable reference support) object creati Examples: ```yaml +helmRepos: + jenkins: https://charts.jenkins.io + appsTemplates: default: &template @@ -322,7 +325,7 @@ apps: <<: *template name: "jenkins-stage" namespace: "staging" - chart: "stable/jenkins" + chart: "jenkins/jenkins" version: "0.9.2" priority: -3 @@ -330,7 +333,7 @@ apps: <<: *template_custom name: "jenkins-prod" namespace: "production" - chart: "stable/jenkins" + chart: "jenkins/jenkins" version: "0.9.0" priority: -2 @@ -390,7 +393,7 @@ Example: namespace = "staging" enabled = true group = "critical" - chart = "stable/jenkins" + chart = "jenkins/jenkins" version = "0.9.0" valuesFile = "" test = true @@ -423,7 +426,7 @@ apps: namespace: "staging" enabled: true group: "critical" - chart: "stable/jenkins" + chart: "jenkins/jenkins" version: "0.9.0" valuesFile: "" test: true diff --git a/docs/how_to/apps/basic.md b/docs/how_to/apps/basic.md index e7e6a5c3..d3df002c 100644 --- a/docs/how_to/apps/basic.md +++ b/docs/how_to/apps/basic.md @@ -65,8 +65,8 @@ apps: description: "jenkins" namespace: "staging" enabled: false # this tells helmsman to delete it - chart: "stable/jenkins" - version: "0.9.1" + chart: "jenkins/jenkins" + version: "2.15.1" valuesFile: "" test: false diff --git a/docs/how_to/apps/helm_tests.md b/docs/how_to/apps/helm_tests.md index c59629c1..412f510e 100644 --- a/docs/how_to/apps/helm_tests.md +++ b/docs/how_to/apps/helm_tests.md @@ -16,8 +16,8 @@ You can specify that you would like a chart to be tested whenever it is installe description = "jenkins" namespace = "staging" enabled = true - chart = "stable/jenkins" - version = "0.9.1" + chart = "jenkins/jenkins" + version = "2.15.1" valuesFile = "" test = true # setting this to true, means you want the charts tests to be run on this release when it is installed. @@ -33,8 +33,8 @@ apps: description: "jenkins" namespace: "staging" enabled: true - chart: "stable/jenkins" - version: "0.9.1" + chart: "jenkins/jenkins" + version: "2.15.1" valuesFile: "" test: true # setting this to true, means you want the charts tests to be run on this release when it is installed. diff --git a/docs/how_to/apps/moving_across_namespaces.md b/docs/how_to/apps/moving_across_namespaces.md index c6ab4f61..add4e6bd 100644 --- a/docs/how_to/apps/moving_across_namespaces.md +++ b/docs/how_to/apps/moving_across_namespaces.md @@ -22,8 +22,8 @@ If you have a workflow for testing a release first in the `staging` namespace th description = "jenkins" namespace = "staging" # this is where it is deployed enabled = true - chart = "stable/jenkins" - version = "0.9.1" + chart = "jenkins/jenkins" + version = "2.15.1" valuesFile = "" test = true @@ -43,8 +43,8 @@ apps: description: "jenkins" namespace: "staging" # this is where it is deployed enabled: true - chart: "stable/jenkins" - version: "0.9.1" + chart: "jenkins/jenkins" + version: "2.15.1" valuesFile: "" test: true @@ -67,8 +67,8 @@ Then if you change the namespace key for jenkins: description = "jenkins" namespace = "production" # we want to move it to production enabled = true - chart = "stable/jenkins" - version = "0.9.1" + chart = "jenkins/jenkins" + version = "2.15.1" valuesFile = "" test = true @@ -88,8 +88,8 @@ apps: description: "jenkins" namespace: "production" # we want to move it to production enabled: true - chart: "stable/jenkins" - version: "0.9.1" + chart: "jenkins/jenkins" + version: "2.15.1" valuesFile: "" test: true diff --git a/docs/how_to/apps/multiple_values_files.md b/docs/how_to/apps/multiple_values_files.md index d821d2e6..ad3cabe1 100644 --- a/docs/how_to/apps/multiple_values_files.md +++ b/docs/how_to/apps/multiple_values_files.md @@ -16,8 +16,8 @@ You can include multiple yaml value files to separate configuration for differen description = "production jenkins" namespace = "production" enabled = true - chart = "stable/jenkins" - version = "0.9.1" # chart version + chart = "jenkins/jenkins" + version = "2.15.1" # chart version valuesFiles = [ "../my-jenkins-common-values.yaml", "../my-jenkins-production-values.yaml" @@ -28,8 +28,8 @@ You can include multiple yaml value files to separate configuration for differen description = "test release of jenkins, testing xyz feature" namespace = "staging" enabled = true - chart = "stable/jenkins" - version = "0.9.1" # chart version + chart = "jenkins/jenkins" + version = "2.15.1" # chart version valuesFiles = [ "../my-jenkins-common-values.yaml", "../my-jenkins-testing-values.yaml" @@ -47,8 +47,8 @@ apps: description: "production jenkins" namespace: "production" enabled: true - chart: "stable/jenkins" - version: "0.9.1" # chart version + chart: "jenkins/jenkins" + version: "2.15.1" # chart version valuesFiles: - "../my-jenkins-common-values.yaml" - "../my-jenkins-production-values.yaml" @@ -59,8 +59,8 @@ apps: description: "test release of jenkins, testing xyz feature" namespace: "staging" enabled: true - chart: "stable/jenkins" - version: "0.9.1" # chart version + chart: "jenkins/jenkins" + version: "2.15.1" # chart version valuesFiles: - "../my-jenkins-common-values.yaml" - "../my-jenkins-testing-values.yaml" diff --git a/docs/how_to/apps/order.md b/docs/how_to/apps/order.md index 185b4a4f..2aa25ef3 100644 --- a/docs/how_to/apps/order.md +++ b/docs/how_to/apps/order.md @@ -28,16 +28,16 @@ If you want your apps to be deleted in the reverse order as they where created, protected = true [helmRepos] -stable = "https://kubernetes-charts.storage.googleapis.com" -incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" +jenkins = https://charts.jenkins.io +center = https://repo.chartcenter.io [apps] [apps.jenkins] description = "jenkins" namespace = "staging" # maps to the namespace as defined in environments above enabled = true # change to false if you want to delete this app release [empty = false] - chart = "stable/jenkins" # changing the chart name means delete and recreate this chart - version = "0.14.3" # chart version + chart = "jenkins/jenkins" # changing the chart name means delete and recreate this chart + version = "2.15.1" # chart version valuesFile = "" # leaving it empty uses the default chart values priority= -2 @@ -45,8 +45,8 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" description = "jenkins" namespace = "staging" # maps to the namespace as defined in environments above enabled = true # change to false if you want to delete this app release [empty = false] - chart = "stable/jenkins" # changing the chart name means delete and recreate this chart - version = "0.14.3" # chart version + chart = "jenkins/jenkins" # changing the chart name means delete and recreate this chart + version = "2.15.1" # chart version valuesFile = "" # leaving it empty uses the default chart values @@ -54,8 +54,8 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" description = "jenkins" namespace = "production" # maps to the namespace as defined in environments above enabled = true # change to false if you want to delete this app release [empty = false] - chart = "stable/jenkins" # changing the chart name means delete and recreate this chart - version = "0.14.3" # chart version + chart = "jenkins/jenkins" # changing the chart name means delete and recreate this chart + version = "2.15.1" # chart version valuesFile = "" # leaving it empty uses the default chart values priority= -3 @@ -63,8 +63,8 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" description = "artifactory" namespace = "staging" # maps to the namespace as defined in environments above enabled = true # change to false if you want to delete this app release [empty = false] - chart = "stable/artifactory" # changing the chart name means delete and recreate this chart - version = "7.0.6" # chart version + chart = "center/jfrog/artifactory" # changing the chart name means delete and recreate this chart + version = "11.4.2" # chart version valuesFile = "" # leaving it empty uses the default chart values priority= -2 ``` diff --git a/docs/how_to/apps/override_namespaces.md b/docs/how_to/apps/override_namespaces.md index a9eca736..b2d143af 100644 --- a/docs/how_to/apps/override_namespaces.md +++ b/docs/how_to/apps/override_namespaces.md @@ -27,8 +27,8 @@ kubeContext = "minikube" prtoected = true [helmRepos] -stable = "https://kubernetes-charts.storage.googleapis.com" -incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" +jenkins = https://charts.jenkins.io +center = https://repo.chartcenter.io [apps] @@ -37,16 +37,16 @@ incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" description = "jenkins" namespace = "production" # maps to the namespace as defined in environments above enabled = true # change to false if you want to delete this app release [empty = false] - chart = "stable/jenkins" # changing the chart name means delete and recreate this chart - version = "0.14.3" # chart version + chart = "jenkins/jenkins" # changing the chart name means delete and recreate this chart + version = "2.15.1" # chart version valuesFile = "" # leaving it empty uses the default chart values [apps.artifactory] description = "artifactory" namespace = "staging" # maps to the namespace as defined in environments above enabled = true # change to false if you want to delete this app release [empty = false] - chart = "stable/artifactory" # changing the chart name means delete and recreate this chart - version = "7.0.6" # chart version + chart = "center/jfrog/artifactory" # changing the chart name means delete and recreate this chart + version = "11.4.2" # chart version valuesFile = "" # leaving it empty uses the default chart values ``` @@ -67,8 +67,8 @@ namespaces: protected: true helmRepos: - stable: "https://kubernetes-charts.storage.googleapis.com" - incubator: "http://storage.googleapis.com/kubernetes-charts-incubator" + jenkins: https://charts.jenkins.io + center: https://repo.chartcenter.io apps: @@ -77,16 +77,16 @@ apps: description: "jenkins" namespace: "production" # maps to the namespace as defined in environments above enabled: true # change to false if you want to delete this app release [empty: false] - chart: "stable/jenkins" # changing the chart name means delete and recreate this chart - version: "0.14.3" # chart version + chart: "jenkins/jenkins" # changing the chart name means delete and recreate this chart + version: "2.15.1" # chart version valuesFile: "" # leaving it empty uses the default chart values artifactory: description: "artifactory" namespace: "staging" # maps to the namespace as defined in environments above enabled: true # change to false if you want to delete this app release [empty: false] - chart: "stable/artifactory" # changing the chart name means delete and recreate this chart - version: "7.0.6" # chart version + chart: "center/jfrog/artifactory" # changing the chart name means delete and recreate this chart + version: "11.4.2" # chart version valuesFile: "" # leaving it empty uses the default chart values ``` diff --git a/docs/how_to/apps/protection.md b/docs/how_to/apps/protection.md index 0f9a7b7b..b67edd9f 100644 --- a/docs/how_to/apps/protection.md +++ b/docs/how_to/apps/protection.md @@ -14,8 +14,8 @@ Here is an example of a protected app: [apps.jenkins] namespace = "staging" enabled = true - chart = "stable/jenkins" - version = "0.9.1" + chart = "jenkins/jenkins" + version = "2.15.1" protected = true # defining this release to be protected. ``` @@ -25,7 +25,7 @@ apps: jenkins: namespace: "staging" enabled: true - chart: "stable/jenkins" - version: "0.9.1" + chart: "jenkins/jenkins" + version: "2.15.1" protected: true # defining this release to be protected. ``` diff --git a/docs/how_to/helm_repos/default.md b/docs/how_to/helm_repos/default.md index 86514597..34177b62 100644 --- a/docs/how_to/helm_repos/default.md +++ b/docs/how_to/helm_repos/default.md @@ -47,7 +47,7 @@ helmRepos: ``` -This example would have `stable` defined with a Google stable repo: +This example would have `stable` defined with a Google deprecated stable repo: ```toml ... diff --git a/docs/how_to/misc/limit-deployment-to-specific-apps.md b/docs/how_to/misc/limit-deployment-to-specific-apps.md index 79939d48..8662d827 100644 --- a/docs/how_to/misc/limit-deployment-to-specific-apps.md +++ b/docs/how_to/misc/limit-deployment-to-specific-apps.md @@ -19,14 +19,14 @@ apps: jenkins: namespace: "staging" # maps to the namespace as defined in namespaces above enabled: true # change to false if you want to delete this app release empty: false: - chart: "stable/jenkins" # changing the chart name means delete and recreate this chart - version: "0.14.3" # chart version + chart: "jenkins/jenkins" # changing the chart name means delete and recreate this chart + version: "2.15.1" # chart version artifactory: namespace: "production" # maps to the namespace as defined in namespaces above enabled: true # change to false if you want to delete this app release empty: false: - chart: "stable/artifactory" # changing the chart name means delete and recreate this chart - version: "7.0.6" # chart version + chart: "center/jfrog/artifactory" # changing the chart name means delete and recreate this chart + version: "11.4.2" # chart version # ... ``` diff --git a/docs/how_to/misc/limit-deployment-to-specific-group-of-apps.md b/docs/how_to/misc/limit-deployment-to-specific-group-of-apps.md index 62649542..5bb48942 100644 --- a/docs/how_to/misc/limit-deployment-to-specific-group-of-apps.md +++ b/docs/how_to/misc/limit-deployment-to-specific-group-of-apps.md @@ -20,15 +20,15 @@ apps: namespace: "staging" # maps to the namespace as defined in namespaces above group: "critical" # group name enabled: true # change to false if you want to delete this app release empty: false: - chart: "stable/jenkins" # changing the chart name means delete and recreate this chart - version: "0.14.3" # chart version + chart: "jenkins/jenkins" # changing the chart name means delete and recreate this chart + version: "2.15.1" # chart version artifactory: namespace: "production" # maps to the namespace as defined in namespaces above group: "sidecar" # group name enabled: true # change to false if you want to delete this app release empty: false: - chart: "stable/artifactory" # changing the chart name means delete and recreate this chart - version: "7.0.6" # chart version + chart: "center/jfrog/artifactory" # changing the chart name means delete and recreate this chart + version: "11.4.2" # chart version # ... ``` diff --git a/docs/how_to/misc/protect_namespaces_and_releases.md b/docs/how_to/misc/protect_namespaces_and_releases.md index 9484d824..289e33ad 100644 --- a/docs/how_to/misc/protect_namespaces_and_releases.md +++ b/docs/how_to/misc/protect_namespaces_and_releases.md @@ -42,8 +42,8 @@ Protection is supported in two forms: [apps.jenkins] namespace = "staging" enabled = true - chart = "stable/jenkins" - version = "0.9.1" + chart = "jenkins/jenkins" + version = "2.15.1" protected = true # defining this release to be protected. ``` @@ -53,8 +53,8 @@ apps: jenkins: namespace: "staging" enabled: true - chart: "stable/jenkins" - version: "0.9.1" + chart: "jenkins/jenkins" + version: "2.15.1" protected: true # defining this release to be protected. ``` diff --git a/examples/appsTemplates/config/helmsman.yaml b/examples/appsTemplates/config/helmsman.yaml index ef9f6198..594384dc 100644 --- a/examples/appsTemplates/config/helmsman.yaml +++ b/examples/appsTemplates/config/helmsman.yaml @@ -37,7 +37,9 @@ namespaces: memory: "400Mi" helmRepos: - stable: "https://kubernetes-charts.storage.googleapis.com" + jenkins: "https://charts.jenkins.io" + center: "https://repo.chartcenter.io" + bitnami: "https://charts.bitnami.com/bitnami" puppet: "https://puppetlabs.github.io/puppetserver-helm-chart" appsTemplates: @@ -66,8 +68,8 @@ appsTemplates: tomcat: &tomcat enabled: true priority: -2 - chart: "stable/tomcat" - version: "0.4.0" # chart version + chart: "bitnami/tomcat" + version: "6.5.3" # chart version valuesFiles: [ "../apps/tomcat/common-values.yaml", ] diff --git a/examples/example.toml b/examples/example.toml index 2275a357..c455be77 100644 --- a/examples/example.toml +++ b/examples/example.toml @@ -82,8 +82,6 @@ context= "test-infra" # defaults to "default" if not provided [helmRepos] argo = "https://argoproj.github.io/argo-helm" jfrog = "https://charts.jfrog.io" - # stable = "https://kubernetes-charts.storage.googleapis.com" - # incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" # myS3repo = "s3://my-S3-private-repo/charts" # myGCSrepo = "gs://my-GCS-private-repo/charts" # custom = "https://$user:$pass@mycustomrepo.org" diff --git a/examples/example.yaml b/examples/example.yaml index 294491b8..ab8f52fa 100644 --- a/examples/example.yaml +++ b/examples/example.yaml @@ -75,8 +75,6 @@ namespaces: helmRepos: argo: "https://argoproj.github.io/argo-helm" jfrog: "https://charts.jfrog.io" - #stable: "https://kubernetes-charts.storage.googleapis.com" - #incubator: "http://storage.googleapis.com/kubernetes-charts-incubator" #myS3repo: "s3://my-S3-private-repo/charts" #myGCSrepo: "gs://my-GCS-private-repo/charts" #custom: "https://$user:$pass@mycustomrepo.org" diff --git a/examples/minimal-example.toml b/examples/minimal-example.toml index 5bc101ed..245f2036 100644 --- a/examples/minimal-example.toml +++ b/examples/minimal-example.toml @@ -3,7 +3,8 @@ ## For the full config spec and options, check https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md [helmRepos] - stable = "https://kubernetes-charts.storage.googleapis.com" + jenkins = https://charts.jenkins.io + center = https://repo.chartcenter.io [namespaces] [namespaces.staging] @@ -12,11 +13,11 @@ [apps.jenkins] namespace = "staging" enabled = true - chart = "stable/jenkins" - version = "1.9.12" + chart = "jenkins/jenkins" + version = "2.15.1" [apps.artifactory] namespace = "staging" enabled = true - chart = "stable/artifactory" - version = "7.3.1" + chart = "center/jfrog/artifactory" + version = "11.4.2" diff --git a/examples/minimal-example.yaml b/examples/minimal-example.yaml index 532d084a..9866a92d 100644 --- a/examples/minimal-example.yaml +++ b/examples/minimal-example.yaml @@ -2,7 +2,8 @@ ## It will use your current kube context and will deploy Tiller without RBAC service account. ## For the full config spec and options, check https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md helmRepos: - stable: https://kubernetes-charts.storage.googleapis.com + jenkins = https://charts.jenkins.io + center = https://repo.chartcenter.io namespaces: staging: @@ -11,11 +12,11 @@ apps: jenkins: namespace: staging enabled: true - chart: stable/jenkins - version: 1.9.12 + chart: jenkins/jenkins + version: 2.15.1 artifactory: namespace: staging enabled: true - chart: stable/artifactory - version: 7.3.1 + chart: center/jfrog/artifactory + version: 11.4.2 diff --git a/internal/app/release_test.go b/internal/app/release_test.go index 454ac978..834f8f7b 100644 --- a/internal/app/release_test.go +++ b/internal/app/release_test.go @@ -544,7 +544,7 @@ func Test_validateReleaseCharts(t *testing.T) { targetFlag: []string{}, args: args{ apps: map[string]*release{ - "app": createFullReleasePointer("stable/prometheus", "9.5.2"), + "app": createFullReleasePointer("prometheus-community/prometheus", "11.16.5"), }, }, want: true, diff --git a/internal/app/state_test.go b/internal/app/state_test.go index c919a099..b8a81080 100644 --- a/internal/app/state_test.go +++ b/internal/app/state_test.go @@ -37,8 +37,8 @@ func Test_state_validate(t *testing.T) { "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, }, HelmRepos: map[string]string{ - "stable": "https://kubernetes-charts.storage.googleapis.com", - "myrepo": "s3://my-repo/charts", + "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", + "myrepo": "s3://my-repo/charts", }, Apps: make(map[string]*release), }, @@ -61,8 +61,8 @@ func Test_state_validate(t *testing.T) { "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, }, HelmRepos: map[string]string{ - "stable": "https://kubernetes-charts.storage.googleapis.com", - "myrepo": "s3://my-repo/charts", + "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", + "myrepo": "s3://my-repo/charts", }, Apps: make(map[string]*release), }, @@ -82,8 +82,8 @@ func Test_state_validate(t *testing.T) { "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, }, HelmRepos: map[string]string{ - "stable": "https://kubernetes-charts.storage.googleapis.com", - "myrepo": "s3://my-repo/charts", + "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", + "myrepo": "s3://my-repo/charts", }, Apps: make(map[string]*release), }, @@ -106,8 +106,8 @@ func Test_state_validate(t *testing.T) { "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, }, HelmRepos: map[string]string{ - "stable": "https://kubernetes-charts.storage.googleapis.com", - "myrepo": "s3://my-repo/charts", + "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", + "myrepo": "s3://my-repo/charts", }, Apps: make(map[string]*release), }, @@ -130,8 +130,8 @@ func Test_state_validate(t *testing.T) { "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, }, HelmRepos: map[string]string{ - "stable": "https://kubernetes-charts.storage.googleapis.com", - "myrepo": "s3://my-repo/charts", + "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", + "myrepo": "s3://my-repo/charts", }, Apps: make(map[string]*release), }, @@ -154,8 +154,8 @@ func Test_state_validate(t *testing.T) { "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, }, HelmRepos: map[string]string{ - "stable": "https://kubernetes-charts.storage.googleapis.com", - "myrepo": "s3://my-repo/charts", + "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", + "myrepo": "s3://my-repo/charts", }, Apps: make(map[string]*release), }, @@ -177,8 +177,8 @@ func Test_state_validate(t *testing.T) { "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, }, HelmRepos: map[string]string{ - "stable": "https://kubernetes-charts.storage.googleapis.com", - "myrepo": "s3://my-repo/charts", + "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", + "myrepo": "s3://my-repo/charts", }, Apps: make(map[string]*release), }, @@ -198,8 +198,8 @@ func Test_state_validate(t *testing.T) { "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, }, HelmRepos: map[string]string{ - "stable": "https://kubernetes-charts.storage.googleapis.com", - "myrepo": "s3://my-repo/charts", + "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", + "myrepo": "s3://my-repo/charts", }, Apps: make(map[string]*release), }, @@ -222,8 +222,8 @@ func Test_state_validate(t *testing.T) { "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, }, HelmRepos: map[string]string{ - "stable": "https://kubernetes-charts.storage.googleapis.com", - "myrepo": "s3://my-repo/charts", + "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", + "myrepo": "s3://my-repo/charts", }, Apps: make(map[string]*release), }, @@ -240,8 +240,8 @@ func Test_state_validate(t *testing.T) { "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, }, HelmRepos: map[string]string{ - "stable": "https://kubernetes-charts.storage.googleapis.com", - "myrepo": "s3://my-repo/charts", + "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", + "myrepo": "s3://my-repo/charts", }, Apps: make(map[string]*release), }, @@ -256,8 +256,8 @@ func Test_state_validate(t *testing.T) { }, Namespaces: nil, HelmRepos: map[string]string{ - "stable": "https://kubernetes-charts.storage.googleapis.com", - "myrepo": "s3://my-repo/charts", + "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", + "myrepo": "s3://my-repo/charts", }, Apps: make(map[string]*release), }, @@ -272,8 +272,8 @@ func Test_state_validate(t *testing.T) { }, Namespaces: map[string]namespace{}, HelmRepos: map[string]string{ - "stable": "https://kubernetes-charts.storage.googleapis.com", - "myrepo": "s3://my-repo/charts", + "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", + "myrepo": "s3://my-repo/charts", }, Apps: make(map[string]*release), }, @@ -320,8 +320,8 @@ func Test_state_validate(t *testing.T) { "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, }, HelmRepos: map[string]string{ - "stable": "https://kubernetes-charts.storage.googleapis.com", - "myrepo": "", + "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", + "myrepo": "", }, Apps: make(map[string]*release), }, @@ -338,8 +338,8 @@ func Test_state_validate(t *testing.T) { "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, }, HelmRepos: map[string]string{ - "stable": "https://kubernetes-charts.storage.googleapis.com", - "myrepo": "s3//my-repo/charts", + "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", + "myrepo": "s3//my-repo/charts", }, Apps: make(map[string]*release), }, diff --git a/tests/invalid_example.toml b/tests/invalid_example.toml index eb63cdb8..67c02ee7 100644 --- a/tests/invalid_example.toml +++ b/tests/invalid_example.toml @@ -13,7 +13,7 @@ staging = "staging" production = "default" [helmRepos] -stable = "https://kubernetes-charts.storage.googleapis.com" -incubator = "http://storage.googleapis.com/kubernetes-charts-incubator" +jenkins = "https://charts.jenkins.io" +center = "https://repo.chartcenter.io" [apps] diff --git a/tests/invalid_example.yaml b/tests/invalid_example.yaml index 410fc558..b40add8a 100644 --- a/tests/invalid_example.yaml +++ b/tests/invalid_example.yaml @@ -13,7 +13,7 @@ namespaces: production: "default" helmRepos: - stable: "https://kubernetes-charts.storage.googleapis.com" - incubator: "http://storage.googleapis.com/kubernetes-charts-incubator" + jenkins: "https://charts.jenkins.io" + center: "https://repo.chartcenter.io" apps: From d7c5f4ff46cfded0243aeb11a8ce4365af61cb33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Desch=C3=AAnes?= Date: Tue, 27 Oct 2020 10:16:23 -0400 Subject: [PATCH 0690/1127] Add --always-upgrade flag --- docs/cmd_reference.md | 3 +++ internal/app/cli.go | 4 +++- internal/app/decision_maker.go | 5 ++++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/cmd_reference.md b/docs/cmd_reference.md index e2e1e214..d72571da 100644 --- a/docs/cmd_reference.md +++ b/docs/cmd_reference.md @@ -8,6 +8,9 @@ This lists available CMD options in Helmsman: > you can find the CMD options for the version you are using by typing: `helmsman -h` or `helmsman --help` + `--always-upgrade` + upgrade release even if no changes are found. + `--apply` apply the plan directly. diff --git a/internal/app/cli.go b/internal/app/cli.go index 5f16ec91..059c0b89 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -64,6 +64,7 @@ type cli struct { noCleanup bool migrateContext bool parallel int + alwaysUpgrade bool } func printUsage() { @@ -107,7 +108,8 @@ func (c *cli) parse() { flag.BoolVar(&c.updateDeps, "update-deps", false, "run 'helm dep up' for local chart") flag.BoolVar(&c.forceUpgrades, "force-upgrades", false, "use --force when upgrading helm releases. May cause resources to be recreated.") flag.BoolVar(&c.noCleanup, "no-cleanup", false, "keeps any credentials files that has been downloaded on the host where helmsman runs.") - flag.BoolVar(&c.migrateContext, "migrate-context", false, "Updates the context name for all apps defined in the DSF and applies Helmsman labels. Using this flag is required if you want to change context name after it has been set.") + flag.BoolVar(&c.migrateContext, "migrate-context", false, "updates the context name for all apps defined in the DSF and applies Helmsman labels. Using this flag is required if you want to change context name after it has been set.") + flag.BoolVar(&c.alwaysUpgrade, "always-upgrade", false, "upgrade release even if no changes are found") flag.Usage = printUsage flag.Parse() diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index 49be67e9..97e218c6 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -406,7 +406,10 @@ func (cs *currentState) inspectUpgradeScenario(r *release, p *plan, chartName, c " ]. Delete of the current release will be planned and new chart will be installed in namespace [ "+ r.Namespace+" ]", r.Priority, change) } else { - if diff := r.diff(); diff != "" { + if flags.alwaysUpgrade { + r.upgrade(p) + p.addDecision("Release [ "+r.Name+" ] will be updated (forced)", r.Priority, change) + } else if diff := r.diff(); diff != "" { r.upgrade(p) p.addDecision("Release [ "+r.Name+" ] will be updated", r.Priority, change) } else { From a4c0633500cc1bf3a8585dd1d65af9b21d9e73e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillaume=20Membr=C3=A9?= Date: Wed, 4 Nov 2020 21:28:18 +0100 Subject: [PATCH 0691/1127] Fix donwload location Hi, thanks for your work. In the quick install section, there are some `\` before `_` which is incorrect. Here is a small fix. Regards --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4a46c6cd..2ac05b6e 100644 --- a/README.md +++ b/README.md @@ -61,9 +61,9 @@ If you use private helm repos, you will need either `helm-gcs` or `helm-s3` plug Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. ``` # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.4.2/helmsman\_3.4.2\_linux\_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.4.2/helmsman_3.4.2_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.4.2/helmsman\_3.4.2\_darwin\_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.4.2/helmsman_3.4.2_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` From 6586e6036c253bd5276bb3cfe892177cb679ee5e Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Thu, 5 Nov 2020 12:45:18 +0100 Subject: [PATCH 0692/1127] Add retryExec func for getting cluster status methods --- internal/app/cli.go | 2 +- internal/app/command.go | 23 +++++++++++++++++++++++ internal/app/decision_maker.go | 6 +++--- internal/app/helm_release.go | 6 +++--- internal/app/kube_helpers.go | 2 +- internal/app/main.go | 14 +++++++------- internal/app/plan.go | 2 +- internal/app/utils.go | 18 ++++++++++++++++++ 8 files changed, 57 insertions(+), 16 deletions(-) diff --git a/internal/app/cli.go b/internal/app/cli.go index 5f16ec91..4c6f71c7 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -264,7 +264,7 @@ func (c *cli) readState(s *state) { if !c.skipValidation { // validate the desired state content if len(c.files) > 0 { - log.Info("Validating desired state definition...") + log.Info("Validating desired state definition") if err := s.validate(); err != nil { // syntax validation log.Fatal(err.Error()) } diff --git a/internal/app/command.go b/internal/app/command.go index 9ddd6edc..4d33777c 100644 --- a/internal/app/command.go +++ b/internal/app/command.go @@ -2,9 +2,11 @@ package app import ( "bytes" + "fmt" "os/exec" "strings" "syscall" + "time" ) // command type representing all executable commands Helmsman needs @@ -25,6 +27,27 @@ func (c *command) String() string { return c.Cmd + " " + strings.Join(c.Args, " ") } +// runs exec command with retry +func (c *command) retryExec(attempts int) exitStatus { + var result exitStatus + var errMsg string + err := retry(attempts, 2*time.Second, func() (err error) { + result = c.exec() + if result.code != 0 { + return fmt.Errorf("%s, it failed with: %s", c.Description, result.errors) + } + return + }) + if errMsg = ""; err != nil { + errMsg = err.Error() + } + return exitStatus{ + code: result.code, + output: result.output, + errors: errMsg, + } +} + // exec executes the executable command and returns the exit code and execution result func (c *command) exec() exitStatus { // Only use non-empty string args diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index 49be67e9..fb88fae4 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -21,7 +21,7 @@ func newCurrentState() *currentState { // buildState builds the currentState map containing information about all releases existing in a k8s cluster func buildState(s *state) *currentState { - log.Info("Acquiring current Helm state from cluster...") + log.Info("Acquiring current Helm state from cluster") cs := newCurrentState() rel := getHelmReleases(s) @@ -298,8 +298,8 @@ func (cs *currentState) getHelmsmanReleases(s *state) map[string]map[string]bool <-sem }() - cmd := kubectl([]string{"get", storageBackend, "-n", ns, "-l", "MANAGED-BY=HELMSMAN", "-o", outputFmt, "--no-headers"}, "Getting Helmsman-managed releases") - result := cmd.exec() + cmd := kubectl([]string{"get", storageBackend, "-n", ns, "-l", "MANAGED-BY=HELMSMAN", "-o", outputFmt, "--no-headers"}, "Getting Helmsman-managed releases from namespace [ " + ns + " ]") + result := cmd.retryExec(3) if result.code != 0 { log.Fatal(result.errors) } diff --git a/internal/app/helm_release.go b/internal/app/helm_release.go index ceeb5cfc..d55a1dd0 100644 --- a/internal/app/helm_release.go +++ b/internal/app/helm_release.go @@ -50,10 +50,10 @@ func getHelmReleases(s *state) []helmRelease { var releases []helmRelease var targetReleases []helmRelease defer wg.Done() - cmd := helmCmd([]string{"list", "--all", "--max", "0", "--output", "json", "-n", ns}, "Listing all existing releases in [ "+ns+" ] namespace...") - result := cmd.exec() + cmd := helmCmd([]string{"list", "--all", "--max", "0", "--output", "json", "-n", ns}, "Listing all existing releases in [ "+ns+" ] namespace") + result := cmd.retryExec(3) if result.code != 0 { - log.Fatal("Failed to list all releases: " + result.errors) + log.Fatal(result.errors) } if err := json.Unmarshal([]byte(result.output), &releases); err != nil { log.Fatal(fmt.Sprintf("failed to unmarshal Helm CLI output: %s", err)) diff --git a/internal/app/kube_helpers.go b/internal/app/kube_helpers.go index 1b827571..fa00bb56 100644 --- a/internal/app/kube_helpers.go +++ b/internal/app/kube_helpers.go @@ -50,7 +50,7 @@ func kubectl(args []string, desc string) command { // createNamespace creates a namespace in the k8s cluster func createNamespace(ns string) { checkCmd := kubectl([]string{"get", "namespace", ns}, "Looking for namespace [ "+ns+" ]") - checkResult := checkCmd.exec() + checkResult := checkCmd.retryExec(3) if checkResult.code == 0 { log.Verbose("Namespace [ " + ns + " ] exists") return diff --git a/internal/app/main.go b/internal/app/main.go index 7b4198d8..6b53eac3 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -37,7 +37,7 @@ func Main() { if len(s.GroupMap) > 0 { s.TargetMap = s.getAppsInGroupsAsTargetMap() if len(s.TargetMap) == 0 { - log.Info("No apps defined with -group flag were found, exiting...") + log.Info("No apps defined with -group flag were found, exiting") os.Exit(0) } } @@ -45,7 +45,7 @@ func Main() { s.TargetApps = s.getAppsInTargetsOnly() s.TargetNamespaces = s.getNamespacesInTargetsOnly() if len(s.TargetApps) == 0 { - log.Info("No apps defined with -target flag were found, exiting...") + log.Info("No apps defined with -target flag were found, exiting") os.Exit(0) } } @@ -53,7 +53,7 @@ func Main() { curContext = s.Context // set the kubecontext to be used Or create it if it does not exist - log.Info("Setting up kubectl...") + log.Info("Setting up kubectl") if !setKubeContext(settings.KubeContext) { if err := createContext(&s); err != nil { log.Fatal(err.Error()) @@ -61,7 +61,7 @@ func Main() { } // add repos -- fails if they are not valid - log.Info("Setting up helm...") + log.Info("Setting up helm") if err := addHelmRepos(s.HelmRepos); err != nil && !flags.destroy { log.Fatal(err.Error()) } @@ -69,7 +69,7 @@ func Main() { if flags.apply || flags.dryRun || flags.destroy { // add/validate namespaces if !flags.noNs { - log.Info("Setting up namespaces...") + log.Info("Setting up namespaces") if flags.nsOverride == "" { addNamespaces(&s) } else { @@ -80,7 +80,7 @@ func Main() { } if !flags.skipValidation { - log.Info("Validating charts...") + log.Info("Validating charts") // validate charts-versions exist in defined repos if err := validateReleaseCharts(&s); err != nil { log.Fatal(err.Error()) @@ -98,7 +98,7 @@ func Main() { s.updateContextLabels() } - log.Info("Preparing plan...") + log.Info("Preparing plan") cs := buildState(&s) p := cs.makePlan(&s) if !flags.keepUntrackedReleases { diff --git a/internal/app/plan.go b/internal/app/plan.go index 0e0d9891..80a5d524 100644 --- a/internal/app/plan.go +++ b/internal/app/plan.go @@ -85,7 +85,7 @@ func (p *plan) addDecision(decision string, priority int, decisionType decisionT func (p *plan) exec() { p.sort() if len(p.Commands) > 0 { - log.Info("Executing plan... ") + log.Info("Executing plan") } else { log.Info("Nothing to execute") } diff --git a/internal/app/utils.go b/internal/app/utils.go index 80456093..78e51553 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -682,3 +682,21 @@ func isValidFile(filePath string, allowedFileTypes []string) error { } return nil } + +func retry(attempts int, sleep time.Duration, callback func() error) (err error) { + for i := 0; ; i++ { + err = callback() + if err == nil { + return + } + + if i >= (attempts - 1) { + break + } + + time.Sleep(sleep) + + log.Info(fmt.Sprintf("Retrying %s", err)) + } + return fmt.Errorf("after %d attempts of %s", attempts, err) +} From c5b7343b37498847c9f5c68263c69f16ea0f6e64 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Thu, 5 Nov 2020 13:27:20 +0100 Subject: [PATCH 0693/1127] Run release labeling as defer before release exec --- internal/app/plan.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/app/plan.go b/internal/app/plan.go index 0e0d9891..11933f54 100644 --- a/internal/app/plan.go +++ b/internal/app/plan.go @@ -146,6 +146,9 @@ func releaseWithHooks(cmd orderedCommand, wg *sync.WaitGroup, sem chan struct{}, return } } + if !flags.dryRun && !flags.destroy { + defer cmd.targetRelease.label() + } if err := execOne(cmd.Command, cmd.targetRelease); err != nil { errors <- err log.Verbose(err.Error()) @@ -157,9 +160,6 @@ func releaseWithHooks(cmd orderedCommand, wg *sync.WaitGroup, sem chan struct{}, log.Verbose(err.Error()) } } - if !flags.dryRun && !flags.destroy { - cmd.targetRelease.label() - } } // execOne executes a single ordered command From a566122113469876f425eb97328378f687c22c07 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Fri, 6 Nov 2020 09:24:12 +0100 Subject: [PATCH 0694/1127] Rewrite retryExec to containt check loop inside of it --- internal/app/command.go | 21 ++++++++++++--------- internal/app/utils.go | 18 ------------------ 2 files changed, 12 insertions(+), 27 deletions(-) diff --git a/internal/app/command.go b/internal/app/command.go index 4d33777c..128267c2 100644 --- a/internal/app/command.go +++ b/internal/app/command.go @@ -3,6 +3,7 @@ package app import ( "bytes" "fmt" + "math" "os/exec" "strings" "syscall" @@ -30,21 +31,23 @@ func (c *command) String() string { // runs exec command with retry func (c *command) retryExec(attempts int) exitStatus { var result exitStatus - var errMsg string - err := retry(attempts, 2*time.Second, func() (err error) { + + for i := 0; ; i++ { result = c.exec() - if result.code != 0 { - return fmt.Errorf("%s, it failed with: %s", c.Description, result.errors) + if result.code == 0 { + return result + } + if i >= (attempts - 1) { + break } - return - }) - if errMsg = ""; err != nil { - errMsg = err.Error() + time.Sleep(time.Duration(math.Pow(2, float64(2+i))) * time.Second) + log.Info(fmt.Sprintf("Retrying %s due to error: %s", c.Description, result.errors)) } + return exitStatus{ code: result.code, output: result.output, - errors: errMsg, + errors: fmt.Sprintf("After %d attempts of %s, it failed with: %s", attempts, c.Description, result.errors), } } diff --git a/internal/app/utils.go b/internal/app/utils.go index 78e51553..80456093 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -682,21 +682,3 @@ func isValidFile(filePath string, allowedFileTypes []string) error { } return nil } - -func retry(attempts int, sleep time.Duration, callback func() error) (err error) { - for i := 0; ; i++ { - err = callback() - if err == nil { - return - } - - if i >= (attempts - 1) { - break - } - - time.Sleep(sleep) - - log.Info(fmt.Sprintf("Retrying %s", err)) - } - return fmt.Errorf("after %d attempts of %s", attempts, err) -} From ef30978e0af009d53135a31e10100bc1a68c7f7e Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Fri, 6 Nov 2020 14:32:17 +0100 Subject: [PATCH 0695/1127] Simplify retryExec loop --- internal/app/command.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/internal/app/command.go b/internal/app/command.go index 128267c2..8dcd0c73 100644 --- a/internal/app/command.go +++ b/internal/app/command.go @@ -32,16 +32,15 @@ func (c *command) String() string { func (c *command) retryExec(attempts int) exitStatus { var result exitStatus - for i := 0; ; i++ { + for i := 0; i < attempts; i++ { result = c.exec() if result.code == 0 { return result } - if i >= (attempts - 1) { - break + if i < (attempts - 1) { + time.Sleep(time.Duration(math.Pow(2, float64(2+i))) * time.Second) + log.Info(fmt.Sprintf("Retrying %s due to error: %s", c.Description, result.errors)) } - time.Sleep(time.Duration(math.Pow(2, float64(2+i))) * time.Second) - log.Info(fmt.Sprintf("Retrying %s due to error: %s", c.Description, result.errors)) } return exitStatus{ From a21c3199f22ba7627631b93d1fb0439551b707b6 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Mon, 9 Nov 2020 08:28:18 +0100 Subject: [PATCH 0696/1127] Release v3.5.0 --- README.md | 2 +- internal/app/main.go | 2 +- release-notes.md | 12 +++++++----- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 2ac05b6e..587d3664 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.4.6&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.5.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) diff --git a/internal/app/main.go b/internal/app/main.go index 6b53eac3..b4752302 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -6,7 +6,7 @@ import ( const ( helmBin = "helm" - appVersion = "v3.4.6" + appVersion = "v3.5.0" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index 86945bd3..f6b9ac17 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,4 +1,4 @@ -# v3.4.6 +# v3.5.0 If you migrating from Helmsman v1.x, it is recommended you read the [migration guide](https://github.com/Praqma/helmsman/blob/master/docs/how_to/misc/migrate_to_3.md) before using this release. @@ -6,10 +6,12 @@ If you migrating from Helmsman v1.x, it is recommended you read the [migration g # Fixes and improvements: -- Fixed an issue that would prevent Helmsman from recognizing releases after helm tests were executed. +- Handle default app name even when --skip-validation is used; PR #526 +- extractChartName works now even when only dev versions are published; PR #532 +- Remove references to the deprecated stable repository; PR #533 +- Fix no labels on first failed deploy with running release labeling as defer before release exec; PR #541 # New features: -Bug fix release, not new features - -None. +- Add --always-upgrade flag; PR #534 +- Add retryExec func for getting cluster status methods; PR #540 From feb10ec8203be8feb5940cc6ac1677081b80ffcf Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Mon, 9 Nov 2020 08:56:44 +0100 Subject: [PATCH 0697/1127] Release v3.5.0 with helm v3.4.0 added --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 47a99c53..9d23f4eb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -44,7 +44,7 @@ jobs: - run: name: build docker images and push them to dockerhub command: | - helm_versions=( "v3.3.4" "v.3.3.3" "v3.3.2" "v3.3.1" "v3.3.0" "v3.2.4" ) + helm_versions=( "v3.4.0" "v3.3.4" ) TAG=$(git describe --abbrev=0 --tags) docker login -u $DOCKER_USER -p $DOCKER_PASS From 66673c04ef13670a5bbbc8f24a4fbfd79ed68679 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Mon, 9 Nov 2020 16:14:30 +0100 Subject: [PATCH 0698/1127] Add retryExec to diff() --- internal/app/release.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/app/release.go b/internal/app/release.go index 05dcfd9c..113866f7 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -337,7 +337,7 @@ func (r *release) diff() string { cmd := helmCmd(concat([]string{"diff", colorFlag, suppressDiffSecretsFlag}, diffContextFlag, r.getHelmArgsFor("diff")), "Diffing release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") - result := cmd.exec() + result := cmd.retryExec(3) if result.code != 0 { log.Fatal(fmt.Sprintf("Command returned with exit code: %d. And error message: %s ", result.code, result.errors)) } else { From 7e56d652ba95bb4371348e95b255161cdbd220d2 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 10 Nov 2020 14:54:53 +0000 Subject: [PATCH 0699/1127] Prepare release 3.5.1 --- README.md | 2 +- internal/app/main.go | 2 +- release-notes.md | 12 ++---------- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 587d3664..f4afb285 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.5.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.5.1&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) diff --git a/internal/app/main.go b/internal/app/main.go index b4752302..c7c06736 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -6,7 +6,7 @@ import ( const ( helmBin = "helm" - appVersion = "v3.5.0" + appVersion = "v3.5.1" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index f6b9ac17..2d0e040f 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,4 +1,4 @@ -# v3.5.0 +# v3.5.1 If you migrating from Helmsman v1.x, it is recommended you read the [migration guide](https://github.com/Praqma/helmsman/blob/master/docs/how_to/misc/migrate_to_3.md) before using this release. @@ -6,12 +6,4 @@ If you migrating from Helmsman v1.x, it is recommended you read the [migration g # Fixes and improvements: -- Handle default app name even when --skip-validation is used; PR #526 -- extractChartName works now even when only dev versions are published; PR #532 -- Remove references to the deprecated stable repository; PR #533 -- Fix no labels on first failed deploy with running release labeling as defer before release exec; PR #541 - -# New features: - -- Add --always-upgrade flag; PR #534 -- Add retryExec func for getting cluster status methods; PR #540 +- Add retryExec func for helm diff; PR #542 From c40bcf9d0593270797df6794628389a42df04173 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 10 Nov 2020 22:19:26 +0000 Subject: [PATCH 0700/1127] refactor: reduce the usage of globals and duplicated data Signed-off-by: Luis Davim --- Makefile | 4 + internal/app/cli.go | 44 ++----- internal/app/cli_test.go | 2 +- internal/app/command.go | 36 +++--- internal/app/command_test.go | 4 +- internal/app/decision_maker.go | 27 ++-- internal/app/decision_maker_test.go | 103 +++++++++------ internal/app/helm_helpers.go | 94 ++++++++++++-- internal/app/helm_release.go | 15 +-- internal/app/kube_helpers.go | 46 ++++--- internal/app/logging.go | 52 ++++---- internal/app/main.go | 26 ++-- internal/app/namespace.go | 7 +- internal/app/plan.go | 34 ++--- internal/app/plan_test.go | 6 +- internal/app/release.go | 189 ++++------------------------ internal/app/release_test.go | 41 ++---- internal/app/state.go | 160 ++++++++++++++++------- internal/app/state_test.go | 60 ++++----- internal/app/utils.go | 6 +- internal/app/utils_test.go | 3 +- 21 files changed, 470 insertions(+), 489 deletions(-) diff --git a/Makefile b/Makefile index 5350afb9..e8767a9f 100644 --- a/Makefile +++ b/Makefile @@ -45,6 +45,10 @@ vet: fmt @go vet ./... .PHONY: vet +imports: ## Ensure imports are present and formatted + @goimports -w $(shell find . -type f -name '*.go' -not -path './vendor/*') +.PHONY: goimports + deps: ## Install depdendencies. Runs `go get` internally. @GOFLAGS="" go get -t -d -v ./... @GOFLAGS="" go mod tidy diff --git a/internal/app/cli.go b/internal/app/cli.go index 7e4282cd..fb08bd1c 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -162,11 +162,11 @@ func (c *cli) parse() { os.Setenv("KUBECONFIG", c.kubeconfig) } - if !toolExists("kubectl") { + if !ToolExists("kubectl") { log.Fatal("kubectl is not installed/configured correctly. Aborting!") } - if !toolExists(helmBin) { + if !ToolExists(helmBin) { log.Fatal("" + helmBin + " is not installed/configured correctly. Aborting!") } @@ -242,27 +242,25 @@ func (c *cli) readState(s *state) { } } - // Default app.Name to state name when unset - for appName, app := range s.Apps { - if app.Name == "" { - app.Name = appName - } - } + s.setDefaults() + s.makeTargetMap(c.group, c.target) if len(c.target) > 0 { - s.TargetMap = map[string]bool{} - for _, v := range c.target { - s.TargetMap[v] = true + if len(s.TargetMap) == 0 { + log.Info("No apps defined with -target flag were found, exiting") + os.Exit(0) } } if len(c.group) > 0 { - s.GroupMap = map[string]bool{} - for _, v := range c.group { - s.GroupMap[v] = true + if len(s.TargetMap) == 0 { + log.Info("No apps defined with -group flag were found, exiting") + os.Exit(0) } } + s.disableUntargettedApps() + if !c.skipValidation { // validate the desired state content if len(c.files) > 0 { @@ -275,24 +273,6 @@ func (c *cli) readState(s *state) { log.Info("Desired state validation is skipped.") } - if s.Settings.StorageBackend != "" { - os.Setenv("HELM_DRIVER", s.Settings.StorageBackend) - } else { - // set default storage background to secret if not set by user - s.Settings.StorageBackend = "secret" - } - - // if there is no user-defined context name in the DSF(s), use the default context name - if s.Context == "" { - s.Context = defaultContextName - } - - // inherit globalHooks if local ones are not set - for _, r := range s.Apps { - r.inheritHooks(s) - r.inheritMaxHistory(s) - } - if c.debug { s.print() } diff --git a/internal/app/cli_test.go b/internal/app/cli_test.go index 3e0f6c7d..04b381cd 100644 --- a/internal/app/cli_test.go +++ b/internal/app/cli_test.go @@ -38,7 +38,7 @@ func Test_toolExists(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := toolExists(tt.args.tool); got != tt.want { + if got := ToolExists(tt.args.tool); got != tt.want { t.Errorf("toolExists() = %v, want %v", got, tt.want) } }) diff --git a/internal/app/command.go b/internal/app/command.go index 8dcd0c73..2fe5f9c0 100644 --- a/internal/app/command.go +++ b/internal/app/command.go @@ -10,30 +10,30 @@ import ( "time" ) -// command type representing all executable commands Helmsman needs +// Command type representing all executable commands Helmsman needs // to execute in order to inspect the environment/ releases/ charts etc. -type command struct { +type Command struct { Cmd string Args []string Description string } -type exitStatus struct { +type ExitStatus struct { code int errors string output string } -func (c *command) String() string { +func (c *Command) String() string { return c.Cmd + " " + strings.Join(c.Args, " ") } -// runs exec command with retry -func (c *command) retryExec(attempts int) exitStatus { - var result exitStatus +// RetryExec runs exec command with retry +func (c *Command) RetryExec(attempts int) ExitStatus { + var result ExitStatus for i := 0; i < attempts; i++ { - result = c.exec() + result = c.Exec() if result.code == 0 { return result } @@ -43,15 +43,15 @@ func (c *command) retryExec(attempts int) exitStatus { } } - return exitStatus{ + return ExitStatus{ code: result.code, output: result.output, errors: fmt.Sprintf("After %d attempts of %s, it failed with: %s", attempts, c.Description, result.errors), } } -// exec executes the executable command and returns the exit code and execution result -func (c *command) exec() exitStatus { +// Exec executes the executable command and returns the exit code and execution result +func (c *Command) Exec() ExitStatus { // Only use non-empty string args args := []string{} for _, str := range c.Args { @@ -70,7 +70,7 @@ func (c *command) exec() exitStatus { if err := cmd.Start(); err != nil { log.Info("cmd.Start: " + err.Error()) - return exitStatus{ + return ExitStatus{ code: 1, errors: err.Error(), } @@ -79,7 +79,7 @@ func (c *command) exec() exitStatus { if err := cmd.Wait(); err != nil { if exiterr, ok := err.(*exec.ExitError); ok { if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { - return exitStatus{ + return ExitStatus{ code: status.ExitStatus(), output: stdout.String(), errors: stderr.String(), @@ -89,23 +89,23 @@ func (c *command) exec() exitStatus { log.Fatal("cmd.Wait: " + err.Error()) } } - return exitStatus{ + return ExitStatus{ code: 0, output: stdout.String(), errors: stderr.String(), } } -// toolExists returns true if the tool is present in the environment and false otherwise. +// ToolExists returns true if the tool is present in the environment and false otherwise. // It takes as input the tool's command to check if it is recognizable or not. e.g. helm or kubectl -func toolExists(tool string) bool { - cmd := command{ +func ToolExists(tool string) bool { + cmd := Command{ Cmd: tool, Args: []string{}, Description: "Validating that [ " + tool + " ] is installed", } - result := cmd.exec() + result := cmd.Exec() return result.code == 0 } diff --git a/internal/app/command_test.go b/internal/app/command_test.go index e4c0d0f5..a0ddfcd1 100644 --- a/internal/app/command_test.go +++ b/internal/app/command_test.go @@ -39,12 +39,12 @@ func Test_command_exec(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := command{ + c := Command{ Cmd: tt.fields.Cmd, Args: tt.fields.Args, Description: tt.fields.Description, } - got := c.exec() + got := c.Exec() if got.code != tt.want { t.Errorf("command.exec() got = %v, want %v", got.code, tt.want) } diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index 1692de53..b3e0af5d 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -42,7 +42,7 @@ func buildState(s *state) *currentState { <-sem }() if flags.contextOverride == "" { - r.HelmsmanContext = getReleaseContext(r.Name, r.Namespace) + r.HelmsmanContext = getReleaseContext(r.Name, r.Namespace, s.Settings.StorageBackend) } else { r.HelmsmanContext = flags.contextOverride log.Info("Overwrote Helmsman context for release [ " + r.Name + " ] to " + flags.contextOverride) @@ -57,6 +57,8 @@ func buildState(s *state) *currentState { // makePlan creates a plan of the actions needed to make the desired state come true. func (cs *currentState) makePlan(s *state) *plan { p := createPlan() + p.StorageBackend = s.Settings.StorageBackend + p.ReverseDelete = s.Settings.ReverseDelete wg := sync.WaitGroup{} sem := make(chan struct{}, resourcePool) @@ -85,7 +87,7 @@ func (cs *currentState) makePlan(s *state) *plan { extractedChartVersions[r.Chart] = make(map[string]string) } - if r.isConsideredToRun(s) { + if r.isConsideredToRun() { charts[r.Chart][r.Version] = true } } @@ -179,7 +181,7 @@ func (cs *currentState) makePlan(s *state) *plan { // to make a release section of the desired state come true. func (cs *currentState) decide(r *release, s *state, p *plan, chartName, chartVersion string) { // check for presence in defined targets or groups - if !r.isConsideredToRun(s) { + if !r.isConsideredToRun() { p.addDecision("Release [ "+r.Name+" ] ignored", r.Priority, ignored) return } @@ -272,21 +274,18 @@ var releaseNameExtractor = regexp.MustCompile(`sh\.helm\.release\.v\d+\.`) func (cs *currentState) getHelmsmanReleases(s *state) map[string]map[string]bool { const outputFmt = "custom-columns=NAME:.metadata.name,CTX:.metadata.labels.HELMSMAN_CONTEXT" var ( - wg sync.WaitGroup - mutex = &sync.Mutex{} - namespaces map[string]namespace + wg sync.WaitGroup + mutex = &sync.Mutex{} ) releases := make(map[string]map[string]bool) sem := make(chan struct{}, resourcePool) - if len(s.TargetMap) > 0 { - namespaces = s.TargetNamespaces - } else { - namespaces = s.Namespaces - } storageBackend := s.Settings.StorageBackend - for ns := range namespaces { + for ns, cfg := range s.Namespaces { + if cfg.disabled { + continue + } // acquire sem <- struct{}{} wg.Add(1) @@ -298,8 +297,8 @@ func (cs *currentState) getHelmsmanReleases(s *state) map[string]map[string]bool <-sem }() - cmd := kubectl([]string{"get", storageBackend, "-n", ns, "-l", "MANAGED-BY=HELMSMAN", "-o", outputFmt, "--no-headers"}, "Getting Helmsman-managed releases from namespace [ " + ns + " ]") - result := cmd.retryExec(3) + cmd := kubectl([]string{"get", storageBackend, "-n", ns, "-l", "MANAGED-BY=HELMSMAN", "-o", outputFmt, "--no-headers"}, "Getting Helmsman-managed releases from namespace [ "+ns+" ]") + result := cmd.RetryExec(3) if result.code != 0 { log.Fatal(result.errors) } diff --git a/internal/app/decision_maker_test.go b/internal/app/decision_maker_test.go index d1f6a54d..aa92538c 100644 --- a/internal/app/decision_maker_test.go +++ b/internal/app/decision_maker_test.go @@ -127,7 +127,7 @@ func Test_inspectUpgradeScenario(t *testing.T) { func Test_decide(t *testing.T) { type args struct { - r *release + r string s *state } tests := []struct { @@ -140,25 +140,33 @@ func Test_decide(t *testing.T) { name: "decide() - targetMap does not contain this service - skip", targetFlag: []string{"someOtherRelease"}, args: args{ - r: &release{ - Name: "release1", - Namespace: "namespace", - Enabled: true, + r: "release1", + s: &state{ + Apps: map[string]*release{ + "release1": { + Name: "release1", + Namespace: "namespace", + Enabled: true, + }, + }, }, - s: &state{}, }, want: ignored, }, { - name: "decide() - targetMap does not contain this service - skip", + name: "decide() - targetMap does not contain this service either - skip", targetFlag: []string{"someOtherRelease", "norThisOne"}, args: args{ - r: &release{ - Name: "release1", - Namespace: "namespace", - Enabled: true, + r: "release1", + s: &state{ + Apps: map[string]*release{ + "release1": { + Name: "release1", + Namespace: "namespace", + Enabled: true, + }, + }, }, - s: &state{}, }, want: ignored, }, @@ -166,12 +174,16 @@ func Test_decide(t *testing.T) { name: "decide() - targetMap is empty - will install", targetFlag: []string{}, args: args{ - r: &release{ - Name: "release4", - Namespace: "namespace", - Enabled: true, + r: "release4", + s: &state{ + Apps: map[string]*release{ + "release4": { + Name: "release4", + Namespace: "namespace", + Enabled: true, + }, + }, }, - s: &state{}, }, want: create, }, @@ -179,12 +191,16 @@ func Test_decide(t *testing.T) { name: "decide() - targetMap is exactly this service - will install", targetFlag: []string{"thisRelease"}, args: args{ - r: &release{ - Name: "thisRelease", - Namespace: "namespace", - Enabled: true, + r: "thisRelease", + s: &state{ + Apps: map[string]*release{ + "thisRelease": { + Name: "thisRelease", + Namespace: "namespace", + Enabled: true, + }, + }, }, - s: &state{}, }, want: create, }, @@ -192,12 +208,16 @@ func Test_decide(t *testing.T) { name: "decide() - targetMap contains this service - will install", targetFlag: []string{"notThisOne", "thisRelease"}, args: args{ - r: &release{ - Name: "thisRelease", - Namespace: "namespace", - Enabled: true, + r: "thisRelease", + s: &state{ + Apps: map[string]*release{ + "thisRelease": { + Name: "thisRelease", + Namespace: "namespace", + Enabled: true, + }, + }, }, - s: &state{}, }, want: create, }, @@ -206,14 +226,11 @@ func Test_decide(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { cs := newCurrentState() - tt.args.s.TargetMap = make(map[string]bool) - - for _, target := range tt.targetFlag { - tt.args.s.TargetMap[target] = true - } + tt.args.s.makeTargetMap([]string{}, tt.targetFlag) + tt.args.s.disableUntargettedApps() outcome := plan{} // Act - cs.decide(tt.args.r, tt.args.s, &outcome, "", "") + cs.decide(tt.args.s.Apps[tt.args.r], tt.args.s, &outcome, "", "") got := outcome.Decisions[0].Type t.Log(outcome.Decisions[0].Description) @@ -244,7 +261,7 @@ func Test_decide_group(t *testing.T) { "release1": { Name: "release1", Namespace: "namespace", - Group: "run-me", + Group: "run-me-not", Enabled: true, }, }, @@ -264,6 +281,18 @@ func Test_decide_group(t *testing.T) { Group: "run-me", Enabled: true, }, + "release2": { + Name: "release2", + Namespace: "namespace", + Group: "run-me-not", + Enabled: true, + }, + "release3": { + Name: "release3", + Namespace: "namespace2", + Group: "run-me-not", + Enabled: true, + }, }, }, }, @@ -275,11 +304,7 @@ func Test_decide_group(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - tt.args.s.GroupMap = make(map[string]bool) - for _, group := range tt.groupFlag { - tt.args.s.GroupMap[group] = true - } - tt.args.s.TargetMap = tt.args.s.getAppsInGroupsAsTargetMap() + tt.args.s.makeTargetMap(tt.groupFlag, []string{}) if len(tt.args.s.TargetMap) != len(tt.want) { t.Errorf("decide() = %d, want %d", len(tt.args.s.TargetMap), len(tt.want)) } diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index 32ccc814..969471df 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -5,6 +5,8 @@ import ( "errors" "fmt" "net/url" + "path/filepath" + "regexp" "strings" "github.com/hashicorp/go-version" @@ -18,8 +20,8 @@ type helmRepo struct { } // helmCmd prepares a helm command to be executed -func helmCmd(args []string, desc string) command { - return command{ +func helmCmd(args []string, desc string) Command { + return Command{ Cmd: helmBin, Args: args, Description: desc, @@ -30,7 +32,7 @@ func helmCmd(args []string, desc string) command { func extractChartName(releaseChart string) string { cmd := helmCmd([]string{"show", "chart", "--devel", releaseChart}, "Extracting chart information for [ "+releaseChart+" ]") - result := cmd.exec() + result := cmd.Exec() if result.code != 0 { log.Fatal("While getting chart information: " + result.errors) } @@ -47,11 +49,85 @@ func extractChartName(releaseChart string) string { return name } +var versionExtractor = regexp.MustCompile(`[\n]version:\s?(.*)`) + +// validateChart validates if chart with the same name and version as specified in the DSF exists +func validateChart(apps, chart, version string, c chan string) { + if isLocalChart(chart) { + cmd := helmCmd([]string{"inspect", "chart", chart}, "Validating [ "+chart+" ] chart's availability") + + result := cmd.Exec() + if result.code != 0 { + maybeRepo := filepath.Base(filepath.Dir(chart)) + c <- "Chart [ " + chart + " ] for apps [" + apps + "] can't be found. Inspection returned error: \"" + + strings.TrimSpace(result.errors) + "\" -- If this is not a local chart, add the repo [ " + maybeRepo + " ] in your helmRepos stanza." + return + } + matches := versionExtractor.FindStringSubmatch(result.output) + if len(matches) == 2 { + v := strings.Trim(matches[1], `'"`) + if strings.Trim(version, `'"`) != v { + c <- "Chart [ " + chart + " ] with version [ " + version + " ] is specified for " + + "apps [" + apps + "] but the chart found at that path has version [ " + v + " ] which does not match." + return + } + } + } else { + v := version + if len(v) == 0 { + v = "*" + } + cmd := helmCmd([]string{"search", "repo", chart, "--version", v, "-l"}, "Validating [ "+chart+" ] chart's version [ "+version+" ] availability") + + if result := cmd.Exec(); result.code != 0 || strings.Contains(result.output, "No results found") { + c <- "Chart [ " + chart + " ] with version [ " + version + " ] is specified for " + + "apps [" + apps + "] but was not found. If this is not a local chart, define its helm repo in the helmRepo stanza in your DSF." + return + } + } +} + +// getChartVersion fetches the lastest chart version matching the semantic versioning constraints. +// If chart is local, returns the given release version +func getChartVersion(chart, version string) (string, string) { + if isLocalChart(chart) { + log.Info("Chart [ " + chart + " ] with version [ " + version + " ] was found locally.") + return version, "" + } + + cmd := helmCmd([]string{"search", "repo", chart, "--version", version, "-o", "json"}, "Getting latest non-local chart's version "+chart+"-"+version+"") + + result := cmd.Exec() + if result.code != 0 { + return "", "Chart [ " + chart + " ] with version [ " + version + " ] is specified but not found in the helm repositories" + } + + chartVersions := make([]chartVersion, 0) + if err := json.Unmarshal([]byte(result.output), &chartVersions); err != nil { + log.Fatal(fmt.Sprint(err)) + } + + filteredChartVersions := make([]chartVersion, 0) + for _, c := range chartVersions { + if c.Name == chart { + filteredChartVersions = append(filteredChartVersions, c) + } + } + + if len(filteredChartVersions) < 1 { + return "", "Chart [ " + chart + " ] with version [ " + version + " ] is specified but not found in the helm repositories" + } else if len(filteredChartVersions) > 1 { + return "", "Multiple versions of chart [ " + chart + " ] with version [ " + version + " ] found in the helm repositories" + } + + return filteredChartVersions[0].Version, "" +} + // getHelmClientVersion returns Helm client Version func getHelmVersion() string { cmd := helmCmd([]string{"version", "--short", "-c"}, "Checking Helm version") - result := cmd.exec() + result := cmd.Exec() if result.code != 0 { log.Fatal("While checking helm version: " + result.errors) } @@ -78,7 +154,7 @@ func checkHelmVersion(constraint string) bool { func helmPluginExists(plugin string) bool { cmd := helmCmd([]string{"plugin", "list"}, "Validating that [ "+plugin+" ] is installed") - result := cmd.exec() + result := cmd.Exec() if result.code != 0 { return false @@ -91,7 +167,7 @@ func helmPluginExists(plugin string) bool { func updateChartDep(chartPath string) error { cmd := helmCmd([]string{"dependency", "update", chartPath}, "Updating dependency for local chart [ "+chartPath+" ]") - result := cmd.exec() + result := cmd.Exec() if result.code != 0 { return errors.New(result.errors) } @@ -106,7 +182,7 @@ func addHelmRepos(repos map[string]string) error { // get existing helm repositories cmdList := helmCmd(concat([]string{"repo", "list", "--output", "json"}), "Listing helm repositories") - if reposResult := cmdList.exec(); reposResult.code == 0 { + if reposResult := cmdList.Exec(); reposResult.code == 0 { if err := json.Unmarshal([]byte(reposResult.output), &helmRepos); err != nil { log.Fatal(fmt.Sprintf("failed to unmarshal Helm CLI output: %s", err)) } @@ -159,7 +235,7 @@ func addHelmRepos(repos map[string]string) error { continue } } - if result := cmd.exec(); result.code != 0 { + if result := cmd.Exec(); result.code != 0 { return fmt.Errorf("While adding helm repository ["+repoName+"]: %s", result.errors) } } @@ -167,7 +243,7 @@ func addHelmRepos(repos map[string]string) error { if len(repos) > 0 { cmd := helmCmd([]string{"repo", "update"}, "Updating helm repositories") - if result := cmd.exec(); result.code != 0 { + if result := cmd.Exec(); result.code != 0 { return errors.New("While updating helm repos : " + result.errors) } } diff --git a/internal/app/helm_release.go b/internal/app/helm_release.go index d55a1dd0..4f570150 100644 --- a/internal/app/helm_release.go +++ b/internal/app/helm_release.go @@ -37,21 +37,18 @@ func getHelmReleases(s *state) []helmRelease { allReleases []helmRelease wg sync.WaitGroup mutex = &sync.Mutex{} - namespaces map[string]namespace ) - if len(s.TargetMap) > 0 { - namespaces = s.TargetNamespaces - } else { - namespaces = s.Namespaces - } - for ns := range namespaces { + for ns, cfg := range s.Namespaces { + if cfg.disabled { + continue + } wg.Add(1) go func(ns string) { var releases []helmRelease var targetReleases []helmRelease defer wg.Done() cmd := helmCmd([]string{"list", "--all", "--max", "0", "--output", "json", "-n", ns}, "Listing all existing releases in [ "+ns+" ] namespace") - result := cmd.retryExec(3) + result := cmd.RetryExec(3) if result.code != 0 { log.Fatal(result.errors) } @@ -84,7 +81,7 @@ func (r *helmRelease) key() string { func (r *helmRelease) uninstall(p *plan) { cmd := helmCmd(concat([]string{"uninstall", r.Name, "--namespace", r.Namespace}, flags.getDryRunFlags()), "Delete untracked release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") - p.addCommand(cmd, -800, nil, []command{}, []command{}) + p.addCommand(cmd, -800, nil, []Command{}, []Command{}) } // getRevision returns the revision number for an existing helm release diff --git a/internal/app/kube_helpers.go b/internal/app/kube_helpers.go index fa00bb56..8e654bd4 100644 --- a/internal/app/kube_helpers.go +++ b/internal/app/kube_helpers.go @@ -16,15 +16,12 @@ import ( // If --ns-override flag is used, it only creates the provided namespace in that flag func addNamespaces(s *state) { var wg sync.WaitGroup - var namespaces map[string]namespace - if len(s.TargetMap) > 0 { - namespaces = s.TargetNamespaces - } else { - namespaces = s.Namespaces - } - for nsName, ns := range namespaces { + for nsName, ns := range s.Namespaces { + if ns.disabled { + continue + } wg.Add(1) - go func(name string, cfg namespace, wg *sync.WaitGroup) { + go func(name string, cfg *namespace, wg *sync.WaitGroup) { defer wg.Done() createNamespace(name) labelNamespace(name, cfg.Labels) @@ -39,8 +36,8 @@ func addNamespaces(s *state) { } // kubectl prepares a kubectl command to be executed -func kubectl(args []string, desc string) command { - return command{ +func kubectl(args []string, desc string) Command { + return Command{ Cmd: "kubectl", Args: args, Description: desc, @@ -50,14 +47,14 @@ func kubectl(args []string, desc string) command { // createNamespace creates a namespace in the k8s cluster func createNamespace(ns string) { checkCmd := kubectl([]string{"get", "namespace", ns}, "Looking for namespace [ "+ns+" ]") - checkResult := checkCmd.retryExec(3) + checkResult := checkCmd.RetryExec(3) if checkResult.code == 0 { log.Verbose("Namespace [ " + ns + " ] exists") return } cmd := kubectl([]string{"create", "namespace", ns}, "Creating namespace [ "+ns+" ]") - result := cmd.exec() + result := cmd.Exec() if result.code == 0 { log.Info("Namespace [ " + ns + " ] created") } else { @@ -81,7 +78,7 @@ func labelNamespace(ns string, labels map[string]string) { cmd := kubectl(args, "Labeling namespace [ "+ns+" ]") - result := cmd.exec() + result := cmd.Exec() if result.code != 0 && flags.verbose { log.Warning(fmt.Sprintf("Could not label namespace [ %s with %v ]. Error message: %s", ns, labelSlice, result.errors)) } @@ -101,7 +98,7 @@ func annotateNamespace(ns string, annotations map[string]string) { args = append(args, annotationSlice...) cmd := kubectl(args, "Annotating namespace [ "+ns+" ]") - result := cmd.exec() + result := cmd.Exec() if result.code != 0 && flags.verbose { log.Info(fmt.Sprintf("Could not annotate namespace [ %s with %v ]. Error message: %s", ns, annotationSlice, result.errors)) } @@ -135,7 +132,7 @@ spec: } cmd := kubectl([]string{"apply", "-f", targetFile, "-n", ns, flags.getKubeDryRunFlag("apply")}, "Creating LimitRange in namespace [ "+ns+" ]") - result := cmd.exec() + result := cmd.Exec() if result.code != 0 { log.Fatal("Failed to create LimitRange in namespace [ " + ns + " ] with error: " + result.errors) @@ -179,7 +176,7 @@ spec: } cmd := kubectl([]string{"apply", "-f", "temp-ResourceQuota.yaml", "-n", ns, flags.getKubeDryRunFlag("apply")}, "Creating ResourceQuota in namespace [ "+ns+" ]") - result := cmd.exec() + result := cmd.Exec() deleteFile("temp-ResourceQuota.yaml") @@ -254,19 +251,19 @@ func createContext(s *state) error { } cmd := kubectl(setCredentialsCmdArgs, "Creating kubectl context - setting credentials") - if result := cmd.exec(); result.code != 0 { + if result := cmd.Exec(); result.code != 0 { return errors.New("failed to create context [ " + s.Settings.KubeContext + " ]: " + result.errors) } cmd = kubectl([]string{"config", "set-cluster", s.Settings.KubeContext, "--server=" + s.Settings.ClusterURI, "--certificate-authority=" + caCrt}, "Creating kubectl context - setting cluster") - if result := cmd.exec(); result.code != 0 { + if result := cmd.Exec(); result.code != 0 { return errors.New("failed to create context [ " + s.Settings.KubeContext + " ]: " + result.errors) } cmd = kubectl([]string{"config", "set-context", s.Settings.KubeContext, "--cluster=" + s.Settings.KubeContext, "--user=" + s.Settings.Username}, "Creating kubectl context - setting context") - if result := cmd.exec(); result.code != 0 { + if result := cmd.Exec(); result.code != 0 { return errors.New("failed to create context [ " + s.Settings.KubeContext + " ]: " + result.errors) } @@ -286,7 +283,7 @@ func setKubeContext(kctx string) bool { cmd := kubectl([]string{"config", "use-context", kctx}, "Setting kube context to [ "+kctx+" ]") - result := cmd.exec() + result := cmd.Exec() if result.code != 0 { log.Info("Kubectl context [ " + kctx + " ] does not exist. Attempting to create it...") @@ -301,7 +298,7 @@ func setKubeContext(kctx string) bool { func getKubeContext() bool { cmd := kubectl([]string{"config", "current-context"}, "Getting kubectl context") - result := cmd.exec() + result := cmd.Exec() if result.code != 0 || result.output == "" { log.Info("Kubectl context is not set") @@ -312,14 +309,13 @@ func getKubeContext() bool { } // getReleaseContext extracts the Helmsman release context from the helm storage driver objects (secret or configmap) labels -func getReleaseContext(releaseName string, namespace string) string { - storageBackend := settings.StorageBackend +func getReleaseContext(releaseName, namespace, storageBackend string) string { // kubectl get secrets -n test1 -l MANAGED-BY=HELMSMAN -o=jsonpath='{.items[0].metadata.labels.HELMSMAN_CONTEXT}' // kubectl get secret sh.helm.release.v1.argo.v1 -n test1 -o=jsonpath='{.metadata.labels.HELMSMAN_CONTEXT}' // kubectl get secret -l owner=helm,name=argo -n test1 -o=jsonpath='{.items[-1].metadata.labels.HELMSMAN_CONTEXT}' cmd := kubectl([]string{"get", storageBackend, "-n", namespace, "-l", "owner=helm", "-l", "name=" + releaseName, "-o", "jsonpath='{.items[-1].metadata.labels.HELMSMAN_CONTEXT}'"}, "Getting Helmsman context for [ "+releaseName+" ] release") - result := cmd.exec() + result := cmd.Exec() if result.code != 0 { log.Fatal(result.errors) } @@ -334,7 +330,7 @@ func getReleaseContext(releaseName string, namespace string) string { func getKubectlClientVersion() string { cmd := kubectl([]string{"version", "--client", "--short"}, "Checking kubectl version") - result := cmd.exec() + result := cmd.Exec() if result.code != 0 { log.Fatal("While checking kubectl version: " + result.errors) } diff --git a/internal/app/logging.go b/internal/app/logging.go index 04eb4a6d..58417d1d 100644 --- a/internal/app/logging.go +++ b/internal/app/logging.go @@ -8,67 +8,59 @@ import ( ) type Logger struct { - *logger.Logger - LoggerInterface + SlackWebhook string + baseLogger *logger.Logger } -type LoggerInterface interface { - Info(string) - Debug(string) - Verbose(string) - Error(string) - Warning(string) - Notice(string) - Critical(string) - Fatal(string) -} - -var log *Logger -var baseLogger *logger.Logger +var log = &Logger{} func (l *Logger) Info(message string) { - baseLogger.Info(message) + l.baseLogger.Info(message) } func (l *Logger) Debug(message string) { if flags.debug { - baseLogger.Debug(message) + l.baseLogger.Debug(message) } } func (l *Logger) Verbose(message string) { if flags.verbose { - baseLogger.Info(message) + l.baseLogger.Info(message) } } func (l *Logger) Error(message string) { - if _, err := url.ParseRequestURI(settings.SlackWebhook); err == nil { - notifySlack(message, settings.SlackWebhook, true, flags.apply) + if _, err := url.ParseRequestURI(l.SlackWebhook); err == nil { + notifySlack(message, l.SlackWebhook, true, flags.apply) } - baseLogger.Error(message) + l.baseLogger.Error(message) +} + +func (l *Logger) Errorf(message string, args ...interface{}) { + l.baseLogger.Errorf(message, args...) } func (l *Logger) Warning(message string) { - baseLogger.Warning(message) + l.baseLogger.Warning(message) } func (l *Logger) Notice(message string) { - baseLogger.Notice(message) + l.baseLogger.Notice(message) } func (l *Logger) Critical(message string) { - if _, err := url.ParseRequestURI(settings.SlackWebhook); err == nil { - notifySlack(message, settings.SlackWebhook, true, flags.apply) + if _, err := url.ParseRequestURI(l.SlackWebhook); err == nil { + notifySlack(message, l.SlackWebhook, true, flags.apply) } - baseLogger.Critical(message) + l.baseLogger.Critical(message) } func (l *Logger) Fatal(message string) { - if _, err := url.ParseRequestURI(settings.SlackWebhook); err == nil { - notifySlack(message, settings.SlackWebhook, true, flags.apply) + if _, err := url.ParseRequestURI(l.SlackWebhook); err == nil { + notifySlack(message, l.SlackWebhook, true, flags.apply) } - baseLogger.Fatal(message) + l.baseLogger.Fatal(message) } func initLogs(verbose bool, noColors bool) { @@ -81,5 +73,5 @@ func initLogs(verbose bool, noColors bool) { if noColors { colors = 0 } - baseLogger, _ = logger.New("logger", colors, os.Stdout, logLevel) + log.baseLogger, _ = logger.New("logger", colors, os.Stdout, logLevel) } diff --git a/internal/app/main.go b/internal/app/main.go index c7c06736..6eabbbc6 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -1,3 +1,4 @@ +// Package app contains the main logic for the application. package app import ( @@ -14,7 +15,7 @@ const ( var ( flags cli - settings config + settings *config curContext string ) @@ -34,27 +35,14 @@ func Main() { } flags.readState(&s) - if len(s.GroupMap) > 0 { - s.TargetMap = s.getAppsInGroupsAsTargetMap() - if len(s.TargetMap) == 0 { - log.Info("No apps defined with -group flag were found, exiting") - os.Exit(0) - } - } - if len(s.TargetMap) > 0 { - s.TargetApps = s.getAppsInTargetsOnly() - s.TargetNamespaces = s.getNamespacesInTargetsOnly() - if len(s.TargetApps) == 0 { - log.Info("No apps defined with -target flag were found, exiting") - os.Exit(0) - } - } - settings = s.Settings + log.SlackWebhook = s.Settings.SlackWebhook + + settings = &s.Settings curContext = s.Context // set the kubecontext to be used Or create it if it does not exist log.Info("Setting up kubectl") - if !setKubeContext(settings.KubeContext) { + if !setKubeContext(s.Settings.KubeContext) { if err := createContext(&s); err != nil { log.Fatal(err.Error()) } @@ -82,7 +70,7 @@ func Main() { if !flags.skipValidation { log.Info("Validating charts") // validate charts-versions exist in defined repos - if err := validateReleaseCharts(&s); err != nil { + if err := s.validateReleaseCharts(); err != nil { log.Fatal(err.Error()) } } else { diff --git a/internal/app/namespace.go b/internal/app/namespace.go index 9cbc918a..27fb5e9e 100644 --- a/internal/app/namespace.go +++ b/internal/app/namespace.go @@ -43,10 +43,15 @@ type namespace struct { Labels map[string]string `yaml:"labels"` Annotations map[string]string `yaml:"annotations"` Quotas *quotas `yaml:"quotas,omitempty"` + disabled bool +} + +func (n *namespace) Disable() { + n.disabled = true } // print prints the namespace -func (n namespace) print() { +func (n *namespace) print() { fmt.Println("") fmt.Println("\tprotected : ", n.Protected) fmt.Println("\tlabels : ") diff --git a/internal/app/plan.go b/internal/app/plan.go index 0ba57ed8..f5a5dde9 100644 --- a/internal/app/plan.go +++ b/internal/app/plan.go @@ -30,19 +30,21 @@ type orderedDecision struct { // orderedCommand type representing a Command and it's priority weight and the targeted release from the desired state type orderedCommand struct { - Command command + Command Command Priority int targetRelease *release - beforeCommands []command - afterCommands []command + beforeCommands []Command + afterCommands []Command } // plan type representing the plan of actions to make the desired state come true. type plan struct { sync.Mutex - Commands []orderedCommand - Decisions []orderedDecision - Created time.Time + Commands []orderedCommand + Decisions []orderedDecision + Created time.Time + StorageBackend string + ReverseDelete bool } // createPlan initializes an empty plan @@ -55,7 +57,7 @@ func createPlan() *plan { } // addCommand adds a command type to the plan -func (p *plan) addCommand(cmd command, priority int, r *release, beforeCommands []command, afterCommands []command) { +func (p *plan) addCommand(cmd Command, priority int, r *release, beforeCommands []Command, afterCommands []Command) { p.Lock() defer p.Unlock() oc := orderedCommand{ @@ -108,7 +110,7 @@ func (p *plan) exec() { for _, cmd := range pl[priority] { sem <- struct{}{} wg.Add(1) - go releaseWithHooks(cmd, &wg, sem, errorsChan) + go releaseWithHooks(cmd, p.StorageBackend, &wg, sem, errorsChan) } wg.Wait() close(errorsChan) @@ -128,7 +130,7 @@ func (p *plan) exec() { } } -func releaseWithHooks(cmd orderedCommand, wg *sync.WaitGroup, sem chan struct{}, errors chan error) { +func releaseWithHooks(cmd orderedCommand, storageBackend string, wg *sync.WaitGroup, sem chan struct{}, errors chan error) { defer func() { wg.Done() <-sem @@ -147,7 +149,7 @@ func releaseWithHooks(cmd orderedCommand, wg *sync.WaitGroup, sem chan struct{}, } } if !flags.dryRun && !flags.destroy { - defer cmd.targetRelease.label() + defer cmd.targetRelease.label(storageBackend) } if err := execOne(cmd.Command, cmd.targetRelease); err != nil { errors <- err @@ -163,9 +165,9 @@ func releaseWithHooks(cmd orderedCommand, wg *sync.WaitGroup, sem chan struct{}, } // execOne executes a single ordered command -func execOne(cmd command, targetRelease *release) error { +func execOne(cmd Command, targetRelease *release) error { log.Notice(cmd.Description) - result := cmd.exec() + result := cmd.Exec() if result.code != 0 { errorMsg := result.errors if !flags.verbose { @@ -183,8 +185,8 @@ func execOne(cmd command, targetRelease *release) error { log.Notice(result.output) successMsg := "Finished: " + cmd.Description log.Notice(successMsg) - if _, err := url.ParseRequestURI(settings.SlackWebhook); err == nil { - notifySlack(cmd.Description+" ... SUCCESS!", settings.SlackWebhook, false, true) + if _, err := url.ParseRequestURI(log.SlackWebhook); err == nil { + notifySlack(cmd.Description+" ... SUCCESS!", log.SlackWebhook, false, true) } return nil } @@ -221,12 +223,12 @@ func (p *plan) print() { // sendPlanToSlack sends the description of plan commands to slack if a webhook is provided. func (p *plan) sendToSlack() { - if _, err := url.ParseRequestURI(settings.SlackWebhook); err == nil { + if _, err := url.ParseRequestURI(log.SlackWebhook); err == nil { str := "" for _, c := range p.Commands { str = str + c.Command.Description + "\n" } - notifySlack(strings.TrimRight(str, "\n"), settings.SlackWebhook, false, false) + notifySlack(strings.TrimRight(str, "\n"), log.SlackWebhook, false, false) } } diff --git a/internal/app/plan_test.go b/internal/app/plan_test.go index 6fb247ab..7b666e5f 100644 --- a/internal/app/plan_test.go +++ b/internal/app/plan_test.go @@ -32,7 +32,7 @@ func Test_plan_addCommand(t *testing.T) { Created time.Time } type args struct { - c command + c Command } tests := []struct { name string @@ -47,7 +47,7 @@ func Test_plan_addCommand(t *testing.T) { Created: time.Now(), }, args: args{ - c: command{ + c: Command{ Cmd: "bash", Args: []string{"-c", "echo this is fun"}, Description: "A bash command execution test with echo.", @@ -63,7 +63,7 @@ func Test_plan_addCommand(t *testing.T) { Created: tt.fields.Created, } r := &release{} - p.addCommand(tt.args.c, 0, r, []command{}, []command{}) + p.addCommand(tt.args.c, 0, r, []Command{}, []Command{}) if got := len(p.Commands); got != 1 { t.Errorf("addCommand(): got %v, want 1", got) } diff --git a/internal/app/release.go b/internal/app/release.go index 113866f7..a36cedbd 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -1,15 +1,11 @@ package app import ( - "encoding/json" "errors" "fmt" "os" - "path/filepath" - "regexp" "strconv" "strings" - "sync" ) // release type representing Helm releases which are described in the desired state @@ -38,6 +34,7 @@ type release struct { Timeout int `yaml:"timeout"` Hooks map[string]interface{} `yaml:"hooks"` MaxHistory int `yaml:"maxHistory"` + disabled bool } type chartVersion struct { @@ -51,21 +48,19 @@ func (r *release) key() string { return fmt.Sprintf("%s-%s", r.Name, r.Namespace) } +func (r *release) Disable() { + r.disabled = true +} + // isReleaseConsideredToRun checks if a release is being targeted for operations as specified by user cmd flags (--group or --target) -func (r *release) isConsideredToRun(s *state) bool { - if len(s.TargetMap) > 0 { - if _, ok := s.TargetMap[r.Name]; ok { - return true - } - return false - } - return true +func (r *release) isConsideredToRun() bool { + return !r.disabled } // validate validates if a release inside a desired state meets the specifications or not. // check the full specification @ https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md -func (r *release) validate(appLabel string, names map[string]map[string]bool, s *state) error { - if names[r.Name][r.Namespace] { +func (r *release) validate(appLabel string, seen map[string]map[string]bool, s *state) error { + if seen[r.Name][r.Namespace] { return errors.New("release name must be unique within a given namespace") } @@ -127,10 +122,10 @@ func (r *release) validate(appLabel string, names map[string]map[string]bool, s } } - if names[r.Name] == nil { - names[r.Name] = make(map[string]bool) + if seen[r.Name] == nil { + seen[r.Name] = make(map[string]bool) } - names[r.Name][r.Namespace] = true + seen[r.Name][r.Namespace] = true return nil } @@ -152,143 +147,8 @@ func validateHooks(hooks map[string]interface{}) (bool, string) { return true, "" } -// validateReleaseCharts validates if the charts defined in a release are valid. -// Valid charts are the ones that can be found in the defined repos. -// This function uses Helm search to verify if the chart can be found or not. -func validateReleaseCharts(s *state) error { - var fail bool - var apps map[string]*release - wg := sync.WaitGroup{} - sem := make(chan struct{}, resourcePool) - if len(s.TargetMap) > 0 { - apps = s.TargetApps - } else { - apps = s.Apps - } - c := make(chan string, len(apps)) - - charts := make(map[string]map[string][]string) - for app, r := range apps { - if charts[r.Chart] == nil { - charts[r.Chart] = make(map[string][]string) - } - - if charts[r.Chart][r.Version] == nil { - charts[r.Chart][r.Version] = make([]string, 0) - } - - if r.isConsideredToRun(s) { - charts[r.Chart][r.Version] = append(charts[r.Chart][r.Version], app) - } - } - - for chart, versions := range charts { - for version, apps := range versions { - concattedApps := strings.Join(apps, ", ") - ch := chart - v := version - sem <- struct{}{} - wg.Add(1) - go func(apps, chart, version string) { - defer func() { - wg.Done() - <-sem - }() - validateChart(concattedApps, chart, version, c) - }(concattedApps, ch, v) - } - } - - wg.Wait() - close(c) - for err := range c { - if err != "" { - fail = true - log.Error(err) - } - } - if fail { - return errors.New("chart validation failed") - } - return nil -} - -var versionExtractor = regexp.MustCompile(`[\n]version:\s?(.*)`) - -// validateChart validates if chart with the same name and version as specified in the DSF exists -func validateChart(apps, chart, version string, c chan string) { - if isLocalChart(chart) { - cmd := helmCmd([]string{"inspect", "chart", chart}, "Validating [ "+chart+" ] chart's availability") - - result := cmd.exec() - if result.code != 0 { - maybeRepo := filepath.Base(filepath.Dir(chart)) - c <- "Chart [ " + chart + " ] for apps [" + apps + "] can't be found. Inspection returned error: \"" + - strings.TrimSpace(result.errors) + "\" -- If this is not a local chart, add the repo [ " + maybeRepo + " ] in your helmRepos stanza." - return - } - matches := versionExtractor.FindStringSubmatch(result.output) - if len(matches) == 2 { - v := strings.Trim(matches[1], `'"`) - if strings.Trim(version, `'"`) != v { - c <- "Chart [ " + chart + " ] with version [ " + version + " ] is specified for " + - "apps [" + apps + "] but the chart found at that path has version [ " + v + " ] which does not match." - return - } - } - } else { - v := version - if len(v) == 0 { - v = "*" - } - cmd := helmCmd([]string{"search", "repo", chart, "--version", v, "-l"}, "Validating [ "+chart+" ] chart's version [ "+version+" ] availability") - - if result := cmd.exec(); result.code != 0 || strings.Contains(result.output, "No results found") { - c <- "Chart [ " + chart + " ] with version [ " + version + " ] is specified for " + - "apps [" + apps + "] but was not found. If this is not a local chart, define its helm repo in the helmRepo stanza in your DSF." - return - } - } -} - -// getChartVersion fetches the lastest chart version matching the semantic versioning constraints. -// If chart is local, returns the given release version -func getChartVersion(chart, version string) (string, string) { - if isLocalChart(chart) { - log.Info("Chart [ " + chart + " ] with version [ " + version + " ] was found locally.") - return version, "" - } - - cmd := helmCmd([]string{"search", "repo", chart, "--version", version, "-o", "json"}, "Getting latest non-local chart's version "+chart+"-"+version+"") - - result := cmd.exec() - if result.code != 0 { - return "", "Chart [ " + chart + " ] with version [ " + version + " ] is specified but not found in the helm repositories" - } - - chartVersions := make([]chartVersion, 0) - if err := json.Unmarshal([]byte(result.output), &chartVersions); err != nil { - log.Fatal(fmt.Sprint(err)) - } - - filteredChartVersions := make([]chartVersion, 0) - for _, c := range chartVersions { - if c.Name == chart { - filteredChartVersions = append(filteredChartVersions, c) - } - } - - if len(filteredChartVersions) < 1 { - return "", "Chart [ " + chart + " ] with version [ " + version + " ] is specified but not found in the helm repositories" - } else if len(filteredChartVersions) > 1 { - return "", "Multiple versions of chart [ " + chart + " ] with version [ " + version + " ] found in the helm repositories" - } - - return filteredChartVersions[0].Version, "" -} - // testRelease creates a Helm command to test a particular release. -func (r *release) test(afterCommands *[]command) { +func (r *release) test(afterCommands *[]Command) { cmd := helmCmd(r.getHelmArgsFor("test"), "Running tests for release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") *afterCommands = append(*afterCommands, cmd) } @@ -312,7 +172,7 @@ func (r *release) uninstall(p *plan, optionalNamespace ...string) { ns = optionalNamespace[0] } priority := r.Priority - if settings.ReverseDelete { + if p.ReverseDelete { priority = priority * -1 } @@ -337,7 +197,7 @@ func (r *release) diff() string { cmd := helmCmd(concat([]string{"diff", colorFlag, suppressDiffSecretsFlag}, diffContextFlag, r.getHelmArgsFor("diff")), "Diffing release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") - result := cmd.retryExec(3) + result := cmd.RetryExec(3) if result.code != 0 { log.Fatal(fmt.Sprintf("Command returned with exit code: %d. And error message: %s ", result.code, result.errors)) } else { @@ -389,7 +249,7 @@ func (r *release) rollback(cs *currentState, p *plan) { if r.Namespace == rs.Namespace { cmd := helmCmd(concat([]string{"rollback", r.Name, rs.getRevision()}, r.getWait(), r.getTimeout(), r.getNoHooks(), flags.getDryRunFlags()), "Rolling back release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") - p.addCommand(cmd, r.Priority, r, []command{}, []command{}) + p.addCommand(cmd, r.Priority, r, []Command{}, []Command{}) r.upgrade(p) // this is to reflect any changes in values file(s) p.addDecision("Release [ "+r.Name+" ] was deleted and is desired to be rolled back to "+ "namespace [ "+r.Namespace+" ]", r.Priority, create) @@ -404,13 +264,12 @@ func (r *release) rollback(cs *currentState, p *plan) { } // label applies Helmsman specific labels to Helm's state resources (secrets/configmaps) -func (r *release) label() { +func (r *release) label(storageBackend string) { if r.Enabled { - storageBackend := settings.StorageBackend cmd := kubectl([]string{"label", storageBackend, "-n", r.Namespace, "-l", "owner=helm,name=" + r.Name, "MANAGED-BY=HELMSMAN", "NAMESPACE=" + r.Namespace, "HELMSMAN_CONTEXT=" + curContext, "--overwrite"}, "Applying Helmsman labels to [ "+r.Name+" ] release") - result := cmd.exec() + result := cmd.Exec() if result.code != 0 { log.Fatal(result.errors) } @@ -460,7 +319,7 @@ func (r *release) getValuesFiles() []string { if r.SecretsFile != "" || len(r.SecretsFiles) > 0 { if settings.EyamlEnabled { - if !toolExists("eyaml") { + if !ToolExists("eyaml") { log.Fatal("hiera-eyaml is not installed/configured correctly. Aborting!") } } else { @@ -626,13 +485,13 @@ func (r *release) inheritMaxHistory(s *state) { // checkHooks checks if a hook of certain type exists and creates its command // if success condition for the hook is defined, a "kubectl wait" command is created // returns two slices of before and after commands -func (r *release) checkHooks(hookType string, p *plan, optionalNamespace ...string) ([]command, []command) { +func (r *release) checkHooks(hookType string, p *plan, optionalNamespace ...string) ([]Command, []Command) { ns := r.Namespace if len(optionalNamespace) > 0 { ns = optionalNamespace[0] } - var beforeCommands []command - var afterCommands []command + var beforeCommands []Command + var afterCommands []Command switch hookType { case "install": { @@ -685,8 +544,8 @@ func (r *release) checkHooks(hookType string, p *plan, optionalNamespace ...stri // shouldWaitForHook checks if there is a success condition to wait for after applying a hook // returns a boolean and the wait command if applicable -func (r *release) shouldWaitForHook(hookFile string, hookType string, namespace string) (bool, []command) { - var cmds []command +func (r *release) shouldWaitForHook(hookFile string, hookType string, namespace string) (bool, []Command) { + var cmds []Command if flags.dryRun { return false, cmds } else if _, ok := r.Hooks["successCondition"]; ok { diff --git a/internal/app/release_test.go b/internal/app/release_test.go index 834f8f7b..7f503280 100644 --- a/internal/app/release_test.go +++ b/internal/app/release_test.go @@ -12,7 +12,7 @@ func setupTestCase(t *testing.T) func(t *testing.T) { os.MkdirAll(os.TempDir()+"/helmsman-tests/myapp", os.ModePerm) os.MkdirAll(os.TempDir()+"/helmsman-tests/dir-with space/myapp", os.ModePerm) cmd := helmCmd([]string{"create", os.TempDir() + "/helmsman-tests/dir-with space/myapp"}, "creating an empty local chart directory") - if result := cmd.exec(); result.code != 0 { + if result := cmd.Exec(); result.code != 0 { log.Fatal(fmt.Sprintf("Command returned with exit code: %d. And error message: %s ", result.code, result.errors)) } @@ -27,7 +27,7 @@ func Test_validateRelease(t *testing.T) { Metadata: make(map[string]string), Certificates: make(map[string]string), Settings: (config{}), - Namespaces: map[string]namespace{"namespace": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}}, + Namespaces: map[string]*namespace{"namespace": &namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}, false}}, HelmRepos: make(map[string]string), Apps: make(map[string]*release), } @@ -424,7 +424,7 @@ func Test_inheritHooks(t *testing.T) { "successTimeout": "60s", }, }, - Namespaces: map[string]namespace{"namespace": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}}, + Namespaces: map[string]*namespace{"namespace": {false, limits{}, make(map[string]string), make(map[string]string), "as{}, false}}, HelmRepos: make(map[string]string), Apps: make(map[string]*release), } @@ -513,17 +513,15 @@ func Test_validateReleaseCharts(t *testing.T) { want bool }{ { - name: "test case 1: valid local path with no chart", - targetFlag: []string{}, + name: "test case 1: valid local path with no chart", args: args{ apps: map[string]*release{ "app": createFullReleasePointer(os.TempDir()+"/helmsman-tests/myapp", ""), }, }, - want: false, + want: true, }, { - name: "test case 2: invalid local path", - targetFlag: []string{}, + name: "test case 2: invalid local path", args: args{ apps: map[string]*release{ "app": createFullReleasePointer(os.TempDir()+"/does-not-exist/myapp", ""), @@ -531,8 +529,7 @@ func Test_validateReleaseCharts(t *testing.T) { }, want: false, }, { - name: "test case 3: valid chart local path with whitespace", - targetFlag: []string{}, + name: "test case 3: valid chart local path with whitespace", args: args{ apps: map[string]*release{ "app": createFullReleasePointer(os.TempDir()+"/helmsman-tests/dir-with space/myapp", "0.1.0"), @@ -540,8 +537,7 @@ func Test_validateReleaseCharts(t *testing.T) { }, want: true, }, { - name: "test case 4: valid chart from repo", - targetFlag: []string{}, + name: "test case 4: valid chart from repo", args: args{ apps: map[string]*release{ "app": createFullReleasePointer("prometheus-community/prometheus", "11.16.5"), @@ -586,23 +582,10 @@ func Test_validateReleaseCharts(t *testing.T) { defer teardownTestCase(t) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - stt := &state{} - stt.Apps = tt.args.apps - stt.TargetMap = make(map[string]bool) - stt.GroupMap = make(map[string]bool) - stt.TargetApps = make(map[string]*release) - for _, target := range tt.targetFlag { - stt.TargetMap[target] = true - } - for name, use := range stt.TargetMap { - if value, ok := stt.Apps[name]; ok && use { - stt.TargetApps[name] = value - } - } - for _, group := range tt.groupFlag { - stt.GroupMap[group] = true - } - err := validateReleaseCharts(stt) + stt := &state{Apps: tt.args.apps} + stt.makeTargetMap(tt.groupFlag, tt.targetFlag) + stt.disableUntargettedApps() + err := stt.validateReleaseCharts() switch err.(type) { case nil: if tt.want != true { diff --git a/internal/app/state.go b/internal/app/state.go index e1853163..e46beb89 100644 --- a/internal/app/state.go +++ b/internal/app/state.go @@ -7,6 +7,7 @@ import ( "os" "reflect" "strings" + "sync" ) // config type represents the settings fields @@ -30,19 +31,16 @@ type config struct { // state type represents the desired state of applications on a k8s cluster. type state struct { - Metadata map[string]string `yaml:"metadata"` - Certificates map[string]string `yaml:"certificates"` - Settings config `yaml:"settings"` - Context string `yaml:"context"` - Namespaces map[string]namespace `yaml:"namespaces"` - HelmRepos map[string]string `yaml:"helmRepos"` - PreconfiguredHelmRepos []string `yaml:"preconfiguredHelmRepos"` - Apps map[string]*release `yaml:"apps"` - AppsTemplates map[string]*release `yaml:"appsTemplates,omitempty"` + Metadata map[string]string `yaml:"metadata"` + Certificates map[string]string `yaml:"certificates"` + Settings config `yaml:"settings"` + Context string `yaml:"context"` + Namespaces map[string]*namespace `yaml:"namespaces"` + HelmRepos map[string]string `yaml:"helmRepos"` + PreconfiguredHelmRepos []string `yaml:"preconfiguredHelmRepos"` + Apps map[string]*release `yaml:"apps"` + AppsTemplates map[string]*release `yaml:"appsTemplates,omitempty"` TargetMap map[string]bool - GroupMap map[string]bool - TargetApps map[string]*release - TargetNamespaces map[string]namespace } // invokes either yaml or toml parser considering file extension @@ -66,6 +64,30 @@ func (s *state) toFile(file string) { } } +func (s *state) setDefaults() { + if s.Settings.StorageBackend != "" { + os.Setenv("HELM_DRIVER", s.Settings.StorageBackend) + } else { + // set default storage background to secret if not set by user + s.Settings.StorageBackend = "secret" + } + + // if there is no user-defined context name in the DSF(s), use the default context name + if s.Context == "" { + s.Context = defaultContextName + } + + for name, r := range s.Apps { + // Default app.Name to state name when unset + if r.Name == "" { + r.Name = name + } + // inherit globalHooks if local ones are not set + r.inheritHooks(s) + r.inheritMaxHistory(s) + } +} + // validate validates that the values specified in the desired state are valid according to the desired state spec. // check https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md for the detailed specification func (s *state) validate() error { @@ -184,6 +206,62 @@ func (s *state) validate() error { return nil } +// validateReleaseCharts validates if the charts defined in a release are valid. +// Valid charts are the ones that can be found in the defined repos. +// This function uses Helm search to verify if the chart can be found or not. +func (s *state) validateReleaseCharts() error { + var fail bool + wg := sync.WaitGroup{} + sem := make(chan struct{}, resourcePool) + c := make(chan string, len(s.Apps)) + + charts := make(map[string]map[string][]string) + for app, r := range s.Apps { + if !r.isConsideredToRun() { + continue + } + if charts[r.Chart] == nil { + charts[r.Chart] = make(map[string][]string) + } + + if charts[r.Chart][r.Version] == nil { + charts[r.Chart][r.Version] = make([]string, 0) + } + + charts[r.Chart][r.Version] = append(charts[r.Chart][r.Version], app) + } + + for chart, versions := range charts { + for version, apps := range versions { + concattedApps := strings.Join(apps, ", ") + ch := chart + v := version + sem <- struct{}{} + wg.Add(1) + go func(apps, chart, version string) { + defer func() { + wg.Done() + <-sem + }() + validateChart(concattedApps, chart, version, c) + }(concattedApps, ch, v) + } + } + + wg.Wait() + close(c) + for err := range c { + if err != "" { + fail = true + log.Error(err) + } + } + if fail { + return errors.New("chart validation failed") + } + return nil +} + // isValidCert checks if a certificate/key path/URI is valid func isValidCert(value string) (bool, string) { _, err1 := url.ParseRequestURI(value) @@ -208,47 +286,50 @@ func (s *state) overrideAppsNamespace(newNs string) { } } -func (s *state) getAppsInGroupsAsTargetMap() map[string]bool { - targetApps := make(map[string]bool) +func (s *state) makeTargetMap(groups, targets []string) { + if s.TargetMap == nil { + s.TargetMap = make(map[string]bool) + } + groupMap := map[string]bool{} + for _, v := range groups { + groupMap[v] = true + } for appName, data := range s.Apps { - if use, ok := s.GroupMap[data.Group]; ok && use { - targetApps[appName] = true + if use, ok := groupMap[data.Group]; ok && use { + s.TargetMap[appName] = true } } - return targetApps + for _, v := range targets { + s.TargetMap[v] = true + } } // get only those Apps that exist in TargetMap -func (s *state) getAppsInTargetsOnly() map[string]*release { - targetApps := make(map[string]*release) - for appName, use := range s.TargetMap { - if use { - if value, ok := s.Apps[appName]; ok { - targetApps[appName] = value - } +func (s *state) disableUntargettedApps() { + if len(s.TargetMap) == 0 { + return + } + namespaces := make(map[string]bool) + for appName, app := range s.Apps { + if use, ok := s.TargetMap[appName]; !use || !ok { + app.Disable() + } else { + namespaces[app.Namespace] = true } } - return targetApps -} - -func (s *state) getNamespacesInTargetsOnly() map[string]namespace { - targetNamespaces := make(map[string]namespace) - for appName, use := range s.TargetMap { - if use { - if value, ok := s.Apps[appName]; ok { - targetNamespaces[value.Namespace] = s.Namespaces[value.Namespace] - } + for nsName, ns := range s.Namespaces { + if use, ok := namespaces[nsName]; !use || !ok { + ns.Disable() } } - return targetNamespaces } // updateContextLabels applies Helmsman labels including overriding any previously-set context with the one found in the DSF func (s *state) updateContextLabels() { for _, r := range s.Apps { - if r.isConsideredToRun(s) { + if r.isConsideredToRun() { log.Info("Updating context and reapplying Helmsman labels for release [ " + r.Name + " ]") - r.label() + r.label(s.Settings.StorageBackend) } else { log.Warning(r.Name + " is not in the target group and therefore context and labels are not changed.") } @@ -286,9 +367,4 @@ func (s *state) print() { for t := range s.TargetMap { fmt.Println(t) } - fmt.Println("\nGroups: ") - fmt.Println("--------------- ") - for g := range s.GroupMap { - fmt.Println(g) - } } diff --git a/internal/app/state_test.go b/internal/app/state_test.go index b8a81080..beddfab2 100644 --- a/internal/app/state_test.go +++ b/internal/app/state_test.go @@ -10,7 +10,7 @@ func Test_state_validate(t *testing.T) { Metadata map[string]string Certificates map[string]string Settings config - Namespaces map[string]namespace + Namespaces map[string]*namespace HelmRepos map[string]string Apps map[string]*release } @@ -33,8 +33,8 @@ func Test_state_validate(t *testing.T) { Password: "$K8S_PASSWORD", ClusterURI: "https://192.168.99.100:8443", }, - Namespaces: map[string]namespace{ - "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, + Namespaces: map[string]*namespace{ + "staging": {false, limits{}, make(map[string]string), make(map[string]string), "as{}, false}, }, HelmRepos: map[string]string{ "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", @@ -57,8 +57,8 @@ func Test_state_validate(t *testing.T) { Password: "$K8S_PASSWORD", ClusterURI: "https://192.168.99.100:8443", }, - Namespaces: map[string]namespace{ - "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, + Namespaces: map[string]*namespace{ + "staging": {false, limits{}, make(map[string]string), make(map[string]string), "as{}, false}, }, HelmRepos: map[string]string{ "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", @@ -78,8 +78,8 @@ func Test_state_validate(t *testing.T) { Settings: config{ KubeContext: "minikube", }, - Namespaces: map[string]namespace{ - "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, + Namespaces: map[string]*namespace{ + "staging": {false, limits{}, make(map[string]string), make(map[string]string), "as{}, false}, }, HelmRepos: map[string]string{ "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", @@ -102,8 +102,8 @@ func Test_state_validate(t *testing.T) { Password: "K8S_PASSWORD", ClusterURI: "https://192.168.99.100:8443", }, - Namespaces: map[string]namespace{ - "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, + Namespaces: map[string]*namespace{ + "staging": {false, limits{}, make(map[string]string), make(map[string]string), "as{}, false}, }, HelmRepos: map[string]string{ "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", @@ -126,8 +126,8 @@ func Test_state_validate(t *testing.T) { Password: "K8S_PASSWORD", ClusterURI: "$URI", // unset env }, - Namespaces: map[string]namespace{ - "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, + Namespaces: map[string]*namespace{ + "staging": {false, limits{}, make(map[string]string), make(map[string]string), "as{}, false}, }, HelmRepos: map[string]string{ "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", @@ -150,8 +150,8 @@ func Test_state_validate(t *testing.T) { Password: "K8S_PASSWORD", ClusterURI: "https//192.168.99.100:8443", // invalid url }, - Namespaces: map[string]namespace{ - "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, + Namespaces: map[string]*namespace{ + "staging": {false, limits{}, make(map[string]string), make(map[string]string), "as{}, false}, }, HelmRepos: map[string]string{ "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", @@ -173,8 +173,8 @@ func Test_state_validate(t *testing.T) { Password: "$K8S_PASSWORD", ClusterURI: "https://192.168.99.100:8443", }, - Namespaces: map[string]namespace{ - "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, + Namespaces: map[string]*namespace{ + "staging": {false, limits{}, make(map[string]string), make(map[string]string), "as{}, false}, }, HelmRepos: map[string]string{ "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", @@ -194,8 +194,8 @@ func Test_state_validate(t *testing.T) { Password: "$K8S_PASSWORD", ClusterURI: "https://192.168.99.100:8443", }, - Namespaces: map[string]namespace{ - "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, + Namespaces: map[string]*namespace{ + "staging": {false, limits{}, make(map[string]string), make(map[string]string), "as{}, false}, }, HelmRepos: map[string]string{ "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", @@ -218,8 +218,8 @@ func Test_state_validate(t *testing.T) { Password: "$K8S_PASSWORD", ClusterURI: "https://192.168.99.100:8443", }, - Namespaces: map[string]namespace{ - "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, + Namespaces: map[string]*namespace{ + "staging": {false, limits{}, make(map[string]string), make(map[string]string), "as{}, false}, }, HelmRepos: map[string]string{ "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", @@ -236,8 +236,8 @@ func Test_state_validate(t *testing.T) { Settings: config{ KubeContext: "minikube", }, - Namespaces: map[string]namespace{ - "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, + Namespaces: map[string]*namespace{ + "staging": {false, limits{}, make(map[string]string), make(map[string]string), "as{}, false}, }, HelmRepos: map[string]string{ "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", @@ -270,7 +270,7 @@ func Test_state_validate(t *testing.T) { Settings: config{ KubeContext: "minikube", }, - Namespaces: map[string]namespace{}, + Namespaces: map[string]*namespace{}, HelmRepos: map[string]string{ "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", "myrepo": "s3://my-repo/charts", @@ -286,8 +286,8 @@ func Test_state_validate(t *testing.T) { Settings: config{ KubeContext: "minikube", }, - Namespaces: map[string]namespace{ - "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, + Namespaces: map[string]*namespace{ + "staging": {false, limits{}, make(map[string]string), make(map[string]string), "as{}, false}, }, HelmRepos: nil, Apps: make(map[string]*release), @@ -301,8 +301,8 @@ func Test_state_validate(t *testing.T) { Settings: config{ KubeContext: "minikube", }, - Namespaces: map[string]namespace{ - "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, + Namespaces: map[string]*namespace{ + "staging": {false, limits{}, make(map[string]string), make(map[string]string), "as{}, false}, }, HelmRepos: map[string]string{}, Apps: make(map[string]*release), @@ -316,8 +316,8 @@ func Test_state_validate(t *testing.T) { Settings: config{ KubeContext: "minikube", }, - Namespaces: map[string]namespace{ - "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, + Namespaces: map[string]*namespace{ + "staging": {false, limits{}, make(map[string]string), make(map[string]string), "as{}, false}, }, HelmRepos: map[string]string{ "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", @@ -334,8 +334,8 @@ func Test_state_validate(t *testing.T) { Settings: config{ KubeContext: "minikube", }, - Namespaces: map[string]namespace{ - "staging": namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}}, + Namespaces: map[string]*namespace{ + "staging": {false, limits{}, make(map[string]string), make(map[string]string), "as{}, false}, }, HelmRepos: map[string]string{ "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", diff --git a/internal/app/utils.go b/internal/app/utils.go index 80456093..9c3dafd2 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -33,7 +33,7 @@ func printMap(m map[string]string, indent int) { } // printObjectMap prints to the console any map of string keys and object values. -func printNamespacesMap(m map[string]namespace) { +func printNamespacesMap(m map[string]*namespace) { for key, value := range m { fmt.Println(key, " : protected = ", value) } @@ -634,13 +634,13 @@ func decryptSecret(name string) error { } } - command := command{ + command := Command{ Cmd: cmd, Args: args, Description: "Decrypting " + name, } - result := command.exec() + result := command.Exec() if !settings.EyamlEnabled { _, fileNotFound := os.Stat(name + ".dec") if fileNotFound != nil && !isOfType(name, []string{".dec"}) { diff --git a/internal/app/utils_test.go b/internal/app/utils_test.go index f47014a3..d9dd4cd5 100644 --- a/internal/app/utils_test.go +++ b/internal/app/utils_test.go @@ -446,8 +446,7 @@ func Test_eyamlSecrets(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Log(tt.want) - defaultSettings := settings - defer func() { settings = defaultSettings }() + settings = &config{} settings.EyamlEnabled = tt.args.s.EyamlEnabled settings.EyamlPublicKeyPath = tt.args.s.EyamlPublicKeyPath settings.EyamlPrivateKeyPath = tt.args.s.EyamlPrivateKeyPath From 39882b351f282076ed2ce6c49818545c346794cd Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Wed, 11 Nov 2020 18:54:19 +0000 Subject: [PATCH 0701/1127] feat: allow executable hooks Signed-off-by: Luis Davim --- internal/app/command.go | 11 +---- internal/app/release.go | 78 ++++++++++++++++++++---------------- internal/app/release_test.go | 18 ++++++++- internal/app/utils.go | 23 ++++++++--- 4 files changed, 79 insertions(+), 51 deletions(-) diff --git a/internal/app/command.go b/internal/app/command.go index 2fe5f9c0..3e57bdbd 100644 --- a/internal/app/command.go +++ b/internal/app/command.go @@ -99,13 +99,6 @@ func (c *Command) Exec() ExitStatus { // ToolExists returns true if the tool is present in the environment and false otherwise. // It takes as input the tool's command to check if it is recognizable or not. e.g. helm or kubectl func ToolExists(tool string) bool { - cmd := Command{ - Cmd: tool, - Args: []string{}, - Description: "Validating that [ " + tool + " ] is installed", - } - - result := cmd.Exec() - - return result.code == 0 + _, err := exec.LookPath(tool) + return err == nil } diff --git a/internal/app/release.go b/internal/app/release.go index a36cedbd..da05b69c 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -107,7 +107,7 @@ func (r *release) validate(appLabel string, seen map[string]map[string]bool, s * } if r.PostRenderer != "" { - if _, err := os.Stat(r.PostRenderer); err != nil { + if !ToolExists(r.PostRenderer) { return fmt.Errorf(r.PostRenderer + " must be valid relative (from dsf file) file path.") } } @@ -135,8 +135,11 @@ func validateHooks(hooks map[string]interface{}) (bool, string) { for key, value := range hooks { switch key { case "preInstall", "postInstall", "preUpgrade", "postUpgrade", "preDelete", "postDelete": - if err := isValidFile(value.(string), []string{".yaml", ".yml"}); err != nil { - return false, err.Error() + hook := value.(string) + if err := isValidFile(hook, []string{".yaml", ".yml", ".json"}); err != nil { + if !ToolExists(strings.Fields(hook)[0]) { + return false, err.Error() + } } case "successCondition", "successTimeout", "deleteOnSuccess": continue @@ -155,7 +158,7 @@ func (r *release) test(afterCommands *[]Command) { // installRelease creates a Helm command to install a particular release in a particular namespace using a particular Tiller. func (r *release) install(p *plan) { - before, after := r.checkHooks("install", p) + before, after := r.checkHooks("install") if r.Test { r.test(&after) @@ -176,7 +179,7 @@ func (r *release) uninstall(p *plan, optionalNamespace ...string) { priority = priority * -1 } - before, after := r.checkHooks("delete", p, ns) + before, after := r.checkHooks("delete", ns) cmd := helmCmd(r.getHelmArgsFor("uninstall", ns), "Delete release [ "+r.Name+" ] in namespace [ "+ns+" ]") p.addCommand(cmd, priority, r, before, after) @@ -212,7 +215,7 @@ func (r *release) diff() string { // upgradeRelease upgrades an existing release with the specified values.yaml func (r *release) upgrade(p *plan) { - before, after := r.checkHooks("upgrade", p) + before, after := r.checkHooks("upgrade") if r.Test { r.test(&after) @@ -485,61 +488,66 @@ func (r *release) inheritMaxHistory(s *state) { // checkHooks checks if a hook of certain type exists and creates its command // if success condition for the hook is defined, a "kubectl wait" command is created // returns two slices of before and after commands -func (r *release) checkHooks(hookType string, p *plan, optionalNamespace ...string) ([]Command, []Command) { +func (r *release) checkHooks(action string, optionalNamespace ...string) ([]Command, []Command) { ns := r.Namespace if len(optionalNamespace) > 0 { ns = optionalNamespace[0] } - var beforeCommands []Command - var afterCommands []Command - switch hookType { + var ( + beforeCmds []Command + afterCmds []Command + ) + switch action { case "install": { if _, ok := r.Hooks["preInstall"]; ok { - beforeCommands = append(beforeCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks["preInstall"].(string), flags.getKubeDryRunFlag("apply")}, "Apply pre-install manifest "+r.Hooks["preInstall"].(string))) - if wait, cmds := r.shouldWaitForHook(r.Hooks["preInstall"].(string), "pre-install", ns); wait { - beforeCommands = append(beforeCommands, cmds...) - } + beforeCmds = append(beforeCmds, r.getHookCommands("preInstall", ns)...) } if _, ok := r.Hooks["postInstall"]; ok { - afterCommands = append(afterCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks["postInstall"].(string), flags.getKubeDryRunFlag("apply")}, "Apply post-install manifest "+r.Hooks["postInstall"].(string))) - if wait, cmds := r.shouldWaitForHook(r.Hooks["postInstall"].(string), "post-install", ns); wait { - afterCommands = append(afterCommands, cmds...) - } + afterCmds = append(afterCmds, r.getHookCommands("postInstall", ns)...) } } case "upgrade": { if _, ok := r.Hooks["preUpgrade"]; ok { - beforeCommands = append(beforeCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks["preUpgrade"].(string), flags.getKubeDryRunFlag("apply")}, "Apply pre-upgrade manifest "+r.Hooks["preUpgrade"].(string))) - if wait, cmds := r.shouldWaitForHook(r.Hooks["preUpgrade"].(string), "pre-upgrade", ns); wait { - beforeCommands = append(beforeCommands, cmds...) - } + beforeCmds = append(beforeCmds, r.getHookCommands("preUpgrade", ns)...) } if _, ok := r.Hooks["postUpgrade"]; ok { - afterCommands = append(afterCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks["postUpgrade"].(string), flags.getKubeDryRunFlag("apply")}, "Apply post-upgrade manifest "+r.Hooks["postUpgrade"].(string))) - if wait, cmds := r.shouldWaitForHook(r.Hooks["postUpgrade"].(string), "post-upgrade", ns); wait { - afterCommands = append(afterCommands, cmds...) - } + afterCmds = append(afterCmds, r.getHookCommands("postUpgrade", ns)...) } } case "delete": { if _, ok := r.Hooks["preDelete"]; ok { - beforeCommands = append(beforeCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks["preDelete"].(string), flags.getKubeDryRunFlag("apply")}, "Apply pre-delete manifest "+r.Hooks["preDelete"].(string))) - if wait, cmds := r.shouldWaitForHook(r.Hooks["preDelete"].(string), "pre-delete", ns); wait { - beforeCommands = append(beforeCommands, cmds...) - } + beforeCmds = append(beforeCmds, r.getHookCommands("preDelete", ns)...) } if _, ok := r.Hooks["postDelete"]; ok { - afterCommands = append(afterCommands, kubectl([]string{"apply", "-n", ns, "-f", r.Hooks["postDelete"].(string), flags.getKubeDryRunFlag("apply")}, "Apply post-delete manifest "+r.Hooks["postDelete"].(string))) - if wait, cmds := r.shouldWaitForHook(r.Hooks["postDelete"].(string), "post-delete", ns); wait { - afterCommands = append(afterCommands, cmds...) - } + afterCmds = append(afterCmds, r.getHookCommands("postDelete", ns)...) } } } - return beforeCommands, afterCommands + return beforeCmds, afterCmds +} + +func (r *release) getHookCommands(hookType, ns string) []Command { + var cmds []Command + if _, ok := r.Hooks[hookType]; ok { + hook := r.Hooks[hookType].(string) + if err := isValidFile(hook, []string{".yaml", ".yml", ".json"}); err != nil { + cmds = append(cmds, kubectl([]string{"apply", "-n", ns, "-f", hook, flags.getKubeDryRunFlag("apply")}, "Apply "+hookType+" manifest "+r.Hooks["preInstall"].(string))) + if wait, waitCmds := r.shouldWaitForHook(hook, hookType, ns); wait { + cmds = append(cmds, waitCmds...) + } + } else { + args := strings.Fields(hook) + cmds = append(cmds, Command{ + Cmd: args[0], + Args: args[1:], + Description: hookType + "hook", + }) + } + } + return cmds } // shouldWaitForHook checks if there is a success condition to wait for after applying a hook diff --git a/internal/app/release_test.go b/internal/app/release_test.go index 7f503280..3a5eaa37 100644 --- a/internal/app/release_test.go +++ b/internal/app/release_test.go @@ -281,7 +281,7 @@ func Test_validateRelease(t *testing.T) { }, s: st, }, - want: "../../tests/values.xml must be of one the following file formats: .yaml, .yml", + want: "../../tests/values.xml must be of one the following file formats: .yaml, .yml, .json", }, { name: "test case 16 - valid hook file type", args: args{ @@ -396,6 +396,22 @@ func Test_validateRelease(t *testing.T) { s: st, }, want: "doesnt-exist.sh must be valid relative (from dsf file) file path.", + }, { + name: "test case 23 - executable hook type", + args: args{ + r: &release{ + Name: "release20", + Description: "", + Namespace: "namespace", + Enabled: true, + Chart: "repo/chartX", + Version: "1.0", + ValuesFile: "../../tests/values.yaml", + Hooks: map[string]interface{}{"preDelete": "../../tests/post-renderer.sh"}, + }, + s: st, + }, + want: "", }, } names := make(map[string]map[string]bool) diff --git a/internal/app/utils.go b/internal/app/utils.go index 9c3dafd2..73df47d3 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -158,7 +158,10 @@ func substituteVarsInStaticFiles(s *state) { if len(v.Hooks) != 0 { for key, val := range v.Hooks { if key != "deleteOnSuccess" && key != "successTimeout" && key != "successCondition" { - v.Hooks[key] = substituteVarsInYaml(val.(string)) + hook := val.(string) + if err := isValidFile(hook, []string{".yaml", ".yml"}); err != nil { + v.Hooks[key] = substituteVarsInYaml(hook) + } } } } @@ -166,7 +169,10 @@ func substituteVarsInStaticFiles(s *state) { if len(s.Settings.GlobalHooks) != 0 { for key, val := range s.Settings.GlobalHooks { if key != "deleteOnSuccess" && key != "successTimeout" && key != "successCondition" { - s.Settings.GlobalHooks[key] = substituteVarsInYaml(val.(string)) + hook := val.(string) + if err := isValidFile(hook, []string{".yaml", ".yml"}); err != nil { + s.Settings.GlobalHooks[key] = substituteVarsInYaml(hook) + } } } } @@ -224,7 +230,7 @@ func stringInSlice(a string, list []string) bool { func resolvePaths(relativeToFile string, s *state) { dir := filepath.Dir(relativeToFile) downloadDest, _ := filepath.Abs(createTempDir(tempFilesDir, "tmp")) - for k, v := range s.Apps { + for _, v := range s.Apps { if v.ValuesFile != "" { v.ValuesFile, _ = resolveOnePath(v.ValuesFile, dir, downloadDest) } @@ -235,7 +241,10 @@ func resolvePaths(relativeToFile string, s *state) { if len(v.Hooks) != 0 { for key, val := range v.Hooks { if key != "deleteOnSuccess" && key != "successTimeout" && key != "successCondition" { - v.Hooks[key], _ = resolveOnePath(val.(string), dir, downloadDest) + hook := val.(string) + if err := isValidFile(hook, []string{".yaml", ".yml", ".json"}); err != nil { + v.Hooks[key], _ = resolveOnePath(hook, dir, downloadDest) + } } } } @@ -262,7 +271,6 @@ func resolvePaths(relativeToFile string, s *state) { } } } - s.Apps[k] = v } // resolving paths for Bearer Token path in settings if s.Settings.BearerTokenPath != "" { @@ -272,7 +280,10 @@ func resolvePaths(relativeToFile string, s *state) { if len(s.Settings.GlobalHooks) != 0 { for key, val := range s.Settings.GlobalHooks { if key != "deleteOnSuccess" && key != "successTimeout" && key != "successCondition" { - s.Settings.GlobalHooks[key], _ = resolveOnePath(val.(string), dir, downloadDest) + hook := val.(string) + if err := isValidFile(hook, []string{".yaml", ".yml", ".json"}); err != nil { + s.Settings.GlobalHooks[key], _ = resolveOnePath(hook, dir, downloadDest) + } } } } From 8baadabe5ff6bb2027276caa96ead75ebdeb6e1d Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Thu, 12 Nov 2020 11:53:35 +0000 Subject: [PATCH 0702/1127] refactor: reduce the number of times we loop through the apps in the DST Signed-off-by: Luis Davim --- internal/app/cli.go | 20 +- internal/app/decision_maker_test.go | 5 +- internal/app/logging.go | 2 - internal/app/main.go | 34 +--- internal/app/release.go | 49 +---- internal/app/release_files.go | 100 +++++++++ internal/app/release_test.go | 5 +- internal/app/state.go | 63 ++---- internal/app/state_files.go | 225 ++++++++++++++++++++ internal/app/state_files_test.go | 304 ++++++++++++++++++++++++++++ internal/app/utils.go | 218 -------------------- internal/app/utils_test.go | 298 --------------------------- 12 files changed, 662 insertions(+), 661 deletions(-) create mode 100644 internal/app/release_files.go create mode 100644 internal/app/state_files.go create mode 100644 internal/app/state_files_test.go diff --git a/internal/app/cli.go b/internal/app/cli.go index fb08bd1c..6b20febc 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -243,24 +243,18 @@ func (c *cli) readState(s *state) { } s.setDefaults() - s.makeTargetMap(c.group, c.target) + s.disableUntargettedApps(c.group, c.target) - if len(c.target) > 0 { - if len(s.TargetMap) == 0 { - log.Info("No apps defined with -target flag were found, exiting") - os.Exit(0) - } + if len(c.target) > 0 && len(s.TargetMap) == 0 { + log.Info("No apps defined with -target flag were found, exiting") + os.Exit(0) } - if len(c.group) > 0 { - if len(s.TargetMap) == 0 { - log.Info("No apps defined with -group flag were found, exiting") - os.Exit(0) - } + if len(c.group) > 0 && len(s.TargetMap) == 0 { + log.Info("No apps defined with -group flag were found, exiting") + os.Exit(0) } - s.disableUntargettedApps() - if !c.skipValidation { // validate the desired state content if len(c.files) > 0 { diff --git a/internal/app/decision_maker_test.go b/internal/app/decision_maker_test.go index aa92538c..1e2b68a9 100644 --- a/internal/app/decision_maker_test.go +++ b/internal/app/decision_maker_test.go @@ -226,8 +226,7 @@ func Test_decide(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { cs := newCurrentState() - tt.args.s.makeTargetMap([]string{}, tt.targetFlag) - tt.args.s.disableUntargettedApps() + tt.args.s.disableUntargettedApps([]string{}, tt.targetFlag) outcome := plan{} // Act cs.decide(tt.args.s.Apps[tt.args.r], tt.args.s, &outcome, "", "") @@ -304,7 +303,7 @@ func Test_decide_group(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - tt.args.s.makeTargetMap(tt.groupFlag, []string{}) + tt.args.s.disableUntargettedApps(tt.groupFlag, []string{}) if len(tt.args.s.TargetMap) != len(tt.want) { t.Errorf("decide() = %d, want %d", len(tt.args.s.TargetMap), len(tt.want)) } diff --git a/internal/app/logging.go b/internal/app/logging.go index 58417d1d..7e8b0a1f 100644 --- a/internal/app/logging.go +++ b/internal/app/logging.go @@ -12,8 +12,6 @@ type Logger struct { baseLogger *logger.Logger } -var log = &Logger{} - func (l *Logger) Info(message string) { l.baseLogger.Info(message) } diff --git a/internal/app/main.go b/internal/app/main.go index 6eabbbc6..adda680e 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -17,6 +17,7 @@ var ( flags cli settings *config curContext string + log = &Logger{} ) func init() { @@ -104,36 +105,3 @@ func Main() { p.exec() } } - -// cleanup deletes the k8s certificates and keys files -// It also deletes any Tiller TLS certs and keys -// and secret files -func (s *state) cleanup() { - log.Verbose("Cleaning up sensitive and temp files") - if _, err := os.Stat("ca.crt"); err == nil { - deleteFile("ca.crt") - } - - if _, err := os.Stat("ca.key"); err == nil { - deleteFile("ca.key") - } - - if _, err := os.Stat("client.crt"); err == nil { - deleteFile("client.crt") - } - - if _, err := os.Stat("bearer.token"); err == nil { - deleteFile("bearer.token") - } - - for _, app := range s.Apps { - if _, err := os.Stat(app.SecretsFile + ".dec"); err == nil { - deleteFile(app.SecretsFile + ".dec") - } - for _, secret := range app.SecretsFiles { - if _, err := os.Stat(secret + ".dec"); err == nil { - deleteFile(secret + ".dec") - } - } - } -} diff --git a/internal/app/release.go b/internal/app/release.go index da05b69c..43a2d932 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -310,53 +310,6 @@ func (r *release) getTimeout() []string { return []string{} } -// getValuesFiles return partial install/upgrade release command to substitute the -f flag in Helm. -func (r *release) getValuesFiles() []string { - var fileList []string - - if r.ValuesFile != "" { - fileList = append(fileList, r.ValuesFile) - } else if len(r.ValuesFiles) > 0 { - fileList = append(fileList, r.ValuesFiles...) - } - - if r.SecretsFile != "" || len(r.SecretsFiles) > 0 { - if settings.EyamlEnabled { - if !ToolExists("eyaml") { - log.Fatal("hiera-eyaml is not installed/configured correctly. Aborting!") - } - } else { - if !helmPluginExists("secrets") { - log.Fatal("helm secrets plugin is not installed/configured correctly. Aborting!") - } - } - } - if r.SecretsFile != "" { - if err := decryptSecret(r.SecretsFile); err != nil { - log.Fatal(err.Error()) - } - fileList = append(fileList, r.SecretsFile+".dec") - } else if len(r.SecretsFiles) > 0 { - for i := 0; i < len(r.SecretsFiles); i++ { - if err := decryptSecret(r.SecretsFiles[i]); err != nil { - log.Fatal(err.Error()) - } - // if .dec extension is added before to the secret filename, don't add it again. - // This happens at upgrade time (where diff and upgrade both call this function) - if !isOfType(r.SecretsFiles[i], []string{".dec"}) { - r.SecretsFiles[i] = r.SecretsFiles[i] + ".dec" - } - } - fileList = append(fileList, r.SecretsFiles...) - } - - fileListArgs := []string{} - for _, file := range fileList { - fileListArgs = append(fileListArgs, "-f", file) - } - return fileListArgs -} - // getSetValues returns --set params to be used with helm install/upgrade commands func (r *release) getSetValues() []string { result := []string{} @@ -543,7 +496,7 @@ func (r *release) getHookCommands(hookType, ns string) []Command { cmds = append(cmds, Command{ Cmd: args[0], Args: args[1:], - Description: hookType + "hook", + Description: hookType, }) } } diff --git a/internal/app/release_files.go b/internal/app/release_files.go new file mode 100644 index 00000000..21f9990b --- /dev/null +++ b/internal/app/release_files.go @@ -0,0 +1,100 @@ +package app + +// substituteVarsInStaticFiles loops through the values/secrets files and substitutes variables into them. +func (r *release) substituteVarsInStaticFiles() { + if r.ValuesFile != "" { + r.ValuesFile = substituteVarsInYaml(r.ValuesFile) + } + if r.SecretsFile != "" { + r.SecretsFile = substituteVarsInYaml(r.SecretsFile) + } + + for i := range r.ValuesFiles { + r.ValuesFiles[i] = substituteVarsInYaml(r.ValuesFiles[i]) + } + for i := range r.SecretsFiles { + r.SecretsFiles[i] = substituteVarsInYaml(r.SecretsFiles[i]) + } + + for key, val := range r.Hooks { + if key != "deleteOnSuccess" && key != "successTimeout" && key != "successCondition" { + hook := val.(string) + if err := isValidFile(hook, []string{".yaml", ".yml"}); err != nil { + r.Hooks[key] = substituteVarsInYaml(hook) + } + } + } +} + +// resolvePaths resolves relative paths of certs/keys/chart/value file/secret files/etc and replace them with a absolute paths +func (r *release) resolvePaths(dir, downloadDest string) { + if r.ValuesFile != "" { + r.ValuesFile, _ = resolveOnePath(r.ValuesFile, dir, downloadDest) + } + if r.SecretsFile != "" { + r.SecretsFile, _ = resolveOnePath(r.SecretsFile, dir, downloadDest) + } + + for i := range r.ValuesFiles { + r.ValuesFiles[i], _ = resolveOnePath(r.ValuesFiles[i], dir, downloadDest) + } + for i := range r.SecretsFiles { + r.SecretsFiles[i], _ = resolveOnePath(r.SecretsFiles[i], dir, downloadDest) + } + + for key, val := range r.Hooks { + if key != "deleteOnSuccess" && key != "successTimeout" && key != "successCondition" { + hook := val.(string) + if err := isValidFile(hook, []string{".yaml", ".yml", ".json"}); err != nil { + r.Hooks[key], _ = resolveOnePath(hook, dir, downloadDest) + } + } + } +} + +// getValuesFiles return partial install/upgrade release command to substitute the -f flag in Helm. +func (r *release) getValuesFiles() []string { + var fileList []string + + if r.ValuesFile != "" { + fileList = append(fileList, r.ValuesFile) + } else if len(r.ValuesFiles) > 0 { + fileList = append(fileList, r.ValuesFiles...) + } + + if r.SecretsFile != "" || len(r.SecretsFiles) > 0 { + if settings.EyamlEnabled { + if !ToolExists("eyaml") { + log.Fatal("hiera-eyaml is not installed/configured correctly. Aborting!") + } + } else { + if !helmPluginExists("secrets") { + log.Fatal("helm secrets plugin is not installed/configured correctly. Aborting!") + } + } + } + if r.SecretsFile != "" { + if err := decryptSecret(r.SecretsFile); err != nil { + log.Fatal(err.Error()) + } + fileList = append(fileList, r.SecretsFile+".dec") + } else if len(r.SecretsFiles) > 0 { + for i := 0; i < len(r.SecretsFiles); i++ { + if err := decryptSecret(r.SecretsFiles[i]); err != nil { + log.Fatal(err.Error()) + } + // if .dec extension is added before to the secret filename, don't add it again. + // This happens at upgrade time (where diff and upgrade both call this function) + if !isOfType(r.SecretsFiles[i], []string{".dec"}) { + r.SecretsFiles[i] = r.SecretsFiles[i] + ".dec" + } + } + fileList = append(fileList, r.SecretsFiles...) + } + + fileListArgs := []string{} + for _, file := range fileList { + fileListArgs = append(fileListArgs, "-f", file) + } + return fileListArgs +} diff --git a/internal/app/release_test.go b/internal/app/release_test.go index 3a5eaa37..6037db56 100644 --- a/internal/app/release_test.go +++ b/internal/app/release_test.go @@ -535,7 +535,7 @@ func Test_validateReleaseCharts(t *testing.T) { "app": createFullReleasePointer(os.TempDir()+"/helmsman-tests/myapp", ""), }, }, - want: true, + want: false, }, { name: "test case 2: invalid local path", args: args{ @@ -599,8 +599,7 @@ func Test_validateReleaseCharts(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { stt := &state{Apps: tt.args.apps} - stt.makeTargetMap(tt.groupFlag, tt.targetFlag) - stt.disableUntargettedApps() + stt.disableUntargettedApps(tt.groupFlag, tt.targetFlag) err := stt.validateReleaseCharts() switch err.(type) { case nil: diff --git a/internal/app/state.go b/internal/app/state.go index e46beb89..38d4d228 100644 --- a/internal/app/state.go +++ b/internal/app/state.go @@ -43,27 +43,6 @@ type state struct { TargetMap map[string]bool } -// invokes either yaml or toml parser considering file extension -func (s *state) fromFile(file string) (bool, string) { - if isOfType(file, []string{".toml"}) { - return fromTOML(file, s) - } else if isOfType(file, []string{".yaml", ".yml"}) { - return fromYAML(file, s) - } else { - return false, "State file does not have toml/yaml extension." - } -} - -func (s *state) toFile(file string) { - if isOfType(file, []string{".toml"}) { - toTOML(file, s) - } else if isOfType(file, []string{".yaml", ".yml"}) { - toYAML(file, s) - } else { - log.Fatal("State file does not have toml/yaml extension.") - } -} - func (s *state) setDefaults() { if s.Settings.StorageBackend != "" { os.Setenv("HELM_DRIVER", s.Settings.StorageBackend) @@ -286,39 +265,37 @@ func (s *state) overrideAppsNamespace(newNs string) { } } -func (s *state) makeTargetMap(groups, targets []string) { +// get only those Apps that exist in TargetMap +func (s *state) disableUntargettedApps(groups, targets []string) { if s.TargetMap == nil { s.TargetMap = make(map[string]bool) } - groupMap := map[string]bool{} - for _, v := range groups { - groupMap[v] = true - } - for appName, data := range s.Apps { - if use, ok := groupMap[data.Group]; ok && use { - s.TargetMap[appName] = true - } + if len(targets) == 0 && len(groups) == 0 { + return } - for _, v := range targets { - s.TargetMap[v] = true + for _, t := range targets { + s.TargetMap[t] = true } -} - -// get only those Apps that exist in TargetMap -func (s *state) disableUntargettedApps() { - if len(s.TargetMap) == 0 { - return + groupMap := make(map[string]struct{}) + namespaces := make(map[string]struct{}) + for _, g := range groups { + groupMap[g] = struct{}{} } - namespaces := make(map[string]bool) for appName, app := range s.Apps { - if use, ok := s.TargetMap[appName]; !use || !ok { - app.Disable() + if _, ok := s.TargetMap[appName]; ok { + namespaces[app.Namespace] = struct{}{} + continue + } + if _, ok := groupMap[app.Group]; ok { + s.TargetMap[appName] = true + namespaces[app.Namespace] = struct{}{} } else { - namespaces[app.Namespace] = true + app.Disable() } } + for nsName, ns := range s.Namespaces { - if use, ok := namespaces[nsName]; !use || !ok { + if _, ok := namespaces[nsName]; !ok { ns.Disable() } } diff --git a/internal/app/state_files.go b/internal/app/state_files.go new file mode 100644 index 00000000..9fd31de7 --- /dev/null +++ b/internal/app/state_files.go @@ -0,0 +1,225 @@ +package app + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strconv" + + "github.com/BurntSushi/toml" + "gopkg.in/yaml.v2" +) + +// invokes either yaml or toml parser considering file extension +func (s *state) fromFile(file string) (bool, string) { + if isOfType(file, []string{".toml"}) { + return s.fromTOML(file) + } else if isOfType(file, []string{".yaml", ".yml"}) { + return s.fromYAML(file) + } else { + return false, "State file does not have toml/yaml extension." + } +} + +func (s *state) toFile(file string) { + if isOfType(file, []string{".toml"}) { + s.toTOML(file) + } else if isOfType(file, []string{".yaml", ".yml"}) { + s.toYAML(file) + } else { + log.Fatal("State file does not have toml/yaml extension.") + } +} + +// fromTOML reads a toml file and decodes it to a state type. +// It uses the BurntSuchi TOML parser which throws an error if the TOML file is not valid. +func (s *state) fromTOML(file string) (bool, string) { + rawTomlFile, err := ioutil.ReadFile(file) + if err != nil { + return false, err.Error() + } + + tomlFile := string(rawTomlFile) + if !flags.noEnvSubst { + if ok, err := validateEnvVars(tomlFile, file); !ok { + return false, err + } + tomlFile = substituteEnv(tomlFile) + } + if !flags.noSSMSubst { + tomlFile = substituteSSM(tomlFile) + } + if _, err := toml.Decode(tomlFile, s); err != nil { + return false, err.Error() + } + s.expand(file) + + return true, "Parsed TOML [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps" +} + +// toTOML encodes a state type into a TOML file. +// It uses the BurntSuchi TOML parser. +func (s *state) toTOML(file string) { + log.Info("Printing generated toml ... ") + var buff bytes.Buffer + var ( + newFile *os.File + err error + ) + + if err := toml.NewEncoder(&buff).Encode(s); err != nil { + log.Fatal(err.Error()) + os.Exit(1) + } + newFile, err = os.Create(file) + if err != nil { + log.Fatal(err.Error()) + } + bytesWritten, err := newFile.Write(buff.Bytes()) + if err != nil { + log.Fatal(err.Error()) + } + log.Info(fmt.Sprintf("Wrote %d bytes.\n", bytesWritten)) + newFile.Close() +} + +// fromYAML reads a yaml file and decodes it to a state type. +// parser which throws an error if the YAML file is not valid. +func (s *state) fromYAML(file string) (bool, string) { + rawYamlFile, err := ioutil.ReadFile(file) + if err != nil { + return false, err.Error() + } + + yamlFile := string(rawYamlFile) + if !flags.noEnvSubst { + if ok, err := validateEnvVars(yamlFile, file); !ok { + return false, err + } + yamlFile = substituteEnv(yamlFile) + } + if !flags.noSSMSubst { + yamlFile = substituteSSM(yamlFile) + } + + if err = yaml.UnmarshalStrict([]byte(yamlFile), s); err != nil { + return false, err.Error() + } + s.expand(file) + + return true, "Parsed YAML [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps" +} + +// toYaml encodes a state type into a YAML file +func (s *state) toYAML(file string) { + log.Info("Printing generated yaml ... ") + var buff bytes.Buffer + var ( + newFile *os.File + err error + ) + + if err := yaml.NewEncoder(&buff).Encode(s); err != nil { + log.Fatal(err.Error()) + os.Exit(1) + } + newFile, err = os.Create(file) + if err != nil { + log.Fatal(err.Error()) + } + bytesWritten, err := newFile.Write(buff.Bytes()) + if err != nil { + log.Fatal(err.Error()) + } + log.Info(fmt.Sprintf("Wrote %d bytes.\n", bytesWritten)) + newFile.Close() +} + +// expand resolves relative paths of certs/keys/chart/value file/secret files/etc and replace them with a absolute paths +// it also loops through the values/secrets files and substitutes variables into them. +func (s *state) expand(relativeToFile string) { + dir := filepath.Dir(relativeToFile) + downloadDest, _ := filepath.Abs(createTempDir(tempFilesDir, "tmp")) + for _, r := range s.Apps { + + r.resolvePaths(dir, downloadDest) + if r.Chart != "" { + var repoOrDir = filepath.Dir(r.Chart) + _, isRepo := s.HelmRepos[repoOrDir] + isRepo = isRepo || stringInSlice(repoOrDir, s.PreconfiguredHelmRepos) + if !isRepo { + // if there is no repo for the chart, we assume it's intended to be a local path + + // support env vars in path + r.Chart = os.ExpandEnv(r.Chart) + // respect absolute paths to charts but resolve relative paths + if !filepath.IsAbs(r.Chart) { + r.Chart, _ = filepath.Abs(filepath.Join(dir, r.Chart)) + } + } + } + + for key, val := range s.Settings.GlobalHooks { + if key != "deleteOnSuccess" && key != "successTimeout" && key != "successCondition" { + hook := val.(string) + if err := isValidFile(hook, []string{".yaml", ".yml"}); err != nil { + s.Settings.GlobalHooks[key] = substituteVarsInYaml(hook) + } + } + } + + r.substituteVarsInStaticFiles() + } + // resolving paths for Bearer Token path in settings + if s.Settings.BearerTokenPath != "" { + s.Settings.BearerTokenPath, _ = resolveOnePath(s.Settings.BearerTokenPath, dir, downloadDest) + } + // resolve paths for global hooks + for key, val := range s.Settings.GlobalHooks { + if key != "deleteOnSuccess" && key != "successTimeout" && key != "successCondition" { + hook := val.(string) + if err := isValidFile(hook, []string{".yaml", ".yml", ".json"}); err != nil { + s.Settings.GlobalHooks[key], _ = resolveOnePath(hook, dir, downloadDest) + } + } + } + // resolving paths for k8s certificate files + for k := range s.Certificates { + s.Certificates[k], _ = resolveOnePath(s.Certificates[k], "", downloadDest) + } +} + +// cleanup deletes the k8s certificates and keys files +// It also deletes any Tiller TLS certs and keys +// and secret files +func (s *state) cleanup() { + log.Verbose("Cleaning up sensitive and temp files") + if _, err := os.Stat("ca.crt"); err == nil { + deleteFile("ca.crt") + } + + if _, err := os.Stat("ca.key"); err == nil { + deleteFile("ca.key") + } + + if _, err := os.Stat("client.crt"); err == nil { + deleteFile("client.crt") + } + + if _, err := os.Stat("bearer.token"); err == nil { + deleteFile("bearer.token") + } + + for _, app := range s.Apps { + if _, err := os.Stat(app.SecretsFile + ".dec"); err == nil { + deleteFile(app.SecretsFile + ".dec") + } + for _, secret := range app.SecretsFiles { + if _, err := os.Stat(secret + ".dec"); err == nil { + deleteFile(secret + ".dec") + } + } + } +} diff --git a/internal/app/state_files_test.go b/internal/app/state_files_test.go new file mode 100644 index 00000000..c6b0cd89 --- /dev/null +++ b/internal/app/state_files_test.go @@ -0,0 +1,304 @@ +package app + +import ( + "os" + "reflect" + "testing" +) + +func Test_fromTOML(t *testing.T) { + type args struct { + file string + s *state + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "test case 1 -- invalid TOML", + args: args{ + file: "../../../tests/invalid_example.toml", + s: new(state), + }, + want: false, + }, { + name: "test case 2 -- valid TOML", + args: args{ + file: "../../examples/example.toml", + s: new(state), + }, + want: true, + }, + } + os.Setenv("ORG_PATH", "sample") + os.Setenv("VALUE", "sample") + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got, _ := tt.args.s.fromTOML(tt.args.file); got != tt.want { + t.Errorf("fromToml() = %v, want %v", got, tt.want) + } + }) + } + os.Unsetenv("ORG_PATH") + os.Unsetenv("VALUE") +} +func Test_fromTOML_Expand(t *testing.T) { + type args struct { + file string + s *state + } + tests := []struct { + name string + args args + section string + field string + want string + }{ + { + name: "test case 1 -- valid TOML expand ClusterURI", + args: args{ + file: "../../examples/example.toml", + s: new(state), + }, + section: "Settings", + field: "ClusterURI", + want: "https://192.168.99.100:8443", + }, + { + name: "test case 2 -- valid TOML expand org", + args: args{ + file: "../../examples/example.toml", + s: new(state), + }, + section: "Metadata", + field: "org", + want: "example.com/sample/", + }, + } + os.Setenv("SET_URI", "https://192.168.99.100:8443") + os.Setenv("ORG_PATH", "sample") + os.Setenv("VALUE", "sample") + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err, msg := tt.args.s.fromTOML(tt.args.file) + if !err { + t.Errorf("fromToml(), got: %v", msg) + } + + tomlVal := reflect.ValueOf(tt.args.s).Elem() + tomlType := reflect.TypeOf(tt.args.s) + + if tomlType.Kind() != reflect.Struct { + + section := tomlVal.FieldByName(tt.section) + sectionType := reflect.TypeOf(section) + + if section.IsValid() && section.Kind() == reflect.Struct { + field := section.FieldByName(tt.field) + if sectionType.Kind() == reflect.String { + if field.String() != tt.want { + t.Errorf("fromToml().section.field = %v, got: %v", tt.want, field.String()) + } + } + } else if section.IsValid() && section.Kind() == reflect.Map { + found := false + value := "" + for _, key := range section.MapKeys() { + if key.String() == tt.field { + found = true + value = section.MapIndex(key).String() + } + } + if !found { + t.Errorf("fromToml().section.field = '%v' not found", tt.field) + } else if value != tt.want { + t.Errorf("fromToml().section.field = %v, got: %v", tt.want, value) + } + + } else { + t.Errorf("fromToml().section = struct, got: %v", sectionType.Kind()) + } + + } else { + t.Errorf("fromToml() = struct, got: %v", tomlType.Kind()) + } + }) + } + os.Unsetenv("ORG_PATH") + os.Unsetenv("SET_URI") + os.Unsetenv("VALUE") +} + +func Test_fromYAML(t *testing.T) { + type args struct { + file string + s *state + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "test case 1 -- invalid YAML", + args: args{ + file: "../../tests/invalid_example.yaml", + s: new(state), + }, + want: false, + }, { + name: "test case 2 -- valid TOML", + args: args{ + file: "../../examples/example.yaml", + s: new(state), + }, + want: true, + }, + } + os.Setenv("VALUE", "sample") + os.Setenv("ORG_PATH", "sample") + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got, _ := tt.args.s.fromYAML(tt.args.file); got != tt.want { + t.Errorf("fromYaml() = %v, want %v", got, tt.want) + } + }) + } + os.Unsetenv("ORG_PATH") + os.Unsetenv("VALUE") +} + +func Test_fromYAML_UnsetVars(t *testing.T) { + type args struct { + file string + s *state + } + tests := []struct { + name string + args args + targetVar string + want bool + }{ + { + name: "test case 1 -- unset ORG_PATH env var", + args: args{ + file: "../../examples/example.yaml", + s: new(state), + }, + targetVar: "ORG_PATH", + want: false, + }, + { + name: "test case 2 -- unset VALUE var", + args: args{ + file: "../../examples/example.yaml", + s: new(state), + }, + targetVar: "VALUE", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.targetVar == "ORG_PATH" { + os.Setenv("VALUE", "sample") + } else if tt.targetVar == "VALUE" { + os.Setenv("ORG_PATH", "sample") + } + if got, _ := tt.args.s.fromYAML(tt.args.file); got != tt.want { + t.Errorf("fromYaml() = %v, want %v", got, tt.want) + } + }) + os.Unsetenv("ORG_PATH") + os.Unsetenv("VALUE") + } +} + +func Test_fromYAML_Expand(t *testing.T) { + type args struct { + file string + s *state + } + tests := []struct { + name string + args args + section string + field string + want string + }{ + { + name: "test case 1 -- valid YAML expand ClusterURI", + args: args{ + file: "../../examples/example.yaml", + s: new(state), + }, + section: "Settings", + field: "ClusterURI", + want: "https://192.168.99.100:8443", + }, + { + name: "test case 2 -- valid YAML expand org", + args: args{ + file: "../../examples/example.yaml", + s: new(state), + }, + section: "Metadata", + field: "org", + want: "example.com/sample/", + }, + } + os.Setenv("SET_URI", "https://192.168.99.100:8443") + os.Setenv("ORG_PATH", "sample") + os.Setenv("VALUE", "sample") + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err, msg := tt.args.s.fromYAML(tt.args.file) + if !err { + t.Errorf("fromYaml(), got: %v", msg) + } + + yamlVal := reflect.ValueOf(tt.args.s).Elem() + yamlType := reflect.TypeOf(tt.args.s) + + if yamlType.Kind() != reflect.Struct { + + section := yamlVal.FieldByName(tt.section) + sectionType := reflect.TypeOf(section) + + if section.IsValid() && section.Kind() == reflect.Struct { + field := section.FieldByName(tt.field) + if sectionType.Kind() == reflect.String { + if field.String() != tt.want { + t.Errorf("fromYaml().section.field = %v, got: %v", tt.want, field.String()) + } + } + } else if section.IsValid() && section.Kind() == reflect.Map { + found := false + value := "" + for _, key := range section.MapKeys() { + if key.String() == tt.field { + found = true + value = section.MapIndex(key).String() + } + } + if !found { + t.Errorf("fromYaml().section.field = '%v' not found", tt.field) + } else if value != tt.want { + t.Errorf("fromYaml().section.field = %v, got: %v", tt.want, value) + } + + } else { + t.Errorf("fromYaml().section = struct, got: %v", sectionType.Kind()) + } + + } else { + t.Errorf("fromYaml() = struct, got: %v", yamlType.Kind()) + } + }) + } + os.Unsetenv("ORG_PATH") + os.Unsetenv("SET_URI") + os.Unsetenv("VALUE") +} diff --git a/internal/app/utils.go b/internal/app/utils.go index 73df47d3..c650e13c 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -17,9 +17,6 @@ import ( "strings" "time" - "gopkg.in/yaml.v2" - - "github.com/BurntSushi/toml" "github.com/Praqma/helmsman/internal/aws" "github.com/Praqma/helmsman/internal/azure" "github.com/Praqma/helmsman/internal/gcs" @@ -39,153 +36,6 @@ func printNamespacesMap(m map[string]*namespace) { } } -// fromTOML reads a toml file and decodes it to a state type. -// It uses the BurntSuchi TOML parser which throws an error if the TOML file is not valid. -func fromTOML(file string, s *state) (bool, string) { - rawTomlFile, err := ioutil.ReadFile(file) - if err != nil { - return false, err.Error() - } - - tomlFile := string(rawTomlFile) - if !flags.noEnvSubst { - if ok, err := validateEnvVars(tomlFile, file); !ok { - return false, err - } - tomlFile = substituteEnv(tomlFile) - } - if !flags.noSSMSubst { - tomlFile = substituteSSM(tomlFile) - } - if _, err := toml.Decode(tomlFile, s); err != nil { - return false, err.Error() - } - resolvePaths(file, s) - substituteVarsInStaticFiles(s) - - return true, "Parsed TOML [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps" -} - -// toTOML encodes a state type into a TOML file. -// It uses the BurntSuchi TOML parser. -func toTOML(file string, s *state) { - log.Info("Printing generated toml ... ") - var buff bytes.Buffer - var ( - newFile *os.File - err error - ) - - if err := toml.NewEncoder(&buff).Encode(s); err != nil { - log.Fatal(err.Error()) - os.Exit(1) - } - newFile, err = os.Create(file) - if err != nil { - log.Fatal(err.Error()) - } - bytesWritten, err := newFile.Write(buff.Bytes()) - if err != nil { - log.Fatal(err.Error()) - } - log.Info(fmt.Sprintf("Wrote %d bytes.\n", bytesWritten)) - newFile.Close() -} - -// fromYAML reads a yaml file and decodes it to a state type. -// parser which throws an error if the YAML file is not valid. -func fromYAML(file string, s *state) (bool, string) { - rawYamlFile, err := ioutil.ReadFile(file) - if err != nil { - return false, err.Error() - } - - yamlFile := string(rawYamlFile) - if !flags.noEnvSubst { - if ok, err := validateEnvVars(yamlFile, file); !ok { - return false, err - } - yamlFile = substituteEnv(yamlFile) - } - if !flags.noSSMSubst { - yamlFile = substituteSSM(yamlFile) - } - - if err = yaml.UnmarshalStrict([]byte(yamlFile), s); err != nil { - return false, err.Error() - } - resolvePaths(file, s) - substituteVarsInStaticFiles(s) - - return true, "Parsed YAML [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps" -} - -// toYaml encodes a state type into a YAML file -func toYAML(file string, s *state) { - log.Info("Printing generated yaml ... ") - var buff bytes.Buffer - var ( - newFile *os.File - err error - ) - - if err := yaml.NewEncoder(&buff).Encode(s); err != nil { - log.Fatal(err.Error()) - os.Exit(1) - } - newFile, err = os.Create(file) - if err != nil { - log.Fatal(err.Error()) - } - bytesWritten, err := newFile.Write(buff.Bytes()) - if err != nil { - log.Fatal(err.Error()) - } - log.Info(fmt.Sprintf("Wrote %d bytes.\n", bytesWritten)) - newFile.Close() -} - -// substituteVarsInStaticFiles loops through the values/secrets files and substitutes variables into them. -func substituteVarsInStaticFiles(s *state) { - for _, v := range s.Apps { - if v.ValuesFile != "" { - v.ValuesFile = substituteVarsInYaml(v.ValuesFile) - } - if v.SecretsFile != "" { - v.SecretsFile = substituteVarsInYaml(v.SecretsFile) - } - - if len(v.Hooks) != 0 { - for key, val := range v.Hooks { - if key != "deleteOnSuccess" && key != "successTimeout" && key != "successCondition" { - hook := val.(string) - if err := isValidFile(hook, []string{".yaml", ".yml"}); err != nil { - v.Hooks[key] = substituteVarsInYaml(hook) - } - } - } - } - - if len(s.Settings.GlobalHooks) != 0 { - for key, val := range s.Settings.GlobalHooks { - if key != "deleteOnSuccess" && key != "successTimeout" && key != "successCondition" { - hook := val.(string) - if err := isValidFile(hook, []string{".yaml", ".yml"}); err != nil { - s.Settings.GlobalHooks[key] = substituteVarsInYaml(hook) - } - } - } - } - - for i := range v.ValuesFiles { - v.ValuesFiles[i] = substituteVarsInYaml(v.ValuesFiles[i]) - } - for i := range v.SecretsFiles { - v.SecretsFiles[i] = substituteVarsInYaml(v.SecretsFiles[i]) - } - } -} - // substituteVarsInYaml substitutes variables in a Yaml file and creates a temp file with these values. // Returns the path for the temp file func substituteVarsInYaml(file string) string { @@ -226,74 +76,6 @@ func stringInSlice(a string, list []string) bool { return false } -// resolvePaths resolves relative paths of certs/keys/chart/value file/secret files/etc and replace them with a absolute paths -func resolvePaths(relativeToFile string, s *state) { - dir := filepath.Dir(relativeToFile) - downloadDest, _ := filepath.Abs(createTempDir(tempFilesDir, "tmp")) - for _, v := range s.Apps { - if v.ValuesFile != "" { - v.ValuesFile, _ = resolveOnePath(v.ValuesFile, dir, downloadDest) - } - if v.SecretsFile != "" { - v.SecretsFile, _ = resolveOnePath(v.SecretsFile, dir, downloadDest) - } - - if len(v.Hooks) != 0 { - for key, val := range v.Hooks { - if key != "deleteOnSuccess" && key != "successTimeout" && key != "successCondition" { - hook := val.(string) - if err := isValidFile(hook, []string{".yaml", ".yml", ".json"}); err != nil { - v.Hooks[key], _ = resolveOnePath(hook, dir, downloadDest) - } - } - } - } - - for i := range v.ValuesFiles { - v.ValuesFiles[i], _ = resolveOnePath(v.ValuesFiles[i], dir, downloadDest) - } - for i := range v.SecretsFiles { - v.SecretsFiles[i], _ = resolveOnePath(v.SecretsFiles[i], dir, downloadDest) - } - - if v.Chart != "" { - var repoOrDir = filepath.Dir(v.Chart) - _, isRepo := s.HelmRepos[repoOrDir] - isRepo = isRepo || stringInSlice(repoOrDir, s.PreconfiguredHelmRepos) - if !isRepo { - // if there is no repo for the chart, we assume it's intended to be a local path - - // support env vars in path - v.Chart = os.ExpandEnv(v.Chart) - // respect absolute paths to charts but resolve relative paths - if !filepath.IsAbs(v.Chart) { - v.Chart, _ = filepath.Abs(filepath.Join(dir, v.Chart)) - } - } - } - } - // resolving paths for Bearer Token path in settings - if s.Settings.BearerTokenPath != "" { - s.Settings.BearerTokenPath, _ = resolveOnePath(s.Settings.BearerTokenPath, dir, downloadDest) - } - // resolve paths for global hooks - if len(s.Settings.GlobalHooks) != 0 { - for key, val := range s.Settings.GlobalHooks { - if key != "deleteOnSuccess" && key != "successTimeout" && key != "successCondition" { - hook := val.(string) - if err := isValidFile(hook, []string{".yaml", ".yml", ".json"}); err != nil { - s.Settings.GlobalHooks[key], _ = resolveOnePath(hook, dir, downloadDest) - } - } - } - } - // resolving paths for k8s certificate files - for k := range s.Certificates { - s.Certificates[k], _ = resolveOnePath(s.Certificates[k], "", downloadDest) - } - -} - // resolveOnePath takes the input file (URL, cloud bucket, or local file relative path), // the directory containing the DSF and the temp directory where files will be fetched to // and downloads/fetches the file locally into helmsman temp directory and returns diff --git a/internal/app/utils_test.go b/internal/app/utils_test.go index d9dd4cd5..ae079c70 100644 --- a/internal/app/utils_test.go +++ b/internal/app/utils_test.go @@ -2,307 +2,9 @@ package app import ( "os" - "reflect" "testing" ) -func Test_fromTOML(t *testing.T) { - type args struct { - file string - s *state - } - tests := []struct { - name string - args args - want bool - }{ - { - name: "test case 1 -- invalid TOML", - args: args{ - file: "../../../tests/invalid_example.toml", - s: new(state), - }, - want: false, - }, { - name: "test case 2 -- valid TOML", - args: args{ - file: "../../examples/example.toml", - s: new(state), - }, - want: true, - }, - } - os.Setenv("ORG_PATH", "sample") - os.Setenv("VALUE", "sample") - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got, _ := fromTOML(tt.args.file, tt.args.s); got != tt.want { - t.Errorf("fromToml() = %v, want %v", got, tt.want) - } - }) - } - os.Unsetenv("ORG_PATH") - os.Unsetenv("VALUE") -} -func Test_fromTOML_Expand(t *testing.T) { - type args struct { - file string - s *state - } - tests := []struct { - name string - args args - section string - field string - want string - }{ - { - name: "test case 1 -- valid TOML expand ClusterURI", - args: args{ - file: "../../examples/example.toml", - s: new(state), - }, - section: "Settings", - field: "ClusterURI", - want: "https://192.168.99.100:8443", - }, - { - name: "test case 2 -- valid TOML expand org", - args: args{ - file: "../../examples/example.toml", - s: new(state), - }, - section: "Metadata", - field: "org", - want: "example.com/sample/", - }, - } - os.Setenv("SET_URI", "https://192.168.99.100:8443") - os.Setenv("ORG_PATH", "sample") - os.Setenv("VALUE", "sample") - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err, msg := fromTOML(tt.args.file, tt.args.s) - if !err { - t.Errorf("fromToml(), got: %v", msg) - } - - tomlVal := reflect.ValueOf(tt.args.s).Elem() - tomlType := reflect.TypeOf(tt.args.s) - - if tomlType.Kind() != reflect.Struct { - - section := tomlVal.FieldByName(tt.section) - sectionType := reflect.TypeOf(section) - - if section.IsValid() && section.Kind() == reflect.Struct { - field := section.FieldByName(tt.field) - if sectionType.Kind() == reflect.String { - if field.String() != tt.want { - t.Errorf("fromToml().section.field = %v, got: %v", tt.want, field.String()) - } - } - } else if section.IsValid() && section.Kind() == reflect.Map { - found := false - value := "" - for _, key := range section.MapKeys() { - if key.String() == tt.field { - found = true - value = section.MapIndex(key).String() - } - } - if !found { - t.Errorf("fromToml().section.field = '%v' not found", tt.field) - } else if value != tt.want { - t.Errorf("fromToml().section.field = %v, got: %v", tt.want, value) - } - - } else { - t.Errorf("fromToml().section = struct, got: %v", sectionType.Kind()) - } - - } else { - t.Errorf("fromToml() = struct, got: %v", tomlType.Kind()) - } - }) - } - os.Unsetenv("ORG_PATH") - os.Unsetenv("SET_URI") - os.Unsetenv("VALUE") -} - -func Test_fromYAML(t *testing.T) { - type args struct { - file string - s *state - } - tests := []struct { - name string - args args - want bool - }{ - { - name: "test case 1 -- invalid YAML", - args: args{ - file: "../../tests/invalid_example.yaml", - s: new(state), - }, - want: false, - }, { - name: "test case 2 -- valid TOML", - args: args{ - file: "../../examples/example.yaml", - s: new(state), - }, - want: true, - }, - } - os.Setenv("VALUE", "sample") - os.Setenv("ORG_PATH", "sample") - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got, _ := fromYAML(tt.args.file, tt.args.s); got != tt.want { - t.Errorf("fromYaml() = %v, want %v", got, tt.want) - } - }) - } - os.Unsetenv("ORG_PATH") - os.Unsetenv("VALUE") -} - -func Test_fromYAML_UnsetVars(t *testing.T) { - type args struct { - file string - s *state - } - tests := []struct { - name string - args args - targetVar string - want bool - }{ - { - name: "test case 1 -- unset ORG_PATH env var", - args: args{ - file: "../../examples/example.yaml", - s: new(state), - }, - targetVar: "ORG_PATH", - want: false, - }, - { - name: "test case 2 -- unset VALUE var", - args: args{ - file: "../../examples/example.yaml", - s: new(state), - }, - targetVar: "VALUE", - want: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.targetVar == "ORG_PATH" { - os.Setenv("VALUE", "sample") - } else if tt.targetVar == "VALUE" { - os.Setenv("ORG_PATH", "sample") - } - if got, _ := fromYAML(tt.args.file, tt.args.s); got != tt.want { - t.Errorf("fromYaml() = %v, want %v", got, tt.want) - } - }) - os.Unsetenv("ORG_PATH") - os.Unsetenv("VALUE") - } -} - -func Test_fromYAML_Expand(t *testing.T) { - type args struct { - file string - s *state - } - tests := []struct { - name string - args args - section string - field string - want string - }{ - { - name: "test case 1 -- valid YAML expand ClusterURI", - args: args{ - file: "../../examples/example.yaml", - s: new(state), - }, - section: "Settings", - field: "ClusterURI", - want: "https://192.168.99.100:8443", - }, - { - name: "test case 2 -- valid YAML expand org", - args: args{ - file: "../../examples/example.yaml", - s: new(state), - }, - section: "Metadata", - field: "org", - want: "example.com/sample/", - }, - } - os.Setenv("SET_URI", "https://192.168.99.100:8443") - os.Setenv("ORG_PATH", "sample") - os.Setenv("VALUE", "sample") - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err, msg := fromYAML(tt.args.file, tt.args.s) - if !err { - t.Errorf("fromYaml(), got: %v", msg) - } - - yamlVal := reflect.ValueOf(tt.args.s).Elem() - yamlType := reflect.TypeOf(tt.args.s) - - if yamlType.Kind() != reflect.Struct { - - section := yamlVal.FieldByName(tt.section) - sectionType := reflect.TypeOf(section) - - if section.IsValid() && section.Kind() == reflect.Struct { - field := section.FieldByName(tt.field) - if sectionType.Kind() == reflect.String { - if field.String() != tt.want { - t.Errorf("fromYaml().section.field = %v, got: %v", tt.want, field.String()) - } - } - } else if section.IsValid() && section.Kind() == reflect.Map { - found := false - value := "" - for _, key := range section.MapKeys() { - if key.String() == tt.field { - found = true - value = section.MapIndex(key).String() - } - } - if !found { - t.Errorf("fromYaml().section.field = '%v' not found", tt.field) - } else if value != tt.want { - t.Errorf("fromYaml().section.field = %v, got: %v", tt.want, value) - } - - } else { - t.Errorf("fromYaml().section = struct, got: %v", sectionType.Kind()) - } - - } else { - t.Errorf("fromYaml() = struct, got: %v", yamlType.Kind()) - } - }) - } - os.Unsetenv("ORG_PATH") - os.Unsetenv("SET_URI") - os.Unsetenv("VALUE") -} - func Test_isOfType(t *testing.T) { type args struct { filename string From 9aac445196d13d2220a6ce4c7883839061919e35 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Thu, 12 Nov 2020 15:22:21 +0000 Subject: [PATCH 0703/1127] feat: annotate the helm release with the result of the hooks Signed-off-by: Luis Davim --- internal/app/helm_release.go | 2 +- internal/app/hooks.go | 16 +++++ internal/app/plan.go | 29 +++++++-- internal/app/plan_test.go | 2 +- internal/app/release.go | 112 +++++++++++++++++++++++------------ internal/app/release_test.go | 22 +++---- internal/app/state.go | 2 +- 7 files changed, 126 insertions(+), 59 deletions(-) create mode 100644 internal/app/hooks.go diff --git a/internal/app/helm_release.go b/internal/app/helm_release.go index 4f570150..e2af9c1b 100644 --- a/internal/app/helm_release.go +++ b/internal/app/helm_release.go @@ -81,7 +81,7 @@ func (r *helmRelease) key() string { func (r *helmRelease) uninstall(p *plan) { cmd := helmCmd(concat([]string{"uninstall", r.Name, "--namespace", r.Namespace}, flags.getDryRunFlags()), "Delete untracked release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") - p.addCommand(cmd, -800, nil, []Command{}, []Command{}) + p.addCommand(cmd, -800, nil, []hookCmd{}, []hookCmd{}) } // getRevision returns the revision number for an existing helm release diff --git a/internal/app/hooks.go b/internal/app/hooks.go new file mode 100644 index 00000000..1e7ae730 --- /dev/null +++ b/internal/app/hooks.go @@ -0,0 +1,16 @@ +package app + +const ( + preInstall = "preInstall" + postInstall = "postInstall" + preUpgrade = "preUpgrade" + postUpgrade = "postUpgrade" + preDelete = "preDelete" + postDelete = "postDelete" + test = "test" +) + +type hookCmd struct { + Command + Type string +} diff --git a/internal/app/plan.go b/internal/app/plan.go index f5a5dde9..3ac31cd5 100644 --- a/internal/app/plan.go +++ b/internal/app/plan.go @@ -33,8 +33,8 @@ type orderedCommand struct { Command Command Priority int targetRelease *release - beforeCommands []Command - afterCommands []Command + beforeCommands []hookCmd + afterCommands []hookCmd } // plan type representing the plan of actions to make the desired state come true. @@ -57,7 +57,7 @@ func createPlan() *plan { } // addCommand adds a command type to the plan -func (p *plan) addCommand(cmd Command, priority int, r *release, beforeCommands []Command, afterCommands []Command) { +func (p *plan) addCommand(cmd Command, priority int, r *release, beforeCommands []hookCmd, afterCommands []hookCmd) { p.Lock() defer p.Unlock() oc := orderedCommand{ @@ -141,15 +141,25 @@ func releaseWithHooks(cmd orderedCommand, storageBackend string, wg *sync.WaitGr log.Verbose(err.Error()) return } + var annotations []string for _, c := range cmd.beforeCommands { - if err := execOne(c, cmd.targetRelease); err != nil { + if err := execOne(c.Command, cmd.targetRelease); err != nil { errors <- err + if c.Type != "" { + annotations = append(annotations, "helmsman/"+c.Type+"=failed") + } log.Verbose(err.Error()) return } + if c.Type != "" { + annotations = append(annotations, "helmsman/"+c.Type+"=ok") + } } if !flags.dryRun && !flags.destroy { - defer cmd.targetRelease.label(storageBackend) + defer func() { + cmd.targetRelease.mark(storageBackend) + cmd.targetRelease.annotate(storageBackend, annotations...) + }() } if err := execOne(cmd.Command, cmd.targetRelease); err != nil { errors <- err @@ -157,9 +167,16 @@ func releaseWithHooks(cmd orderedCommand, storageBackend string, wg *sync.WaitGr return } for _, c := range cmd.afterCommands { - if err := execOne(c, cmd.targetRelease); err != nil { + if err := execOne(c.Command, cmd.targetRelease); err != nil { errors <- err + if c.Type != "" { + annotations = append(annotations, "helmsman/"+c.Type+"=failed") + } log.Verbose(err.Error()) + } else { + if c.Type != "" { + annotations = append(annotations, "helmsman/"+c.Type+"=ok") + } } } } diff --git a/internal/app/plan_test.go b/internal/app/plan_test.go index 7b666e5f..89d7faa3 100644 --- a/internal/app/plan_test.go +++ b/internal/app/plan_test.go @@ -63,7 +63,7 @@ func Test_plan_addCommand(t *testing.T) { Created: tt.fields.Created, } r := &release{} - p.addCommand(tt.args.c, 0, r, []Command{}, []Command{}) + p.addCommand(tt.args.c, 0, r, []hookCmd{}, []hookCmd{}) if got := len(p.Commands); got != 1 { t.Errorf("addCommand(): got %v, want 1", got) } diff --git a/internal/app/release.go b/internal/app/release.go index 43a2d932..fcdce0cf 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -134,7 +134,7 @@ func (r *release) validate(appLabel string, seen map[string]map[string]bool, s * func validateHooks(hooks map[string]interface{}) (bool, string) { for key, value := range hooks { switch key { - case "preInstall", "postInstall", "preUpgrade", "postUpgrade", "preDelete", "postDelete": + case preInstall, postInstall, preUpgrade, postUpgrade, preDelete, postDelete: hook := value.(string) if err := isValidFile(hook, []string{".yaml", ".yml", ".json"}); err != nil { if !ToolExists(strings.Fields(hook)[0]) { @@ -151,9 +151,9 @@ func validateHooks(hooks map[string]interface{}) (bool, string) { } // testRelease creates a Helm command to test a particular release. -func (r *release) test(afterCommands *[]Command) { +func (r *release) test(afterCommands *[]hookCmd) { cmd := helmCmd(r.getHelmArgsFor("test"), "Running tests for release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") - *afterCommands = append(*afterCommands, cmd) + *afterCommands = append(*afterCommands, hookCmd{Command: cmd, Type: test}) } // installRelease creates a Helm command to install a particular release in a particular namespace using a particular Tiller. @@ -252,7 +252,7 @@ func (r *release) rollback(cs *currentState, p *plan) { if r.Namespace == rs.Namespace { cmd := helmCmd(concat([]string{"rollback", r.Name, rs.getRevision()}, r.getWait(), r.getTimeout(), r.getNoHooks(), flags.getDryRunFlags()), "Rolling back release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") - p.addCommand(cmd, r.Priority, r, []Command{}, []Command{}) + p.addCommand(cmd, r.Priority, r, []hookCmd{}, []hookCmd{}) r.upgrade(p) // this is to reflect any changes in values file(s) p.addDecision("Release [ "+r.Name+" ] was deleted and is desired to be rolled back to "+ "namespace [ "+r.Namespace+" ]", r.Priority, create) @@ -266,11 +266,39 @@ func (r *release) rollback(cs *currentState, p *plan) { } } -// label applies Helmsman specific labels to Helm's state resources (secrets/configmaps) -func (r *release) label(storageBackend string) { +// mark applies Helmsman specific labels to Helm's state resources (secrets/configmaps) +func (r *release) mark(storageBackend string) { + r.label(storageBackend, "MANAGED-BY=HELMSMAN", "NAMESPACE="+r.Namespace, "HELMSMAN_CONTEXT="+curContext) +} + +// label labels Helm's state resources (secrets/configmaps) +func (r *release) label(storageBackend string, labels ...string) { + if len(labels) == 0 { + return + } + if r.Enabled { + + args := []string{"label", "--overwrite", storageBackend, "-n", r.Namespace, "-l", "owner=helm,name=" + r.Name} + args = append(args, labels...) + cmd := kubectl(args, "Applying Helmsman labels to [ "+r.Name+" ] release") + + result := cmd.Exec() + if result.code != 0 { + log.Fatal(result.errors) + } + } +} + +// annotate annotates Helm's state resources (secrets/configmaps) +func (r *release) annotate(storageBackend string, annotations ...string) { + if len(annotations) == 0 { + return + } if r.Enabled { - cmd := kubectl([]string{"label", storageBackend, "-n", r.Namespace, "-l", "owner=helm,name=" + r.Name, "MANAGED-BY=HELMSMAN", "NAMESPACE=" + r.Namespace, "HELMSMAN_CONTEXT=" + curContext, "--overwrite"}, "Applying Helmsman labels to [ "+r.Name+" ] release") + args := []string{"annotate", "--overwrite", storageBackend, "-n", r.Namespace, "-l", "owner=helm,name=" + r.Name} + args = append(args, annotations...) + cmd := kubectl(args, "Applying Helmsman annotations to [ "+r.Name+" ] release") result := cmd.Exec() if result.code != 0 { @@ -441,62 +469,66 @@ func (r *release) inheritMaxHistory(s *state) { // checkHooks checks if a hook of certain type exists and creates its command // if success condition for the hook is defined, a "kubectl wait" command is created // returns two slices of before and after commands -func (r *release) checkHooks(action string, optionalNamespace ...string) ([]Command, []Command) { +func (r *release) checkHooks(action string, optionalNamespace ...string) ([]hookCmd, []hookCmd) { ns := r.Namespace if len(optionalNamespace) > 0 { ns = optionalNamespace[0] } var ( - beforeCmds []Command - afterCmds []Command + beforeCmds []hookCmd + afterCmds []hookCmd ) switch action { case "install": { - if _, ok := r.Hooks["preInstall"]; ok { - beforeCmds = append(beforeCmds, r.getHookCommands("preInstall", ns)...) + if _, ok := r.Hooks[preInstall]; ok { + beforeCmds = append(beforeCmds, r.getHookCommands(preInstall, ns)...) } - if _, ok := r.Hooks["postInstall"]; ok { - afterCmds = append(afterCmds, r.getHookCommands("postInstall", ns)...) + if _, ok := r.Hooks[postInstall]; ok { + afterCmds = append(afterCmds, r.getHookCommands(postInstall, ns)...) } } case "upgrade": { - if _, ok := r.Hooks["preUpgrade"]; ok { - beforeCmds = append(beforeCmds, r.getHookCommands("preUpgrade", ns)...) + if _, ok := r.Hooks[preUpgrade]; ok { + beforeCmds = append(beforeCmds, r.getHookCommands(preUpgrade, ns)...) } - if _, ok := r.Hooks["postUpgrade"]; ok { - afterCmds = append(afterCmds, r.getHookCommands("postUpgrade", ns)...) + if _, ok := r.Hooks[postUpgrade]; ok { + afterCmds = append(afterCmds, r.getHookCommands(postUpgrade, ns)...) } } case "delete": { - if _, ok := r.Hooks["preDelete"]; ok { - beforeCmds = append(beforeCmds, r.getHookCommands("preDelete", ns)...) + if _, ok := r.Hooks[preDelete]; ok { + beforeCmds = append(beforeCmds, r.getHookCommands(preDelete, ns)...) } - if _, ok := r.Hooks["postDelete"]; ok { - afterCmds = append(afterCmds, r.getHookCommands("postDelete", ns)...) + if _, ok := r.Hooks[postDelete]; ok { + afterCmds = append(afterCmds, r.getHookCommands(postDelete, ns)...) } } } return beforeCmds, afterCmds } -func (r *release) getHookCommands(hookType, ns string) []Command { - var cmds []Command +func (r *release) getHookCommands(hookType, ns string) []hookCmd { + var cmds []hookCmd if _, ok := r.Hooks[hookType]; ok { hook := r.Hooks[hookType].(string) if err := isValidFile(hook, []string{".yaml", ".yml", ".json"}); err != nil { - cmds = append(cmds, kubectl([]string{"apply", "-n", ns, "-f", hook, flags.getKubeDryRunFlag("apply")}, "Apply "+hookType+" manifest "+r.Hooks["preInstall"].(string))) + cmd := kubectl([]string{"apply", "-n", ns, "-f", hook, flags.getKubeDryRunFlag("apply")}, "Apply "+hook+" manifest "+hookType) + cmds = append(cmds, hookCmd{Command: cmd, Type: hookType}) if wait, waitCmds := r.shouldWaitForHook(hook, hookType, ns); wait { cmds = append(cmds, waitCmds...) } } else { args := strings.Fields(hook) - cmds = append(cmds, Command{ - Cmd: args[0], - Args: args[1:], - Description: hookType, + cmds = append(cmds, hookCmd{ + Command: Command{ + Cmd: args[0], + Args: args[1:], + Description: hookType + "Hook", + }, + Type: hookType, }) } } @@ -505,8 +537,8 @@ func (r *release) getHookCommands(hookType, ns string) []Command { // shouldWaitForHook checks if there is a success condition to wait for after applying a hook // returns a boolean and the wait command if applicable -func (r *release) shouldWaitForHook(hookFile string, hookType string, namespace string) (bool, []Command) { - var cmds []Command +func (r *release) shouldWaitForHook(hookFile string, hookType string, namespace string) (bool, []hookCmd) { + var cmds []hookCmd if flags.dryRun { return false, cmds } else if _, ok := r.Hooks["successCondition"]; ok { @@ -514,9 +546,11 @@ func (r *release) shouldWaitForHook(hookFile string, hookType string, namespace if _, ok := r.Hooks["successTimeout"]; ok { timeoutFlag = "--timeout=" + r.Hooks["successTimeout"].(string) } - cmds = append(cmds, kubectl([]string{"wait", "-n", namespace, "-f", hookFile, "--for=condition=" + r.Hooks["successCondition"].(string), timeoutFlag}, "Wait for "+hookType+" : "+hookFile)) + cmd := kubectl([]string{"wait", "-n", namespace, "-f", hookFile, "--for=condition=" + r.Hooks["successCondition"].(string), timeoutFlag}, "Wait for "+hookType+" : "+hookFile) + cmds = append(cmds, hookCmd{Command: cmd}) if _, ok := r.Hooks["deleteOnSuccess"]; ok && r.Hooks["deleteOnSuccess"].(bool) { - cmds = append(cmds, kubectl([]string{"delete", "-n", namespace, "-f", hookFile}, "Delete "+hookType+" : "+hookFile)) + cmd = kubectl([]string{"delete", "-n", namespace, "-f", hookFile}, "Delete "+hookType+" : "+hookFile) + cmds = append(cmds, hookCmd{Command: cmd}) } return true, cmds } @@ -542,12 +576,12 @@ func (r release) print() { fmt.Println("\tSuccessCondition: ", r.Hooks["successCondition"]) fmt.Println("\tSuccessTimeout: ", r.Hooks["successTimeout"]) fmt.Println("\tDeleteOnSuccess: ", r.Hooks["deleteOnSuccess"]) - fmt.Println("\tpreInstall: ", r.Hooks["preInstall"]) - fmt.Println("\tpostInstall: ", r.Hooks["postInstall"]) - fmt.Println("\tpreUpgrade: ", r.Hooks["preUpgrade"]) - fmt.Println("\tpostUpgrade: ", r.Hooks["postUpgrade"]) - fmt.Println("\tpreDelete: ", r.Hooks["preDelete"]) - fmt.Println("\tpostDelete: ", r.Hooks["postDelete"]) + fmt.Println("\tpreInstall: ", r.Hooks[preInstall]) + fmt.Println("\tpostInstall: ", r.Hooks[postInstall]) + fmt.Println("\tpreUpgrade: ", r.Hooks[preUpgrade]) + fmt.Println("\tpostUpgrade: ", r.Hooks[postUpgrade]) + fmt.Println("\tpreDelete: ", r.Hooks[preDelete]) + fmt.Println("\tpostDelete: ", r.Hooks[postDelete]) fmt.Println("\tno-hooks: ", r.NoHooks) fmt.Println("\ttimeout: ", r.Timeout) fmt.Println("\tvalues to override from env:") diff --git a/internal/app/release_test.go b/internal/app/release_test.go index 6037db56..a53cd13b 100644 --- a/internal/app/release_test.go +++ b/internal/app/release_test.go @@ -261,7 +261,7 @@ func Test_validateRelease(t *testing.T) { Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", - Hooks: map[string]interface{}{"preInstall": "xyz.fake"}, + Hooks: map[string]interface{}{preInstall: "xyz.fake"}, }, s: st, }, @@ -277,7 +277,7 @@ func Test_validateRelease(t *testing.T) { Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", - Hooks: map[string]interface{}{"preInstall": "../../tests/values.xml"}, + Hooks: map[string]interface{}{preInstall: "../../tests/values.xml"}, }, s: st, }, @@ -293,7 +293,7 @@ func Test_validateRelease(t *testing.T) { Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", - Hooks: map[string]interface{}{"preDelete": "../../tests/values.yaml"}, + Hooks: map[string]interface{}{preDelete: "../../tests/values.yaml"}, }, s: st, }, @@ -309,7 +309,7 @@ func Test_validateRelease(t *testing.T) { Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", - Hooks: map[string]interface{}{"postUpgrade": "https://raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml"}, + Hooks: map[string]interface{}{postUpgrade: "https://raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml"}, }, s: st, }, @@ -325,7 +325,7 @@ func Test_validateRelease(t *testing.T) { Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", - Hooks: map[string]interface{}{"preDelete": "https//raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml"}, + Hooks: map[string]interface{}{preDelete: "https//raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml"}, }, s: st, }, @@ -407,7 +407,7 @@ func Test_validateRelease(t *testing.T) { Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", - Hooks: map[string]interface{}{"preDelete": "../../tests/post-renderer.sh"}, + Hooks: map[string]interface{}{preDelete: "../../tests/post-renderer.sh"}, }, s: st, }, @@ -434,8 +434,8 @@ func Test_inheritHooks(t *testing.T) { Certificates: make(map[string]string), Settings: config{ GlobalHooks: map[string]interface{}{ - "preInstall": "https://raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml", - "postInstall": "https://raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml", + preInstall: "https://raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml", + postInstall: "https://raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml", "successCondition": "Complete", "successTimeout": "60s", }, @@ -466,8 +466,8 @@ func Test_inheritHooks(t *testing.T) { Version: "1.0", ValuesFile: "../../tests/values.yaml", Hooks: map[string]interface{}{ - "postInstall": "../../tests/values.yaml", - "preDelete": "../../tests/values.yaml", + postInstall: "../../tests/values.yaml", + preDelete: "../../tests/values.yaml", "successTimeout": "360s", }, }, @@ -482,7 +482,7 @@ func Test_inheritHooks(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.args.r.inheritHooks(&tt.args.s) - got := tt.args.r.Hooks["preInstall"].(string) + " -- " + tt.args.r.Hooks["postInstall"].(string) + " -- " + tt.args.r.Hooks["preDelete"].(string) + + got := tt.args.r.Hooks[preInstall].(string) + " -- " + tt.args.r.Hooks[postInstall].(string) + " -- " + tt.args.r.Hooks[preDelete].(string) + " -- " + tt.args.r.Hooks["successCondition"].(string) + " -- " + tt.args.r.Hooks["successTimeout"].(string) if got != tt.want { t.Errorf("inheritHooks() got = %v, want %v", got, tt.want) diff --git a/internal/app/state.go b/internal/app/state.go index 38d4d228..fe0ffe0f 100644 --- a/internal/app/state.go +++ b/internal/app/state.go @@ -306,7 +306,7 @@ func (s *state) updateContextLabels() { for _, r := range s.Apps { if r.isConsideredToRun() { log.Info("Updating context and reapplying Helmsman labels for release [ " + r.Name + " ]") - r.label(s.Settings.StorageBackend) + r.mark(s.Settings.StorageBackend) } else { log.Warning(r.Name + " is not in the target group and therefore context and labels are not changed.") } From 51dc5b65b7fea7869e7e1c2d0dc102588c76bd9d Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Fri, 13 Nov 2020 11:24:30 +0000 Subject: [PATCH 0704/1127] chore: lint markdown docs --- README.md | 8 +- docs/desired_state_specification.md | 30 ++++--- docs/how_to/README.md | 82 +++++++++---------- docs/how_to/apps/basic.md | 12 ++- docs/how_to/apps/lifecycle_hooks.md | 11 +-- docs/how_to/apps/moving_across_namespaces.md | 74 +++++++---------- docs/how_to/apps/multiple_values_files.md | 4 +- docs/how_to/apps/order.md | 6 +- docs/how_to/apps/override_namespaces.md | 8 +- docs/how_to/apps/secrets.md | 16 ++-- docs/how_to/deployments/inside_k8s.md | 23 +++--- docs/how_to/helm_repos/basic_auth.md | 5 +- docs/how_to/helm_repos/default.md | 23 +----- docs/how_to/helm_repos/gcs.md | 7 +- docs/how_to/helm_repos/s3.md | 4 - .../misc/limit-deployment-to-specific-apps.md | 7 +- ...it-deployment-to-specific-group-of-apps.md | 7 +- docs/how_to/misc/merge_desired_state_files.md | 21 +++-- 18 files changed, 160 insertions(+), 188 deletions(-) diff --git a/README.md b/README.md index f4afb285..8f0eb253 100644 --- a/README.md +++ b/README.md @@ -57,9 +57,9 @@ Please make sure the following are installed prior to using `helmsman` as a bina If you use private helm repos, you will need either `helm-gcs` or `helm-s3` plugin or you can use basic auth to authenticate to your repos. See the [docs](https://github.com/Praqma/helmsman/blob/master/docs/how_to/helm_repos) for details. - Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the different versions. -``` + +```sh # on Linux curl -L https://github.com/Praqma/helmsman/releases/download/v3.4.2/helmsman_3.4.2_linux_amd64.tar.gz | tar zx # on MacOS @@ -69,9 +69,11 @@ mv helmsman /usr/local/bin/helmsman ``` ## As a docker image + Check the images on [dockerhub](https://hub.docker.com/r/praqma/helmsman/tags/) ## As a package + Helmsman has been packaged in Archlinux under `helmsman-bin` for the latest binary release, and `helmsman-git` for master. # Documentation @@ -84,7 +86,6 @@ Helmsman has been packaged in Archlinux under `helmsman-bin` for the latest bina - [CMD reference](https://github.com/Praqma/helmsman/blob/master/docs/cmd_reference.md) - ## Usage Helmsman can be used in three different settings: @@ -93,7 +94,6 @@ Helmsman can be used in three different settings: - [As a docker image in a CI system or local machine](https://github.com/Praqma/helmsman/blob/master/docs/how_to/deployments/ci.md) Always use a tagged docker image from [dockerhub](https://hub.docker.com/r/praqma/helmsman/) as the `latest` image can (at times) be unstable. - [As a docker image inside a k8s cluster](https://github.com/Praqma/helmsman/blob/master/docs/how_to/deployments/inside_k8s.md) - # Contributing Pull requests, feedback/feature requests are welcome. Please check our [contribution guide](CONTRIBUTION.md). diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index f4992652..6330707d 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -14,7 +14,6 @@ This document describes the specification for how to write your Helm charts' des - [Helm Repos](#helm-repos) [Optional] -- defines the repos where you want to get Helm charts from. - [Apps](#apps) -- defines the applications/charts you want to manage in your cluster. - > You can use environment variables in the desired state files. The environment variable name should start with "$", or encapsulated in "${", "}". "$" characters can be escaped like "$$". > Starting from v1.9.0, you can also use environment variables in your helm values/secrets files. @@ -26,6 +25,7 @@ Optional : Yes. Synopsis: Metadata is used for the human reader of the desired state file. While it is optional, we recommend having a maintainer and scope/cluster metadata. Options: + - you can define any key/value pairs. Example: @@ -49,11 +49,11 @@ Optional : Yes, only needed if you want Helmsman to connect kubectl to your clus Synopsis: defines where to find the certificates needed for connecting kubectl to a k8s cluster. If connection settings (username/password/clusterAPI) are provided in the Settings section below, then you need **AT LEAST** to provide caCrt and caKey. You can optionally provide a client certificate (caClient) depending on your cluster connection setup. Options: + - **caCrt** : a valid URL/S3/GCS/Azure bucket or local relative file path to a certificate file. - **caKey** : a valid URL/S3/GCS/Azure bucket or local relative file path to a client key file. - **caClient**: a valid URL/S3/GCS/Azure bucket or local relative file path to a client certificate file. - > bucket format is: [s3 or gs or az]://bucket-name/dir1/dir2/.../file.extension Example: @@ -75,6 +75,7 @@ certificates: caClient: "../path/to/my/local/client-certificate.crt" #caClient: "$CA_CLIENT" ``` + ## Context Optional : Yes. @@ -97,6 +98,7 @@ Synopsis: provides settings for connecting to your k8s cluster. > If you don't provide the `settings` stanza, helmsman would use your current kube context. Options: + - **kubeContext** : the kube context you want Helmsman to use or create. Helmsman will try connect to this context first, if it does not exist, it will try to create it (i.e. connect to a k8s cluster) using the options below. The following options can be skipped if your kubectl context is already created and you don't want Helmsman to connect kubectl to your cluster for you. @@ -115,7 +117,6 @@ The following options can be skipped if your kubectl context is already created - **globalHooks** : defines global lifecycle hooks to apply yaml manifest before and/or after different helmsman operations. Check [here](how_to/apps/lifecycle_hooks.md) for more details. - **globalMaxHistory** : defines the **global** maximum number of helm revisions state (secrets/configmap) to keep. Releases can override this global value by setting `maxHistory`. If both are not set or are set to `0`, it is defaulted to 10. - Example: ```toml @@ -134,8 +135,8 @@ kubeContext = "minikube" # [settings.globalHooks] # successCondition= "Complete" # deleteOnSuccess= true -# postInstall= "job.yaml" -globalMaxHistory= 10 +# postInstall= "job.yaml" +globalMaxHistory= 10 ``` ```yaml @@ -154,8 +155,8 @@ settings: # globalHooks: # successCondition: "Complete" # deleteOnSuccess: true - # preInstall: "job.yaml" - globalMaxHistory: 10 + # preInstall: "job.yaml" + globalMaxHistory: 10 ``` ## Namespaces @@ -166,7 +167,9 @@ Synopsis: defines the namespaces to be used/created in your k8s cluster and whet If a namespace does not already exist, Helmsman will create it. Options: + - **protected** : defines if a namespace is protected (true or false). Default false. + > For the definition of what a protected namespace means, check the [protection guide](how_to/misc/protect_namespaces_and_releases.md) - **labels** : defines labels to be added to the namespace, doesn't remove existing labels but updates them if the label key exists with any other different value. You can define any key/value pairs. Default is empty. @@ -175,7 +178,6 @@ Options: - **limits** : defines a [LimitRange](https://kubernetes.io/docs/tasks/administer-cluster/manage-resources/memory-default-namespace/) to be configured on the namespace - Example: ```toml @@ -238,14 +240,16 @@ Synopsis: defines the Helm repos where your charts can be found. You can add as > As of version v1.8.0, you can use private repos with basic auth and you can use pre-configured helm repos. Authenticating to private cloud helm repos: + - **For S3 repos**: you need to have valid AWS access keys in your environment variables. See [here](https://github.com/hypnoglow/helm-s3#note-on-aws-authentication) for more details. - **For GCS repos**: check [here](https://www.terraform.io/docs/providers/google/index.html#authentication-json-file) for getting the required authentication file. Once you have the file, you have two options, either: - - set `GOOGLE\_APPLICATION\_CREDENTIALS` environment variable to contain the absolute path to your Google cloud credentials.json file. - - Or, set `GCLOUD\_CREDENTIALS` environment variable to contain the content of the credentials.json file. + - set `GOOGLE\_APPLICATION\_CREDENTIALS` environment variable to contain the absolute path to your Google cloud credentials.json file. + - Or, set `GCLOUD\_CREDENTIALS` environment variable to contain the content of the credentials.json file. > You can also provide basic auth to access private repos that support basic auth. See the example below. Options: + - you can define any key/value pair where the key is the repo name and value is a valid URI for the repo. Basic auth info can be added in the repo URL as in the example below. Example: @@ -350,12 +354,14 @@ Releases must have unique names which are defined under `apps`. Example: in `[ap Options: **Required** + - **namespace** : the namespace where the release should be deployed. The namespace should map to one of the ones defined in [namespaces](#namespaces). - **enabled** : describes the required state of the release (true for enabled, false for disabled). Once a release is deployed, you can change it to false if you want to delete this release [default is false]. - **chart** : the chart name. It should contain the repo name as well. Example: repoName/chartName. Changing the chart name means delete and reinstall this release using the new Chart. - **version** : the chart version. **Optional** + - **group** : group name this apps belongs to. It has no effect until Helmsman's flag `-group` is passed. Check this [doc](how_to/misc/limit-deployment-to-specific-group-of-apps.md) for more details. - **description** : a release metadata for human readers. - **valuesFile** : a valid path (URL, cloud bucket, local absolute/relative file path) to custom Helm values.yaml file. File extension must be `yaml`. Cannot be used with valuesFiles together. Leaving it empty uses the default chart values. @@ -415,7 +421,7 @@ Example: successTimeout= "90s" deleteOnSuccess= true postInstall="job.yaml" - preInstall="https://github.com/jetstack/cert-manager/releases/download/v0.14.0/cert-manager.crds.yaml" + preInstall="https://github.com/jetstack/cert-manager/releases/download/v0.14.0/cert-manager.crds.yaml" ``` ```yaml @@ -448,5 +454,5 @@ apps: successTimeout: "90s" deleteOnSuccess: true postInstall: "job.yaml" - preInstall: "https://github.com/jetstack/cert-manager/releases/download/v0.14.0/cert-manager.crds.yaml" + preInstall: "https://github.com/jetstack/cert-manager/releases/download/v0.14.0/cert-manager.crds.yaml" ``` diff --git a/docs/how_to/README.md b/docs/how_to/README.md index 20331880..0f8698d1 100644 --- a/docs/how_to/README.md +++ b/docs/how_to/README.md @@ -8,49 +8,49 @@ It is recommended that you also check the [DSF spec](../desired_state_specificat - [Migrating from Helm 2 (Helmsman v1.x) to Helm 3 (Helmsman v3.x)](misc/migrate_to_3.md) - Connecting to Kubernetes clusters - - [Using an existing kube context](settings/existing_kube_context.md) - - [Using the current kube context](settings/current_kube_context.md) - - [Connecting with certificates](settings/creating_kube_context_with_certs.md) - - [Connecting with bearer token](settings/creating_kube_context_with_token.md) + - [Using an existing kube context](settings/existing_kube_context.md) + - [Using the current kube context](settings/current_kube_context.md) + - [Connecting with certificates](settings/creating_kube_context_with_certs.md) + - [Connecting with bearer token](settings/creating_kube_context_with_token.md) - Defining Namespaces - - [Create namespaces](namespaces/create.md) - - [Label namespaces](namespaces/labels_and_annotations.md) - - [Set resource limits for namespaces](namespaces/limits.md) - - [Protecting namespaces](namespaces/protection.md) - - [Namespace resource quotas](namespaces/quotas.md) + - [Create namespaces](namespaces/create.md) + - [Label namespaces](namespaces/labels_and_annotations.md) + - [Set resource limits for namespaces](namespaces/limits.md) + - [Protecting namespaces](namespaces/protection.md) + - [Namespace resource quotas](namespaces/quotas.md) - Defining Helm repositories - - [Using default helm repos](helm_repos/default.md) - - [Using private repos in Google GCS](helm_repos/gcs.md) - - [Using private repos in AWS S3](helm_repos/s3.md) - - [Using private repos with basic auth](helm_repos/basic_auth.md) - - [Using pre-configured repos](helm_repos/pre_configured.md) - - [Using local charts](helm_repos/local.md) + - [Using default helm repos](helm_repos/default.md) + - [Using private repos in Google GCS](helm_repos/gcs.md) + - [Using private repos in AWS S3](helm_repos/s3.md) + - [Using private repos with basic auth](helm_repos/basic_auth.md) + - [Using pre-configured repos](helm_repos/pre_configured.md) + - [Using local charts](helm_repos/local.md) - Manipulating Apps - - [Basic operations](apps/basic.md) - - [Passing secrets to releases](apps/secrets.md) - - [Using environment variables in helmsman file and helm values files](apps/environment_vars.md) - - [Apply K8S manifest before/after Helmsman operations](apps/lifecycle_hooks.md) - - [Use multiple values files for apps](apps/multiple_values_files.md) - - [Protect releases (apps)](apps/protection.md) - - [Moving releases (apps) across namespaces](apps/moving_across_namespaces.md) - - [Override defined namespaces](apps/override_namespaces.md) - - [Run helm tests for deployed releases (apps)](apps/helm_tests.md) - - [Define the order of apps operations](apps/order.md) - - [Delete all releases (apps)](apps/destroy.md) - - [Distinguish releases deployed from different DSF files using Helmsman's contexts](misc/merge_desired_state_files.md#distinguishing-releases-deployed-from-different-desired-state-files) - - [Migrating releases from Helmsman context to another](apps/migrate_contexts.md) - - [Rename Helmsman's contexts](apps/migrate_contexts.md) - - [Speed up Helmsman execution by skipping context fetching](apps/override_context_from_cmd.md) - - [Override context from cmd flags](apps/override_context_from_cmd.md) + - [Basic operations](apps/basic.md) + - [Passing secrets to releases](apps/secrets.md) + - [Using environment variables in helmsman file and helm values files](apps/environment_vars.md) + - [Apply K8S manifest before/after Helmsman operations](apps/lifecycle_hooks.md) + - [Use multiple values files for apps](apps/multiple_values_files.md) + - [Protect releases (apps)](apps/protection.md) + - [Moving releases (apps) across namespaces](apps/moving_across_namespaces.md) + - [Override defined namespaces](apps/override_namespaces.md) + - [Run helm tests for deployed releases (apps)](apps/helm_tests.md) + - [Define the order of apps operations](apps/order.md) + - [Delete all releases (apps)](apps/destroy.md) + - [Distinguish releases deployed from different DSF files using Helmsman's contexts](misc/merge_desired_state_files.md#distinguishing-releases-deployed-from-different-desired-state-files) + - [Migrating releases from Helmsman context to another](apps/migrate_contexts.md) + - [Rename Helmsman's contexts](apps/migrate_contexts.md) + - [Speed up Helmsman execution by skipping context fetching](apps/override_context_from_cmd.md) + - [Override context from cmd flags](apps/override_context_from_cmd.md) - Running Helmsman in different environments - - [Running Helmsman in CI](deployments/ci.md) - - [Running Helmsman inside your k8s cluster](deployments/inside_k8s.md) + - [Running Helmsman in CI](deployments/ci.md) + - [Running Helmsman inside your k8s cluster](deployments/inside_k8s.md) - Misc - - [Authenticating to cloud storage providers](misc/auth_to_storage_providers.md) - - [Protecting namespaces and releases](misc/protect_namespaces_and_releases.md) - - [Send slack notifications from Helmsman](misc/send_slack_notifications_from_helmsman.md) - - [Merge multiple desired state files](misc/merge_desired_state_files.md) - - [Limit Helmsman deployment to specific apps](misc/limit-deployment-to-specific-apps.md) - - [Limit Helmsman deployment to specific group of apps](misc/limit-deployment-to-specific-group-of-apps.md) - - [Use hiera-eyaml as secrets encryption backend](settings/use-hiera-eyaml-as-secrets-encryption.md) - - [Use DRY-ed code](misc/use-dry-code.md) + - [Authenticating to cloud storage providers](misc/auth_to_storage_providers.md) + - [Protecting namespaces and releases](misc/protect_namespaces_and_releases.md) + - [Send slack notifications from Helmsman](misc/send_slack_notifications_from_helmsman.md) + - [Merge multiple desired state files](misc/merge_desired_state_files.md) + - [Limit Helmsman deployment to specific apps](misc/limit-deployment-to-specific-apps.md) + - [Limit Helmsman deployment to specific group of apps](misc/limit-deployment-to-specific-group-of-apps.md) + - [Use hiera-eyaml as secrets encryption backend](settings/use-hiera-eyaml-as-secrets-encryption.md) + - [Use DRY-ed code](misc/use-dry-code.md) diff --git a/docs/how_to/apps/basic.md b/docs/how_to/apps/basic.md index d3df002c..3e3cc3ef 100644 --- a/docs/how_to/apps/basic.md +++ b/docs/how_to/apps/basic.md @@ -2,12 +2,13 @@ version: v3.0.0-beta5 --- -# Install releases +# Basics + +## Install releases You can run helmsman with the [example.toml](https://github.com/Praqma/helmsman/blob/master/example.toml) or [example.yaml](https://github.com/Praqma/helmsman/blob/master/example.yaml) file. ```shell - $ helmsman --apply -f example.toml 2017/11/19 18:17:57 Parsed [[ example.toml ]] successfully and found [ 2 ] apps. 2017/11/19 18:17:59 WARN: I could not create namespace [staging ]. It already exists. I am skipping this. @@ -19,7 +20,6 @@ DECISION: release [ jenkins ] is not present in the current k8s context. Will in DECISION: release [ artifactory ] is not present in the current k8s context. Will install it in namespace [[ staging ]] 2017/11/19 18:18:02 INFO: attempting: -- installing release [ jenkins ] in namespace [[ staging ]] 2017/11/19 18:18:05 INFO: attempting: -- installing release [ artifactory ] in namespace [[ staging ]] - ``` ```shell @@ -29,7 +29,7 @@ artifactory 1 Sun Nov 19 18:18:06 2017 DEPLOYED artifactory-6.2.0 staging jenkins 1 Sun Nov 19 18:18:03 2017 DEPLOYED jenkins-0.9.1 staging ``` -# Delete releases +## Delete releases You can then change your desire, for example to disable the Jenkins release that was created above by setting `enabled = false` : @@ -57,7 +57,6 @@ NAME REVISION UPDATED STATUS CHART NAMESPA artifactory 2 Sun Nov 19 18:29:11 2017 DEPLOYED artifactory-6.2.0 staging ``` - ```yaml # ... apps: @@ -92,10 +91,9 @@ DECISION: release [ artifactory ] is desired to be upgraded. Planning this for y 2017/11/19 18:30:50 INFO: attempting: -- upgrading release [ artifactory ] ``` -# Upgrade releases +## Upgrade releases Every time you run Helmsman, (unless the release is [protected or deployed in a protected namespace](../misc/protect_namespaces_and_releases.md)) it will check if upgrade is necessary (using the helm-diff plugin) and only upgrade if there are changes. If you change the chart, the existing release will be deleted and a new one with the same name will be created using the new chart. - diff --git a/docs/how_to/apps/lifecycle_hooks.md b/docs/how_to/apps/lifecycle_hooks.md index 06ef239a..3bf9a3dc 100644 --- a/docs/how_to/apps/lifecycle_hooks.md +++ b/docs/how_to/apps/lifecycle_hooks.md @@ -15,7 +15,7 @@ Another useful use-case is if you are using a 3rd party chart which does not def ## Supported lifecycle stages -> hook types are case sensitive. Also, note the camleCase. +> hook types are case sensitive. Also, note the camleCase. - `preInstall` : before installing a release. - `postInstall`: after installing a release. @@ -27,8 +27,9 @@ Another useful use-case is if you are using a 3rd party chart which does not def ## Hooks stanza details The following items can be defined in the hooks stanza: + - pre/postInstall, pre/postUpgrade, pre/postDelete : a valid path (URL, cloud bucket, local file path) to your hook's k8s manifest. -- `successCondition` the Kubernetes status condition that indicates that your resources have finished their job successfully. You can find out what the status conditions are for different k8s resources with a kubectl command similar to: `kubectl get job -o=jsonpath='{range .items[*]}{.status.conditions[0].type}{"\n"}{end}'` +- `successCondition` the Kubernetes status condition that indicates that your resources have finished their job successfully. You can find out what the status conditions are for different k8s resources with a kubectl command similar to: `kubectl get job -o=jsonpath='{range .items[*]}{.status.conditions[0].type}{"\n"}{end}'` For jobs, it is `Complete` For pods, it is `Initialized` @@ -37,7 +38,7 @@ For deployments, it is `Available` - `successTimeout` (default 30s) how much time to wait for the `successCondition` - `deleteOnSuccess` (true/false) indicates if you wish to delete the hook's manifest after the hook succeeds. This is only used if you define `successCondition` -> Note: successCondition, deleteOnSuccess and successTimeout are ignored when the `--dry-run` flag is used. +> Note: successCondition, deleteOnSuccess and successTimeout are ignored when the `--dry-run` flag is used. ## Global vs App-specific hooks @@ -46,6 +47,7 @@ You can define two types of hooks in your desired state file: - **Global** hooks: are defined in the `settings` stanza and are inherited by all releases in the DSF if they haven't defined their own. These are defined as follows: + ```toml [settings] ... @@ -124,10 +126,9 @@ You can expand variables/parameters in the hook manifests at run time in one of - Pass encrypted values with [hiera-eyaml](https://github.com/Praqma/helmsman/blob/master/docs/how_to/settings/use-hiera-eyaml-as-secrets-encryption.md) - ## Limitations - You can only have one manifest file per lifecycle. - Your arbitrary tasks must run within a k8s resource (example inside a pod). You can't use a plain bash script as a hook manifest for example. -- If you have multiple k8s resources in your hook manifest file, `successCondition` may not work. +- If you have multiple k8s resources in your hook manifest file, `successCondition` may not work. - pre/postDelete hooks are not respected before/after deleting untracked releases (releases which are no longer defined in your desired state file). diff --git a/docs/how_to/apps/moving_across_namespaces.md b/docs/how_to/apps/moving_across_namespaces.md index add4e6bd..56f2fc21 100644 --- a/docs/how_to/apps/moving_across_namespaces.md +++ b/docs/how_to/apps/moving_across_namespaces.md @@ -9,15 +9,12 @@ If you have a workflow for testing a release first in the `staging` namespace th > NOTE: If your chart uses a persistent volume, then you have to read the note on PVs below first. ```toml -... - +#... [namespaces] [namespaces.staging] [namespaces.production] - [apps] - [apps.jenkins] description = "jenkins" namespace = "staging" # this is where it is deployed @@ -27,13 +24,11 @@ If you have a workflow for testing a release first in the `staging` namespace th valuesFile = "" test = true -... - +#... ``` ```yaml # ... - namespaces: staging: production: @@ -49,20 +44,17 @@ apps: test: true # ... - ``` Then if you change the namespace key for jenkins: ```toml -... - +#... [namespaces] [namespaces.staging] [namespaces.production] [apps] - [apps.jenkins] description = "jenkins" namespace = "production" # we want to move it to production @@ -72,13 +64,11 @@ Then if you change the namespace key for jenkins: valuesFile = "" test = true -... - +#... ``` ```yaml # ... - namespaces: staging: production: @@ -94,7 +84,6 @@ apps: test: true # ... - ``` Helmsman will delete the jenkins release from the `staging` namespace and install it in the `production` namespace (default in the above setup). @@ -112,50 +101,49 @@ Now, the newly created PVC (in the new namespace) will not be able to mount to t 1. You have to make sure the _Reclaim Policy_ of the old PV is set to **Retain**. In dynamic provisioned PVs, the default is Delete. To change it: -```shell -kubectl patch pv -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}' -``` + ```shell + kubectl patch pv -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}' + ``` 2. Once your old helm release is deleted, the old PVC and PV are still there. Go ahead and delete the PVC -```shell -kubectl delete pvc --namespace -``` + ```shell + kubectl delete pvc --namespace + ``` -Since, we changed the Reclaim Policy to Retain, the PV will stay around (with all your data). + Since, we changed the Reclaim Policy to Retain, the PV will stay around (with all your data). 3. The PV is now in the **Released** state but not yet available for mounting. -```shell -kubectl get pv -NAME CAPACITY ACCESSMODES RECLAIMPOLICY STATUS CLAIM STORAGECLASS REASON AGE - ... -pvc-f791ef92-01ab-11e8-8a7e-02412acf5adc 20Gi RWO Retain Released staging/myapp-persistent-storage-test-old-0 gp2 5m + ```shell + kubectl get pv + NAME CAPACITY ACCESSMODES RECLAIMPOLICY STATUS CLAIM STORAGECLASS REASON AGE + ... + pvc-f791ef92-01ab-11e8-8a7e-02412acf5adc 20Gi RWO Retain Released staging/myapp-persistent-storage-test-old-0 gp2 5m + ``` -```shell + Now, you need to make it Available, for that we need to remove the `PV.Spec.ClaimRef` from the PV spec: -Now, you need to make it Available, for that we need to remove the `PV.Spec.ClaimRef` from the PV spec: + ```shell + kubectl edit pv + # edit the file and save it + ``` -```shell -kubectl edit pv -# edit the file and save it -``` + Now, the PV should become in the **Available** state: -Now, the PV should become in the **Available** state: - -```shell -kubectl get pv -NAME CAPACITY ACCESSMODES RECLAIMPOLICY STATUS CLAIM STORAGECLASS REASON AGE -... -pvc-f791ef92-01ab-11e8-8a7e-02412acf5adc 20Gi RWO Retain Available gp2 7m - -``` + ```shell + kubectl get pv + NAME CAPACITY ACCESSMODES RECLAIMPOLICY STATUS CLAIM STORAGECLASS REASON AGE + ... + pvc-f791ef92-01ab-11e8-8a7e-02412acf5adc 20Gi RWO Retain Available gp2 7m + ``` 4. Delete the new PVC (and its mounted PV if necessary), then delete your application pod(s) in the new namespace. Assuming you have a deployment/replication controller in place, the pod will be recreated in the new namespace and this time will mount to the old volume and your data will be once again available to your application. -> NOTE: if there are multiple PVs in the Available state and they match capacity and read access for your application, then your application (in the new namespace) might mount to any of them. In this case, either ensure only the right PV is in the available state or make the PV available to a specific PVC - pre-fill `PV.Spec.ClaimRef` with a pointer to a PVC. Leave the `PV.Spec.ClaimRef,UID` empty, as the PVC does not need to exist at this point and you don't know PVC's UID. This PV can be bound only to the specified PVC + > NOTE: if there are multiple PVs in the Available state and they match capacity and read access for your application, then your application (in the new namespace) might mount to any of them. In this case, either ensure only the right PV is in the available state or make the PV available to a specific PVC - pre-fill `PV.Spec.ClaimRef` with a pointer to a PVC. Leave the `PV.Spec.ClaimRef,UID` empty, as the PVC does not need to exist at this point and you don't know PVC's UID. This PV can be bound only to the specified PVC Further details: + * https://github.com/kubernetes/kubernetes/issues/48609 * https://kubernetes.io/docs/tasks/administer-cluster/change-pv-reclaim-policy/ diff --git a/docs/how_to/apps/multiple_values_files.md b/docs/how_to/apps/multiple_values_files.md index ad3cabe1..4cfc75b1 100644 --- a/docs/how_to/apps/multiple_values_files.md +++ b/docs/how_to/apps/multiple_values_files.md @@ -35,8 +35,7 @@ You can include multiple yaml value files to separate configuration for differen "../my-jenkins-testing-values.yaml" ] -... - +#... ``` ```yaml @@ -65,5 +64,4 @@ apps: - "../my-jenkins-common-values.yaml" - "../my-jenkins-testing-values.yaml" # ... - ``` diff --git a/docs/how_to/apps/order.md b/docs/how_to/apps/order.md index 2aa25ef3..60ad5538 100644 --- a/docs/how_to/apps/order.md +++ b/docs/how_to/apps/order.md @@ -10,7 +10,7 @@ Priority is an optional flag and has a default value of 0 (zero). If set, it can If you want your apps to be deleted in the reverse order as they where created, you can also use the optional `Settings` flag `reverseDelete`, to achieve this, set it to `true` -# Example +## Example ```toml [metadata] @@ -49,7 +49,6 @@ center = https://repo.chartcenter.io version = "2.15.1" # chart version valuesFile = "" # leaving it empty uses the default chart values - [apps.jenkins2] description = "jenkins" namespace = "production" # maps to the namespace as defined in environments above @@ -71,10 +70,9 @@ center = https://repo.chartcenter.io The above example will generate the following plan: -``` +```console DECISION: release [ jenkins2 ] is not present in the current k8s context. Will install it in namespace [[ production ]] -- priority: -3 DECISION: release [ jenkins ] is not present in the current k8s context. Will install it in namespace [[ staging ]] -- priority: -2 DECISION: release [ artifactory ] is not present in the current k8s context. Will install it in namespace [[ staging ]] -- priority: -2 DECISION: release [ jenkins1 ] is not present in the current k8s context. Will install it in namespace [[ staging ]] -- priority: 0 - ``` diff --git a/docs/how_to/apps/override_namespaces.md b/docs/how_to/apps/override_namespaces.md index b2d143af..8cb57634 100644 --- a/docs/how_to/apps/override_namespaces.md +++ b/docs/how_to/apps/override_namespaces.md @@ -8,9 +8,10 @@ If you use different release branches for your releasing/managing your applicati This flag overrides all namespaces defined in your DSF with the single one you pass from command line. -# Example +## Example `dsf.toml`: + ```toml [metadata] org = "example.com" @@ -30,7 +31,6 @@ kubeContext = "minikube" jenkins = https://charts.jenkins.io center = https://repo.chartcenter.io - [apps] [apps.jenkins] @@ -51,6 +51,7 @@ center = https://repo.chartcenter.io ``` `dsf.yaml`: + ```yaml metadata: org: "example.com" @@ -70,7 +71,6 @@ helmRepos: jenkins: https://charts.jenkins.io center: https://repo.chartcenter.io - apps: jenkins: @@ -98,7 +98,7 @@ helmsman -f dsf.toml --debug --ns-override testing This will override the `staging` and `production` namespaces defined in `dsf.toml` : -``` +```console 2018/03/31 17:38:12 INFO: Plan generated at: Sat Mar 31 2018 17:37:57 DECISION: release [ jenkins ] is not present in the current k8s context. Will install it in namespace [[ testing ]] -- priority: 0 DECISION: release [ artifactory ] is not present in the current k8s context. Will install it in namespace [[ testing ]] -- priority: 0 diff --git a/docs/how_to/apps/secrets.md b/docs/how_to/apps/secrets.md index 2c36aa46..01d91d8d 100644 --- a/docs/how_to/apps/secrets.md +++ b/docs/how_to/apps/secrets.md @@ -2,7 +2,9 @@ version: v3.0.0-beta5 --- -# Passing secrets from env variables: +# Secrets + +## Passing secrets from env variables Starting from v0.1.3, Helmsman allows you to pass secrets and other user input to helm charts from environment variables as follows: @@ -22,7 +24,6 @@ Starting from v0.1.3, Helmsman allows you to pass secrets and other user input t db_username= "$JIRA_DB_USERNAME" # pass any number of key/value pairs where the key is the input expected by the helm charts and the value is an env variable name starting with $ db_password= "$JIRA_DB_PASSWORD" # ... - ``` ```yaml @@ -46,7 +47,8 @@ apps: These input variables will be passed to the chart when it is deployed/upgraded using helm's `--set <>=<>` -# Passing secrets from env files +## Passing secrets from env files + You can also keep these environment variables in files, by default Helmsman will load variables from a `.env` file but you can also specify files by using the `-e` option: ```shell @@ -61,6 +63,7 @@ SOME_VAR=someval FOO=BAR # comments at line end are OK too export BAR=BAZ ``` + Or you can do YAML(ish) style ```yaml @@ -68,10 +71,11 @@ FOO: bar BAR: baz ``` -# Passing secrets using helm secrets plugin +## Passing secrets using helm secrets plugin You can also use the [helm secrets plugin](https://github.com/futuresimple/helm-secrets) to pass your secrets. -# Passing secrets using hiera eyaml +## Passing secrets using hiera eyaml + +An alternative method is to use heira eyaml as described in [this guide](../settings/use-hiera-eyaml-as-secrets-encryption.md). -An alternative method is to use heira eyaml as described in [this guide](../settings/use-hiera-eyaml-as-secrets-encryption.md). \ No newline at end of file diff --git a/docs/how_to/deployments/inside_k8s.md b/docs/how_to/deployments/inside_k8s.md index e46cc48c..55196cbf 100644 --- a/docs/how_to/deployments/inside_k8s.md +++ b/docs/how_to/deployments/inside_k8s.md @@ -8,7 +8,6 @@ Helmsman can be deployed inside your k8s cluster and can talk to the k8s API usi See [connecting to your cluster with bearer token](../settings/creating_kube_context_with_token.md) for more details. - Your desired state will look like: ```toml @@ -31,22 +30,22 @@ To deploy Helmsman into a k8s cluster, few steps are needed: 1. Create a k8s service account -```shell -$ kubectl create sa helmsman -``` + ```shell + kubectl create sa helmsman + ``` 2. Create a clusterrolebinding -```shell -$ kubectl create clusterrolebinding helmsman-cluster-admin --clusterrole=cluster-admin --serviceaccount=default:helmsman -``` + ```shell + kubectl create clusterrolebinding helmsman-cluster-admin --clusterrole=cluster-admin --serviceaccount=default:helmsman + ``` 3. Deploy helmsman -This command gives an interactive session: + This command gives an interactive session: -```shell -$ kubectl run helmsman --restart Never --image praqma/helmsman --serviceaccount=helmsman -- helmsman -f -- sleep 3600 -``` + ```shell + kubectl run helmsman --restart Never --image praqma/helmsman --serviceaccount=helmsman -- helmsman -f -- sleep 3600 + ``` -But you can also create a proper kubernetes deployment and mount a volume to it containing your desired state file(s). + But you can also create a proper kubernetes deployment and mount a volume to it containing your desired state file(s). diff --git a/docs/how_to/helm_repos/basic_auth.md b/docs/how_to/helm_repos/basic_auth.md index 0ea2b544..a0fe4830 100644 --- a/docs/how_to/helm_repos/basic_auth.md +++ b/docs/how_to/helm_repos/basic_auth.md @@ -11,17 +11,14 @@ For such repos, you need to add the basic auth information in the repo URL as in > Be aware that some special characters in the username or password can make the URL invalid. ```toml - [helmRepos] # PASS is an env var containing the password myPrivateRepo = "https://user:$PASS@myprivaterepo.org" - ``` ```yaml - helmRepos: # PASS is an env var containing the password myPrivateRepo: "https://user:$PASS@myprivaterepo.org" +``` -``` \ No newline at end of file diff --git a/docs/how_to/helm_repos/default.md b/docs/how_to/helm_repos/default.md index 34177b62..72cdb199 100644 --- a/docs/how_to/helm_repos/default.md +++ b/docs/how_to/helm_repos/default.md @@ -7,62 +7,47 @@ version: v3.0.0-beta5 Helm v3 no longer adds the `stable` and `incubator` repos by default. Up to Helmsman v3.0.0-beta5, Helmsman adds these two repos by default. And you can disable the automatic addition of these two repos, use the `--no-default-repos` flag. Starting from `v3.0.0-beta6`, Helmsman complies with the Helm v3 behavior and DOES NOT add `stable` nor `incubator` by default. The `--no-default-repos` is also deprecated. - This example would have only the `custom` repo defined explicitly: ```toml - - [helmRepos] custom = "https://mycustomrepo.org" - ``` ```yaml - helmRepos: custom: "https://mycustomrepo.org" - - ``` This example would have `stable` defined with a custom repo: ```toml -... - +#... [helmRepos] stable = "https://mycustomstablerepo.com" -... - +#... ``` ```yaml # ... - helmRepos: stable: "https://mycustomstablerepo.com" # ... - ``` This example would have `stable` defined with a Google deprecated stable repo: ```toml -... - +#... [helmRepos] stable = "https://kubernetes-charts.storage.googleapis.com" -... - +#... ``` ```yaml # ... - helmRepos: stable: "https://kubernetes-charts.storage.googleapis.com" # ... - ``` diff --git a/docs/how_to/helm_repos/gcs.md b/docs/how_to/helm_repos/gcs.md index f4a47826..25bfc054 100644 --- a/docs/how_to/helm_repos/gcs.md +++ b/docs/how_to/helm_repos/gcs.md @@ -14,17 +14,12 @@ You need to provide one of the following env variables: Helmsman uses the [helm GCS](https://github.com/nouney/helm-gcs) plugin to work with GCS helm repos. ```toml - - [helmRepos] gcsRepo = "gs://myrepobucket/charts" - ``` ```yaml - helmRepos: gcsRepo: "gs://myrepobucket/charts" +``` - -``` \ No newline at end of file diff --git a/docs/how_to/helm_repos/s3.md b/docs/how_to/helm_repos/s3.md index 6c31423f..57772885 100644 --- a/docs/how_to/helm_repos/s3.md +++ b/docs/how_to/helm_repos/s3.md @@ -15,15 +15,11 @@ You need to provide one of the following env variables: Helmsman uses the [helm s3](https://github.com/hypnoglow/helm-s3) plugin to work with S3 helm repos. ```toml - [helmRepos] myPrivateRepo = "s3://this-is-a-private-repo/charts" - ``` ```yaml - helmRepos: myPrivateRepo: "s3://this-is-a-private-repo/charts" - ``` diff --git a/docs/how_to/misc/limit-deployment-to-specific-apps.md b/docs/how_to/misc/limit-deployment-to-specific-apps.md index 8662d827..6d1d2518 100644 --- a/docs/how_to/misc/limit-deployment-to-specific-apps.md +++ b/docs/how_to/misc/limit-deployment-to-specific-apps.md @@ -12,7 +12,8 @@ Thanks to this one can deploy specific applications among all defined for an env Having environment defined with such apps: -* example.yaml: +example.yaml: + ```yaml # ... apps: @@ -35,7 +36,7 @@ running Helmsman with `-f example.yaml` would result in checking state and invok With `--target` flag in command like ```shell -$ helmsman -f example.yaml --target artifactory ... +helmsman -f example.yaml --target artifactory ... ``` one can execute Helmsman's environment defined with example.yaml limited to only one `artifactory` app. Others are ignored until the flag is defined. @@ -43,5 +44,5 @@ one can execute Helmsman's environment defined with example.yaml limited to only Multiple applications can be set with `--target`, like ```shell -$ helmsman -f example.yaml --target artifactory --target jenkins ... +helmsman -f example.yaml --target artifactory --target jenkins ... ``` diff --git a/docs/how_to/misc/limit-deployment-to-specific-group-of-apps.md b/docs/how_to/misc/limit-deployment-to-specific-group-of-apps.md index 5bb48942..187372b4 100644 --- a/docs/how_to/misc/limit-deployment-to-specific-group-of-apps.md +++ b/docs/how_to/misc/limit-deployment-to-specific-group-of-apps.md @@ -12,7 +12,8 @@ Thanks to this one can deploy specific applications among all defined for an env Having environment defined with such apps: -* example.yaml: +example.yaml: + ```yaml # ... apps: @@ -37,7 +38,7 @@ running Helmsman with `-f example.yaml` would result in checking state and invok With `--group` flag in command like ```shell -$ helmsman -f example.yaml --group critical ... +helmsman -f example.yaml --group critical ... ``` one can execute Helmsman's environment defined with example.yaml limited to only one `jenkins` app, since its group is `critical`. @@ -46,5 +47,5 @@ Others are ignored until the flag is defined. Multiple applications can be set with `--group`, like ```shell -$ helmsman -f example.yaml --group critical --group sidecar ... +helmsman -f example.yaml --group critical --group sidecar ... ``` diff --git a/docs/how_to/misc/merge_desired_state_files.md b/docs/how_to/misc/merge_desired_state_files.md index c12f9cf5..b94f0701 100644 --- a/docs/how_to/misc/merge_desired_state_files.md +++ b/docs/how_to/misc/merge_desired_state_files.md @@ -11,7 +11,8 @@ to do the merging, and is subject to the limitations described there. For example: -* `common.toml`: +`common.toml`: + ```toml [metadata] org = "Organization Name" @@ -24,7 +25,8 @@ storageBackend = "secret" ... ``` -* `nonprod.toml`: +`nonprod.toml`: + ```toml [settings] kubeContext = "cluster-nonprod" @@ -39,8 +41,9 @@ kubeContext = "cluster-nonprod" ``` One can then run the following to use the merged config of the above files, with later files override values of earlier ones: + ```shell -$ helmsman -f common.toml -f nonprod.toml ... +helmsman -f common.toml -f nonprod.toml ... ``` ## Distinguishing releases deployed from different Desired State Files @@ -51,7 +54,8 @@ Starting from Helmsman v3.0.0-beta5, `context` is introduced to define the conte Here is how it is used: -* `infra.yaml`: +`infra.yaml`: + ```yaml context: infra-apps settings: @@ -75,7 +79,8 @@ apps: ... ``` -* `prod.yaml`: +`prod.yaml`: + ```yaml context: prod-apps settings: @@ -98,8 +103,8 @@ apps: ### Limitations -- If no context is provided in DSF (or merged DSFs), `default` is applied as a default context. This means any set of DSFs that don't define custom contexts can still operate on each other's releases (same behavior as in Helmsman 1.x). +* If no context is provided in DSF (or merged DSFs), `default` is applied as a default context. This means any set of DSFs that don't define custom contexts can still operate on each other's releases (same behavior as in Helmsman 1.x). -- When merging multiple DSFs, context from the firs DSF in the list gets overridden by the context in the last DSF. +* When merging multiple DSFs, context from the firs DSF in the list gets overridden by the context in the last DSF. -- If multiple DSFs use the same context name, they will mess up each other's releases. You can use `--keep-untracked-releases` to avoid that. However, it is recommended to avoid having multiple DSFs using the same context name. +* If multiple DSFs use the same context name, they will mess up each other's releases. You can use `--keep-untracked-releases` to avoid that. However, it is recommended to avoid having multiple DSFs using the same context name. From a57b4c9d2db6cf517b8ddb0ca4142d5660ff4468 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Fri, 13 Nov 2020 12:00:22 +0000 Subject: [PATCH 0705/1127] docs: updated the lifecycle hooks documentation to reflect the code changes --- docs/how_to/apps/lifecycle_hooks.md | 50 ++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/docs/how_to/apps/lifecycle_hooks.md b/docs/how_to/apps/lifecycle_hooks.md index 3bf9a3dc..576f6491 100644 --- a/docs/how_to/apps/lifecycle_hooks.md +++ b/docs/how_to/apps/lifecycle_hooks.md @@ -1,5 +1,5 @@ --- -version: v3.4.0 +version: v3.5.2 --- # Helmsman Lifecycle hooks @@ -10,8 +10,11 @@ Another useful use-case is if you are using a 3rd party chart which does not def ## Prerequisites -- Hook operations must be defined in a Kubernetes manifest. They can be any kubernetes resource(s) (jobs, cron jobs, deployments, pods, etc). -- You can only define one manifest file for each lifecycle hook. So make sure all your needed resources are in this manifest. +- Hook operations can be defined in a Kubernetes manifest. They can be any kubernetes resource(s) (jobs, cron jobs, deployments, pods, etc). + - You can only define one manifest file for each lifecycle hook. So make sure all your needed resources are in this manifest. +- Hook operations can also be a script or a command. +- Script or manifest paths must be either absolute or relative to the DSF. +- Hook k8s manifests can also be defined as an URL. ## Supported lifecycle stages @@ -28,15 +31,27 @@ Another useful use-case is if you are using a 3rd party chart which does not def The following items can be defined in the hooks stanza: -- pre/postInstall, pre/postUpgrade, pre/postDelete : a valid path (URL, cloud bucket, local file path) to your hook's k8s manifest. -- `successCondition` the Kubernetes status condition that indicates that your resources have finished their job successfully. You can find out what the status conditions are for different k8s resources with a kubectl command similar to: `kubectl get job -o=jsonpath='{range .items[*]}{.status.conditions[0].type}{"\n"}{end}'` +**pre/postInstall, pre/postUpgrade, pre/postDelete**: -For jobs, it is `Complete` -For pods, it is `Initialized` -For deployments, it is `Available` +A valid path (URL, cloud bucket, local file path) to your hook's k8s manifest or a valid path to a script or a shell command. -- `successTimeout` (default 30s) how much time to wait for the `successCondition` -- `deleteOnSuccess` (true/false) indicates if you wish to delete the hook's manifest after the hook succeeds. This is only used if you define `successCondition` +The following options only apply to kubernetes manifest type of hooks. + +**successCondition**: + +The Kubernetes status condition that indicates that your resources have finished their job successfully. You can find out what the status conditions are for different k8s resources with a kubectl command similar to: `kubectl get job -o=jsonpath='{range .items[*]}{.status.conditions[0].type}{"\n"}{end}'` + +- For jobs, it is `Complete` +- For pods, it is `Initialized` +- For deployments, it is `Available` + +**successTimeout**: (default 30s) + +How much time to wait for the `successCondition` + +**deleteOnSuccess**: (true/false) + +Indicates if you wish to delete the hook's manifest after the hook succeeds. This is only used if you define `successCondition` > Note: successCondition, deleteOnSuccess and successTimeout are ignored when the `--dry-run` flag is used. @@ -44,13 +59,15 @@ For deployments, it is `Available` You can define two types of hooks in your desired state file: -- **Global** hooks: are defined in the `settings` stanza and are inherited by all releases in the DSF if they haven't defined their own. +**Global** hooks: + +Are defined in the `settings` stanza and are inherited by all releases in the DSF if they haven't defined their own. These are defined as follows: ```toml [settings] - ... + #... [settings.globalHooks] successCondition= "Initialized" deleteOnSuccess= true @@ -59,15 +76,17 @@ These are defined as follows: ```yaml settings: - ... + #... globalHooks: successCondition: "Initialized" deleteOnSuccess: true postInstall: "job.yaml" - ... + #... ``` -- **App-specific** hooks: each app (release) can define its own hooks which **override any global ones**. +**App-specific** hooks: + +Each app (release) can define its own hooks which **override any global ones**. These are defined as follows: @@ -129,6 +148,5 @@ You can expand variables/parameters in the hook manifests at run time in one of ## Limitations - You can only have one manifest file per lifecycle. -- Your arbitrary tasks must run within a k8s resource (example inside a pod). You can't use a plain bash script as a hook manifest for example. - If you have multiple k8s resources in your hook manifest file, `successCondition` may not work. - pre/postDelete hooks are not respected before/after deleting untracked releases (releases which are no longer defined in your desired state file). From be31748e23114d63bfa3a7700f3063814bb28c6c Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Fri, 13 Nov 2020 13:02:46 +0000 Subject: [PATCH 0706/1127] fix: overlocking --- internal/app/decision_maker.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index b3e0af5d..c829e164 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -34,9 +34,7 @@ func buildState(s *state) *currentState { sem <- struct{}{} wg.Add(1) go func(r helmRelease) { - cs.Lock() defer func() { - cs.Unlock() wg.Done() // release <-sem @@ -47,7 +45,9 @@ func buildState(s *state) *currentState { r.HelmsmanContext = flags.contextOverride log.Info("Overwrote Helmsman context for release [ " + r.Name + " ] to " + flags.contextOverride) } + cs.Lock() cs.releases[r.key()] = r + cs.Unlock() }(r) } wg.Wait() From 3565c31ff22dcfe0ef5c471b434a2c34995fb254 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Fri, 13 Nov 2020 13:11:47 +0000 Subject: [PATCH 0707/1127] chore: lint circleci config file --- .circleci/config.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 9d23f4eb..ba289197 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -16,10 +16,10 @@ jobs: docker: - image: goreleaser/goreleaser entrypoint: /bin/bash - working_directory: "/go/src/github.com/Praqma/helmsman" + working_directory: "/go/src/github.com/Praqma/helmsman" steps: - run: - name: install git + name: install git command: apk update && apk add --no-cache git openssh - checkout - run: @@ -76,4 +76,4 @@ workflows: branches: ignore: /.*/ tags: - only: /v[0-9]+(\.[0-9]+)*(-.*)*/ + only: /v[0-9]+(\.[0-9]+)*(-.*)*/ From a947f12ac5e02742b2b73a982ad0dfe0cc8cf88f Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Fri, 13 Nov 2020 19:20:39 +0000 Subject: [PATCH 0708/1127] refactor: address PR comments Signed-off-by: Luis Davim --- internal/app/cli.go | 2 +- internal/app/decision_maker.go | 28 ++++++++------ internal/app/decision_maker_test.go | 6 +-- internal/app/helm_helpers.go | 12 +++--- internal/app/hooks.go | 36 +++++++++++++++++ internal/app/kube_helpers.go | 18 +++------ internal/app/plan.go | 16 ++++---- internal/app/release.go | 60 +++++++++-------------------- internal/app/release_test.go | 16 ++++---- internal/app/state.go | 20 ++-------- internal/app/state_files.go | 8 ++-- internal/app/utils.go | 41 ++++++++++++-------- 12 files changed, 137 insertions(+), 126 deletions(-) diff --git a/internal/app/cli.go b/internal/app/cli.go index 6b20febc..2d12902c 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -243,7 +243,7 @@ func (c *cli) readState(s *state) { } s.setDefaults() - s.disableUntargettedApps(c.group, c.target) + s.disableUntargetedApps(c.group, c.target) if len(c.target) > 0 && len(s.TargetMap) == 0 { log.Info("No apps defined with -target flag were found, exiting") diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index c829e164..b8d642e6 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -1,7 +1,6 @@ package app import ( - "os" "regexp" "strings" "sync" @@ -55,6 +54,7 @@ func buildState(s *state) *currentState { } // makePlan creates a plan of the actions needed to make the desired state come true. +// TODO: this code needs to be simplified func (cs *currentState) makePlan(s *state) *plan { p := createPlan() p.StorageBackend = s.Settings.StorageBackend @@ -76,8 +76,9 @@ func (cs *currentState) makePlan(s *state) *plan { // Ideally we'd have a pipeline of helm command tasks with several stages that can all come home if one of them fails. // Is it better to fail early here? I am not sure. - // Initialize the rejigged data structures + // Unique chart names and versions in the DSF charts := make(map[string]map[string]bool) + // Initialize the rejigged data structures for _, r := range s.Apps { if charts[r.Chart] == nil { charts[r.Chart] = make(map[string]bool) @@ -123,7 +124,11 @@ func (cs *currentState) makePlan(s *state) *plan { // even though I wrote it, lines like this are a code smell to me -- we need to rethink the concurrency as i mentioned above. // ideally you just process all helm commands "in a background pool", they return channels, // and we would be looping over select statements until we had the results of the helm commands we wanted. - n, m := getChartVersion(chart, version) + n, e := getChartVersion(chart, version) + m := "" + if e != nil { + m = e.Error() + } versionsC <- [4]string{chart, version, n, m} }(chart, version) } @@ -169,7 +174,7 @@ func (cs *currentState) makePlan(s *state) *plan { wg.Done() <-sem }() - cs.decide(r, s, p, chartName, chartVersion) + cs.decide(r, s.Namespaces[r.Namespace], p, chartName, chartVersion) }(r, extractedChartNames[r.Chart], extractedChartVersions[r.Chart][r.Version]) } wg.Wait() @@ -179,7 +184,7 @@ func (cs *currentState) makePlan(s *state) *plan { // decide makes a decision about what commands (actions) need to be executed // to make a release section of the desired state come true. -func (cs *currentState) decide(r *release, s *state, p *plan, chartName, chartVersion string) { +func (cs *currentState) decide(r *release, n *namespace, p *plan, chartName, chartVersion string) { // check for presence in defined targets or groups if !r.isConsideredToRun() { p.addDecision("Release [ "+r.Name+" ] ignored", r.Priority, ignored) @@ -196,7 +201,7 @@ func (cs *currentState) decide(r *release, s *state, p *plan, chartName, chartVe if !r.Enabled { if ok := cs.releaseExists(r, ""); ok { - if r.isProtected(cs, s) { + if r.isProtected(cs, n) { p.addDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ "protection is removed.", r.Priority, noop) return @@ -210,21 +215,21 @@ func (cs *currentState) decide(r *release, s *state, p *plan, chartName, chartVe } if ok := cs.releaseExists(r, helmStatusDeployed); ok { - if !r.isProtected(cs, s) { + if !r.isProtected(cs, n) { cs.inspectUpgradeScenario(r, p, chartName, chartVersion) // upgrade or move } else { p.addDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ "you remove its protection.", r.Priority, noop) } } else if ok := cs.releaseExists(r, helmStatusUninstalled); ok { - if !r.isProtected(cs, s) { + if !r.isProtected(cs, n) { r.rollback(cs, p) // rollback } else { p.addDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ "you remove its protection.", r.Priority, noop) } } else if ok := cs.releaseExists(r, helmStatusFailed); ok { - if !r.isProtected(cs, s) { + if !r.isProtected(cs, n) { p.addDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is in FAILED state. Upgrade is scheduled!", r.Priority, change) r.upgrade(p) } else { @@ -232,10 +237,9 @@ func (cs *currentState) decide(r *release, s *state, p *plan, chartName, chartVe "you remove its protection.", r.Priority, noop) } } else if cs.releaseExists(r, helmStatusPendingInstall) || cs.releaseExists(r, helmStatusPendingUpgrade) || cs.releaseExists(r, helmStatusPendingRollback) || cs.releaseExists(r, helmStatusUninstalling) { - log.Error("Release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ] is in a pending (install/upgrade/rollback or uninstalling) state. " + + log.Fatal("Release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ] is in a pending (install/upgrade/rollback or uninstalling) state. " + "This means application is being operated on outside of this Helmsman invocation's scope." + "Exiting, as this may cause issues when continuing...") - os.Exit(1) } else { // If there is no release in the cluster with this name and in this namespace, then install it! if _, ok := cs.releases[r.key()]; !ok { @@ -244,7 +248,7 @@ func (cs *currentState) decide(r *release, s *state, p *plan, chartName, chartVe } else { // A release with the same name and in the same namespace exists, but it has a different context label (managed by another DSF) log.Fatal("Release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ] already exists but is not managed by the" + - " current context: [ " + s.Context + " ]. Applying changes will likely cause conflicts. Change the release name or namespace.") + " current context. Applying changes will likely cause conflicts. Change the release name or namespace.") } } } diff --git a/internal/app/decision_maker_test.go b/internal/app/decision_maker_test.go index 1e2b68a9..243da8db 100644 --- a/internal/app/decision_maker_test.go +++ b/internal/app/decision_maker_test.go @@ -226,10 +226,10 @@ func Test_decide(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { cs := newCurrentState() - tt.args.s.disableUntargettedApps([]string{}, tt.targetFlag) + tt.args.s.disableUntargetedApps([]string{}, tt.targetFlag) outcome := plan{} // Act - cs.decide(tt.args.s.Apps[tt.args.r], tt.args.s, &outcome, "", "") + cs.decide(tt.args.s.Apps[tt.args.r], tt.args.s.Namespaces[tt.args.s.Apps[tt.args.r].Namespace], &outcome, "", "") got := outcome.Decisions[0].Type t.Log(outcome.Decisions[0].Description) @@ -303,7 +303,7 @@ func Test_decide_group(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - tt.args.s.disableUntargettedApps(tt.groupFlag, []string{}) + tt.args.s.disableUntargetedApps(tt.groupFlag, []string{}) if len(tt.args.s.TargetMap) != len(tt.want) { t.Errorf("decide() = %d, want %d", len(tt.args.s.TargetMap), len(tt.want)) } diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index 969471df..cc645af0 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -89,17 +89,17 @@ func validateChart(apps, chart, version string, c chan string) { // getChartVersion fetches the lastest chart version matching the semantic versioning constraints. // If chart is local, returns the given release version -func getChartVersion(chart, version string) (string, string) { +func getChartVersion(chart, version string) (string, error) { if isLocalChart(chart) { log.Info("Chart [ " + chart + " ] with version [ " + version + " ] was found locally.") - return version, "" + return version, nil } cmd := helmCmd([]string{"search", "repo", chart, "--version", version, "-o", "json"}, "Getting latest non-local chart's version "+chart+"-"+version+"") result := cmd.Exec() if result.code != 0 { - return "", "Chart [ " + chart + " ] with version [ " + version + " ] is specified but not found in the helm repositories" + return "", fmt.Errorf("Chart [ %s ] with version [ %s ] is specified but not found in the helm repositories", chart, version) } chartVersions := make([]chartVersion, 0) @@ -115,12 +115,12 @@ func getChartVersion(chart, version string) (string, string) { } if len(filteredChartVersions) < 1 { - return "", "Chart [ " + chart + " ] with version [ " + version + " ] is specified but not found in the helm repositories" + return "", fmt.Errorf("Chart [ %s ] with version [ %s ] is specified but not found in the helm repositories", chart, version) } else if len(filteredChartVersions) > 1 { - return "", "Multiple versions of chart [ " + chart + " ] with version [ " + version + " ] found in the helm repositories" + return "", fmt.Errorf("Multiple versions of chart [ %s ] with version [ %s ] found in the helm repositories", chart, version) } - return filteredChartVersions[0].Version, "" + return filteredChartVersions[0].Version, nil } // getHelmClientVersion returns Helm client Version diff --git a/internal/app/hooks.go b/internal/app/hooks.go index 1e7ae730..8837a734 100644 --- a/internal/app/hooks.go +++ b/internal/app/hooks.go @@ -1,5 +1,10 @@ package app +import ( + "fmt" + "strings" +) + const ( preInstall = "preInstall" postInstall = "postInstall" @@ -10,7 +15,38 @@ const ( test = "test" ) +// TODO: Create different types for Command and Manifest hooks +// with methods for getting their commands for the plan type hookCmd struct { Command Type string } + +func (h *hookCmd) getAnnotationKey() (string, error) { + if h.Type == "" { + return "", fmt.Errorf("no type specified") + } + return "helmsman/" + h.Type, nil +} + +// validateHooks validates that hook files exist and are of correct type +func validateHooks(hooks map[string]interface{}) error { + validFiles := []string{".yaml", ".yml", ".json"} + for key, value := range hooks { + switch key { + case preInstall, postInstall, preUpgrade, postUpgrade, preDelete, postDelete: + hook := value.(string) + if !isOfType(hook, validFiles) && ToolExists(strings.Fields(hook)[0]) { + return nil + } + if err := isValidFile(hook, validFiles); err != nil { + return err + } + case "successCondition", "successTimeout", "deleteOnSuccess": + continue + default: + return fmt.Errorf("%s is an Invalid hook type", key) + } + } + return nil +} diff --git a/internal/app/kube_helpers.go b/internal/app/kube_helpers.go index 8e654bd4..9c1a825c 100644 --- a/internal/app/kube_helpers.go +++ b/internal/app/kube_helpers.go @@ -68,19 +68,15 @@ func labelNamespace(ns string, labels map[string]string) { return } - var labelSlice []string + args := []string{"label", "--overwrite", "namespace/" + ns, flags.getKubeDryRunFlag("label")} for k, v := range labels { - labelSlice = append(labelSlice, k+"="+v) + args = append(args, k+"="+v) } - - args := []string{"label", "--overwrite", "namespace/" + ns, flags.getKubeDryRunFlag("label")} - args = append(args, labelSlice...) - cmd := kubectl(args, "Labeling namespace [ "+ns+" ]") result := cmd.Exec() if result.code != 0 && flags.verbose { - log.Warning(fmt.Sprintf("Could not label namespace [ %s with %v ]. Error message: %s", ns, labelSlice, result.errors)) + log.Warning(fmt.Sprintf("Could not label namespace [ %s with %v ]. Error message: %s", ns, strings.Join(args[4:], ","), result.errors)) } } @@ -90,17 +86,15 @@ func annotateNamespace(ns string, annotations map[string]string) { return } - var annotationSlice []string + args := []string{"annotate", "--overwrite", "namespace/" + ns, flags.getKubeDryRunFlag("annotate")} for k, v := range annotations { - annotationSlice = append(annotationSlice, k+"="+v) + args = append(args, k+"="+v) } - args := []string{"annotate", "--overwrite", "namespace/" + ns, flags.getKubeDryRunFlag("annotate")} - args = append(args, annotationSlice...) cmd := kubectl(args, "Annotating namespace [ "+ns+" ]") result := cmd.Exec() if result.code != 0 && flags.verbose { - log.Info(fmt.Sprintf("Could not annotate namespace [ %s with %v ]. Error message: %s", ns, annotationSlice, result.errors)) + log.Info(fmt.Sprintf("Could not annotate namespace [ %s with %v ]. Error message: %s", ns, strings.Join(args[4:], ","), result.errors)) } } diff --git a/internal/app/plan.go b/internal/app/plan.go index 3ac31cd5..a1826336 100644 --- a/internal/app/plan.go +++ b/internal/app/plan.go @@ -145,14 +145,14 @@ func releaseWithHooks(cmd orderedCommand, storageBackend string, wg *sync.WaitGr for _, c := range cmd.beforeCommands { if err := execOne(c.Command, cmd.targetRelease); err != nil { errors <- err - if c.Type != "" { - annotations = append(annotations, "helmsman/"+c.Type+"=failed") + if key, err := c.getAnnotationKey(); err != nil { + annotations = append(annotations, key+"=failed") } log.Verbose(err.Error()) return } - if c.Type != "" { - annotations = append(annotations, "helmsman/"+c.Type+"=ok") + if key, err := c.getAnnotationKey(); err != nil { + annotations = append(annotations, key+"=ok") } } if !flags.dryRun && !flags.destroy { @@ -169,13 +169,13 @@ func releaseWithHooks(cmd orderedCommand, storageBackend string, wg *sync.WaitGr for _, c := range cmd.afterCommands { if err := execOne(c.Command, cmd.targetRelease); err != nil { errors <- err - if c.Type != "" { - annotations = append(annotations, "helmsman/"+c.Type+"=failed") + if key, err := c.getAnnotationKey(); err != nil { + annotations = append(annotations, key+"=failed") } log.Verbose(err.Error()) } else { - if c.Type != "" { - annotations = append(annotations, "helmsman/"+c.Type+"=ok") + if key, err := c.getAnnotationKey(); err != nil { + annotations = append(annotations, key+"=ok") } } } diff --git a/internal/app/release.go b/internal/app/release.go index fcdce0cf..69b81494 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -78,16 +78,18 @@ func (r *release) validate(appLabel string, seen map[string]map[string]bool, s * return errors.New("version can't be empty") } + validFiles := []string{".yaml", ".yml", ".json"} + if r.ValuesFile != "" && len(r.ValuesFiles) > 0 { return errors.New("valuesFile and valuesFiles should not be used together") } else if r.ValuesFile != "" { - if err := isValidFile(r.ValuesFile, []string{".yaml", ".yml", ".json"}); err != nil { - return fmt.Errorf(err.Error()) + if err := isValidFile(r.ValuesFile, validFiles); err != nil { + return err } } else if len(r.ValuesFiles) > 0 { for _, filePath := range r.ValuesFiles { - if err := isValidFile(filePath, []string{".yaml", ".yml", ".json"}); err != nil { - return fmt.Errorf(err.Error()) + if err := isValidFile(filePath, validFiles); err != nil { + return err } } } @@ -95,21 +97,19 @@ func (r *release) validate(appLabel string, seen map[string]map[string]bool, s * if r.SecretsFile != "" && len(r.SecretsFiles) > 0 { return errors.New("secretsFile and secretsFiles should not be used together") } else if r.SecretsFile != "" { - if err := isValidFile(r.SecretsFile, []string{".yaml", ".yml", ".json"}); err != nil { - return fmt.Errorf(err.Error()) + if err := isValidFile(r.SecretsFile, validFiles); err != nil { + return err } } else if len(r.SecretsFiles) > 0 { for _, filePath := range r.SecretsFiles { - if err := isValidFile(filePath, []string{".yaml", ".yml", ".json"}); err != nil { - return fmt.Errorf(err.Error()) + if err := isValidFile(filePath, validFiles); err != nil { + return err } } } - if r.PostRenderer != "" { - if !ToolExists(r.PostRenderer) { - return fmt.Errorf(r.PostRenderer + " must be valid relative (from dsf file) file path.") - } + if r.PostRenderer != "" && !ToolExists(r.PostRenderer) { + return fmt.Errorf("%s must be valid relative (from dsf file) file path", r.PostRenderer) } if r.Priority != 0 && r.Priority > 0 { @@ -117,8 +117,8 @@ func (r *release) validate(appLabel string, seen map[string]map[string]bool, s * } if (len(r.Hooks)) != 0 { - if ok, errorMsg := validateHooks(r.Hooks); !ok { - return fmt.Errorf(errorMsg) + if err := validateHooks(r.Hooks); err != nil { + return err } } @@ -130,26 +130,6 @@ func (r *release) validate(appLabel string, seen map[string]map[string]bool, s * return nil } -// validateHooks validates that hook files exist and of YAML type -func validateHooks(hooks map[string]interface{}) (bool, string) { - for key, value := range hooks { - switch key { - case preInstall, postInstall, preUpgrade, postUpgrade, preDelete, postDelete: - hook := value.(string) - if err := isValidFile(hook, []string{".yaml", ".yml", ".json"}); err != nil { - if !ToolExists(strings.Fields(hook)[0]) { - return false, err.Error() - } - } - case "successCondition", "successTimeout", "deleteOnSuccess": - continue - default: - return false, key + " is an Invalid hook type." - } - } - return true, "" -} - // testRelease creates a Helm command to test a particular release. func (r *release) test(afterCommands *[]hookCmd) { cmd := helmCmd(r.getHelmArgsFor("test"), "Running tests for release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") @@ -311,12 +291,12 @@ func (r *release) annotate(storageBackend string, annotations ...string) { // A protected is release is either: a) deployed in a protected namespace b) flagged as protected in the desired state file // Any release in a protected namespace is protected by default regardless of its flag // returns true if a release is protected, false otherwise -func (r *release) isProtected(cs *currentState, s *state) bool { +func (r *release) isProtected(cs *currentState, n *namespace) bool { // if the release does not exist in the cluster, it is not protected if ok := cs.releaseExists(r, ""); !ok { return false } - if s.Namespaces[r.Namespace].Protected || r.Protected { + if n.Protected || r.Protected { return true } return false @@ -474,10 +454,8 @@ func (r *release) checkHooks(action string, optionalNamespace ...string) ([]hook if len(optionalNamespace) > 0 { ns = optionalNamespace[0] } - var ( - beforeCmds []hookCmd - afterCmds []hookCmd - ) + var beforeCmds, afterCmds []hookCmd + switch action { case "install": { @@ -526,7 +504,7 @@ func (r *release) getHookCommands(hookType, ns string) []hookCmd { Command: Command{ Cmd: args[0], Args: args[1:], - Description: hookType + "Hook", + Description: fmt.Sprintf("%s shell hook", hookType), }, Type: hookType, }) diff --git a/internal/app/release_test.go b/internal/app/release_test.go index a53cd13b..e6ec25fa 100644 --- a/internal/app/release_test.go +++ b/internal/app/release_test.go @@ -72,7 +72,7 @@ func Test_validateRelease(t *testing.T) { }, s: st, }, - want: "xyz.yaml must be valid relative (from dsf file) file path.", + want: "xyz.yaml must be valid relative (from dsf file) file path", }, { name: "test case 3", args: args{ @@ -233,7 +233,7 @@ func Test_validateRelease(t *testing.T) { }, s: st, }, - want: "xyz.yaml must be valid relative (from dsf file) file path.", + want: "xyz.yaml must be valid relative (from dsf file) file path", }, { name: "test case 13", args: args{ @@ -265,7 +265,7 @@ func Test_validateRelease(t *testing.T) { }, s: st, }, - want: "xyz.fake must be valid relative (from dsf file) file path.", + want: "xyz.fake must be valid relative (from dsf file) file path", }, { name: "test case 15 - invalid hook file type", args: args{ @@ -329,7 +329,7 @@ func Test_validateRelease(t *testing.T) { }, s: st, }, - want: "https//raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml must be valid URL path to a raw file.", + want: "https//raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml must be valid URL path to a raw file", }, { name: "test case 19 - invalid hook type 1", args: args{ @@ -345,7 +345,7 @@ func Test_validateRelease(t *testing.T) { }, s: st, }, - want: "afterDelete is an Invalid hook type.", + want: "afterDelete is an Invalid hook type", }, { name: "test case 20 - invalid hook type 2", args: args{ @@ -361,7 +361,7 @@ func Test_validateRelease(t *testing.T) { }, s: st, }, - want: "PreDelete is an Invalid hook type.", + want: "PreDelete is an Invalid hook type", }, { name: "test case 21", args: args{ @@ -395,7 +395,7 @@ func Test_validateRelease(t *testing.T) { }, s: st, }, - want: "doesnt-exist.sh must be valid relative (from dsf file) file path.", + want: "doesnt-exist.sh must be valid relative (from dsf file) file path", }, { name: "test case 23 - executable hook type", args: args{ @@ -599,7 +599,7 @@ func Test_validateReleaseCharts(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { stt := &state{Apps: tt.args.apps} - stt.disableUntargettedApps(tt.groupFlag, tt.targetFlag) + stt.disableUntargetedApps(tt.groupFlag, tt.targetFlag) err := stt.validateReleaseCharts() switch err.(type) { case nil: diff --git a/internal/app/state.go b/internal/app/state.go index fe0ffe0f..80338e1c 100644 --- a/internal/app/state.go +++ b/internal/app/state.go @@ -108,8 +108,8 @@ func (s *state) validate() error { // lifecycle hooks validation if len(s.Settings.GlobalHooks) != 0 { - if ok, errorMsg := validateHooks(s.Settings.GlobalHooks); !ok { - return fmt.Errorf(errorMsg) + if err := validateHooks(s.Settings.GlobalHooks); err != nil { + return err } } @@ -124,11 +124,9 @@ func (s *state) validate() error { if s.Certificates != nil && len(s.Certificates) != 0 { for key, value := range s.Certificates { - r, path := isValidCert(value) - if !r { + if !isValidCert(value) { return errors.New("certifications validation failed -- [ " + key + " ] must be a valid S3, GCS, AZ bucket/container URL or a valid relative file path") } - s.Certificates[key] = path } _, caCrt := s.Certificates["caCrt"] @@ -241,16 +239,6 @@ func (s *state) validateReleaseCharts() error { return nil } -// isValidCert checks if a certificate/key path/URI is valid -func isValidCert(value string) (bool, string) { - _, err1 := url.ParseRequestURI(value) - _, err2 := os.Stat(value) - if err2 != nil && (err1 != nil || (!strings.HasPrefix(value, "s3://") && !strings.HasPrefix(value, "gs://") && !strings.HasPrefix(value, "az://"))) { - return false, "" - } - return true, value -} - // isNamespaceDefined checks if a given namespace is defined in the namespaces section of the desired state file func (s *state) isNamespaceDefined(ns string) bool { _, ok := s.Namespaces[ns] @@ -266,7 +254,7 @@ func (s *state) overrideAppsNamespace(newNs string) { } // get only those Apps that exist in TargetMap -func (s *state) disableUntargettedApps(groups, targets []string) { +func (s *state) disableUntargetedApps(groups, targets []string) { if s.TargetMap == nil { s.TargetMap = make(map[string]bool) } diff --git a/internal/app/state_files.go b/internal/app/state_files.go index 9fd31de7..97ebff8e 100644 --- a/internal/app/state_files.go +++ b/internal/app/state_files.go @@ -43,8 +43,8 @@ func (s *state) fromTOML(file string) (bool, string) { tomlFile := string(rawTomlFile) if !flags.noEnvSubst { - if ok, err := validateEnvVars(tomlFile, file); !ok { - return false, err + if err := validateEnvVars(tomlFile, file); err != nil { + return false, err.Error() } tomlFile = substituteEnv(tomlFile) } @@ -95,8 +95,8 @@ func (s *state) fromYAML(file string) (bool, string) { yamlFile := string(rawYamlFile) if !flags.noEnvSubst { - if ok, err := validateEnvVars(yamlFile, file); !ok { - return false, err + if err := validateEnvVars(yamlFile, file); err != nil { + return false, err.Error() } yamlFile = substituteEnv(yamlFile) } diff --git a/internal/app/utils.go b/internal/app/utils.go index c650e13c..92ebf8a3 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -46,8 +46,8 @@ func substituteVarsInYaml(file string) string { yamlFile := string(rawYamlFile) if !flags.noEnvSubst && flags.substEnvValues { - if ok, err := validateEnvVars(yamlFile, file); !ok { - log.Critical(err) + if err := validateEnvVars(yamlFile, file); err != nil { + log.Critical(err.Error()) } yamlFile = substituteEnv(yamlFile) } @@ -133,12 +133,12 @@ func substituteEnv(name string) string { // validateEnvVars parses a string line-by-line and detect env variables in // non-comment lines. It then checks that each env var found has a value. -func validateEnvVars(s string, filename string) (bool, string) { +func validateEnvVars(s string, filename string) error { if !flags.skipValidation && strings.Contains(s, "$") { log.Info("validating environment variables in " + filename) var key string comment, _ := regexp.Compile("#(.*)$") - envVar, _ := regexp.Compile("\\${([a-zA-Z_][a-zA-Z0-9_-]*)}|\\$([a-zA-Z_][a-zA-Z0-9_-]*)") + envVar, _ := regexp.Compile(`\${([a-zA-Z_][a-zA-Z0-9_-]*)}|\$([a-zA-Z_][a-zA-Z0-9_-]*)`) scanner := bufio.NewScanner(strings.NewReader(s)) for scanner.Scan() { // remove spaces from the single line, then replace $$ with !? to prevent it from matching the regex, @@ -152,7 +152,7 @@ func validateEnvVars(s string, filename string) (bool, string) { key = v[2] } if _, ok := os.LookupEnv(key); !ok { - return false, v[0] + " is used as an env variable but is currently unset. Either set it or escape it like so: $" + v[0] + return fmt.Errorf("%s is used as an env variable but is currently unset. Either set it or escape it like so: $%s", v[0], v[0]) } } } @@ -160,7 +160,7 @@ func validateEnvVars(s string, filename string) (bool, string) { log.Critical(err.Error()) } } - return true, "" + return nil } // substituteSSM checks if a string has an SSM parameter variable (contains '{{ssm: '), then it returns its value @@ -385,12 +385,6 @@ func Indent(s, prefix string) string { return string(res) } -// isLocalChart checks if a chart specified in the DSF is a local directory or not -func isLocalChart(chart string) bool { - _, err := os.Stat(chart) - return err == nil -} - // concat appends all slices to a single slice func concat(slices ...[]string) []string { slice := []string{} @@ -462,16 +456,33 @@ func decryptSecret(name string) error { return nil } +// isLocalChart checks if a chart specified in the DSF is a local directory or not +func isLocalChart(chart string) bool { + _, err := os.Stat(chart) + return err == nil +} + +// isValidCert checks if a certificate/key path/URI is valid +func isValidCert(value string) bool { + if _, err := os.Stat(value); err != nil { + _, err1 := url.ParseRequestURI(value) + if err1 != nil || (!strings.HasPrefix(value, "s3://") && !strings.HasPrefix(value, "gs://") && !strings.HasPrefix(value, "az://")) { + return false + } + } + return true +} + // isValidFile checks if the file exists in the given path or accessible via http and is of allowed file extension (e.g. yaml, json ...) func isValidFile(filePath string, allowedFileTypes []string) error { if strings.HasPrefix(filePath, "http") { if _, err := url.ParseRequestURI(filePath); err != nil { - return errors.New(filePath + " must be valid URL path to a raw file.") + return fmt.Errorf("%s must be valid URL path to a raw file", filePath) } } else if _, pathErr := os.Stat(filePath); pathErr != nil { - return errors.New(filePath + " must be valid relative (from dsf file) file path.") + return fmt.Errorf("%s must be valid relative (from dsf file) file path", filePath) } else if !isOfType(filePath, allowedFileTypes) { - return errors.New(filePath + " must be of one the following file formats: " + strings.Join(allowedFileTypes, ", ")) + return fmt.Errorf("%s must be of one the following file formats: %s", filePath, strings.Join(allowedFileTypes, ", ")) } return nil } From 3bdbe6d54be4fcc4ba6a799739e7731bff27958c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Desch=C3=AAnes?= Date: Tue, 17 Nov 2020 10:53:23 -0500 Subject: [PATCH 0709/1127] extract chart name and version simultaneously --- go.mod | 1 + internal/app/decision_maker.go | 67 +++++++++++------------------ internal/app/decision_maker_test.go | 5 +-- internal/app/helm_helpers.go | 51 ++++------------------ internal/app/release.go | 10 ++--- internal/app/release_test.go | 32 ++++++++++---- 6 files changed, 64 insertions(+), 102 deletions(-) diff --git a/go.mod b/go.mod index daedaed8..c2716107 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/apsdehal/go-logger v0.0.0-20190515211354-1abdf898e024 github.com/aws/aws-sdk-go v1.26.2 github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 // indirect + github.com/google/go-cmp v0.3.0 github.com/hashicorp/go-version v1.2.0 github.com/imdario/mergo v0.3.8 github.com/joho/godotenv v1.3.0 diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index b8d642e6..6efe4318 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -62,12 +62,15 @@ func (cs *currentState) makePlan(s *state) *plan { wg := sync.WaitGroup{} sem := make(chan struct{}, resourcePool) - namesC := make(chan [2]string, len(s.Apps)) - versionsC := make(chan [4]string, len(s.Apps)) + infoC := make(chan struct { + c string + v string + i *chartInfo + e error + }, len(s.Apps)) // We store the results of the helm commands - extractedChartNames := make(map[string]string) - extractedChartVersions := make(map[string]map[string]string) + extractedChartInfo := make(map[string]map[string]chartInfo) // We get the charts and versions with the expensive helm commands first. // We can probably DRY this concurrency stuff up somehow. @@ -84,8 +87,8 @@ func (cs *currentState) makePlan(s *state) *plan { charts[r.Chart] = make(map[string]bool) } - if extractedChartVersions[r.Chart] == nil { - extractedChartVersions[r.Chart] = make(map[string]string) + if extractedChartInfo[r.Chart] == nil { + extractedChartInfo[r.Chart] = make(map[string]chartInfo) } if r.isConsideredToRun() { @@ -97,17 +100,6 @@ func (cs *currentState) makePlan(s *state) *plan { // I'm not fond of this concurrency pattern, it's just a continuation of what's already there. // This seems like overkill somehow. Is it necessary to run all the helm commands concurrently? Does that speed it up? (Quick sanity check says: yes, it does) for chart, versions := range charts { - sem <- struct{}{} - wg.Add(1) - go func(chart string) { - defer func() { - wg.Done() - <-sem - }() - - namesC <- [2]string{chart, extractChartName(chart)} - }(chart) - for version, shouldRun := range versions { if !shouldRun { continue @@ -124,39 +116,30 @@ func (cs *currentState) makePlan(s *state) *plan { // even though I wrote it, lines like this are a code smell to me -- we need to rethink the concurrency as i mentioned above. // ideally you just process all helm commands "in a background pool", they return channels, // and we would be looping over select statements until we had the results of the helm commands we wanted. - n, e := getChartVersion(chart, version) - m := "" - if e != nil { - m = e.Error() - } - versionsC <- [4]string{chart, version, n, m} + i, e := getChartInfo(chart, version) + infoC <- struct { + c string + v string + i *chartInfo + e error + }{chart, version, i, e} }(chart, version) } } wg.Wait() - close(namesC) - close(versionsC) + close(infoC) // One thing we could do here instead would be: // instead of waiting for everything to come back, just start deciding for releases as their chart names and versions come through. - for nameResult := range namesC { - // Is there a ... that can do this? I forget - c, n := nameResult[0], nameResult[1] - log.Verbose("Extracted chart name [ " + c + " ].") - extractedChartNames[c] = n - } - - for versionResult := range versionsC { - // Is there a ... that can do this? I forget - c, v, n, m := versionResult[0], versionResult[1], versionResult[2], versionResult[3] - if m != "" { + for info := range infoC { + if info.e != nil { // Better to fail early and return here? - log.Error(m) + log.Error(info.e.Error()) } else { - log.Verbose("Extracted chart version from chart [ " + c + " ] with version [ " + v + " ]: '" + n + "'") - extractedChartVersions[c][v] = n + log.Verbose("Extracted chart information from chart [ " + info.c + " ] with version [ " + info.v + " ]: '" + info.i.Version + "'") + extractedChartInfo[info.c][info.v] = *info.i } } @@ -169,13 +152,13 @@ func (cs *currentState) makePlan(s *state) *plan { r.checkChartDepUpdate() sem <- struct{}{} wg.Add(1) - go func(r *release, chartName, chartVersion string) { + go func(r *release, c chartInfo) { defer func() { wg.Done() <-sem }() - cs.decide(r, s.Namespaces[r.Namespace], p, chartName, chartVersion) - }(r, extractedChartNames[r.Chart], extractedChartVersions[r.Chart][r.Version]) + cs.decide(r, s.Namespaces[r.Namespace], p, c.Name, c.Version) + }(r, extractedChartInfo[r.Chart][r.Version]) } wg.Wait() diff --git a/internal/app/decision_maker_test.go b/internal/app/decision_maker_test.go index 243da8db..61fb9f7a 100644 --- a/internal/app/decision_maker_test.go +++ b/internal/app/decision_maker_test.go @@ -111,9 +111,8 @@ func Test_inspectUpgradeScenario(t *testing.T) { cs := currentState{releases: *tt.args.s} // Act - chartName := extractChartName(tt.args.r.Chart) - chartVersion, _ := getChartVersion(tt.args.r.Chart, tt.args.r.Version) - cs.inspectUpgradeScenario(tt.args.r, &outcome, chartName, chartVersion) + c, _ := getChartInfo(tt.args.r.Chart, tt.args.r.Version) + cs.inspectUpgradeScenario(tt.args.r, &outcome, c.Name, c.Version) got := outcome.Decisions[0].Type t.Log(outcome.Decisions[0].Description) diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index cc645af0..cdf9d811 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "gopkg.in/yaml.v2" "net/url" "path/filepath" "regexp" @@ -28,27 +29,6 @@ func helmCmd(args []string, desc string) Command { } } -// extractChartName extracts the Helm chart name from full chart name in the desired state. -func extractChartName(releaseChart string) string { - cmd := helmCmd([]string{"show", "chart", "--devel", releaseChart}, "Extracting chart information for [ "+releaseChart+" ]") - - result := cmd.Exec() - if result.code != 0 { - log.Fatal("While getting chart information: " + result.errors) - } - - name := "" - for _, v := range strings.Split(result.output, "\n") { - split := strings.Split(v, ":") - if len(split) == 2 && split[0] == "name" { - name = strings.Trim(split[1], `"' `) - break - } - } - - return name -} - var versionExtractor = regexp.MustCompile(`[\n]version:\s?(.*)`) // validateChart validates if chart with the same name and version as specified in the DSF exists @@ -87,40 +67,25 @@ func validateChart(apps, chart, version string, c chan string) { } } -// getChartVersion fetches the lastest chart version matching the semantic versioning constraints. -// If chart is local, returns the given release version -func getChartVersion(chart, version string) (string, error) { +// getChartInfo fetches the latest chart information (name, version) matching the semantic versioning constraints. +func getChartInfo(chart, version string) (*chartInfo, error) { if isLocalChart(chart) { log.Info("Chart [ " + chart + " ] with version [ " + version + " ] was found locally.") - return version, nil } - cmd := helmCmd([]string{"search", "repo", chart, "--version", version, "-o", "json"}, "Getting latest non-local chart's version "+chart+"-"+version+"") + cmd := helmCmd([]string{"show", "chart", chart, "--version", version}, "Getting latest non-local chart's version "+chart+"-"+version+"") result := cmd.Exec() if result.code != 0 { - return "", fmt.Errorf("Chart [ %s ] with version [ %s ] is specified but not found in the helm repositories", chart, version) + return nil, fmt.Errorf("Chart [ %s ] with version [ %s ] is specified but not found in the helm repositories", chart, version) } - chartVersions := make([]chartVersion, 0) - if err := json.Unmarshal([]byte(result.output), &chartVersions); err != nil { + c := &chartInfo{} + if err := yaml.Unmarshal([]byte(result.output), &c); err != nil { log.Fatal(fmt.Sprint(err)) } - filteredChartVersions := make([]chartVersion, 0) - for _, c := range chartVersions { - if c.Name == chart { - filteredChartVersions = append(filteredChartVersions, c) - } - } - - if len(filteredChartVersions) < 1 { - return "", fmt.Errorf("Chart [ %s ] with version [ %s ] is specified but not found in the helm repositories", chart, version) - } else if len(filteredChartVersions) > 1 { - return "", fmt.Errorf("Multiple versions of chart [ %s ] with version [ %s ] found in the helm repositories", chart, version) - } - - return filteredChartVersions[0].Version, nil + return c, nil } // getHelmClientVersion returns Helm client Version diff --git a/internal/app/release.go b/internal/app/release.go index 69b81494..35e30974 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -37,11 +37,11 @@ type release struct { disabled bool } -type chartVersion struct { - Name string `json:"name"` - Version string `json:"version"` - AppVersion string `json:"app_version"` - Description string `json:"description"` +type chartInfo struct { + Name string `yaml:"name"` + Version string `yaml:"version"` + AppVersion string `yaml:"appVersion"` + Description string `yaml:"description"` } func (r *release) key() string { diff --git a/internal/app/release_test.go b/internal/app/release_test.go index e6ec25fa..51d367f2 100644 --- a/internal/app/release_test.go +++ b/internal/app/release_test.go @@ -2,6 +2,7 @@ package app import ( "fmt" + "github.com/google/go-cmp/cmp" "os" "testing" ) @@ -710,7 +711,7 @@ func Test_getReleaseChartVersion(t *testing.T) { } } -func Test_getChartVersion(t *testing.T) { +func Test_getChartInfo(t *testing.T) { // version string = the first semver-valid string after the last hypen in the chart string. type args struct { r *release @@ -718,10 +719,10 @@ func Test_getChartVersion(t *testing.T) { tests := []struct { name string args args - want string + want *chartInfo }{ { - name: "getChartVersion - local chart should return given release version", + name: "getChartInfo - local chart should return given release info", args: args{ r: &release{ Name: "release1", @@ -731,10 +732,23 @@ func Test_getChartVersion(t *testing.T) { Enabled: true, }, }, - want: "1.0.0", + want: &chartInfo{Name: "chart-test", Version: "1.0.0"}, }, { - name: "getChartVersion - unknown chart should error", + name: "getChartInfo - local chart semver should return latest matching release", + args: args{ + r: &release{ + Name: "release1", + Namespace: "namespace", + Version: "1.0.*", + Chart: "./../../tests/chart-test", + Enabled: true, + }, + }, + want: &chartInfo{Name: "chart-test", Version: "1.0.0"}, + }, + { + name: "getChartInfo - unknown chart should error", args: args{ r: &release{ Name: "release1", @@ -744,15 +758,15 @@ func Test_getChartVersion(t *testing.T) { Enabled: true, }, }, - want: "", + want: nil, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Log(tt.want) - got, _ := getChartVersion(tt.args.r.Chart, tt.args.r.Version) - if got != tt.want { - t.Errorf("getChartVersion() = %v, want %v", got, tt.want) + got, _ := getChartInfo(tt.args.r.Chart, tt.args.r.Version) + if !cmp.Equal(got, tt.want) { + t.Errorf("getChartInfo() = %v, want %v", got, tt.want) } }) } From aeb2335281bd18fcf6451d38a475fec726123b6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Desch=C3=AAnes?= Date: Tue, 17 Nov 2020 10:53:53 -0500 Subject: [PATCH 0710/1127] only check helm version once when adding repositories --- internal/app/helm_helpers.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index cdf9d811..248dee7e 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -161,6 +161,11 @@ func addHelmRepos(repos map[string]string) error { } } + repoAddFlags := "" + if checkHelmVersion(">=3.3.2") { + repoAddFlags += "--force-update" + } + for repoName, repoLink := range repos { basicAuthArgs := []string{} // check if repo is in GCS, then perform GCS auth -- needed for private GCS helm repos @@ -189,10 +194,6 @@ func addHelmRepos(repos map[string]string) error { repoLink = u.String() } - repoAddFlags := "" - if checkHelmVersion(">=3.3.2") { - repoAddFlags += "--force-update" - } cmd := helmCmd(concat([]string{"repo", "add", repoAddFlags, repoName, repoLink}, basicAuthArgs), "Adding helm repository [ "+repoName+" ]") // check current repository against existing repositories map in order to make sure it's missing and needs to be added if existingRepoUrl, ok := existingRepos[repoName]; ok { From 9d75752503f6539e9ff55ba42b370b3a43bc9228 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Desch=C3=AAnes?= Date: Wed, 18 Nov 2020 08:14:05 -0500 Subject: [PATCH 0711/1127] replace channel with mutex --- internal/app/decision_maker.go | 45 +++++++++------------------------- 1 file changed, 12 insertions(+), 33 deletions(-) diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index 6efe4318..e0ba6d70 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -1,6 +1,7 @@ package app import ( + "fmt" "regexp" "strings" "sync" @@ -61,13 +62,8 @@ func (cs *currentState) makePlan(s *state) *plan { p.ReverseDelete = s.Settings.ReverseDelete wg := sync.WaitGroup{} + mutex := sync.Mutex{} sem := make(chan struct{}, resourcePool) - infoC := make(chan struct { - c string - v string - i *chartInfo - e error - }, len(s.Apps)) // We store the results of the helm commands extractedChartInfo := make(map[string]map[string]chartInfo) @@ -97,8 +93,6 @@ func (cs *currentState) makePlan(s *state) *plan { } // Concurrently extract chart names and versions - // I'm not fond of this concurrency pattern, it's just a continuation of what's already there. - // This seems like overkill somehow. Is it necessary to run all the helm commands concurrently? Does that speed it up? (Quick sanity check says: yes, it does) for chart, versions := range charts { for version, shouldRun := range versions { if !shouldRun { @@ -113,35 +107,20 @@ func (cs *currentState) makePlan(s *state) *plan { <-sem }() - // even though I wrote it, lines like this are a code smell to me -- we need to rethink the concurrency as i mentioned above. - // ideally you just process all helm commands "in a background pool", they return channels, - // and we would be looping over select statements until we had the results of the helm commands we wanted. - i, e := getChartInfo(chart, version) - infoC <- struct { - c string - v string - i *chartInfo - e error - }{chart, version, i, e} + info, err := getChartInfo(chart, version) + if err != nil { + log.Error(err.Error()) + } else { + mutex.Lock() + log.Verbose(fmt.Sprintf("Extracted chart information from chart [ %s ] with version [ %s ]: %s %s", chart, version, info.Name, info.Version)) + extractedChartInfo[chart][version] = *info + mutex.Unlock() + } }(chart, version) } } wg.Wait() - close(infoC) - - // One thing we could do here instead would be: - // instead of waiting for everything to come back, just start deciding for releases as their chart names and versions come through. - - for info := range infoC { - if info.e != nil { - // Better to fail early and return here? - log.Error(info.e.Error()) - } else { - log.Verbose("Extracted chart information from chart [ " + info.c + " ] with version [ " + info.v + " ]: '" + info.i.Version + "'") - extractedChartInfo[info.c][info.v] = *info.i - } - } // Pass the extracted names and versions back to the apps to decide. // We still have to run decide on all the apps, even the ones we previously filtered out when extracting names and versions. @@ -252,7 +231,7 @@ func (cs *currentState) releaseExists(r *release, status string) bool { return true } -var resourceNameExtractor = regexp.MustCompile(`(^\w+\/|\.v\d+$)`) +var resourceNameExtractor = regexp.MustCompile(`(^\w+/|\.v\d+$)`) var releaseNameExtractor = regexp.MustCompile(`sh\.helm\.release\.v\d+\.`) // getHelmsmanReleases returns a map of all releases that are labeled with "MANAGED-BY=HELMSMAN" From 19af11e95c823c0104935f406642b5eb4f33fec3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Desch=C3=AAnes?= Date: Wed, 18 Nov 2020 08:51:00 -0500 Subject: [PATCH 0712/1127] pass chartInfo to functions --- internal/app/decision_maker.go | 24 ++++++++++++------------ internal/app/decision_maker_test.go | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index e0ba6d70..c54b8bf7 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -66,7 +66,7 @@ func (cs *currentState) makePlan(s *state) *plan { sem := make(chan struct{}, resourcePool) // We store the results of the helm commands - extractedChartInfo := make(map[string]map[string]chartInfo) + extractedChartInfo := make(map[string]map[string]*chartInfo) // We get the charts and versions with the expensive helm commands first. // We can probably DRY this concurrency stuff up somehow. @@ -84,7 +84,7 @@ func (cs *currentState) makePlan(s *state) *plan { } if extractedChartInfo[r.Chart] == nil { - extractedChartInfo[r.Chart] = make(map[string]chartInfo) + extractedChartInfo[r.Chart] = make(map[string]*chartInfo) } if r.isConsideredToRun() { @@ -113,7 +113,7 @@ func (cs *currentState) makePlan(s *state) *plan { } else { mutex.Lock() log.Verbose(fmt.Sprintf("Extracted chart information from chart [ %s ] with version [ %s ]: %s %s", chart, version, info.Name, info.Version)) - extractedChartInfo[chart][version] = *info + extractedChartInfo[chart][version] = info mutex.Unlock() } }(chart, version) @@ -131,12 +131,12 @@ func (cs *currentState) makePlan(s *state) *plan { r.checkChartDepUpdate() sem <- struct{}{} wg.Add(1) - go func(r *release, c chartInfo) { + go func(r *release, c *chartInfo) { defer func() { wg.Done() <-sem }() - cs.decide(r, s.Namespaces[r.Namespace], p, c.Name, c.Version) + cs.decide(r, s.Namespaces[r.Namespace], p, c) }(r, extractedChartInfo[r.Chart][r.Version]) } wg.Wait() @@ -146,7 +146,7 @@ func (cs *currentState) makePlan(s *state) *plan { // decide makes a decision about what commands (actions) need to be executed // to make a release section of the desired state come true. -func (cs *currentState) decide(r *release, n *namespace, p *plan, chartName, chartVersion string) { +func (cs *currentState) decide(r *release, n *namespace, p *plan, c *chartInfo) { // check for presence in defined targets or groups if !r.isConsideredToRun() { p.addDecision("Release [ "+r.Name+" ] ignored", r.Priority, ignored) @@ -178,7 +178,7 @@ func (cs *currentState) decide(r *release, n *namespace, p *plan, chartName, cha if ok := cs.releaseExists(r, helmStatusDeployed); ok { if !r.isProtected(cs, n) { - cs.inspectUpgradeScenario(r, p, chartName, chartVersion) // upgrade or move + cs.inspectUpgradeScenario(r, p, c) // upgrade or move } else { p.addDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ "you remove its protection.", r.Priority, noop) @@ -346,8 +346,8 @@ func (cs *currentState) cleanUntrackedReleases(s *state, p *plan) { // it will be uninstalled and installed in the same namespace using the new chart. // - If the release is NOT in the same namespace specified in the input, // it will be purge deleted and installed in the new namespace. -func (cs *currentState) inspectUpgradeScenario(r *release, p *plan, chartName, chartVersion string) { - if chartName == "" || chartVersion == "" { +func (cs *currentState) inspectUpgradeScenario(r *release, p *plan, c *chartInfo) { + if c == nil || c.Name == "" || c.Version == "" { return } @@ -357,15 +357,15 @@ func (cs *currentState) inspectUpgradeScenario(r *release, p *plan, chartName, c } if r.Namespace == rs.Namespace { - r.Version = chartVersion + r.Version = c.Version - if chartName == rs.getChartName() && r.Version != rs.getChartVersion() { + if c.Name == rs.getChartName() && r.Version != rs.getChartVersion() { // upgrade r.diff() r.upgrade(p) p.addDecision("Release [ "+r.Name+" ] will be upgraded", r.Priority, change) - } else if chartName != rs.getChartName() { + } else if c.Name != rs.getChartName() { r.reInstall(p) p.addDecision("Release [ "+r.Name+" ] is desired to use a new chart [ "+r.Chart+ " ]. Delete of the current release will be planned and new chart will be installed in namespace [ "+ diff --git a/internal/app/decision_maker_test.go b/internal/app/decision_maker_test.go index 61fb9f7a..a85d0ec5 100644 --- a/internal/app/decision_maker_test.go +++ b/internal/app/decision_maker_test.go @@ -112,7 +112,7 @@ func Test_inspectUpgradeScenario(t *testing.T) { // Act c, _ := getChartInfo(tt.args.r.Chart, tt.args.r.Version) - cs.inspectUpgradeScenario(tt.args.r, &outcome, c.Name, c.Version) + cs.inspectUpgradeScenario(tt.args.r, &outcome, c) got := outcome.Decisions[0].Type t.Log(outcome.Decisions[0].Description) @@ -228,7 +228,7 @@ func Test_decide(t *testing.T) { tt.args.s.disableUntargetedApps([]string{}, tt.targetFlag) outcome := plan{} // Act - cs.decide(tt.args.s.Apps[tt.args.r], tt.args.s.Namespaces[tt.args.s.Apps[tt.args.r].Namespace], &outcome, "", "") + cs.decide(tt.args.s.Apps[tt.args.r], tt.args.s.Namespaces[tt.args.s.Apps[tt.args.r].Namespace], &outcome, &chartInfo{}) got := outcome.Decisions[0].Type t.Log(outcome.Decisions[0].Description) From 511599c7ef9ff5f525d4bd8e6fad406f0723f7dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Desch=C3=AAnes?= Date: Wed, 18 Nov 2020 08:53:47 -0500 Subject: [PATCH 0713/1127] move chartInfo to helpers --- internal/app/helm_helpers.go | 5 +++++ internal/app/release.go | 7 ------- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index 248dee7e..3111665b 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -20,6 +20,11 @@ type helmRepo struct { Url string `json:"url"` } +type chartInfo struct { + Name string `yaml:"name"` + Version string `yaml:"version"` +} + // helmCmd prepares a helm command to be executed func helmCmd(args []string, desc string) Command { return Command{ diff --git a/internal/app/release.go b/internal/app/release.go index 35e30974..4288bfcb 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -37,13 +37,6 @@ type release struct { disabled bool } -type chartInfo struct { - Name string `yaml:"name"` - Version string `yaml:"version"` - AppVersion string `yaml:"appVersion"` - Description string `yaml:"description"` -} - func (r *release) key() string { return fmt.Sprintf("%s-%s", r.Name, r.Namespace) } From 62aa5e1584ab03b730614f819df0f6f511dc5765 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Desch=C3=AAnes?= Date: Wed, 18 Nov 2020 10:00:48 -0500 Subject: [PATCH 0714/1127] extract chart info before plan --- internal/app/decision_maker.go | 62 +--------------------------------- internal/app/helm_helpers.go | 40 ---------------------- internal/app/main.go | 15 ++++---- internal/app/release_test.go | 9 +++-- internal/app/state.go | 42 ++++++++++++++++------- 5 files changed, 43 insertions(+), 125 deletions(-) diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index c54b8bf7..6bc44d74 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -1,7 +1,6 @@ package app import ( - "fmt" "regexp" "strings" "sync" @@ -55,73 +54,14 @@ func buildState(s *state) *currentState { } // makePlan creates a plan of the actions needed to make the desired state come true. -// TODO: this code needs to be simplified func (cs *currentState) makePlan(s *state) *plan { p := createPlan() p.StorageBackend = s.Settings.StorageBackend p.ReverseDelete = s.Settings.ReverseDelete wg := sync.WaitGroup{} - mutex := sync.Mutex{} sem := make(chan struct{}, resourcePool) - // We store the results of the helm commands - extractedChartInfo := make(map[string]map[string]*chartInfo) - - // We get the charts and versions with the expensive helm commands first. - // We can probably DRY this concurrency stuff up somehow. - // We can also definitely DRY this with validateReleaseChart. - // We should probably have a data structure earlier on that sorts this out properly. - // Ideally we'd have a pipeline of helm command tasks with several stages that can all come home if one of them fails. - // Is it better to fail early here? I am not sure. - - // Unique chart names and versions in the DSF - charts := make(map[string]map[string]bool) - // Initialize the rejigged data structures - for _, r := range s.Apps { - if charts[r.Chart] == nil { - charts[r.Chart] = make(map[string]bool) - } - - if extractedChartInfo[r.Chart] == nil { - extractedChartInfo[r.Chart] = make(map[string]*chartInfo) - } - - if r.isConsideredToRun() { - charts[r.Chart][r.Version] = true - } - } - - // Concurrently extract chart names and versions - for chart, versions := range charts { - for version, shouldRun := range versions { - if !shouldRun { - continue - } - - sem <- struct{}{} - wg.Add(1) - go func(chart, version string) { - defer func() { - wg.Done() - <-sem - }() - - info, err := getChartInfo(chart, version) - if err != nil { - log.Error(err.Error()) - } else { - mutex.Lock() - log.Verbose(fmt.Sprintf("Extracted chart information from chart [ %s ] with version [ %s ]: %s %s", chart, version, info.Name, info.Version)) - extractedChartInfo[chart][version] = info - mutex.Unlock() - } - }(chart, version) - } - } - - wg.Wait() - // Pass the extracted names and versions back to the apps to decide. // We still have to run decide on all the apps, even the ones we previously filtered out when extracting names and versions. // We can now proceed without trying lots of identical helm commands at the same time. @@ -137,7 +77,7 @@ func (cs *currentState) makePlan(s *state) *plan { <-sem }() cs.decide(r, s.Namespaces[r.Namespace], p, c) - }(r, extractedChartInfo[r.Chart][r.Version]) + }(r, s.ChartInfo[r.Chart][r.Version]) } wg.Wait() diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index 3111665b..82732591 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -6,8 +6,6 @@ import ( "fmt" "gopkg.in/yaml.v2" "net/url" - "path/filepath" - "regexp" "strings" "github.com/hashicorp/go-version" @@ -34,44 +32,6 @@ func helmCmd(args []string, desc string) Command { } } -var versionExtractor = regexp.MustCompile(`[\n]version:\s?(.*)`) - -// validateChart validates if chart with the same name and version as specified in the DSF exists -func validateChart(apps, chart, version string, c chan string) { - if isLocalChart(chart) { - cmd := helmCmd([]string{"inspect", "chart", chart}, "Validating [ "+chart+" ] chart's availability") - - result := cmd.Exec() - if result.code != 0 { - maybeRepo := filepath.Base(filepath.Dir(chart)) - c <- "Chart [ " + chart + " ] for apps [" + apps + "] can't be found. Inspection returned error: \"" + - strings.TrimSpace(result.errors) + "\" -- If this is not a local chart, add the repo [ " + maybeRepo + " ] in your helmRepos stanza." - return - } - matches := versionExtractor.FindStringSubmatch(result.output) - if len(matches) == 2 { - v := strings.Trim(matches[1], `'"`) - if strings.Trim(version, `'"`) != v { - c <- "Chart [ " + chart + " ] with version [ " + version + " ] is specified for " + - "apps [" + apps + "] but the chart found at that path has version [ " + v + " ] which does not match." - return - } - } - } else { - v := version - if len(v) == 0 { - v = "*" - } - cmd := helmCmd([]string{"search", "repo", chart, "--version", v, "-l"}, "Validating [ "+chart+" ] chart's version [ "+version+" ] availability") - - if result := cmd.Exec(); result.code != 0 || strings.Contains(result.output, "No results found") { - c <- "Chart [ " + chart + " ] with version [ " + version + " ] is specified for " + - "apps [" + apps + "] but was not found. If this is not a local chart, define its helm repo in the helmRepo stanza in your DSF." - return - } - } -} - // getChartInfo fetches the latest chart information (name, version) matching the semantic versioning constraints. func getChartInfo(chart, version string) (*chartInfo, error) { if isLocalChart(chart) { diff --git a/internal/app/main.go b/internal/app/main.go index adda680e..1b957bc8 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -68,14 +68,15 @@ func Main() { } } - if !flags.skipValidation { - log.Info("Validating charts") - // validate charts-versions exist in defined repos - if err := s.validateReleaseCharts(); err != nil { - log.Fatal(err.Error()) - } - } else { + log.Info("Getting chart information") + + err := s.getReleaseChartsInfo() + if flags.skipValidation { log.Info("Skipping charts' validation.") + } else if err != nil { + log.Fatal(err.Error()) + } else { + log.Info("Charts validated.") } if flags.destroy { diff --git a/internal/app/release_test.go b/internal/app/release_test.go index 51d367f2..45a99dd0 100644 --- a/internal/app/release_test.go +++ b/internal/app/release_test.go @@ -517,7 +517,7 @@ func createFullReleasePointer(chart, version string) *release { } } -func Test_validateReleaseCharts(t *testing.T) { +func Test_getReleaseChartsInfo(t *testing.T) { type args struct { apps map[string]*release } @@ -601,15 +601,15 @@ func Test_validateReleaseCharts(t *testing.T) { t.Run(tt.name, func(t *testing.T) { stt := &state{Apps: tt.args.apps} stt.disableUntargetedApps(tt.groupFlag, tt.targetFlag) - err := stt.validateReleaseCharts() + err := stt.getReleaseChartsInfo() switch err.(type) { case nil: if tt.want != true { - t.Errorf("validateReleaseCharts() = %v, want error", err) + t.Errorf("getReleaseChartsInfo() = %v, want error", err) } case error: if tt.want != false { - t.Errorf("validateReleaseCharts() = %v, want nil", err) + t.Errorf("getReleaseChartsInfo() = %v, want nil", err) } } }) @@ -618,7 +618,6 @@ func Test_validateReleaseCharts(t *testing.T) { func Test_getReleaseChartVersion(t *testing.T) { // version string = the first semver-valid string after the last hypen in the chart string. - type args struct { r helmRelease } diff --git a/internal/app/state.go b/internal/app/state.go index 80338e1c..b41dc349 100644 --- a/internal/app/state.go +++ b/internal/app/state.go @@ -41,6 +41,7 @@ type state struct { Apps map[string]*release `yaml:"apps"` AppsTemplates map[string]*release `yaml:"appsTemplates,omitempty"` TargetMap map[string]bool + ChartInfo map[string]map[string]*chartInfo } func (s *state) setDefaults() { @@ -183,20 +184,23 @@ func (s *state) validate() error { return nil } -// validateReleaseCharts validates if the charts defined in a release are valid. +// getReleaseChartsInfo retrieves valid chart information. // Valid charts are the ones that can be found in the defined repos. -// This function uses Helm search to verify if the chart can be found or not. -func (s *state) validateReleaseCharts() error { +func (s *state) getReleaseChartsInfo() error { var fail bool wg := sync.WaitGroup{} + mutex := sync.Mutex{} sem := make(chan struct{}, resourcePool) - c := make(chan string, len(s.Apps)) + chartErrors := make(chan error, len(s.Apps)) charts := make(map[string]map[string][]string) + s.ChartInfo = make(map[string]map[string]*chartInfo) + for app, r := range s.Apps { if !r.isConsideredToRun() { continue } + if charts[r.Chart] == nil { charts[r.Chart] = make(map[string][]string) } @@ -205,14 +209,17 @@ func (s *state) validateReleaseCharts() error { charts[r.Chart][r.Version] = make([]string, 0) } + if s.ChartInfo[r.Chart] == nil { + s.ChartInfo[r.Chart] = make(map[string]*chartInfo) + } + charts[r.Chart][r.Version] = append(charts[r.Chart][r.Version], app) } for chart, versions := range charts { for version, apps := range versions { concattedApps := strings.Join(apps, ", ") - ch := chart - v := version + sem <- struct{}{} wg.Add(1) go func(apps, chart, version string) { @@ -220,17 +227,28 @@ func (s *state) validateReleaseCharts() error { wg.Done() <-sem }() - validateChart(concattedApps, chart, version, c) - }(concattedApps, ch, v) + + info, err := getChartInfo(chart, version) + if err != nil { + chartErrors <- err + } else { + log.Verbose(fmt.Sprintf("Extracted chart information from chart [ %s ] with version [ %s ]: %s %s", chart, version, info.Name, info.Version)) + mutex.Lock() + s.ChartInfo[chart][version] = info + mutex.Unlock() + } + + //validateChart(concattedApps, chart, version, chartErrors) + }(concattedApps, chart, version) } } wg.Wait() - close(c) - for err := range c { - if err != "" { + close(chartErrors) + for err := range chartErrors { + if err != nil { fail = true - log.Error(err) + log.Error(err.Error()) } } if fail { From 4d77914784687d037eff19ee9abb58f95bc710f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Desch=C3=AAnes?= Date: Wed, 18 Nov 2020 10:57:01 -0500 Subject: [PATCH 0715/1127] getChartInfo: add version check and test, move tests --- go.mod | 2 +- go.sum | 2 + internal/app/helm_helpers.go | 9 ++ internal/app/helm_helpers_test.go | 80 ++++++++++++ internal/app/release_test.go | 198 +----------------------------- internal/app/state_test.go | 124 +++++++++++++++++++ 6 files changed, 222 insertions(+), 193 deletions(-) create mode 100644 internal/app/helm_helpers_test.go diff --git a/go.mod b/go.mod index c2716107..d3625368 100644 --- a/go.mod +++ b/go.mod @@ -7,10 +7,10 @@ require ( github.com/Azure/azure-pipeline-go v0.1.9 github.com/Azure/azure-storage-blob-go v0.0.0-20181022225951-5152f14ace1c github.com/BurntSushi/toml v0.3.1 + github.com/Masterminds/semver/v3 v3.1.0 github.com/apsdehal/go-logger v0.0.0-20190515211354-1abdf898e024 github.com/aws/aws-sdk-go v1.26.2 github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 // indirect - github.com/google/go-cmp v0.3.0 github.com/hashicorp/go-version v1.2.0 github.com/imdario/mergo v0.3.8 github.com/joho/godotenv v1.3.0 diff --git a/go.sum b/go.sum index afb32cee..c723e1a5 100644 --- a/go.sum +++ b/go.sum @@ -9,6 +9,8 @@ github.com/Azure/azure-storage-blob-go v0.0.0-20181022225951-5152f14ace1c h1:Y5u github.com/Azure/azure-storage-blob-go v0.0.0-20181022225951-5152f14ace1c/go.mod h1:oGfmITT1V6x//CswqY2gtAHND+xIP64/qL7a5QJix0Y= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk= +github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/apsdehal/go-logger v0.0.0-20190515211354-1abdf898e024 h1:dfZ6RF0UxHqt7xPz0r7h00apsaa6rIrFhT6Xly55Exk= github.com/apsdehal/go-logger v0.0.0-20190515211354-1abdf898e024/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= github.com/aws/aws-sdk-go v1.26.2 h1:MzYLmCeny4bMQcAbYcucIduVZKp0sEf1eRLvHpKI5Is= diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index 82732591..d7576acd 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -11,6 +11,8 @@ import ( "github.com/hashicorp/go-version" "github.com/Praqma/helmsman/internal/gcs" + + "github.com/Masterminds/semver/v3" ) type helmRepo struct { @@ -50,6 +52,13 @@ func getChartInfo(chart, version string) (*chartInfo, error) { log.Fatal(fmt.Sprint(err)) } + constraint, _ := semver.NewConstraint(version) + found, _ := semver.NewVersion(c.Version) + + if !constraint.Check(found) { + return nil, fmt.Errorf("Chart [ %s ] with version [ %s ] was found with a mismatched version: %s", chart, version, c.Version) + } + return c, nil } diff --git a/internal/app/helm_helpers_test.go b/internal/app/helm_helpers_test.go new file mode 100644 index 00000000..f9ce826e --- /dev/null +++ b/internal/app/helm_helpers_test.go @@ -0,0 +1,80 @@ +package app + +import ( + "reflect" + "testing" +) + +func Test_getChartInfo(t *testing.T) { + // version string = the first semver-valid string after the last hypen in the chart string. + type args struct { + r *release + } + tests := []struct { + name string + args args + want *chartInfo + }{ + { + name: "getChartInfo - local chart should return given release info", + args: args{ + r: &release{ + Name: "release1", + Namespace: "namespace", + Version: "1.0.0", + Chart: "./../../tests/chart-test", + Enabled: true, + }, + }, + want: &chartInfo{Name: "chart-test", Version: "1.0.0"}, + }, + { + name: "getChartInfo - local chart semver should return latest matching release", + args: args{ + r: &release{ + Name: "release1", + Namespace: "namespace", + Version: "1.0.*", + Chart: "./../../tests/chart-test", + Enabled: true, + }, + }, + want: &chartInfo{Name: "chart-test", Version: "1.0.0"}, + }, + { + name: "getChartInfo - unknown chart should error", + args: args{ + r: &release{ + Name: "release1", + Namespace: "namespace", + Version: "1.0.0", + Chart: "random-chart-name-1f8147", + Enabled: true, + }, + }, + want: nil, + }, + { + name: "getChartInfo - wrong local version should error", + args: args{ + r: &release{ + Name: "release1", + Namespace: "namespace", + Version: "0.9.0", + Chart: "./../../tests/chart-test", + Enabled: true, + }, + }, + want: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Log(tt.want) + got, _ := getChartInfo(tt.args.r.Chart, tt.args.r.Version) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("getChartInfo() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/app/release_test.go b/internal/app/release_test.go index 45a99dd0..b32d6c54 100644 --- a/internal/app/release_test.go +++ b/internal/app/release_test.go @@ -2,7 +2,6 @@ package app import ( "fmt" - "github.com/google/go-cmp/cmp" "os" "testing" ) @@ -23,7 +22,7 @@ func setupTestCase(t *testing.T) func(t *testing.T) { } } -func Test_validateRelease(t *testing.T) { +func Test_release_validate(t *testing.T) { st := state{ Metadata: make(map[string]string), Certificates: make(map[string]string), @@ -423,13 +422,13 @@ func Test_validateRelease(t *testing.T) { got = r.Error() } if got != tt.want { - t.Errorf("validateRelease() got = %v, want %v", got, tt.want) + t.Errorf("release.validate() got = %v, want %v", got, tt.want) } }) } } -func Test_inheritHooks(t *testing.T) { +func Test_release_inheritHooks(t *testing.T) { st := state{ Metadata: make(map[string]string), Certificates: make(map[string]string), @@ -486,137 +485,13 @@ func Test_inheritHooks(t *testing.T) { got := tt.args.r.Hooks[preInstall].(string) + " -- " + tt.args.r.Hooks[postInstall].(string) + " -- " + tt.args.r.Hooks[preDelete].(string) + " -- " + tt.args.r.Hooks["successCondition"].(string) + " -- " + tt.args.r.Hooks["successTimeout"].(string) if got != tt.want { - t.Errorf("inheritHooks() got = %v, want %v", got, tt.want) + t.Errorf("release.inheritHooks() got = %v, want %v", got, tt.want) } }) } } -func createFullReleasePointer(chart, version string) *release { - return &release{ - Name: "", - Description: "", - Namespace: "", - Enabled: true, - Chart: chart, - Version: version, - ValuesFile: "", - ValuesFiles: []string{}, - SecretsFile: "", - SecretsFiles: []string{}, - Test: false, - Protected: false, - Wait: false, - Priority: 0, - Set: make(map[string]string), - SetString: make(map[string]string), - HelmFlags: []string{}, - NoHooks: false, - Timeout: 0, - PostRenderer: "", - } -} - -func Test_getReleaseChartsInfo(t *testing.T) { - type args struct { - apps map[string]*release - } - - tests := []struct { - name string - targetFlag []string - groupFlag []string - args args - want bool - }{ - { - name: "test case 1: valid local path with no chart", - args: args{ - apps: map[string]*release{ - "app": createFullReleasePointer(os.TempDir()+"/helmsman-tests/myapp", ""), - }, - }, - want: false, - }, { - name: "test case 2: invalid local path", - args: args{ - apps: map[string]*release{ - "app": createFullReleasePointer(os.TempDir()+"/does-not-exist/myapp", ""), - }, - }, - want: false, - }, { - name: "test case 3: valid chart local path with whitespace", - args: args{ - apps: map[string]*release{ - "app": createFullReleasePointer(os.TempDir()+"/helmsman-tests/dir-with space/myapp", "0.1.0"), - }, - }, - want: true, - }, { - name: "test case 4: valid chart from repo", - args: args{ - apps: map[string]*release{ - "app": createFullReleasePointer("prometheus-community/prometheus", "11.16.5"), - }, - }, - want: true, - }, { - name: "test case 5: invalid local path for chart ignored with -target flag, while other app was targeted", - targetFlag: []string{"notThisOne"}, - args: args{ - apps: map[string]*release{ - "app": createFullReleasePointer(os.TempDir()+"/does-not-exist/myapp", ""), - }, - }, - want: true, - }, { - name: "test case 6: invalid local path for chart included with -target flag", - targetFlag: []string{"app"}, - args: args{ - apps: map[string]*release{ - "app": createFullReleasePointer(os.TempDir()+"/does-not-exist/myapp", ""), - }, - }, - want: false, - }, { - name: "test case 7: multiple valid local apps with the same chart version", - targetFlag: []string{"app"}, - args: args{ - apps: map[string]*release{ - "app1": createFullReleasePointer(os.TempDir()+"/helmsman-tests/dir-with space/myapp", "0.1.0"), - "app2": createFullReleasePointer(os.TempDir()+"/helmsman-tests/dir-with space/myapp", "0.1.0"), - "app3": createFullReleasePointer(os.TempDir()+"/helmsman-tests/dir-with space/myapp", "0.1.0"), - "app4": createFullReleasePointer(os.TempDir()+"/helmsman-tests/dir-with space/myapp", "0.1.0"), - "app5": createFullReleasePointer(os.TempDir()+"/helmsman-tests/dir-with space/myapp", "0.1.0"), - }, - }, - want: true, - }, - } - - teardownTestCase := setupTestCase(t) - defer teardownTestCase(t) - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - stt := &state{Apps: tt.args.apps} - stt.disableUntargetedApps(tt.groupFlag, tt.targetFlag) - err := stt.getReleaseChartsInfo() - switch err.(type) { - case nil: - if tt.want != true { - t.Errorf("getReleaseChartsInfo() = %v, want error", err) - } - case error: - if tt.want != false { - t.Errorf("getReleaseChartsInfo() = %v, want nil", err) - } - } - }) - } -} - -func Test_getReleaseChartVersion(t *testing.T) { +func Test_release_getChartVersion(t *testing.T) { // version string = the first semver-valid string after the last hypen in the chart string. type args struct { r helmRelease @@ -704,68 +579,7 @@ func Test_getReleaseChartVersion(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Log(tt.want) if got := tt.args.r.getChartVersion(); got != tt.want { - t.Errorf("getReleaseChartVersion() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_getChartInfo(t *testing.T) { - // version string = the first semver-valid string after the last hypen in the chart string. - type args struct { - r *release - } - tests := []struct { - name string - args args - want *chartInfo - }{ - { - name: "getChartInfo - local chart should return given release info", - args: args{ - r: &release{ - Name: "release1", - Namespace: "namespace", - Version: "1.0.0", - Chart: "./../../tests/chart-test", - Enabled: true, - }, - }, - want: &chartInfo{Name: "chart-test", Version: "1.0.0"}, - }, - { - name: "getChartInfo - local chart semver should return latest matching release", - args: args{ - r: &release{ - Name: "release1", - Namespace: "namespace", - Version: "1.0.*", - Chart: "./../../tests/chart-test", - Enabled: true, - }, - }, - want: &chartInfo{Name: "chart-test", Version: "1.0.0"}, - }, - { - name: "getChartInfo - unknown chart should error", - args: args{ - r: &release{ - Name: "release1", - Namespace: "namespace", - Version: "1.0.0", - Chart: "random-chart-name-1f8147", - Enabled: true, - }, - }, - want: nil, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Log(tt.want) - got, _ := getChartInfo(tt.args.r.Chart, tt.args.r.Version) - if !cmp.Equal(got, tt.want) { - t.Errorf("getChartInfo() = %v, want %v", got, tt.want) + t.Errorf("release.getChartVersion() = %v, want %v", got, tt.want) } }) } diff --git a/internal/app/state_test.go b/internal/app/state_test.go index beddfab2..290a709c 100644 --- a/internal/app/state_test.go +++ b/internal/app/state_test.go @@ -372,3 +372,127 @@ func Test_state_validate(t *testing.T) { }) } } + +func createFullReleasePointer(chart, version string) *release { + return &release{ + Name: "", + Description: "", + Namespace: "", + Enabled: true, + Chart: chart, + Version: version, + ValuesFile: "", + ValuesFiles: []string{}, + SecretsFile: "", + SecretsFiles: []string{}, + Test: false, + Protected: false, + Wait: false, + Priority: 0, + Set: make(map[string]string), + SetString: make(map[string]string), + HelmFlags: []string{}, + NoHooks: false, + Timeout: 0, + PostRenderer: "", + } +} + +func Test_state_getReleaseChartsInfo(t *testing.T) { + type args struct { + apps map[string]*release + } + + tests := []struct { + name string + targetFlag []string + groupFlag []string + args args + want bool + }{ + { + name: "test case 1: valid local path with no chart", + args: args{ + apps: map[string]*release{ + "app": createFullReleasePointer(os.TempDir()+"/helmsman-tests/myapp", ""), + }, + }, + want: false, + }, { + name: "test case 2: invalid local path", + args: args{ + apps: map[string]*release{ + "app": createFullReleasePointer(os.TempDir()+"/does-not-exist/myapp", ""), + }, + }, + want: false, + }, { + name: "test case 3: valid chart local path with whitespace", + args: args{ + apps: map[string]*release{ + "app": createFullReleasePointer(os.TempDir()+"/helmsman-tests/dir-with space/myapp", "0.1.0"), + }, + }, + want: true, + }, { + name: "test case 4: valid chart from repo", + args: args{ + apps: map[string]*release{ + "app": createFullReleasePointer("prometheus-community/prometheus", "11.16.5"), + }, + }, + want: true, + }, { + name: "test case 5: invalid local path for chart ignored with -target flag, while other app was targeted", + targetFlag: []string{"notThisOne"}, + args: args{ + apps: map[string]*release{ + "app": createFullReleasePointer(os.TempDir()+"/does-not-exist/myapp", ""), + }, + }, + want: true, + }, { + name: "test case 6: invalid local path for chart included with -target flag", + targetFlag: []string{"app"}, + args: args{ + apps: map[string]*release{ + "app": createFullReleasePointer(os.TempDir()+"/does-not-exist/myapp", ""), + }, + }, + want: false, + }, { + name: "test case 7: multiple valid local apps with the same chart version", + targetFlag: []string{"app"}, + args: args{ + apps: map[string]*release{ + "app1": createFullReleasePointer(os.TempDir()+"/helmsman-tests/dir-with space/myapp", "0.1.0"), + "app2": createFullReleasePointer(os.TempDir()+"/helmsman-tests/dir-with space/myapp", "0.1.0"), + "app3": createFullReleasePointer(os.TempDir()+"/helmsman-tests/dir-with space/myapp", "0.1.0"), + "app4": createFullReleasePointer(os.TempDir()+"/helmsman-tests/dir-with space/myapp", "0.1.0"), + "app5": createFullReleasePointer(os.TempDir()+"/helmsman-tests/dir-with space/myapp", "0.1.0"), + }, + }, + want: true, + }, + } + + teardownTestCase := setupTestCase(t) + defer teardownTestCase(t) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + stt := &state{Apps: tt.args.apps} + stt.disableUntargetedApps(tt.groupFlag, tt.targetFlag) + err := stt.getReleaseChartsInfo() + switch err.(type) { + case nil: + if tt.want != true { + t.Errorf("getReleaseChartsInfo() = %v, want error", err) + } + case error: + if tt.want != false { + t.Errorf("getReleaseChartsInfo() = %v, want nil", err) + } + } + }) + } +} From 226f92158755cd08f6a1b7a1bd662409a015b72f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Desch=C3=AAnes?= Date: Wed, 18 Nov 2020 11:10:18 -0500 Subject: [PATCH 0716/1127] improve missing repository error --- internal/app/helm_helpers.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index d7576acd..772d21bf 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -6,6 +6,7 @@ import ( "fmt" "gopkg.in/yaml.v2" "net/url" + "path/filepath" "strings" "github.com/hashicorp/go-version" @@ -44,7 +45,10 @@ func getChartInfo(chart, version string) (*chartInfo, error) { result := cmd.Exec() if result.code != 0 { - return nil, fmt.Errorf("Chart [ %s ] with version [ %s ] is specified but not found in the helm repositories", chart, version) + maybeRepo := filepath.Base(filepath.Dir(chart)) + message := strings.TrimSpace(result.errors) + + return nil, fmt.Errorf("Chart [ %s ] version [ %s ] can't be found. Inspection returned error: \"%s\" -- If this is not a local chart, add the repo [ %s ] in your helmRepos stanza.", chart, version, message, maybeRepo) } c := &chartInfo{} From e837e1dcbaf0dcbc9c72b859308e36f46be086a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Desch=C3=AAnes?= Date: Thu, 19 Nov 2020 13:08:28 -0500 Subject: [PATCH 0717/1127] use go-version for semver checks --- go.mod | 1 - go.sum | 2 -- internal/app/helm_helpers.go | 20 +++++++++----------- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index d3625368..daedaed8 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,6 @@ require ( github.com/Azure/azure-pipeline-go v0.1.9 github.com/Azure/azure-storage-blob-go v0.0.0-20181022225951-5152f14ace1c github.com/BurntSushi/toml v0.3.1 - github.com/Masterminds/semver/v3 v3.1.0 github.com/apsdehal/go-logger v0.0.0-20190515211354-1abdf898e024 github.com/aws/aws-sdk-go v1.26.2 github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 // indirect diff --git a/go.sum b/go.sum index c723e1a5..afb32cee 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,6 @@ github.com/Azure/azure-storage-blob-go v0.0.0-20181022225951-5152f14ace1c h1:Y5u github.com/Azure/azure-storage-blob-go v0.0.0-20181022225951-5152f14ace1c/go.mod h1:oGfmITT1V6x//CswqY2gtAHND+xIP64/qL7a5QJix0Y= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk= -github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/apsdehal/go-logger v0.0.0-20190515211354-1abdf898e024 h1:dfZ6RF0UxHqt7xPz0r7h00apsaa6rIrFhT6Xly55Exk= github.com/apsdehal/go-logger v0.0.0-20190515211354-1abdf898e024/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= github.com/aws/aws-sdk-go v1.26.2 h1:MzYLmCeny4bMQcAbYcucIduVZKp0sEf1eRLvHpKI5Is= diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index 772d21bf..bd0a4060 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -12,8 +12,6 @@ import ( "github.com/hashicorp/go-version" "github.com/Praqma/helmsman/internal/gcs" - - "github.com/Masterminds/semver/v3" ) type helmRepo struct { @@ -36,19 +34,19 @@ func helmCmd(args []string, desc string) Command { } // getChartInfo fetches the latest chart information (name, version) matching the semantic versioning constraints. -func getChartInfo(chart, version string) (*chartInfo, error) { - if isLocalChart(chart) { - log.Info("Chart [ " + chart + " ] with version [ " + version + " ] was found locally.") +func getChartInfo(chartName, chartVersion string) (*chartInfo, error) { + if isLocalChart(chartName) { + log.Info("Chart [ " + chartName + " ] with version [ " + chartVersion + " ] was found locally.") } - cmd := helmCmd([]string{"show", "chart", chart, "--version", version}, "Getting latest non-local chart's version "+chart+"-"+version+"") + cmd := helmCmd([]string{"show", "chart", chartName, "--version", chartVersion}, "Getting latest non-local chart's version "+chartName+"-"+chartVersion+"") result := cmd.Exec() if result.code != 0 { - maybeRepo := filepath.Base(filepath.Dir(chart)) + maybeRepo := filepath.Base(filepath.Dir(chartName)) message := strings.TrimSpace(result.errors) - return nil, fmt.Errorf("Chart [ %s ] version [ %s ] can't be found. Inspection returned error: \"%s\" -- If this is not a local chart, add the repo [ %s ] in your helmRepos stanza.", chart, version, message, maybeRepo) + return nil, fmt.Errorf("Chart [ %s ] version [ %s ] can't be found. Inspection returned error: \"%s\" -- If this is not a local chart, add the repo [ %s ] in your helmRepos stanza.", chartName, chartVersion, message, maybeRepo) } c := &chartInfo{} @@ -56,11 +54,11 @@ func getChartInfo(chart, version string) (*chartInfo, error) { log.Fatal(fmt.Sprint(err)) } - constraint, _ := semver.NewConstraint(version) - found, _ := semver.NewVersion(c.Version) + constraint, _ := version.NewConstraint(chartVersion) + found, _ := version.NewVersion(c.Version) if !constraint.Check(found) { - return nil, fmt.Errorf("Chart [ %s ] with version [ %s ] was found with a mismatched version: %s", chart, version, c.Version) + return nil, fmt.Errorf("Chart [ %s ] with version [ %s ] was found with a mismatched version: %s", chartName, chartVersion, c.Version) } return c, nil From 9317715aba5dbf49f0080c97521c1182a4fdd369 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Desch=C3=AAnes?= Date: Thu, 19 Nov 2020 13:25:44 -0500 Subject: [PATCH 0718/1127] move test cases --- internal/app/release_test.go | 18 ------------------ internal/app/state_files_test.go | 25 +++++++++++++++++++++++++ internal/app/state_test.go | 18 ++++++++++++++++++ 3 files changed, 43 insertions(+), 18 deletions(-) diff --git a/internal/app/release_test.go b/internal/app/release_test.go index b32d6c54..682d21a1 100644 --- a/internal/app/release_test.go +++ b/internal/app/release_test.go @@ -1,27 +1,9 @@ package app import ( - "fmt" - "os" "testing" ) -func setupTestCase(t *testing.T) func(t *testing.T) { - t.Log("setup test case") - os.MkdirAll(tempFilesDir, 0755) - os.MkdirAll(os.TempDir()+"/helmsman-tests/myapp", os.ModePerm) - os.MkdirAll(os.TempDir()+"/helmsman-tests/dir-with space/myapp", os.ModePerm) - cmd := helmCmd([]string{"create", os.TempDir() + "/helmsman-tests/dir-with space/myapp"}, "creating an empty local chart directory") - if result := cmd.Exec(); result.code != 0 { - log.Fatal(fmt.Sprintf("Command returned with exit code: %d. And error message: %s ", result.code, result.errors)) - } - - return func(t *testing.T) { - t.Log("teardown test case") - //os.RemoveAll("/tmp/helmsman-tests/") - } -} - func Test_release_validate(t *testing.T) { st := state{ Metadata: make(map[string]string), diff --git a/internal/app/state_files_test.go b/internal/app/state_files_test.go index c6b0cd89..ae7cadc0 100644 --- a/internal/app/state_files_test.go +++ b/internal/app/state_files_test.go @@ -6,6 +6,16 @@ import ( "testing" ) +func setupStateFileTestCase(t *testing.T) func(t *testing.T) { + t.Log("setup test case") + os.MkdirAll(tempFilesDir, 0755) + + return func(t *testing.T) { + t.Log("teardown test case") + os.RemoveAll(tempFilesDir) + } +} + func Test_fromTOML(t *testing.T) { type args struct { file string @@ -34,6 +44,9 @@ func Test_fromTOML(t *testing.T) { } os.Setenv("ORG_PATH", "sample") os.Setenv("VALUE", "sample") + + teardownTestCase := setupStateFileTestCase(t) + defer teardownTestCase(t) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got, _ := tt.args.s.fromTOML(tt.args.file); got != tt.want { @@ -80,6 +93,9 @@ func Test_fromTOML_Expand(t *testing.T) { os.Setenv("SET_URI", "https://192.168.99.100:8443") os.Setenv("ORG_PATH", "sample") os.Setenv("VALUE", "sample") + + teardownTestCase := setupStateFileTestCase(t) + defer teardownTestCase(t) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err, msg := tt.args.s.fromTOML(tt.args.file) @@ -159,6 +175,9 @@ func Test_fromYAML(t *testing.T) { } os.Setenv("VALUE", "sample") os.Setenv("ORG_PATH", "sample") + + teardownTestCase := setupStateFileTestCase(t) + defer teardownTestCase(t) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got, _ := tt.args.s.fromYAML(tt.args.file); got != tt.want { @@ -200,6 +219,9 @@ func Test_fromYAML_UnsetVars(t *testing.T) { want: false, }, } + + teardownTestCase := setupStateFileTestCase(t) + defer teardownTestCase(t) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.targetVar == "ORG_PATH" { @@ -252,6 +274,9 @@ func Test_fromYAML_Expand(t *testing.T) { os.Setenv("SET_URI", "https://192.168.99.100:8443") os.Setenv("ORG_PATH", "sample") os.Setenv("VALUE", "sample") + + teardownTestCase := setupStateFileTestCase(t) + defer teardownTestCase(t) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err, msg := tt.args.s.fromYAML(tt.args.file) diff --git a/internal/app/state_test.go b/internal/app/state_test.go index 290a709c..398c64ce 100644 --- a/internal/app/state_test.go +++ b/internal/app/state_test.go @@ -1,10 +1,28 @@ package app import ( + "fmt" "os" "testing" ) +func setupTestCase(t *testing.T) func(t *testing.T) { + t.Log("setup test case") + os.MkdirAll(tempFilesDir, 0755) + os.MkdirAll(os.TempDir()+"/helmsman-tests/myapp", os.ModePerm) + os.MkdirAll(os.TempDir()+"/helmsman-tests/dir-with space/myapp", os.ModePerm) + cmd := helmCmd([]string{"create", os.TempDir() + "/helmsman-tests/dir-with space/myapp"}, "creating an empty local chart directory") + if result := cmd.Exec(); result.code != 0 { + log.Fatal(fmt.Sprintf("Command returned with exit code: %d. And error message: %s ", result.code, result.errors)) + } + + return func(t *testing.T) { + t.Log("teardown test case") + os.RemoveAll(tempFilesDir) + os.RemoveAll(os.TempDir() + "/helmsman-tests/") + } +} + func Test_state_validate(t *testing.T) { type fields struct { Metadata map[string]string From 2d39c93a067017d05324ca46340f9651866e4fa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Desch=C3=AAnes?= Date: Wed, 18 Nov 2020 12:33:05 -0500 Subject: [PATCH 0719/1127] run "helm dep up" concurrently also add '--skip-refresh' flag as 'helm repo update' is already run by helmsman, and is not safe to use in parallel --- internal/app/decision_maker.go | 2 +- internal/app/helm_helpers.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index 6bc44d74..5e33efd1 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -68,7 +68,6 @@ func (cs *currentState) makePlan(s *state) *plan { for _, r := range s.Apps { // To be honest, the helmCmd function should probably pass back a channel at this point, making the resource pool "global", for all helm commands. // It would make more sense than parallelising *some of the workload* like we do here with r.checkChartDepUpdate(), leaving some helm commands outside the concurrent part. - r.checkChartDepUpdate() sem <- struct{}{} wg.Add(1) go func(r *release, c *chartInfo) { @@ -76,6 +75,7 @@ func (cs *currentState) makePlan(s *state) *plan { wg.Done() <-sem }() + r.checkChartDepUpdate() cs.decide(r, s.Namespaces[r.Namespace], p, c) }(r, s.ChartInfo[r.Chart][r.Version]) } diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index bd0a4060..13d06a63 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -106,7 +106,7 @@ func helmPluginExists(plugin string) bool { // updateChartDep updates dependencies for a local chart func updateChartDep(chartPath string) error { - cmd := helmCmd([]string{"dependency", "update", chartPath}, "Updating dependency for local chart [ "+chartPath+" ]") + cmd := helmCmd([]string{"dependency", "update", "--skip-refresh", chartPath}, "Updating dependency for local chart [ "+chartPath+" ]") result := cmd.Exec() if result.code != 0 { From 090cf67059ca69c1ee2a693f76fe7d2d13290490 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Wed, 18 Nov 2020 10:59:09 +0000 Subject: [PATCH 0720/1127] fix: no special logic for rename is needed using helm v3 refactor: reduce nesting Signed-off-by: Luis Davim --- internal/app/cli.go | 2 ++ internal/app/decision_maker.go | 54 ++++++++++++++++++---------------- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/internal/app/cli.go b/internal/app/cli.go index 2d12902c..ed355508 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -60,6 +60,7 @@ type cli struct { substSSMValues bool updateDeps bool forceUpgrades bool + renameReplace bool version bool noCleanup bool migrateContext bool @@ -107,6 +108,7 @@ func (c *cli) parse() { flag.BoolVar(&c.substSSMValues, "subst-ssm-values", false, "turn on SSM parameter substitution in values files.") flag.BoolVar(&c.updateDeps, "update-deps", false, "run 'helm dep up' for local chart") flag.BoolVar(&c.forceUpgrades, "force-upgrades", false, "use --force when upgrading helm releases. May cause resources to be recreated.") + flag.BoolVar(&c.renameReplace, "replace-on-rename", false, "Uninstall the existing release when a chart with a different name is used.") flag.BoolVar(&c.noCleanup, "no-cleanup", false, "keeps any credentials files that has been downloaded on the host where helmsman runs.") flag.BoolVar(&c.migrateContext, "migrate-context", false, "updates the context name for all apps defined in the DSF and applies Helmsman labels. Using this flag is required if you want to change context name after it has been set.") flag.BoolVar(&c.alwaysUpgrade, "always-upgrade", false, "upgrade release even if no changes are found") diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index 5e33efd1..d42c0060 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -296,32 +296,8 @@ func (cs *currentState) inspectUpgradeScenario(r *release, p *plan, c *chartInfo return } - if r.Namespace == rs.Namespace { - r.Version = c.Version - - if c.Name == rs.getChartName() && r.Version != rs.getChartVersion() { - // upgrade - r.diff() - r.upgrade(p) - p.addDecision("Release [ "+r.Name+" ] will be upgraded", r.Priority, change) - - } else if c.Name != rs.getChartName() { - r.reInstall(p) - p.addDecision("Release [ "+r.Name+" ] is desired to use a new chart [ "+r.Chart+ - " ]. Delete of the current release will be planned and new chart will be installed in namespace [ "+ - r.Namespace+" ]", r.Priority, change) - } else { - if flags.alwaysUpgrade { - r.upgrade(p) - p.addDecision("Release [ "+r.Name+" ] will be updated (forced)", r.Priority, change) - } else if diff := r.diff(); diff != "" { - r.upgrade(p) - p.addDecision("Release [ "+r.Name+" ] will be updated", r.Priority, change) - } else { - p.addDecision("Release [ "+r.Name+" ] installed and up-to-date", r.Priority, noop) - } - } - } else { + if r.Namespace != rs.Namespace { + // Namespace changed r.reInstall(p, rs.Namespace) p.addDecision("Release [ "+r.Name+" ] is desired to be enabled in a new namespace [ "+r.Namespace+ " ]. Uninstall of the current release from namespace [ "+rs.Namespace+" ] will be performed "+ @@ -329,5 +305,31 @@ func (cs *currentState) inspectUpgradeScenario(r *release, p *plan, c *chartInfo p.addDecision("WARNING: moving release [ "+r.Name+" ] from [ "+rs.Namespace+" ] to [ "+r.Namespace+ " ] might not correctly connect existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/apps/moving_across_namespaces.md#note-on-persistent-volumes"+ " for details if this release uses PV and PVC.", r.Priority, change) + return + } + + r.Version = c.Version + + if c.Name != rs.getChartName() && flags.renameReplace { + // Chart changed + rs.uninstall(p) + r.install(p) + p.addDecision("Release [ "+r.Name+" ] is desired to use a new chart [ "+r.Chart+ + " ]. Delete of the current release will be planned and new chart will be installed in namespace [ "+ + r.Namespace+" ]", r.Priority, change) + return + } + + if flags.alwaysUpgrade || r.Version != rs.getChartVersion() || c.Name != rs.getChartName() { + // Version changed + r.upgrade(p) + p.addDecision("Release [ "+r.Name+" ] will be upgraded", r.Priority, change) + return + } + if diff := r.diff(); diff != "" { + r.upgrade(p) + p.addDecision("Release [ "+r.Name+" ] will be updated", r.Priority, change) + return } + p.addDecision("Release [ "+r.Name+" ] installed and up-to-date", r.Priority, noop) } From f69724d97a184fe6bac560c994b1cae58819f6d5 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Thu, 19 Nov 2020 21:37:22 +0000 Subject: [PATCH 0721/1127] fix: NPE when checking versions Signed-off-by: Luis Davim --- internal/app/helm_helpers.go | 36 ++++++++++++++++++++----------- internal/app/helm_helpers_test.go | 6 ++++-- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index 13d06a63..0bdd68c4 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -4,19 +4,19 @@ import ( "encoding/json" "errors" "fmt" - "gopkg.in/yaml.v2" "net/url" "path/filepath" "strings" - "github.com/hashicorp/go-version" + "gopkg.in/yaml.v2" "github.com/Praqma/helmsman/internal/gcs" + "github.com/hashicorp/go-version" ) type helmRepo struct { Name string `json:"name"` - Url string `json:"url"` + URL string `json:"url"` } type chartInfo struct { @@ -46,7 +46,7 @@ func getChartInfo(chartName, chartVersion string) (*chartInfo, error) { maybeRepo := filepath.Base(filepath.Dir(chartName)) message := strings.TrimSpace(result.errors) - return nil, fmt.Errorf("Chart [ %s ] version [ %s ] can't be found. Inspection returned error: \"%s\" -- If this is not a local chart, add the repo [ %s ] in your helmRepos stanza.", chartName, chartVersion, message, maybeRepo) + return nil, fmt.Errorf("chart [ %s ] version [ %s ] can't be found. Inspection returned error: \"%s\" -- If this is not a local chart, add the repo [ %s ] in your helmRepos stanza", chartName, chartVersion, message, maybeRepo) } c := &chartInfo{} @@ -54,11 +54,17 @@ func getChartInfo(chartName, chartVersion string) (*chartInfo, error) { log.Fatal(fmt.Sprint(err)) } - constraint, _ := version.NewConstraint(chartVersion) - found, _ := version.NewVersion(c.Version) + constraint, err := version.NewConstraint(chartVersion) + if err != nil { + return nil, err + } + found, err := version.NewVersion(c.Version) + if err != nil { + return nil, err + } if !constraint.Check(found) { - return nil, fmt.Errorf("Chart [ %s ] with version [ %s ] was found with a mismatched version: %s", chartName, chartVersion, c.Version) + return nil, fmt.Errorf("chart [ %s ] with version [ %s ] was found with a mismatched version: %s", chartName, chartVersion, c.Version) } return c, nil @@ -82,12 +88,16 @@ func checkHelmVersion(constraint string) bool { if !strings.HasPrefix(helmVersion, "v") { extractedHelmVersion = strings.TrimSpace(strings.Split(helmVersion, ":")[1]) } - v, _ := version.NewVersion(extractedHelmVersion) - jsonConstraint, _ := version.NewConstraint(constraint) - if jsonConstraint.Check(v) { - return true + v, err := version.NewVersion(extractedHelmVersion) + if err != nil { + return false + } + + jsonConstraint, err := version.NewConstraint(constraint) + if err != nil { + return false } - return false + return jsonConstraint.Check(v) } // helmPluginExists returns true if the plugin is present in the environment and false otherwise. @@ -129,7 +139,7 @@ func addHelmRepos(repos map[string]string) error { } // create map of existing repositories for _, repo := range helmRepos { - existingRepos[repo.Name] = repo.Url + existingRepos[repo.Name] = repo.URL } } else { if !strings.Contains(reposResult.errors, "no repositories to show") { diff --git a/internal/app/helm_helpers_test.go b/internal/app/helm_helpers_test.go index f9ce826e..e08f03ee 100644 --- a/internal/app/helm_helpers_test.go +++ b/internal/app/helm_helpers_test.go @@ -70,8 +70,10 @@ func Test_getChartInfo(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - t.Log(tt.want) - got, _ := getChartInfo(tt.args.r.Chart, tt.args.r.Version) + got, err := getChartInfo(tt.args.r.Chart, tt.args.r.Version) + if err != nil && tt.want != nil { + t.Errorf("getChartInfo() = Unexpected error: %w", err) + } if !reflect.DeepEqual(got, tt.want) { t.Errorf("getChartInfo() = %v, want %v", got, tt.want) } From 4dcc81c9c201b6b46eb85b4a413da0d58938e8f8 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Thu, 19 Nov 2020 23:28:10 +0000 Subject: [PATCH 0722/1127] fix version constaint test --- internal/app/helm_helpers_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/app/helm_helpers_test.go b/internal/app/helm_helpers_test.go index e08f03ee..7a9a107d 100644 --- a/internal/app/helm_helpers_test.go +++ b/internal/app/helm_helpers_test.go @@ -34,7 +34,7 @@ func Test_getChartInfo(t *testing.T) { r: &release{ Name: "release1", Namespace: "namespace", - Version: "1.0.*", + Version: "~>1.0", Chart: "./../../tests/chart-test", Enabled: true, }, From fd4da5f91984fd8daf21b3fb3410ad3fc92a15b0 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Thu, 19 Nov 2020 23:36:04 +0000 Subject: [PATCH 0723/1127] feat: switch to the semver package since it is more flexible --- go.mod | 2 +- go.sum | 4 ++-- internal/app/helm_helpers.go | 10 +++++----- internal/app/helm_helpers_test.go | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index daedaed8..29e4dada 100644 --- a/go.mod +++ b/go.mod @@ -7,10 +7,10 @@ require ( github.com/Azure/azure-pipeline-go v0.1.9 github.com/Azure/azure-storage-blob-go v0.0.0-20181022225951-5152f14ace1c github.com/BurntSushi/toml v0.3.1 + github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515211354-1abdf898e024 github.com/aws/aws-sdk-go v1.26.2 github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 // indirect - github.com/hashicorp/go-version v1.2.0 github.com/imdario/mergo v0.3.8 github.com/joho/godotenv v1.3.0 github.com/kr/pretty v0.1.0 // indirect diff --git a/go.sum b/go.sum index afb32cee..b1a39ca0 100644 --- a/go.sum +++ b/go.sum @@ -9,6 +9,8 @@ github.com/Azure/azure-storage-blob-go v0.0.0-20181022225951-5152f14ace1c h1:Y5u github.com/Azure/azure-storage-blob-go v0.0.0-20181022225951-5152f14ace1c/go.mod h1:oGfmITT1V6x//CswqY2gtAHND+xIP64/qL7a5QJix0Y= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515211354-1abdf898e024 h1:dfZ6RF0UxHqt7xPz0r7h00apsaa6rIrFhT6Xly55Exk= github.com/apsdehal/go-logger v0.0.0-20190515211354-1abdf898e024/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= github.com/aws/aws-sdk-go v1.26.2 h1:MzYLmCeny4bMQcAbYcucIduVZKp0sEf1eRLvHpKI5Is= @@ -40,8 +42,6 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OI github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= -github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ= diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index 0bdd68c4..0202306c 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -10,8 +10,8 @@ import ( "gopkg.in/yaml.v2" + "github.com/Masterminds/semver" "github.com/Praqma/helmsman/internal/gcs" - "github.com/hashicorp/go-version" ) type helmRepo struct { @@ -54,11 +54,11 @@ func getChartInfo(chartName, chartVersion string) (*chartInfo, error) { log.Fatal(fmt.Sprint(err)) } - constraint, err := version.NewConstraint(chartVersion) + constraint, err := semver.NewConstraint(chartVersion) if err != nil { return nil, err } - found, err := version.NewVersion(c.Version) + found, err := semver.NewVersion(c.Version) if err != nil { return nil, err } @@ -88,12 +88,12 @@ func checkHelmVersion(constraint string) bool { if !strings.HasPrefix(helmVersion, "v") { extractedHelmVersion = strings.TrimSpace(strings.Split(helmVersion, ":")[1]) } - v, err := version.NewVersion(extractedHelmVersion) + v, err := semver.NewVersion(extractedHelmVersion) if err != nil { return false } - jsonConstraint, err := version.NewConstraint(constraint) + jsonConstraint, err := semver.NewConstraint(constraint) if err != nil { return false } diff --git a/internal/app/helm_helpers_test.go b/internal/app/helm_helpers_test.go index 7a9a107d..e08f03ee 100644 --- a/internal/app/helm_helpers_test.go +++ b/internal/app/helm_helpers_test.go @@ -34,7 +34,7 @@ func Test_getChartInfo(t *testing.T) { r: &release{ Name: "release1", Namespace: "namespace", - Version: "~>1.0", + Version: "1.0.*", Chart: "./../../tests/chart-test", Enabled: true, }, From bcde1ceac9e5bd143a3da8f65ad31b3ea5d48fba Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Fri, 20 Nov 2020 22:53:58 +0000 Subject: [PATCH 0724/1127] refactor --- internal/app/logging.go | 30 +++++++----------------------- internal/azure/azblob.go | 12 ++++++------ 2 files changed, 13 insertions(+), 29 deletions(-) diff --git a/internal/app/logging.go b/internal/app/logging.go index 7e8b0a1f..e0a4abb4 100644 --- a/internal/app/logging.go +++ b/internal/app/logging.go @@ -8,23 +8,19 @@ import ( ) type Logger struct { + *logger.Logger SlackWebhook string - baseLogger *logger.Logger -} - -func (l *Logger) Info(message string) { - l.baseLogger.Info(message) } func (l *Logger) Debug(message string) { if flags.debug { - l.baseLogger.Debug(message) + l.Logger.Debug(message) } } func (l *Logger) Verbose(message string) { if flags.verbose { - l.baseLogger.Info(message) + l.Logger.Info(message) } } @@ -32,33 +28,21 @@ func (l *Logger) Error(message string) { if _, err := url.ParseRequestURI(l.SlackWebhook); err == nil { notifySlack(message, l.SlackWebhook, true, flags.apply) } - l.baseLogger.Error(message) -} - -func (l *Logger) Errorf(message string, args ...interface{}) { - l.baseLogger.Errorf(message, args...) -} - -func (l *Logger) Warning(message string) { - l.baseLogger.Warning(message) -} - -func (l *Logger) Notice(message string) { - l.baseLogger.Notice(message) + l.Logger.Error(message) } func (l *Logger) Critical(message string) { if _, err := url.ParseRequestURI(l.SlackWebhook); err == nil { notifySlack(message, l.SlackWebhook, true, flags.apply) } - l.baseLogger.Critical(message) + l.Logger.Critical(message) } func (l *Logger) Fatal(message string) { if _, err := url.ParseRequestURI(l.SlackWebhook); err == nil { notifySlack(message, l.SlackWebhook, true, flags.apply) } - l.baseLogger.Fatal(message) + l.Logger.Fatal(message) } func initLogs(verbose bool, noColors bool) { @@ -71,5 +55,5 @@ func initLogs(verbose bool, noColors bool) { if noColors { colors = 0 } - log.baseLogger, _ = logger.New("logger", colors, os.Stdout, logLevel) + log.Logger, _ = logger.New("logger", colors, os.Stdout, logLevel) } diff --git a/internal/azure/azblob.go b/internal/azure/azblob.go index 20d16515..235a8bb9 100644 --- a/internal/azure/azblob.go +++ b/internal/azure/azblob.go @@ -23,7 +23,7 @@ var p pipeline.Pipeline // auth checks for AZURE_STORAGE_ACCOUNT and AZURE_STORAGE_ACCESS_KEY in the environment // if env vars are set, it will authenticate and create an azblob request pipeline // returns false and error message if credentials are not set or are invalid -func auth() (bool, string) { +func auth() error { accountName, accountKey = os.Getenv("AZURE_STORAGE_ACCOUNT"), os.Getenv("AZURE_STORAGE_ACCESS_KEY") if len(accountName) != 0 && len(accountKey) != 0 { log.Println("AZURE_STORAGE_ACCOUNT and AZURE_STORAGE_ACCESS_KEY are set in the environment. They will be used to connect to Azure storage.") @@ -31,19 +31,19 @@ func auth() (bool, string) { credential, err := azblob.NewSharedKeyCredential(accountName, accountKey) if err == nil { p = azblob.NewPipeline(credential, azblob.PipelineOptions{}) - return true, "" + return nil } - return false, err.Error() + return err } - return false, "either the AZURE_STORAGE_ACCOUNT or AZURE_STORAGE_ACCESS_KEY environment variable is not set" + return fmt.Errorf("either the AZURE_STORAGE_ACCOUNT or AZURE_STORAGE_ACCESS_KEY environment variable is not set") } // ReadFile reads a file from storage container and saves it in a desired location. func ReadFile(containerName string, filename string, outFile string, noColors bool) { style = aurora.NewAurora(!noColors) - if ok, err := auth(); !ok { - log.Fatal(style.Bold(style.Red("ERROR: " + err))) + if err := auth(); err != nil { + log.Fatal(style.Bold(style.Red("ERROR: " + err.Error()))) } URL, _ := url.Parse( From 584763758201251ea5693851078b9dd475cf3656 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Mon, 23 Nov 2020 14:55:34 +0000 Subject: [PATCH 0725/1127] prepare release 3.6.0 --- README.md | 2 +- internal/app/main.go | 2 +- release-notes.md | 13 ++++++++++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 8f0eb253..8ba97c8c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.5.1&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.6.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) diff --git a/internal/app/main.go b/internal/app/main.go index 1b957bc8..cd1ef8b3 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -7,7 +7,7 @@ import ( const ( helmBin = "helm" - appVersion = "v3.5.1" + appVersion = "v3.6.0" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index 2d0e040f..4b48d7e0 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,9 +1,16 @@ -# v3.5.1 +# v3.6.0 If you migrating from Helmsman v1.x, it is recommended you read the [migration guide](https://github.com/Praqma/helmsman/blob/master/docs/how_to/misc/migrate_to_3.md) before using this release. > Starting from Helmsman v3.0.0 GA release, support for Helmsman v1.x will be limited to bug fixes. -# Fixes and improvements: +## New failures -- Add retryExec func for helm diff; PR #542 +- Lifecycle hooks can now be shell commands or script in addition to k8s resources (#543) +- The Helm release secrets are annotated with the result from the lifecycle hooks + +## Fixes and improvements + +- Code cleanup +- Performance improvements (#543; #545; #547) +- Fixed a bug when the chart is renamed (#546) From 51f041fb21efacb177d69dd72bc3554d442a395f Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Mon, 23 Nov 2020 15:08:59 +0000 Subject: [PATCH 0726/1127] fix: autocorrect issue --- release-notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release-notes.md b/release-notes.md index 4b48d7e0..833b6fff 100644 --- a/release-notes.md +++ b/release-notes.md @@ -4,7 +4,7 @@ If you migrating from Helmsman v1.x, it is recommended you read the [migration g > Starting from Helmsman v3.0.0 GA release, support for Helmsman v1.x will be limited to bug fixes. -## New failures +## New features - Lifecycle hooks can now be shell commands or script in addition to k8s resources (#543) - The Helm release secrets are annotated with the result from the lifecycle hooks From ad3c2172f7c729b8f71d33265450766d6b80c561 Mon Sep 17 00:00:00 2001 From: John Montroy Date: Mon, 23 Nov 2020 17:34:29 -0500 Subject: [PATCH 0727/1127] fix: isValidFile error for hooks should not lead to a file resolve. --- internal/app/release.go | 4 ++-- internal/app/release_files.go | 4 ++-- internal/app/state_files.go | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/app/release.go b/internal/app/release.go index 4288bfcb..28b3992c 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -485,13 +485,13 @@ func (r *release) getHookCommands(hookType, ns string) []hookCmd { var cmds []hookCmd if _, ok := r.Hooks[hookType]; ok { hook := r.Hooks[hookType].(string) - if err := isValidFile(hook, []string{".yaml", ".yml", ".json"}); err != nil { + if err := isValidFile(hook, []string{".yaml", ".yml", ".json"}); err == nil { cmd := kubectl([]string{"apply", "-n", ns, "-f", hook, flags.getKubeDryRunFlag("apply")}, "Apply "+hook+" manifest "+hookType) cmds = append(cmds, hookCmd{Command: cmd, Type: hookType}) if wait, waitCmds := r.shouldWaitForHook(hook, hookType, ns); wait { cmds = append(cmds, waitCmds...) } - } else { + } else { // shell hook args := strings.Fields(hook) cmds = append(cmds, hookCmd{ Command: Command{ diff --git a/internal/app/release_files.go b/internal/app/release_files.go index 21f9990b..13b08c6d 100644 --- a/internal/app/release_files.go +++ b/internal/app/release_files.go @@ -19,7 +19,7 @@ func (r *release) substituteVarsInStaticFiles() { for key, val := range r.Hooks { if key != "deleteOnSuccess" && key != "successTimeout" && key != "successCondition" { hook := val.(string) - if err := isValidFile(hook, []string{".yaml", ".yml"}); err != nil { + if err := isValidFile(hook, []string{".yaml", ".yml"}); err == nil { r.Hooks[key] = substituteVarsInYaml(hook) } } @@ -45,7 +45,7 @@ func (r *release) resolvePaths(dir, downloadDest string) { for key, val := range r.Hooks { if key != "deleteOnSuccess" && key != "successTimeout" && key != "successCondition" { hook := val.(string) - if err := isValidFile(hook, []string{".yaml", ".yml", ".json"}); err != nil { + if err := isValidFile(hook, []string{".yaml", ".yml", ".json"}); err == nil { r.Hooks[key], _ = resolveOnePath(hook, dir, downloadDest) } } diff --git a/internal/app/state_files.go b/internal/app/state_files.go index 97ebff8e..f9fc3a31 100644 --- a/internal/app/state_files.go +++ b/internal/app/state_files.go @@ -164,7 +164,7 @@ func (s *state) expand(relativeToFile string) { for key, val := range s.Settings.GlobalHooks { if key != "deleteOnSuccess" && key != "successTimeout" && key != "successCondition" { hook := val.(string) - if err := isValidFile(hook, []string{".yaml", ".yml"}); err != nil { + if err := isValidFile(hook, []string{".yaml", ".yml"}); err == nil { s.Settings.GlobalHooks[key] = substituteVarsInYaml(hook) } } @@ -180,7 +180,7 @@ func (s *state) expand(relativeToFile string) { for key, val := range s.Settings.GlobalHooks { if key != "deleteOnSuccess" && key != "successTimeout" && key != "successCondition" { hook := val.(string) - if err := isValidFile(hook, []string{".yaml", ".yml", ".json"}); err != nil { + if err := isValidFile(hook, []string{".yaml", ".yml", ".json"}); err == nil { s.Settings.GlobalHooks[key], _ = resolveOnePath(hook, dir, downloadDest) } } From 1f49672032a5231b7a47d6c230775f5ca683ad3d Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Tue, 24 Nov 2020 10:45:10 +0100 Subject: [PATCH 0728/1127] Initialize namespaces defined without any properties on init --- internal/app/cli.go | 1 + internal/app/state.go | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/internal/app/cli.go b/internal/app/cli.go index ed355508..b58a12c6 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -245,6 +245,7 @@ func (c *cli) readState(s *state) { } s.setDefaults() + s.initializeNamespaces() s.disableUntargetedApps(c.group, c.target) if len(c.target) > 0 && len(s.TargetMap) == 0 { diff --git a/internal/app/state.go b/internal/app/state.go index b41dc349..b13cc232 100644 --- a/internal/app/state.go +++ b/internal/app/state.go @@ -68,6 +68,15 @@ func (s *state) setDefaults() { } } +func (s *state) initializeNamespaces() { + for nsName, ns := range s.Namespaces { + if ns == nil { + ns = &namespace{} + s.Namespaces[nsName] = ns + } + } +} + // validate validates that the values specified in the desired state are valid according to the desired state spec. // check https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md for the detailed specification func (s *state) validate() error { From 924ba97ca0e02af79b1ecd95843b33802bb83f87 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Tue, 24 Nov 2020 12:26:44 +0100 Subject: [PATCH 0729/1127] Remove redundant variable for initializing namespaces step --- internal/app/state.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/app/state.go b/internal/app/state.go index b13cc232..72fa0f7d 100644 --- a/internal/app/state.go +++ b/internal/app/state.go @@ -71,8 +71,7 @@ func (s *state) setDefaults() { func (s *state) initializeNamespaces() { for nsName, ns := range s.Namespaces { if ns == nil { - ns = &namespace{} - s.Namespaces[nsName] = ns + s.Namespaces[nsName] = &namespace{} } } } From decbfbc79312a3d863661d4d1e60701dd821174a Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 24 Nov 2020 11:38:40 +0000 Subject: [PATCH 0730/1127] prepare release 3.6.1 --- README.md | 2 +- internal/app/main.go | 2 +- internal/app/state.go | 3 +++ release-notes.md | 12 +++--------- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 8ba97c8c..77457dfd 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.6.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.6.1&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) diff --git a/internal/app/main.go b/internal/app/main.go index cd1ef8b3..bc2a77f9 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -7,7 +7,7 @@ import ( const ( helmBin = "helm" - appVersion = "v3.6.0" + appVersion = "v3.6.1" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/internal/app/state.go b/internal/app/state.go index 72fa0f7d..9d587f11 100644 --- a/internal/app/state.go +++ b/internal/app/state.go @@ -308,6 +308,9 @@ func (s *state) disableUntargetedApps(groups, targets []string) { } } + if s.Namespaces == nil || len(s.Namespaces) == 0 { + return + } for nsName, ns := range s.Namespaces { if _, ok := namespaces[nsName]; !ok { ns.Disable() diff --git a/release-notes.md b/release-notes.md index 833b6fff..73fdb5c8 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,16 +1,10 @@ -# v3.6.0 +# v3.6.1 If you migrating from Helmsman v1.x, it is recommended you read the [migration guide](https://github.com/Praqma/helmsman/blob/master/docs/how_to/misc/migrate_to_3.md) before using this release. > Starting from Helmsman v3.0.0 GA release, support for Helmsman v1.x will be limited to bug fixes. -## New features - -- Lifecycle hooks can now be shell commands or script in addition to k8s resources (#543) -- The Helm release secrets are annotated with the result from the lifecycle hooks - ## Fixes and improvements -- Code cleanup -- Performance improvements (#543; #545; #547) -- Fixed a bug when the chart is renamed (#546) +- Fixed NPE when no config is passed to the NSs (#551) +- Fixed wrong detection of executable hook (#549) From 82e4f16e358ed6de61c1ef450391c6aab8f621d7 Mon Sep 17 00:00:00 2001 From: John Montroy Date: Tue, 24 Nov 2020 09:51:07 -0500 Subject: [PATCH 0731/1127] feat: commands should optionally log stdout on failures. --- internal/app/plan.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/app/plan.go b/internal/app/plan.go index a1826336..37a012f9 100644 --- a/internal/app/plan.go +++ b/internal/app/plan.go @@ -186,6 +186,7 @@ func execOne(cmd Command, targetRelease *release) error { log.Notice(cmd.Description) result := cmd.Exec() if result.code != 0 { + log.Verbose(result.output) errorMsg := result.errors if !flags.verbose { errorMsg = strings.Split(result.errors, "---")[0] From 660459148d0fabbf52c51763b10c579f2edd1c83 Mon Sep 17 00:00:00 2001 From: John Montroy Date: Tue, 24 Nov 2020 13:53:26 -0500 Subject: [PATCH 0732/1127] feat: Helm test command should run first after the release is installed. --- internal/app/release.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/app/release.go b/internal/app/release.go index 28b3992c..896006c1 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -126,7 +126,7 @@ func (r *release) validate(appLabel string, seen map[string]map[string]bool, s * // testRelease creates a Helm command to test a particular release. func (r *release) test(afterCommands *[]hookCmd) { cmd := helmCmd(r.getHelmArgsFor("test"), "Running tests for release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") - *afterCommands = append(*afterCommands, hookCmd{Command: cmd, Type: test}) + *afterCommands = append([]hookCmd{{Command: cmd, Type: test}}, *afterCommands...) } // installRelease creates a Helm command to install a particular release in a particular namespace using a particular Tiller. From 52733a70aa77e5805dcd99847132581931cad93c Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 24 Nov 2020 13:26:01 +0000 Subject: [PATCH 0733/1127] test: add test cases for readState Signed-off-by: Luis Davim --- examples/minimal-example.toml | 4 +- examples/minimal-example.yaml | 4 +- internal/app/cli.go | 32 +++------ internal/app/cli_test.go | 126 +++++++++++++++++++++++++++------- internal/app/command_test.go | 38 ++++++++++ internal/app/kube_helpers.go | 2 +- internal/app/main.go | 16 ++++- internal/app/namespace.go | 10 +-- internal/app/state.go | 5 ++ internal/app/utils.go | 5 +- 10 files changed, 184 insertions(+), 58 deletions(-) diff --git a/examples/minimal-example.toml b/examples/minimal-example.toml index 245f2036..bec47cff 100644 --- a/examples/minimal-example.toml +++ b/examples/minimal-example.toml @@ -3,8 +3,8 @@ ## For the full config spec and options, check https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md [helmRepos] - jenkins = https://charts.jenkins.io - center = https://repo.chartcenter.io + jenkins = "https://charts.jenkins.io" + center = "https://repo.chartcenter.io" [namespaces] [namespaces.staging] diff --git a/examples/minimal-example.yaml b/examples/minimal-example.yaml index 9866a92d..fae065c7 100644 --- a/examples/minimal-example.yaml +++ b/examples/minimal-example.yaml @@ -2,8 +2,8 @@ ## It will use your current kube context and will deploy Tiller without RBAC service account. ## For the full config spec and options, check https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md helmRepos: - jenkins = https://charts.jenkins.io - center = https://repo.chartcenter.io + jenkins: https://charts.jenkins.io + center: https://repo.chartcenter.io namespaces: staging: diff --git a/internal/app/cli.go b/internal/app/cli.go index b58a12c6..947b926f 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -164,7 +164,7 @@ func (c *cli) parse() { os.Setenv("KUBECONFIG", c.kubeconfig) } - if !ToolExists("kubectl") { + if !ToolExists(kubectlBin) { log.Fatal("kubectl is not installed/configured correctly. Aborting!") } @@ -191,13 +191,13 @@ func (c *cli) parse() { } // readState gets the desired state from files -func (c *cli) readState(s *state) { +func (c *cli) readState(s *state) error { // read the env file if len(c.envFiles) == 0 { if _, err := os.Stat(".env"); err == nil { err = godotenv.Load() if err != nil { - log.Fatal("Error loading .env file") + return fmt.Errorf("error loading .env file: %w", err) } } } @@ -205,7 +205,7 @@ func (c *cli) readState(s *state) { for _, e := range c.envFiles { err := godotenv.Load(e) if err != nil { - log.Fatal("Error loading " + e + " env file") + return fmt.Errorf("error loading %s env file: %w", e, err) } } @@ -221,49 +221,38 @@ func (c *cli) readState(s *state) { if result { log.Info(msg) } else { - log.Fatal(msg) + return fmt.Errorf(msg) } // Merge Apps that already existed in the state for appName, app := range fileState.Apps { if _, ok := s.Apps[appName]; ok { if err := mergo.Merge(s.Apps[appName], app, mergo.WithAppendSlice, mergo.WithOverride); err != nil { - log.Fatal("Failed to merge " + appName + " from desired state file" + f) + return fmt.Errorf("failed to merge %s from desired state file %s: %w", appName, f, err) } } } // Merge the remaining Apps if err := mergo.Merge(&s.Apps, &fileState.Apps); err != nil { - log.Fatal("Failed to merge desired state file" + f) + return fmt.Errorf("failed to merge desired state file %s: %w", f, err) } // All the apps are already merged, make fileState.Apps empty to avoid conflicts in the final merge fileState.Apps = make(map[string]*release) if err := mergo.Merge(s, &fileState, mergo.WithAppendSlice, mergo.WithOverride); err != nil { - log.Fatal("Failed to merge desired state file" + f) + return fmt.Errorf("failed to merge desired state file %s: %w", f, err) } } - s.setDefaults() - s.initializeNamespaces() + s.init() // Set defaults s.disableUntargetedApps(c.group, c.target) - if len(c.target) > 0 && len(s.TargetMap) == 0 { - log.Info("No apps defined with -target flag were found, exiting") - os.Exit(0) - } - - if len(c.group) > 0 && len(s.TargetMap) == 0 { - log.Info("No apps defined with -group flag were found, exiting") - os.Exit(0) - } - if !c.skipValidation { // validate the desired state content if len(c.files) > 0 { log.Info("Validating desired state definition") if err := s.validate(); err != nil { // syntax validation - log.Fatal(err.Error()) + return err } } } else { @@ -273,6 +262,7 @@ func (c *cli) readState(s *state) { if c.debug { s.print() } + return nil } // getDryRunFlags returns dry-run flag diff --git a/internal/app/cli_test.go b/internal/app/cli_test.go index 04b381cd..79680b1f 100644 --- a/internal/app/cli_test.go +++ b/internal/app/cli_test.go @@ -7,39 +7,115 @@ var _ = func() bool { return true }() -func Test_toolExists(t *testing.T) { - type args struct { - tool string +func Test_readState(t *testing.T) { + type result struct { + numApps int + numNSs int + numEnabledApps int + numEnabledNSs int } tests := []struct { - name string - args args - want bool + name string + flags cli + want result }{ { - name: "test case 1 -- checking helm exists.", - args: args{ - tool: helmBin, - }, - want: true, - }, { - name: "test case 2 -- checking kubectl exists.", - args: args{ - tool: "kubectl", - }, - want: true, - }, { - name: "test case 3 -- checking nonExistingTool exists.", - args: args{ - tool: "nonExistingTool", - }, - want: false, + name: "yaml minimal example; no validation", + flags: cli{ + files: stringArray([]string{"../../examples/minimal-example.yaml"}), + skipValidation: true, + }, + want: result{ + numApps: 2, + numNSs: 1, + numEnabledApps: 2, + numEnabledNSs: 1, + }, + }, + { + name: "toml minimal example; no validation", + flags: cli{ + files: stringArray([]string{"../../examples/minimal-example.toml"}), + skipValidation: true, + }, + want: result{ + numApps: 2, + numNSs: 1, + numEnabledApps: 2, + numEnabledNSs: 1, + }, + }, + { + name: "yaml minimal example; no validation with bad target", + flags: cli{ + target: stringArray([]string{"foo"}), + files: stringArray([]string{"../../examples/minimal-example.yaml"}), + skipValidation: true, + }, + want: result{ + numApps: 2, + numNSs: 1, + numEnabledApps: 0, + numEnabledNSs: 0, + }, + }, + { + name: "yaml minimal example; no validation; target jenkins", + flags: cli{ + target: stringArray([]string{"jenkins"}), + files: stringArray([]string{"../../examples/minimal-example.yaml"}), + skipValidation: true, + }, + want: result{ + numApps: 2, + numNSs: 1, + numEnabledApps: 1, + numEnabledNSs: 1, + }, + }, + { + name: "yaml and toml minimal examples merged; no validation", + flags: cli{ + files: stringArray([]string{"../../examples/minimal-example.yaml", "../../examples/minimal-example.toml"}), + skipValidation: true, + }, + want: result{ + numApps: 2, + numNSs: 1, + numEnabledApps: 2, + numEnabledNSs: 1, + }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := ToolExists(tt.args.tool); got != tt.want { - t.Errorf("toolExists() = %v, want %v", got, tt.want) + s := state{} + if err := tt.flags.readState(&s); err != nil { + t.Errorf("readState() = Unexpected error: %v", err) + } + if len(s.Apps) != tt.want.numApps { + t.Errorf("readState() = app count mismatch: want: %d, got: %d", tt.want.numApps, len(s.Apps)) + } + if len(s.Namespaces) != tt.want.numNSs { + t.Errorf("readState() = NS count mismatch: want: %d, got: %d", tt.want.numNSs, len(s.Namespaces)) + } + + var enabledApps, enabledNSs int + for _, a := range s.Apps { + if !a.disabled { + enabledApps++ + } + } + if enabledApps != tt.want.numEnabledApps { + t.Errorf("readState() = app count mismatch: want: %d, got: %d", tt.want.numEnabledApps, enabledApps) + } + for _, n := range s.Namespaces { + if !n.disabled { + enabledNSs++ + } + } + if enabledNSs != tt.want.numEnabledNSs { + t.Errorf("readState() = app count mismatch: want: %d, got: %d", tt.want.numEnabledNSs, enabledNSs) } }) } diff --git a/internal/app/command_test.go b/internal/app/command_test.go index a0ddfcd1..e3ca5d76 100644 --- a/internal/app/command_test.go +++ b/internal/app/command_test.go @@ -5,6 +5,44 @@ import ( "testing" ) +func Test_toolExists(t *testing.T) { + type args struct { + tool string + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "test case 1 -- checking helm exists.", + args: args{ + tool: helmBin, + }, + want: true, + }, { + name: "test case 2 -- checking kubectl exists.", + args: args{ + tool: kubectlBin, + }, + want: true, + }, { + name: "test case 3 -- checking nonExistingTool exists.", + args: args{ + tool: "nonExistingTool", + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ToolExists(tt.args.tool); got != tt.want { + t.Errorf("toolExists() = %v, want %v", got, tt.want) + } + }) + } +} + func Test_command_exec(t *testing.T) { type fields struct { Cmd string diff --git a/internal/app/kube_helpers.go b/internal/app/kube_helpers.go index 9c1a825c..bfcc2c97 100644 --- a/internal/app/kube_helpers.go +++ b/internal/app/kube_helpers.go @@ -38,7 +38,7 @@ func addNamespaces(s *state) { // kubectl prepares a kubectl command to be executed func kubectl(args []string, desc string) Command { return Command{ - Cmd: "kubectl", + Cmd: kubectlBin, Args: args, Description: desc, } diff --git a/internal/app/main.go b/internal/app/main.go index bc2a77f9..fc09f83c 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -7,6 +7,7 @@ import ( const ( helmBin = "helm" + kubectlBin = "kubectl" appVersion = "v3.6.1" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" @@ -35,7 +36,20 @@ func Main() { defer s.cleanup() } - flags.readState(&s) + if err := flags.readState(&s); err != nil { + log.Fatal(err.Error()) + } + + if len(flags.target) > 0 && len(s.TargetMap) == 0 { + log.Info("No apps defined with -target flag were found, exiting") + os.Exit(0) + } + + if len(flags.group) > 0 && len(s.TargetMap) == 0 { + log.Info("No apps defined with -group flag were found, exiting") + os.Exit(0) + } + log.SlackWebhook = s.Settings.SlackWebhook settings = &s.Settings diff --git a/internal/app/namespace.go b/internal/app/namespace.go index 27fb5e9e..5086f25f 100644 --- a/internal/app/namespace.go +++ b/internal/app/namespace.go @@ -52,9 +52,11 @@ func (n *namespace) Disable() { // print prints the namespace func (n *namespace) print() { - fmt.Println("") - fmt.Println("\tprotected : ", n.Protected) - fmt.Println("\tlabels : ") + fmt.Println("\tprotected: ", n.Protected) + fmt.Println("\tdisabled: ", n.disabled) + fmt.Println("\tlabels:") printMap(n.Labels, 2) - fmt.Println("------------------- ") + fmt.Println("\tannotations:") + printMap(n.Annotations, 2) + fmt.Println("-------------------") } diff --git a/internal/app/state.go b/internal/app/state.go index 9d587f11..aaf40ff4 100644 --- a/internal/app/state.go +++ b/internal/app/state.go @@ -44,6 +44,11 @@ type state struct { ChartInfo map[string]map[string]*chartInfo } +func (s *state) init() { + s.setDefaults() + s.initializeNamespaces() +} + func (s *state) setDefaults() { if s.Settings.StorageBackend != "" { os.Setenv("HELM_DRIVER", s.Settings.StorageBackend) diff --git a/internal/app/utils.go b/internal/app/utils.go index 92ebf8a3..697b7287 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -31,8 +31,9 @@ func printMap(m map[string]string, indent int) { // printObjectMap prints to the console any map of string keys and object values. func printNamespacesMap(m map[string]*namespace) { - for key, value := range m { - fmt.Println(key, " : protected = ", value) + for name, ns := range m { + fmt.Println(name, ":") + ns.print() } } From 453ab712efb61c2717e79bdfb32edbcd8072fab5 Mon Sep 17 00:00:00 2001 From: John Montroy Date: Wed, 25 Nov 2020 12:52:04 -0500 Subject: [PATCH 0734/1127] prepare release 3.6.2 --- .version | 2 +- README.md | 6 +++--- internal/app/main.go | 2 +- release-notes.md | 7 ++++--- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.version b/.version index d0ec26c7..b9bf9ed4 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.4.6 +v3.6.2 diff --git a/README.md b/README.md index 77457dfd..ba1c30ac 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.6.1&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.6.2&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -61,9 +61,9 @@ Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the d ```sh # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.4.2/helmsman_3.4.2_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.2/helmsman_3.6.2_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.4.2/helmsman_3.4.2_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.2/helmsman_3.6.2_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/internal/app/main.go b/internal/app/main.go index fc09f83c..fc579564 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.6.1" + appVersion = "v3.6.2" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index 73fdb5c8..4946134d 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,4 +1,4 @@ -# v3.6.1 +# v3.6.2 If you migrating from Helmsman v1.x, it is recommended you read the [migration guide](https://github.com/Praqma/helmsman/blob/master/docs/how_to/misc/migrate_to_3.md) before using this release. @@ -6,5 +6,6 @@ If you migrating from Helmsman v1.x, it is recommended you read the [migration g ## Fixes and improvements -- Fixed NPE when no config is passed to the NSs (#551) -- Fixed wrong detection of executable hook (#549) +- Commands will optionally (Verbose) log stdout on non-zero exit code (#554) +- `helm test` command will run immediately after install / upgrade (before hooks and labelling). (#553) +- Add test cases for `readState`; improved error handling and small order-of-operations refactor. (#552) From 97b79f2ee2298dd754c137fde815978141e0ed0c Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Thu, 26 Nov 2020 00:37:06 +0000 Subject: [PATCH 0735/1127] fix: 555 --show-diff missing on chart version bump --- internal/app/decision_maker.go | 40 ++++++++++++++++++++++++---------- internal/app/release.go | 10 +++------ 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index d42c0060..61d68a52 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -1,6 +1,7 @@ package app import ( + "fmt" "regexp" "strings" "sync" @@ -118,7 +119,9 @@ func (cs *currentState) decide(r *release, n *namespace, p *plan, c *chartInfo) if ok := cs.releaseExists(r, helmStatusDeployed); ok { if !r.isProtected(cs, n) { - cs.inspectUpgradeScenario(r, p, c) // upgrade or move + if err := cs.inspectUpgradeScenario(r, p, c); err != nil { // upgrade or move + log.Fatal(err.Error()) + } } else { p.addDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ "you remove its protection.", r.Priority, noop) @@ -286,14 +289,14 @@ func (cs *currentState) cleanUntrackedReleases(s *state, p *plan) { // it will be uninstalled and installed in the same namespace using the new chart. // - If the release is NOT in the same namespace specified in the input, // it will be purge deleted and installed in the new namespace. -func (cs *currentState) inspectUpgradeScenario(r *release, p *plan, c *chartInfo) { +func (cs *currentState) inspectUpgradeScenario(r *release, p *plan, c *chartInfo) error { if c == nil || c.Name == "" || c.Version == "" { - return + return nil } rs, ok := cs.releases[r.key()] if !ok { - return + return nil } if r.Namespace != rs.Namespace { @@ -305,31 +308,46 @@ func (cs *currentState) inspectUpgradeScenario(r *release, p *plan, c *chartInfo p.addDecision("WARNING: moving release [ "+r.Name+" ] from [ "+rs.Namespace+" ] to [ "+r.Namespace+ " ] might not correctly connect existing volumes. Check https://github.com/Praqma/helmsman/blob/master/docs/how_to/apps/moving_across_namespaces.md#note-on-persistent-volumes"+ " for details if this release uses PV and PVC.", r.Priority, change) - return + return nil } r.Version = c.Version + // Chart changed if c.Name != rs.getChartName() && flags.renameReplace { - // Chart changed rs.uninstall(p) r.install(p) p.addDecision("Release [ "+r.Name+" ] is desired to use a new chart [ "+r.Chart+ " ]. Delete of the current release will be planned and new chart will be installed in namespace [ "+ r.Namespace+" ]", r.Priority, change) - return + return nil } + // Version changed or forced upgrade if flags.alwaysUpgrade || r.Version != rs.getChartVersion() || c.Name != rs.getChartName() { - // Version changed + if flags.verbose || flags.showDiff { + if diff, err := r.diff(); err != nil { + log.Error(err.Error()) + } else if diff != "" { + fmt.Println(diff) + } + } r.upgrade(p) p.addDecision("Release [ "+r.Name+" ] will be upgraded", r.Priority, change) - return + return nil } - if diff := r.diff(); diff != "" { + + if diff, err := r.diff(); err != nil { + return err + } else if diff != "" { + if flags.verbose || flags.showDiff { + fmt.Println(diff) + } r.upgrade(p) p.addDecision("Release [ "+r.Name+" ] will be updated", r.Priority, change) - return + return nil } + p.addDecision("Release [ "+r.Name+" ] installed and up-to-date", r.Priority, noop) + return nil } diff --git a/internal/app/release.go b/internal/app/release.go index 896006c1..834704db 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -160,7 +160,7 @@ func (r *release) uninstall(p *plan, optionalNamespace ...string) { } // diffRelease diffs an existing release with the specified values.yaml -func (r *release) diff() string { +func (r *release) diff() (string, error) { colorFlag := "" diffContextFlag := []string{} suppressDiffSecretsFlag := "--suppress-secrets" @@ -175,14 +175,10 @@ func (r *release) diff() string { result := cmd.RetryExec(3) if result.code != 0 { - log.Fatal(fmt.Sprintf("Command returned with exit code: %d. And error message: %s ", result.code, result.errors)) - } else { - if (flags.verbose || flags.showDiff) && result.output != "" { - fmt.Println(result.output) - } + return "", fmt.Errorf("Command returned with exit code: %d. And error message: %s ", result.code, result.errors) } - return result.output + return result.output, nil } // upgradeRelease upgrades an existing release with the specified values.yaml From 4697bb6fbd81e42f43b7f02405e80e848bc4aa58 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Thu, 26 Nov 2020 00:47:39 +0000 Subject: [PATCH 0736/1127] fix: dont check for updates on disabled releases Signed-off-by: Luis Davim --- internal/app/release.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/app/release.go b/internal/app/release.go index 834704db..54e09005 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -47,6 +47,9 @@ func (r *release) Disable() { // isReleaseConsideredToRun checks if a release is being targeted for operations as specified by user cmd flags (--group or --target) func (r *release) isConsideredToRun() bool { + if r == nil { + return false + } return !r.disabled } @@ -397,6 +400,9 @@ func (r *release) getHelmArgsFor(action string, optionalNamespaceOverride ...str } func (r *release) checkChartDepUpdate() { + if !r.isConsideredToRun() { + return + } if flags.updateDeps && isLocalChart(r.Chart) { if err := updateChartDep(r.Chart); err != nil { log.Fatal("helm dependency update failed: " + err.Error()) From d5b076f12fd3081c9fdb69c7af42c5b6f1390b1c Mon Sep 17 00:00:00 2001 From: Michal Keder Date: Tue, 1 Dec 2020 13:53:34 +0100 Subject: [PATCH 0737/1127] Fix notifySlack segmentation fault --- internal/app/utils.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/app/utils.go b/internal/app/utils.go index 697b7287..13e85b35 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -333,6 +333,7 @@ func notifySlack(content string, url string, failure bool, executing bool) bool req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr)) if err != nil { log.Errorf("Failed to send slack message: %v", err) + return false } req.Header.Set("Content-Type", "application/json") @@ -340,6 +341,7 @@ func notifySlack(content string, url string, failure bool, executing bool) bool resp, err := client.Do(req) if err != nil { log.Errorf("Failed to send notification to slack: %v", err) + return false } defer resp.Body.Close() From 551ea3b5564d49531302d1ace85c00779fde7b7e Mon Sep 17 00:00:00 2001 From: Michal Keder Date: Tue, 1 Dec 2020 14:56:46 +0100 Subject: [PATCH 0738/1127] Clarify diff error message --- internal/app/release.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/app/release.go b/internal/app/release.go index 54e09005..ae947c18 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -178,7 +178,7 @@ func (r *release) diff() (string, error) { result := cmd.RetryExec(3) if result.code != 0 { - return "", fmt.Errorf("Command returned with exit code: %d. And error message: %s ", result.code, result.errors) + return "", fmt.Errorf("Diff for release [%s] in namespace [%s] returned with exit code: %d. And error message: %s ", r.Name, r.Namespace, result.code, result.errors) } return result.output, nil From 594caf93f113a55f49535d418a98b545c5b71f35 Mon Sep 17 00:00:00 2001 From: Michal Keder Date: Wed, 9 Dec 2020 19:53:40 +0100 Subject: [PATCH 0739/1127] Revert "Merge pull request #560 from mickeder/diff-error-more-precise" This reverts commit 9e0a20ee3d5979ac06abb4a6134da0276277bec9, reversing changes made to 7ad4b1392979be29b8bb6efb186b487b35859af8. --- internal/app/release.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/app/release.go b/internal/app/release.go index ae947c18..54e09005 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -178,7 +178,7 @@ func (r *release) diff() (string, error) { result := cmd.RetryExec(3) if result.code != 0 { - return "", fmt.Errorf("Diff for release [%s] in namespace [%s] returned with exit code: %d. And error message: %s ", r.Name, r.Namespace, result.code, result.errors) + return "", fmt.Errorf("Command returned with exit code: %d. And error message: %s ", result.code, result.errors) } return result.output, nil From a8b69b9c324e5a01816ae806125d1a0f1e07c953 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Fri, 11 Dec 2020 11:44:36 +0000 Subject: [PATCH 0740/1127] chore: code linting --- internal/app/decision_maker.go | 4 ++-- internal/app/helm_helpers.go | 4 ++-- internal/app/main.go | 2 +- internal/app/release_test.go | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index 61d68a52..dad224f6 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -19,8 +19,8 @@ func newCurrentState() *currentState { } } -// buildState builds the currentState map containing information about all releases existing in a k8s cluster -func buildState(s *state) *currentState { +// getCurrentState builds the currentState map containing information about all releases existing in a k8s cluster +func (s *state) getCurrentState() *currentState { log.Info("Acquiring current Helm state from cluster") cs := newCurrentState() diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index 0202306c..95c8c890 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -182,8 +182,8 @@ func addHelmRepos(repos map[string]string) error { cmd := helmCmd(concat([]string{"repo", "add", repoAddFlags, repoName, repoLink}, basicAuthArgs), "Adding helm repository [ "+repoName+" ]") // check current repository against existing repositories map in order to make sure it's missing and needs to be added - if existingRepoUrl, ok := existingRepos[repoName]; ok { - if repoLink == existingRepoUrl { + if existingRepoURL, ok := existingRepos[repoName]; ok { + if repoLink == existingRepoURL { continue } } diff --git a/internal/app/main.go b/internal/app/main.go index fc579564..8d7f5e0a 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -103,7 +103,7 @@ func Main() { } log.Info("Preparing plan") - cs := buildState(&s) + cs := s.getCurrentState() p := cs.makePlan(&s) if !flags.keepUntrackedReleases { cs.cleanUntrackedReleases(&s, p) diff --git a/internal/app/release_test.go b/internal/app/release_test.go index 682d21a1..6fdbdb77 100644 --- a/internal/app/release_test.go +++ b/internal/app/release_test.go @@ -9,7 +9,7 @@ func Test_release_validate(t *testing.T) { Metadata: make(map[string]string), Certificates: make(map[string]string), Settings: (config{}), - Namespaces: map[string]*namespace{"namespace": &namespace{false, limits{}, make(map[string]string), make(map[string]string), "as{}, false}}, + Namespaces: map[string]*namespace{"namespace": {false, limits{}, make(map[string]string), make(map[string]string), "as{}, false}}, HelmRepos: make(map[string]string), Apps: make(map[string]*release), } From e93aee83769ffbfd1bcb68ead23616101718202f Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Mon, 4 Jan 2021 14:43:36 +0100 Subject: [PATCH 0741/1127] Accept nil targetRelease on executing command, just omit hooks part --- internal/app/plan.go | 58 +++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/internal/app/plan.go b/internal/app/plan.go index 37a012f9..c9a9f6f5 100644 --- a/internal/app/plan.go +++ b/internal/app/plan.go @@ -135,47 +135,45 @@ func releaseWithHooks(cmd orderedCommand, storageBackend string, wg *sync.WaitGr wg.Done() <-sem }() - if cmd.targetRelease == nil && !flags.destroy { - err := fmt.Errorf("nil target release") - errors <- err - log.Verbose(err.Error()) - return - } var annotations []string - for _, c := range cmd.beforeCommands { - if err := execOne(c.Command, cmd.targetRelease); err != nil { - errors <- err + if cmd.targetRelease != nil && !flags.destroy { + for _, c := range cmd.beforeCommands { + if err := execOne(c.Command, cmd.targetRelease); err != nil { + errors <- err + if key, err := c.getAnnotationKey(); err != nil { + annotations = append(annotations, key+"=failed") + } + log.Verbose(err.Error()) + return + } if key, err := c.getAnnotationKey(); err != nil { - annotations = append(annotations, key+"=failed") + annotations = append(annotations, key+"=ok") } - log.Verbose(err.Error()) - return } - if key, err := c.getAnnotationKey(); err != nil { - annotations = append(annotations, key+"=ok") + if !flags.dryRun && !flags.destroy { + defer func() { + cmd.targetRelease.mark(storageBackend) + cmd.targetRelease.annotate(storageBackend, annotations...) + }() } } - if !flags.dryRun && !flags.destroy { - defer func() { - cmd.targetRelease.mark(storageBackend) - cmd.targetRelease.annotate(storageBackend, annotations...) - }() - } if err := execOne(cmd.Command, cmd.targetRelease); err != nil { errors <- err log.Verbose(err.Error()) return } - for _, c := range cmd.afterCommands { - if err := execOne(c.Command, cmd.targetRelease); err != nil { - errors <- err - if key, err := c.getAnnotationKey(); err != nil { - annotations = append(annotations, key+"=failed") - } - log.Verbose(err.Error()) - } else { - if key, err := c.getAnnotationKey(); err != nil { - annotations = append(annotations, key+"=ok") + if cmd.targetRelease != nil && !flags.destroy { + for _, c := range cmd.afterCommands { + if err := execOne(c.Command, cmd.targetRelease); err != nil { + errors <- err + if key, err := c.getAnnotationKey(); err != nil { + annotations = append(annotations, key+"=failed") + } + log.Verbose(err.Error()) + } else { + if key, err := c.getAnnotationKey(); err != nil { + annotations = append(annotations, key+"=ok") + } } } } From 7ded8ed8391703a3b409fe9cfb82c3699517fea3 Mon Sep 17 00:00:00 2001 From: Gagan Deep Singh Date: Tue, 12 Jan 2021 12:36:12 +0530 Subject: [PATCH 0742/1127] ms teams suport added --- internal/app/utils.go | 79 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/internal/app/utils.go b/internal/app/utils.go index 13e85b35..a0436862 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -489,3 +489,82 @@ func isValidFile(filePath string, allowedFileTypes []string) error { } return nil } + +// notify MSTeams sends a JSON formatted message to MSTeams channel over a webhook url +// It takes the content of the message (what changes helmsman is going to do or have done separated by \n) +// and the webhook URL as well as a flag specifying if this is a failure message or not +// It returns true if the sending of the message is successful, otherwise returns false +// This implementation is inspired from Slack notification +func notifyMSTeams(content string, url string, failure bool, executing bool) bool { + log.Info("Posting notifications to MS Teams ... ") + + color := "#36a64f" // green + if failure { + color = "#FF0000" // red + } + + var contentBold string + var pretext string + + if content == "" { + pretext = "**No actions to perform!**" + } else if failure { + pretext = "**Failed to generate/execute a plan: **" + contentTrimmed := strings.TrimSuffix(content, "\n\n") + contentBold = "**" + contentTrimmed + "**" + } else if executing && !failure { + pretext = "**Here is what I have done: **" + contentBold = "**" + content + "**" + } else { + pretext = "**Here is what I am going to do: **" + contentSplit := strings.Split(content, "\n\n") + for i := range contentSplit { + contentSplit[i] = "* *" + contentSplit[i] + "*" + } + contentBold = strings.Join(contentSplit, "\n\n") + } + + t := time.Now().UTC() + + var jsonStr = []byte(`{ + "@type": "MessageCard", + "@context": "http://schema.org/extensions", + "themeColor": "`+color+`", + "title":"`+pretext+`", + "summary":"Helmsman results.", + "sections":[ + { + "type":"textBlock", + "text":"`+contentBold+`", + "wrap": true + }, + { + "type":"textBlock", + "text":"Helmsman ` + appVersion + `", + "wrap": true + }, + { + "type":"textBlock", + "text":"`+ strconv.FormatInt(t.Unix(), 10) +`", + "wrap": true + } + ] + }`) + + req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr)) + if err != nil { + log.Errorf("Failed to send MS Teams message: %v", err) + return false + } + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + log.Errorf("Failed to send notification to MS Teams: %v", err) + return false + } + defer resp.Body.Close() + + return resp.StatusCode == 200 +} \ No newline at end of file From de9cf5b7823fc2cf00187f33a4e8e616c9e7331e Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 12 Jan 2021 09:42:53 +0000 Subject: [PATCH 0743/1127] fix: only add hook annotations when we dont fail to get the annotation key --- internal/app/plan.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/app/plan.go b/internal/app/plan.go index c9a9f6f5..84db003c 100644 --- a/internal/app/plan.go +++ b/internal/app/plan.go @@ -140,13 +140,13 @@ func releaseWithHooks(cmd orderedCommand, storageBackend string, wg *sync.WaitGr for _, c := range cmd.beforeCommands { if err := execOne(c.Command, cmd.targetRelease); err != nil { errors <- err - if key, err := c.getAnnotationKey(); err != nil { + if key, err := c.getAnnotationKey(); err == nil { annotations = append(annotations, key+"=failed") } log.Verbose(err.Error()) return } - if key, err := c.getAnnotationKey(); err != nil { + if key, err := c.getAnnotationKey(); err == nil { annotations = append(annotations, key+"=ok") } } @@ -166,12 +166,12 @@ func releaseWithHooks(cmd orderedCommand, storageBackend string, wg *sync.WaitGr for _, c := range cmd.afterCommands { if err := execOne(c.Command, cmd.targetRelease); err != nil { errors <- err - if key, err := c.getAnnotationKey(); err != nil { + if key, err := c.getAnnotationKey(); err == nil { annotations = append(annotations, key+"=failed") } log.Verbose(err.Error()) } else { - if key, err := c.getAnnotationKey(); err != nil { + if key, err := c.getAnnotationKey(); err == nil { annotations = append(annotations, key+"=ok") } } From 7d9c9ee47ee019362bbeda6632e2fb0db63f5c8f Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 12 Jan 2021 10:07:23 +0000 Subject: [PATCH 0744/1127] feat: pass the debug flag down to helm if set in helmsman --- internal/app/cli.go | 7 +++++-- internal/app/helm_release.go | 2 +- internal/app/release.go | 6 +++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/internal/app/cli.go b/internal/app/cli.go index 947b926f..ee9a81f6 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -265,10 +265,13 @@ func (c *cli) readState(s *state) error { return nil } -// getDryRunFlags returns dry-run flag -func (c *cli) getDryRunFlags() []string { +// getRunFlags returns dry-run and debug flags +func (c *cli) getRunFlags() []string { if c.dryRun { return []string{"--dry-run", "--debug"} } + if c.debug { + return []string{"--debug"} + } return []string{} } diff --git a/internal/app/helm_release.go b/internal/app/helm_release.go index e2af9c1b..4dad909d 100644 --- a/internal/app/helm_release.go +++ b/internal/app/helm_release.go @@ -79,7 +79,7 @@ func (r *helmRelease) key() string { // uninstall creates the helm command to uninstall an untracked release func (r *helmRelease) uninstall(p *plan) { - cmd := helmCmd(concat([]string{"uninstall", r.Name, "--namespace", r.Namespace}, flags.getDryRunFlags()), "Delete untracked release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") + cmd := helmCmd(concat([]string{"uninstall", r.Name, "--namespace", r.Namespace}, flags.getRunFlags()), "Delete untracked release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") p.addCommand(cmd, -800, nil, []hookCmd{}, []hookCmd{}) } diff --git a/internal/app/release.go b/internal/app/release.go index 54e09005..d2147eaf 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -223,7 +223,7 @@ func (r *release) rollback(cs *currentState, p *plan) { if r.Namespace == rs.Namespace { - cmd := helmCmd(concat([]string{"rollback", r.Name, rs.getRevision()}, r.getWait(), r.getTimeout(), r.getNoHooks(), flags.getDryRunFlags()), "Rolling back release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") + cmd := helmCmd(concat([]string{"rollback", r.Name, rs.getRevision()}, r.getWait(), r.getTimeout(), r.getNoHooks(), flags.getRunFlags()), "Rolling back release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") p.addCommand(cmd, r.Priority, r, []hookCmd{}, []hookCmd{}) r.upgrade(p) // this is to reflect any changes in values file(s) p.addDecision("Release [ "+r.Name+" ] was deleted and is desired to be rolled back to "+ @@ -369,7 +369,7 @@ func (r *release) getHelmFlags() []string { } flgs = append(flgs, r.HelmFlags...) - return concat(r.getNoHooks(), r.getWait(), r.getTimeout(), r.getMaxHistory(), flags.getDryRunFlags(), []string{force}, flgs) + return concat(r.getNoHooks(), r.getWait(), r.getTimeout(), r.getMaxHistory(), flags.getRunFlags(), []string{force}, flgs) } // getPostRenderer returns the post-renderer Helm flag @@ -393,7 +393,7 @@ func (r *release) getHelmArgsFor(action string, optionalNamespaceOverride ...str case "diff": return concat([]string{"upgrade", r.Name, r.Chart, "--version", r.Version, "--namespace", r.Namespace}, r.getValuesFiles(), r.getSetValues(), r.getSetStringValues(), r.getSetFileValues(), r.getPostRenderer()) case "uninstall": - return concat([]string{action, "--namespace", ns, r.Name}, flags.getDryRunFlags()) + return concat([]string{action, "--namespace", ns, r.Name}, flags.getRunFlags()) default: return []string{action, "--namespace", ns, r.Name} } From ec9b706d9b219cdcfaf3e5dbccb5631e6924f0fa Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 12 Jan 2021 10:10:31 +0000 Subject: [PATCH 0745/1127] chore: code linting --- internal/app/cli.go | 4 ++-- internal/app/decision_maker.go | 7 ++++--- internal/app/helm_release.go | 1 - internal/app/helm_time.go | 6 ++++-- internal/app/kube_helpers.go | 8 +++----- internal/app/release.go | 3 --- internal/app/state.go | 5 +---- internal/app/state_files.go | 2 +- internal/app/state_files_test.go | 3 ++- internal/app/state_test.go | 2 +- internal/app/utils.go | 9 ++++----- internal/aws/aws.go | 7 ------- internal/azure/azblob.go | 10 ++++++---- internal/gcs/gcs.go | 3 +-- 14 files changed, 29 insertions(+), 41 deletions(-) diff --git a/internal/app/cli.go b/internal/app/cli.go index ee9a81f6..de7e068e 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -79,7 +79,7 @@ func printUsage() { // Cli parses cmd flags, validates them and performs some initializations func (c *cli) parse() { - //parsing command line flags + // parsing command line flags flag.Var(&c.files, "f", "desired state file name(s), may be supplied more than once to merge state files") flag.Var(&c.envFiles, "e", "file(s) to load environment variables from (default .env), may be supplied more than once") flag.Var(&c.target, "target", "limit execution to specific app.") @@ -211,7 +211,7 @@ func (c *cli) readState(s *state) error { // wipe & create a temporary directory os.RemoveAll(tempFilesDir) - _ = os.MkdirAll(tempFilesDir, 0755) + _ = os.MkdirAll(tempFilesDir, 0o755) // read the TOML/YAML desired state file var fileState state diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index dad224f6..2da9f520 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -174,8 +174,10 @@ func (cs *currentState) releaseExists(r *release, status string) bool { return true } -var resourceNameExtractor = regexp.MustCompile(`(^\w+/|\.v\d+$)`) -var releaseNameExtractor = regexp.MustCompile(`sh\.helm\.release\.v\d+\.`) +var ( + resourceNameExtractor = regexp.MustCompile(`(^\w+/|\.v\d+$)`) + releaseNameExtractor = regexp.MustCompile(`sh\.helm\.release\.v\d+\.`) +) // getHelmsmanReleases returns a map of all releases that are labeled with "MANAGED-BY=HELMSMAN" // The releases are categorized by the namespaces in which they are deployed @@ -252,7 +254,6 @@ func (cs *currentState) getHelmsmanReleases(s *state) map[string]map[string]bool } mutex.Unlock() } - }(ns) } wg.Wait() diff --git a/internal/app/helm_release.go b/internal/app/helm_release.go index 4dad909d..4375a3d0 100644 --- a/internal/app/helm_release.go +++ b/internal/app/helm_release.go @@ -92,7 +92,6 @@ func (r *helmRelease) getRevision() string { // getChartName extracts and returns the Helm chart name from the chart info in a release state. // example: chart in release state is "jenkins-0.9.0" and this function will extract "jenkins" from it. func (r *helmRelease) getChartName() string { - chart := r.Chart runes := []rune(chart) return string(runes[0:strings.LastIndexByte(chart[0:strings.IndexByte(chart, '.')], '-')]) diff --git a/internal/app/helm_time.go b/internal/app/helm_time.go index 1254a860..c4abcbff 100644 --- a/internal/app/helm_time.go +++ b/internal/app/helm_time.go @@ -6,8 +6,10 @@ import ( "time" ) -const ctLayout = "2006-01-02 15:04:05.000000000 -0700 MST" -const ctLayout2 = "2006-01-02 15:04:05.000000000 -0700 -0700" +const ( + ctLayout = "2006-01-02 15:04:05.000000000 -0700 MST" + ctLayout2 = "2006-01-02 15:04:05.000000000 -0700 -0700" +) var nilTime = (time.Time{}).UnixNano() diff --git a/internal/app/kube_helpers.go b/internal/app/kube_helpers.go index bfcc2c97..5bfb6dac 100644 --- a/internal/app/kube_helpers.go +++ b/internal/app/kube_helpers.go @@ -100,7 +100,6 @@ func annotateNamespace(ns string, annotations map[string]string) { // setLimits creates a LimitRange resource in the provided Namespace func setLimits(ns string, lims limits) { - if len(lims) == 0 { return } @@ -121,7 +120,7 @@ spec: definition = definition + Indent(string(d), strings.Repeat(" ", 4)) targetFile := path.Join(createTempDir(tempFilesDir, "tmp"), "temp-LimitRange.yaml") - if err := ioutil.WriteFile(targetFile, []byte(definition), 0666); err != nil { + if err := ioutil.WriteFile(targetFile, []byte(definition), 0o666); err != nil { log.Fatal(err.Error()) } @@ -133,7 +132,6 @@ spec: } deleteFile(targetFile) - } func setQuotas(ns string, quotas *quotas) { @@ -155,7 +153,7 @@ spec: definition = definition + Indent(customQuota.Name+": '"+customQuota.Value+"'\n", strings.Repeat(" ", 4)) } - //Special formatting for custom quotas so manually write these and then set to nil for marshalling + // Special formatting for custom quotas so manually write these and then set to nil for marshalling quotas.CustomQuotas = nil d, err := yaml.Marshal("as) @@ -165,7 +163,7 @@ spec: definition = definition + Indent(string(d), strings.Repeat(" ", 4)) - if err := ioutil.WriteFile("temp-ResourceQuota.yaml", []byte(definition), 0666); err != nil { + if err := ioutil.WriteFile("temp-ResourceQuota.yaml", []byte(definition), 0o666); err != nil { log.Fatal(err.Error()) } diff --git a/internal/app/release.go b/internal/app/release.go index d2147eaf..cffad379 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -159,7 +159,6 @@ func (r *release) uninstall(p *plan, optionalNamespace ...string) { cmd := helmCmd(r.getHelmArgsFor("uninstall", ns), "Delete release [ "+r.Name+" ] in namespace [ "+ns+" ]") p.addCommand(cmd, priority, r, before, after) - } // diffRelease diffs an existing release with the specified values.yaml @@ -186,7 +185,6 @@ func (r *release) diff() (string, error) { // upgradeRelease upgrades an existing release with the specified values.yaml func (r *release) upgrade(p *plan) { - before, after := r.checkHooks("upgrade") if r.Test { @@ -196,7 +194,6 @@ func (r *release) upgrade(p *plan) { cmd := helmCmd(r.getHelmArgsFor("upgrade"), "Upgrade release [ "+r.Name+" ] to version [ "+r.Version+" ] in namespace [ "+r.Namespace+" ]") p.addCommand(cmd, r.Priority, r, before, after) - } // reInstall uninstalls a release and reinstalls it. diff --git a/internal/app/state.go b/internal/app/state.go index aaf40ff4..f8f06000 100644 --- a/internal/app/state.go +++ b/internal/app/state.go @@ -84,7 +84,6 @@ func (s *state) initializeNamespaces() { // validate validates that the values specified in the desired state are valid according to the desired state spec. // check https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md for the detailed specification func (s *state) validate() error { - // apps if s.Apps == nil { log.Info("No apps specified. Nothing to be executed.") @@ -151,7 +150,6 @@ func (s *state) validate() error { return errors.New("certificates validation failed -- connection to cluster is required " + "but no cert/key was given. Please add [caCrt] and [caKey] under Certifications. You might also need to provide [clientCrt]") } - } else if s.Settings.ClusterURI != "" && s.Settings.BearerToken { if !caCrt { return errors.New("certificates validation failed -- cluster connection with bearer token is enabled but " + @@ -251,7 +249,7 @@ func (s *state) getReleaseChartsInfo() error { mutex.Unlock() } - //validateChart(concattedApps, chart, version, chartErrors) + // validateChart(concattedApps, chart, version, chartErrors) }(concattedApps, chart, version) } } @@ -337,7 +335,6 @@ func (s *state) updateContextLabels() { // print prints the desired state func (s *state) print() { - fmt.Println("\nMetadata: ") fmt.Println("--------- ") printMap(s.Metadata, 0) diff --git a/internal/app/state_files.go b/internal/app/state_files.go index f9fc3a31..b71a0aa9 100644 --- a/internal/app/state_files.go +++ b/internal/app/state_files.go @@ -146,7 +146,7 @@ func (s *state) expand(relativeToFile string) { r.resolvePaths(dir, downloadDest) if r.Chart != "" { - var repoOrDir = filepath.Dir(r.Chart) + repoOrDir := filepath.Dir(r.Chart) _, isRepo := s.HelmRepos[repoOrDir] isRepo = isRepo || stringInSlice(repoOrDir, s.PreconfiguredHelmRepos) if !isRepo { diff --git a/internal/app/state_files_test.go b/internal/app/state_files_test.go index ae7cadc0..6746e198 100644 --- a/internal/app/state_files_test.go +++ b/internal/app/state_files_test.go @@ -8,7 +8,7 @@ import ( func setupStateFileTestCase(t *testing.T) func(t *testing.T) { t.Log("setup test case") - os.MkdirAll(tempFilesDir, 0755) + os.MkdirAll(tempFilesDir, 0o755) return func(t *testing.T) { t.Log("teardown test case") @@ -57,6 +57,7 @@ func Test_fromTOML(t *testing.T) { os.Unsetenv("ORG_PATH") os.Unsetenv("VALUE") } + func Test_fromTOML_Expand(t *testing.T) { type args struct { file string diff --git a/internal/app/state_test.go b/internal/app/state_test.go index 398c64ce..3cb137ae 100644 --- a/internal/app/state_test.go +++ b/internal/app/state_test.go @@ -8,7 +8,7 @@ import ( func setupTestCase(t *testing.T) func(t *testing.T) { t.Log("setup test case") - os.MkdirAll(tempFilesDir, 0755) + os.MkdirAll(tempFilesDir, 0o755) os.MkdirAll(os.TempDir()+"/helmsman-tests/myapp", os.ModePerm) os.MkdirAll(os.TempDir()+"/helmsman-tests/dir-with space/myapp", os.ModePerm) cmd := helmCmd([]string{"create", os.TempDir() + "/helmsman-tests/dir-with space/myapp"}, "creating an empty local chart directory") diff --git a/internal/app/utils.go b/internal/app/utils.go index 13e85b35..ec4d3214 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -60,7 +60,7 @@ func substituteVarsInYaml(file string) string { // output file contents with env variables substituted into temp files outFile := path.Join(dir, filepath.Base(file)) - err = ioutil.WriteFile(outFile, []byte(yamlFile), 0644) + err = ioutil.WriteFile(outFile, []byte(yamlFile), 0o644) if err != nil { log.Fatal(err.Error()) } @@ -262,7 +262,7 @@ func copyFile(source string, destination string) { } defer from.Close() - to, err := os.OpenFile(destination, os.O_RDWR|os.O_CREATE, 0666) + to, err := os.OpenFile(destination, os.O_RDWR|os.O_CREATE, 0o666) if err != nil { log.Fatal("while copying " + source + " to " + destination + " : " + err.Error()) } @@ -317,7 +317,7 @@ func notifySlack(content string, url string, failure bool, executing bool) bool t := time.Now().UTC() - var jsonStr = []byte(`{ + jsonStr := []byte(`{ "attachments": [ { "fallback": "Helmsman results.", @@ -352,7 +352,6 @@ func notifySlack(content string, url string, failure bool, executing bool) bool // this func works for S3, Azure and GCS bucket links of the format: // s3 or gs://bucketname/dir.../file.ext func getBucketElements(link string) map[string]string { - tmp := strings.SplitAfterN(link, "//", 2)[1] m := make(map[string]string) m["bucketName"] = strings.SplitN(tmp, "/", 2)[0] @@ -368,7 +367,7 @@ func replaceStringInFile(input []byte, outfile string, replacements map[string]s output = bytes.Replace(output, []byte(k), []byte(v), -1) } - if err := ioutil.WriteFile(outfile, output, 0666); err != nil { + if err := ioutil.WriteFile(outfile, output, 0o666); err != nil { log.Fatal(err.Error()) } } diff --git a/internal/aws/aws.go b/internal/aws/aws.go index 8d4acc40..702ffb8b 100644 --- a/internal/aws/aws.go +++ b/internal/aws/aws.go @@ -16,11 +16,8 @@ import ( var style aurora.Aurora func checkCredentialsEnvVar() bool { - if os.Getenv("AWS_ACCESS_KEY_ID") == "" || os.Getenv("AWS_SECRET_ACCESS_KEY") == "" { - return false - } else if os.Getenv("AWS_REGION") == "" { if os.Getenv("AWS_DEFAULT_REGION") == "" { @@ -42,7 +39,6 @@ func ReadFile(bucketName string, filename string, outFile string, noColors bool) // Create Session -- use config (credentials + region) from env vars or aws profile sess, err := session.NewSession() - if err != nil { log.Fatal(style.Bold(style.Red("ERROR: Can't create AWS session: " + err.Error()))) } @@ -66,7 +62,6 @@ func ReadFile(bucketName string, filename string, outFile string, noColors bool) } log.Println("Successfully downloaded " + filename + " from S3 as " + outFile) - } // ReadSSMParam reads a value from an SSM Parameter @@ -80,7 +75,6 @@ func ReadSSMParam(keyname string, withDecryption bool, noColors bool) string { // Create Session -- use config (credentials + region) from env vars or aws profile sess, err := session.NewSession() - if err != nil { log.Fatal(style.Bold(style.Red("ERROR: Can't create AWS session: " + err.Error()))) } @@ -90,7 +84,6 @@ func ReadSSMParam(keyname string, withDecryption bool, noColors bool) string { Name: &keyname, WithDecryption: &withDecryption, }) - if err != nil { log.Fatal(style.Bold(style.Red("ERROR: Can't find the SSM Parameter " + keyname + " : " + err.Error()))) } diff --git a/internal/azure/azblob.go b/internal/azure/azblob.go index 235a8bb9..b148eb1a 100644 --- a/internal/azure/azblob.go +++ b/internal/azure/azblob.go @@ -15,10 +15,12 @@ import ( ) // colorizer -var style aurora.Aurora -var accountName string -var accountKey string -var p pipeline.Pipeline +var ( + style aurora.Aurora + accountName string + accountKey string + p pipeline.Pipeline +) // auth checks for AZURE_STORAGE_ACCOUNT and AZURE_STORAGE_ACCESS_KEY in the environment // if env vars are set, it will authenticate and create an azblob request pipeline diff --git a/internal/gcs/gcs.go b/internal/gcs/gcs.go index 3c6d36f8..71846bb8 100644 --- a/internal/gcs/gcs.go +++ b/internal/gcs/gcs.go @@ -27,8 +27,7 @@ func Auth() (string, error) { credFile := "/tmp/gcloud_credentials.json" // write the credentials content into a json file d := []byte(os.Getenv("GCLOUD_CREDENTIALS")) - err := ioutil.WriteFile(credFile, d, 0644) - + err := ioutil.WriteFile(credFile, d, 0o644) if err != nil { return fmt.Sprintf("Cannot create credentials file: %s", err), err } From d21a27bbedb834cf0dd98252eac9a3ec49f165d5 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 12 Jan 2021 10:20:48 +0000 Subject: [PATCH 0746/1127] feat: add retries to curl --- Dockerfile | 4 ++-- scripts/setup.sh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index b6fcb88f..b91bb5ad 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,10 +15,10 @@ ENV HELM_DIFF_VERSION=$GLOBAL_HELM_DIFF_VERSION RUN apk add --update --no-cache ca-certificates git openssh ruby curl tar gzip make bash -RUN curl -L https://storage.googleapis.com/kubernetes-release/release/${KUBE_VERSION}/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl +RUN curl --retry 5 -L https://storage.googleapis.com/kubernetes-release/release/${KUBE_VERSION}/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl RUN chmod +x /usr/local/bin/kubectl -RUN curl -Lk https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz | tar zxv -C /tmp +RUN curl --retry 5 -Lk https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz | tar zxv -C /tmp RUN mv /tmp/linux-amd64/helm /usr/local/bin/helm && rm -rf /tmp/linux-amd64 RUN chmod +x /usr/local/bin/helm diff --git a/scripts/setup.sh b/scripts/setup.sh index 686e08a0..9a753ced 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -3,10 +3,10 @@ set -e apk add --update --no-cache ca-certificates git openssh ruby curl tar gzip make bash gnupg -curl -L https://storage.googleapis.com/kubernetes-release/release/${KUBE_VERSION}/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl +curl --retry 5 -L https://storage.googleapis.com/kubernetes-release/release/${KUBE_VERSION}/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl chmod +x /usr/local/bin/kubectl -curl -L https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz | tar zxv -C /tmp +curl --retry 5 -L https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz | tar zxv -C /tmp mv /tmp/linux-amd64/helm /usr/local/bin/helm rm -rf /tmp/linux-amd64 chmod +x /usr/local/bin/helm From be20ce655ba498a59b2865319d2a328361aac973 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 12 Jan 2021 11:32:23 +0000 Subject: [PATCH 0747/1127] fix: improve error logging for file validation Signed-off-by: Luis Davim --- internal/app/hooks.go | 2 +- internal/app/release.go | 10 +++++----- internal/app/release_test.go | 14 +++++++------- internal/app/utils.go | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/internal/app/hooks.go b/internal/app/hooks.go index 8837a734..8ea0ec1d 100644 --- a/internal/app/hooks.go +++ b/internal/app/hooks.go @@ -40,7 +40,7 @@ func validateHooks(hooks map[string]interface{}) error { return nil } if err := isValidFile(hook, validFiles); err != nil { - return err + return fmt.Errorf("invalid hook manifest: %w", err) } case "successCondition", "successTimeout", "deleteOnSuccess": continue diff --git a/internal/app/release.go b/internal/app/release.go index cffad379..b367aca5 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -80,12 +80,12 @@ func (r *release) validate(appLabel string, seen map[string]map[string]bool, s * return errors.New("valuesFile and valuesFiles should not be used together") } else if r.ValuesFile != "" { if err := isValidFile(r.ValuesFile, validFiles); err != nil { - return err + return fmt.Errorf("invalid values file: %w", err) } } else if len(r.ValuesFiles) > 0 { for _, filePath := range r.ValuesFiles { if err := isValidFile(filePath, validFiles); err != nil { - return err + return fmt.Errorf("invalid values file: %w", err) } } } @@ -94,18 +94,18 @@ func (r *release) validate(appLabel string, seen map[string]map[string]bool, s * return errors.New("secretsFile and secretsFiles should not be used together") } else if r.SecretsFile != "" { if err := isValidFile(r.SecretsFile, validFiles); err != nil { - return err + return fmt.Errorf("invalid secrets file: %w", err) } } else if len(r.SecretsFiles) > 0 { for _, filePath := range r.SecretsFiles { if err := isValidFile(filePath, validFiles); err != nil { - return err + return fmt.Errorf("invalid secrets file: %w", err) } } } if r.PostRenderer != "" && !ToolExists(r.PostRenderer) { - return fmt.Errorf("%s must be valid relative (from dsf file) file path", r.PostRenderer) + return fmt.Errorf("%s must be executable and available in your PATH", r.PostRenderer) } if r.Priority != 0 && r.Priority > 0 { diff --git a/internal/app/release_test.go b/internal/app/release_test.go index 6fdbdb77..253f0233 100644 --- a/internal/app/release_test.go +++ b/internal/app/release_test.go @@ -54,7 +54,7 @@ func Test_release_validate(t *testing.T) { }, s: st, }, - want: "xyz.yaml must be valid relative (from dsf file) file path", + want: "invalid values file: xyz.yaml must be valid relative (from dsf file) file path: stat xyz.yaml: no such file or directory", }, { name: "test case 3", args: args{ @@ -70,7 +70,7 @@ func Test_release_validate(t *testing.T) { }, s: st, }, - want: "../../tests/values.xml must be of one the following file formats: .yaml, .yml, .json", + want: "invalid values file: ../../tests/values.xml must be of one the following file formats: .yaml, .yml, .json", }, { name: "test case 4", args: args{ @@ -215,7 +215,7 @@ func Test_release_validate(t *testing.T) { }, s: st, }, - want: "xyz.yaml must be valid relative (from dsf file) file path", + want: "invalid values file: xyz.yaml must be valid relative (from dsf file) file path: stat xyz.yaml: no such file or directory", }, { name: "test case 13", args: args{ @@ -247,7 +247,7 @@ func Test_release_validate(t *testing.T) { }, s: st, }, - want: "xyz.fake must be valid relative (from dsf file) file path", + want: "invalid hook manifest: xyz.fake must be valid relative (from dsf file) file path: stat xyz.fake: no such file or directory", }, { name: "test case 15 - invalid hook file type", args: args{ @@ -263,7 +263,7 @@ func Test_release_validate(t *testing.T) { }, s: st, }, - want: "../../tests/values.xml must be of one the following file formats: .yaml, .yml, .json", + want: "invalid hook manifest: ../../tests/values.xml must be of one the following file formats: .yaml, .yml, .json", }, { name: "test case 16 - valid hook file type", args: args{ @@ -311,7 +311,7 @@ func Test_release_validate(t *testing.T) { }, s: st, }, - want: "https//raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml must be valid URL path to a raw file", + want: "invalid hook manifest: https//raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml must be valid URL path to a raw file", }, { name: "test case 19 - invalid hook type 1", args: args{ @@ -377,7 +377,7 @@ func Test_release_validate(t *testing.T) { }, s: st, }, - want: "doesnt-exist.sh must be valid relative (from dsf file) file path", + want: "doesnt-exist.sh must be executable and available in your PATH", }, { name: "test case 23 - executable hook type", args: args{ diff --git a/internal/app/utils.go b/internal/app/utils.go index ec4d3214..83796553 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -482,7 +482,7 @@ func isValidFile(filePath string, allowedFileTypes []string) error { return fmt.Errorf("%s must be valid URL path to a raw file", filePath) } } else if _, pathErr := os.Stat(filePath); pathErr != nil { - return fmt.Errorf("%s must be valid relative (from dsf file) file path", filePath) + return fmt.Errorf("%s must be valid relative (from dsf file) file path: %w", filePath, pathErr) } else if !isOfType(filePath, allowedFileTypes) { return fmt.Errorf("%s must be of one the following file formats: %s", filePath, strings.Join(allowedFileTypes, ", ")) } From 88438d7ae2f96ea1ca69134b5d8a153c20cd0d1e Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 12 Jan 2021 17:47:03 +0000 Subject: [PATCH 0748/1127] Prepeare release v3.6.3 --- .version | 2 +- README.md | 6 +++--- internal/app/main.go | 2 +- release-notes.md | 11 +++++++---- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/.version b/.version index b9bf9ed4..50dde2d0 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.6.2 +v3.6.3 diff --git a/README.md b/README.md index ba1c30ac..3d28571b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.6.2&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.6.3&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -61,9 +61,9 @@ Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the d ```sh # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.2/helmsman_3.6.2_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.3/helmsman_3.6.2_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.2/helmsman_3.6.2_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.3/helmsman_3.6.2_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/internal/app/main.go b/internal/app/main.go index 8d7f5e0a..1b184666 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.6.2" + appVersion = "v3.6.3" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index 4946134d..a33f4dcb 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,4 +1,4 @@ -# v3.6.2 +# v3.6.3 If you migrating from Helmsman v1.x, it is recommended you read the [migration guide](https://github.com/Praqma/helmsman/blob/master/docs/how_to/misc/migrate_to_3.md) before using this release. @@ -6,6 +6,9 @@ If you migrating from Helmsman v1.x, it is recommended you read the [migration g ## Fixes and improvements -- Commands will optionally (Verbose) log stdout on non-zero exit code (#554) -- `helm test` command will run immediately after install / upgrade (before hooks and labelling). (#553) -- Add test cases for `readState`; improved error handling and small order-of-operations refactor. (#552) +- Fixed missing diff on chart version change (#557) +- Fixed checking for updates on disabled releases (#557) +- Fixed segmentation fault on slack notifications (#559) +- Fixed failure to remove untracked releases (#566) +- The debug flag is now passed down to the helm commands (#568) +- Improved error reporting (#568) From 4d5ed1d6443beaa2b29ccb4d5d531ff76e38a5b2 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 12 Jan 2021 20:36:13 +0000 Subject: [PATCH 0749/1127] Fix download URLs --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3d28571b..1851399d 100644 --- a/README.md +++ b/README.md @@ -61,9 +61,9 @@ Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the d ```sh # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.3/helmsman_3.6.2_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.3/helmsman_3.6.3_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.3/helmsman_3.6.2_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.3/helmsman_3.6.3_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` From 4778df40f3db84a519d1c3ad4ea0a687a155b858 Mon Sep 17 00:00:00 2001 From: Gagan Deep Singh Date: Wed, 13 Jan 2021 11:55:37 +0530 Subject: [PATCH 0750/1127] Card look and feel updated. --- internal/app/utils.go | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/internal/app/utils.go b/internal/app/utils.go index a0436862..eaf01aa7 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -507,45 +507,42 @@ func notifyMSTeams(content string, url string, failure bool, executing bool) boo var pretext string if content == "" { - pretext = "**No actions to perform!**" + pretext = "No actions to perform!" } else if failure { - pretext = "**Failed to generate/execute a plan: **" - contentTrimmed := strings.TrimSuffix(content, "\n\n") + pretext = "Failed to generate/execute a plan:" + contentTrimmed := strings.TrimSuffix(content, "\n") contentBold = "**" + contentTrimmed + "**" } else if executing && !failure { - pretext = "**Here is what I have done: **" + pretext = "Here is what I have done:" contentBold = "**" + content + "**" } else { - pretext = "**Here is what I am going to do: **" - contentSplit := strings.Split(content, "\n\n") + pretext = "Here is what I am going to do:" + contentSplit := strings.Split(content, "\n") for i := range contentSplit { - contentSplit[i] = "* *" + contentSplit[i] + "*" + contentSplit[i] = "* **" + contentSplit[i] + "**" } contentBold = strings.Join(contentSplit, "\n\n") } t := time.Now().UTC() + footer := "Helmsman " + appVersion + " | " + t.Format(time.UnixDate) + summary := "Helmsman results." var jsonStr = []byte(`{ "@type": "MessageCard", "@context": "http://schema.org/extensions", - "themeColor": "`+color+`", - "title":"`+pretext+`", - "summary":"Helmsman results.", + "themeColor": "` + color + `", + "title":"` + pretext + `", + "summary":` + summary + `, "sections":[ { "type":"textBlock", - "text":"`+contentBold+`", - "wrap": true - }, - { - "type":"textBlock", - "text":"Helmsman ` + appVersion + `", + "text":"` + contentBold + `", "wrap": true }, { "type":"textBlock", - "text":"`+ strconv.FormatInt(t.Unix(), 10) +`", + "text": ` + footer + `, "wrap": true } ] @@ -567,4 +564,4 @@ func notifyMSTeams(content string, url string, failure bool, executing bool) boo defer resp.Body.Close() return resp.StatusCode == 200 -} \ No newline at end of file +} From 5dd43e00028b5beecf3b64f2d42f319cdd8fbd5a Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Wed, 13 Jan 2021 11:01:52 +0000 Subject: [PATCH 0751/1127] fix: lifecycle hook file paths not being resolved --- internal/app/release_files.go | 16 +++++++-------- internal/app/state_files.go | 37 +++++++++++++++-------------------- 2 files changed, 24 insertions(+), 29 deletions(-) diff --git a/internal/app/release_files.go b/internal/app/release_files.go index 13b08c6d..d2376adc 100644 --- a/internal/app/release_files.go +++ b/internal/app/release_files.go @@ -19,7 +19,7 @@ func (r *release) substituteVarsInStaticFiles() { for key, val := range r.Hooks { if key != "deleteOnSuccess" && key != "successTimeout" && key != "successCondition" { hook := val.(string) - if err := isValidFile(hook, []string{".yaml", ".yml"}); err == nil { + if isOfType(hook, []string{".yaml", ".yml"}) { r.Hooks[key] = substituteVarsInYaml(hook) } } @@ -35,18 +35,18 @@ func (r *release) resolvePaths(dir, downloadDest string) { r.SecretsFile, _ = resolveOnePath(r.SecretsFile, dir, downloadDest) } - for i := range r.ValuesFiles { - r.ValuesFiles[i], _ = resolveOnePath(r.ValuesFiles[i], dir, downloadDest) + for i, file := range r.ValuesFiles { + r.ValuesFiles[i], _ = resolveOnePath(file, dir, downloadDest) } - for i := range r.SecretsFiles { - r.SecretsFiles[i], _ = resolveOnePath(r.SecretsFiles[i], dir, downloadDest) + for i, file := range r.SecretsFiles { + r.SecretsFiles[i], _ = resolveOnePath(file, dir, downloadDest) } for key, val := range r.Hooks { if key != "deleteOnSuccess" && key != "successTimeout" && key != "successCondition" { - hook := val.(string) - if err := isValidFile(hook, []string{".yaml", ".yml", ".json"}); err == nil { - r.Hooks[key], _ = resolveOnePath(hook, dir, downloadDest) + file := val.(string) + if isOfType(file, []string{".yaml", ".yml", ".json", ".sh", ".py", ".rb"}) { + r.Hooks[key], _ = resolveOnePath(file, dir, downloadDest) } } } diff --git a/internal/app/state_files.go b/internal/app/state_files.go index b71a0aa9..89b664a1 100644 --- a/internal/app/state_files.go +++ b/internal/app/state_files.go @@ -143,15 +143,16 @@ func (s *state) expand(relativeToFile string) { dir := filepath.Dir(relativeToFile) downloadDest, _ := filepath.Abs(createTempDir(tempFilesDir, "tmp")) for _, r := range s.Apps { - + // resolve paths for all release files (values, secrets, hooks, etc...) r.resolvePaths(dir, downloadDest) + + // resolve paths for local charts if r.Chart != "" { repoOrDir := filepath.Dir(r.Chart) _, isRepo := s.HelmRepos[repoOrDir] isRepo = isRepo || stringInSlice(repoOrDir, s.PreconfiguredHelmRepos) + // if there is no repo for the chart, we assume it's intended to be a local path if !isRepo { - // if there is no repo for the chart, we assume it's intended to be a local path - // support env vars in path r.Chart = os.ExpandEnv(r.Chart) // respect absolute paths to charts but resolve relative paths @@ -160,31 +161,25 @@ func (s *state) expand(relativeToFile string) { } } } - - for key, val := range s.Settings.GlobalHooks { - if key != "deleteOnSuccess" && key != "successTimeout" && key != "successCondition" { - hook := val.(string) - if err := isValidFile(hook, []string{".yaml", ".yml"}); err == nil { - s.Settings.GlobalHooks[key] = substituteVarsInYaml(hook) - } - } - } - + // expand env variables for all release files r.substituteVarsInStaticFiles() } - // resolving paths for Bearer Token path in settings - if s.Settings.BearerTokenPath != "" { - s.Settings.BearerTokenPath, _ = resolveOnePath(s.Settings.BearerTokenPath, dir, downloadDest) - } - // resolve paths for global hooks + // resolve paths and expand env variables for global hook files for key, val := range s.Settings.GlobalHooks { if key != "deleteOnSuccess" && key != "successTimeout" && key != "successCondition" { - hook := val.(string) - if err := isValidFile(hook, []string{".yaml", ".yml", ".json"}); err == nil { - s.Settings.GlobalHooks[key], _ = resolveOnePath(hook, dir, downloadDest) + file := val.(string) + if isOfType(file, []string{".yaml", ".yml", ".json", ".sh", ".py", ".rb"}) { + s.Settings.GlobalHooks[key], _ = resolveOnePath(file, dir, downloadDest) + } + if isOfType(file, []string{".yaml", ".yml"}) { + s.Settings.GlobalHooks[key] = substituteVarsInYaml(file) } } } + // resolving paths for Bearer Token path in settings + if s.Settings.BearerTokenPath != "" { + s.Settings.BearerTokenPath, _ = resolveOnePath(s.Settings.BearerTokenPath, dir, downloadDest) + } // resolving paths for k8s certificate files for k := range s.Certificates { s.Certificates[k], _ = resolveOnePath(s.Certificates[k], "", downloadDest) From b35b7cb8bc5a3cc8c840c5726f64d5eb08223773 Mon Sep 17 00:00:00 2001 From: Michal Keder Date: Fri, 15 Jan 2021 13:34:53 +0100 Subject: [PATCH 0752/1127] Added full support for MS Teams webhooks --- internal/app/logging.go | 20 ++++++++++------- internal/app/main.go | 2 ++ internal/app/plan.go | 14 ++++++++++++ internal/app/state.go | 8 +++++++ internal/app/utils.go | 50 +++++++++++++++++++++++++++-------------- 5 files changed, 69 insertions(+), 25 deletions(-) diff --git a/internal/app/logging.go b/internal/app/logging.go index e0a4abb4..35ee4677 100644 --- a/internal/app/logging.go +++ b/internal/app/logging.go @@ -9,7 +9,8 @@ import ( type Logger struct { *logger.Logger - SlackWebhook string + SlackWebhook string + MSTeamsWebhook string } func (l *Logger) Debug(message string) { @@ -25,24 +26,27 @@ func (l *Logger) Verbose(message string) { } func (l *Logger) Error(message string) { - if _, err := url.ParseRequestURI(l.SlackWebhook); err == nil { - notifySlack(message, l.SlackWebhook, true, flags.apply) - } + l.notifyAboutFailureUsingWebhooks(message) l.Logger.Error(message) } func (l *Logger) Critical(message string) { - if _, err := url.ParseRequestURI(l.SlackWebhook); err == nil { - notifySlack(message, l.SlackWebhook, true, flags.apply) - } + l.notifyAboutFailureUsingWebhooks(message) l.Logger.Critical(message) } func (l *Logger) Fatal(message string) { + l.notifyAboutFailureUsingWebhooks(message) + l.Logger.Fatal(message) +} + +func (l *Logger) notifyAboutFailureUsingWebhooks(message string) { if _, err := url.ParseRequestURI(l.SlackWebhook); err == nil { notifySlack(message, l.SlackWebhook, true, flags.apply) } - l.Logger.Fatal(message) + if _, err := url.ParseRequestURI(l.MSTeamsWebhook); err == nil { + notifyMSTeams(message, l.MSTeamsWebhook, true, flags.apply) + } } func initLogs(verbose bool, noColors bool) { diff --git a/internal/app/main.go b/internal/app/main.go index 8d7f5e0a..0340182d 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -51,6 +51,7 @@ func Main() { } log.SlackWebhook = s.Settings.SlackWebhook + log.MSTeamsWebhook = s.Settings.MSTeamsWebhook settings = &s.Settings curContext = s.Context @@ -115,6 +116,7 @@ func Main() { p.printCmds() } p.sendToSlack() + p.sendToMSTeams() if flags.apply || flags.dryRun || flags.destroy { p.exec() diff --git a/internal/app/plan.go b/internal/app/plan.go index 37a012f9..b99b8d82 100644 --- a/internal/app/plan.go +++ b/internal/app/plan.go @@ -206,6 +206,9 @@ func execOne(cmd Command, targetRelease *release) error { if _, err := url.ParseRequestURI(log.SlackWebhook); err == nil { notifySlack(cmd.Description+" ... SUCCESS!", log.SlackWebhook, false, true) } + if _, err := url.ParseRequestURI(log.MSTeamsWebhook); err == nil { + notifyMSTeams(cmd.Description+" ... SUCCESS!", log.MSTeamsWebhook, false, true) + } return nil } } @@ -250,6 +253,17 @@ func (p *plan) sendToSlack() { } } +// sendToMSTeams sends the description of plan commands to MS Teams if a webhook is provided. +func (p *plan) sendToMSTeams() { + if _, err := url.ParseRequestURI(log.MSTeamsWebhook); err == nil { + str := "" + for _, c := range p.Commands { + str = str + c.Command.Description + "\n" + } + notifyMSTeams(strings.TrimRight(str, "\n"), log.MSTeamsWebhook, false, false) + } +} + // sortPlan sorts the slices of commands and decisions based on priorities // the lower the priority value the earlier a command should be attempted func (p *plan) sort() { diff --git a/internal/app/state.go b/internal/app/state.go index aaf40ff4..d56cfdb8 100644 --- a/internal/app/state.go +++ b/internal/app/state.go @@ -19,6 +19,7 @@ type config struct { ServiceAccount string `yaml:"serviceAccount"` StorageBackend string `yaml:"storageBackend"` SlackWebhook string `yaml:"slackWebhook"` + MSTeamsWebhook string `yaml:"msTeamsWebhook"` ReverseDelete bool `yaml:"reverseDelete"` BearerToken bool `yaml:"bearerToken"` BearerTokenPath string `yaml:"bearerTokenPath"` @@ -134,6 +135,13 @@ func (s *state) validate() error { } } + // ms teams webhook validation (if provided) + if s.Settings.MSTeamsWebhook != "" { + if _, err := url.ParseRequestURI(s.Settings.MSTeamsWebhook); err != nil { + return errors.New("settings validation failed -- msTeamsWebhook must be a valid URL") + } + } + // certificates if s.Certificates != nil && len(s.Certificates) != 0 { diff --git a/internal/app/utils.go b/internal/app/utils.go index eaf01aa7..9e9a58db 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -301,8 +301,14 @@ func notifySlack(content string, url string, failure bool, executing bool) bool pretext = "*No actions to perform!*" } else if failure { pretext = "*Failed to generate/execute a plan: *" - contentTrimmed := strings.TrimSuffix(content, "\n") - contentBold = "*" + contentTrimmed + "*" + content = strings.Replace(content, "\"", "\\\"", -1) + contentSplit := strings.Split(content, "\n") + for i := range contentSplit { + if strings.TrimSpace(contentSplit[i]) != "" { + contentBold += "*" + contentSplit[i] + "*\n" + } + } + contentBold = strings.TrimRight(contentBold, "\n") } else if executing && !failure { pretext = "*Here is what I have done: *" contentBold = "*" + content + "*" @@ -345,7 +351,11 @@ func notifySlack(content string, url string, failure bool, executing bool) bool } defer resp.Body.Close() - return resp.StatusCode == 200 + if resp.StatusCode != 200 { + log.Logger.Errorf("Could not deliver message to Slack. HTTP response status: %s", resp.Status) + return false + } + return true } // getBucketElements returns a map containing the bucket name and the file path inside the bucket @@ -510,8 +520,14 @@ func notifyMSTeams(content string, url string, failure bool, executing bool) boo pretext = "No actions to perform!" } else if failure { pretext = "Failed to generate/execute a plan:" - contentTrimmed := strings.TrimSuffix(content, "\n") - contentBold = "**" + contentTrimmed + "**" + content = strings.Replace(content, "\"", "\\\"", -1) + contentSplit := strings.Split(content, "\n") + for i := range contentSplit { + if strings.TrimSpace(contentSplit[i]) != "" { + contentBold += "**" + contentSplit[i] + "**\n\n" + } + } + contentBold = strings.TrimRight(contentBold, "\n\n") } else if executing && !failure { pretext = "Here is what I have done:" contentBold = "**" + content + "**" @@ -524,25 +540,21 @@ func notifyMSTeams(content string, url string, failure bool, executing bool) boo contentBold = strings.Join(contentSplit, "\n\n") } - t := time.Now().UTC() - footer := "Helmsman " + appVersion + " | " + t.Format(time.UnixDate) - summary := "Helmsman results." - var jsonStr = []byte(`{ "@type": "MessageCard", "@context": "http://schema.org/extensions", "themeColor": "` + color + `", - "title":"` + pretext + `", - "summary":` + summary + `, - "sections":[ + "title": "` + pretext + `", + "summary": "Helmsman results.", + "sections": [ { - "type":"textBlock", - "text":"` + contentBold + `", + "type": "textBlock", + "text": "` + contentBold + `", "wrap": true }, { - "type":"textBlock", - "text": ` + footer + `, + "type": "textBlock", + "text": "Helmsman ` + appVersion + `", "wrap": true } ] @@ -563,5 +575,9 @@ func notifyMSTeams(content string, url string, failure bool, executing bool) boo } defer resp.Body.Close() - return resp.StatusCode == 200 + if resp.StatusCode != 200 { + log.Logger.Errorf("Could not deliver message to MS Teams. HTTP response status: %s", resp.Status) + return false + } + return true } From 275617d257ac43611743a4de5e0fba897a276721 Mon Sep 17 00:00:00 2001 From: Michal Keder Date: Fri, 15 Jan 2021 13:35:15 +0100 Subject: [PATCH 0753/1127] Added docs for MS Teams webhooks --- docs/desired_state_specification.md | 3 +++ docs/how_to/README.md | 1 + ...nd_ms_teams_notifications_from_helmsman.md | 20 +++++++++++++++++++ 3 files changed, 24 insertions(+) create mode 100644 docs/how_to/misc/send_ms_teams_notifications_from_helmsman.md diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 6330707d..52113ec9 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -110,6 +110,7 @@ The following options can be skipped if your kubectl context is already created - **bearerTokenPath**: optional. If bearer token is used, you can specify a custom location (URL, cloud bucket, local file path) for the token file. - **storageBackend** : by default Helm v3 stores release information in secrets, using secrets for storage is recommended for security. - **slackWebhook** : a [Slack](http://slack.com) Webhook URL to receive Helmsman notifications. This can be passed directly or in an environment variable. +- **msTeamsWebhook** : a [Microsoft Teams](https://www.microsoft.com/pl-pl/microsoft-teams/group-chat-software) Webhook URL to receive Helmsman notifications. This can be passed directly or in an environment variable. - **reverseDelete** : if set to `true` it will reverse the priority order whilst deleting. - **eyamlEnabled** : if set to `true' it will use [hiera-eyaml](https://github.com/voxpupuli/hiera-eyaml) to decrypt secret files instead of using default helm-secrets based on sops - **eyamlPrivateKeyPath** : if set with path to the eyaml private key file, it will use it instead of looking for default one in ./keys directory relative to where Helmsman were run. It needs to be defined in conjunction with eyamlPublicKeyPath. @@ -128,6 +129,7 @@ kubeContext = "minikube" ## clusterURI= "$K8S_URI" # storageBackend = "secret" # slackWebhook = $MY_SLACK_WEBHOOK +# msTeamsWebhook = $MY_MS_TEAMS_WEBHOOK # reverseDelete = false # eyamlEnabled = true # eyamlPrivateKeyPath = "../keys/custom-key.pem" @@ -148,6 +150,7 @@ settings: ##clusterURI: "$K8S_URI" #storageBackend: "secret" #slackWebhook: "$MY_SLACK_WEBHOOK" + #msTeamsWebhook: "$MY_MS_TEAMS_WEBHOOK" #reverseDelete: false # eyamlEnabled: true # eyamlPrivateKeyPath: ../keys/custom-key.pem diff --git a/docs/how_to/README.md b/docs/how_to/README.md index 0f8698d1..94a61370 100644 --- a/docs/how_to/README.md +++ b/docs/how_to/README.md @@ -49,6 +49,7 @@ It is recommended that you also check the [DSF spec](../desired_state_specificat - [Authenticating to cloud storage providers](misc/auth_to_storage_providers.md) - [Protecting namespaces and releases](misc/protect_namespaces_and_releases.md) - [Send slack notifications from Helmsman](misc/send_slack_notifications_from_helmsman.md) + - [Send MS Teams notifications from Helmsman](misc/send_ms_teams_notifications_from_helmsman.md) - [Merge multiple desired state files](misc/merge_desired_state_files.md) - [Limit Helmsman deployment to specific apps](misc/limit-deployment-to-specific-apps.md) - [Limit Helmsman deployment to specific group of apps](misc/limit-deployment-to-specific-group-of-apps.md) diff --git a/docs/how_to/misc/send_ms_teams_notifications_from_helmsman.md b/docs/how_to/misc/send_ms_teams_notifications_from_helmsman.md new file mode 100644 index 00000000..c14b7333 --- /dev/null +++ b/docs/how_to/misc/send_ms_teams_notifications_from_helmsman.md @@ -0,0 +1,20 @@ +# Microsoft Teams notifications from Helmsman + +Helmsman can send MS Teams notifications to a channel of your choice. To enable the notifications, simply add a `msTeamsWebhook webhook` in the `settings` section of your desired state file. The webhook URL can be passed directly or from an environment variable. + +```toml +[settings] +... +msTeamsWebhook = $MY_MS_TEAMS_WEBHOOK +``` + +```yaml +settings: + # ... + msTeamsWebhook : "$MY_MS_TEAMS_WEBHOOK" + # ... +``` + +## Getting a MS Teams Webhook URL + +Follow the [Microsoft Teams Guide](https://docs.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook) for generating a webhook URL. From 608618ddf44586e9591fd7e79eeea1d4ef3f0a3c Mon Sep 17 00:00:00 2001 From: rasta-rocket Date: Tue, 19 Jan 2021 23:52:11 +0100 Subject: [PATCH 0754/1127] fix: side-effect on r.SecretsFiles when having multiple successive call (diff, upgrade, ...) In the case there is several successive action called in the same run (diff, upgrade, ...) the field r.SecretsFiles is overwritten during the first call. Therefore the field r.SecretsFiles is different between the first call and the following ones. One side effect of that can be seen by doing helmsman -show-diff with the secretFiles enabled (helm-secrets plugin + vault driver): helmsman will try do decrypt a file already decrypted. --- internal/app/release_files.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/internal/app/release_files.go b/internal/app/release_files.go index 13b08c6d..c775177b 100644 --- a/internal/app/release_files.go +++ b/internal/app/release_files.go @@ -80,14 +80,17 @@ func (r *release) getValuesFiles() []string { fileList = append(fileList, r.SecretsFile+".dec") } else if len(r.SecretsFiles) > 0 { for i := 0; i < len(r.SecretsFiles); i++ { + if isOfType(r.SecretsFiles[i], []string{".dec"}) { + // if .dec extension is added before to the secret filename, don't add it again. + // This happens at upgrade time (where diff and upgrade both call this function) + // and we don't need to decrypt the file again + continue + } + if err := decryptSecret(r.SecretsFiles[i]); err != nil { log.Fatal(err.Error()) } - // if .dec extension is added before to the secret filename, don't add it again. - // This happens at upgrade time (where diff and upgrade both call this function) - if !isOfType(r.SecretsFiles[i], []string{".dec"}) { - r.SecretsFiles[i] = r.SecretsFiles[i] + ".dec" - } + r.SecretsFiles[i] = r.SecretsFiles[i] + ".dec" } fileList = append(fileList, r.SecretsFiles...) } From 161fdd398f0214d526aadd55137812db0ea85a1a Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Mon, 25 Jan 2021 12:21:52 +0000 Subject: [PATCH 0755/1127] fix: avoid decrypting the same secrets multiple times --- internal/app/release_files.go | 9 ++++++--- internal/app/utils.go | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/internal/app/release_files.go b/internal/app/release_files.go index 0e8b2599..242aebb7 100644 --- a/internal/app/release_files.go +++ b/internal/app/release_files.go @@ -74,10 +74,13 @@ func (r *release) getValuesFiles() []string { } } if r.SecretsFile != "" { - if err := decryptSecret(r.SecretsFile); err != nil { - log.Fatal(err.Error()) + if !isOfType(r.SecretsFile, []string{".dec"}) { + if err := decryptSecret(r.SecretsFile); err != nil { + log.Fatal(err.Error()) + } + r.SecretsFile = r.SecretsFile + ".dec" } - fileList = append(fileList, r.SecretsFile+".dec") + fileList = append(fileList, r.SecretsFile) } else if len(r.SecretsFiles) > 0 { for i := 0; i < len(r.SecretsFiles); i++ { if isOfType(r.SecretsFiles[i], []string{".dec"}) { diff --git a/internal/app/utils.go b/internal/app/utils.go index 0404f527..22da6b74 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -539,7 +539,7 @@ func notifyMSTeams(content string, url string, failure bool, executing bool) boo contentBold = strings.Join(contentSplit, "\n\n") } - var jsonStr = []byte(`{ + jsonStr := []byte(`{ "@type": "MessageCard", "@context": "http://schema.org/extensions", "themeColor": "` + color + `", From fcdc154d4b9e510d12b34fc5d57d0f632234edfa Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Mon, 25 Jan 2021 12:33:41 +0000 Subject: [PATCH 0756/1127] chore: update deps --- go.mod | 38 ++-- go.sum | 432 +++++++++++++++++++++++++++++++++++++-- internal/azure/azblob.go | 2 +- 3 files changed, 431 insertions(+), 41 deletions(-) diff --git a/go.mod b/go.mod index 29e4dada..ecd34b84 100644 --- a/go.mod +++ b/go.mod @@ -3,27 +3,29 @@ module github.com/Praqma/helmsman go 1.13 require ( - cloud.google.com/go v0.38.0 - github.com/Azure/azure-pipeline-go v0.1.9 - github.com/Azure/azure-storage-blob-go v0.0.0-20181022225951-5152f14ace1c + cloud.google.com/go v0.75.0 // indirect + cloud.google.com/go/storage v1.12.0 + github.com/Azure/azure-pipeline-go v0.2.3 + github.com/Azure/azure-storage-blob-go v0.12.0 + github.com/Azure/go-autorest/autorest/adal v0.9.10 // indirect github.com/BurntSushi/toml v0.3.1 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515211354-1abdf898e024 - github.com/aws/aws-sdk-go v1.26.2 - github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 // indirect - github.com/imdario/mergo v0.3.8 + github.com/aws/aws-sdk-go v1.36.31 + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/google/uuid v1.2.0 // indirect + github.com/imdario/mergo v0.3.11 github.com/joho/godotenv v1.3.0 - github.com/kr/pretty v0.1.0 // indirect + github.com/kr/text v0.2.0 // indirect github.com/logrusorgru/aurora v0.0.0-20191116043053-66b7ad493a23 - github.com/pkg/errors v0.8.1 // indirect - go.opencensus.io v0.22.2 // indirect - golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 - golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 // indirect - golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 // indirect - google.golang.org/api v0.14.0 // indirect - google.golang.org/appengine v1.6.5 // indirect - google.golang.org/genproto v0.0.0-20191206224255-0243a4be9c8f // indirect - google.golang.org/grpc v1.25.1 // indirect - gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect - gopkg.in/yaml.v2 v2.2.7 + golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect + golang.org/x/mod v0.4.1 // indirect + golang.org/x/net v0.0.0-20210119194325-5f4716e94777 + golang.org/x/oauth2 v0.0.0-20210113205817-d3ed898aa8a3 // indirect + golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect + golang.org/x/text v0.3.5 // indirect + golang.org/x/tools v0.1.0 // indirect + google.golang.org/genproto v0.0.0-20210122163508-8081c04a3579 // indirect + google.golang.org/grpc v1.35.0 // indirect + gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index b1a39ca0..6e53c997 100644 --- a/go.sum +++ b/go.sum @@ -2,147 +2,535 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -github.com/Azure/azure-pipeline-go v0.1.8/go.mod h1:XA1kFWRVhSK+KNFiOhfv83Fv8L9achrP7OxIzeTn1Yg= -github.com/Azure/azure-pipeline-go v0.1.9 h1:u7JFb9fFTE6Y/j8ae2VK33ePrRqJqoCM/IWkQdAZ+rg= -github.com/Azure/azure-pipeline-go v0.1.9/go.mod h1:XA1kFWRVhSK+KNFiOhfv83Fv8L9achrP7OxIzeTn1Yg= -github.com/Azure/azure-storage-blob-go v0.0.0-20181022225951-5152f14ace1c h1:Y5ueznoCekgCWBytF1Q9lTpZ3tJeX37dQtCcGjMCLYI= -github.com/Azure/azure-storage-blob-go v0.0.0-20181022225951-5152f14ace1c/go.mod h1:oGfmITT1V6x//CswqY2gtAHND+xIP64/qL7a5QJix0Y= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.66.0/go.mod h1:dgqGAjKCDxyhGTtC9dAREQGUJpkceNm1yt590Qno0Ko= +cloud.google.com/go v0.72.0 h1:eWRCuwubtDrCJG0oSUMgnsbD4CmPFQF2ei4OFbXvwww= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.75.0 h1:XgtDnVJRCPEUG21gjFiRPz4zI1Mjg16R+NYQjfmU4XY= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.12.0 h1:4y3gHptW1EHVtcPAVE0eBBlFuGqEejTTG3KdIE0lUX4= +cloud.google.com/go/storage v1.12.0/go.mod h1:fFLk2dp2oAhDz8QFKwqrjdJvxSp/W2g7nillojlL5Ho= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= +github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= +github.com/Azure/azure-storage-blob-go v0.12.0 h1:7bFXA1QB+lOK2/ASWHhp6/vnxjaeeZq6t8w1Jyp0Iaw= +github.com/Azure/azure-storage-blob-go v0.12.0/go.mod h1:A0u4VjtpgZJ7Y7um/+ix2DHBuEKFC6sEIlj0xc13a4Q= +github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest/adal v0.9.2/go.mod h1:/3SMAM86bP6wC9Ev35peQDUeqFZBMH07vvUOmg4z/fE= +github.com/Azure/go-autorest/autorest/adal v0.9.10 h1:r6fZHMaHD8B6LDCn0o5vyBFHIHrM6Ywwx7mb49lPItI= +github.com/Azure/go-autorest/autorest/adal v0.9.10/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= +github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515211354-1abdf898e024 h1:dfZ6RF0UxHqt7xPz0r7h00apsaa6rIrFhT6Xly55Exk= github.com/apsdehal/go-logger v0.0.0-20190515211354-1abdf898e024/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.26.2 h1:MzYLmCeny4bMQcAbYcucIduVZKp0sEf1eRLvHpKI5Is= -github.com/aws/aws-sdk-go v1.26.2/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.36.31 h1:BMVngapDGAfLBVEVzaSIw3fmJdWx7jOvhLCXgRXbXQI= +github.com/aws/aws-sdk-go v1.36.31/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 h1:uHTyIjqVhYRhLbJ8nIiOJHkEZZ+5YoOsAbD3sk82NiE= -github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0 h1:wCKgOCHuUEVfsaQLpPSJb7VdYCdTVZQAuOdYm1yc/60= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ= -github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/logrusorgru/aurora v0.0.0-20191116043053-66b7ad493a23 h1:Wp7NjqGKGN9te9N/rvXYRhlVcrulGdxnz8zadXWs7fc= github.com/logrusorgru/aurora v0.0.0-20191116043053-66b7ad493a23/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI= +github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0 h1:8pl+sMODzuvGJkmj2W4kZihvVb5mKm8pB/X44PIQHv8= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1 h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 h1:pE8b58s1HRDMi8RDc79m0HISf9D4TzseP40cEA6IGfs= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210113205817-d3ed898aa8a3 h1:BaN3BAqnopnKjvl+15DYP6LLrbBHfbfmlFYzmFj/Q9Q= +golang.org/x/oauth2 v0.0.0-20210113205817-d3ed898aa8a3/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 h1:gSbV7h1NRL2G1xTg/owz62CST1oJBmxy4QpMMregXVQ= -golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200828161849-5deb26317202/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20200915173823-2db8f0ff891c/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0 h1:uMf5uLi4eQMRrMKhCplNik4U4H8Z6C1br3zOtAa/aDE= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.31.0/go.mod h1:CL+9IBCa2WWU6gRuBWaKqGWLFFwbEUXkfeMkHLQWYWo= +google.golang.org/api v0.32.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0 h1:l2Nfbl2GPXdWorv+dT2XfinX2jOOw4zv1VhLstx+6rE= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20191206224255-0243a4be9c8f h1:naitw5DILWPQvG0oG04mR9jF8fmKpRdW3E3zzKA4D0Y= -google.golang.org/genproto v0.0.0-20191206224255-0243a4be9c8f/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200831141814-d751682dd103/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200914193844-75d14daec038/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200921151605-7abf4a1a14d5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210122163508-8081c04a3579 h1:Iwh0ba2kTgq2Q6mJiXhzrrjD7h11nEVnbMHFmp0/HsQ= +google.golang.org/genproto v0.0.0-20210122163508-8081c04a3579/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1 h1:wdKvqQk7IttEw92GoRyKG2IDrUIpgpj6H6m81yfeMW0= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0 h1:raiipEjMOIC/TO2AvyTxP25XFdLxNIBwzDh3FM3XztI= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0 h1:TwIQcH3es+MojMVojxxfQ3l3OF2KzlRxML2xZq0kRo8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= -gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/internal/azure/azblob.go b/internal/azure/azblob.go index b148eb1a..c80069ff 100644 --- a/internal/azure/azblob.go +++ b/internal/azure/azblob.go @@ -56,7 +56,7 @@ func ReadFile(containerName string, filename string, outFile string, noColors bo ctx := context.Background() blobURL := containerURL.NewBlockBlobURL(filename) - downloadResponse, err := blobURL.Download(ctx, 0, azblob.CountToEnd, azblob.BlobAccessConditions{}, false) + downloadResponse, err := blobURL.Download(ctx, 0, azblob.CountToEnd, azblob.BlobAccessConditions{}, false, azblob.ClientProvidedKeyOptions{}) if err != nil { log.Fatal(style.Bold(style.Red("ERROR: failed to download file " + filename + " with error: " + err.Error()))) } From 9776a91b902d0eda88b1c8d887953e9fc6c29c77 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Mon, 25 Jan 2021 12:43:05 +0000 Subject: [PATCH 0757/1127] Preparing release v3.6.4 --- .version | 2 +- README.md | 8 ++++---- internal/app/main.go | 2 +- release-notes.md | 12 +++++------- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/.version b/.version index 50dde2d0..5d7c78b1 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.6.3 +v3.6.4 diff --git a/README.md b/README.md index 1851399d..30b81e73 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.6.3&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.6.4&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -61,9 +61,9 @@ Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the d ```sh # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.3/helmsman_3.6.3_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.4/helmsman_3.6.4_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.3/helmsman_3.6.3_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.4/helmsman_3.6.4_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` @@ -78,7 +78,7 @@ Helmsman has been packaged in Archlinux under `helmsman-bin` for the latest bina # Documentation -> Documentation for Helmsman v1.x can be found at: https://github.com/Praqma/helmsman/tree/1.x/docs +> Documentation for Helmsman v1.x can be found at: [docs v1.x](https://github.com/Praqma/helmsman/tree/1.x/docs) - [How-Tos](https://github.com/Praqma/helmsman/blob/master/docs/how_to/). diff --git a/internal/app/main.go b/internal/app/main.go index 29d8bc75..29c154fe 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.6.3" + appVersion = "v3.6.4" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index a33f4dcb..e4191c53 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,4 +1,4 @@ -# v3.6.3 +# v3.6.4 If you migrating from Helmsman v1.x, it is recommended you read the [migration guide](https://github.com/Praqma/helmsman/blob/master/docs/how_to/misc/migrate_to_3.md) before using this release. @@ -6,9 +6,7 @@ If you migrating from Helmsman v1.x, it is recommended you read the [migration g ## Fixes and improvements -- Fixed missing diff on chart version change (#557) -- Fixed checking for updates on disabled releases (#557) -- Fixed segmentation fault on slack notifications (#559) -- Fixed failure to remove untracked releases (#566) -- The debug flag is now passed down to the helm commands (#568) -- Improved error reporting (#568) +- Fixed issue when decrypting multiple files would result in an error (#575). +- Fixed issue when resolving hooks paths (#569). +- Added support for MS Teams notifications (#574). +- Updated library dependencies. From bbd6cd825506a78d3b39a96fa39bd78e4116a516 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Wed, 10 Mar 2021 11:36:29 +0000 Subject: [PATCH 0758/1127] fix: panic if the file path starts with a prefix that matches a supported URL scheme --- go.sum | 26 ------------------------- internal/app/utils.go | 45 +++++++++++++++++++++++++++---------------- 2 files changed, 28 insertions(+), 43 deletions(-) diff --git a/go.sum b/go.sum index 6e53c997..a7d32cd5 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,5 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= @@ -15,7 +14,6 @@ cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZ cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.66.0/go.mod h1:dgqGAjKCDxyhGTtC9dAREQGUJpkceNm1yt590Qno0Ko= -cloud.google.com/go v0.72.0 h1:eWRCuwubtDrCJG0oSUMgnsbD4CmPFQF2ei4OFbXvwww= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.75.0 h1:XgtDnVJRCPEUG21gjFiRPz4zI1Mjg16R+NYQjfmU4XY= cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= @@ -67,13 +65,11 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -89,7 +85,6 @@ github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoD github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -104,7 +99,6 @@ github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= @@ -121,7 +115,6 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -148,7 +141,6 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -170,12 +162,9 @@ github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqx github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= @@ -192,7 +181,6 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -201,7 +189,6 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -236,7 +223,6 @@ golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= @@ -248,7 +234,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0 h1:8pl+sMODzuvGJkmj2W4kZihvVb5mKm8pB/X44PIQHv8= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1 h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -265,7 +250,6 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -290,7 +274,6 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 h1:pE8b58s1HRDMi8RDc79m0HISf9D4TzseP40cEA6IGfs= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= @@ -302,7 +285,6 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -348,10 +330,8 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -417,7 +397,6 @@ google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0 h1:uMf5uLi4eQMRrMKhCplNik4U4H8Z6C1br3zOtAa/aDE= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= @@ -438,7 +417,6 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= @@ -485,7 +463,6 @@ google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZi google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1 h1:wdKvqQk7IttEw92GoRyKG2IDrUIpgpj6H6m81yfeMW0= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= @@ -497,7 +474,6 @@ google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0 h1:raiipEjMOIC/TO2AvyTxP25XFdLxNIBwzDh3FM3XztI= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0 h1:TwIQcH3es+MojMVojxxfQ3l3OF2KzlRxML2xZq0kRo8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= @@ -507,14 +483,12 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/app/utils.go b/internal/app/utils.go index 22da6b74..55ca3f15 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -199,30 +199,37 @@ func sliceContains(slice []string, s string) bool { // and saves it with a given outfile name and in a given dir // if downloaded, returns the outfile name. If the file path is local file system path, it is copied to current directory. func downloadFile(file string, dir string, outfile string) string { - if strings.HasPrefix(file, "http") { + u, err := url.Parse(file) + if err != nil { + log.Fatalf("%s is not a valid path: %s", file, err) + } + + switch u.Scheme { + case "https", "http": if err := downloadFileFromURL(file, outfile); err != nil { log.Fatal(err.Error()) } - } else if strings.HasPrefix(file, "s3") { - + case "s3": tmp := getBucketElements(file) + if tmp == nil { + log.Fatalf("%s is not a valid bucket URL", file) + } aws.ReadFile(tmp["bucketName"], tmp["filePath"], outfile, flags.noColors) - - } else if strings.HasPrefix(file, "gs") { - + case "gs": tmp := getBucketElements(file) - msg, err := gcs.ReadFile(tmp["bucketName"], tmp["filePath"], outfile, flags.noColors) - if err != nil { + if tmp == nil { + log.Fatalf("%s is not a valid bucket URL", file) + } + if msg, err := gcs.ReadFile(tmp["bucketName"], tmp["filePath"], outfile, flags.noColors); err != nil { log.Fatal(msg) } - - } else if strings.HasPrefix(file, "az") { - + case "az": tmp := getBucketElements(file) + if tmp == nil { + log.Fatalf("%s is not a valid bucket URL", file) + } azure.ReadFile(tmp["bucketName"], tmp["filePath"], outfile, flags.noColors) - - } else { - + default: log.Verbose("" + file + " will be used from local file system.") toCopy := file if !filepath.IsAbs(file) { @@ -230,6 +237,7 @@ func downloadFile(file string, dir string, outfile string) string { } copyFile(toCopy, outfile) } + return outfile } @@ -362,10 +370,13 @@ func notifySlack(content string, url string, failure bool, executing bool) bool // this func works for S3, Azure and GCS bucket links of the format: // s3 or gs://bucketname/dir.../file.ext func getBucketElements(link string) map[string]string { - tmp := strings.SplitAfterN(link, "//", 2)[1] + u, err := url.Parse(link) + if err != nil { + return nil + } m := make(map[string]string) - m["bucketName"] = strings.SplitN(tmp, "/", 2)[0] - m["filePath"] = strings.SplitN(tmp, "/", 2)[1] + m["bucketName"] = u.Host + m["filePath"] = u.Path return m } From e8d750e8d6cedbb63f376b909b49f652915a02d3 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Wed, 10 Mar 2021 11:46:41 +0000 Subject: [PATCH 0759/1127] refactor: remove getBucketElements method Signed-off-by: Luis Davim --- internal/app/utils.go | 34 ++++------------------------------ 1 file changed, 4 insertions(+), 30 deletions(-) diff --git a/internal/app/utils.go b/internal/app/utils.go index 55ca3f15..f5ea656a 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -210,27 +210,15 @@ func downloadFile(file string, dir string, outfile string) string { log.Fatal(err.Error()) } case "s3": - tmp := getBucketElements(file) - if tmp == nil { - log.Fatalf("%s is not a valid bucket URL", file) - } - aws.ReadFile(tmp["bucketName"], tmp["filePath"], outfile, flags.noColors) + aws.ReadFile(u.Host, u.Path, outfile, flags.noColors) case "gs": - tmp := getBucketElements(file) - if tmp == nil { - log.Fatalf("%s is not a valid bucket URL", file) - } - if msg, err := gcs.ReadFile(tmp["bucketName"], tmp["filePath"], outfile, flags.noColors); err != nil { + if msg, err := gcs.ReadFile(u.Host, u.Path, outfile, flags.noColors); err != nil { log.Fatal(msg) } case "az": - tmp := getBucketElements(file) - if tmp == nil { - log.Fatalf("%s is not a valid bucket URL", file) - } - azure.ReadFile(tmp["bucketName"], tmp["filePath"], outfile, flags.noColors) + azure.ReadFile(u.Host, u.Path, outfile, flags.noColors) default: - log.Verbose("" + file + " will be used from local file system.") + log.Verbose(file + " will be used from local file system.") toCopy := file if !filepath.IsAbs(file) { toCopy, _ = filepath.Abs(filepath.Join(dir, file)) @@ -366,20 +354,6 @@ func notifySlack(content string, url string, failure bool, executing bool) bool return true } -// getBucketElements returns a map containing the bucket name and the file path inside the bucket -// this func works for S3, Azure and GCS bucket links of the format: -// s3 or gs://bucketname/dir.../file.ext -func getBucketElements(link string) map[string]string { - u, err := url.Parse(link) - if err != nil { - return nil - } - m := make(map[string]string) - m["bucketName"] = u.Host - m["filePath"] = u.Path - return m -} - // replaceStringInFile takes a map of keys and values and replaces the keys with values within a given file. // It saves the modified content in a new file func replaceStringInFile(input []byte, outfile string, replacements map[string]string) { From 8d1b224aa0c4cde772b8c5216bd5806ad285db5f Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Wed, 10 Mar 2021 15:36:58 +0000 Subject: [PATCH 0760/1127] prepare release v3.6.5 --- .version | 2 +- README.md | 6 +++--- internal/app/main.go | 2 +- release-notes.md | 7 ++----- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/.version b/.version index 5d7c78b1..df731bb0 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.6.4 +v3.6.5 diff --git a/README.md b/README.md index 30b81e73..469b887e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.6.4&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.6.5&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -61,9 +61,9 @@ Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the d ```sh # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.4/helmsman_3.6.4_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.5/helmsman_3.6.4_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.4/helmsman_3.6.4_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.5/helmsman_3.6.4_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/internal/app/main.go b/internal/app/main.go index 29c154fe..e1bf627d 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.6.4" + appVersion = "v3.6.5" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index e4191c53..b69343c4 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,4 +1,4 @@ -# v3.6.4 +# v3.6.5 If you migrating from Helmsman v1.x, it is recommended you read the [migration guide](https://github.com/Praqma/helmsman/blob/master/docs/how_to/misc/migrate_to_3.md) before using this release. @@ -6,7 +6,4 @@ If you migrating from Helmsman v1.x, it is recommended you read the [migration g ## Fixes and improvements -- Fixed issue when decrypting multiple files would result in an error (#575). -- Fixed issue when resolving hooks paths (#569). -- Added support for MS Teams notifications (#574). -- Updated library dependencies. +- Fixed issue when the path to a file would match a URL schema prefix (#587). From 1c3bf2c7a5ea569e9e791eb7a677fc7560839935 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sat, 13 Mar 2021 00:40:35 +0000 Subject: [PATCH 0761/1127] fix: race condition when applying namespace quotas Signed-off-by: Luis Davim --- internal/app/kube_helpers.go | 38 ++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/internal/app/kube_helpers.go b/internal/app/kube_helpers.go index 5bfb6dac..066422d8 100644 --- a/internal/app/kube_helpers.go +++ b/internal/app/kube_helpers.go @@ -4,7 +4,7 @@ import ( "errors" "fmt" "io/ioutil" - "path" + "os" "strings" "sync" @@ -119,19 +119,10 @@ spec: } definition = definition + Indent(string(d), strings.Repeat(" ", 4)) - targetFile := path.Join(createTempDir(tempFilesDir, "tmp"), "temp-LimitRange.yaml") - if err := ioutil.WriteFile(targetFile, []byte(definition), 0o666); err != nil { - log.Fatal(err.Error()) - } - - cmd := kubectl([]string{"apply", "-f", targetFile, "-n", ns, flags.getKubeDryRunFlag("apply")}, "Creating LimitRange in namespace [ "+ns+" ]") - result := cmd.Exec() - if result.code != 0 { - log.Fatal("Failed to create LimitRange in namespace [ " + ns + " ] with error: " + result.errors) + if err := apply(definition, ns, "LimitRange"); err != nil { + log.Fatal(err.Error()) } - - deleteFile(targetFile) } func setQuotas(ns string, quotas *quotas) { @@ -163,18 +154,31 @@ spec: definition = definition + Indent(string(d), strings.Repeat(" ", 4)) - if err := ioutil.WriteFile("temp-ResourceQuota.yaml", []byte(definition), 0o666); err != nil { + if err := apply(definition, ns, "ResourceQuota"); err != nil { log.Fatal(err.Error()) } +} - cmd := kubectl([]string{"apply", "-f", "temp-ResourceQuota.yaml", "-n", ns, flags.getKubeDryRunFlag("apply")}, "Creating ResourceQuota in namespace [ "+ns+" ]") - result := cmd.Exec() +func apply(definition, ns, kind string) error { + targetFile, err := ioutil.TempFile(tempFilesDir, kind+"-*.yaml") + if err != nil { + return err + } + defer os.Remove(targetFile.Name()) - deleteFile("temp-ResourceQuota.yaml") + if _, err = targetFile.Write([]byte(definition)); err != nil { + return err + } + + cmd := kubectl([]string{"apply", "-f", targetFile.Name(), "-n", ns, flags.getKubeDryRunFlag("apply")}, + "Creating "+kind+" in namespace [ "+ns+" ]") + result := cmd.Exec() if result.code != 0 { - log.Fatal("ERROR: failed to create ResourceQuota in namespace [ " + ns + " ]: " + result.errors) + return fmt.Errorf("ERROR: failed to create %s in namespace [ %s ]: %s", kind, ns, result.errors) } + + return nil } // createContext creates a context -connecting to a k8s cluster- in kubectl config. From b30358ab94dde52ee317d4274e77826c770eacdf Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sat, 13 Mar 2021 13:31:38 +0000 Subject: [PATCH 0762/1127] prepare release v3.6.6 --- .version | 2 +- README.md | 6 +++--- internal/app/main.go | 2 +- release-notes.md | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.version b/.version index df731bb0..dde31d2e 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.6.5 +v3.6.6 diff --git a/README.md b/README.md index 469b887e..20858752 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.6.5&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.6.6&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -61,9 +61,9 @@ Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the d ```sh # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.5/helmsman_3.6.4_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.6/helmsman_3.6.4_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.5/helmsman_3.6.4_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.6/helmsman_3.6.4_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/internal/app/main.go b/internal/app/main.go index e1bf627d..1cb972ef 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.6.5" + appVersion = "v3.6.6" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index b69343c4..35643a2c 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,4 +1,4 @@ -# v3.6.5 +# v3.6.6 If you migrating from Helmsman v1.x, it is recommended you read the [migration guide](https://github.com/Praqma/helmsman/blob/master/docs/how_to/misc/migrate_to_3.md) before using this release. @@ -6,4 +6,4 @@ If you migrating from Helmsman v1.x, it is recommended you read the [migration g ## Fixes and improvements -- Fixed issue when the path to a file would match a URL schema prefix (#587). +- Fixed race condition when applying namespace quotas (#589) From ac6b7cca3d35d3fe98b2f46b4d678f5189c62911 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sat, 13 Mar 2021 14:02:18 +0000 Subject: [PATCH 0763/1127] feat: add flag to skip helm repo update --- internal/app/cli.go | 2 ++ internal/app/helm_helpers.go | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/internal/app/cli.go b/internal/app/cli.go index de7e068e..93f2edca 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -66,6 +66,7 @@ type cli struct { migrateContext bool parallel int alwaysUpgrade bool + noUpdate bool } func printUsage() { @@ -112,6 +113,7 @@ func (c *cli) parse() { flag.BoolVar(&c.noCleanup, "no-cleanup", false, "keeps any credentials files that has been downloaded on the host where helmsman runs.") flag.BoolVar(&c.migrateContext, "migrate-context", false, "updates the context name for all apps defined in the DSF and applies Helmsman labels. Using this flag is required if you want to change context name after it has been set.") flag.BoolVar(&c.alwaysUpgrade, "always-upgrade", false, "upgrade release even if no changes are found") + flag.BoolVar(&c.noUpdate, "no-update", false, "skip updating helm repos") flag.Usage = printUsage flag.Parse() diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index 95c8c890..1f2c49b3 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -152,13 +152,17 @@ func addHelmRepos(repos map[string]string) error { repoAddFlags += "--force-update" } - for repoName, repoLink := range repos { + for repoName, repoURL := range repos { basicAuthArgs := []string{} + u, err := url.Parse(repoURL) + if err != nil { + log.Fatal("failed to add helm repo: " + err.Error()) + } // check if repo is in GCS, then perform GCS auth -- needed for private GCS helm repos // failed auth would not throw an error here, as it is possible that the repo is public and does not need authentication - if strings.HasPrefix(repoLink, "gs://") { + if u.Scheme == "gs" { if !helmPluginExists("gcs") { - log.Fatal(fmt.Sprintf("repository %s can't be used: helm-gcs plugin is missing", repoLink)) + log.Fatal(fmt.Sprintf("repository %s can't be used: helm-gcs plugin is missing", repoURL)) } msg, err := gcs.Auth() if err != nil { @@ -166,10 +170,6 @@ func addHelmRepos(repos map[string]string) error { } } - u, err := url.Parse(repoLink) - if err != nil { - log.Fatal("failed to add helm repo: " + err.Error()) - } if u.User != nil { p, ok := u.User.Password() if !ok { @@ -177,22 +177,22 @@ func addHelmRepos(repos map[string]string) error { } basicAuthArgs = append(basicAuthArgs, "--username", u.User.Username(), "--password", p) u.User = nil - repoLink = u.String() + repoURL = u.String() } - cmd := helmCmd(concat([]string{"repo", "add", repoAddFlags, repoName, repoLink}, basicAuthArgs), "Adding helm repository [ "+repoName+" ]") // check current repository against existing repositories map in order to make sure it's missing and needs to be added if existingRepoURL, ok := existingRepos[repoName]; ok { - if repoLink == existingRepoURL { + if repoURL == existingRepoURL { continue } } + cmd := helmCmd(concat([]string{"repo", "add", repoAddFlags, repoName, repoURL}, basicAuthArgs), "Adding helm repository [ "+repoName+" ]") if result := cmd.Exec(); result.code != 0 { return fmt.Errorf("While adding helm repository ["+repoName+"]: %s", result.errors) } } - if len(repos) > 0 { + if !flags.noUpdate && len(repos) > 0 { cmd := helmCmd([]string{"repo", "update"}, "Updating helm repositories") if result := cmd.Exec(); result.code != 0 { From eaefcd744286a3bdc0d7d6d53e417f90f8a194aa Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sat, 20 Mar 2021 19:24:42 +0000 Subject: [PATCH 0764/1127] fix: dont expect a username and password if the caClient cert is present --- internal/app/state.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/internal/app/state.go b/internal/app/state.go index 6464ff4c..24500e14 100644 --- a/internal/app/state.go +++ b/internal/app/state.go @@ -98,6 +98,8 @@ func (s *state) validate() error { "kubeContext to use. Either define it in the desired state file or pass a kubeconfig with --kubeconfig to use an existing context") } + _, caClient := s.Certificates["caClient"] + if s.Settings.ClusterURI != "" { if _, err := url.ParseRequestURI(s.Settings.ClusterURI); err != nil { return errors.New("settings validation failed -- clusterURI must have a valid URL set in an env variable or passed directly. Either the env var is missing/empty or the URL is invalid") @@ -105,11 +107,13 @@ func (s *state) validate() error { if s.Settings.KubeContext == "" { return errors.New("settings validation failed -- KubeContext needs to be provided in the settings stanza") } - if !s.Settings.BearerToken && s.Settings.Username == "" { - return errors.New("settings validation failed -- username needs to be provided in the settings stanza") - } - if !s.Settings.BearerToken && s.Settings.Password == "" { - return errors.New("settings validation failed -- password needs to be provided (directly or from env var) in the settings stanza") + if !s.Settings.BearerToken && !caClient { + if s.Settings.Username == "" { + return errors.New("settings validation failed -- username needs to be provided in the settings stanza") + } + if s.Settings.Password == "" { + return errors.New("settings validation failed -- password needs to be provided (directly or from env var) in the settings stanza") + } } if s.Settings.BearerToken && s.Settings.BearerTokenPath != "" { if _, err := os.Stat(s.Settings.BearerTokenPath); err != nil { From 472f2a86c35e11903eea226d2b4dc29b709c9bc7 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sun, 21 Mar 2021 17:18:25 +0000 Subject: [PATCH 0765/1127] fix: skip helm tests in dry-run mode --- internal/app/release.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/app/release.go b/internal/app/release.go index b367aca5..cf54520a 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -128,6 +128,10 @@ func (r *release) validate(appLabel string, seen map[string]map[string]bool, s * // testRelease creates a Helm command to test a particular release. func (r *release) test(afterCommands *[]hookCmd) { + if flags.dryRun { + log.Verbose("Dry-run, skipping tests: " + r.Name) + return + } cmd := helmCmd(r.getHelmArgsFor("test"), "Running tests for release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") *afterCommands = append([]hookCmd{{Command: cmd, Type: test}}, *afterCommands...) } From 669220864bd50650a929267ff8693583a201d508 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sat, 27 Mar 2021 14:15:05 +0000 Subject: [PATCH 0766/1127] fix: properly expand relative hook file paths Signed-off-by: Luis Davim --- internal/app/decision_maker_test.go | 6 +++--- internal/app/hooks.go | 10 +++++++--- internal/app/release.go | 12 +++++------- internal/app/release_files.go | 11 ++++++++--- internal/app/state_files.go | 8 ++++++-- 5 files changed, 29 insertions(+), 18 deletions(-) diff --git a/internal/app/decision_maker_test.go b/internal/app/decision_maker_test.go index a85d0ec5..c165e360 100644 --- a/internal/app/decision_maker_test.go +++ b/internal/app/decision_maker_test.go @@ -27,7 +27,7 @@ func Test_getValuesFiles(t *testing.T) { ValuesFile: "../../tests/values.yaml", Test: true, }, - //s: st, + // s: st, }, want: []string{"-f", "../../tests/values.yaml"}, }, @@ -44,7 +44,7 @@ func Test_getValuesFiles(t *testing.T) { ValuesFiles: []string{"../../tests/values.yaml"}, Test: true, }, - //s: st, + // s: st, }, want: []string{"-f", "../../tests/values.yaml"}, }, @@ -61,7 +61,7 @@ func Test_getValuesFiles(t *testing.T) { ValuesFiles: []string{"../../tests/values.yaml", "../../tests/values2.yaml"}, Test: true, }, - //s: st, + // s: st, }, want: []string{"-f", "../../tests/values.yaml", "-f", "../../tests/values2.yaml"}, }, diff --git a/internal/app/hooks.go b/internal/app/hooks.go index 8ea0ec1d..ea012fb2 100644 --- a/internal/app/hooks.go +++ b/internal/app/hooks.go @@ -15,6 +15,11 @@ const ( test = "test" ) +var ( + validManifestFiles = []string{".yaml", ".yml", ".json"} + validHookFiles = []string{".yaml", ".yml", ".json", ".sh", ".py", ".rb"} +) + // TODO: Create different types for Command and Manifest hooks // with methods for getting their commands for the plan type hookCmd struct { @@ -31,15 +36,14 @@ func (h *hookCmd) getAnnotationKey() (string, error) { // validateHooks validates that hook files exist and are of correct type func validateHooks(hooks map[string]interface{}) error { - validFiles := []string{".yaml", ".yml", ".json"} for key, value := range hooks { switch key { case preInstall, postInstall, preUpgrade, postUpgrade, preDelete, postDelete: hook := value.(string) - if !isOfType(hook, validFiles) && ToolExists(strings.Fields(hook)[0]) { + if !isOfType(hook, validManifestFiles) && ToolExists(strings.Fields(hook)[0]) { return nil } - if err := isValidFile(hook, validFiles); err != nil { + if err := isValidFile(hook, validManifestFiles); err != nil { return fmt.Errorf("invalid hook manifest: %w", err) } case "successCondition", "successTimeout", "deleteOnSuccess": diff --git a/internal/app/release.go b/internal/app/release.go index cf54520a..d7d3755a 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -74,17 +74,15 @@ func (r *release) validate(appLabel string, seen map[string]map[string]bool, s * return errors.New("version can't be empty") } - validFiles := []string{".yaml", ".yml", ".json"} - if r.ValuesFile != "" && len(r.ValuesFiles) > 0 { return errors.New("valuesFile and valuesFiles should not be used together") } else if r.ValuesFile != "" { - if err := isValidFile(r.ValuesFile, validFiles); err != nil { + if err := isValidFile(r.ValuesFile, validManifestFiles); err != nil { return fmt.Errorf("invalid values file: %w", err) } } else if len(r.ValuesFiles) > 0 { for _, filePath := range r.ValuesFiles { - if err := isValidFile(filePath, validFiles); err != nil { + if err := isValidFile(filePath, validManifestFiles); err != nil { return fmt.Errorf("invalid values file: %w", err) } } @@ -93,12 +91,12 @@ func (r *release) validate(appLabel string, seen map[string]map[string]bool, s * if r.SecretsFile != "" && len(r.SecretsFiles) > 0 { return errors.New("secretsFile and secretsFiles should not be used together") } else if r.SecretsFile != "" { - if err := isValidFile(r.SecretsFile, validFiles); err != nil { + if err := isValidFile(r.SecretsFile, validManifestFiles); err != nil { return fmt.Errorf("invalid secrets file: %w", err) } } else if len(r.SecretsFiles) > 0 { for _, filePath := range r.SecretsFiles { - if err := isValidFile(filePath, validFiles); err != nil { + if err := isValidFile(filePath, validManifestFiles); err != nil { return fmt.Errorf("invalid secrets file: %w", err) } } @@ -488,7 +486,7 @@ func (r *release) getHookCommands(hookType, ns string) []hookCmd { var cmds []hookCmd if _, ok := r.Hooks[hookType]; ok { hook := r.Hooks[hookType].(string) - if err := isValidFile(hook, []string{".yaml", ".yml", ".json"}); err == nil { + if err := isValidFile(hook, validManifestFiles); err == nil { cmd := kubectl([]string{"apply", "-n", ns, "-f", hook, flags.getKubeDryRunFlag("apply")}, "Apply "+hook+" manifest "+hookType) cmds = append(cmds, hookCmd{Command: cmd, Type: hookType}) if wait, waitCmds := r.shouldWaitForHook(hook, hookType, ns); wait { diff --git a/internal/app/release_files.go b/internal/app/release_files.go index 242aebb7..ab0fe6b3 100644 --- a/internal/app/release_files.go +++ b/internal/app/release_files.go @@ -1,5 +1,9 @@ package app +import ( + "strings" +) + // substituteVarsInStaticFiles loops through the values/secrets files and substitutes variables into them. func (r *release) substituteVarsInStaticFiles() { if r.ValuesFile != "" { @@ -44,9 +48,10 @@ func (r *release) resolvePaths(dir, downloadDest string) { for key, val := range r.Hooks { if key != "deleteOnSuccess" && key != "successTimeout" && key != "successCondition" { - file := val.(string) - if isOfType(file, []string{".yaml", ".yml", ".json", ".sh", ".py", ".rb"}) { - r.Hooks[key], _ = resolveOnePath(file, dir, downloadDest) + hook := strings.Fields(val.(string)) + if isOfType(hook[0], validHookFiles) && !ToolExists(hook[0]) { + hook[0], _ = resolveOnePath(hook[0], dir, downloadDest) + r.Hooks[key] = strings.Join(hook, " ") } } } diff --git a/internal/app/state_files.go b/internal/app/state_files.go index 89b664a1..58a6409c 100644 --- a/internal/app/state_files.go +++ b/internal/app/state_files.go @@ -7,6 +7,7 @@ import ( "os" "path/filepath" "strconv" + "strings" "github.com/BurntSushi/toml" "gopkg.in/yaml.v2" @@ -168,8 +169,11 @@ func (s *state) expand(relativeToFile string) { for key, val := range s.Settings.GlobalHooks { if key != "deleteOnSuccess" && key != "successTimeout" && key != "successCondition" { file := val.(string) - if isOfType(file, []string{".yaml", ".yml", ".json", ".sh", ".py", ".rb"}) { - s.Settings.GlobalHooks[key], _ = resolveOnePath(file, dir, downloadDest) + hook := strings.Fields(file) + if isOfType(hook[0], validHookFiles) && !ToolExists(hook[0]) { + hook[0], _ = resolveOnePath(hook[0], dir, downloadDest) + file = strings.Join(hook, " ") + s.Settings.GlobalHooks[key] = file } if isOfType(file, []string{".yaml", ".yml"}) { s.Settings.GlobalHooks[key] = substituteVarsInYaml(file) From 26948f7018250a78b3da681dfc80b0da575622e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Desch=C3=AAnes?= Date: Mon, 29 Mar 2021 10:06:09 -0400 Subject: [PATCH 0767/1127] replace deprecated helm-secrets repo Helm plugin is currently maintained here: https://github.com/jkroepke/helm-secrets --- Dockerfile | 2 +- Makefile | 2 +- docs/how_to/apps/secrets.md | 2 +- scripts/setup.sh | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index b91bb5ad..8350da4c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,7 +25,7 @@ RUN chmod +x /usr/local/bin/helm RUN helm plugin install https://github.com/hypnoglow/helm-s3.git RUN helm plugin install https://github.com/nouney/helm-gcs RUN helm plugin install https://github.com/databus23/helm-diff --version ${HELM_DIFF_VERSION} -RUN helm plugin install https://github.com/futuresimple/helm-secrets +RUN helm plugin install https://github.com/jkroepke/helm-secrets RUN rm -r /tmp/helm-diff /tmp/helm-diff.tgz ### Go Builder & Tester ### diff --git a/Makefile b/Makefile index e8767a9f..e518c685 100644 --- a/Makefile +++ b/Makefile @@ -95,5 +95,5 @@ helmPlugins: ## Install helm plugins used by Helmsman @helm plugin install https://github.com/hypnoglow/helm-s3.git @helm plugin install https://github.com/nouney/helm-gcs @helm plugin install https://github.com/databus23/helm-diff - @helm plugin install https://github.com/futuresimple/helm-secrets + @helm plugin install https://github.com/jkroepke/helm-secrets .PHONY: helmPlugins diff --git a/docs/how_to/apps/secrets.md b/docs/how_to/apps/secrets.md index 01d91d8d..d5697de7 100644 --- a/docs/how_to/apps/secrets.md +++ b/docs/how_to/apps/secrets.md @@ -73,7 +73,7 @@ BAR: baz ## Passing secrets using helm secrets plugin -You can also use the [helm secrets plugin](https://github.com/futuresimple/helm-secrets) to pass your secrets. +You can also use the [helm secrets plugin](https://github.com/jkroepke/helm-secrets) to pass your secrets. ## Passing secrets using hiera eyaml diff --git a/scripts/setup.sh b/scripts/setup.sh index 9a753ced..9cd7f409 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -15,6 +15,6 @@ mkdir -p ~/.helm/plugins helm plugin install https://github.com/hypnoglow/helm-s3.git helm plugin install https://github.com/nouney/helm-gcs helm plugin install https://github.com/databus23/helm-diff --version ${HELM_DIFF_VERSION} -helm plugin install https://github.com/futuresimple/helm-secrets +helm plugin install https://github.com/jkroepke/helm-secrets rm -r /tmp/helm-diff /tmp/helm-diff.tgz gem install hiera-eyaml --no-doc From 14efa5dbeeec5a37d9dafa6c1a1565144e0f122a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Desch=C3=AAnes?= Date: Tue, 30 Mar 2021 10:18:42 -0400 Subject: [PATCH 0768/1127] Dockerfile: Manage sops version Helm plugin 'helm-secrets' stopped installing it silently. --- Dockerfile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Dockerfile b/Dockerfile index 8350da4c..26d4b093 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,18 +3,24 @@ ARG ALPINE_VERSION="3.12" ARG GLOBAL_KUBE_VERSION="v1.19.0" ARG GLOBAL_HELM_VERSION="v3.3.4" ARG GLOBAL_HELM_DIFF_VERSION="v3.1.3" +ARG GLOBAL_SOPS_VERSION="v3.7.0" ### Helm Installer ### FROM alpine:${ALPINE_VERSION} as helm-installer ARG GLOBAL_KUBE_VERSION ARG GLOBAL_HELM_VERSION ARG GLOBAL_HELM_DIFF_VERSION +ARG GLOBAL_SOPS_VERSION ENV KUBE_VERSION=$GLOBAL_KUBE_VERSION ENV HELM_VERSION=$GLOBAL_HELM_VERSION ENV HELM_DIFF_VERSION=$GLOBAL_HELM_DIFF_VERSION +ENV SOPS_VERSION=$GLOBAL_SOPS_VERSION RUN apk add --update --no-cache ca-certificates git openssh ruby curl tar gzip make bash +ADD https://github.com/mozilla/sops/releases/download/${SOPS_VERSION}/sops-${SOPS_VERSION}.linux /usr/local/bin/sops +RUN chmod +x /usr/local/bin/sops + RUN curl --retry 5 -L https://storage.googleapis.com/kubernetes-release/release/${KUBE_VERSION}/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl RUN chmod +x /usr/local/bin/kubectl From b54278bbb1bcb7d177189a94c7bac89f8c0c64ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Desch=C3=AAnes?= Date: Tue, 30 Mar 2021 10:33:09 -0400 Subject: [PATCH 0769/1127] scripts: add sops installation --- scripts/setup.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/setup.sh b/scripts/setup.sh index 9cd7f409..e5f30a32 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -6,6 +6,9 @@ apk add --update --no-cache ca-certificates git openssh ruby curl tar gzip make curl --retry 5 -L https://storage.googleapis.com/kubernetes-release/release/${KUBE_VERSION}/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl chmod +x /usr/local/bin/kubectl +curl --retry 5 -L https://github.com/mozilla/sops/releases/download/${SOPS_VERSION}/sops-${SOPS_VERSION}.linux -o /usr/local/bin/sops +chmod +x /usr/local/bin/sops + curl --retry 5 -L https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz | tar zxv -C /tmp mv /tmp/linux-amd64/helm /usr/local/bin/helm rm -rf /tmp/linux-amd64 From 83cc41933cb38f30b90839aee22ee3ef6c0aa0d9 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Wed, 7 Apr 2021 22:32:21 +0100 Subject: [PATCH 0770/1127] chore: use the latest helm version Signed-off-by: Luis Davim --- .circleci/config.yml | 2 +- Dockerfile | 2 +- go.mod | 29 +++++----- go.sum | 135 ++++++++++++++++++++++++++++--------------- 4 files changed, 106 insertions(+), 62 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ba289197..6d8dec60 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -44,7 +44,7 @@ jobs: - run: name: build docker images and push them to dockerhub command: | - helm_versions=( "v3.4.0" "v3.3.4" ) + helm_versions=( "v3.3.4" "v3.4.2" "3.5.3" ) TAG=$(git describe --abbrev=0 --tags) docker login -u $DOCKER_USER -p $DOCKER_PASS diff --git a/Dockerfile b/Dockerfile index 26d4b093..3afe62d4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ ARG GO_VERSION="1.15.2" ARG ALPINE_VERSION="3.12" ARG GLOBAL_KUBE_VERSION="v1.19.0" -ARG GLOBAL_HELM_VERSION="v3.3.4" +ARG GLOBAL_HELM_VERSION="v3.5.3" ARG GLOBAL_HELM_DIFF_VERSION="v3.1.3" ARG GLOBAL_SOPS_VERSION="v3.7.0" diff --git a/go.mod b/go.mod index ecd34b84..be91db76 100644 --- a/go.mod +++ b/go.mod @@ -3,29 +3,28 @@ module github.com/Praqma/helmsman go 1.13 require ( - cloud.google.com/go v0.75.0 // indirect - cloud.google.com/go/storage v1.12.0 + cloud.google.com/go/storage v1.14.0 github.com/Azure/azure-pipeline-go v0.2.3 - github.com/Azure/azure-storage-blob-go v0.12.0 - github.com/Azure/go-autorest/autorest/adal v0.9.10 // indirect + github.com/Azure/azure-storage-blob-go v0.13.0 + github.com/Azure/go-autorest/autorest/adal v0.9.13 // indirect github.com/BurntSushi/toml v0.3.1 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515211354-1abdf898e024 - github.com/aws/aws-sdk-go v1.36.31 + github.com/aws/aws-sdk-go v1.38.15 github.com/davecgh/go-spew v1.1.1 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/uuid v1.2.0 // indirect - github.com/imdario/mergo v0.3.11 + github.com/imdario/mergo v0.3.12 github.com/joho/godotenv v1.3.0 github.com/kr/text v0.2.0 // indirect github.com/logrusorgru/aurora v0.0.0-20191116043053-66b7ad493a23 - golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect - golang.org/x/mod v0.4.1 // indirect - golang.org/x/net v0.0.0-20210119194325-5f4716e94777 - golang.org/x/oauth2 v0.0.0-20210113205817-d3ed898aa8a3 // indirect - golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect - golang.org/x/text v0.3.5 // indirect - golang.org/x/tools v0.1.0 // indirect - google.golang.org/genproto v0.0.0-20210122163508-8081c04a3579 // indirect - google.golang.org/grpc v1.35.0 // indirect + golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect + golang.org/x/mod v0.4.2 // indirect + golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 + golang.org/x/text v0.3.6 // indirect + google.golang.org/api v0.44.0 // indirect + google.golang.org/genproto v0.0.0-20210406143921-e86de6bf7a46 // indirect + google.golang.org/grpc v1.37.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index a7d32cd5..b0a178e3 100644 --- a/go.sum +++ b/go.sum @@ -13,10 +13,13 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.66.0/go.mod h1:dgqGAjKCDxyhGTtC9dAREQGUJpkceNm1yt590Qno0Ko= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.75.0 h1:XgtDnVJRCPEUG21gjFiRPz4zI1Mjg16R+NYQjfmU4XY= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -34,22 +37,24 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.12.0 h1:4y3gHptW1EHVtcPAVE0eBBlFuGqEejTTG3KdIE0lUX4= -cloud.google.com/go/storage v1.12.0/go.mod h1:fFLk2dp2oAhDz8QFKwqrjdJvxSp/W2g7nillojlL5Ho= +cloud.google.com/go/storage v1.14.0 h1:6RRlFMv1omScs6iq2hfE3IvgE+l6RfJPampq8UZc5TU= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= -github.com/Azure/azure-storage-blob-go v0.12.0 h1:7bFXA1QB+lOK2/ASWHhp6/vnxjaeeZq6t8w1Jyp0Iaw= -github.com/Azure/azure-storage-blob-go v0.12.0/go.mod h1:A0u4VjtpgZJ7Y7um/+ix2DHBuEKFC6sEIlj0xc13a4Q= +github.com/Azure/azure-storage-blob-go v0.13.0 h1:lgWHvFh+UYBNVQLFHXkvul2f6yOPA9PIH82RTG2cSwc= +github.com/Azure/azure-storage-blob-go v0.13.0/go.mod h1:pA9kNqtjUeQF2zOSu4s//nUdBD+e64lEuc4sVnuOfNs= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest/adal v0.9.2/go.mod h1:/3SMAM86bP6wC9Ev35peQDUeqFZBMH07vvUOmg4z/fE= -github.com/Azure/go-autorest/autorest/adal v0.9.10 h1:r6fZHMaHD8B6LDCn0o5vyBFHIHrM6Ywwx7mb49lPItI= -github.com/Azure/go-autorest/autorest/adal v0.9.10/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= +github.com/Azure/go-autorest/autorest/adal v0.9.13 h1:Mp5hbtOePIzM8pJVRa3YLrWWmZtoxRXqUEzCfJt3+/Q= +github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= +github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= @@ -59,8 +64,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515211354-1abdf898e024 h1:dfZ6RF0UxHqt7xPz0r7h00apsaa6rIrFhT6Xly55Exk= github.com/apsdehal/go-logger v0.0.0-20190515211354-1abdf898e024/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.36.31 h1:BMVngapDGAfLBVEVzaSIw3fmJdWx7jOvhLCXgRXbXQI= -github.com/aws/aws-sdk-go v1.36.31/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= +github.com/aws/aws-sdk-go v1.38.15 h1:usaPeqoxFUzy0FfBLZLZHya5Kv2cpURjb1jqCa7+odA= +github.com/aws/aws-sdk-go v1.38.15/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -79,6 +84,7 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= @@ -88,8 +94,9 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -97,6 +104,7 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -110,8 +118,11 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -122,8 +133,10 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -136,9 +149,11 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -151,8 +166,8 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= -github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= @@ -164,6 +179,8 @@ github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfE github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -172,7 +189,6 @@ github.com/logrusorgru/aurora v0.0.0-20191116043053-66b7ad493a23 h1:Wp7NjqGKGN9t github.com/logrusorgru/aurora v0.0.0-20191116043053-66b7ad493a23/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI= github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -183,6 +199,7 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -192,16 +209,17 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= -golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -235,8 +253,9 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1 h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -264,13 +283,16 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -279,8 +301,11 @@ golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210113205817-d3ed898aa8a3 h1:BaN3BAqnopnKjvl+15DYP6LLrbBHfbfmlFYzmFj/Q9Q= -golang.org/x/oauth2 v0.0.0-20210113205817-d3ed898aa8a3/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 h1:0Ja1LBD+yisY6RWM/BH7TJVXWsSjs2VwBSmvSX4HdBc= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -290,6 +315,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -300,7 +327,6 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -322,10 +348,16 @@ golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 h1:F5Gozwx4I1xtr/sr/8CFbb57iKi3297KFs0QDbGN60A= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -333,8 +365,9 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -378,12 +411,11 @@ golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200828161849-5deb26317202/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20200915173823-2db8f0ff891c/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= -golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= @@ -408,11 +440,13 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.31.0/go.mod h1:CL+9IBCa2WWU6gRuBWaKqGWLFFwbEUXkfeMkHLQWYWo= -google.golang.org/api v0.32.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0 h1:l2Nfbl2GPXdWorv+dT2XfinX2jOOw4zv1VhLstx+6rE= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0 h1:URs6qR1lAxDsqWITsQXI4ZkGiYJ5dHtRNiCpfs2OeKA= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -450,15 +484,20 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200831141814-d751682dd103/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200914193844-75d14daec038/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200921151605-7abf4a1a14d5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210122163508-8081c04a3579 h1:Iwh0ba2kTgq2Q6mJiXhzrrjD7h11nEVnbMHFmp0/HsQ= -google.golang.org/genproto v0.0.0-20210122163508-8081c04a3579/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210406143921-e86de6bf7a46 h1:f4STrQZf8jaowsiUitigvrqMCCM4QJH1A2JCSI7U1ow= +google.golang.org/genproto v0.0.0-20210406143921-e86de6bf7a46/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -472,11 +511,13 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0 h1:TwIQcH3es+MojMVojxxfQ3l3OF2KzlRxML2xZq0kRo8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0 h1:uSZWeQJX5j11bIQ4AJoj+McDBo29cY1MCoC1wO3ts+c= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -486,18 +527,22 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From 8f436849914da9825439394d292b2c24de0387f5 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Wed, 7 Apr 2021 22:48:37 +0100 Subject: [PATCH 0771/1127] Prepare release v3.6.7 --- .version | 2 +- README.md | 6 +++--- internal/app/main.go | 2 +- release-notes.md | 10 ++++++++-- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/.version b/.version index dde31d2e..08d6564b 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.6.6 +v3.6.7 diff --git a/README.md b/README.md index 20858752..0a9da842 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.6.6&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.6.7&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -61,9 +61,9 @@ Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the d ```sh # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.6/helmsman_3.6.4_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.7/helmsman_3.6.4_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.6/helmsman_3.6.4_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.7/helmsman_3.6.4_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/internal/app/main.go b/internal/app/main.go index 1cb972ef..3d062952 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.6.6" + appVersion = "v3.6.7" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index 35643a2c..66853b3d 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,4 +1,4 @@ -# v3.6.6 +# v3.6.7 If you migrating from Helmsman v1.x, it is recommended you read the [migration guide](https://github.com/Praqma/helmsman/blob/master/docs/how_to/misc/migrate_to_3.md) before using this release. @@ -6,4 +6,10 @@ If you migrating from Helmsman v1.x, it is recommended you read the [migration g ## Fixes and improvements -- Fixed race condition when applying namespace quotas (#589) + +- Updated the Docker images to use the latest helm version (#597) +- Replaced the deprecated helm-secrets repo with the new one (#595) +- Fised issue preventing the proper expansion of paths for executable hooks (#594) +- Helmsman will now skip helm tests in dry-run mode (#593) +- Dont expect a username and password if the caClient cert is present (#592) +- Added flag to skip helm repo updates (#590) From c767262b6f7af3f734cf83bc33fe1645197978ae Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Wed, 7 Apr 2021 23:02:04 +0100 Subject: [PATCH 0772/1127] fix: bad version number in circleci config --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6d8dec60..df8f6e48 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -44,7 +44,7 @@ jobs: - run: name: build docker images and push them to dockerhub command: | - helm_versions=( "v3.3.4" "v3.4.2" "3.5.3" ) + helm_versions=( "v3.3.4" "v3.4.2" "v3.5.3" ) TAG=$(git describe --abbrev=0 --tags) docker login -u $DOCKER_USER -p $DOCKER_PASS From eb637598bda987185d7c1c0cd1231c79ee50bbc9 Mon Sep 17 00:00:00 2001 From: John Montroy Date: Sun, 18 Apr 2021 21:26:54 -0400 Subject: [PATCH 0773/1127] feat: command.Exec() returns (*ExitStatus, error) enabling more idiomatic error management. --- internal/app/command.go | 86 +++++++++++++++++++--------------- internal/app/command_test.go | 65 +++++++++++++------------ internal/app/decision_maker.go | 6 +-- internal/app/helm_helpers.go | 45 ++++++++---------- internal/app/helm_release.go | 6 +-- internal/app/kube_helpers.go | 62 ++++++++++-------------- internal/app/plan.go | 38 ++++++--------- internal/app/release.go | 16 +++---- internal/app/state_test.go | 5 +- internal/app/utils.go | 13 ++--- 10 files changed, 164 insertions(+), 178 deletions(-) diff --git a/internal/app/command.go b/internal/app/command.go index 3e57bdbd..f5648ca4 100644 --- a/internal/app/command.go +++ b/internal/app/command.go @@ -6,7 +6,6 @@ import ( "math" "os/exec" "strings" - "syscall" "time" ) @@ -18,42 +17,30 @@ type Command struct { Description string } -type ExitStatus struct { - code int - errors string - output string -} - func (c *Command) String() string { return c.Cmd + " " + strings.Join(c.Args, " ") } // RetryExec runs exec command with retry -func (c *Command) RetryExec(attempts int) ExitStatus { - var result ExitStatus - +func (c *Command) RetryExec(attempts int) (res *ExitStatus, err error) { for i := 0; i < attempts; i++ { - result = c.Exec() - if result.code == 0 { - return result + res, err = c.Exec() + if err == nil { + return } if i < (attempts - 1) { time.Sleep(time.Duration(math.Pow(2, float64(2+i))) * time.Second) - log.Info(fmt.Sprintf("Retrying %s due to error: %s", c.Description, result.errors)) + log.Infof("Retrying %s due to error: %v", c.Description, err) } } - return ExitStatus{ - code: result.code, - output: result.output, - errors: fmt.Sprintf("After %d attempts of %s, it failed with: %s", attempts, c.Description, result.errors), - } + return nil, fmt.Errorf("cmd %s failed after %d attempts: %w", c.Description, attempts, err) } // Exec executes the executable command and returns the exit code and execution result -func (c *Command) Exec() ExitStatus { +func (c *Command) Exec() (*ExitStatus, error) { // Only use non-empty string args - args := []string{} + var args []string for _, str := range c.Args { if str != "" { args = append(args, str) @@ -69,33 +56,58 @@ func (c *Command) Exec() ExitStatus { cmd.Stderr = &stderr if err := cmd.Start(); err != nil { - log.Info("cmd.Start: " + err.Error()) - return ExitStatus{ - code: 1, - errors: err.Error(), - } + return nil, err } if err := cmd.Wait(); err != nil { if exiterr, ok := err.(*exec.ExitError); ok { - if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { - return ExitStatus{ - code: status.ExitStatus(), - output: stdout.String(), - errors: stderr.String(), - } - } + return nil, newExitError(c, exiterr.ExitCode(), stdout, stderr) } else { log.Fatal("cmd.Wait: " + err.Error()) } } - return ExitStatus{ - code: 0, - output: stdout.String(), - errors: stderr.String(), + return newExitStatus(c, stdout, stderr), nil +} + +type ExitStatus struct { + cmd string + output string + errors string +} + +func newExitStatus(cmd *Command, stdout, stderr bytes.Buffer) *ExitStatus { + return &ExitStatus{ + cmd: cmd.String(), + output: strings.TrimSpace(stdout.String()), + errors: strings.TrimSpace(stderr.String()), } } +func (es *ExitStatus) String() string { + return fmt.Sprintf("cmd %s exited successfully\noutput: %s", es.cmd, es.output) +} + +type exitError struct { + cmd string + code int + output string +} + +func newExitError(cmd *Command, code int, stdout, stderr bytes.Buffer) *exitError { + return &exitError{ + cmd: cmd.String(), + code: code, + output: fmt.Sprintf( + "--- stdout ---\n%s\n--- stderr ---\n%s", + strings.TrimSpace(stdout.String()), + strings.TrimSpace(stderr.String())), + } +} + +func (ee *exitError) Error() string { + return fmt.Sprintf("cmd %s failed with non-zero exit code %d\noutput: %s", ee.cmd, ee.code, ee.output) +} + // ToolExists returns true if the tool is present in the environment and false otherwise. // It takes as input the tool's command to check if it is recognizable or not. e.g. helm or kubectl func ToolExists(tool string) bool { diff --git a/internal/app/command_test.go b/internal/app/command_test.go index e3ca5d76..e6f5253f 100644 --- a/internal/app/command_test.go +++ b/internal/app/command_test.go @@ -1,7 +1,6 @@ package app import ( - "strings" "testing" ) @@ -43,51 +42,57 @@ func Test_toolExists(t *testing.T) { } } -func Test_command_exec(t *testing.T) { - type fields struct { - Cmd string - Args []string - Description string +func TestCommandExec(t *testing.T) { + type input struct { + cmd string + args []string + desc string + } + type expected struct { + err error + output string } tests := []struct { - name string - fields fields - want int - want1 string + name string + input input + expected expected }{ { name: "echo", - fields: fields{ - Cmd: "bash", - Args: []string{"-c", "echo this is fun"}, - Description: "A bash command execution test with echo.", + input: input{ + cmd: "bash", + args: []string{"-c", "echo this is fun"}, + desc: "A bash command execution test with echo.", + }, + expected: expected{ + output: "this is fun", }, - want: 0, - want1: "this is fun", }, { name: "exitCode", - fields: fields{ - Cmd: "bash", - Args: []string{"-c", "echo $?"}, - Description: "A bash command execution test with exitCode.", + input: input{ + cmd: "bash", + args: []string{"-c", "echo $?"}, + desc: "A bash command execution test with exitCode.", + }, + expected: expected{ + output: "0", }, - want: 0, - want1: "0", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := Command{ - Cmd: tt.fields.Cmd, - Args: tt.fields.Args, - Description: tt.fields.Description, + Cmd: tt.input.cmd, + Args: tt.input.args, + Description: tt.input.desc, } - got := c.Exec() - if got.code != tt.want { - t.Errorf("command.exec() got = %v, want %v", got.code, tt.want) + res, err := c.Exec() + if err != nil && tt.expected.err.Error() != err.Error() { + t.Errorf("unexpected error running command.exec(): %v", err) } - if strings.TrimSpace(got.output) != tt.want1 { - t.Errorf("command.exec() got1 = %v, want %v", got.output, tt.want1) + + if res.output != tt.expected.output { + t.Errorf("command.exec() expected: %v, got %v", tt.expected.output, res.output) } }) } diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index 2da9f520..6e0e200c 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -209,9 +209,9 @@ func (cs *currentState) getHelmsmanReleases(s *state) map[string]map[string]bool }() cmd := kubectl([]string{"get", storageBackend, "-n", ns, "-l", "MANAGED-BY=HELMSMAN", "-o", outputFmt, "--no-headers"}, "Getting Helmsman-managed releases from namespace [ "+ns+" ]") - result := cmd.RetryExec(3) - if result.code != 0 { - log.Fatal(result.errors) + result, err := cmd.RetryExec(3) + if err != nil { + log.Fatalf("%v", err) } if !strings.EqualFold("No resources found.", strings.TrimSpace(result.output)) { diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index 1f2c49b3..b2b2e575 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -2,7 +2,6 @@ package app import ( "encoding/json" - "errors" "fmt" "net/url" "path/filepath" @@ -41,12 +40,10 @@ func getChartInfo(chartName, chartVersion string) (*chartInfo, error) { cmd := helmCmd([]string{"show", "chart", chartName, "--version", chartVersion}, "Getting latest non-local chart's version "+chartName+"-"+chartVersion+"") - result := cmd.Exec() - if result.code != 0 { + result, err := cmd.Exec() + if err != nil { maybeRepo := filepath.Base(filepath.Dir(chartName)) - message := strings.TrimSpace(result.errors) - - return nil, fmt.Errorf("chart [ %s ] version [ %s ] can't be found. Inspection returned error: \"%s\" -- If this is not a local chart, add the repo [ %s ] in your helmRepos stanza", chartName, chartVersion, message, maybeRepo) + return nil, fmt.Errorf("chart [ %s ] version [ %s ] can't be found. If this is not a local chart, add the repo [ %s ] in your helmRepos stanza. Error output: %w", chartName, chartVersion, maybeRepo, err) } c := &chartInfo{} @@ -74,9 +71,9 @@ func getChartInfo(chartName, chartVersion string) (*chartInfo, error) { func getHelmVersion() string { cmd := helmCmd([]string{"version", "--short", "-c"}, "Checking Helm version") - result := cmd.Exec() - if result.code != 0 { - log.Fatal("While checking helm version: " + result.errors) + result, err := cmd.Exec() + if err != nil { + log.Fatalf("Error checking helm version: %v", err) } return result.output @@ -105,9 +102,8 @@ func checkHelmVersion(constraint string) bool { func helmPluginExists(plugin string) bool { cmd := helmCmd([]string{"plugin", "list"}, "Validating that [ "+plugin+" ] is installed") - result := cmd.Exec() - - if result.code != 0 { + result, err := cmd.Exec() + if err != nil { return false } @@ -118,9 +114,8 @@ func helmPluginExists(plugin string) bool { func updateChartDep(chartPath string) error { cmd := helmCmd([]string{"dependency", "update", "--skip-refresh", chartPath}, "Updating dependency for local chart [ "+chartPath+" ]") - result := cmd.Exec() - if result.code != 0 { - return errors.New(result.errors) + if _, err := cmd.Exec(); err != nil { + return err } return nil } @@ -133,18 +128,18 @@ func addHelmRepos(repos map[string]string) error { // get existing helm repositories cmdList := helmCmd(concat([]string{"repo", "list", "--output", "json"}), "Listing helm repositories") - if reposResult := cmdList.Exec(); reposResult.code == 0 { - if err := json.Unmarshal([]byte(reposResult.output), &helmRepos); err != nil { + res, err := cmdList.Exec() + if err != nil { + if err := json.Unmarshal([]byte(res.output), &helmRepos); err != nil { log.Fatal(fmt.Sprintf("failed to unmarshal Helm CLI output: %s", err)) } // create map of existing repositories for _, repo := range helmRepos { existingRepos[repo.Name] = repo.URL } - } else { - if !strings.Contains(reposResult.errors, "no repositories to show") { - return fmt.Errorf("while listing helm repositories: %s", reposResult.errors) - } + } + if !strings.Contains(res.output, "no repositories to show") { + return fmt.Errorf("error listing helm repositories: %w", err) } repoAddFlags := "" @@ -187,16 +182,16 @@ func addHelmRepos(repos map[string]string) error { } } cmd := helmCmd(concat([]string{"repo", "add", repoAddFlags, repoName, repoURL}, basicAuthArgs), "Adding helm repository [ "+repoName+" ]") - if result := cmd.Exec(); result.code != 0 { - return fmt.Errorf("While adding helm repository ["+repoName+"]: %s", result.errors) + if _, err := cmd.Exec(); err != nil { + return fmt.Errorf("error adding helm repository ["+repoName+"]: %w", err) } } if !flags.noUpdate && len(repos) > 0 { cmd := helmCmd([]string{"repo", "update"}, "Updating helm repositories") - if result := cmd.Exec(); result.code != 0 { - return errors.New("While updating helm repos : " + result.errors) + if _, err := cmd.Exec(); err != nil { + return fmt.Errorf("err updating helm repos: %w", err) } } diff --git a/internal/app/helm_release.go b/internal/app/helm_release.go index 4375a3d0..b3abc2ef 100644 --- a/internal/app/helm_release.go +++ b/internal/app/helm_release.go @@ -48,9 +48,9 @@ func getHelmReleases(s *state) []helmRelease { var targetReleases []helmRelease defer wg.Done() cmd := helmCmd([]string{"list", "--all", "--max", "0", "--output", "json", "-n", ns}, "Listing all existing releases in [ "+ns+" ] namespace") - result := cmd.RetryExec(3) - if result.code != 0 { - log.Fatal(result.errors) + result, err := cmd.RetryExec(3) + if err != nil { + log.Fatalf("%v", err) } if err := json.Unmarshal([]byte(result.output), &releases); err != nil { log.Fatal(fmt.Sprintf("failed to unmarshal Helm CLI output: %s", err)) diff --git a/internal/app/kube_helpers.go b/internal/app/kube_helpers.go index 066422d8..64dd484f 100644 --- a/internal/app/kube_helpers.go +++ b/internal/app/kube_helpers.go @@ -47,19 +47,16 @@ func kubectl(args []string, desc string) Command { // createNamespace creates a namespace in the k8s cluster func createNamespace(ns string) { checkCmd := kubectl([]string{"get", "namespace", ns}, "Looking for namespace [ "+ns+" ]") - checkResult := checkCmd.RetryExec(3) - if checkResult.code == 0 { + if _, err := checkCmd.RetryExec(3); err != nil { log.Verbose("Namespace [ " + ns + " ] exists") return } cmd := kubectl([]string{"create", "namespace", ns}, "Creating namespace [ "+ns+" ]") - result := cmd.Exec() - if result.code == 0 { - log.Info("Namespace [ " + ns + " ] created") - } else { - log.Fatal("Failed creating namespace [ " + ns + " ] with error: " + result.errors) + if _, err := cmd.Exec(); err != nil { + log.Fatalf("Failed creating namespace [ "+ns+" ] with error: %v", err) } + log.Info("Namespace [ " + ns + " ] created") } // labelNamespace labels a namespace with provided labels @@ -74,9 +71,8 @@ func labelNamespace(ns string, labels map[string]string) { } cmd := kubectl(args, "Labeling namespace [ "+ns+" ]") - result := cmd.Exec() - if result.code != 0 && flags.verbose { - log.Warning(fmt.Sprintf("Could not label namespace [ %s with %v ]. Error message: %s", ns, strings.Join(args[4:], ","), result.errors)) + if _, err := cmd.Exec(); err != nil && flags.verbose { + log.Warning(fmt.Sprintf("Could not label namespace [ %s with %v ]. Error message: %v", ns, strings.Join(args[4:], ","), err)) } } @@ -92,9 +88,8 @@ func annotateNamespace(ns string, annotations map[string]string) { } cmd := kubectl(args, "Annotating namespace [ "+ns+" ]") - result := cmd.Exec() - if result.code != 0 && flags.verbose { - log.Info(fmt.Sprintf("Could not annotate namespace [ %s with %v ]. Error message: %s", ns, strings.Join(args[4:], ","), result.errors)) + if _, err := cmd.Exec(); err != nil && flags.verbose { + log.Info(fmt.Sprintf("Could not annotate namespace [ %s with %v ]. Error message: %v", ns, strings.Join(args[4:], ","), err)) } } @@ -172,10 +167,9 @@ func apply(definition, ns, kind string) error { cmd := kubectl([]string{"apply", "-f", targetFile.Name(), "-n", ns, flags.getKubeDryRunFlag("apply")}, "Creating "+kind+" in namespace [ "+ns+" ]") - result := cmd.Exec() - if result.code != 0 { - return fmt.Errorf("ERROR: failed to create %s in namespace [ %s ]: %s", kind, ns, result.errors) + if _, err := cmd.Exec(); err != nil { + return fmt.Errorf("error creating %s in namespace [ %s ]: %w", kind, ns, err) } return nil @@ -247,20 +241,20 @@ func createContext(s *state) error { } cmd := kubectl(setCredentialsCmdArgs, "Creating kubectl context - setting credentials") - if result := cmd.Exec(); result.code != 0 { - return errors.New("failed to create context [ " + s.Settings.KubeContext + " ]: " + result.errors) + if _, err := cmd.Exec(); err != nil { + return fmt.Errorf("failed to create context [ "+s.Settings.KubeContext+" ]: %w", err) } cmd = kubectl([]string{"config", "set-cluster", s.Settings.KubeContext, "--server=" + s.Settings.ClusterURI, "--certificate-authority=" + caCrt}, "Creating kubectl context - setting cluster") - if result := cmd.Exec(); result.code != 0 { - return errors.New("failed to create context [ " + s.Settings.KubeContext + " ]: " + result.errors) + if _, err := cmd.Exec(); err != nil { + return fmt.Errorf("failed to create context [ "+s.Settings.KubeContext+" ]: %w", err) } cmd = kubectl([]string{"config", "set-context", s.Settings.KubeContext, "--cluster=" + s.Settings.KubeContext, "--user=" + s.Settings.Username}, "Creating kubectl context - setting context") - if result := cmd.Exec(); result.code != 0 { - return errors.New("failed to create context [ " + s.Settings.KubeContext + " ]: " + result.errors) + if _, err := cmd.Exec(); err != nil { + return fmt.Errorf("failed to create context [ "+s.Settings.KubeContext+" ]: %w", err) } if setKubeContext(s.Settings.KubeContext) { @@ -279,9 +273,7 @@ func setKubeContext(kctx string) bool { cmd := kubectl([]string{"config", "use-context", kctx}, "Setting kube context to [ "+kctx+" ]") - result := cmd.Exec() - - if result.code != 0 { + if _, err := cmd.Exec(); err != nil { log.Info("Kubectl context [ " + kctx + " ] does not exist. Attempting to create it...") return false } @@ -294,9 +286,7 @@ func setKubeContext(kctx string) bool { func getKubeContext() bool { cmd := kubectl([]string{"config", "current-context"}, "Getting kubectl context") - result := cmd.Exec() - - if result.code != 0 || result.output == "" { + if res, err := cmd.Exec(); err != nil || res.output == "" { log.Info("Kubectl context is not set") return false } @@ -311,11 +301,11 @@ func getReleaseContext(releaseName, namespace, storageBackend string) string { // kubectl get secret -l owner=helm,name=argo -n test1 -o=jsonpath='{.items[-1].metadata.labels.HELMSMAN_CONTEXT}' cmd := kubectl([]string{"get", storageBackend, "-n", namespace, "-l", "owner=helm", "-l", "name=" + releaseName, "-o", "jsonpath='{.items[-1].metadata.labels.HELMSMAN_CONTEXT}'"}, "Getting Helmsman context for [ "+releaseName+" ] release") - result := cmd.Exec() - if result.code != 0 { - log.Fatal(result.errors) + res, err := cmd.Exec() + if err != nil { + log.Fatalf("%v", err) } - rctx := strings.Trim(result.output, `"' `) + rctx := strings.Trim(res.output, `"' `) if rctx == "" { rctx = defaultContextName } @@ -326,11 +316,11 @@ func getReleaseContext(releaseName, namespace, storageBackend string) string { func getKubectlClientVersion() string { cmd := kubectl([]string{"version", "--client", "--short"}, "Checking kubectl version") - result := cmd.Exec() - if result.code != 0 { - log.Fatal("While checking kubectl version: " + result.errors) + res, err := cmd.Exec() + if err != nil { + log.Fatalf("While checking kubectl version: %v", err) } - return result.output + return res.output } // getKubeDryRunFlag returns kubectl dry-run flag if helmsman --dry-run flag is enabled diff --git a/internal/app/plan.go b/internal/app/plan.go index 6820f290..e22e4491 100644 --- a/internal/app/plan.go +++ b/internal/app/plan.go @@ -182,33 +182,23 @@ func releaseWithHooks(cmd orderedCommand, storageBackend string, wg *sync.WaitGr // execOne executes a single ordered command func execOne(cmd Command, targetRelease *release) error { log.Notice(cmd.Description) - result := cmd.Exec() - if result.code != 0 { - log.Verbose(result.output) - errorMsg := result.errors - if !flags.verbose { - errorMsg = strings.Split(result.errors, "---")[0] - } + res, err := cmd.Exec() + if err != nil { if targetRelease != nil { - return fmt.Errorf("command for release [%s] returned [ %d ] exit code and error message [ %s ]", - targetRelease.Name, result.code, strings.TrimSpace(errorMsg)) - } else { - return fmt.Errorf("%s returned [ %d ] exit code and error message [ %s ]", - cmd.Description, result.code, strings.TrimSpace(errorMsg)) - } - - } else { - log.Notice(result.output) - successMsg := "Finished: " + cmd.Description - log.Notice(successMsg) - if _, err := url.ParseRequestURI(log.SlackWebhook); err == nil { - notifySlack(cmd.Description+" ... SUCCESS!", log.SlackWebhook, false, true) + return fmt.Errorf("command for release [%s] failed: %w", targetRelease.Name, err) } - if _, err := url.ParseRequestURI(log.MSTeamsWebhook); err == nil { - notifyMSTeams(cmd.Description+" ... SUCCESS!", log.MSTeamsWebhook, false, true) - } - return nil + return fmt.Errorf("%s failed: %w", cmd.Description, err) + } + log.Notice(res.output) + successMsg := "Finished: " + cmd.Description + log.Notice(successMsg) + if _, err := url.ParseRequestURI(log.SlackWebhook); err == nil { + notifySlack(cmd.Description+" ... SUCCESS!", log.SlackWebhook, false, true) + } + if _, err := url.ParseRequestURI(log.MSTeamsWebhook); err == nil { + notifyMSTeams(cmd.Description+" ... SUCCESS!", log.MSTeamsWebhook, false, true) } + return nil } // printPlanCmds prints the actual commands that will be executed as part of a plan. diff --git a/internal/app/release.go b/internal/app/release.go index d7d3755a..aed11366 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -177,9 +177,9 @@ func (r *release) diff() (string, error) { cmd := helmCmd(concat([]string{"diff", colorFlag, suppressDiffSecretsFlag}, diffContextFlag, r.getHelmArgsFor("diff")), "Diffing release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") - result := cmd.RetryExec(3) - if result.code != 0 { - return "", fmt.Errorf("Command returned with exit code: %d. And error message: %s ", result.code, result.errors) + result, err := cmd.RetryExec(3) + if err != nil { + return "", fmt.Errorf("command failed: %w", err) } return result.output, nil @@ -253,9 +253,8 @@ func (r *release) label(storageBackend string, labels ...string) { args = append(args, labels...) cmd := kubectl(args, "Applying Helmsman labels to [ "+r.Name+" ] release") - result := cmd.Exec() - if result.code != 0 { - log.Fatal(result.errors) + if _, err := cmd.Exec(); err != nil { + log.Fatalf("%v", err) } } } @@ -271,9 +270,8 @@ func (r *release) annotate(storageBackend string, annotations ...string) { args = append(args, annotations...) cmd := kubectl(args, "Applying Helmsman annotations to [ "+r.Name+" ] release") - result := cmd.Exec() - if result.code != 0 { - log.Fatal(result.errors) + if _, err := cmd.Exec(); err != nil { + log.Fatalf("%v", err) } } } diff --git a/internal/app/state_test.go b/internal/app/state_test.go index 3cb137ae..1e26ae22 100644 --- a/internal/app/state_test.go +++ b/internal/app/state_test.go @@ -1,7 +1,6 @@ package app import ( - "fmt" "os" "testing" ) @@ -12,8 +11,8 @@ func setupTestCase(t *testing.T) func(t *testing.T) { os.MkdirAll(os.TempDir()+"/helmsman-tests/myapp", os.ModePerm) os.MkdirAll(os.TempDir()+"/helmsman-tests/dir-with space/myapp", os.ModePerm) cmd := helmCmd([]string{"create", os.TempDir() + "/helmsman-tests/dir-with space/myapp"}, "creating an empty local chart directory") - if result := cmd.Exec(); result.code != 0 { - log.Fatal(fmt.Sprintf("Command returned with exit code: %d. And error message: %s ", result.code, result.errors)) + if _, err := cmd.Exec(); err != nil { + log.Fatalf("Command failed: %v", err) } return func(t *testing.T) { diff --git a/internal/app/utils.go b/internal/app/utils.go index f5ea656a..7ed32ac5 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -424,20 +424,17 @@ func decryptSecret(name string) error { Description: "Decrypting " + name, } - result := command.Exec() + result, err := command.Exec() + if err != nil { + return err + } if !settings.EyamlEnabled { _, fileNotFound := os.Stat(name + ".dec") if fileNotFound != nil && !isOfType(name, []string{".dec"}) { - return errors.New(result.errors) + return errors.New(result.String()) } } - if result.code != 0 { - return errors.New(result.errors) - } else if result.errors != "" { - return errors.New(result.errors) - } - if settings.EyamlEnabled { var outfile string if isOfType(name, []string{".dec"}) { From 9d5854ef602faa277637c413955f516e1e4a1e2e Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Wed, 21 Apr 2021 13:24:40 +0100 Subject: [PATCH 0774/1127] feat: better error handling for command.Exec() Signed-off-by: Luis Davim --- internal/app/command.go | 101 +++++++++++++++++---------------- internal/app/command_test.go | 31 ++++++---- internal/app/decision_maker.go | 8 +-- internal/app/helm_helpers.go | 26 ++++----- internal/app/helm_release.go | 6 +- internal/app/kube_helpers.go | 10 ++-- internal/app/plan.go | 6 +- internal/app/release.go | 40 ++++++------- internal/app/release_files.go | 4 +- internal/app/state.go | 6 +- internal/app/utils.go | 26 +++++---- 11 files changed, 135 insertions(+), 129 deletions(-) diff --git a/internal/app/command.go b/internal/app/command.go index f5648ca4..76a69695 100644 --- a/internal/app/command.go +++ b/internal/app/command.go @@ -17,16 +17,35 @@ type Command struct { Description string } +type ExitStatus struct { + code int + errors string + output string +} + +func (e ExitStatus) String() string { + str := strings.TrimSpace(e.output) + if errs := strings.TrimSpace(e.errors); errs != "" { + str = fmt.Sprintf("%s\n--- stderr ---\n%s", str, errs) + } + return str +} + func (c *Command) String() string { return c.Cmd + " " + strings.Join(c.Args, " ") } // RetryExec runs exec command with retry -func (c *Command) RetryExec(attempts int) (res *ExitStatus, err error) { +func (c *Command) RetryExec(attempts int) (ExitStatus, error) { + var ( + result ExitStatus + err error + ) + for i := 0; i < attempts; i++ { - res, err = c.Exec() + result, err = c.Exec() if err == nil { - return + return result, nil } if i < (attempts - 1) { time.Sleep(time.Duration(math.Pow(2, float64(2+i))) * time.Second) @@ -34,13 +53,26 @@ func (c *Command) RetryExec(attempts int) (res *ExitStatus, err error) { } } - return nil, fmt.Errorf("cmd %s failed after %d attempts: %w", c.Description, attempts, err) + return result, fmt.Errorf("%s, failed after %d attempts with: %w", c.Description, attempts, err) +} + +func (c *Command) newExitError(code int, stdout, stderr bytes.Buffer, cause error) error { + return fmt.Errorf( + "%s failed with non-zero exit code %d: %w\noutput: %s", + c.Description, code, cause, + fmt.Sprintf( + "\n--- stdout ---\n%s\n--- stderr ---\n%s", + strings.TrimSpace(stdout.String()), + strings.TrimSpace(stderr.String()), + ), + ) } // Exec executes the executable command and returns the exit code and execution result -func (c *Command) Exec() (*ExitStatus, error) { +func (c *Command) Exec() (ExitStatus, error) { // Only use non-empty string args var args []string + for _, str := range c.Args { if str != "" { args = append(args, str) @@ -56,56 +88,27 @@ func (c *Command) Exec() (*ExitStatus, error) { cmd.Stderr = &stderr if err := cmd.Start(); err != nil { - return nil, err + log.Info("cmd.Start: " + err.Error()) + return ExitStatus{ + code: 1, + errors: err.Error(), + }, err } - if err := cmd.Wait(); err != nil { - if exiterr, ok := err.(*exec.ExitError); ok { - return nil, newExitError(c, exiterr.ExitCode(), stdout, stderr) - } else { - log.Fatal("cmd.Wait: " + err.Error()) - } - } - return newExitStatus(c, stdout, stderr), nil -} - -type ExitStatus struct { - cmd string - output string - errors string -} - -func newExitStatus(cmd *Command, stdout, stderr bytes.Buffer) *ExitStatus { - return &ExitStatus{ - cmd: cmd.String(), + err := cmd.Wait() + res := ExitStatus{ output: strings.TrimSpace(stdout.String()), errors: strings.TrimSpace(stderr.String()), } -} - -func (es *ExitStatus) String() string { - return fmt.Sprintf("cmd %s exited successfully\noutput: %s", es.cmd, es.output) -} - -type exitError struct { - cmd string - code int - output string -} - -func newExitError(cmd *Command, code int, stdout, stderr bytes.Buffer) *exitError { - return &exitError{ - cmd: cmd.String(), - code: code, - output: fmt.Sprintf( - "--- stdout ---\n%s\n--- stderr ---\n%s", - strings.TrimSpace(stdout.String()), - strings.TrimSpace(stderr.String())), + if err != nil { + res.code = 1 + err = fmt.Errorf("failed to run %s: %w", c.Description, err) + if exiterr, ok := err.(*exec.ExitError); ok { + res.code = exiterr.ExitCode() + err = c.newExitError(exiterr.ExitCode(), stdout, stderr, err) + } } -} - -func (ee *exitError) Error() string { - return fmt.Sprintf("cmd %s failed with non-zero exit code %d\noutput: %s", ee.cmd, ee.code, ee.output) + return res, err } // ToolExists returns true if the tool is present in the environment and false otherwise. diff --git a/internal/app/command_test.go b/internal/app/command_test.go index e6f5253f..295a74b4 100644 --- a/internal/app/command_test.go +++ b/internal/app/command_test.go @@ -4,7 +4,7 @@ import ( "testing" ) -func Test_toolExists(t *testing.T) { +func TestToolExists(t *testing.T) { type args struct { tool string } @@ -49,13 +49,14 @@ func TestCommandExec(t *testing.T) { desc string } type expected struct { + code int err error output string } tests := []struct { - name string - input input - expected expected + name string + input input + want expected }{ { name: "echo", @@ -64,8 +65,10 @@ func TestCommandExec(t *testing.T) { args: []string{"-c", "echo this is fun"}, desc: "A bash command execution test with echo.", }, - expected: expected{ + want: expected{ + code: 0, output: "this is fun", + err: nil, }, }, { name: "exitCode", @@ -74,8 +77,10 @@ func TestCommandExec(t *testing.T) { args: []string{"-c", "echo $?"}, desc: "A bash command execution test with exitCode.", }, - expected: expected{ + want: expected{ + code: 0, output: "0", + err: nil, }, }, } @@ -86,13 +91,15 @@ func TestCommandExec(t *testing.T) { Args: tt.input.args, Description: tt.input.desc, } - res, err := c.Exec() - if err != nil && tt.expected.err.Error() != err.Error() { - t.Errorf("unexpected error running command.exec(): %v", err) + got, err := c.Exec() + if err != tt.want.err { + t.Errorf("command.exec() unexpected error got = %v, want %v", err, tt.want.err) } - - if res.output != tt.expected.output { - t.Errorf("command.exec() expected: %v, got %v", tt.expected.output, res.output) + if got.code != tt.want.code { + t.Errorf("command.exec() unexpected code got = %v, want %v", got.code, tt.want.code) + } + if got.output != tt.want.output { + t.Errorf("command.exec() unexpected output got = %v, want %v", got.output, tt.want.output) } }) } diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index 6e0e200c..b38b14ea 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -209,13 +209,13 @@ func (cs *currentState) getHelmsmanReleases(s *state) map[string]map[string]bool }() cmd := kubectl([]string{"get", storageBackend, "-n", ns, "-l", "MANAGED-BY=HELMSMAN", "-o", outputFmt, "--no-headers"}, "Getting Helmsman-managed releases from namespace [ "+ns+" ]") - result, err := cmd.RetryExec(3) + res, err := cmd.RetryExec(3) if err != nil { - log.Fatalf("%v", err) + log.Fatal(err.Error()) } - if !strings.EqualFold("No resources found.", strings.TrimSpace(result.output)) { - lines = strings.Split(result.output, "\n") + if !strings.EqualFold("No resources found.", strings.TrimSpace(res.output)) { + lines = strings.Split(res.output, "\n") } for _, line := range lines { diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index b2b2e575..61c87020 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -40,14 +40,14 @@ func getChartInfo(chartName, chartVersion string) (*chartInfo, error) { cmd := helmCmd([]string{"show", "chart", chartName, "--version", chartVersion}, "Getting latest non-local chart's version "+chartName+"-"+chartVersion+"") - result, err := cmd.Exec() + res, err := cmd.Exec() if err != nil { maybeRepo := filepath.Base(filepath.Dir(chartName)) return nil, fmt.Errorf("chart [ %s ] version [ %s ] can't be found. If this is not a local chart, add the repo [ %s ] in your helmRepos stanza. Error output: %w", chartName, chartVersion, maybeRepo, err) } c := &chartInfo{} - if err := yaml.Unmarshal([]byte(result.output), &c); err != nil { + if err := yaml.Unmarshal([]byte(res.output), &c); err != nil { log.Fatal(fmt.Sprint(err)) } @@ -71,12 +71,12 @@ func getChartInfo(chartName, chartVersion string) (*chartInfo, error) { func getHelmVersion() string { cmd := helmCmd([]string{"version", "--short", "-c"}, "Checking Helm version") - result, err := cmd.Exec() + res, err := cmd.Exec() if err != nil { - log.Fatalf("Error checking helm version: %v", err) + log.Fatalf("While checking helm version: %v", err) } - return result.output + return res.output } func checkHelmVersion(constraint string) bool { @@ -102,12 +102,12 @@ func checkHelmVersion(constraint string) bool { func helmPluginExists(plugin string) bool { cmd := helmCmd([]string{"plugin", "list"}, "Validating that [ "+plugin+" ] is installed") - result, err := cmd.Exec() + res, err := cmd.Exec() if err != nil { return false } - return strings.Contains(result.output, plugin) + return strings.Contains(res.output, plugin) } // updateChartDep updates dependencies for a local chart @@ -128,18 +128,16 @@ func addHelmRepos(repos map[string]string) error { // get existing helm repositories cmdList := helmCmd(concat([]string{"repo", "list", "--output", "json"}), "Listing helm repositories") - res, err := cmdList.Exec() - if err != nil { - if err := json.Unmarshal([]byte(res.output), &helmRepos); err != nil { + if reposResult, err := cmdList.Exec(); err == nil { + if err := json.Unmarshal([]byte(reposResult.output), &helmRepos); err != nil { log.Fatal(fmt.Sprintf("failed to unmarshal Helm CLI output: %s", err)) } // create map of existing repositories for _, repo := range helmRepos { existingRepos[repo.Name] = repo.URL } - } - if !strings.Contains(res.output, "no repositories to show") { - return fmt.Errorf("error listing helm repositories: %w", err) + } else if !strings.Contains(reposResult.errors, "no repositories to show") { + return fmt.Errorf("while listing helm repositories: %w", err) } repoAddFlags := "" @@ -183,7 +181,7 @@ func addHelmRepos(repos map[string]string) error { } cmd := helmCmd(concat([]string{"repo", "add", repoAddFlags, repoName, repoURL}, basicAuthArgs), "Adding helm repository [ "+repoName+" ]") if _, err := cmd.Exec(); err != nil { - return fmt.Errorf("error adding helm repository ["+repoName+"]: %w", err) + return fmt.Errorf("While adding helm repository [%s]]: %w", repoName, err) } } diff --git a/internal/app/helm_release.go b/internal/app/helm_release.go index b3abc2ef..3f7c9180 100644 --- a/internal/app/helm_release.go +++ b/internal/app/helm_release.go @@ -48,11 +48,11 @@ func getHelmReleases(s *state) []helmRelease { var targetReleases []helmRelease defer wg.Done() cmd := helmCmd([]string{"list", "--all", "--max", "0", "--output", "json", "-n", ns}, "Listing all existing releases in [ "+ns+" ] namespace") - result, err := cmd.RetryExec(3) + res, err := cmd.RetryExec(3) if err != nil { - log.Fatalf("%v", err) + log.Fatal(err.Error()) } - if err := json.Unmarshal([]byte(result.output), &releases); err != nil { + if err := json.Unmarshal([]byte(res.output), &releases); err != nil { log.Fatal(fmt.Sprintf("failed to unmarshal Helm CLI output: %s", err)) } if len(s.TargetMap) > 0 { diff --git a/internal/app/kube_helpers.go b/internal/app/kube_helpers.go index 64dd484f..0d505bb3 100644 --- a/internal/app/kube_helpers.go +++ b/internal/app/kube_helpers.go @@ -47,7 +47,7 @@ func kubectl(args []string, desc string) Command { // createNamespace creates a namespace in the k8s cluster func createNamespace(ns string) { checkCmd := kubectl([]string{"get", "namespace", ns}, "Looking for namespace [ "+ns+" ]") - if _, err := checkCmd.RetryExec(3); err != nil { + if _, err := checkCmd.RetryExec(3); err == nil { log.Verbose("Namespace [ " + ns + " ] exists") return } @@ -113,7 +113,7 @@ spec: log.Fatal(err.Error()) } - definition = definition + Indent(string(d), strings.Repeat(" ", 4)) + definition += Indent(string(d), strings.Repeat(" ", 4)) if err := apply(definition, ns, "LimitRange"); err != nil { log.Fatal(err.Error()) @@ -136,7 +136,7 @@ spec: ` for _, customQuota := range quotas.CustomQuotas { - definition = definition + Indent(customQuota.Name+": '"+customQuota.Value+"'\n", strings.Repeat(" ", 4)) + definition += Indent(customQuota.Name+": '"+customQuota.Value+"'\n", strings.Repeat(" ", 4)) } // Special formatting for custom quotas so manually write these and then set to nil for marshalling @@ -147,7 +147,7 @@ spec: log.Fatal(err.Error()) } - definition = definition + Indent(string(d), strings.Repeat(" ", 4)) + definition += Indent(string(d), strings.Repeat(" ", 4)) if err := apply(definition, ns, "ResourceQuota"); err != nil { log.Fatal(err.Error()) @@ -303,7 +303,7 @@ func getReleaseContext(releaseName, namespace, storageBackend string) string { res, err := cmd.Exec() if err != nil { - log.Fatalf("%v", err) + log.Fatal(err.Error()) } rctx := strings.Trim(res.output, `"' `) if rctx == "" { diff --git a/internal/app/plan.go b/internal/app/plan.go index e22e4491..077709d1 100644 --- a/internal/app/plan.go +++ b/internal/app/plan.go @@ -170,10 +170,8 @@ func releaseWithHooks(cmd orderedCommand, storageBackend string, wg *sync.WaitGr annotations = append(annotations, key+"=failed") } log.Verbose(err.Error()) - } else { - if key, err := c.getAnnotationKey(); err == nil { - annotations = append(annotations, key+"=ok") - } + } else if key, err := c.getAnnotationKey(); err == nil { + annotations = append(annotations, key+"=ok") } } } diff --git a/internal/app/release.go b/internal/app/release.go index aed11366..552ddcbc 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -154,7 +154,7 @@ func (r *release) uninstall(p *plan, optionalNamespace ...string) { } priority := r.Priority if p.ReverseDelete { - priority = priority * -1 + priority *= -1 } before, after := r.checkHooks("delete", ns) @@ -177,12 +177,12 @@ func (r *release) diff() (string, error) { cmd := helmCmd(concat([]string{"diff", colorFlag, suppressDiffSecretsFlag}, diffContextFlag, r.getHelmArgsFor("diff")), "Diffing release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") - result, err := cmd.RetryExec(3) + res, err := cmd.RetryExec(3) if err != nil { return "", fmt.Errorf("command failed: %w", err) } - return result.output, nil + return res.output, nil } // upgradeRelease upgrades an existing release with the specified values.yaml @@ -254,7 +254,7 @@ func (r *release) label(storageBackend string, labels ...string) { cmd := kubectl(args, "Applying Helmsman labels to [ "+r.Name+" ] release") if _, err := cmd.Exec(); err != nil { - log.Fatalf("%v", err) + log.Fatal(err.Error()) } } } @@ -271,7 +271,7 @@ func (r *release) annotate(storageBackend string, annotations ...string) { cmd := kubectl(args, "Applying Helmsman annotations to [ "+r.Name+" ] release") if _, err := cmd.Exec(); err != nil { - log.Fatalf("%v", err) + log.Fatal(err.Error()) } } } @@ -309,39 +309,39 @@ func (r *release) getTimeout() []string { // getSetValues returns --set params to be used with helm install/upgrade commands func (r *release) getSetValues() []string { - result := []string{} + res := []string{} for k, v := range r.Set { - result = append(result, "--set", k+"="+strings.Replace(v, ",", "\\,", -1)+"") + res = append(res, "--set", k+"="+strings.ReplaceAll(v, ",", "\\,")+"") } - return result + return res } // getSetStringValues returns --set-string params to be used with helm install/upgrade commands func (r *release) getSetStringValues() []string { - result := []string{} + res := []string{} for k, v := range r.SetString { - result = append(result, "--set-string", k+"="+strings.Replace(v, ",", "\\,", -1)+"") + res = append(res, "--set-string", k+"="+strings.ReplaceAll(v, ",", "\\,")+"") } - return result + return res } // getSetFileValues returns --set-file params to be used with helm install/upgrade commands func (r *release) getSetFileValues() []string { - result := []string{} + res := []string{} for k, v := range r.SetFile { - result = append(result, "--set-file", k+"="+strings.Replace(v, ",", "\\,", -1)+"") + res = append(res, "--set-file", k+"="+strings.ReplaceAll(v, ",", "\\,")+"") } - return result + return res } // getWait returns a partial helm command containing the helm wait flag (--wait) if the wait flag for the release was set to true // Otherwise, retruns an empty string func (r *release) getWait() []string { - result := []string{} + res := []string{} if r.Wait { - result = append(result, "--wait") + res = append(res, "--wait") } - return result + return res } // getDesiredNamespace returns the namespace of a release @@ -371,11 +371,11 @@ func (r *release) getHelmFlags() []string { // getPostRenderer returns the post-renderer Helm flag func (r *release) getPostRenderer() []string { - result := []string{} + args := []string{} if r.PostRenderer != "" { - result = append(result, "--post-renderer", r.PostRenderer) + args = append(args, "--post-renderer", r.PostRenderer) } - return result + return args } // getHelmArgsFor returns helm arguments for a specific helm operation diff --git a/internal/app/release_files.go b/internal/app/release_files.go index ab0fe6b3..b3b10174 100644 --- a/internal/app/release_files.go +++ b/internal/app/release_files.go @@ -83,7 +83,7 @@ func (r *release) getValuesFiles() []string { if err := decryptSecret(r.SecretsFile); err != nil { log.Fatal(err.Error()) } - r.SecretsFile = r.SecretsFile + ".dec" + r.SecretsFile += ".dec" } fileList = append(fileList, r.SecretsFile) } else if len(r.SecretsFiles) > 0 { @@ -98,7 +98,7 @@ func (r *release) getValuesFiles() []string { if err := decryptSecret(r.SecretsFiles[i]); err != nil { log.Fatal(err.Error()) } - r.SecretsFiles[i] = r.SecretsFiles[i] + ".dec" + r.SecretsFiles[i] += ".dec" } fileList = append(fileList, r.SecretsFiles...) } diff --git a/internal/app/state.go b/internal/app/state.go index 24500e14..29a06869 100644 --- a/internal/app/state.go +++ b/internal/app/state.go @@ -169,10 +169,8 @@ func (s *state) validate() error { } } - } else { - if s.Settings.ClusterURI != "" { - return errors.New("certificates validation failed -- kube context setup is required but no certificates stanza provided") - } + } else if s.Settings.ClusterURI != "" { + return errors.New("certificates validation failed -- kube context setup is required but no certificates stanza provided") } if (s.Settings.EyamlPrivateKeyPath != "" && s.Settings.EyamlPublicKeyPath == "") || (s.Settings.EyamlPrivateKeyPath == "" && s.Settings.EyamlPublicKeyPath != "") { diff --git a/internal/app/utils.go b/internal/app/utils.go index 7ed32ac5..6dfbe503 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -3,7 +3,6 @@ package app import ( "bufio" "bytes" - "errors" "fmt" "io" "io/ioutil" @@ -22,6 +21,12 @@ import ( "github.com/Praqma/helmsman/internal/gcs" ) +var ( + comment = regexp.MustCompile("#(.*)$") + envVar = regexp.MustCompile(`\${([a-zA-Z_][a-zA-Z0-9_-]*)}|\$([a-zA-Z_][a-zA-Z0-9_-]*)`) + ssmParam = regexp.MustCompile(`{{ssm: ([^~}]+)(~(true))?}}`) +) + // printMap prints to the console any map of string keys and values. func printMap(m map[string]string, indent int) { for key, value := range m { @@ -127,7 +132,7 @@ func substituteEnv(name string) string { if strings.Contains(name, "$") { // add $$ escaping for $ strings os.Setenv("HELMSMAN_DOLLAR", "$") - return os.ExpandEnv(strings.Replace(name, "$$", "${HELMSMAN_DOLLAR}", -1)) + return os.ExpandEnv(strings.ReplaceAll(name, "$$", "${HELMSMAN_DOLLAR}")) } return name } @@ -138,8 +143,6 @@ func validateEnvVars(s string, filename string) error { if !flags.skipValidation && strings.Contains(s, "$") { log.Info("validating environment variables in " + filename) var key string - comment, _ := regexp.Compile("#(.*)$") - envVar, _ := regexp.Compile(`\${([a-zA-Z_][a-zA-Z0-9_-]*)}|\$([a-zA-Z_][a-zA-Z0-9_-]*)`) scanner := bufio.NewScanner(strings.NewReader(s)) for scanner.Scan() { // remove spaces from the single line, then replace $$ with !? to prevent it from matching the regex, @@ -169,8 +172,7 @@ func validateEnvVars(s string, filename string) error { // if the string does not contain '$', it is returned as is. func substituteSSM(name string) string { if strings.Contains(name, "{{ssm: ") { - re := regexp.MustCompile(`{{ssm: ([^~}]+)(~(true))?}}`) - matches := re.FindAllSubmatch([]byte(name), -1) + matches := ssmParam.FindAllSubmatch([]byte(name), -1) for _, match := range matches { placeholder := string(match[0]) paramPath := string(match[1]) @@ -297,7 +299,7 @@ func notifySlack(content string, url string, failure bool, executing bool) bool pretext = "*No actions to perform!*" } else if failure { pretext = "*Failed to generate/execute a plan: *" - content = strings.Replace(content, "\"", "\\\"", -1) + content = strings.ReplaceAll(content, "\"", "\\\"") contentSplit := strings.Split(content, "\n") for i := range contentSplit { if strings.TrimSpace(contentSplit[i]) != "" { @@ -359,7 +361,7 @@ func notifySlack(content string, url string, failure bool, executing bool) bool func replaceStringInFile(input []byte, outfile string, replacements map[string]string) { output := input for k, v := range replacements { - output = bytes.Replace(output, []byte(k), []byte(v), -1) + output = bytes.ReplaceAll(output, []byte(k), []byte(v)) } if err := ioutil.WriteFile(outfile, output, 0o666); err != nil { @@ -424,14 +426,14 @@ func decryptSecret(name string) error { Description: "Decrypting " + name, } - result, err := command.Exec() + res, err := command.Exec() if err != nil { return err } if !settings.EyamlEnabled { _, fileNotFound := os.Stat(name + ".dec") if fileNotFound != nil && !isOfType(name, []string{".dec"}) { - return errors.New(result.String()) + return fmt.Errorf(res.String()) } } @@ -442,7 +444,7 @@ func decryptSecret(name string) error { } else { outfile = name + ".dec" } - err := writeStringToFile(outfile, result.output) + err := writeStringToFile(outfile, res.output) if err != nil { log.Fatal("Can't write [ " + outfile + " ] file") } @@ -501,7 +503,7 @@ func notifyMSTeams(content string, url string, failure bool, executing bool) boo pretext = "No actions to perform!" } else if failure { pretext = "Failed to generate/execute a plan:" - content = strings.Replace(content, "\"", "\\\"", -1) + content = strings.ReplaceAll(content, "\"", "\\\"") contentSplit := strings.Split(content, "\n") for i := range contentSplit { if strings.TrimSpace(contentSplit[i]) != "" { From 1b3c4c34277762837bd3e2189ed28b8f2258a1e0 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Thu, 22 Apr 2021 14:37:54 +0100 Subject: [PATCH 0775/1127] chore: update helm to the latest version --- .circleci/config.yml | 11 +---------- Dockerfile | 2 +- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index df8f6e48..d30609be 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -32,19 +32,10 @@ jobs: machine: true steps: - checkout - # - run: - # name: release helmsman - # command: | - # echo "releasing ..." - # mkdir /home/circleci/.go_workspace/bin - # curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh - # make build - # curl -sL https://git.io/goreleaser | bash -s -- --release-notes release-notes.md --rm-dist - - run: name: build docker images and push them to dockerhub command: | - helm_versions=( "v3.3.4" "v3.4.2" "v3.5.3" ) + helm_versions=( "v3.3.4" "v3.4.2" "v3.5.4" ) TAG=$(git describe --abbrev=0 --tags) docker login -u $DOCKER_USER -p $DOCKER_PASS diff --git a/Dockerfile b/Dockerfile index 3afe62d4..e24aee2d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ ARG GO_VERSION="1.15.2" ARG ALPINE_VERSION="3.12" ARG GLOBAL_KUBE_VERSION="v1.19.0" -ARG GLOBAL_HELM_VERSION="v3.5.3" +ARG GLOBAL_HELM_VERSION="v3.5.4" ARG GLOBAL_HELM_DIFF_VERSION="v3.1.3" ARG GLOBAL_SOPS_VERSION="v3.7.0" From abf8a7f234da5e694fc26a2b6c63098d724ac3a5 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Mon, 26 Apr 2021 12:17:54 +0100 Subject: [PATCH 0776/1127] Prepare release v3.6.8 --- .version | 2 +- README.md | 6 +++--- internal/app/main.go | 2 +- release-notes.md | 15 +++------------ 4 files changed, 8 insertions(+), 17 deletions(-) diff --git a/.version b/.version index 08d6564b..738cb210 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.6.7 +v3.6.8 diff --git a/README.md b/README.md index 0a9da842..29e0b048 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.6.7&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.6.8&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -61,9 +61,9 @@ Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the d ```sh # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.7/helmsman_3.6.4_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.8/helmsman_3.6.4_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.7/helmsman_3.6.4_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.8/helmsman_3.6.4_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/internal/app/main.go b/internal/app/main.go index 3d062952..cd2e8cf7 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.6.7" + appVersion = "v3.6.8" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index 66853b3d..3f88a373 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,15 +1,6 @@ -# v3.6.7 - -If you migrating from Helmsman v1.x, it is recommended you read the [migration guide](https://github.com/Praqma/helmsman/blob/master/docs/how_to/misc/migrate_to_3.md) before using this release. - -> Starting from Helmsman v3.0.0 GA release, support for Helmsman v1.x will be limited to bug fixes. +# v3.6.8 ## Fixes and improvements - -- Updated the Docker images to use the latest helm version (#597) -- Replaced the deprecated helm-secrets repo with the new one (#595) -- Fised issue preventing the proper expansion of paths for executable hooks (#594) -- Helmsman will now skip helm tests in dry-run mode (#593) -- Dont expect a username and password if the caClient cert is present (#592) -- Added flag to skip helm repo updates (#590) +- Updated helm to the latest version, we now have Docker images for the last patch version of the latest 3 minor helm versions. +- Improved error handling for shell out commands. (#601) From 0a1c3ddb5ec276795cc3670486f3e9556905d652 Mon Sep 17 00:00:00 2001 From: Justen Walker Date: Wed, 28 Apr 2021 20:35:25 -0400 Subject: [PATCH 0777/1127] do not "download" local files --- internal/app/kube_helpers.go | 2 +- internal/app/utils.go | 39 +++++++++++------------------------- 2 files changed, 13 insertions(+), 28 deletions(-) diff --git a/internal/app/kube_helpers.go b/internal/app/kube_helpers.go index 0d505bb3..002ca56f 100644 --- a/internal/app/kube_helpers.go +++ b/internal/app/kube_helpers.go @@ -221,7 +221,7 @@ func createContext(s *state) error { // bearer token tokenPath := "bearer.token" if s.Settings.BearerToken && s.Settings.BearerTokenPath != "" { - downloadFile(s.Settings.BearerTokenPath, "", tokenPath) + tokenPath = downloadFile(s.Settings.BearerTokenPath, "", tokenPath) } // connecting to the cluster diff --git a/internal/app/utils.go b/internal/app/utils.go index 6dfbe503..55867345 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -197,9 +197,9 @@ func sliceContains(slice []string, s string) bool { return false } -// downloadFile downloads a file from a URL, GCS, Azure or AWS buckets or local file system -// and saves it with a given outfile name and in a given dir -// if downloaded, returns the outfile name. If the file path is local file system path, it is copied to current directory. +// downloadFile downloads a file from a URL, GCS, Azure or AWS buckets and saves it with a +// given outfile name and in a given dir +// If the file path is local file system path, it returns the absolute path to the file func downloadFile(file string, dir string, outfile string) string { u, err := url.Parse(file) if err != nil { @@ -220,12 +220,17 @@ func downloadFile(file string, dir string, outfile string) string { case "az": azure.ReadFile(u.Host, u.Path, outfile, flags.noColors) default: - log.Verbose(file + " will be used from local file system.") - toCopy := file if !filepath.IsAbs(file) { - toCopy, _ = filepath.Abs(filepath.Join(dir, file)) + file, err = filepath.Abs(filepath.Join(dir, file)) + if err != nil { + log.Fatal("could not get absolute path to " + file + " : " + err.Error()) + } + } + log.Verbose(file + " will be used from local file system.") + if _, err = os.Stat(file); err != nil { + log.Fatal("could not stat " + file + " : " + err.Error()) } - copyFile(toCopy, outfile) + return file } return outfile @@ -252,26 +257,6 @@ func downloadFileFromURL(url string, filepath string) error { return err } -// copyFile copies a file from source to destination -func copyFile(source string, destination string) { - from, err := os.Open(source) - if err != nil { - log.Fatal("while copying " + source + " to " + destination + " : " + err.Error()) - } - defer from.Close() - - to, err := os.OpenFile(destination, os.O_RDWR|os.O_CREATE, 0o666) - if err != nil { - log.Fatal("while copying " + source + " to " + destination + " : " + err.Error()) - } - defer to.Close() - - _, err = io.Copy(to, from) - if err != nil { - log.Fatal("while copying " + source + " to " + destination + " : " + err.Error()) - } -} - // deleteFile deletes a file func deleteFile(path string) { log.Info("Cleaning up... deleting " + path) From 774bfa34102aa000a504e7162ba0fefa06408669 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Thu, 29 Apr 2021 17:03:21 +0100 Subject: [PATCH 0778/1127] Prepare release v3.6.9 --- .version | 2 +- README.md | 6 +++--- internal/app/main.go | 2 +- release-notes.md | 5 ++--- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.version b/.version index 738cb210..0287f3eb 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.6.8 +v3.6.9 diff --git a/README.md b/README.md index 29e0b048..8a297e32 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.6.8&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.6.9&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -61,9 +61,9 @@ Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the d ```sh # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.8/helmsman_3.6.4_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.9/helmsman_3.6.4_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.8/helmsman_3.6.4_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.9/helmsman_3.6.4_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/internal/app/main.go b/internal/app/main.go index cd2e8cf7..56d59cdb 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.6.8" + appVersion = "v3.6.9" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index 3f88a373..651c838c 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,6 +1,5 @@ -# v3.6.8 +# v3.6.9 ## Fixes and improvements -- Updated helm to the latest version, we now have Docker images for the last patch version of the latest 3 minor helm versions. -- Improved error handling for shell out commands. (#601) +- Fixed and issue that in some cases would lead local files to be truncated. (#606) From d436ef5989870693c2ba9746e8f6642de0373da5 Mon Sep 17 00:00:00 2001 From: John Montroy Date: Wed, 5 May 2021 14:01:44 -0400 Subject: [PATCH 0779/1127] fix: include stdout and stderr in all Exec returns. --- internal/app/command.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/app/command.go b/internal/app/command.go index 76a69695..7116b440 100644 --- a/internal/app/command.go +++ b/internal/app/command.go @@ -102,11 +102,10 @@ func (c *Command) Exec() (ExitStatus, error) { } if err != nil { res.code = 1 - err = fmt.Errorf("failed to run %s: %w", c.Description, err) if exiterr, ok := err.(*exec.ExitError); ok { res.code = exiterr.ExitCode() - err = c.newExitError(exiterr.ExitCode(), stdout, stderr, err) } + err = c.newExitError(res.code, stdout, stderr, err) } return res, err } From 59b2564ff3549cd8f7fc24018e53aacce62f7d84 Mon Sep 17 00:00:00 2001 From: John Montroy Date: Fri, 7 May 2021 14:34:50 -0400 Subject: [PATCH 0780/1127] Prepare release v3.6.10 --- .version | 2 +- README.md | 6 +++--- internal/app/main.go | 2 +- release-notes.md | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.version b/.version index 0287f3eb..6515133f 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.6.9 +v3.6.10 diff --git a/README.md b/README.md index 8a297e32..07eed319 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.6.9&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.6.10&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -61,9 +61,9 @@ Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the d ```sh # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.9/helmsman_3.6.4_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.10/helmsman_3.6.10_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.9/helmsman_3.6.4_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.10/helmsman_3.6.10_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/internal/app/main.go b/internal/app/main.go index 56d59cdb..deaa96f5 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.6.9" + appVersion = "v3.6.10" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index 651c838c..d8a9c4d0 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,5 +1,5 @@ -# v3.6.9 +# v3.6.10 ## Fixes and improvements -- Fixed and issue that in some cases would lead local files to be truncated. (#606) +- Added stdout and stderr to all errors returned from cmd.Exec() to avoid dropping relevant information. From 853a33888d8e17985917cb18fb9ae54a6d7692d3 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Thu, 13 May 2021 11:30:58 +0100 Subject: [PATCH 0781/1127] fix: dont force updates on repo add if the no-update flag is passed --- internal/app/helm_helpers.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index 61c87020..469b01bf 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -140,9 +140,9 @@ func addHelmRepos(repos map[string]string) error { return fmt.Errorf("while listing helm repositories: %w", err) } - repoAddFlags := "" - if checkHelmVersion(">=3.3.2") { - repoAddFlags += "--force-update" + forceUpdateFlag := "" + if checkHelmVersion(">=3.3.2") && !flags.noUpdate { + forceUpdateFlag += "--force-update" } for repoName, repoURL := range repos { @@ -179,9 +179,9 @@ func addHelmRepos(repos map[string]string) error { continue } } - cmd := helmCmd(concat([]string{"repo", "add", repoAddFlags, repoName, repoURL}, basicAuthArgs), "Adding helm repository [ "+repoName+" ]") + cmd := helmCmd(concat([]string{"repo", "add", forceUpdateFlag, repoName, repoURL}, basicAuthArgs), "Adding helm repository [ "+repoName+" ]") if _, err := cmd.Exec(); err != nil { - return fmt.Errorf("While adding helm repository [%s]]: %w", repoName, err) + return fmt.Errorf("while adding helm repository [%s]]: %w", repoName, err) } } From 6463618064c4dec747306c22dddecc5fa3e4d020 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Thu, 13 May 2021 13:33:43 +0100 Subject: [PATCH 0782/1127] refactor: cleanup config files --- .../apps/puppetserver/common-values.yaml | 2 +- examples/appsTemplates/config/helmsman.yaml | 34 +++---- examples/example.toml | 4 +- examples/example.yaml | 95 +++++++++---------- examples/job.yaml | 7 +- 5 files changed, 63 insertions(+), 79 deletions(-) diff --git a/examples/appsTemplates/apps/puppetserver/common-values.yaml b/examples/appsTemplates/apps/puppetserver/common-values.yaml index 9741903f..5bc15e1a 100644 --- a/examples/appsTemplates/apps/puppetserver/common-values.yaml +++ b/examples/appsTemplates/apps/puppetserver/common-values.yaml @@ -1,2 +1,2 @@ puppetserver: - puppeturl: 'https://github.com/puppetlabs/control-repo.git' + puppeturl: "https://github.com/puppetlabs/control-repo.git" diff --git a/examples/appsTemplates/config/helmsman.yaml b/examples/appsTemplates/config/helmsman.yaml index 594384dc..5403b3cd 100644 --- a/examples/appsTemplates/config/helmsman.yaml +++ b/examples/appsTemplates/config/helmsman.yaml @@ -3,7 +3,7 @@ metadata: maintainer: "devops" settings: - kubeContext: "kind-kind-1" # the name of the context to be created + kubeContext: "kind-kind-1" # the name of the context to be created slackWebhook: "$MY_SLACK_WEBHOOK" namespaces: @@ -48,61 +48,49 @@ appsTemplates: testing: &testing namespace: "testing" - protected: false # defining all "testing" releases to be protected. + protected: false # defining all "testing" releases to be protected. wait: true development: &development namespace: "development" - protected: true # defining all "development" releases to be protected. + protected: true # defining all "development" releases to be protected. wait: false puppetserver: &puppetserver enabled: true priority: -1 chart: "puppet/puppetserver-helm-chart" - version: "3.0.2" # chart version - valuesFiles: [ - "../apps/puppetserver/common-values.yaml", - ] + version: "3.0.2" # chart version + valuesFiles: ["../apps/puppetserver/common-values.yaml"] tomcat: &tomcat enabled: true priority: -2 chart: "bitnami/tomcat" - version: "6.5.3" # chart version - valuesFiles: [ - "../apps/tomcat/common-values.yaml", - ] + version: "6.5.3" # chart version + valuesFiles: ["../apps/tomcat/common-values.yaml"] apps: testing-puppetserver: <<: *common <<: *testing <<: *puppetserver - valuesFiles: [ - "../apps/puppetserver/testing-values.yaml", - ] + valuesFiles: ["../apps/puppetserver/testing-values.yaml"] testing-tomcat: <<: *common <<: *testing <<: *tomcat - valuesFiles: [ - "../apps/tomcat/testing-values.yaml", - ] + valuesFiles: ["../apps/tomcat/testing-values.yaml"] development-puppetserver: <<: *common <<: *development <<: *puppetserver - valuesFiles: [ - "../apps/puppetserver/development-values.yaml", - ] + valuesFiles: ["../apps/puppetserver/development-values.yaml"] development-tomcat: <<: *common <<: *development <<: *tomcat - valuesFiles: [ - "../apps/tomcat/development-values.yaml", - ] + valuesFiles: ["../apps/tomcat/development-values.yaml"] diff --git a/examples/example.toml b/examples/example.toml index c455be77..b6c03cee 100644 --- a/examples/example.toml +++ b/examples/example.toml @@ -60,7 +60,7 @@ context= "test-infra" # defaults to "default" if not provided [[namespaces.production.limits]] type = "Pod" [namespaces.production.limits.max] - memory = "300Mi" + memory = "300Mi" [namespaces.staging] protected = false [namespaces.staging.labels] @@ -135,6 +135,6 @@ context= "test-infra" # defaults to "default" if not provided # additional helm flags for this release helmFlags= [ "--devel", - ] + ] # See https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md#apps for more apps options diff --git a/examples/example.yaml b/examples/example.yaml index ab8f52fa..6c1318ab 100644 --- a/examples/example.yaml +++ b/examples/example.yaml @@ -3,7 +3,7 @@ # context defines the context of this Desired State File. # It is used to allow Helmsman identify which releases are managed by which DSF. # Therefore, it is important that each DSF uses a unique context. -context: test-infra # defaults to "default" if not provided +context: test-infra # defaults to "default" if not provided # metadata -- add as many key/value pairs as you want metadata: @@ -16,9 +16,9 @@ metadata: # You can skip this if you use Helmsman on a machine with kubectl already connected to your k8s cluster. # you have to use exact key names here : 'caCrt' for certificate and 'caKey' for the key and caClient for the client certificate # certificates: - #caClient: "gs://mybucket/client.crt" # GCS bucket path - #caCrt: "s3://mybucket/ca.crt" # S3 bucket path - #caKey: "../ca.key" # valid local file relative path +#caClient: "gs://mybucket/client.crt" # GCS bucket path +#caCrt: "s3://mybucket/ca.crt" # S3 bucket path +#caKey: "../ca.key" # valid local file relative path settings: kubeContext: "minikube" # will try connect to this context first, if it does not exist, it will be created using the details below @@ -64,10 +64,8 @@ namespaces: requests.cpu: "10" requests.memory: "30Gi" customQuotas: - - name: "requests.nvidia.com/gpu" - value: "2" - - + - name: "requests.nvidia.com/gpu" + value: "2" # define any private/public helm charts repos you would like to get charts from # syntax: repo_name: "repo_url" @@ -83,46 +81,45 @@ helmRepos: # each contains the following: apps: - argo: - namespace: "staging" # maps to the namespace as defined in namespaces above - enabled: true # change to false if you want to delete this app release empty: false: - chart: "argo/argo" # changing the chart name means delete and recreate this chart - version: "0.8.5" # chart version - ### Optional values below - valuesFile: "" # leaving it empty uses the default chart values - test: false - protected: true - priority: -3 - wait: true - hooks: - successCondition: "Complete" - successTimeout: "90s" - deleteOnSuccess: true - preInstall: "job.yaml" - # preInstall: "https://github.com/jetstack/cert-manager/releases/download/v0.14.0/cert-manager.crds.yaml" - # postInstall: "https://raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml" - # postInstall: "job.yaml" - # preUpgrade: "job.yaml" - # postUpgrade: "job.yaml" - # preDelete: "job.yaml" - # postDelete: "job.yaml" - set: - "images.tag": $$TAG # $$ is escaped and $TAG is passed literally to images.tag (no env variable expansion) - - artifactory: - namespace: "production" # maps to the namespace as defined in namespaces above - enabled: true # change to false if you want to delete this app release empty: false: - chart: "jfrog/artifactory" # changing the chart name means delete and recreate this chart - version: "8.3.2" # chart version - ### Optional values below - valuesFile: "" - test: false - priority: -2 - noHooks: false - timeout: 300 - maxHistory: 4 - # additional helm flags for this release - helmFlags: - - "--devel" + argo: + namespace: "staging" # maps to the namespace as defined in namespaces above + enabled: true # change to false if you want to delete this app release empty: false: + chart: "argo/argo" # changing the chart name means delete and recreate this chart + version: "0.8.5" # chart version + ### Optional values below + valuesFile: "" # leaving it empty uses the default chart values + test: false + protected: true + priority: -3 + wait: true + hooks: + successCondition: "Complete" + successTimeout: "90s" + deleteOnSuccess: true + preInstall: "job.yaml" + # preInstall: "https://github.com/jetstack/cert-manager/releases/download/v0.14.0/cert-manager.crds.yaml" + # postInstall: "https://raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml" + # postInstall: "job.yaml" + # preUpgrade: "job.yaml" + # postUpgrade: "job.yaml" + # preDelete: "job.yaml" + # postDelete: "job.yaml" + set: + "images.tag": $$TAG # $$ is escaped and $TAG is passed literally to images.tag (no env variable expansion) + artifactory: + namespace: "production" # maps to the namespace as defined in namespaces above + enabled: true # change to false if you want to delete this app release empty: false: + chart: "jfrog/artifactory" # changing the chart name means delete and recreate this chart + version: "8.3.2" # chart version + ### Optional values below + valuesFile: "" + test: false + priority: -2 + noHooks: false + timeout: 300 + maxHistory: 4 + # additional helm flags for this release + helmFlags: + - "--devel" # See https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md#apps for more apps options diff --git a/examples/job.yaml b/examples/job.yaml index b448f2eb..3f734e5e 100644 --- a/examples/job.yaml +++ b/examples/job.yaml @@ -6,9 +6,8 @@ spec: template: spec: containers: - - name: pi - image: perl - command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"] + - name: pi + image: perl + command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"] restartPolicy: Never backoffLimit: 4 - From 5b0b061df4005135df386f122f0e9626e73a5911 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Thu, 13 May 2021 12:29:13 +0100 Subject: [PATCH 0783/1127] refactor: use errors instead of bool + string --- internal/app/cli.go | 8 +++----- internal/app/state_files.go | 25 ++++++++++++------------- internal/app/state_files_test.go | 24 +++++++++++++++--------- 3 files changed, 30 insertions(+), 27 deletions(-) diff --git a/internal/app/cli.go b/internal/app/cli.go index 93f2edca..ae019b7f 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -219,12 +219,10 @@ func (c *cli) readState(s *state) error { var fileState state for _, f := range c.files { - result, msg := fileState.fromFile(f) - if result { - log.Info(msg) - } else { - return fmt.Errorf(msg) + if err := fileState.fromFile(f); err != nil { + return err } + log.Infof("Parsed [[ %s ]] successfully and found [ %d ] apps", f, len(s.Apps)) // Merge Apps that already existed in the state for appName, app := range fileState.Apps { if _, ok := s.Apps[appName]; ok { diff --git a/internal/app/state_files.go b/internal/app/state_files.go index 58a6409c..be10143f 100644 --- a/internal/app/state_files.go +++ b/internal/app/state_files.go @@ -6,7 +6,6 @@ import ( "io/ioutil" "os" "path/filepath" - "strconv" "strings" "github.com/BurntSushi/toml" @@ -14,13 +13,13 @@ import ( ) // invokes either yaml or toml parser considering file extension -func (s *state) fromFile(file string) (bool, string) { +func (s *state) fromFile(file string) error { if isOfType(file, []string{".toml"}) { return s.fromTOML(file) } else if isOfType(file, []string{".yaml", ".yml"}) { return s.fromYAML(file) } else { - return false, "State file does not have toml/yaml extension." + return fmt.Errorf("state file does not have a valid extension") } } @@ -36,16 +35,16 @@ func (s *state) toFile(file string) { // fromTOML reads a toml file and decodes it to a state type. // It uses the BurntSuchi TOML parser which throws an error if the TOML file is not valid. -func (s *state) fromTOML(file string) (bool, string) { +func (s *state) fromTOML(file string) error { rawTomlFile, err := ioutil.ReadFile(file) if err != nil { - return false, err.Error() + return err } tomlFile := string(rawTomlFile) if !flags.noEnvSubst { if err := validateEnvVars(tomlFile, file); err != nil { - return false, err.Error() + return err } tomlFile = substituteEnv(tomlFile) } @@ -53,11 +52,11 @@ func (s *state) fromTOML(file string) (bool, string) { tomlFile = substituteSSM(tomlFile) } if _, err := toml.Decode(tomlFile, s); err != nil { - return false, err.Error() + return err } s.expand(file) - return true, "Parsed TOML [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps" + return nil } // toTOML encodes a state type into a TOML file. @@ -88,16 +87,16 @@ func (s *state) toTOML(file string) { // fromYAML reads a yaml file and decodes it to a state type. // parser which throws an error if the YAML file is not valid. -func (s *state) fromYAML(file string) (bool, string) { +func (s *state) fromYAML(file string) error { rawYamlFile, err := ioutil.ReadFile(file) if err != nil { - return false, err.Error() + return err } yamlFile := string(rawYamlFile) if !flags.noEnvSubst { if err := validateEnvVars(yamlFile, file); err != nil { - return false, err.Error() + return err } yamlFile = substituteEnv(yamlFile) } @@ -106,11 +105,11 @@ func (s *state) fromYAML(file string) (bool, string) { } if err = yaml.UnmarshalStrict([]byte(yamlFile), s); err != nil { - return false, err.Error() + return err } s.expand(file) - return true, "Parsed YAML [[ " + file + " ]] successfully and found [ " + strconv.Itoa(len(s.Apps)) + " ] apps" + return nil } // toYaml encodes a state type into a YAML file diff --git a/internal/app/state_files_test.go b/internal/app/state_files_test.go index 6746e198..82c670c5 100644 --- a/internal/app/state_files_test.go +++ b/internal/app/state_files_test.go @@ -49,7 +49,9 @@ func Test_fromTOML(t *testing.T) { defer teardownTestCase(t) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got, _ := tt.args.s.fromTOML(tt.args.file); got != tt.want { + err := tt.args.s.fromTOML(tt.args.file) + got := err == nil + if got != tt.want { t.Errorf("fromToml() = %v, want %v", got, tt.want) } }) @@ -99,9 +101,9 @@ func Test_fromTOML_Expand(t *testing.T) { defer teardownTestCase(t) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err, msg := tt.args.s.fromTOML(tt.args.file) - if !err { - t.Errorf("fromToml(), got: %v", msg) + err := tt.args.s.fromTOML(tt.args.file) + if err != nil { + t.Errorf("fromToml(), got: %v", err) } tomlVal := reflect.ValueOf(tt.args.s).Elem() @@ -181,7 +183,9 @@ func Test_fromYAML(t *testing.T) { defer teardownTestCase(t) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got, _ := tt.args.s.fromYAML(tt.args.file); got != tt.want { + err := tt.args.s.fromYAML(tt.args.file) + got := err == nil + if got != tt.want { t.Errorf("fromYaml() = %v, want %v", got, tt.want) } }) @@ -230,7 +234,9 @@ func Test_fromYAML_UnsetVars(t *testing.T) { } else if tt.targetVar == "VALUE" { os.Setenv("ORG_PATH", "sample") } - if got, _ := tt.args.s.fromYAML(tt.args.file); got != tt.want { + err := tt.args.s.fromYAML(tt.args.file) + got := err == nil + if got != tt.want { t.Errorf("fromYaml() = %v, want %v", got, tt.want) } }) @@ -280,9 +286,9 @@ func Test_fromYAML_Expand(t *testing.T) { defer teardownTestCase(t) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err, msg := tt.args.s.fromYAML(tt.args.file) - if !err { - t.Errorf("fromYaml(), got: %v", msg) + err := tt.args.s.fromYAML(tt.args.file) + if err != nil { + t.Errorf("fromYaml(), got: %v", err) } yamlVal := reflect.ValueOf(tt.args.s).Elem() From f1f07c8023da28296004bdc71b5967b620003afb Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Thu, 13 May 2021 13:02:07 +0100 Subject: [PATCH 0784/1127] fix: the repo name is the fisrt element only --- internal/app/cli.go | 2 +- internal/app/state_files.go | 15 ++++++--------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/internal/app/cli.go b/internal/app/cli.go index ae019b7f..9067bc09 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -222,7 +222,7 @@ func (c *cli) readState(s *state) error { if err := fileState.fromFile(f); err != nil { return err } - log.Infof("Parsed [[ %s ]] successfully and found [ %d ] apps", f, len(s.Apps)) + log.Infof("Parsed [[ %s ]] successfully and found [ %d ] apps", f, len(fileState.Apps)) // Merge Apps that already existed in the state for appName, app := range fileState.Apps { if _, ok := s.Apps[appName]; ok { diff --git a/internal/app/state_files.go b/internal/app/state_files.go index be10143f..29c4d02e 100644 --- a/internal/app/state_files.go +++ b/internal/app/state_files.go @@ -148,17 +148,14 @@ func (s *state) expand(relativeToFile string) { // resolve paths for local charts if r.Chart != "" { - repoOrDir := filepath.Dir(r.Chart) - _, isRepo := s.HelmRepos[repoOrDir] - isRepo = isRepo || stringInSlice(repoOrDir, s.PreconfiguredHelmRepos) + // support env vars in path + r.Chart = os.ExpandEnv(r.Chart) + repoName := strings.Split(r.Chart, "/")[0] + _, isRepo := s.HelmRepos[repoName] + isRepo = isRepo || stringInSlice(repoName, s.PreconfiguredHelmRepos) // if there is no repo for the chart, we assume it's intended to be a local path if !isRepo { - // support env vars in path - r.Chart = os.ExpandEnv(r.Chart) - // respect absolute paths to charts but resolve relative paths - if !filepath.IsAbs(r.Chart) { - r.Chart, _ = filepath.Abs(filepath.Join(dir, r.Chart)) - } + r.Chart, _ = resolveOnePath(r.Chart, dir, downloadDest) } } // expand env variables for all release files From 73a70645c4a0065755b5df6684f1ea28f0338a89 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Thu, 13 May 2021 15:03:47 +0100 Subject: [PATCH 0785/1127] chore: remove references to deprecated helm repos --- docs/deployment_strategies.md | 6 +++--- docs/how_to/apps/order.md | 2 +- docs/how_to/apps/override_namespaces.md | 6 +++--- docs/how_to/misc/limit-deployment-to-specific-apps.md | 2 +- .../misc/limit-deployment-to-specific-group-of-apps.md | 2 +- examples/appsTemplates/config/helmsman.yaml | 2 +- examples/minimal-example.toml | 4 ++-- examples/minimal-example.yaml | 4 ++-- tests/invalid_example.toml | 2 +- tests/invalid_example.yaml | 2 +- 10 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/deployment_strategies.md b/docs/deployment_strategies.md index 82bfb8c9..04cc46e8 100644 --- a/docs/deployment_strategies.md +++ b/docs/deployment_strategies.md @@ -47,7 +47,7 @@ You can test 3rd party charts in designated namespaces (e.g, staging) within the description = "production artifactory" namespace = "production" enabled = true - chart = "center/jfrog/artifactory" + chart = "jfrog/artifactory" version = "11.4.2" # chart version valuesFile = "../my-artificatory-production-values.yaml" @@ -79,7 +79,7 @@ namespaces: helmRepos: jenkins: https://charts.jenkins.io - center: https://repo.chartcenter.io + jfrog: https://charts.jfrog.io apps: jenkins: @@ -96,7 +96,7 @@ apps: description: "production artifactory" namespace: "production" enabled: true - chart: "center/jfrog/artifactory" + chart: "jfrog/artifactory" version: "11.4.2" # chart version valuesFile: "../my-artifactory-production-values.yaml" diff --git a/docs/how_to/apps/order.md b/docs/how_to/apps/order.md index 60ad5538..854a5aad 100644 --- a/docs/how_to/apps/order.md +++ b/docs/how_to/apps/order.md @@ -62,7 +62,7 @@ center = https://repo.chartcenter.io description = "artifactory" namespace = "staging" # maps to the namespace as defined in environments above enabled = true # change to false if you want to delete this app release [empty = false] - chart = "center/jfrog/artifactory" # changing the chart name means delete and recreate this chart + chart = "jfrog/artifactory" # changing the chart name means delete and recreate this chart version = "11.4.2" # chart version valuesFile = "" # leaving it empty uses the default chart values priority= -2 diff --git a/docs/how_to/apps/override_namespaces.md b/docs/how_to/apps/override_namespaces.md index 8cb57634..2c4bbdf1 100644 --- a/docs/how_to/apps/override_namespaces.md +++ b/docs/how_to/apps/override_namespaces.md @@ -45,7 +45,7 @@ center = https://repo.chartcenter.io description = "artifactory" namespace = "staging" # maps to the namespace as defined in environments above enabled = true # change to false if you want to delete this app release [empty = false] - chart = "center/jfrog/artifactory" # changing the chart name means delete and recreate this chart + chart = "jfrog/artifactory" # changing the chart name means delete and recreate this chart version = "11.4.2" # chart version valuesFile = "" # leaving it empty uses the default chart values ``` @@ -69,7 +69,7 @@ namespaces: helmRepos: jenkins: https://charts.jenkins.io - center: https://repo.chartcenter.io + jfrog: https://charts.jfrog.io apps: @@ -85,7 +85,7 @@ apps: description: "artifactory" namespace: "staging" # maps to the namespace as defined in environments above enabled: true # change to false if you want to delete this app release [empty: false] - chart: "center/jfrog/artifactory" # changing the chart name means delete and recreate this chart + chart: "jfrog/artifactory" # changing the chart name means delete and recreate this chart version: "11.4.2" # chart version valuesFile: "" # leaving it empty uses the default chart values ``` diff --git a/docs/how_to/misc/limit-deployment-to-specific-apps.md b/docs/how_to/misc/limit-deployment-to-specific-apps.md index 6d1d2518..94e8d4d0 100644 --- a/docs/how_to/misc/limit-deployment-to-specific-apps.md +++ b/docs/how_to/misc/limit-deployment-to-specific-apps.md @@ -26,7 +26,7 @@ apps: artifactory: namespace: "production" # maps to the namespace as defined in namespaces above enabled: true # change to false if you want to delete this app release empty: false: - chart: "center/jfrog/artifactory" # changing the chart name means delete and recreate this chart + chart: "jfrog/artifactory" # changing the chart name means delete and recreate this chart version: "11.4.2" # chart version # ... ``` diff --git a/docs/how_to/misc/limit-deployment-to-specific-group-of-apps.md b/docs/how_to/misc/limit-deployment-to-specific-group-of-apps.md index 187372b4..22525200 100644 --- a/docs/how_to/misc/limit-deployment-to-specific-group-of-apps.md +++ b/docs/how_to/misc/limit-deployment-to-specific-group-of-apps.md @@ -28,7 +28,7 @@ apps: namespace: "production" # maps to the namespace as defined in namespaces above group: "sidecar" # group name enabled: true # change to false if you want to delete this app release empty: false: - chart: "center/jfrog/artifactory" # changing the chart name means delete and recreate this chart + chart: "jfrog/artifactory" # changing the chart name means delete and recreate this chart version: "11.4.2" # chart version # ... ``` diff --git a/examples/appsTemplates/config/helmsman.yaml b/examples/appsTemplates/config/helmsman.yaml index 5403b3cd..406c8980 100644 --- a/examples/appsTemplates/config/helmsman.yaml +++ b/examples/appsTemplates/config/helmsman.yaml @@ -38,7 +38,7 @@ namespaces: helmRepos: jenkins: "https://charts.jenkins.io" - center: "https://repo.chartcenter.io" + jfrog: "https://charts.jfrog.io" bitnami: "https://charts.bitnami.com/bitnami" puppet: "https://puppetlabs.github.io/puppetserver-helm-chart" diff --git a/examples/minimal-example.toml b/examples/minimal-example.toml index bec47cff..35c3fda7 100644 --- a/examples/minimal-example.toml +++ b/examples/minimal-example.toml @@ -4,7 +4,7 @@ [helmRepos] jenkins = "https://charts.jenkins.io" - center = "https://repo.chartcenter.io" + jfrog = "https://charts.jfrog.io" [namespaces] [namespaces.staging] @@ -19,5 +19,5 @@ [apps.artifactory] namespace = "staging" enabled = true - chart = "center/jfrog/artifactory" + chart = "jfrog/artifactory" version = "11.4.2" diff --git a/examples/minimal-example.yaml b/examples/minimal-example.yaml index fae065c7..df9552eb 100644 --- a/examples/minimal-example.yaml +++ b/examples/minimal-example.yaml @@ -3,7 +3,7 @@ ## For the full config spec and options, check https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md helmRepos: jenkins: https://charts.jenkins.io - center: https://repo.chartcenter.io + jfrog: https://charts.jfrog.io namespaces: staging: @@ -18,5 +18,5 @@ apps: artifactory: namespace: staging enabled: true - chart: center/jfrog/artifactory + chart: jfrog/artifactory version: 11.4.2 diff --git a/tests/invalid_example.toml b/tests/invalid_example.toml index 67c02ee7..4baf339f 100644 --- a/tests/invalid_example.toml +++ b/tests/invalid_example.toml @@ -14,6 +14,6 @@ production = "default" [helmRepos] jenkins = "https://charts.jenkins.io" -center = "https://repo.chartcenter.io" +jfrog = "https://charts.jfrog.io" [apps] diff --git a/tests/invalid_example.yaml b/tests/invalid_example.yaml index b40add8a..6e30246e 100644 --- a/tests/invalid_example.yaml +++ b/tests/invalid_example.yaml @@ -14,6 +14,6 @@ namespaces: helmRepos: jenkins: "https://charts.jenkins.io" - center: "https://repo.chartcenter.io" + jfrog: "https://charts.jfrog.io" apps: From a189b3bcc3d520f9156247b4a169f939d19e914a Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Fri, 14 May 2021 16:08:18 +0100 Subject: [PATCH 0786/1127] fix: --server-dry-run was deprecated in kubectl 1.18 --- internal/app/cli.go | 2 +- internal/app/helm_helpers.go | 24 +++++++----------------- internal/app/kube_helpers.go | 32 +++++++++++++++++++++++++------- internal/app/utils.go | 14 ++++++++++++++ 4 files changed, 47 insertions(+), 25 deletions(-) diff --git a/internal/app/cli.go b/internal/app/cli.go index 9067bc09..c947670c 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -154,7 +154,7 @@ func (c *cli) parse() { log.Fatal("this version of Helmsman does not work with helm releases older than 3.0.0") } - kubectlVersion := strings.TrimSpace(strings.SplitN(getKubectlClientVersion(), ": ", 2)[1]) + kubectlVersion := getKubectlVersion() log.Verbose("kubectl client version: " + kubectlVersion) if len(c.files) == 0 { diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index 469b01bf..d32cc526 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -69,32 +69,22 @@ func getChartInfo(chartName, chartVersion string) (*chartInfo, error) { // getHelmClientVersion returns Helm client Version func getHelmVersion() string { - cmd := helmCmd([]string{"version", "--short", "-c"}, "Checking Helm version") + cmd := helmCmd([]string{"version", "--short", "--client"}, "Checking Helm version") res, err := cmd.Exec() if err != nil { log.Fatalf("While checking helm version: %v", err) } - return res.output + version := strings.TrimSpace(res.output) + if !strings.HasPrefix(version, "v") { + version = strings.SplitN(version, ":", 2)[1] + } + return version } func checkHelmVersion(constraint string) bool { - helmVersion := strings.TrimSpace(getHelmVersion()) - extractedHelmVersion := helmVersion - if !strings.HasPrefix(helmVersion, "v") { - extractedHelmVersion = strings.TrimSpace(strings.Split(helmVersion, ":")[1]) - } - v, err := semver.NewVersion(extractedHelmVersion) - if err != nil { - return false - } - - jsonConstraint, err := semver.NewConstraint(constraint) - if err != nil { - return false - } - return jsonConstraint.Check(v) + return checkVersion(getHelmVersion(), constraint) } // helmPluginExists returns true if the plugin is present in the environment and false otherwise. diff --git a/internal/app/kube_helpers.go b/internal/app/kube_helpers.go index 002ca56f..64f021de 100644 --- a/internal/app/kube_helpers.go +++ b/internal/app/kube_helpers.go @@ -312,26 +312,44 @@ func getReleaseContext(releaseName, namespace, storageBackend string) string { return rctx } -// getKubectlClientVersion returns kubectl client version -func getKubectlClientVersion() string { - cmd := kubectl([]string{"version", "--client", "--short"}, "Checking kubectl version") +// getKubectlVersion returns kubectl client version +func getKubectlVersion() string { + cmd := kubectl([]string{"version", "--short", "--client"}, "Checking kubectl version") res, err := cmd.Exec() if err != nil { log.Fatalf("While checking kubectl version: %v", err) } - return res.output + version := strings.TrimSpace(res.output) + if !strings.HasPrefix(version, "v") { + version = strings.SplitN(version, ":", 2)[1] + } + return version +} + +func checkKubectlVersion(constraint string) bool { + return checkVersion(getKubectlVersion(), constraint) } // getKubeDryRunFlag returns kubectl dry-run flag if helmsman --dry-run flag is enabled +// TODO: this should be cleanup once 1.18 is old enough func (c *cli) getKubeDryRunFlag(action string) string { + var flag string if c.dryRun { + flag = "--dry-run" + recent := checkKubectlVersion(">=v1.18.0") switch action { case "apply": - return "--server-dry-run" + if recent { + flag += "=server" + } else { + flag = "--server-dry-run" + } default: - return "--dry-run" + if recent { + flag += "=client" + } } } - return "" + return flag } diff --git a/internal/app/utils.go b/internal/app/utils.go index 55867345..08950cd5 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -16,6 +16,7 @@ import ( "strings" "time" + "github.com/Masterminds/semver" "github.com/Praqma/helmsman/internal/aws" "github.com/Praqma/helmsman/internal/azure" "github.com/Praqma/helmsman/internal/gcs" @@ -468,6 +469,19 @@ func isValidFile(filePath string, allowedFileTypes []string) error { return nil } +func checkVersion(version, constraint string) bool { + v, err := semver.NewVersion(version) + if err != nil { + return false + } + + jsonConstraint, err := semver.NewConstraint(constraint) + if err != nil { + return false + } + return jsonConstraint.Check(v) +} + // notify MSTeams sends a JSON formatted message to MSTeams channel over a webhook url // It takes the content of the message (what changes helmsman is going to do or have done separated by \n) // and the webhook URL as well as a flag specifying if this is a failure message or not From 000ca376c9c955673e58762b6476e871d115bd49 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Fri, 14 May 2021 16:47:10 +0100 Subject: [PATCH 0787/1127] release: v3.6.11 --- .version | 2 +- README.md | 6 +++--- internal/app/main.go | 2 +- release-notes.md | 9 +++++++-- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/.version b/.version index 6515133f..03183d52 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.6.10 +v3.6.11 diff --git a/README.md b/README.md index 07eed319..7ff6e009 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.6.10&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.6.11&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -61,9 +61,9 @@ Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the d ```sh # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.10/helmsman_3.6.10_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.11/helmsman_3.6.10_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.10/helmsman_3.6.10_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.11/helmsman_3.6.10_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/internal/app/main.go b/internal/app/main.go index deaa96f5..2555c359 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.6.10" + appVersion = "v3.6.11" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index d8a9c4d0..62c3dc24 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,5 +1,10 @@ -# v3.6.10 +# v3.6.11 ## Fixes and improvements -- Added stdout and stderr to all errors returned from cmd.Exec() to avoid dropping relevant information. +- fix: dont force updates on repo add if the no-update flag is passed +- refactor: cleanup config files +- refactor: use errors instead of bool + string +- fix: the repo name is the fisrt element only +- chore: remove references to deprecated helm repos +- fix: --server-dry-run was deprecated in kubectl 1.18 From 1f329f38475a69fb65ccd038852f4ab340c88367 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sun, 9 May 2021 20:11:57 +0100 Subject: [PATCH 0788/1127] feat: allow using kubectl diff instead of helm diff Signed-off-by: Luis Davim --- docs/cmd_reference.md | 5 +- internal/app/cli.go | 9 ++- internal/app/command.go | 125 ++++++++++++++++++++++++++++++----- internal/app/command_test.go | 119 +++++++++++++++++++++++++++++++-- internal/app/release.go | 37 ++++++++--- 5 files changed, 261 insertions(+), 34 deletions(-) diff --git a/docs/cmd_reference.md b/docs/cmd_reference.md index d72571da..6660398c 100644 --- a/docs/cmd_reference.md +++ b/docs/cmd_reference.md @@ -48,7 +48,10 @@ This lists available CMD options in Helmsman: keep releases that are managed by Helmsman from the used DSFs in the command, and are no longer tracked in your desired state. `--kubeconfig` - path to the kubeconfig file to use for CLI requests. + path to the kubeconfig file to use for CLI requests. Defalts to false if the helm diff plugin is installed. + + `--kubectl-diff` + Use kubectl diff instead of helm diff `--migrate-context` Updates the context name for all apps defined in the DSF and applies Helmsman labels. Using this flag is required if you want to change context name after it has been set. diff --git a/internal/app/cli.go b/internal/app/cli.go index c947670c..61ce5233 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -67,6 +67,7 @@ type cli struct { parallel int alwaysUpgrade bool noUpdate bool + kubectlDiff bool } func printUsage() { @@ -107,13 +108,14 @@ func (c *cli) parse() { flag.BoolVar(&c.substEnvValues, "subst-env-values", false, "turn on environment substitution in values files.") flag.BoolVar(&c.noSSMSubst, "no-ssm-subst", false, "turn off SSM parameter substitution globally") flag.BoolVar(&c.substSSMValues, "subst-ssm-values", false, "turn on SSM parameter substitution in values files.") - flag.BoolVar(&c.updateDeps, "update-deps", false, "run 'helm dep up' for local chart") + flag.BoolVar(&c.updateDeps, "update-deps", false, "run 'helm dep up' for local charts") flag.BoolVar(&c.forceUpgrades, "force-upgrades", false, "use --force when upgrading helm releases. May cause resources to be recreated.") flag.BoolVar(&c.renameReplace, "replace-on-rename", false, "Uninstall the existing release when a chart with a different name is used.") flag.BoolVar(&c.noCleanup, "no-cleanup", false, "keeps any credentials files that has been downloaded on the host where helmsman runs.") flag.BoolVar(&c.migrateContext, "migrate-context", false, "updates the context name for all apps defined in the DSF and applies Helmsman labels. Using this flag is required if you want to change context name after it has been set.") flag.BoolVar(&c.alwaysUpgrade, "always-upgrade", false, "upgrade release even if no changes are found") flag.BoolVar(&c.noUpdate, "no-update", false, "skip updating helm repos") + flag.BoolVar(&c.kubectlDiff, "kubectl-diff", false, "use kubectl diff instead of helm diff. Defalts to false if the helm diff plugin is installed.") flag.Usage = printUsage flag.Parse() @@ -174,8 +176,9 @@ func (c *cli) parse() { log.Fatal("" + helmBin + " is not installed/configured correctly. Aborting!") } - if !helmPluginExists("diff") { - log.Fatal("helm diff plugin is not installed/configured correctly. Aborting!") + if !c.kubectlDiff && !helmPluginExists("diff") { + c.kubectlDiff = true + log.Warning("helm diff not found, using kubectl diff") } if !c.noEnvSubst { diff --git a/internal/app/command.go b/internal/app/command.go index 7116b440..fd1db320 100644 --- a/internal/app/command.go +++ b/internal/app/command.go @@ -10,13 +10,16 @@ import ( ) // Command type representing all executable commands Helmsman needs -// to execute in order to inspect the environment/ releases/ charts etc. +// to execute in order to inspect the environment|releases|charts etc. type Command struct { Cmd string Args []string Description string } +// CmdPipe is a os/exec.Commnad wrapper for UNIX pipe +type CmdPipe []Command + type ExitStatus struct { code int errors string @@ -56,20 +59,7 @@ func (c *Command) RetryExec(attempts int) (ExitStatus, error) { return result, fmt.Errorf("%s, failed after %d attempts with: %w", c.Description, attempts, err) } -func (c *Command) newExitError(code int, stdout, stderr bytes.Buffer, cause error) error { - return fmt.Errorf( - "%s failed with non-zero exit code %d: %w\noutput: %s", - c.Description, code, cause, - fmt.Sprintf( - "\n--- stdout ---\n%s\n--- stderr ---\n%s", - strings.TrimSpace(stdout.String()), - strings.TrimSpace(stderr.String()), - ), - ) -} - -// Exec executes the executable command and returns the exit code and execution result -func (c *Command) Exec() (ExitStatus, error) { +func (c *Command) command() *exec.Cmd { // Only use non-empty string args var args []string @@ -82,8 +72,13 @@ func (c *Command) Exec() (ExitStatus, error) { log.Verbose(c.Description) log.Debug(c.String()) - cmd := exec.Command(c.Cmd, args...) + return exec.Command(c.Cmd, args...) +} + +// Exec executes the executable command and returns the exit code and execution result +func (c *Command) Exec() (ExitStatus, error) { var stdout, stderr bytes.Buffer + cmd := c.command() cmd.Stdout = &stdout cmd.Stderr = &stderr @@ -105,11 +100,107 @@ func (c *Command) Exec() (ExitStatus, error) { if exiterr, ok := err.(*exec.ExitError); ok { res.code = exiterr.ExitCode() } - err = c.newExitError(res.code, stdout, stderr, err) + err = newExitError(c.Description, res.code, res.output, res.errors, err) } return res, err } +// Exec pipes the executable commands and returns the exit code and execution result +func (p CmdPipe) Exec() (ExitStatus, error) { + var ( + stdout, stderr bytes.Buffer + stack []*exec.Cmd + ) + + l := len(p) - 1 + if l < 0 { + // nonthing to do here + return ExitStatus{}, nil + } + if l == 0 { + // it's just one command we can just run it + return p[0].Exec() + } + + for i, c := range p { + stack = append(stack, c.command()) + stack[i].Stderr = &stderr + if i > 0 { + stack[i].Stdin, _ = stack[i-1].StdoutPipe() + } + } + stack[l].Stdout = &stdout + + err := call(stack) + res := ExitStatus{ + output: strings.TrimSpace(stdout.String()), + errors: strings.TrimSpace(stderr.String()), + } + if err != nil { + res.code = 1 + if exiterr, ok := err.(*exec.ExitError); ok { + res.code = exiterr.ExitCode() + } + err = newExitError(p[l].Description, res.code, res.output, res.errors, err) + } + return res, err +} + +// RetryExec runs piped commands with retry +func (p CmdPipe) RetryExec(attempts int) (ExitStatus, error) { + var ( + result ExitStatus + err error + ) + + l := len(p) - 1 + for i := 0; i < attempts; i++ { + result, err = p.Exec() + if err == nil { + return result, nil + } + if i < (attempts - 1) { + time.Sleep(time.Duration(math.Pow(2, float64(2+i))) * time.Second) + log.Infof("Retrying %s due to error: %v", p[l].Description, err) + } + } + + return result, fmt.Errorf("%s, failed after %d attempts with: %w", p[l].Description, attempts, err) +} + +func call(stack []*exec.Cmd) (err error) { + if stack[0].Process == nil { + if err = stack[0].Start(); err != nil { + return err + } + } + if len(stack) > 1 { + if err = stack[1].Start(); err != nil { + return err + } + defer func() { + if err == nil { + err = call(stack[1:]) + } else { + stack[1].Wait() + } + }() + } + return stack[0].Wait() +} + +func newExitError(cmd string, code int, stdout, stderr string, cause error) error { + return fmt.Errorf( + "%s failed with non-zero exit code %d: %w\noutput: %s", + cmd, code, cause, + fmt.Sprintf( + "\n--- stdout ---\n%s\n--- stderr ---\n%s", + strings.TrimSpace(stdout), + strings.TrimSpace(stderr), + ), + ) +} + // ToolExists returns true if the tool is present in the environment and false otherwise. // It takes as input the tool's command to check if it is recognizable or not. e.g. helm or kubectl func ToolExists(tool string) bool { diff --git a/internal/app/command_test.go b/internal/app/command_test.go index 295a74b4..7c9e596d 100644 --- a/internal/app/command_test.go +++ b/internal/app/command_test.go @@ -1,6 +1,7 @@ package app import ( + "fmt" "testing" ) @@ -92,14 +93,124 @@ func TestCommandExec(t *testing.T) { Description: tt.input.desc, } got, err := c.Exec() - if err != tt.want.err { - t.Errorf("command.exec() unexpected error got = %v, want %v", err, tt.want.err) + if err != nil && tt.want.err == nil { + t.Errorf("command.exec() unexpected error got:\n%v want:\n%v", err, tt.want.err) + } + if err != nil && tt.want.err != nil { + if err.Error() != tt.want.err.Error() { + t.Errorf("command.exec() unexpected error got:\n%v want:\n%v", err, tt.want.err) + } + } + if got.code != tt.want.code { + t.Errorf("command.exec() unexpected code got = %v, want = %v", got.code, tt.want.code) + } + if got.output != tt.want.output { + t.Errorf("command.exec() unexpected output got:\n%v want:\n%v", got.output, tt.want.output) + } + }) + } +} + +func TestPipeExec(t *testing.T) { + type expected struct { + code int + err error + output string + } + tests := []struct { + name string + input CmdPipe + want expected + }{ + { + name: "echo", + input: CmdPipe{ + Command{ + Cmd: "echo", + Args: []string{"-e", `first string\nsecond string\nthird string`}, + Description: "muliline echo", + }, + }, + want: expected{ + code: 0, + output: "first string\nsecond string\nthird string", + err: nil, + }, + }, { + name: "line count", + input: CmdPipe{ + Command{ + Cmd: "echo", + Args: []string{"-e", `first string\nsecond string\nthird string`}, + Description: "muliline echo", + }, + Command{ + Cmd: "wc", + Args: []string{"-l"}, + Description: "line count", + }, + }, + want: expected{ + code: 0, + output: "3", + err: nil, + }, + }, { + name: "grep", + input: CmdPipe{ + Command{ + Cmd: "echo", + Args: []string{"-e", `first string\nsecond string\nthird string`}, + Description: "muliline echo", + }, + Command{ + Cmd: "grep", + Args: []string{"second"}, + Description: "grep", + }, + }, + want: expected{ + code: 0, + output: "second string", + err: nil, + }, + }, { + name: "grep no matches", + input: CmdPipe{ + Command{ + Cmd: "echo", + Args: []string{"-e", `first string\nsecond string\nthird string`}, + Description: "muliline echo", + }, + Command{ + Cmd: "grep", + Args: []string{"fourth"}, + Description: "grep", + }, + }, + want: expected{ + code: 1, + output: "", + err: newExitError("grep", 1, "", "", fmt.Errorf("exit status 1")), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.input.Exec() + if err != nil && tt.want.err == nil { + t.Errorf("command.exec() unexpected error got:\n%v want:\n%v", err, tt.want.err) + } + if err != nil && tt.want.err != nil { + if err.Error() != tt.want.err.Error() { + t.Errorf("command.exec() unexpected error got:\n%v want:\n%v", err, tt.want.err) + } } if got.code != tt.want.code { - t.Errorf("command.exec() unexpected code got = %v, want %v", got.code, tt.want.code) + t.Errorf("command.exec() unexpected code got = %v, want = %v", got.code, tt.want.code) } if got.output != tt.want.output { - t.Errorf("command.exec() unexpected output got = %v, want %v", got.output, tt.want.output) + t.Errorf("command.exec() unexpected output got:\n%v want:\n%v", got.output, tt.want.output) } }) } diff --git a/internal/app/release.go b/internal/app/release.go index 552ddcbc..a4fcc21a 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -165,20 +165,37 @@ func (r *release) uninstall(p *plan, optionalNamespace ...string) { // diffRelease diffs an existing release with the specified values.yaml func (r *release) diff() (string, error) { - colorFlag := "" - diffContextFlag := []string{} - suppressDiffSecretsFlag := "--suppress-secrets" - if flags.noColors { - colorFlag = "--no-color" - } - if flags.diffContext != -1 { - diffContextFlag = []string{"--context", strconv.Itoa(flags.diffContext)} + var args []string + + if !flags.kubectlDiff { + args = []string{"diff", "--suppress-secrets"} + if flags.noColors { + args = append(args, "--no-color") + } + if flags.diffContext != -1 { + args = append(args, "--context", strconv.Itoa(flags.diffContext)) + } + args = concat(args, r.getHelmArgsFor("diff")) + } else { + args = r.getHelmArgsFor("template") } - cmd := helmCmd(concat([]string{"diff", colorFlag, suppressDiffSecretsFlag}, diffContextFlag, r.getHelmArgsFor("diff")), "Diffing release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ]") + desc := "Diffing release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ]" + cmd := CmdPipe{helmCmd(args, desc)} + + if flags.kubectlDiff { + cmd = append(cmd, kubectl([]string{"diff", "-f", "-"}, desc)) + } res, err := cmd.RetryExec(3) if err != nil { + if flags.kubectlDiff && res.code <= 1 { + // kubectl diff exit status: + // 0 No differences were found. + // 1 Differences were found. + // >1 Kubectl or diff failed with an error. + return res.output, nil + } return "", fmt.Errorf("command failed: %w", err) } @@ -385,6 +402,8 @@ func (r *release) getHelmArgsFor(action string, optionalNamespaceOverride ...str ns = optionalNamespaceOverride[0] } switch action { + case "template": + return concat([]string{"template", r.Name, r.Chart, "--version", r.Version, "--namespace", r.Namespace}, r.getValuesFiles(), r.getSetValues(), r.getSetStringValues(), r.getSetFileValues(), r.getPostRenderer()) case "install", "upgrade": return concat([]string{"upgrade", r.Name, r.Chart, "--install", "--version", r.Version, "--namespace", r.Namespace}, r.getValuesFiles(), r.getSetValues(), r.getSetStringValues(), r.getSetFileValues(), r.getHelmFlags(), r.getPostRenderer()) case "diff": From c9f26baaaf0090c26d5745373bc3c1a98c8ae83f Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Wed, 12 May 2021 12:25:31 +0100 Subject: [PATCH 0789/1127] fix: add missing flags --- internal/app/release.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/app/release.go b/internal/app/release.go index a4fcc21a..eeeaa424 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -184,7 +184,7 @@ func (r *release) diff() (string, error) { cmd := CmdPipe{helmCmd(args, desc)} if flags.kubectlDiff { - cmd = append(cmd, kubectl([]string{"diff", "-f", "-"}, desc)) + cmd = append(cmd, kubectl([]string{"diff", "--namespace", r.Namespace, "-f", "-"}, desc)) } res, err := cmd.RetryExec(3) @@ -403,7 +403,7 @@ func (r *release) getHelmArgsFor(action string, optionalNamespaceOverride ...str } switch action { case "template": - return concat([]string{"template", r.Name, r.Chart, "--version", r.Version, "--namespace", r.Namespace}, r.getValuesFiles(), r.getSetValues(), r.getSetStringValues(), r.getSetFileValues(), r.getPostRenderer()) + return concat([]string{"template", r.Name, r.Chart, "--version", r.Version, "--namespace", r.Namespace, "--skip-tests", "--no-hooks"}, r.getValuesFiles(), r.getSetValues(), r.getSetStringValues(), r.getSetFileValues(), r.getPostRenderer()) case "install", "upgrade": return concat([]string{"upgrade", r.Name, r.Chart, "--install", "--version", r.Version, "--namespace", r.Namespace}, r.getValuesFiles(), r.getSetValues(), r.getSetStringValues(), r.getSetFileValues(), r.getHelmFlags(), r.getPostRenderer()) case "diff": From 8af79188095d04d5366bad3fb411a857d267b325 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 18 May 2021 15:45:26 +0100 Subject: [PATCH 0790/1127] feat: support oci charts Signed-off-by: Luis Davim --- docs/how_to/helm_repos/README.md | 1 + docs/how_to/helm_repos/oci.md | 34 ++++++++++++++++++++++++++++++ internal/app/helm_helpers.go | 13 ++++++++++++ internal/app/state_files.go | 5 ++++- internal/app/utils.go | 36 ++++++++++++++++++++++---------- 5 files changed, 77 insertions(+), 12 deletions(-) create mode 100644 docs/how_to/helm_repos/oci.md diff --git a/docs/how_to/helm_repos/README.md b/docs/how_to/helm_repos/README.md index 5f7eeeb8..134fda46 100644 --- a/docs/how_to/helm_repos/README.md +++ b/docs/how_to/helm_repos/README.md @@ -9,3 +9,4 @@ Following list contains guides on how to define helm repositories - [Using private repos with basic auth](basic_auth.md) - [Using pre-configured repos](pre_configured.md) - [Using local charts](local.md) +- [Using OCI registries](oci.md) diff --git a/docs/how_to/helm_repos/oci.md b/docs/how_to/helm_repos/oci.md new file mode 100644 index 00000000..2ebdf9ef --- /dev/null +++ b/docs/how_to/helm_repos/oci.md @@ -0,0 +1,34 @@ +--- +version: v3.7.0 +--- + +# Using OCI registries for helm charts + +Helmsman allows you to use charts stored in OCI registries. + +You need to export the following env variables: + +- `HELM_EXPERIMENTAL_OCI=1` + +if the registry requires authentication, you must login before running Helmsman + +```sh +helm registry login -u myuser my-registry.local +``` + +```toml +[apps] + [apps.my-app] + chart = "oci://my-registry.local/my-chart" + version = "1.0.0" +``` + +```yaml +#... +apps: + my-app: + chart: oci://my-registry.local/my-chart + version: 1.0.0 +``` + +For more information, read the [helm registries documentation](https://helm.sh/docs/topics/registries/). diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index d32cc526..59c6de50 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -110,6 +110,19 @@ func updateChartDep(chartPath string) error { return nil } +// helmExportChart pulls chart and exports it to the specified destination +func helmExportChart(chart, dest string) error { + cmd := helmCmd([]string{"chart", "pull", chart}, "Pulling chart [ "+chart+" ] to local registry cache") + if _, err := cmd.Exec(); err != nil { + return err + } + cmd = helmCmd([]string{"chart", "export", chart, "-d", dest}, "Exporting chart [ "+chart+" ] to "+dest) + if _, err := cmd.Exec(); err != nil { + return err + } + return nil +} + // addHelmRepos adds repositories to Helm if they don't exist already. // Helm does not mind if a repo with the same name exists. It treats it as an update. func addHelmRepos(repos map[string]string) error { diff --git a/internal/app/state_files.go b/internal/app/state_files.go index 29c4d02e..e9059ba6 100644 --- a/internal/app/state_files.go +++ b/internal/app/state_files.go @@ -153,8 +153,11 @@ func (s *state) expand(relativeToFile string) { repoName := strings.Split(r.Chart, "/")[0] _, isRepo := s.HelmRepos[repoName] isRepo = isRepo || stringInSlice(repoName, s.PreconfiguredHelmRepos) - // if there is no repo for the chart, we assume it's intended to be a local path + // if there is no repo for the chart, we assume it's intended to be a local path or url if !isRepo { + if strings.HasPrefix(r.Chart, "oci://") && !strings.HasSuffix(r.Chart, r.Version) { + r.Chart = fmt.Sprintf("%s:%s", r.Chart, r.Version) + } r.Chart, _ = resolveOnePath(r.Chart, dir, downloadDest) } } diff --git a/internal/app/utils.go b/internal/app/utils.go index 08950cd5..fefb8c3a 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -88,12 +88,12 @@ func stringInSlice(a string, list []string) bool { // and downloads/fetches the file locally into helmsman temp directory and returns // its absolute path func resolveOnePath(file string, dir string, downloadDest string) (string, error) { - if destFile, err := ioutil.TempFile(downloadDest, fmt.Sprintf("*%s", path.Base(file))); err != nil { + destFile, err := ioutil.TempFile(downloadDest, fmt.Sprintf("*%s", path.Base(file))) + if err != nil { return "", err - } else { - _ = destFile.Close() - return filepath.Abs(downloadFile(file, dir, destFile.Name())) } + destFile.Close() + return filepath.Abs(downloadFile(file, dir, destFile.Name())) } // createTempDir creates a temp directory in a specific location with a pattern @@ -208,6 +208,11 @@ func downloadFile(file string, dir string, outfile string) string { } switch u.Scheme { + case "oci": + dest := filepath.Dir(outfile) + fileName := strings.Split(filepath.Base(file), ":")[0] + helmExportChart(strings.ReplaceAll(file, "oci://", ""), dest) + return filepath.Join(dest, fileName) case "https", "http": if err := downloadFileFromURL(file, outfile); err != nil { log.Fatal(err.Error()) @@ -446,18 +451,27 @@ func isLocalChart(chart string) bool { // isValidCert checks if a certificate/key path/URI is valid func isValidCert(value string) bool { - if _, err := os.Stat(value); err != nil { - _, err1 := url.ParseRequestURI(value) - if err1 != nil || (!strings.HasPrefix(value, "s3://") && !strings.HasPrefix(value, "gs://") && !strings.HasPrefix(value, "az://")) { + if _, err := os.Stat(value); err == nil { + return true + } + u, err := url.ParseRequestURI(value) + if err != nil { + return false + } + switch u.Scheme { + case "http", "https", "s3", "gs", "az": + if !isOfType(u.Path, []string{".cert", ".key", ".pem", ".crt"}) { return false } + return true + default: + return false } - return true } // isValidFile checks if the file exists in the given path or accessible via http and is of allowed file extension (e.g. yaml, json ...) func isValidFile(filePath string, allowedFileTypes []string) error { - if strings.HasPrefix(filePath, "http") { + if strings.HasPrefix(filePath, "http") || strings.HasPrefix(filePath, "s3://") || strings.HasPrefix(filePath, "gs://") || strings.HasPrefix(filePath, "az://") { if _, err := url.ParseRequestURI(filePath); err != nil { return fmt.Errorf("%s must be valid URL path to a raw file", filePath) } @@ -475,11 +489,11 @@ func checkVersion(version, constraint string) bool { return false } - jsonConstraint, err := semver.NewConstraint(constraint) + c, err := semver.NewConstraint(constraint) if err != nil { return false } - return jsonConstraint.Check(v) + return c.Check(v) } // notify MSTeams sends a JSON formatted message to MSTeams channel over a webhook url From 5b8401590598ba5a1b6b556c14d17bfd94adb521 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Wed, 19 May 2021 19:48:03 +0100 Subject: [PATCH 0791/1127] Prepare release v3.7.0 --- .version | 2 +- README.md | 6 +++--- internal/app/main.go | 2 +- release-notes.md | 13 +++++-------- 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/.version b/.version index 03183d52..d1e9cf3b 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.6.11 +v3.7.0 diff --git a/README.md b/README.md index 7ff6e009..33fc9fe8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.6.11&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.7.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -61,9 +61,9 @@ Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the d ```sh # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.11/helmsman_3.6.10_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.0/helmsman_3.6.10_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.6.11/helmsman_3.6.10_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.0/helmsman_3.6.10_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/internal/app/main.go b/internal/app/main.go index 2555c359..24a1c5c1 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.6.11" + appVersion = "v3.7.0" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index 62c3dc24..19109e2f 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,10 +1,7 @@ -# v3.6.11 +# v3.7.0 -## Fixes and improvements +## New features -- fix: dont force updates on repo add if the no-update flag is passed -- refactor: cleanup config files -- refactor: use errors instead of bool + string -- fix: the repo name is the fisrt element only -- chore: remove references to deprecated helm repos -- fix: --server-dry-run was deprecated in kubectl 1.18 +- Added support for OCI registries (#612) + - more details in [docs/how_to/helm_repos/oci.md](https://github.com/Praqma/helmsman/blob/master/docs/how_to/helm_repos/oci.md) +- Added support for using `kubectl diff` instead of `helm diff` (#609) - Experimental From 91939b16082195ccf74ff895128f8f2414ef1ce9 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Wed, 19 May 2021 19:50:57 +0100 Subject: [PATCH 0792/1127] docs: fix download URLs --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 33fc9fe8..9dc9ca02 100644 --- a/README.md +++ b/README.md @@ -61,9 +61,9 @@ Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the d ```sh # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.0/helmsman_3.6.10_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.0/helmsman_3.7.0_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.0/helmsman_3.6.10_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.0/helmsman_3.7.0_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` From 7a0c60a61d7de4cadd1f7f691dbe7c00acb4e864 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Fri, 21 May 2021 09:52:19 +0100 Subject: [PATCH 0793/1127] fix: checkKubectlVersion doesnt work with trailng spaces in the version number --- internal/app/helm_helpers.go | 2 +- internal/app/kube_helpers.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index 59c6de50..598f906d 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -80,7 +80,7 @@ func getHelmVersion() string { if !strings.HasPrefix(version, "v") { version = strings.SplitN(version, ":", 2)[1] } - return version + return strings.TrimSpace(version) } func checkHelmVersion(constraint string) bool { diff --git a/internal/app/kube_helpers.go b/internal/app/kube_helpers.go index 64f021de..f93f1dd2 100644 --- a/internal/app/kube_helpers.go +++ b/internal/app/kube_helpers.go @@ -47,13 +47,13 @@ func kubectl(args []string, desc string) Command { // createNamespace creates a namespace in the k8s cluster func createNamespace(ns string) { checkCmd := kubectl([]string{"get", "namespace", ns}, "Looking for namespace [ "+ns+" ]") - if _, err := checkCmd.RetryExec(3); err == nil { + if _, err := checkCmd.Exec(); err == nil { log.Verbose("Namespace [ " + ns + " ] exists") return } - cmd := kubectl([]string{"create", "namespace", ns}, "Creating namespace [ "+ns+" ]") - if _, err := cmd.Exec(); err != nil { + cmd := kubectl([]string{"create", "namespace", ns, flags.getKubeDryRunFlag("create")}, "Creating namespace [ "+ns+" ]") + if _, err := cmd.RetryExec(3); err != nil { log.Fatalf("Failed creating namespace [ "+ns+" ] with error: %v", err) } log.Info("Namespace [ " + ns + " ] created") @@ -324,7 +324,7 @@ func getKubectlVersion() string { if !strings.HasPrefix(version, "v") { version = strings.SplitN(version, ":", 2)[1] } - return version + return strings.TrimSpace(version) } func checkKubectlVersion(constraint string) bool { From 27f0484427b32e0964de4fddb26a4673da87bdd7 Mon Sep 17 00:00:00 2001 From: Justen Walker Date: Fri, 11 Jun 2021 17:20:20 -0400 Subject: [PATCH 0794/1127] feat: redact --token and --password arguments When printing kubectl commands for debug logs, redact the --token and --password arguments so that it doesn't leak credentials into logs. --- internal/app/command.go | 29 +++++++++++++++++++++- internal/app/command_test.go | 47 ++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/internal/app/command.go b/internal/app/command.go index fd1db320..76a09d7a 100644 --- a/internal/app/command.go +++ b/internal/app/command.go @@ -35,7 +35,34 @@ func (e ExitStatus) String() string { } func (c *Command) String() string { - return c.Cmd + " " + strings.Join(c.Args, " ") + var sb strings.Builder + sb.WriteString(c.Cmd) + for i := 0; i < len(c.Args); i++ { + arg := c.Args[i] + sb.WriteRune(' ') + if strings.HasPrefix(arg, "--token=") { + sb.WriteString("--token=******") + continue + } + if strings.HasPrefix(arg, "--password=") { + sb.WriteString("--password=******") + continue + } + if arg == "--token" { + sb.WriteString(arg) + sb.WriteString("=******") + i++ + continue + } + if arg == "--password" { + sb.WriteString(arg) + sb.WriteString("=******") + i++ + continue + } + sb.WriteString(arg) + } + return sb.String() } // RetryExec runs exec command with retry diff --git a/internal/app/command_test.go b/internal/app/command_test.go index 7c9e596d..b54fc4df 100644 --- a/internal/app/command_test.go +++ b/internal/app/command_test.go @@ -215,3 +215,50 @@ func TestPipeExec(t *testing.T) { }) } } + +func TestCommand_String(t *testing.T) { + tests := []struct { + name string + cmd Command + expected string + }{ + { + "regular", + kubectl([]string{"config", "set-cluster", "CONTEXT", "--server=http://localhost:8080", "--certificate-authority=cacert.crt"}, ""), + "kubectl config set-cluster CONTEXT --server=http://localhost:8080 --certificate-authority=cacert.crt", + }, + { + "cert-key", + kubectl([]string{"config", "set-credentials", "USER", "--client-key=client.key", "--client-certificate=client.crt"}, ""), + "kubectl config set-credentials USER --client-key=client.key --client-certificate=client.crt", + }, + { + "password", + kubectl([]string{"config", "set-credentials", "USER", "--username=foo", "--password=secret"}, ""), + "kubectl config set-credentials USER --username=foo --password=******", + }, + { + "password2", + kubectl([]string{"config", "set-credentials", "USER", "--username", "foo", "--password", "secret"}, ""), + "kubectl config set-credentials USER --username foo --password=******", + }, + { + "token", + kubectl([]string{"config", "set-credentials", "USER", "--token=secret"}, ""), + "kubectl config set-credentials USER --token=******", + }, + { + "token2", + kubectl([]string{"config", "set-credentials", "USER", "--token", "secret"}, ""), + "kubectl config set-credentials USER --token=******", + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + actual := test.cmd.String() + if actual != test.expected { + t.Errorf("command.String() unexpected got = %s, want = %s\n", actual, test.expected) + } + }) + } +} From fd0209af9cbb0d28deaa0778091f85a18c2a56ee Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Fri, 11 Jun 2021 22:49:16 +0100 Subject: [PATCH 0795/1127] Prepare release v3.7.1 --- .version | 2 +- Dockerfile | 2 +- README.md | 6 +++--- docs/how_to/helm_repos/oci.md | 2 +- internal/app/main.go | 2 +- release-notes.md | 9 ++++----- 6 files changed, 11 insertions(+), 12 deletions(-) diff --git a/.version b/.version index d1e9cf3b..18852632 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.7.0 +v3.7.1 diff --git a/Dockerfile b/Dockerfile index e24aee2d..d7896630 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ ARG ALPINE_VERSION="3.12" ARG GLOBAL_KUBE_VERSION="v1.19.0" ARG GLOBAL_HELM_VERSION="v3.5.4" ARG GLOBAL_HELM_DIFF_VERSION="v3.1.3" -ARG GLOBAL_SOPS_VERSION="v3.7.0" +ARG GLOBAL_SOPS_VERSION="v3.7.1" ### Helm Installer ### FROM alpine:${ALPINE_VERSION} as helm-installer diff --git a/README.md b/README.md index 9dc9ca02..294742b8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.7.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.7.1&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -61,9 +61,9 @@ Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the d ```sh # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.0/helmsman_3.7.0_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.1/helmsman_3.7.0_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.0/helmsman_3.7.0_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.1/helmsman_3.7.0_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/docs/how_to/helm_repos/oci.md b/docs/how_to/helm_repos/oci.md index 2ebdf9ef..656e4f06 100644 --- a/docs/how_to/helm_repos/oci.md +++ b/docs/how_to/helm_repos/oci.md @@ -1,5 +1,5 @@ --- -version: v3.7.0 +version: v3.7.1 --- # Using OCI registries for helm charts diff --git a/internal/app/main.go b/internal/app/main.go index 24a1c5c1..2643919e 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.7.0" + appVersion = "v3.7.1" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index 19109e2f..d8e590ae 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,7 +1,6 @@ -# v3.7.0 +# v3.7.1 -## New features +## Fixes and improvements -- Added support for OCI registries (#612) - - more details in [docs/how_to/helm_repos/oci.md](https://github.com/Praqma/helmsman/blob/master/docs/how_to/helm_repos/oci.md) -- Added support for using `kubectl diff` instead of `helm diff` (#609) - Experimental +- fixed an issue with checking chart versions (#613) +- hide tokens and passwords from the logs (#615) From 962cc56a9db1527e4435552ce49bcd05fbd57ebf Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Fri, 11 Jun 2021 22:55:18 +0100 Subject: [PATCH 0796/1127] fix: wrong download urls in the README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 294742b8..1ffcb7ea 100644 --- a/README.md +++ b/README.md @@ -61,9 +61,9 @@ Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the d ```sh # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.1/helmsman_3.7.0_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.1/helmsman_3.7.1_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.1/helmsman_3.7.0_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.1/helmsman_3.7.1_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` From 76887688a1595435db5fab6be3d5d542fa8ab2e8 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 29 Jun 2021 13:13:21 +0100 Subject: [PATCH 0797/1127] fix: avoid marshal errors when merging multiple files --- go.mod | 20 +++++----- go.sum | 92 +++++++++++++++++++++++++++++---------------- internal/app/cli.go | 2 +- 3 files changed, 71 insertions(+), 43 deletions(-) diff --git a/go.mod b/go.mod index be91db76..ee25a5d3 100644 --- a/go.mod +++ b/go.mod @@ -3,28 +3,28 @@ module github.com/Praqma/helmsman go 1.13 require ( - cloud.google.com/go/storage v1.14.0 + cloud.google.com/go/storage v1.16.0 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.13.0 - github.com/Azure/go-autorest/autorest/adal v0.9.13 // indirect + github.com/Azure/go-autorest/autorest/adal v0.9.14 // indirect github.com/BurntSushi/toml v0.3.1 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515211354-1abdf898e024 - github.com/aws/aws-sdk-go v1.38.15 + github.com/aws/aws-sdk-go v1.38.69 github.com/davecgh/go-spew v1.1.1 // indirect + github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/uuid v1.2.0 // indirect github.com/imdario/mergo v0.3.12 github.com/joho/godotenv v1.3.0 github.com/kr/text v0.2.0 // indirect github.com/logrusorgru/aurora v0.0.0-20191116043053-66b7ad493a23 - golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect - golang.org/x/mod v0.4.2 // indirect - golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 - golang.org/x/text v0.3.6 // indirect - google.golang.org/api v0.44.0 // indirect - google.golang.org/genproto v0.0.0-20210406143921-e86de6bf7a46 // indirect - google.golang.org/grpc v1.37.0 // indirect + golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e // indirect + golang.org/x/net v0.0.0-20210614182718-04defd469f4e + golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914 // indirect + golang.org/x/tools v0.1.4 // indirect + google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84 // indirect + google.golang.org/protobuf v1.27.1 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index b0a178e3..93f3dab0 100644 --- a/go.sum +++ b/go.sum @@ -15,11 +15,12 @@ cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOY cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0 h1:hVhK90DwCdOAYGME/FJd9vNIZye9HBR6Yy3fu4js3N8= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -37,8 +38,8 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0 h1:6RRlFMv1omScs6iq2hfE3IvgE+l6RfJPampq8UZc5TU= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +cloud.google.com/go/storage v1.16.0 h1:1UwAux2OZP4310YXg5ohqBEpV16Y93uZG4+qOX7K2Kg= +cloud.google.com/go/storage v1.16.0/go.mod h1:ieKBmUyzcftN5tbxwnXClMKH00CfcQ+xL6NN0r5QfmE= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= @@ -47,8 +48,8 @@ github.com/Azure/azure-storage-blob-go v0.13.0/go.mod h1:pA9kNqtjUeQF2zOSu4s//nU github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest/adal v0.9.2/go.mod h1:/3SMAM86bP6wC9Ev35peQDUeqFZBMH07vvUOmg4z/fE= -github.com/Azure/go-autorest/autorest/adal v0.9.13 h1:Mp5hbtOePIzM8pJVRa3YLrWWmZtoxRXqUEzCfJt3+/Q= -github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= +github.com/Azure/go-autorest/autorest/adal v0.9.14 h1:G8hexQdV5D4khOXrWG2YuLCFKhWYmWD8bHYaXN5ophk= +github.com/Azure/go-autorest/autorest/adal v0.9.14/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= @@ -64,8 +65,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515211354-1abdf898e024 h1:dfZ6RF0UxHqt7xPz0r7h00apsaa6rIrFhT6Xly55Exk= github.com/apsdehal/go-logger v0.0.0-20190515211354-1abdf898e024/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.38.15 h1:usaPeqoxFUzy0FfBLZLZHya5Kv2cpURjb1jqCa7+odA= -github.com/aws/aws-sdk-go v1.38.15/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= +github.com/aws/aws-sdk-go v1.38.69 h1:V489lmrdkIQSfF6OAGZZ1Cavcm7eczCm2JcGvX+yHRg= +github.com/aws/aws-sdk-go v1.38.69/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -86,8 +87,9 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c= +github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -123,6 +125,8 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -135,13 +139,15 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0 h1:wCKgOCHuUEVfsaQLpPSJb7VdYCdTVZQAuOdYm1yc/60= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1VLQ= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -151,9 +157,9 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -204,6 +210,7 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -218,8 +225,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -242,8 +249,9 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= @@ -287,12 +295,13 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -304,8 +313,10 @@ golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 h1:0Ja1LBD+yisY6RWM/BH7TJVXWsSjs2VwBSmvSX4HdBc= -golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210615190721-d04028783cf1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914 h1:3B43BWw0xEBsLZ/NO1VALz6fppU3481pik+2Ksv45z8= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -351,13 +362,17 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 h1:F5Gozwx4I1xtr/sr/8CFbb57iKi3297KFs0QDbGN60A= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -416,9 +431,12 @@ golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4 h1:cVngSRcfgyZCzys3KYOpCFa+4dqX/Oub9tAq00ttGVs= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -445,8 +463,10 @@ google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34q google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.44.0 h1:URs6qR1lAxDsqWITsQXI4ZkGiYJ5dHtRNiCpfs2OeKA= -google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.49.0 h1:gjIBDxlTG7vnzMmEnYwTnvLTF8Rjzo+ETCgEX1YZ/fY= +google.golang.org/api v0.49.0/go.mod h1:BECiH72wsfwUvOVn3+btPD5WHi0LzavZReBndi42L18= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -489,15 +509,19 @@ google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210406143921-e86de6bf7a46 h1:f4STrQZf8jaowsiUitigvrqMCCM4QJH1A2JCSI7U1ow= -google.golang.org/genproto v0.0.0-20210406143921-e86de6bf7a46/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210624174822-c5cf32407d0a/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84 h1:R1r5J0u6Cx+RNl/6mezTw6oA14cmKC96FeUwL6A9bd4= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -516,8 +540,11 @@ google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA5 google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0 h1:uSZWeQJX5j11bIQ4AJoj+McDBo29cY1MCoC1wO3ts+c= google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -529,8 +556,9 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/app/cli.go b/internal/app/cli.go index 61ce5233..eb7cff84 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -219,8 +219,8 @@ func (c *cli) readState(s *state) error { _ = os.MkdirAll(tempFilesDir, 0o755) // read the TOML/YAML desired state file - var fileState state for _, f := range c.files { + var fileState state if err := fileState.fromFile(f); err != nil { return err From e3cd8871aa56fc77c2fe8fb6350e6b88673ae8bc Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Thu, 8 Jul 2021 16:16:22 +0100 Subject: [PATCH 0798/1127] Prepare release v3.7.2 --- .circleci/config.yml | 2 +- .version | 2 +- Dockerfile | 4 ++-- README.md | 6 +++--- internal/app/main.go | 2 +- release-notes.md | 5 ++--- 6 files changed, 10 insertions(+), 11 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d30609be..31e70ee3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -35,7 +35,7 @@ jobs: - run: name: build docker images and push them to dockerhub command: | - helm_versions=( "v3.3.4" "v3.4.2" "v3.5.4" ) + helm_versions=( "v3.4.2" "v3.5.4" "v3.6.2" ) TAG=$(git describe --abbrev=0 --tags) docker login -u $DOCKER_USER -p $DOCKER_PASS diff --git a/.version b/.version index 18852632..22726832 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.7.1 +v3.7.2 diff --git a/Dockerfile b/Dockerfile index d7896630..ddb591e7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ ARG GO_VERSION="1.15.2" ARG ALPINE_VERSION="3.12" -ARG GLOBAL_KUBE_VERSION="v1.19.0" -ARG GLOBAL_HELM_VERSION="v3.5.4" +ARG GLOBAL_KUBE_VERSION="v1.19.8" +ARG GLOBAL_HELM_VERSION="v3.6.2" ARG GLOBAL_HELM_DIFF_VERSION="v3.1.3" ARG GLOBAL_SOPS_VERSION="v3.7.1" diff --git a/README.md b/README.md index 1ffcb7ea..68192e79 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.7.1&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.7.2&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -61,9 +61,9 @@ Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the d ```sh # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.1/helmsman_3.7.1_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.2/helmsman_3.7.2_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.1/helmsman_3.7.1_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.2/helmsman_3.7.2_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/internal/app/main.go b/internal/app/main.go index 2643919e..43ddf7e6 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.7.1" + appVersion = "v3.7.2" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index d8e590ae..7d37e0e4 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,6 +1,5 @@ -# v3.7.1 +# v3.7.2 ## Fixes and improvements -- fixed an issue with checking chart versions (#613) -- hide tokens and passwords from the logs (#615) +- fixed an issue with yaml marshalling when merging multiple files From 58ca8d5640c6c0e59bad28cf3c0884e35215ef93 Mon Sep 17 00:00:00 2001 From: Calliope Gardner Date: Mon, 9 Aug 2021 17:03:47 +0100 Subject: [PATCH 0799/1127] bug(oci): error to user on failing to handle OCI charts --- internal/app/utils.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/app/utils.go b/internal/app/utils.go index fefb8c3a..78cc3df9 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -211,7 +211,9 @@ func downloadFile(file string, dir string, outfile string) string { case "oci": dest := filepath.Dir(outfile) fileName := strings.Split(filepath.Base(file), ":")[0] - helmExportChart(strings.ReplaceAll(file, "oci://", ""), dest) + if err := helmExportChart(strings.ReplaceAll(file, "oci://", ""), dest); err != nil { + log.Fatal(err.Error()) + } return filepath.Join(dest, fileName) case "https", "http": if err := downloadFileFromURL(file, outfile); err != nil { From d534298672bace59b9dcd347ff3f754cd4ab518f Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 10 Aug 2021 19:40:12 +0100 Subject: [PATCH 0800/1127] feat: allow setting helm diff flags --- docs/desired_state_specification.md | 58 +++++++++++++++-------------- internal/app/release.go | 53 +++++++++++++------------- internal/app/state_test.go | 41 ++++++++++---------- 3 files changed, 78 insertions(+), 74 deletions(-) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 52113ec9..4be7b4cb 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -86,7 +86,7 @@ Synopsis: defines the context in which a DSF is used. This context is used as th ```yaml context: prod-apps -... +# ... ``` ## Settings @@ -112,7 +112,7 @@ The following options can be skipped if your kubectl context is already created - **slackWebhook** : a [Slack](http://slack.com) Webhook URL to receive Helmsman notifications. This can be passed directly or in an environment variable. - **msTeamsWebhook** : a [Microsoft Teams](https://www.microsoft.com/pl-pl/microsoft-teams/group-chat-software) Webhook URL to receive Helmsman notifications. This can be passed directly or in an environment variable. - **reverseDelete** : if set to `true` it will reverse the priority order whilst deleting. -- **eyamlEnabled** : if set to `true' it will use [hiera-eyaml](https://github.com/voxpupuli/hiera-eyaml) to decrypt secret files instead of using default helm-secrets based on sops +- **eyamlEnabled** : if set to `true` it will use [hiera-eyaml](https://github.com/voxpupuli/hiera-eyaml) to decrypt secret files instead of using default helm-secrets based on sops - **eyamlPrivateKeyPath** : if set with path to the eyaml private key file, it will use it instead of looking for default one in ./keys directory relative to where Helmsman were run. It needs to be defined in conjunction with eyamlPublicKeyPath. - **eyamlPublicKeyPath** : if set with path to the eyaml public key file, it will use it instead of looking for default one in ./keys directory relative to where Helmsman were run. It needs to be defined in conjunction with eyamlPrivateKeyPath. - **globalHooks** : defines global lifecycle hooks to apply yaml manifest before and/or after different helmsman operations. Check [here](how_to/apps/lifecycle_hooks.md) for more details. @@ -358,36 +358,38 @@ Options: **Required** -- **namespace** : the namespace where the release should be deployed. The namespace should map to one of the ones defined in [namespaces](#namespaces). -- **enabled** : describes the required state of the release (true for enabled, false for disabled). Once a release is deployed, you can change it to false if you want to delete this release [default is false]. -- **chart** : the chart name. It should contain the repo name as well. Example: repoName/chartName. Changing the chart name means delete and reinstall this release using the new Chart. -- **version** : the chart version. +- **namespace** : the namespace where the release should be deployed. The namespace should map to one of the ones defined in [namespaces](#namespaces). +- **enabled** : describes the required state of the release (true for enabled, false for disabled). Once a release is deployed, you can change it to false if you want to delete this release [default is false]. +- **chart** : the chart name. It should contain the repo name as well. Example: repoName/chartName. Changing the chart name means delete and reinstall this release using the new Chart. +- **version** : the chart version. **Optional** -- **group** : group name this apps belongs to. It has no effect until Helmsman's flag `-group` is passed. Check this [doc](how_to/misc/limit-deployment-to-specific-group-of-apps.md) for more details. -- **description** : a release metadata for human readers. -- **valuesFile** : a valid path (URL, cloud bucket, local absolute/relative file path) to custom Helm values.yaml file. File extension must be `yaml`. Cannot be used with valuesFiles together. Leaving it empty uses the default chart values. -- **valuesFiles** : array of valid paths (URL, cloud bucket, local absolute/relative file path) to custom Helm values.yaml file. File extension must be `yaml`. Cannot be used with valuesFile together. Leaving it empty uses the default chart values. -> The values file(s) path is resolved when the DSF yaml/toml file is loaded, relative to the path that the dsf was loaded from. -- **secretsFile** : a valid path (URL, cloud bucket, local absolute/relative file path) to custom Helm secrets.yaml file. File extension must be `yaml`. Cannot be used with secretsFiles together. Leaving it empty uses the default chart secrets. -- **secretsFiles** : array of valid paths (URL, cloud bucket, local absolute/relative file path) to custom Helm secrets.yaml file. File extension must be `yaml`. Cannot be used with secretsFile together. Leaving it empty uses the default chart secrets. -> The secrets file(s) path is resolved when the DSF yaml/toml file is loaded, relative to the path that the dsf was loaded from. -> To use the secrets files you must have the helm-secrets plugin -- **test** : defines whether to run the chart tests whenever the release is installed. Default is false. -- **protected** : defines if the release should be protected against changes. Namespace-level protection has higher priority than this flag. Check the [protection guide](how_to/misc/protect_namespaces_and_releases.md) for more details. Default is false. -- **wait** : defines whether Helmsman should block execution until all k8s resources are in a ready state. Default is false. -- **timeout** : helm timeout in seconds. Default 300 seconds. -- **noHooks** : helm noHooks option. If true, it will disable pre/post upgrade hooks. Default is false. -- **priority** : defines the priority of applying operations on this release. Only negative values allowed and the lower the value, the higher the priority. Default priority is 0. Apps with equal priorities will be applied in the order they were added in your state file (DSF). -- **set** : is used to override certain values from values.yaml with values from environment variables (or ,starting from v1.3.0-rc, directly provided in the Desired State File). This is particularly useful for passing secrets to charts. If the an environment variable with the same name as the provided value exists, the environment variable value will be used, otherwise, the provided value will be used as is. The TOML stanza for this is `[apps..set]` -- **setString** : is used to override String values from values.yaml or chart's defaults. This uses the `--set-string` flag in helm which is available only in helm >v2.9.0. This option is useful for image tags and the like. The TOML stanza for this is `[apps..setString]` -- **setFile** : is used to override values from values.yaml or chart's defaults from provided file. This uses the `--set-file` flag in helm. This option is useful for embedding file contents in the values. The TOML stanza for this is `[apps..setFile]` -> set, setString and setFile can't take nested elements. If you need to provide nested values, you can combine them in one line with dots e.g. `TOML: "image.tag"=some\_value` `YAML: "image.tag": some\_value` -- **helmFlags** : array of `helm` upgrade flags, is used to pass flags to helm install/upgrade commands. **These flags are not passed to helm diff**. For setting values, use **set**, **setString** or **setFile** instead. +- **group** : group name this apps belongs to. It has no effect until Helmsman's flag `-group` is passed. Check this [doc](how_to/misc/limit-deployment-to-specific-group-of-apps.md) for more details. +- **description** : a release metadata for human readers. +- **valuesFile** : a valid path (URL, cloud bucket, local absolute/relative file path) to custom Helm values.yaml file. File extension must be `yaml`. Cannot be used with valuesFiles together. Leaving it empty uses the default chart values. +- **valuesFiles** : array of valid paths (URL, cloud bucket, local absolute/relative file path) to custom Helm values.yaml file. File extension must be `yaml`. Cannot be used with valuesFile together. Leaving it empty uses the default chart values. + > The values file(s) path is resolved when the DSF yaml/toml file is loaded, relative to the path that the dsf was loaded from. +- **secretsFile** : a valid path (URL, cloud bucket, local absolute/relative file path) to custom Helm secrets.yaml file. File extension must be `yaml`. Cannot be used with secretsFiles together. Leaving it empty uses the default chart secrets. +- **secretsFiles** : array of valid paths (URL, cloud bucket, local absolute/relative file path) to custom Helm secrets.yaml file. File extension must be `yaml`. Cannot be used with secretsFile together. Leaving it empty uses the default chart secrets. + > The secrets file(s) path is resolved when the DSF yaml/toml file is loaded, relative to the path that the dsf was loaded from. + > To use the secrets files you must have the helm-secrets plugin +- **test** : defines whether to run the chart tests whenever the release is installed. Default is false. +- **protected** : defines if the release should be protected against changes. Namespace-level protection has higher priority than this flag. Check the [protection guide](how_to/misc/protect_namespaces_and_releases.md) for more details. Default is false. +- **wait** : defines whether Helmsman should block execution until all k8s resources are in a ready state. Default is false. +- **timeout** : helm timeout in seconds. Default 300 seconds. +- **noHooks** : helm noHooks option. If true, it will disable pre/post upgrade hooks. Default is false. +- **priority** : defines the priority of applying operations on this release. Only negative values allowed and the lower the value, the higher the priority. Default priority is 0. Apps with equal priorities will be applied in the order they were added in your state file (DSF). +- **set** : is used to override certain values from values.yaml with values from environment variables (or ,starting from v1.3.0-rc, directly provided in the Desired State File). This is particularly useful for passing secrets to charts. If the an environment variable with the same name as the provided value exists, the environment variable value will be used, otherwise, the provided value will be used as is. The TOML stanza for this is `[apps..set]` +- **setString** : is used to override String values from values.yaml or chart's defaults. This uses the `--set-string` flag in helm which is available only in helm >v2.9.0. This option is useful for image tags and the like. The TOML stanza for this is `[apps..setString]` +- **setFile** : is used to override values from values.yaml or chart's defaults from provided file. This uses the `--set-file` flag in helm. This option is useful for embedding file contents in the values. The TOML stanza for this is `[apps..setFile]` + > set, setString and setFile can't take nested elements. If you need to provide nested values, you can combine them in one line with dots e.g. `TOML: "image.tag"=some\_value` `YAML: "image.tag": some\_value` +- **helmFlags** : array of `helm upgrade` flags, is used to pass flags to helm install/upgrade commands. **These flags are not passed to helm diff**. For setting values, use **set**, **setString** or **setFile** instead. +- **helmDiffFlags** : array of `helm diff upgrade` flags, is used to pass flags to helm diff upgrade commands. **These flags are not passed to helm during upgrade**. For setting values, use **set**, **setString** or **setFile** instead. + > helmDiffFlags can be useful for example if you need to use the `--disable-openapi-validation` flag, in that case you would need to set it both in helmFlags and helmDiffFlags - **hooks** : defines global lifecycle hooks to apply yaml manifest before and/or after different helmsman operations. Check [here](how_to/apps/lifecycle_hooks.md) for more details. Unset hooks for a release are inherited from `globalHooks` in the [settings](#Settings) stanza. -- **maxHistory** : defines the maximum number of helm revisions state (secrets/configmap) to keep. If unset, it will inherit the value of `settings.globalMaxHistory`, if that's also unset, it defaults to 10. -- **postRenderer** : the path to an executable to be used for post rendering (requires Helm 3.1+ and helm-diff v3.1.2+) +- **maxHistory** : defines the maximum number of helm revisions state (secrets/configmap) to keep. If unset, it will inherit the value of `settings.globalMaxHistory`, if that's also unset, it defaults to 10. +- **postRenderer** : the path to an executable to be used for post rendering (requires Helm 3.1+ and helm-diff v3.1.2+) Example: diff --git a/internal/app/release.go b/internal/app/release.go index eeeaa424..2780d1c7 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -10,31 +10,32 @@ import ( // release type representing Helm releases which are described in the desired state type release struct { - Name string `yaml:"name"` - Description string `yaml:"description"` - Namespace string `yaml:"namespace"` - Enabled bool `yaml:"enabled"` - Group string `yaml:"group"` - Chart string `yaml:"chart"` - Version string `yaml:"version"` - ValuesFile string `yaml:"valuesFile"` - ValuesFiles []string `yaml:"valuesFiles"` - SecretsFile string `yaml:"secretsFile"` - SecretsFiles []string `yaml:"secretsFiles"` - PostRenderer string `yaml:"postRenderer"` - Test bool `yaml:"test"` - Protected bool `yaml:"protected"` - Wait bool `yaml:"wait"` - Priority int `yaml:"priority"` - Set map[string]string `yaml:"set"` - SetString map[string]string `yaml:"setString"` - SetFile map[string]string `yaml:"setFile"` - HelmFlags []string `yaml:"helmFlags"` - NoHooks bool `yaml:"noHooks"` - Timeout int `yaml:"timeout"` - Hooks map[string]interface{} `yaml:"hooks"` - MaxHistory int `yaml:"maxHistory"` - disabled bool + Name string `yaml:"name"` + Description string `yaml:"description"` + Namespace string `yaml:"namespace"` + Enabled bool `yaml:"enabled"` + Group string `yaml:"group"` + Chart string `yaml:"chart"` + Version string `yaml:"version"` + ValuesFile string `yaml:"valuesFile"` + ValuesFiles []string `yaml:"valuesFiles"` + SecretsFile string `yaml:"secretsFile"` + SecretsFiles []string `yaml:"secretsFiles"` + PostRenderer string `yaml:"postRenderer"` + Test bool `yaml:"test"` + Protected bool `yaml:"protected"` + Wait bool `yaml:"wait"` + Priority int `yaml:"priority"` + Set map[string]string `yaml:"set"` + SetString map[string]string `yaml:"setString"` + SetFile map[string]string `yaml:"setFile"` + HelmFlags []string `yaml:"helmFlags"` + HelmDiffFlags []string `yaml:"helmDiffFlags"` + NoHooks bool `yaml:"noHooks"` + Timeout int `yaml:"timeout"` + Hooks map[string]interface{} `yaml:"hooks"` + MaxHistory int `yaml:"maxHistory"` + disabled bool } func (r *release) key() string { @@ -407,7 +408,7 @@ func (r *release) getHelmArgsFor(action string, optionalNamespaceOverride ...str case "install", "upgrade": return concat([]string{"upgrade", r.Name, r.Chart, "--install", "--version", r.Version, "--namespace", r.Namespace}, r.getValuesFiles(), r.getSetValues(), r.getSetStringValues(), r.getSetFileValues(), r.getHelmFlags(), r.getPostRenderer()) case "diff": - return concat([]string{"upgrade", r.Name, r.Chart, "--version", r.Version, "--namespace", r.Namespace}, r.getValuesFiles(), r.getSetValues(), r.getSetStringValues(), r.getSetFileValues(), r.getPostRenderer()) + return concat([]string{"upgrade", r.Name, r.Chart, "--version", r.Version, "--namespace", r.Namespace}, r.getValuesFiles(), r.getSetValues(), r.getSetStringValues(), r.getSetFileValues(), r.HelmDiffFlags, r.getPostRenderer()) case "uninstall": return concat([]string{action, "--namespace", ns, r.Name}, flags.getRunFlags()) default: diff --git a/internal/app/state_test.go b/internal/app/state_test.go index 1e26ae22..5330a2be 100644 --- a/internal/app/state_test.go +++ b/internal/app/state_test.go @@ -392,26 +392,27 @@ func Test_state_validate(t *testing.T) { func createFullReleasePointer(chart, version string) *release { return &release{ - Name: "", - Description: "", - Namespace: "", - Enabled: true, - Chart: chart, - Version: version, - ValuesFile: "", - ValuesFiles: []string{}, - SecretsFile: "", - SecretsFiles: []string{}, - Test: false, - Protected: false, - Wait: false, - Priority: 0, - Set: make(map[string]string), - SetString: make(map[string]string), - HelmFlags: []string{}, - NoHooks: false, - Timeout: 0, - PostRenderer: "", + Name: "", + Description: "", + Namespace: "", + Enabled: true, + Chart: chart, + Version: version, + ValuesFile: "", + ValuesFiles: []string{}, + SecretsFile: "", + SecretsFiles: []string{}, + Test: false, + Protected: false, + Wait: false, + Priority: 0, + Set: make(map[string]string), + SetString: make(map[string]string), + HelmFlags: []string{}, + HelmDiffFlags: []string{}, + NoHooks: false, + Timeout: 0, + PostRenderer: "", } } From 57c592417942a44221a94b8cf450d2df11909c8b Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 10 Aug 2021 20:05:59 +0100 Subject: [PATCH 0801/1127] refactor: cleanup the getHelmFlags method Signed-off-by: Luis Davim --- internal/app/release.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/internal/app/release.go b/internal/app/release.go index 2780d1c7..e9fd82b9 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -378,13 +378,11 @@ func (r *release) getMaxHistory() []string { // getHelmFlags returns helm flags func (r *release) getHelmFlags() []string { var flgs []string - var force string if flags.forceUpgrades { - force = "--force" + flgs = append(flgs, "--force") } - flgs = append(flgs, r.HelmFlags...) - return concat(r.getNoHooks(), r.getWait(), r.getTimeout(), r.getMaxHistory(), flags.getRunFlags(), []string{force}, flgs) + return concat(r.getNoHooks(), r.getWait(), r.getTimeout(), r.getMaxHistory(), flags.getRunFlags(), r.HelmFlags, flgs) } // getPostRenderer returns the post-renderer Helm flag From 93fa027f7e768ee98aaa3dc14d0ade2edd1cf9eb Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Thu, 12 Aug 2021 12:59:33 +0100 Subject: [PATCH 0802/1127] fix: the release protection flag should be respected even when destroying Signed-off-by: Luis Davim --- internal/app/decision_maker.go | 76 +++++++++++++++++----------------- internal/app/helm_release.go | 2 + 2 files changed, 41 insertions(+), 37 deletions(-) diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index b38b14ea..dc1f7a84 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -94,6 +94,12 @@ func (cs *currentState) decide(r *release, n *namespace, p *plan, c *chartInfo) return } + if r.isProtected(cs, n) { + p.addDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ + "protection is removed.", r.Priority, noop) + return + } + if flags.destroy { if ok := cs.releaseExists(r, ""); ok { p.addDecision("Release [ "+r.Name+" ] will be DELETED (destroy flag enabled).", r.Priority, delete) @@ -104,48 +110,33 @@ func (cs *currentState) decide(r *release, n *namespace, p *plan, c *chartInfo) if !r.Enabled { if ok := cs.releaseExists(r, ""); ok { - if r.isProtected(cs, n) { - p.addDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ - "protection is removed.", r.Priority, noop) - return - } p.addDecision("Release [ "+r.Name+" ] is desired to be DELETED.", r.Priority, delete) r.uninstall(p) - return + } else { + p.addDecision("Release [ "+r.Name+" ] disabled", r.Priority, noop) } - p.addDecision("Release [ "+r.Name+" ] disabled", r.Priority, noop) return } - if ok := cs.releaseExists(r, helmStatusDeployed); ok { - if !r.isProtected(cs, n) { - if err := cs.inspectUpgradeScenario(r, p, c); err != nil { // upgrade or move - log.Fatal(err.Error()) - } - } else { - p.addDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ - "you remove its protection.", r.Priority, noop) + switch cs.releaseStatus(r) { + case helmStatusDeployed: + if err := cs.inspectUpgradeScenario(r, p, c); err != nil { // upgrade or move + log.Fatal(err.Error()) } - } else if ok := cs.releaseExists(r, helmStatusUninstalled); ok { - if !r.isProtected(cs, n) { - r.rollback(cs, p) // rollback - } else { - p.addDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ - "you remove its protection.", r.Priority, noop) - } - } else if ok := cs.releaseExists(r, helmStatusFailed); ok { - if !r.isProtected(cs, n) { - p.addDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is in FAILED state. Upgrade is scheduled!", r.Priority, change) - r.upgrade(p) - } else { - p.addDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ - "you remove its protection.", r.Priority, noop) - } - } else if cs.releaseExists(r, helmStatusPendingInstall) || cs.releaseExists(r, helmStatusPendingUpgrade) || cs.releaseExists(r, helmStatusPendingRollback) || cs.releaseExists(r, helmStatusUninstalling) { + + case helmStatusUninstalled: + r.rollback(cs, p) // rollback + + case helmStatusFailed: + p.addDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is in FAILED state. Upgrade is scheduled!", r.Priority, change) + r.upgrade(p) + return + + case helmStatusPendingInstall, helmStatusPendingUpgrade, helmStatusPendingRollback, helmStatusUninstalling: log.Fatal("Release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ] is in a pending (install/upgrade/rollback or uninstalling) state. " + "This means application is being operated on outside of this Helmsman invocation's scope." + "Exiting, as this may cause issues when continuing...") - } else { + default: // If there is no release in the cluster with this name and in this namespace, then install it! if _, ok := cs.releases[r.key()]; !ok { p.addDecision("Release [ "+r.Name+" ] version [ "+r.Version+" ] will be installed in [ "+r.Namespace+" ] namespace", r.Priority, create) @@ -158,19 +149,30 @@ func (cs *currentState) decide(r *release, n *namespace, p *plan, c *chartInfo) } } +// releaseStatus returns the status of a release in the Current State. +func (cs *currentState) releaseStatus(r *release) string { + v, ok := cs.releases[r.key()] + if !ok || v.HelmsmanContext != curContext { + return helmStatusMissing + } + return v.Status +} + // releaseExists checks if a Helm release is/was deployed in a k8s cluster. // It searches the Current State for releases. // The key format for releases uniqueness is: // If status is provided as an input [deployed, deleted, failed], then the search will verify the release status matches the search status. func (cs *currentState) releaseExists(r *release, status string) bool { - v, ok := cs.releases[r.key()] - if !ok || v.HelmsmanContext != curContext { - return false - } + currentState := cs.releaseStatus(r) if status != "" { - return v.Status == status + return currentState == status } + + if currentState == helmStatusMissing { + return false + } + return true } diff --git a/internal/app/helm_release.go b/internal/app/helm_release.go index 3f7c9180..f25ace5b 100644 --- a/internal/app/helm_release.go +++ b/internal/app/helm_release.go @@ -9,6 +9,7 @@ import ( "sync" ) +// TODO: can we import these from helm? const ( helmStatusDeployed = "deployed" helmStatusUninstalled = "uninstalled" @@ -17,6 +18,7 @@ const ( helmStatusPendingInstall = "pending-install" helmStatusPendingRollback = "pending-rollback" helmStatusUninstalling = "uninstalling" + helmStatusMissing = "missing" ) // helmRelease represents the current state of a release From d1e916e3fa6841b711a7eb894d4c4c7b0f7a35cc Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sat, 28 Aug 2021 17:18:29 +0100 Subject: [PATCH 0803/1127] release: v3.7.3 --- .circleci/config.yml | 2 +- .version | 2 +- Dockerfile | 2 +- README.md | 6 +++--- internal/app/main.go | 2 +- release-notes.md | 7 +++++-- 6 files changed, 12 insertions(+), 9 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 31e70ee3..836d81dd 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -35,7 +35,7 @@ jobs: - run: name: build docker images and push them to dockerhub command: | - helm_versions=( "v3.4.2" "v3.5.4" "v3.6.2" ) + helm_versions=( "v3.4.2" "v3.5.4" "v3.6.3" ) TAG=$(git describe --abbrev=0 --tags) docker login -u $DOCKER_USER -p $DOCKER_PASS diff --git a/.version b/.version index 22726832..46c13383 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.7.2 +v3.7.3 diff --git a/Dockerfile b/Dockerfile index ddb591e7..79740de3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ ARG GO_VERSION="1.15.2" ARG ALPINE_VERSION="3.12" ARG GLOBAL_KUBE_VERSION="v1.19.8" -ARG GLOBAL_HELM_VERSION="v3.6.2" +ARG GLOBAL_HELM_VERSION="v3.6.3" ARG GLOBAL_HELM_DIFF_VERSION="v3.1.3" ARG GLOBAL_SOPS_VERSION="v3.7.1" diff --git a/README.md b/README.md index 68192e79..cc456630 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.7.2&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.7.3&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -61,9 +61,9 @@ Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the d ```sh # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.2/helmsman_3.7.2_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.3/helmsman_3.7.3_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.2/helmsman_3.7.2_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.3/helmsman_3.7.3_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/internal/app/main.go b/internal/app/main.go index 43ddf7e6..3d6219f4 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.7.2" + appVersion = "v3.7.3" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index 7d37e0e4..a5dce252 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,5 +1,8 @@ -# v3.7.2 +# v3.7.3 ## Fixes and improvements -- fixed an issue with yaml marshalling when merging multiple files +- feat: allow setting helm diff flags (#620) +- fix: the release protection flag should be respected even when destroying (#622) +- fix: Pass error to user if we fail to correctly pull or export oci chart images (#619) +- refactor: code clean up From a2ec87b52ee8e6319dfa4d4a8fb3ea3b6a147baf Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Mon, 30 Aug 2021 13:13:58 +0100 Subject: [PATCH 0804/1127] chore: update Go version --- CONTRIBUTION.md | 13 +++++++------ Dockerfile | 6 +++--- go.mod | 20 +++++++++++++++++++- go.sum | 2 -- 4 files changed, 29 insertions(+), 12 deletions(-) diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md index 533bc835..262810a3 100644 --- a/CONTRIBUTION.md +++ b/CONTRIBUTION.md @@ -4,9 +4,9 @@ Pull requests, feeback/feature requests are all welcome. This guide will be upda ## Build helmsman from source -To build helmsman from source, you need go:1.13+. Follow the steps below: +To build helmsman from source, you need go:1.17+. Follow the steps below: -``` +```sh git clone https://github.com/Praqma/helmsman.git make tools # installs few tools for testing, building, releasing make build @@ -17,6 +17,7 @@ make test `master` is where Helmsman latest code lives. `1.x` this is where Helmsman versions 1.x lives. + > Helmsman v1.x supports helm v2.x only and will no longer be supported except for bug fixes and minor changes. ## Submitting pull requests @@ -24,9 +25,9 @@ make test - If your PR is for Helmsman v1.x, it should target the `1.x` branch. - Please make sure you state the purpose of the pull request and that the code you submit is documented. If in doubt, [this guide](https://blog.github.com/2015-01-21-how-to-write-the-perfect-pull-request/) offers some good tips on writing a PR. - Please make sure you update the documentation with new features or the changes your PR adds. The following places are required. - - Update existing [how_to](docs/how_to/) guides or create new ones. - - If necessary, Update the [Desired State File spec](docs/desired_state_specification.md) - - If adding new flags, Update the [cmd reference](docs/cmd_reference.md) + - Update existing [how_to](docs/how_to/) guides or create new ones. + - If necessary, Update the [Desired State File spec](docs/desired_state_specification.md) + - If adding new flags, Update the [cmd reference](docs/cmd_reference.md) - Please add tests wherever possible to test your new changes. ## Contribution to documentation @@ -42,6 +43,7 @@ Please provide details of the issue, versions of helmsman, helm and kubernetes a Release is automated from CicrcleCI based on Git tags. [Goreleaser](goreleaser.com) is used to release the binaries and update the release notes on Github while the CircleCI pipeline builds a set of docker images and pushes them to dockerhub. The following steps are needed to cut a release (They assume that you are on master and the code is up to date): + 1. Change the version variable in [main.go](internal/app/main.go) and in [.version](.version) 2. Update the [release-notes.md](release-notes.md) file with new version and changelog. 3. Update the installation section in the [README.md](README.md) file to point to the latest version. @@ -50,4 +52,3 @@ The following steps are needed to cut a release (They assume that you are on mas 6. Create a git tag with the following command: `git tag -a -m "" ` 7. Push your commit and tag with `git push --follow-tags` 8. This should trigger the [pipeline on circleci](https://circleci.com/gh/Praqma/workflows/helmsman) which eventually releases to Github and dockerhub. - diff --git a/Dockerfile b/Dockerfile index 79740de3..5d52b43d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ -ARG GO_VERSION="1.15.2" -ARG ALPINE_VERSION="3.12" -ARG GLOBAL_KUBE_VERSION="v1.19.8" +ARG GO_VERSION="1.17.0" +ARG ALPINE_VERSION="3.14" +ARG GLOBAL_KUBE_VERSION="v1.22.1" ARG GLOBAL_HELM_VERSION="v3.6.3" ARG GLOBAL_HELM_DIFF_VERSION="v3.1.3" ARG GLOBAL_SOPS_VERSION="v3.7.1" diff --git a/go.mod b/go.mod index ee25a5d3..93de1872 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/Praqma/helmsman -go 1.13 +go 1.17 require ( cloud.google.com/go/storage v1.16.0 @@ -28,3 +28,21 @@ require ( gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v2 v2.4.0 ) + +require ( + cloud.google.com/go v0.84.0 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/googleapis/gax-go/v2 v2.0.5 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/jstemmer/go-junit-report v0.9.1 // indirect + github.com/mattn/go-ieproxy v0.0.1 // indirect + go.opencensus.io v0.23.0 // indirect + golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect + golang.org/x/mod v0.4.2 // indirect + golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect + golang.org/x/text v0.3.6 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + google.golang.org/api v0.49.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/grpc v1.38.0 // indirect +) diff --git a/go.sum b/go.sum index 93f3dab0..ebc1cacf 100644 --- a/go.sum +++ b/go.sum @@ -52,7 +52,6 @@ github.com/Azure/go-autorest/autorest/adal v0.9.14 h1:G8hexQdV5D4khOXrWG2YuLCFKh github.com/Azure/go-autorest/autorest/adal v0.9.14/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= -github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= @@ -125,7 +124,6 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= From 0ce139c560d7a39c5bf254c8485c614e11a011c1 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Mon, 30 Aug 2021 13:18:22 +0100 Subject: [PATCH 0805/1127] chore: update dependencies --- Makefile | 2 +- go.mod | 32 +++++++++------------ go.sum | 84 ++++++++++++++++++++++++++++++++++++++------------------ 3 files changed, 72 insertions(+), 46 deletions(-) diff --git a/Makefile b/Makefile index e518c685..e26247f7 100644 --- a/Makefile +++ b/Makefile @@ -56,7 +56,7 @@ deps: ## Install depdendencies. Runs `go get` internally. update-deps: ## Update depdendencies. Runs `go get -u` internally. - @GOFLAGS="" go get -u + @GOFLAGS="" go get -t -u ./... @GOFLAGS="" go mod tidy @GOFLAGS="" go mod vendor diff --git a/go.mod b/go.mod index 93de1872..9f9b7201 100644 --- a/go.mod +++ b/go.mod @@ -5,44 +5,38 @@ go 1.17 require ( cloud.google.com/go/storage v1.16.0 github.com/Azure/azure-pipeline-go v0.2.3 - github.com/Azure/azure-storage-blob-go v0.13.0 + github.com/Azure/azure-storage-blob-go v0.14.0 github.com/Azure/go-autorest/autorest/adal v0.9.14 // indirect - github.com/BurntSushi/toml v0.3.1 + github.com/BurntSushi/toml v0.4.1 github.com/Masterminds/semver v1.5.0 - github.com/apsdehal/go-logger v0.0.0-20190515211354-1abdf898e024 - github.com/aws/aws-sdk-go v1.38.69 + github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 + github.com/aws/aws-sdk-go v1.40.32 github.com/davecgh/go-spew v1.1.1 // indirect github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/google/uuid v1.2.0 // indirect + github.com/google/uuid v1.3.0 // indirect github.com/imdario/mergo v0.3.12 github.com/joho/godotenv v1.3.0 github.com/kr/text v0.2.0 // indirect - github.com/logrusorgru/aurora v0.0.0-20191116043053-66b7ad493a23 + github.com/logrusorgru/aurora v2.0.3+incompatible golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e // indirect golang.org/x/net v0.0.0-20210614182718-04defd469f4e - golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914 // indirect - golang.org/x/tools v0.1.4 // indirect - google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84 // indirect + golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect + google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71 // indirect google.golang.org/protobuf v1.27.1 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v2 v2.4.0 ) require ( - cloud.google.com/go v0.84.0 // indirect + cloud.google.com/go v0.93.3 // indirect github.com/golang/protobuf v1.5.2 // indirect - github.com/googleapis/gax-go/v2 v2.0.5 // indirect + github.com/googleapis/gax-go/v2 v2.1.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/jstemmer/go-junit-report v0.9.1 // indirect github.com/mattn/go-ieproxy v0.0.1 // indirect go.opencensus.io v0.23.0 // indirect - golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect - golang.org/x/mod v0.4.2 // indirect - golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect + golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 // indirect golang.org/x/text v0.3.6 // indirect - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect - google.golang.org/api v0.49.0 // indirect + google.golang.org/api v0.54.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/grpc v1.38.0 // indirect + google.golang.org/grpc v1.40.0 // indirect ) diff --git a/go.sum b/go.sum index ebc1cacf..44705d7a 100644 --- a/go.sum +++ b/go.sum @@ -19,8 +19,11 @@ cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECH cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= -cloud.google.com/go v0.84.0 h1:hVhK90DwCdOAYGME/FJd9vNIZye9HBR6Yy3fu4js3N8= cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3 h1:wPBktZFzYBcCZVARvwVKqH1uEj+aLXofJEtrb4oOsio= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -43,11 +46,11 @@ cloud.google.com/go/storage v1.16.0/go.mod h1:ieKBmUyzcftN5tbxwnXClMKH00CfcQ+xL6 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= -github.com/Azure/azure-storage-blob-go v0.13.0 h1:lgWHvFh+UYBNVQLFHXkvul2f6yOPA9PIH82RTG2cSwc= -github.com/Azure/azure-storage-blob-go v0.13.0/go.mod h1:pA9kNqtjUeQF2zOSu4s//nUdBD+e64lEuc4sVnuOfNs= +github.com/Azure/azure-storage-blob-go v0.14.0 h1:1BCg74AmVdYwO3dlKwtFU1V0wU2PZdREkXvAmZJRUlM= +github.com/Azure/azure-storage-blob-go v0.14.0/go.mod h1:SMqIBi+SuiQH32bvyjngEewEeXoPfKMgWlBDaYf6fck= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest/adal v0.9.2/go.mod h1:/3SMAM86bP6wC9Ev35peQDUeqFZBMH07vvUOmg4z/fE= +github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= github.com/Azure/go-autorest/autorest/adal v0.9.14 h1:G8hexQdV5D4khOXrWG2YuLCFKhWYmWD8bHYaXN5ophk= github.com/Azure/go-autorest/autorest/adal v0.9.14/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= @@ -57,16 +60,20 @@ github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+Z github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw= +github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/apsdehal/go-logger v0.0.0-20190515211354-1abdf898e024 h1:dfZ6RF0UxHqt7xPz0r7h00apsaa6rIrFhT6Xly55Exk= -github.com/apsdehal/go-logger v0.0.0-20190515211354-1abdf898e024/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.38.69 h1:V489lmrdkIQSfF6OAGZZ1Cavcm7eczCm2JcGvX+yHRg= -github.com/aws/aws-sdk-go v1.38.69/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= +github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= +github.com/aws/aws-sdk-go v1.40.32 h1:ok+9vnnqYWJXofYhaOtfP/bOt2reDqTA6ZAS00AO5pA= +github.com/aws/aws-sdk-go v1.40.32/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -74,21 +81,23 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -106,6 +115,7 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -158,14 +168,18 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0 h1:6DWmvNpomjL1+3liNSZbVns3zsYzzCjm6pRBO1tLeso= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -179,7 +193,6 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfC github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -189,17 +202,18 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/logrusorgru/aurora v0.0.0-20191116043053-66b7ad493a23 h1:Wp7NjqGKGN9te9N/rvXYRhlVcrulGdxnz8zadXWs7fc= -github.com/logrusorgru/aurora v0.0.0-20191116043053-66b7ad493a23/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= +github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI= github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= @@ -217,6 +231,7 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -248,7 +263,6 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= @@ -260,7 +274,6 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -313,8 +326,10 @@ golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210615190721-d04028783cf1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914 h1:3B43BWw0xEBsLZ/NO1VALz6fppU3481pik+2Ksv45z8= golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f h1:Qmd2pbz05z7z6lm0DrgQVVPuBm92jqujBKMHMOlOQEw= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -369,8 +384,10 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraCFObP8S1v6PRp0bLrtU= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -433,8 +450,8 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.4 h1:cVngSRcfgyZCzys3KYOpCFa+4dqX/Oub9tAq00ttGVs= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -463,8 +480,11 @@ google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBz google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= -google.golang.org/api v0.49.0 h1:gjIBDxlTG7vnzMmEnYwTnvLTF8Rjzo+ETCgEX1YZ/fY= google.golang.org/api v0.49.0/go.mod h1:BECiH72wsfwUvOVn3+btPD5WHi0LzavZReBndi42L18= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0 h1:ECJUVngj71QI6XEm7b1sAf8BljU5inEhMbKPR8Lxhhk= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -496,6 +516,7 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= @@ -518,8 +539,15 @@ google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxH google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= google.golang.org/genproto v0.0.0-20210624174822-c5cf32407d0a/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84 h1:R1r5J0u6Cx+RNl/6mezTw6oA14cmKC96FeUwL6A9bd4= google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71 h1:z+ErRPu0+KS02Td3fOAgdX+lnPDh/VyaABEJPD4JRQs= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -533,6 +561,7 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= @@ -540,8 +569,11 @@ google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -559,11 +591,11 @@ google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+Rur google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= From 63f711af5b28752e8edd8dd16bc7ae55e708cd93 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Mon, 30 Aug 2021 13:29:14 +0100 Subject: [PATCH 0806/1127] fix: docker build failing --- .circleci/config.yml | 2 +- Dockerfile | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 836d81dd..7d371e64 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,7 +2,7 @@ version: 2 jobs: build: docker: - - image: docker:19.03.13-git + - image: docker:20.10.8-git steps: - checkout - setup_remote_docker diff --git a/Dockerfile b/Dockerfile index 5d52b43d..60824f32 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,7 @@ ENV HELM_VERSION=$GLOBAL_HELM_VERSION ENV HELM_DIFF_VERSION=$GLOBAL_HELM_DIFF_VERSION ENV SOPS_VERSION=$GLOBAL_SOPS_VERSION -RUN apk add --update --no-cache ca-certificates git openssh ruby curl tar gzip make bash +RUN apk add --update --no-cache ca-certificates git openssh openssl ruby curl wget tar gzip make bash ADD https://github.com/mozilla/sops/releases/download/${SOPS_VERSION}/sops-${SOPS_VERSION}.linux /usr/local/bin/sops RUN chmod +x /usr/local/bin/sops @@ -37,7 +37,7 @@ RUN rm -r /tmp/helm-diff /tmp/helm-diff.tgz ### Go Builder & Tester ### FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} as builder -RUN apk add --update --no-cache ca-certificates git openssh ruby bash make +RUN apk add --update --no-cache ca-certificates git openssh ruby bash make curl RUN gem install hiera-eyaml --no-doc RUN update-ca-certificates From a713f137116c5cc19e9cbc39590fa0dafdde449e Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Mon, 30 Aug 2021 13:47:19 +0100 Subject: [PATCH 0807/1127] fix: docker build failing --- .circleci/config.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7d371e64..d825f60b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,10 +2,12 @@ version: 2 jobs: build: docker: - - image: docker:20.10.8-git + - image: docker:20.10.6-git steps: - checkout - - setup_remote_docker + - setup_remote_docker: + docker_layer_caching: true + version: 20.10.6 - run: name: build docker image command: | From cd92aac3e823d0033d266b91140b5183a26242c2 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Mon, 30 Aug 2021 13:48:35 +0100 Subject: [PATCH 0808/1127] fix: docker layer caching is not available --- .circleci/config.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d825f60b..a279049a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -6,7 +6,6 @@ jobs: steps: - checkout - setup_remote_docker: - docker_layer_caching: true version: 20.10.6 - run: name: build docker image From 0265758df4f4de42d22260bb8017a851cef78838 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Fri, 17 Sep 2021 09:26:43 +0100 Subject: [PATCH 0809/1127] feat: support the new OCI implementation from helm 3.7.0 Signed-off-by: Luis Davim --- internal/app/helm_helpers.go | 11 ++++++++++ internal/app/utils.go | 40 ++++++++++++++++++++++++++++++++---- internal/app/utils_test.go | 40 ++++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 4 deletions(-) diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index 598f906d..43af55b2 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -111,6 +111,7 @@ func updateChartDep(chartPath string) error { } // helmExportChart pulls chart and exports it to the specified destination +// this is only compatible with hlem versions lower than 3.0.7 func helmExportChart(chart, dest string) error { cmd := helmCmd([]string{"chart", "pull", chart}, "Pulling chart [ "+chart+" ] to local registry cache") if _, err := cmd.Exec(); err != nil { @@ -123,6 +124,16 @@ func helmExportChart(chart, dest string) error { return nil } +// helmPullChart pulls chart and exports it to the specified destination +// this should only be used with helm versions greater or equal to 3.7.0 +func helmPullChart(chart, dest string) error { + cmd := helmCmd([]string{"chart", "pull", chart, "-d", dest}, "Pulling chart [ "+chart+" ] to "+dest) + if _, err := cmd.Exec(); err != nil { + return err + } + return nil +} + // addHelmRepos adds repositories to Helm if they don't exist already. // Helm does not mind if a repo with the same name exists. It treats it as an update. func addHelmRepos(repos map[string]string) error { diff --git a/internal/app/utils.go b/internal/app/utils.go index 78cc3df9..da5ce729 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -15,6 +15,7 @@ import ( "strconv" "strings" "time" + "unicode/utf8" "github.com/Masterminds/semver" "github.com/Praqma/helmsman/internal/aws" @@ -198,6 +199,25 @@ func sliceContains(slice []string, s string) bool { return false } +// replaceAtIndex replaces the charecter at the given index in the string with the given rune +func replaceAtIndex(in string, r rune, i int) (string, error) { + if i < 0 || i >= utf8.RuneCountInString(in) { + return in, fmt.Errorf("index out of bounds") + } + out := []rune(in) + out[i] = r + return string(out), nil +} + +// ociRefToFilename computes the helm package filename for a given OCI ref +func ociRefToFilename(ref string) (string, error) { + var err error + fileName := filepath.Base(ref) + i := strings.LastIndex(fileName, ":") + fileName, err = replaceAtIndex(fileName, '-', i) + return fmt.Sprintf("%s.tgz", fileName), err +} + // downloadFile downloads a file from a URL, GCS, Azure or AWS buckets and saves it with a // given outfile name and in a given dir // If the file path is local file system path, it returns the absolute path to the file @@ -210,11 +230,23 @@ func downloadFile(file string, dir string, outfile string) string { switch u.Scheme { case "oci": dest := filepath.Dir(outfile) - fileName := strings.Split(filepath.Base(file), ":")[0] - if err := helmExportChart(strings.ReplaceAll(file, "oci://", ""), dest); err != nil { - log.Fatal(err.Error()) + switch { + case checkHelmVersion("<3.7.0"): + fileName := strings.Split(filepath.Base(file), ":")[0] + if err := helmExportChart(strings.ReplaceAll(file, "oci://", ""), dest); err != nil { + log.Fatal(err.Error()) + } + return filepath.Join(dest, fileName) + default: + fileName, err := ociRefToFilename(file) + if err != nil { + log.Fatal(err.Error()) + } + if err := helmPullChart(file, dest); err != nil { + log.Fatal(err.Error()) + } + return filepath.Join(dest, fileName) } - return filepath.Join(dest, fileName) case "https", "http": if err := downloadFileFromURL(file, outfile); err != nil { log.Fatal(err.Error()) diff --git a/internal/app/utils_test.go b/internal/app/utils_test.go index ae079c70..1e282016 100644 --- a/internal/app/utils_test.go +++ b/internal/app/utils_test.go @@ -5,6 +5,46 @@ import ( "testing" ) +func TestOciRefToFilename(t *testing.T) { + tests := []struct { + name string + in string + want string + }{ + { + name: "no_repo", + in: "my-chart:1.2.3", + want: "my-chart-1.2.3.tgz", + }, + { + name: "two_colons", + in: "my:chart:1.2.3", + want: "my:chart-1.2.3.tgz", + }, + { + name: "with_Host", + in: "my-repo.example.com/charts/my-chart:1.2.3", + want: "my-chart-1.2.3.tgz", + }, + { + name: "full_url", + in: "oci://my-repo.example.com/charts/my-chart:1.2.3", + want: "my-chart-1.2.3.tgz", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ociRefToFilename(tt.in) + if err != nil { + t.Errorf("ociRefToFilename() unexpected error: %v", err) + } + if got != tt.want { + t.Errorf("ociRefToFilename() got = %v, want %v", got, tt.want) + } + }) + } +} + func Test_isOfType(t *testing.T) { type args struct { filename string From 0d7111ac99df479af6b34cf2bc8678e7f278f755 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sat, 18 Sep 2021 21:12:07 +0100 Subject: [PATCH 0810/1127] Prepare release v3.7.4 --- .version | 2 +- README.md | 6 +++--- internal/app/main.go | 2 +- release-notes.md | 8 +++----- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/.version b/.version index 46c13383..51277bdb 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.7.3 +v3.7.4 diff --git a/README.md b/README.md index cc456630..a1a67d15 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.7.3&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.7.4&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -61,9 +61,9 @@ Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the d ```sh # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.3/helmsman_3.7.3_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.4/helmsman_3.7.4_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.3/helmsman_3.7.3_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.4/helmsman_3.7.4_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/internal/app/main.go b/internal/app/main.go index 3d6219f4..058f5de4 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.7.3" + appVersion = "v3.7.4" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index a5dce252..2d6f38a2 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,8 +1,6 @@ -# v3.7.3 +# v3.7.4 ## Fixes and improvements -- feat: allow setting helm diff flags (#620) -- fix: the release protection flag should be respected even when destroying (#622) -- fix: Pass error to user if we fail to correctly pull or export oci chart images (#619) -- refactor: code clean up +- feat: support the new OCI implementation from helm 3.7.0 (#627) +- chore: updated the project to use Go 1.17 From 39645425b28205cffa0c5f7503485eda675a8dc8 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 21 Sep 2021 13:59:08 +0100 Subject: [PATCH 0811/1127] fix: unknown command "chart" for "helm" Signed-off-by: Luis Davim --- internal/app/helm_helpers.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index 43af55b2..11b79dbe 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -127,7 +127,13 @@ func helmExportChart(chart, dest string) error { // helmPullChart pulls chart and exports it to the specified destination // this should only be used with helm versions greater or equal to 3.7.0 func helmPullChart(chart, dest string) error { - cmd := helmCmd([]string{"chart", "pull", chart, "-d", dest}, "Pulling chart [ "+chart+" ] to "+dest) + chartParts := strings.Split(chart, ":") + if len(chartParts) < 2 { + return fmt.Errorf("missing chart version") + } + version := chartParts[len(chartParts)-1] + chart = strings.Join(chartParts[:len(chartParts)-1], ":") + cmd := helmCmd([]string{"pull", chart, "-d", dest, "--version", version}, "Pulling chart [ "+chart+" ] to "+dest) if _, err := cmd.Exec(); err != nil { return err } From 75de5ed98a506aca3cb7ab0ce90eaa080843d328 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 21 Sep 2021 14:46:11 +0100 Subject: [PATCH 0812/1127] Prepare release v3.7.5 --- .version | 2 +- README.md | 31 ++++++++++++++++++++++--------- internal/app/main.go | 2 +- release-notes.md | 5 ++--- 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/.version b/.version index 51277bdb..0c0dd077 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.7.4 +v3.7.5 diff --git a/README.md b/README.md index a1a67d15..63f47747 100644 --- a/README.md +++ b/README.md @@ -18,19 +18,34 @@ The desired state file (DSF) follows the [desired state specification](https://g Helmsman sees what you desire, validates that your desire makes sense (e.g. that the charts you desire are available in the repos you defined), compares it with the current state of Helm and figures out what to do to make your desire come true. To plan without executing: -``` $ helmsman -f example.toml ``` + +```sh +helmsman -f example.toml +``` To plan and execute the plan: -``` $ helmsman --apply -f example.toml ``` + +```sh +helmsman --apply -f example.toml +``` To show debugging details: -``` $ helmsman --debug --apply -f example.toml ``` + +```sh +helmsman --debug --apply -f example.toml +``` To run a dry-run: -``` $ helmsman --debug --dry-run -f example.toml ``` + +```sh +helmsman --debug --dry-run -f example.toml +``` To limit execution to specific application: -``` $ helmsman --debug --dry-run --target artifactory -f example.toml ``` + +```sh +helmsman --debug --dry-run --target artifactory -f example.toml +``` # Features @@ -61,9 +76,9 @@ Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the d ```sh # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.4/helmsman_3.7.4_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.5/helmsman_3.7.5_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.4/helmsman_3.7.4_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.5/helmsman_3.7.5_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` @@ -81,9 +96,7 @@ Helmsman has been packaged in Archlinux under `helmsman-bin` for the latest bina > Documentation for Helmsman v1.x can be found at: [docs v1.x](https://github.com/Praqma/helmsman/tree/1.x/docs) - [How-Tos](https://github.com/Praqma/helmsman/blob/master/docs/how_to/). - - [Desired state specification](https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md). - - [CMD reference](https://github.com/Praqma/helmsman/blob/master/docs/cmd_reference.md) ## Usage diff --git a/internal/app/main.go b/internal/app/main.go index 058f5de4..57b2dee4 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.7.4" + appVersion = "v3.7.5" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index 2d6f38a2..a27233b9 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,6 +1,5 @@ -# v3.7.4 +# v3.7.5 ## Fixes and improvements -- feat: support the new OCI implementation from helm 3.7.0 (#627) -- chore: updated the project to use Go 1.17 +- fix: unknown command "chart" for "helm" (#628) From 75bdf3b4e8cb1b362d4edf14cc7da9464e15f1b4 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Mon, 11 Oct 2021 14:48:56 +0200 Subject: [PATCH 0813/1127] Use RetryExec(3) form helm repo add step --- internal/app/helm_helpers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index 11b79dbe..9635ba5b 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -200,7 +200,7 @@ func addHelmRepos(repos map[string]string) error { } } cmd := helmCmd(concat([]string{"repo", "add", forceUpdateFlag, repoName, repoURL}, basicAuthArgs), "Adding helm repository [ "+repoName+" ]") - if _, err := cmd.Exec(); err != nil { + if _, err := cmd.RetryExec(3); err != nil { return fmt.Errorf("while adding helm repository [%s]]: %w", repoName, err) } } From 80e26758beeca66bbf08991a30a09791b36b15a5 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Thu, 14 Oct 2021 12:30:05 +0200 Subject: [PATCH 0814/1127] Prepare release v3.7.6 --- .circleci/config.yml | 2 +- README.md | 6 +++--- internal/app/main.go | 2 +- release-notes.md | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index a279049a..b554d562 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -36,7 +36,7 @@ jobs: - run: name: build docker images and push them to dockerhub command: | - helm_versions=( "v3.4.2" "v3.5.4" "v3.6.3" ) + helm_versions=( "v3.4.2" "v3.5.4" "v3.6.3" "v3.7.1" ) TAG=$(git describe --abbrev=0 --tags) docker login -u $DOCKER_USER -p $DOCKER_PASS diff --git a/README.md b/README.md index 63f47747..35ae1b11 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.7.4&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.7.6&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -76,9 +76,9 @@ Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the d ```sh # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.5/helmsman_3.7.5_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.6/helmsman_3.7.6_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.5/helmsman_3.7.5_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.6/helmsman_3.7.6_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/internal/app/main.go b/internal/app/main.go index 57b2dee4..60bf5b23 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.7.5" + appVersion = "v3.7.6" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index a27233b9..9fe31ed8 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,5 +1,5 @@ -# v3.7.5 +# v3.7.6 ## Fixes and improvements -- fix: unknown command "chart" for "helm" (#628) +- fix: retry on issues when adding helm repository (#630) From 388302095fd4101386c2f1370550f64d30768f48 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Fri, 22 Oct 2021 12:05:47 +0200 Subject: [PATCH 0815/1127] Add namespaceLabelsAuthoritative settings option to allow removing undefined ns labels --- docs/desired_state_specification.md | 1 + internal/app/decision_maker.go | 6 +++--- internal/app/decision_maker_test.go | 4 ++-- internal/app/kube_helpers.go | 31 +++++++++++++++++++++++---- internal/app/plan.go | 4 ++-- internal/app/state.go | 33 +++++++++++++++-------------- 6 files changed, 52 insertions(+), 27 deletions(-) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 4be7b4cb..d7080652 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -112,6 +112,7 @@ The following options can be skipped if your kubectl context is already created - **slackWebhook** : a [Slack](http://slack.com) Webhook URL to receive Helmsman notifications. This can be passed directly or in an environment variable. - **msTeamsWebhook** : a [Microsoft Teams](https://www.microsoft.com/pl-pl/microsoft-teams/group-chat-software) Webhook URL to receive Helmsman notifications. This can be passed directly or in an environment variable. - **reverseDelete** : if set to `true` it will reverse the priority order whilst deleting. +- **namespaceLabelsAuthoritative** : if set to `true` it will remove all the namespace's labels that are not defined in DSL for particular namespace - **eyamlEnabled** : if set to `true` it will use [hiera-eyaml](https://github.com/voxpupuli/hiera-eyaml) to decrypt secret files instead of using default helm-secrets based on sops - **eyamlPrivateKeyPath** : if set with path to the eyaml private key file, it will use it instead of looking for default one in ./keys directory relative to where Helmsman were run. It needs to be defined in conjunction with eyamlPublicKeyPath. - **eyamlPublicKeyPath** : if set with path to the eyaml public key file, it will use it instead of looking for default one in ./keys directory relative to where Helmsman were run. It needs to be defined in conjunction with eyamlPrivateKeyPath. diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index dc1f7a84..51b14682 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -102,7 +102,7 @@ func (cs *currentState) decide(r *release, n *namespace, p *plan, c *chartInfo) if flags.destroy { if ok := cs.releaseExists(r, ""); ok { - p.addDecision("Release [ "+r.Name+" ] will be DELETED (destroy flag enabled).", r.Priority, delete) + p.addDecision("Release [ "+r.Name+" ] will be DELETED (destroy flag enabled).", r.Priority, remove) r.uninstall(p) } return @@ -110,7 +110,7 @@ func (cs *currentState) decide(r *release, n *namespace, p *plan, c *chartInfo) if !r.Enabled { if ok := cs.releaseExists(r, ""); ok { - p.addDecision("Release [ "+r.Name+" ] is desired to be DELETED.", r.Priority, delete) + p.addDecision("Release [ "+r.Name+" ] is desired to be DELETED.", r.Priority, remove) r.uninstall(p) } else { p.addDecision("Release [ "+r.Name+" ] disabled", r.Priority, noop) @@ -275,7 +275,7 @@ func (cs *currentState) cleanUntrackedReleases(s *state, p *plan) { if !tracked { toDelete++ r := cs.releases[name+"-"+ns] - p.addDecision("Untracked release [ "+r.Name+" ] found and it will be deleted", -1000, delete) + p.addDecision("Untracked release [ "+r.Name+" ] found and it will be deleted", -1000, remove) r.uninstall(p) } } diff --git a/internal/app/decision_maker_test.go b/internal/app/decision_maker_test.go index c165e360..01169293 100644 --- a/internal/app/decision_maker_test.go +++ b/internal/app/decision_maker_test.go @@ -317,8 +317,8 @@ func (dt decisionType) String() string { return "create" case change: return "change" - case delete: - return "delete" + case remove: + return "remove" case noop: return "noop" } diff --git a/internal/app/kube_helpers.go b/internal/app/kube_helpers.go index f93f1dd2..f175ea92 100644 --- a/internal/app/kube_helpers.go +++ b/internal/app/kube_helpers.go @@ -1,6 +1,7 @@ package app import ( + "encoding/json" "errors" "fmt" "io/ioutil" @@ -24,7 +25,7 @@ func addNamespaces(s *state) { go func(name string, cfg *namespace, wg *sync.WaitGroup) { defer wg.Done() createNamespace(name) - labelNamespace(name, cfg.Labels) + labelNamespace(name, cfg.Labels, s.Settings.NamespaceLabelsAuthoritative) annotateNamespace(name, cfg.Annotations) if !flags.dryRun { setLimits(name, cfg.Limits) @@ -60,12 +61,34 @@ func createNamespace(ns string) { } // labelNamespace labels a namespace with provided labels -func labelNamespace(ns string, labels map[string]string) { - if len(labels) == 0 { +func labelNamespace(ns string, labels map[string]string, authoritative bool) { + var nsLabels map[string]string + + args := []string{"label", "--overwrite", "namespace/" + ns, flags.getKubeDryRunFlag("label")} + + if authoritative { + cmdGetLabels := kubectl([]string{"get", "namespace", ns, "-o", "jsonpath='{.metadata.labels}'"}, "Getting namespace [ "+ns+" ] current labels") + res, err := cmdGetLabels.Exec() + if err != nil { + log.Error(fmt.Sprintf("Could not get namespace [ %s ] labels. Error message: %v", ns, err)) + } + if err := json.Unmarshal([]byte(strings.Trim(res.output, `'`)), &nsLabels); err != nil { + log.Fatal(fmt.Sprintf("failed to unmarshal kubectl get namespace labels output: %s, ended with error: %s", res.output, err)) + } + // ignore default k8s namespace label from being removed + delete(nsLabels, "kubernetes.io/metadata.name") + // ignore every label defined in DSF for the namespace from being removed + for definedLabelKey, _ := range labels { + delete(nsLabels, definedLabelKey) + } + for label, _ := range nsLabels { + args = append(args, label+"-") + } + } + if len(labels) == 0 && len(nsLabels) == 0 { return } - args := []string{"label", "--overwrite", "namespace/" + ns, flags.getKubeDryRunFlag("label")} for k, v := range labels { args = append(args, k+"="+v) } diff --git a/internal/app/plan.go b/internal/app/plan.go index 077709d1..1c8e6757 100644 --- a/internal/app/plan.go +++ b/internal/app/plan.go @@ -16,7 +16,7 @@ type decisionType int const ( create decisionType = iota + 1 change - delete + remove noop ignored ) @@ -219,7 +219,7 @@ func (p *plan) print() { for _, decision := range p.Decisions { if decision.Type == ignored || decision.Type == noop { log.Info(decision.Description + " -- priority: " + strconv.Itoa(decision.Priority)) - } else if decision.Type == delete { + } else if decision.Type == remove { log.Warning(decision.Description + " -- priority: " + strconv.Itoa(decision.Priority)) } else { log.Notice(decision.Description + " -- priority: " + strconv.Itoa(decision.Priority)) diff --git a/internal/app/state.go b/internal/app/state.go index 29a06869..4440d93b 100644 --- a/internal/app/state.go +++ b/internal/app/state.go @@ -12,22 +12,23 @@ import ( // config type represents the settings fields type config struct { - KubeContext string `yaml:"kubeContext"` - Username string `yaml:"username"` - Password string `yaml:"password"` - ClusterURI string `yaml:"clusterURI"` - ServiceAccount string `yaml:"serviceAccount"` - StorageBackend string `yaml:"storageBackend"` - SlackWebhook string `yaml:"slackWebhook"` - MSTeamsWebhook string `yaml:"msTeamsWebhook"` - ReverseDelete bool `yaml:"reverseDelete"` - BearerToken bool `yaml:"bearerToken"` - BearerTokenPath string `yaml:"bearerTokenPath"` - EyamlEnabled bool `yaml:"eyamlEnabled"` - EyamlPrivateKeyPath string `yaml:"eyamlPrivateKeyPath"` - EyamlPublicKeyPath string `yaml:"eyamlPublicKeyPath"` - GlobalHooks map[string]interface{} `yaml:"globalHooks"` - GlobalMaxHistory int `yaml:"globalMaxHistory"` + KubeContext string `yaml:"kubeContext"` + Username string `yaml:"username"` + Password string `yaml:"password"` + ClusterURI string `yaml:"clusterURI"` + ServiceAccount string `yaml:"serviceAccount"` + StorageBackend string `yaml:"storageBackend"` + SlackWebhook string `yaml:"slackWebhook"` + MSTeamsWebhook string `yaml:"msTeamsWebhook"` + ReverseDelete bool `yaml:"reverseDelete"` + BearerToken bool `yaml:"bearerToken"` + BearerTokenPath string `yaml:"bearerTokenPath"` + NamespaceLabelsAuthoritative bool `yaml:"namespaceLabelsAuthoritative"` + EyamlEnabled bool `yaml:"eyamlEnabled"` + EyamlPrivateKeyPath string `yaml:"eyamlPrivateKeyPath"` + EyamlPublicKeyPath string `yaml:"eyamlPublicKeyPath"` + GlobalHooks map[string]interface{} `yaml:"globalHooks"` + GlobalMaxHistory int `yaml:"globalMaxHistory"` } // state type represents the desired state of applications on a k8s cluster. From 5ce3ccd8a4c0abb280eb5d3de21338e0ff1d2719 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Mon, 25 Oct 2021 13:02:46 +0100 Subject: [PATCH 0816/1127] Preparing release v3.7.7 --- .version | 2 +- README.md | 6 +++--- internal/app/main.go | 2 +- release-notes.md | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.version b/.version index 0c0dd077..b592e5e4 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.7.5 +v3.7.7 diff --git a/README.md b/README.md index 35ae1b11..6d246083 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.7.6&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.7.7&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -76,9 +76,9 @@ Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the d ```sh # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.6/helmsman_3.7.6_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.7/helmsman_3.7.7_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.6/helmsman_3.7.6_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.7/helmsman_3.7.7_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/internal/app/main.go b/internal/app/main.go index 60bf5b23..5bac94b6 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.7.6" + appVersion = "v3.7.7" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index 9fe31ed8..0504ef78 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,5 +1,5 @@ -# v3.7.6 +# v3.7.7 ## Fixes and improvements -- fix: retry on issues when adding helm repository (#630) +- feat: Add namespaceLabelsAuthoritative settings option to allow removing undefined ns labels (#631) From eaacbf7a9521fad7cbb62d194a7ae55bf2c6a590 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 16 Nov 2021 21:07:29 +0000 Subject: [PATCH 0817/1127] docs: mention brew and asdf as installation options --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 6d246083..48a916db 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,19 @@ Check the images on [dockerhub](https://hub.docker.com/r/praqma/helmsman/tags/) Helmsman has been packaged in Archlinux under `helmsman-bin` for the latest binary release, and `helmsman-git` for master. +You can also install Helmsman using [Homebrew](https://brew.sh) + +```sh +brew install helmsman +``` + +## As an [asdf-vm](https://asdf-vm.com/) plugin + +```sh +asdf plugin-add helmsman +asdf install helmsman latest +``` + # Documentation > Documentation for Helmsman v1.x can be found at: [docs v1.x](https://github.com/Praqma/helmsman/tree/1.x/docs) From 258c1649623c84d4f70b44d0ea570420e3b6eddd Mon Sep 17 00:00:00 2001 From: adamarnold-msm Date: Mon, 1 Nov 2021 10:45:53 +0000 Subject: [PATCH 0818/1127] Add -spec flag to support specification file consisting of multiple desired state files state files, add priority override --- docs/cmd_reference.md | 3 ++ examples/example-spec.yaml | 6 ++++ internal/app/cli.go | 62 +++++++++++++++++++++++++++++---- internal/app/cli_test.go | 10 +++--- internal/app/spec_state.go | 52 +++++++++++++++++++++++++++ internal/app/spec_state_test.go | 50 ++++++++++++++++++++++++++ tests/Invalid_example_spec.yaml | 4 +++ 7 files changed, 175 insertions(+), 12 deletions(-) create mode 100644 examples/example-spec.yaml create mode 100644 internal/app/spec_state.go create mode 100644 internal/app/spec_state_test.go create mode 100644 tests/Invalid_example_spec.yaml diff --git a/docs/cmd_reference.md b/docs/cmd_reference.md index 6660398c..e12a7e09 100644 --- a/docs/cmd_reference.md +++ b/docs/cmd_reference.md @@ -77,6 +77,9 @@ This lists available CMD options in Helmsman: `--no-ssm-subst` turn off SSM parameter substitution globally. + `-spec string` + specification file name, contains locations of desired state files to be merged + `--subst-ssm-values` turn on SSM parameter substitution in values files. diff --git a/examples/example-spec.yaml b/examples/example-spec.yaml new file mode 100644 index 00000000..d4590476 --- /dev/null +++ b/examples/example-spec.yaml @@ -0,0 +1,6 @@ +--- +stateFiles: + - path: examples/example.yaml + priority: -10 + - path: examples/minimal-example.yaml + - path: examples/minimal-example.toml diff --git a/internal/app/cli.go b/internal/app/cli.go index eb7cff84..a455ce45 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -24,6 +24,29 @@ const ( // Allow parsing of multiple string command line options into an array of strings type stringArray []string +type fileOptionArray []fileOption + +type fileOption struct { + name string + priority int +} + +func (f *fileOptionArray) String() string { + var a []string + for _, v := range *f { + a = append(a, v.name) + } + return strings.Join(a, " ") +} + +func (f *fileOptionArray) Set(value string) error { + var fo fileOption + + fo.name = value + *f = append(*f, fo) + return nil +} + func (i *stringArray) String() string { return strings.Join(*i, " ") } @@ -35,7 +58,8 @@ func (i *stringArray) Set(value string) error { type cli struct { debug bool - files stringArray + files fileOptionArray + spec string envFiles stringArray target stringArray group stringArray @@ -88,6 +112,7 @@ func (c *cli) parse() { flag.Var(&c.group, "group", "limit execution to specific group of apps.") flag.IntVar(&c.diffContext, "diff-context", -1, "number of lines of context to show around changes in helm diff output") flag.IntVar(&c.parallel, "p", 1, "max number of concurrent helm releases to run") + flag.StringVar(&c.spec, "spec", "", "specification file name, contains locations of desired state files to be merged") flag.StringVar(&c.kubeconfig, "kubeconfig", "", "path to the kubeconfig file to use for CLI requests") flag.StringVar(&c.nsOverride, "ns-override", "", "override defined namespaces with this one") flag.StringVar(&c.contextOverride, "context-override", "", "override releases context defined in release state with this one") @@ -147,6 +172,10 @@ func (c *cli) parse() { log.Fatal("--target and --group can't be used together.") } + if len(flags.files) > 0 && len(flags.spec) > 0 { + log.Fatal("-f and -spec can't be used together.") + } + if c.parallel < 1 { c.parallel = 1 } @@ -159,7 +188,7 @@ func (c *cli) parse() { kubectlVersion := getKubectlVersion() log.Verbose("kubectl client version: " + kubectlVersion) - if len(c.files) == 0 { + if len(c.files) == 0 && len(c.spec) == 0 { log.Info("No desired state files provided.") os.Exit(0) } @@ -218,32 +247,51 @@ func (c *cli) readState(s *state) error { os.RemoveAll(tempFilesDir) _ = os.MkdirAll(tempFilesDir, 0o755) + if len(c.spec) > 0 { + + sp := new(StateFiles) + sp.specFromYAML(c.spec) + + for _, val := range sp.StateFiles { + fo := fileOption{} + fo.name = val.Path + fo.priority = val.Priority + checkSpecValid(fo.name) + c.files = append(c.files, fo) + } + } + // read the TOML/YAML desired state file for _, f := range c.files { var fileState state - if err := fileState.fromFile(f); err != nil { + if err := fileState.fromFile(f.name); err != nil { return err } - log.Infof("Parsed [[ %s ]] successfully and found [ %d ] apps", f, len(fileState.Apps)) + + if f.priority != 0 { + fileState.patchPriority(f.priority) + } + + log.Infof("Parsed [[ %s ]] successfully and found [ %d ] apps", f.name, len(fileState.Apps)) // Merge Apps that already existed in the state for appName, app := range fileState.Apps { if _, ok := s.Apps[appName]; ok { if err := mergo.Merge(s.Apps[appName], app, mergo.WithAppendSlice, mergo.WithOverride); err != nil { - return fmt.Errorf("failed to merge %s from desired state file %s: %w", appName, f, err) + return fmt.Errorf("failed to merge %s from desired state file %s: %w", appName, f.name, err) } } } // Merge the remaining Apps if err := mergo.Merge(&s.Apps, &fileState.Apps); err != nil { - return fmt.Errorf("failed to merge desired state file %s: %w", f, err) + return fmt.Errorf("failed to merge desired state file %s: %w", f.name, err) } // All the apps are already merged, make fileState.Apps empty to avoid conflicts in the final merge fileState.Apps = make(map[string]*release) if err := mergo.Merge(s, &fileState, mergo.WithAppendSlice, mergo.WithOverride); err != nil { - return fmt.Errorf("failed to merge desired state file %s: %w", f, err) + return fmt.Errorf("failed to merge desired state file %s: %w", f.name, err) } } diff --git a/internal/app/cli_test.go b/internal/app/cli_test.go index 79680b1f..9b34399f 100644 --- a/internal/app/cli_test.go +++ b/internal/app/cli_test.go @@ -22,7 +22,7 @@ func Test_readState(t *testing.T) { { name: "yaml minimal example; no validation", flags: cli{ - files: stringArray([]string{"../../examples/minimal-example.yaml"}), + files: fileOptionArray([]fileOption{fileOption{"../../examples/minimal-example.yaml", 0}}), skipValidation: true, }, want: result{ @@ -35,7 +35,7 @@ func Test_readState(t *testing.T) { { name: "toml minimal example; no validation", flags: cli{ - files: stringArray([]string{"../../examples/minimal-example.toml"}), + files: fileOptionArray([]fileOption{fileOption{"../../examples/minimal-example.toml", 0}}), skipValidation: true, }, want: result{ @@ -49,7 +49,7 @@ func Test_readState(t *testing.T) { name: "yaml minimal example; no validation with bad target", flags: cli{ target: stringArray([]string{"foo"}), - files: stringArray([]string{"../../examples/minimal-example.yaml"}), + files: fileOptionArray([]fileOption{{"../../examples/minimal-example.yaml", 0}}), skipValidation: true, }, want: result{ @@ -63,7 +63,7 @@ func Test_readState(t *testing.T) { name: "yaml minimal example; no validation; target jenkins", flags: cli{ target: stringArray([]string{"jenkins"}), - files: stringArray([]string{"../../examples/minimal-example.yaml"}), + files: fileOptionArray([]fileOption{{"../../examples/minimal-example.yaml", 0}}), skipValidation: true, }, want: result{ @@ -76,7 +76,7 @@ func Test_readState(t *testing.T) { { name: "yaml and toml minimal examples merged; no validation", flags: cli{ - files: stringArray([]string{"../../examples/minimal-example.yaml", "../../examples/minimal-example.toml"}), + files: fileOptionArray([]fileOption{{"../../examples/minimal-example.yaml", 0}, {"../../examples/minimal-example.toml", 0}}), skipValidation: true, }, want: result{ diff --git a/internal/app/spec_state.go b/internal/app/spec_state.go new file mode 100644 index 00000000..d926c296 --- /dev/null +++ b/internal/app/spec_state.go @@ -0,0 +1,52 @@ +package app + +import ( + "io/ioutil" + + "gopkg.in/yaml.v2" +) + +type SpecConfig struct { + Path string `yaml:"path"` + Priority int `yaml:"priority"` +} + +type StateFiles struct { + StateFiles []SpecConfig `yaml:"stateFiles"` +} + +// fromYAML reads a yaml file and decodes it to a state type. +// parser which throws an error if the YAML file is not valid. +func (pc *StateFiles) specFromYAML(file string) error { + rawYamlFile, err := ioutil.ReadFile(file) + if err != nil { + log.Errorf("specFromYaml %v %v", file, err) + return err + } + + yamlFile := string(rawYamlFile) + + if err = yaml.UnmarshalStrict([]byte(yamlFile), pc); err != nil { + return err + } + + return nil +} + +func checkSpecValid(f string) error { + if err := isValidFile(f, []string{"yaml", "yml"}); err != nil { + // exit + return err + } + + return nil +} + +func (s *state) patchPriority(priority int) error { + for app, _ := range s.Apps { + if s.Apps[app].Priority > priority { + s.Apps[app].Priority = priority + } + } + return nil +} diff --git a/internal/app/spec_state_test.go b/internal/app/spec_state_test.go new file mode 100644 index 00000000..21a2df1a --- /dev/null +++ b/internal/app/spec_state_test.go @@ -0,0 +1,50 @@ +package app + +import ( + "testing" +) + +func Test_specFromYAML(t *testing.T) { + type args struct { + file string + s *StateFiles + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "test case 1 -- Valid YAML", + args: args{ + file: "../../examples/example-spec.yaml", + s: new(StateFiles), + }, + want: true, + }, { + name: "test case 2 -- Invalid Yaml", + args: args{ + file: "../../tests/Invalid_example_spec.yaml", + s: new(StateFiles), + }, + want: false, + }, + } + + teardownTestCase := setupStateFileTestCase(t) + defer teardownTestCase(t) + for _, tt := range tests { + // os.Args = append(os.Args, "-f ../../examples/example.yaml") + t.Run(tt.name, func(t *testing.T) { + err := tt.args.s.specFromYAML(tt.args.file) + if err != nil { + t.Log(err) + } + + got := err == nil + if got != tt.want { + t.Errorf("specFromYaml() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/tests/Invalid_example_spec.yaml b/tests/Invalid_example_spec.yaml new file mode 100644 index 00000000..19ccd2c1 --- /dev/null +++ b/tests/Invalid_example_spec.yaml @@ -0,0 +1,4 @@ +--- +stateFiles: + name1: invalid/example.yaml + name2: invalid/minimal-example.yaml From c120aa083b139431e4d1d3fcad791fc9b6b6337a Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Fri, 3 Dec 2021 09:57:57 +0100 Subject: [PATCH 0819/1127] Add skipIgnoredApps option to DSF's Settings block --- docs/desired_state_specification.md | 1 + internal/app/decision_maker.go | 8 +-- internal/app/decision_maker_test.go | 83 ++++++++++++++++++++++++++++- internal/app/state.go | 1 + 4 files changed, 89 insertions(+), 4 deletions(-) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index d7080652..19ca034b 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -118,6 +118,7 @@ The following options can be skipped if your kubectl context is already created - **eyamlPublicKeyPath** : if set with path to the eyaml public key file, it will use it instead of looking for default one in ./keys directory relative to where Helmsman were run. It needs to be defined in conjunction with eyamlPrivateKeyPath. - **globalHooks** : defines global lifecycle hooks to apply yaml manifest before and/or after different helmsman operations. Check [here](how_to/apps/lifecycle_hooks.md) for more details. - **globalMaxHistory** : defines the **global** maximum number of helm revisions state (secrets/configmap) to keep. Releases can override this global value by setting `maxHistory`. If both are not set or are set to `0`, it is defaulted to 10. +- **skipIgnoredApps** : if set to true apps, that would normally be listed in the plan as `ignored`, will be skipped. They won't show up on the plan output and won't be considered in decisions. This is especially useful when using `-target` or `-group` flags with significant amount of apps where most of them show up as `ignored` in the plan output making it hard to read. Example: diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index 51b14682..76133846 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -77,7 +77,7 @@ func (cs *currentState) makePlan(s *state) *plan { <-sem }() r.checkChartDepUpdate() - cs.decide(r, s.Namespaces[r.Namespace], p, c) + cs.decide(r, s.Namespaces[r.Namespace], p, c, s.Settings) }(r, s.ChartInfo[r.Chart][r.Version]) } wg.Wait() @@ -87,10 +87,12 @@ func (cs *currentState) makePlan(s *state) *plan { // decide makes a decision about what commands (actions) need to be executed // to make a release section of the desired state come true. -func (cs *currentState) decide(r *release, n *namespace, p *plan, c *chartInfo) { +func (cs *currentState) decide(r *release, n *namespace, p *plan, c *chartInfo, settings config) { // check for presence in defined targets or groups if !r.isConsideredToRun() { - p.addDecision("Release [ "+r.Name+" ] ignored", r.Priority, ignored) + if !settings.SkipIgnoredApps { + p.addDecision("Release [ "+r.Name+" ] ignored", r.Priority, ignored) + } return } diff --git a/internal/app/decision_maker_test.go b/internal/app/decision_maker_test.go index 01169293..3c9ccb7d 100644 --- a/internal/app/decision_maker_test.go +++ b/internal/app/decision_maker_test.go @@ -226,9 +226,10 @@ func Test_decide(t *testing.T) { t.Run(tt.name, func(t *testing.T) { cs := newCurrentState() tt.args.s.disableUntargetedApps([]string{}, tt.targetFlag) + settings := config{} outcome := plan{} // Act - cs.decide(tt.args.s.Apps[tt.args.r], tt.args.s.Namespaces[tt.args.s.Apps[tt.args.r].Namespace], &outcome, &chartInfo{}) + cs.decide(tt.args.s.Apps[tt.args.r], tt.args.s.Namespaces[tt.args.s.Apps[tt.args.r].Namespace], &outcome, &chartInfo{}, settings) got := outcome.Decisions[0].Type t.Log(outcome.Decisions[0].Description) @@ -240,6 +241,86 @@ func Test_decide(t *testing.T) { } } +func Test_decide_skip_ignored_apps(t *testing.T) { + type args struct { + rs []string + s *state + } + tests := []struct { + name string + targetFlag []string + args args + want int + }{ + { + name: "decide() - skipIgnoredApps is defined and target flag contains 2 app names - plan should have 2 decisions", + targetFlag: []string{"service1", "service2"}, + args: args{ + rs: []string{"service1", "service2"}, + s: &state{ + Apps: map[string]*release{ + "service1": { + Name: "service1", + Namespace: "namespace", + Enabled: true, + }, + "service2": { + Name: "service2", + Namespace: "namespace", + Enabled: true, + }, + }, + }, + }, + want: 2, + }, + { + name: "decide() - skipIgnoredApps is defined and target flag contains just 1 app name - plan should have 1 decision - one app should be ignored", + targetFlag: []string{"service1"}, + args: args{ + rs: []string{"service1", "service2"}, + s: &state{ + Apps: map[string]*release{ + "service1": { + Name: "service1", + Namespace: "namespace", + Enabled: true, + }, + "service2": { + Name: "service2", + Namespace: "namespace", + Enabled: true, + }, + }, + }, + }, + want: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cs := newCurrentState() + tt.args.s.disableUntargetedApps([]string{}, tt.targetFlag) + settings := config{ + SkipIgnoredApps: true, + } + outcome := plan{} + // Act + for _, r := range tt.args.rs { + cs.decide(tt.args.s.Apps[r], tt.args.s.Namespaces[tt.args.s.Apps[r].Namespace], &outcome, &chartInfo{}, settings) + } + got := outcome.Decisions + t.Log(outcome.Decisions) + + // Assert + if len(got) != tt.want { + t.Errorf("decide() = %d, want %d", len(got), tt.want) + } + }) + } +} + func Test_decide_group(t *testing.T) { type args struct { s *state diff --git a/internal/app/state.go b/internal/app/state.go index 4440d93b..6cd02fc7 100644 --- a/internal/app/state.go +++ b/internal/app/state.go @@ -29,6 +29,7 @@ type config struct { EyamlPublicKeyPath string `yaml:"eyamlPublicKeyPath"` GlobalHooks map[string]interface{} `yaml:"globalHooks"` GlobalMaxHistory int `yaml:"globalMaxHistory"` + SkipIgnoredApps bool `yaml:"skipIgnoredApps"` } // state type represents the desired state of applications on a k8s cluster. From 0c733195c887372194f2e8a027470840cad3ad86 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Mon, 6 Dec 2021 12:52:18 +0100 Subject: [PATCH 0820/1127] Add ordering of files in -spec file before patching the priorities of apps --- internal/app/cli.go | 14 ++++++++++- internal/app/spec_state.go | 9 -------- internal/app/spec_state_test.go | 41 +++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 10 deletions(-) diff --git a/internal/app/cli.go b/internal/app/cli.go index a455ce45..7034b52e 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -4,6 +4,7 @@ import ( "flag" "fmt" "os" + "sort" "strings" "github.com/imdario/mergo" @@ -47,6 +48,14 @@ func (f *fileOptionArray) Set(value string) error { return nil } +func (f fileOptionArray) sort() { + log.Verbose("Sorting files listed in the -spec file based on their priorities... ") + + sort.SliceStable(f, func(i, j int) bool { + return (f)[i].priority < (f)[j].priority + }) +} + func (i *stringArray) String() string { return strings.Join(*i, " ") } @@ -256,9 +265,12 @@ func (c *cli) readState(s *state) error { fo := fileOption{} fo.name = val.Path fo.priority = val.Priority - checkSpecValid(fo.name) + if err := isValidFile(fo.name, validManifestFiles); err != nil { + return fmt.Errorf("invalid -spec file: %w", err) + } c.files = append(c.files, fo) } + c.files.sort() } // read the TOML/YAML desired state file diff --git a/internal/app/spec_state.go b/internal/app/spec_state.go index d926c296..d7dee36a 100644 --- a/internal/app/spec_state.go +++ b/internal/app/spec_state.go @@ -33,15 +33,6 @@ func (pc *StateFiles) specFromYAML(file string) error { return nil } -func checkSpecValid(f string) error { - if err := isValidFile(f, []string{"yaml", "yml"}); err != nil { - // exit - return err - } - - return nil -} - func (s *state) patchPriority(priority int) error { for app, _ := range s.Apps { if s.Apps[app].Priority > priority { diff --git a/internal/app/spec_state_test.go b/internal/app/spec_state_test.go index 21a2df1a..e3b5f66c 100644 --- a/internal/app/spec_state_test.go +++ b/internal/app/spec_state_test.go @@ -48,3 +48,44 @@ func Test_specFromYAML(t *testing.T) { }) } } + +func Test_specFileSort(t *testing.T) { + type args struct { + files fileOptionArray + } + tests := []struct { + name string + args args + want [3]int + }{ + { + name: "test case 1 -- Files sorted by priority", + args: args{ + files: fileOptionArray( + []fileOption{ + fileOption{"third.yaml", 0}, + fileOption{"first.yaml", -20}, + fileOption{"second.yaml", -10}, + }), + }, + want: [3]int{-20, -10, 0}, + }, + } + + teardownTestCase := setupStateFileTestCase(t) + defer teardownTestCase(t) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.args.files.sort() + + got := [3]int{} + for i, f := range tt.args.files { + got[i] = f.priority + } + if got != tt.want { + t.Errorf("files from spec file are not sorted by priority = %v, want %v", got, tt.want) + } + }) + } +} + From 50d11a9999ecab6189483ab3abb0d90c49f82ccd Mon Sep 17 00:00:00 2001 From: adamarnold-msm Date: Wed, 8 Dec 2021 19:24:45 +0000 Subject: [PATCH 0821/1127] Remove patchPriority as now sorting on priority --- examples/example-spec.yaml | 1 - internal/app/cli.go | 5 ----- internal/app/spec_state.go | 16 +++------------- internal/app/spec_state_test.go | 3 +-- 4 files changed, 4 insertions(+), 21 deletions(-) diff --git a/examples/example-spec.yaml b/examples/example-spec.yaml index d4590476..14757ed1 100644 --- a/examples/example-spec.yaml +++ b/examples/example-spec.yaml @@ -1,6 +1,5 @@ --- stateFiles: - path: examples/example.yaml - priority: -10 - path: examples/minimal-example.yaml - path: examples/minimal-example.toml diff --git a/internal/app/cli.go b/internal/app/cli.go index 7034b52e..99a3f09c 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -264,7 +264,6 @@ func (c *cli) readState(s *state) error { for _, val := range sp.StateFiles { fo := fileOption{} fo.name = val.Path - fo.priority = val.Priority if err := isValidFile(fo.name, validManifestFiles); err != nil { return fmt.Errorf("invalid -spec file: %w", err) } @@ -281,10 +280,6 @@ func (c *cli) readState(s *state) error { return err } - if f.priority != 0 { - fileState.patchPriority(f.priority) - } - log.Infof("Parsed [[ %s ]] successfully and found [ %d ] apps", f.name, len(fileState.Apps)) // Merge Apps that already existed in the state for appName, app := range fileState.Apps { diff --git a/internal/app/spec_state.go b/internal/app/spec_state.go index d7dee36a..3a7b5144 100644 --- a/internal/app/spec_state.go +++ b/internal/app/spec_state.go @@ -6,13 +6,12 @@ import ( "gopkg.in/yaml.v2" ) -type SpecConfig struct { - Path string `yaml:"path"` - Priority int `yaml:"priority"` +type StatePath struct { + Path string `yaml:"path"` } type StateFiles struct { - StateFiles []SpecConfig `yaml:"stateFiles"` + StateFiles []StatePath `yaml:"stateFiles"` } // fromYAML reads a yaml file and decodes it to a state type. @@ -32,12 +31,3 @@ func (pc *StateFiles) specFromYAML(file string) error { return nil } - -func (s *state) patchPriority(priority int) error { - for app, _ := range s.Apps { - if s.Apps[app].Priority > priority { - s.Apps[app].Priority = priority - } - } - return nil -} diff --git a/internal/app/spec_state_test.go b/internal/app/spec_state_test.go index e3b5f66c..dc209414 100644 --- a/internal/app/spec_state_test.go +++ b/internal/app/spec_state_test.go @@ -51,7 +51,7 @@ func Test_specFromYAML(t *testing.T) { func Test_specFileSort(t *testing.T) { type args struct { - files fileOptionArray + files fileOptionArray } tests := []struct { name string @@ -88,4 +88,3 @@ func Test_specFileSort(t *testing.T) { }) } } - From c19b36b42315acdc478e569f645a88e463229a66 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Wed, 15 Dec 2021 12:26:45 +0100 Subject: [PATCH 0822/1127] Release v3.8.0 --- .circleci/config.yml | 2 +- .version | 2 +- README.md | 2 +- docs/cmd_reference.md | 2 +- docs/how_to/README.md | 1 + ...tiple_desired_state_files_specification.md | 42 +++++++++++++++++++ internal/app/main.go | 2 +- release-notes.md | 5 ++- 8 files changed, 51 insertions(+), 7 deletions(-) create mode 100644 docs/how_to/misc/multiple_desired_state_files_specification.md diff --git a/.circleci/config.yml b/.circleci/config.yml index b554d562..b69c2ab8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -36,7 +36,7 @@ jobs: - run: name: build docker images and push them to dockerhub command: | - helm_versions=( "v3.4.2" "v3.5.4" "v3.6.3" "v3.7.1" ) + helm_versions=( "v3.5.4" "v3.6.3" "v3.7.2" ) TAG=$(git describe --abbrev=0 --tags) docker login -u $DOCKER_USER -p $DOCKER_PASS diff --git a/.version b/.version index b592e5e4..40c06ccb 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.7.7 +v3.8.0 diff --git a/README.md b/README.md index 48a916db..1a09de3f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.7.7&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.8.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) diff --git a/docs/cmd_reference.md b/docs/cmd_reference.md index e12a7e09..9dc20145 100644 --- a/docs/cmd_reference.md +++ b/docs/cmd_reference.md @@ -77,7 +77,7 @@ This lists available CMD options in Helmsman: `--no-ssm-subst` turn off SSM parameter substitution globally. - `-spec string` + `--spec string` specification file name, contains locations of desired state files to be merged `--subst-ssm-values` diff --git a/docs/how_to/README.md b/docs/how_to/README.md index 94a61370..ffa85185 100644 --- a/docs/how_to/README.md +++ b/docs/how_to/README.md @@ -50,6 +50,7 @@ It is recommended that you also check the [DSF spec](../desired_state_specificat - [Protecting namespaces and releases](misc/protect_namespaces_and_releases.md) - [Send slack notifications from Helmsman](misc/send_slack_notifications_from_helmsman.md) - [Send MS Teams notifications from Helmsman](misc/send_ms_teams_notifications_from_helmsman.md) + - [Use multiple desired state files with Specification file (--spec flag)](misc/multiple_desired_state_files_specification.md) - [Merge multiple desired state files](misc/merge_desired_state_files.md) - [Limit Helmsman deployment to specific apps](misc/limit-deployment-to-specific-apps.md) - [Limit Helmsman deployment to specific group of apps](misc/limit-deployment-to-specific-group-of-apps.md) diff --git a/docs/how_to/misc/multiple_desired_state_files_specification.md b/docs/how_to/misc/multiple_desired_state_files_specification.md new file mode 100644 index 00000000..0de9c740 --- /dev/null +++ b/docs/how_to/misc/multiple_desired_state_files_specification.md @@ -0,0 +1,42 @@ +--- +version: v3.8.0 +--- + +# Specification file + +Starting from v3.8.0, Helmsman allows you to use Specification file passed with `--spec ` flag +in order to define multiple Desired State Files to be merged in particular order and with specific priorities. + +An example Specification file: + +`spec.yaml`: + +``` +--- +stateFiles: + - path: examples/example.yaml + - path: examples/minimal-example.yaml + priority: -10 + - path: examples/minimal-example.toml + priority: -20 + +``` + +This file can be then run with: +```shell +helmsman --spec spec.yaml ... +``` + +What it does is it takes the files from `stateFiles` list and orders them based on their priorities same way it does with the apps in DSF file. +In an example above the result order would be: +``` + - path: examples/minimal-example.toml + - path: examples/minimal-example.yaml + - path: examples/example.yaml +``` +with priorities being `-20, -10, 0` after ordering. + +Once ordering is done, Helmsman will read each file one by one and merge the previous states with the current file it goes through. + +One can take advantage of that and define the state of the environment starting with more general definitions and then reaching more specific cases in the end, +which would overwrite or extend things from previous files. diff --git a/internal/app/main.go b/internal/app/main.go index 5bac94b6..3bf88f83 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.7.7" + appVersion = "v3.8.0" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index 0504ef78..9533176d 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,5 +1,6 @@ -# v3.7.7 +# v3.8.0 ## Fixes and improvements -- feat: Add namespaceLabelsAuthoritative settings option to allow removing undefined ns labels (#631) +- feat: Add skipIgnoredApps settings option to allow ignoring apps not considered when using `-target` or `-group` flag (#634) +- feat: Add Specification file support with `-spec` flag in order to define an environment with multiple Desired State Files (#633) From 10e91b4891c11f9021d3bfcadf994c4ae8cd4beb Mon Sep 17 00:00:00 2001 From: Rasooll Date: Fri, 31 Dec 2021 21:00:31 +0330 Subject: [PATCH 0823/1127] A typo fixed. --- docs/cmd_reference.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cmd_reference.md b/docs/cmd_reference.md index 9dc20145..1ff80437 100644 --- a/docs/cmd_reference.md +++ b/docs/cmd_reference.md @@ -48,7 +48,7 @@ This lists available CMD options in Helmsman: keep releases that are managed by Helmsman from the used DSFs in the command, and are no longer tracked in your desired state. `--kubeconfig` - path to the kubeconfig file to use for CLI requests. Defalts to false if the helm diff plugin is installed. + path to the kubeconfig file to use for CLI requests. Defaults to false if the helm diff plugin is installed. `--kubectl-diff` Use kubectl diff instead of helm diff From e2f68073180deec70523e7518c54750b92d93679 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 11 Jan 2022 12:54:07 +0000 Subject: [PATCH 0824/1127] fix: kubectl diff may return 1 on success --- internal/app/command.go | 21 ++++++++++++++++----- internal/app/release.go | 8 ++++++-- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/internal/app/command.go b/internal/app/command.go index 76a09d7a..e8b660eb 100644 --- a/internal/app/command.go +++ b/internal/app/command.go @@ -67,6 +67,10 @@ func (c *Command) String() string { // RetryExec runs exec command with retry func (c *Command) RetryExec(attempts int) (ExitStatus, error) { + return c.RetryExecWithThreshold(attempts, 0) +} + +func (c *Command) RetryExecWithThreshold(attempts, exitCodeThreshold int) (ExitStatus, error) { var ( result ExitStatus err error @@ -74,7 +78,7 @@ func (c *Command) RetryExec(attempts int) (ExitStatus, error) { for i := 0; i < attempts; i++ { result, err = c.Exec() - if err == nil { + if err == nil || (result.code >= 0 && result.code <= exitCodeThreshold) { return result, nil } if i < (attempts - 1) { @@ -123,7 +127,7 @@ func (c *Command) Exec() (ExitStatus, error) { errors: strings.TrimSpace(stderr.String()), } if err != nil { - res.code = 1 + res.code = 126 if exiterr, ok := err.(*exec.ExitError); ok { res.code = exiterr.ExitCode() } @@ -164,7 +168,7 @@ func (p CmdPipe) Exec() (ExitStatus, error) { errors: strings.TrimSpace(stderr.String()), } if err != nil { - res.code = 1 + res.code = 126 if exiterr, ok := err.(*exec.ExitError); ok { res.code = exiterr.ExitCode() } @@ -175,6 +179,10 @@ func (p CmdPipe) Exec() (ExitStatus, error) { // RetryExec runs piped commands with retry func (p CmdPipe) RetryExec(attempts int) (ExitStatus, error) { + return p.RetryExecWithThreshold(attempts, 0) +} + +func (p CmdPipe) RetryExecWithThreshold(attempts, exitCodeThreshold int) (ExitStatus, error) { var ( result ExitStatus err error @@ -183,7 +191,7 @@ func (p CmdPipe) RetryExec(attempts int) (ExitStatus, error) { l := len(p) - 1 for i := 0; i < attempts; i++ { result, err = p.Exec() - if err == nil { + if err == nil || (result.code >= 0 && result.code <= exitCodeThreshold) { return result, nil } if i < (attempts - 1) { @@ -209,7 +217,10 @@ func call(stack []*exec.Cmd) (err error) { if err == nil { err = call(stack[1:]) } else { - stack[1].Wait() + err = stack[1].Wait() + } + if err != nil { + log.Infof("call: %v", err) } }() } diff --git a/internal/app/release.go b/internal/app/release.go index e9fd82b9..5f4613ab 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -166,7 +166,10 @@ func (r *release) uninstall(p *plan, optionalNamespace ...string) { // diffRelease diffs an existing release with the specified values.yaml func (r *release) diff() (string, error) { - var args []string + var ( + args []string + maxExitCode int + ) if !flags.kubectlDiff { args = []string{"diff", "--suppress-secrets"} @@ -186,9 +189,10 @@ func (r *release) diff() (string, error) { if flags.kubectlDiff { cmd = append(cmd, kubectl([]string{"diff", "--namespace", r.Namespace, "-f", "-"}, desc)) + maxExitCode = 1 } - res, err := cmd.RetryExec(3) + res, err := cmd.RetryExecWithThreshold(3, maxExitCode) if err != nil { if flags.kubectlDiff && res.code <= 1 { // kubectl diff exit status: From 42ac48d9e4c3d9dc4be32b128ed3ef019430d158 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 11 Jan 2022 12:57:58 +0000 Subject: [PATCH 0825/1127] refactor: add missing error checks --- internal/app/cli.go | 4 ++- internal/app/decision_maker_test.go | 4 ++- internal/app/helm_helpers_test.go | 2 +- internal/app/kube_helpers.go | 4 +-- internal/app/spec_state_test.go | 10 ++++++-- internal/app/state_files_test.go | 39 +++++++++++++++++++++-------- internal/app/state_test.go | 22 +++++++++++----- 7 files changed, 62 insertions(+), 23 deletions(-) diff --git a/internal/app/cli.go b/internal/app/cli.go index 99a3f09c..f5779822 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -259,7 +259,9 @@ func (c *cli) readState(s *state) error { if len(c.spec) > 0 { sp := new(StateFiles) - sp.specFromYAML(c.spec) + if err := sp.specFromYAML(c.spec); err != nil { + return fmt.Errorf("error parsing spec file: %w", err) + } for _, val := range sp.StateFiles { fo := fileOption{} diff --git a/internal/app/decision_maker_test.go b/internal/app/decision_maker_test.go index 3c9ccb7d..6f148741 100644 --- a/internal/app/decision_maker_test.go +++ b/internal/app/decision_maker_test.go @@ -112,7 +112,9 @@ func Test_inspectUpgradeScenario(t *testing.T) { // Act c, _ := getChartInfo(tt.args.r.Chart, tt.args.r.Version) - cs.inspectUpgradeScenario(tt.args.r, &outcome, c) + if err := cs.inspectUpgradeScenario(tt.args.r, &outcome, c); err != nil { + t.Errorf("inspectUpgradeScenario() error = %v", err) + } got := outcome.Decisions[0].Type t.Log(outcome.Decisions[0].Description) diff --git a/internal/app/helm_helpers_test.go b/internal/app/helm_helpers_test.go index e08f03ee..f816f891 100644 --- a/internal/app/helm_helpers_test.go +++ b/internal/app/helm_helpers_test.go @@ -72,7 +72,7 @@ func Test_getChartInfo(t *testing.T) { t.Run(tt.name, func(t *testing.T) { got, err := getChartInfo(tt.args.r.Chart, tt.args.r.Version) if err != nil && tt.want != nil { - t.Errorf("getChartInfo() = Unexpected error: %w", err) + t.Errorf("getChartInfo() = Unexpected error: %v", err) } if !reflect.DeepEqual(got, tt.want) { t.Errorf("getChartInfo() = %v, want %v", got, tt.want) diff --git a/internal/app/kube_helpers.go b/internal/app/kube_helpers.go index f175ea92..e7771a63 100644 --- a/internal/app/kube_helpers.go +++ b/internal/app/kube_helpers.go @@ -78,10 +78,10 @@ func labelNamespace(ns string, labels map[string]string, authoritative bool) { // ignore default k8s namespace label from being removed delete(nsLabels, "kubernetes.io/metadata.name") // ignore every label defined in DSF for the namespace from being removed - for definedLabelKey, _ := range labels { + for definedLabelKey := range labels { delete(nsLabels, definedLabelKey) } - for label, _ := range nsLabels { + for label := range nsLabels { args = append(args, label+"-") } } diff --git a/internal/app/spec_state_test.go b/internal/app/spec_state_test.go index dc209414..80fa3686 100644 --- a/internal/app/spec_state_test.go +++ b/internal/app/spec_state_test.go @@ -31,7 +31,10 @@ func Test_specFromYAML(t *testing.T) { }, } - teardownTestCase := setupStateFileTestCase(t) + teardownTestCase, err := setupStateFileTestCase(t) + if err != nil { + t.Fatal(err) + } defer teardownTestCase(t) for _, tt := range tests { // os.Args = append(os.Args, "-f ../../examples/example.yaml") @@ -72,7 +75,10 @@ func Test_specFileSort(t *testing.T) { }, } - teardownTestCase := setupStateFileTestCase(t) + teardownTestCase, err := setupStateFileTestCase(t) + if err != nil { + t.Fatal(err) + } defer teardownTestCase(t) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/internal/app/state_files_test.go b/internal/app/state_files_test.go index 82c670c5..235cda6c 100644 --- a/internal/app/state_files_test.go +++ b/internal/app/state_files_test.go @@ -6,14 +6,17 @@ import ( "testing" ) -func setupStateFileTestCase(t *testing.T) func(t *testing.T) { +func setupStateFileTestCase(t *testing.T) (func(t *testing.T), error) { t.Log("setup test case") - os.MkdirAll(tempFilesDir, 0o755) + if err := os.MkdirAll(tempFilesDir, 0o755); err != nil { + t.Errorf("setupStateFileTestCase(), failed to create temp files dir: %v", err) + return nil, err + } return func(t *testing.T) { t.Log("teardown test case") os.RemoveAll(tempFilesDir) - } + }, nil } func Test_fromTOML(t *testing.T) { @@ -45,7 +48,10 @@ func Test_fromTOML(t *testing.T) { os.Setenv("ORG_PATH", "sample") os.Setenv("VALUE", "sample") - teardownTestCase := setupStateFileTestCase(t) + teardownTestCase, err := setupStateFileTestCase(t) + if err != nil { + t.Errorf("setupStateFileTestCase(), got: %v", err) + } defer teardownTestCase(t) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -97,7 +103,10 @@ func Test_fromTOML_Expand(t *testing.T) { os.Setenv("ORG_PATH", "sample") os.Setenv("VALUE", "sample") - teardownTestCase := setupStateFileTestCase(t) + teardownTestCase, err := setupStateFileTestCase(t) + if err != nil { + t.Errorf("setupStateFileTestCase(), got: %v", err) + } defer teardownTestCase(t) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -179,7 +188,10 @@ func Test_fromYAML(t *testing.T) { os.Setenv("VALUE", "sample") os.Setenv("ORG_PATH", "sample") - teardownTestCase := setupStateFileTestCase(t) + teardownTestCase, err := setupStateFileTestCase(t) + if err != nil { + t.Errorf("setupStateFileTestCase(), got: %v", err) + } defer teardownTestCase(t) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -225,13 +237,17 @@ func Test_fromYAML_UnsetVars(t *testing.T) { }, } - teardownTestCase := setupStateFileTestCase(t) + teardownTestCase, err := setupStateFileTestCase(t) + if err != nil { + t.Errorf("setupStateFileTestCase(), got: %v", err) + } defer teardownTestCase(t) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if tt.targetVar == "ORG_PATH" { + switch tt.targetVar { + case "ORG_PATH": os.Setenv("VALUE", "sample") - } else if tt.targetVar == "VALUE" { + case "VALUE": os.Setenv("ORG_PATH", "sample") } err := tt.args.s.fromYAML(tt.args.file) @@ -282,7 +298,10 @@ func Test_fromYAML_Expand(t *testing.T) { os.Setenv("ORG_PATH", "sample") os.Setenv("VALUE", "sample") - teardownTestCase := setupStateFileTestCase(t) + teardownTestCase, err := setupStateFileTestCase(t) + if err != nil { + t.Errorf("setupStateFileTestCase(), got: %v", err) + } defer teardownTestCase(t) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/internal/app/state_test.go b/internal/app/state_test.go index 5330a2be..26cbe03d 100644 --- a/internal/app/state_test.go +++ b/internal/app/state_test.go @@ -5,11 +5,17 @@ import ( "testing" ) -func setupTestCase(t *testing.T) func(t *testing.T) { +func setupTestCase(t *testing.T) (func(t *testing.T), error) { t.Log("setup test case") - os.MkdirAll(tempFilesDir, 0o755) - os.MkdirAll(os.TempDir()+"/helmsman-tests/myapp", os.ModePerm) - os.MkdirAll(os.TempDir()+"/helmsman-tests/dir-with space/myapp", os.ModePerm) + if err := os.MkdirAll(tempFilesDir, 0o755); err != nil { + return nil, err + } + if err := os.MkdirAll(os.TempDir()+"/helmsman-tests/myapp", os.ModePerm); err != nil { + return nil, err + } + if err := os.MkdirAll(os.TempDir()+"/helmsman-tests/dir-with space/myapp", os.ModePerm); err != nil { + return nil, err + } cmd := helmCmd([]string{"create", os.TempDir() + "/helmsman-tests/dir-with space/myapp"}, "creating an empty local chart directory") if _, err := cmd.Exec(); err != nil { log.Fatalf("Command failed: %v", err) @@ -19,7 +25,7 @@ func setupTestCase(t *testing.T) func(t *testing.T) { t.Log("teardown test case") os.RemoveAll(tempFilesDir) os.RemoveAll(os.TempDir() + "/helmsman-tests/") - } + }, nil } func Test_state_validate(t *testing.T) { @@ -494,7 +500,11 @@ func Test_state_getReleaseChartsInfo(t *testing.T) { }, } - teardownTestCase := setupTestCase(t) + teardownTestCase, err := setupTestCase(t) + if err != nil { + t.Errorf("setupTestCase() = %v", err) + return + } defer teardownTestCase(t) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From 7ccde4b5fdda68b9769c4fdead0983546c915891 Mon Sep 17 00:00:00 2001 From: Troy Gaines Date: Fri, 21 Jan 2022 11:29:32 -0600 Subject: [PATCH 0826/1127] Fix broken links in examples --- docs/how_to/apps/basic.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how_to/apps/basic.md b/docs/how_to/apps/basic.md index 3e3cc3ef..3d6d845a 100644 --- a/docs/how_to/apps/basic.md +++ b/docs/how_to/apps/basic.md @@ -6,7 +6,7 @@ version: v3.0.0-beta5 ## Install releases -You can run helmsman with the [example.toml](https://github.com/Praqma/helmsman/blob/master/example.toml) or [example.yaml](https://github.com/Praqma/helmsman/blob/master/example.yaml) file. +You can run helmsman with the [example.toml](https://github.com/Praqma/helmsman/blob/master/examples/example.toml) or [example.yaml](https://github.com/Praqma/helmsman/blob/master/examples/example.yaml) file. ```shell $ helmsman --apply -f example.toml From 4ddbe51dd059ce97e9b37ab0857d66e231a171eb Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Mon, 24 Jan 2022 11:27:15 +0000 Subject: [PATCH 0827/1127] chore: prepare new release --- .version | 2 +- README.md | 6 +++--- .../multiple_desired_state_files_specification.md | 15 ++++++++------- internal/app/main.go | 2 +- release-notes.md | 2 +- 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/.version b/.version index 40c06ccb..32f8572e 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.8.0 +v3.8.1 diff --git a/README.md b/README.md index 1a09de3f..c2e90f4f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.8.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.8.1&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -76,9 +76,9 @@ Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the d ```sh # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.7/helmsman_3.7.7_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.8.1/helmsman_3.8.1_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.7.7/helmsman_3.7.7_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.8.1/helmsman_3.8.1_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/docs/how_to/misc/multiple_desired_state_files_specification.md b/docs/how_to/misc/multiple_desired_state_files_specification.md index 0de9c740..8167ec79 100644 --- a/docs/how_to/misc/multiple_desired_state_files_specification.md +++ b/docs/how_to/misc/multiple_desired_state_files_specification.md @@ -1,17 +1,15 @@ --- -version: v3.8.0 +version: v3.8.1 --- # Specification file -Starting from v3.8.0, Helmsman allows you to use Specification file passed with `--spec ` flag +Starting from v3.8.0, Helmsman allows you to use Specification file passed with `--spec ` flag in order to define multiple Desired State Files to be merged in particular order and with specific priorities. -An example Specification file: +An example Specification file `spec.yaml`: -`spec.yaml`: - -``` +```yaml --- stateFiles: - path: examples/example.yaml @@ -23,17 +21,20 @@ stateFiles: ``` This file can be then run with: + ```shell helmsman --spec spec.yaml ... ``` What it does is it takes the files from `stateFiles` list and orders them based on their priorities same way it does with the apps in DSF file. In an example above the result order would be: -``` + +```yaml - path: examples/minimal-example.toml - path: examples/minimal-example.yaml - path: examples/example.yaml ``` + with priorities being `-20, -10, 0` after ordering. Once ordering is done, Helmsman will read each file one by one and merge the previous states with the current file it goes through. diff --git a/internal/app/main.go b/internal/app/main.go index 3bf88f83..153b3144 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.8.0" + appVersion = "v3.8.1" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index 9533176d..14645ebb 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,4 +1,4 @@ -# v3.8.0 +# v3.8.1 ## Fixes and improvements From e196c3794037ab22938700751b7c742e94c102fa Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Mon, 24 Jan 2022 11:30:52 +0000 Subject: [PATCH 0828/1127] chore: update release notes --- release-notes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/release-notes.md b/release-notes.md index 14645ebb..334c4ae8 100644 --- a/release-notes.md +++ b/release-notes.md @@ -2,5 +2,5 @@ ## Fixes and improvements -- feat: Add skipIgnoredApps settings option to allow ignoring apps not considered when using `-target` or `-group` flag (#634) -- feat: Add Specification file support with `-spec` flag in order to define an environment with multiple Desired State Files (#633) +- fix: kubectl diff may return 1 on success (#635) +- refactor: add missing error checks (#635) From 14067401a761e44a9d5e90e0cd29c795dccaa210 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Mon, 24 Jan 2022 11:32:07 +0000 Subject: [PATCH 0829/1127] fix: wrong PR number in release notes --- release-notes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/release-notes.md b/release-notes.md index 334c4ae8..7fb4eff2 100644 --- a/release-notes.md +++ b/release-notes.md @@ -2,5 +2,5 @@ ## Fixes and improvements -- fix: kubectl diff may return 1 on success (#635) -- refactor: add missing error checks (#635) +- fix: kubectl diff may return 1 on success (#637) +- refactor: add missing error checks (#637) From 8f9542b0692102edfc3d887f334e3eff9df9631a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vesa=20Hagstr=C3=B6m?= Date: Tue, 1 Mar 2022 17:20:23 +0200 Subject: [PATCH 0830/1127] Add option to check for new chart updates --- internal/app/cli.go | 2 ++ internal/app/main.go | 6 ++++++ internal/app/release.go | 17 +++++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/internal/app/cli.go b/internal/app/cli.go index f5779822..cfed9000 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -101,6 +101,7 @@ type cli struct { alwaysUpgrade bool noUpdate bool kubectlDiff bool + checkForChartUpdates bool } func printUsage() { @@ -150,6 +151,7 @@ func (c *cli) parse() { flag.BoolVar(&c.alwaysUpgrade, "always-upgrade", false, "upgrade release even if no changes are found") flag.BoolVar(&c.noUpdate, "no-update", false, "skip updating helm repos") flag.BoolVar(&c.kubectlDiff, "kubectl-diff", false, "use kubectl diff instead of helm diff. Defalts to false if the helm diff plugin is installed.") + flag.BoolVar(&c.checkForChartUpdates, "check-for-chart-updates", false, "compares the version in the state file to the latest version in the chart repository") flag.Usage = printUsage flag.Parse() diff --git a/internal/app/main.go b/internal/app/main.go index 153b3144..ec40e38b 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -103,6 +103,12 @@ func Main() { s.updateContextLabels() } + if flags.checkForChartUpdates { + for _, r := range s.Apps { + r.checkChartForUpdates() + } + } + log.Info("Preparing plan") cs := s.getCurrentState() p := cs.makePlan(&s) diff --git a/internal/app/release.go b/internal/app/release.go index 5f4613ab..4993ea9a 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -429,6 +429,23 @@ func (r *release) checkChartDepUpdate() { } } +func (r *release) checkChartForUpdates() { + if !r.isConsideredToRun() { + return + } + + if flags.checkForChartUpdates && !isLocalChart(r.Chart) { + chartInfo, err := getChartInfo(r.Chart, ">= "+r.Version) + if err != nil { + log.Fatal("Couldn't check version for " + r.Chart + ": " + err.Error()) + } + + if checkVersion(r.Version, "< "+chartInfo.Version) { + log.Infof("Newer version for release %s, chart %s found: current %s, latest %s", r.Name, r.Chart, r.Version, chartInfo.Version) + } + } +} + // overrideNamespace overrides a release defined namespace with a new given one func (r *release) overrideNamespace(newNs string) { log.Info("Overriding namespace for app: " + r.Name) From e0f28eda4c3c36fb1ed7d05bae6f6f3f3a2c862a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vesa=20Hagstr=C3=B6m?= Date: Wed, 2 Mar 2022 07:17:41 +0200 Subject: [PATCH 0831/1127] Docs for check-for-chart-updates flag --- docs/cmd_reference.md | 3 +++ internal/app/cli.go | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/cmd_reference.md b/docs/cmd_reference.md index 1ff80437..28ab73e3 100644 --- a/docs/cmd_reference.md +++ b/docs/cmd_reference.md @@ -101,4 +101,7 @@ This lists available CMD options in Helmsman: `--update-deps` run 'helm dep up' for local chart + `--check-for-chart-updates` + compares the chart versions in the state file to the latest versions in the chart repositories and shows available updates + `--v` show the version. diff --git a/internal/app/cli.go b/internal/app/cli.go index cfed9000..ab5a9b8b 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -151,7 +151,7 @@ func (c *cli) parse() { flag.BoolVar(&c.alwaysUpgrade, "always-upgrade", false, "upgrade release even if no changes are found") flag.BoolVar(&c.noUpdate, "no-update", false, "skip updating helm repos") flag.BoolVar(&c.kubectlDiff, "kubectl-diff", false, "use kubectl diff instead of helm diff. Defalts to false if the helm diff plugin is installed.") - flag.BoolVar(&c.checkForChartUpdates, "check-for-chart-updates", false, "compares the version in the state file to the latest version in the chart repository") + flag.BoolVar(&c.checkForChartUpdates, "check-for-chart-updates", false, "compares the chart versions in the state file to the latest versions in the chart repositories and shows available updates") flag.Usage = printUsage flag.Parse() From eef7161c18c70b16a661b3f41c53ac5e57007f49 Mon Sep 17 00:00:00 2001 From: Justin Menga Date: Sun, 20 Mar 2022 23:13:20 -0700 Subject: [PATCH 0832/1127] Support arm64 builds --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e26247f7..b88bf9f8 100644 --- a/Makefile +++ b/Makefile @@ -77,7 +77,7 @@ test: deps vet repo ## Run unit tests .PHONY: test cross: deps ## Create binaries for all OSs - @gox -os '!freebsd !netbsd' -arch '!arm' -output "dist/{{.Dir}}_{{.OS}}_{{.Arch}}" -ldflags '-X main.Version=${TAG}-${DATE}' ./... + @gox -os 'windows linux darwin' -arch 'arm64 amd64' -output "dist/{{.Dir}}_{{.OS}}_{{.Arch}}" -ldflags '-X main.Version=${TAG}-${DATE}' ./... .PHONY: cross release: ## Generate a new release From 4950c323493fd0f6b7e3fcf43cc9bdeb4af4103e Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sat, 12 Mar 2022 21:54:37 +0000 Subject: [PATCH 0833/1127] chore: update dependencies Signed-off-by: Luis Davim --- go.mod | 36 +++++++++------- go.sum | 129 ++++++++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 120 insertions(+), 45 deletions(-) diff --git a/go.mod b/go.mod index 9f9b7201..cb70419f 100644 --- a/go.mod +++ b/go.mod @@ -3,40 +3,44 @@ module github.com/Praqma/helmsman go 1.17 require ( - cloud.google.com/go/storage v1.16.0 + cloud.google.com/go/storage v1.21.0 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.14.0 - github.com/Azure/go-autorest/autorest/adal v0.9.14 // indirect - github.com/BurntSushi/toml v0.4.1 + github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect + github.com/BurntSushi/toml v1.0.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.40.32 + github.com/aws/aws-sdk-go v1.43.17 github.com/davecgh/go-spew v1.1.1 // indirect - github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/uuid v1.3.0 // indirect github.com/imdario/mergo v0.3.12 - github.com/joho/godotenv v1.3.0 + github.com/joho/godotenv v1.4.0 github.com/kr/text v0.2.0 // indirect github.com/logrusorgru/aurora v2.0.3+incompatible - golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e // indirect - golang.org/x/net v0.0.0-20210614182718-04defd469f4e - golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect - google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71 // indirect + golang.org/x/crypto v0.0.0-20220312131142-6068a2e6cfdc // indirect + golang.org/x/net v0.0.0-20220225172249-27dd8689420f + golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a // indirect + google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6 // indirect google.golang.org/protobuf v1.27.1 // indirect gopkg.in/yaml.v2 v2.4.0 ) require ( - cloud.google.com/go v0.93.3 // indirect + cloud.google.com/go v0.100.2 // indirect + cloud.google.com/go/compute v1.5.0 // indirect + cloud.google.com/go/iam v0.3.0 // indirect + github.com/golang-jwt/jwt/v4 v4.3.0 // indirect github.com/golang/protobuf v1.5.2 // indirect - github.com/googleapis/gax-go/v2 v2.1.0 // indirect + github.com/google/go-cmp v0.5.7 // indirect + github.com/googleapis/gax-go/v2 v2.1.1 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/mattn/go-ieproxy v0.0.1 // indirect go.opencensus.io v0.23.0 // indirect - golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 // indirect - golang.org/x/text v0.3.6 // indirect - google.golang.org/api v0.54.0 // indirect + golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + google.golang.org/api v0.71.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/grpc v1.40.0 // indirect + google.golang.org/grpc v1.45.0 // indirect ) diff --git a/go.sum b/go.sum index 44705d7a..fbec28bf 100644 --- a/go.sum +++ b/go.sum @@ -22,16 +22,29 @@ cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAV cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= -cloud.google.com/go v0.93.3 h1:wPBktZFzYBcCZVARvwVKqH1uEj+aLXofJEtrb4oOsio= cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U= +cloud.google.com/go v0.100.2 h1:t9Iw5QH5v4XtlEQaCtUY7x6sCABps8sW0acw7e2WQ6Y= +cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= +cloud.google.com/go/compute v1.2.0/go.mod h1:xlogom/6gr8RJGBe7nT2eGsQYAFUbbv8dbC29qE3Xmw= +cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= +cloud.google.com/go/compute v1.5.0 h1:b1zWmYuuHz7gO9kDcM/EpHGr06UgsYNRpNJzI2kFiLM= +cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/iam v0.1.1/go.mod h1:CKqrcnI/suGpybEHxZ7BMehL0oA4LpdyJdUlTl9jVMw= +cloud.google.com/go/iam v0.3.0 h1:exkAomrVUuzx9kWFI1wm3KI0uoDeUFPB4kKGzx6x+Gc= +cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -41,8 +54,8 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.16.0 h1:1UwAux2OZP4310YXg5ohqBEpV16Y93uZG4+qOX7K2Kg= -cloud.google.com/go/storage v1.16.0/go.mod h1:ieKBmUyzcftN5tbxwnXClMKH00CfcQ+xL6NN0r5QfmE= +cloud.google.com/go/storage v1.21.0 h1:HwnT2u2D309SFDHQII6m18HlrCi3jAXhUMTLOWXYH14= +cloud.google.com/go/storage v1.21.0/go.mod h1:XmRlxkgPjlBONznT2dDUU/5XlpU2OjMnKuqnZI01LAA= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= @@ -51,8 +64,8 @@ github.com/Azure/azure-storage-blob-go v0.14.0/go.mod h1:SMqIBi+SuiQH32bvyjngEew github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= -github.com/Azure/go-autorest/autorest/adal v0.9.14 h1:G8hexQdV5D4khOXrWG2YuLCFKhWYmWD8bHYaXN5ophk= -github.com/Azure/go-autorest/autorest/adal v0.9.14/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= +github.com/Azure/go-autorest/autorest/adal v0.9.18 h1:kLnPsRjzZZUF3K5REu/Kc+qMQrvuza2bwSnNdhmzLfQ= +github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= @@ -61,8 +74,8 @@ github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw= -github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU= +github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= @@ -70,10 +83,11 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.40.32 h1:ok+9vnnqYWJXofYhaOtfP/bOt2reDqTA6ZAS00AO5pA= -github.com/aws/aws-sdk-go v1.40.32/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= +github.com/aws/aws-sdk-go v1.43.17 h1:jDPBz1UuTxmyRo0eLgaRiro0fiI1zL7lkscqYxoEDLM= +github.com/aws/aws-sdk-go v1.43.17/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -81,7 +95,11 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -93,14 +111,16 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c= -github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog= +github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -148,8 +168,9 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -177,8 +198,9 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.1.0 h1:6DWmvNpomjL1+3liNSZbVns3zsYzzCjm6pRBO1tLeso= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1 h1:dp3bWCh+PPO1zjRRiCSczJav13sBvG4UhNyVTa1KqdU= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -190,8 +212,8 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= -github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= +github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -218,6 +240,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -238,8 +261,9 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI= -golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220312131142-6068a2e6cfdc h1:i6Z9eOQAdM7lvsbkT3fwFNtSAAC+A59TYilFj53HW+E= +golang.org/x/crypto v0.0.0-20220312131142-6068a2e6cfdc/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -311,8 +335,10 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q= -golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -325,11 +351,13 @@ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210615190721-d04028783cf1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f h1:Qmd2pbz05z7z6lm0DrgQVVPuBm92jqujBKMHMOlOQEw= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a h1:qfl7ob3DIEs3Ml9oLuPwY2N04gymzAW04WsUQHIClgM= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -386,9 +414,20 @@ golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraCFObP8S1v6PRp0bLrtU= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -396,8 +435,9 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -480,11 +520,21 @@ google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBz google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= -google.golang.org/api v0.49.0/go.mod h1:BECiH72wsfwUvOVn3+btPD5WHi0LzavZReBndi42L18= google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= -google.golang.org/api v0.54.0 h1:ECJUVngj71QI6XEm7b1sAf8BljU5inEhMbKPR8Lxhhk= google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +google.golang.org/api v0.64.0/go.mod h1:931CdxA8Rm4t6zqTFGSsgwbAEZ2+GMYurbndwSimebM= +google.golang.org/api v0.66.0/go.mod h1:I1dmXYpX7HGwz/ejRxwQp2qj5bFAz93HiCU1C1oYd9M= +google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= +google.golang.org/api v0.69.0/go.mod h1:boanBiw+h5c3s+tBPgEzLDRHfFLWV0qXxRHz3ws7C80= +google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= +google.golang.org/api v0.71.0 h1:SgWof18M8V2NylsX7bL4fM28j+nFdRopHZbdipaaw20= +google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -537,8 +587,6 @@ google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQ google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20210624174822-c5cf32407d0a/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= @@ -546,8 +594,28 @@ google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKr google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71 h1:z+ErRPu0+KS02Td3fOAgdX+lnPDh/VyaABEJPD4JRQs= google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220111164026-67b88f271998/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220114231437-d2e6a121cae0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220201184016-50beb8ab5c44/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220211171837-173942840c17/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220216160803-4663080d8bc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6 h1:FglFEfyj61zP3c6LgjmVHxYxZWXYul9oiS1EZqD5gLc= +google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -572,8 +640,11 @@ google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQ google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= From cd89450692e52ac8ef2468b5bf722ab7d80332a5 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Thu, 24 Mar 2022 13:38:10 +0000 Subject: [PATCH 0834/1127] feat: dont download charts if helm can do it by itself Signed-off-by: Luis Davim --- .circleci/config.yml | 2 +- Dockerfile | 2 +- internal/app/cli.go | 2 ++ internal/app/state_files.go | 9 +++++++++ internal/app/utils.go | 21 +++++++++++++++++---- 5 files changed, 30 insertions(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b69c2ab8..05324173 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -36,7 +36,7 @@ jobs: - run: name: build docker images and push them to dockerhub command: | - helm_versions=( "v3.5.4" "v3.6.3" "v3.7.2" ) + helm_versions=( "v3.6.3" "v3.7.2" "v3.8.1" ) TAG=$(git describe --abbrev=0 --tags) docker login -u $DOCKER_USER -p $DOCKER_PASS diff --git a/Dockerfile b/Dockerfile index 60824f32..f1a7dacb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ ARG GO_VERSION="1.17.0" ARG ALPINE_VERSION="3.14" ARG GLOBAL_KUBE_VERSION="v1.22.1" -ARG GLOBAL_HELM_VERSION="v3.6.3" +ARG GLOBAL_HELM_VERSION="v3.8.1" ARG GLOBAL_HELM_DIFF_VERSION="v3.1.3" ARG GLOBAL_SOPS_VERSION="v3.7.1" diff --git a/internal/app/cli.go b/internal/app/cli.go index ab5a9b8b..c20a2a0e 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -101,6 +101,7 @@ type cli struct { alwaysUpgrade bool noUpdate bool kubectlDiff bool + downloadCharts bool checkForChartUpdates bool } @@ -152,6 +153,7 @@ func (c *cli) parse() { flag.BoolVar(&c.noUpdate, "no-update", false, "skip updating helm repos") flag.BoolVar(&c.kubectlDiff, "kubectl-diff", false, "use kubectl diff instead of helm diff. Defalts to false if the helm diff plugin is installed.") flag.BoolVar(&c.checkForChartUpdates, "check-for-chart-updates", false, "compares the chart versions in the state file to the latest versions in the chart repositories and shows available updates") + flag.BoolVar(&c.downloadCharts, "download-charts", false, "download charts referenced by URLs in the state file") flag.Usage = printUsage flag.Parse() diff --git a/internal/app/state_files.go b/internal/app/state_files.go index e9059ba6..024438a2 100644 --- a/internal/app/state_files.go +++ b/internal/app/state_files.go @@ -142,12 +142,17 @@ func (s *state) toYAML(file string) { func (s *state) expand(relativeToFile string) { dir := filepath.Dir(relativeToFile) downloadDest, _ := filepath.Abs(createTempDir(tempFilesDir, "tmp")) + validProtocols := []string{"http", "https"} + if checkHelmVersion(">=3.8.0") { + validProtocols = append(validProtocols, "oci") + } for _, r := range s.Apps { // resolve paths for all release files (values, secrets, hooks, etc...) r.resolvePaths(dir, downloadDest) // resolve paths for local charts if r.Chart != "" { + var download bool // support env vars in path r.Chart = os.ExpandEnv(r.Chart) repoName := strings.Split(r.Chart, "/")[0] @@ -155,6 +160,10 @@ func (s *state) expand(relativeToFile string) { isRepo = isRepo || stringInSlice(repoName, s.PreconfiguredHelmRepos) // if there is no repo for the chart, we assume it's intended to be a local path or url if !isRepo { + // unless explicitly requested by the user, we don't need to download if the protocol is natively supported by helm + download = flags.downloadCharts || !isSupportedProtocol(r.Chart, validProtocols) + } + if download { if strings.HasPrefix(r.Chart, "oci://") && !strings.HasSuffix(r.Chart, r.Version) { r.Chart = fmt.Sprintf("%s:%s", r.Chart, r.Version) } diff --git a/internal/app/utils.go b/internal/app/utils.go index da5ce729..a5573600 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -117,6 +117,19 @@ func isOfType(filename string, filetypes []string) bool { return result } +func isSupportedProtocol(ref string, protocols []string) bool { + u, err := url.Parse(ref) + if err != nil { + log.Fatalf("%s is not a valid path: %s", ref, err) + } + for _, p := range protocols { + if u.Scheme == p { + return true + } + } + return false +} + // readFile returns the content of a file as a string. // takes a file path as input. It throws an error and breaks the program execution if it fails to read the file. func readFile(filepath string) string { @@ -375,7 +388,7 @@ func notifySlack(content string, url string, failure bool, executing bool) bool defer resp.Body.Close() if resp.StatusCode != 200 { - log.Logger.Errorf("Could not deliver message to Slack. HTTP response status: %s", resp.Status) + log.Errorf("Could not deliver message to Slack. HTTP response status: %s", resp.Status) return false } return true @@ -557,7 +570,7 @@ func notifyMSTeams(content string, url string, failure bool, executing bool) boo contentBold += "**" + contentSplit[i] + "**\n\n" } } - contentBold = strings.TrimRight(contentBold, "\n\n") + contentBold = strings.TrimSuffix(contentBold, "\n\n") } else if executing && !failure { pretext = "Here is what I have done:" contentBold = "**" + content + "**" @@ -572,8 +585,8 @@ func notifyMSTeams(content string, url string, failure bool, executing bool) boo jsonStr := []byte(`{ "@type": "MessageCard", - "@context": "http://schema.org/extensions", - "themeColor": "` + color + `", + "@context": "http://schema.org/extensions", + "themeColor": "` + color + `", "title": "` + pretext + `", "summary": "Helmsman results.", "sections": [ From d61579a27054548568538e977f4d83782462586c Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sun, 27 Mar 2022 20:06:11 +0100 Subject: [PATCH 0835/1127] feat: allow running a specific test using the Makefile --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index b88bf9f8..592035e6 100644 --- a/Makefile +++ b/Makefile @@ -76,6 +76,9 @@ test: deps vet repo ## Run unit tests @go test -v -cover -p=1 ./... -args -f ../../examples/example.toml .PHONY: test +test-%: deps vet repo ## Run a specific unit test + @go test -v -cover -p=1 -run $(*) ./... -args -f ../../examples/example.toml + cross: deps ## Create binaries for all OSs @gox -os 'windows linux darwin' -arch 'arm64 amd64' -output "dist/{{.Dir}}_{{.OS}}_{{.Arch}}" -ldflags '-X main.Version=${TAG}-${DATE}' ./... .PHONY: cross From 0f5ae9ce4294dd6c49dce2102da6c8ef90307efa Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sun, 27 Mar 2022 20:08:48 +0100 Subject: [PATCH 0836/1127] refactor: breakout the state building into a separate function --- internal/app/cli.go | 32 +------------------------------- internal/app/state_files.go | 37 ++++++++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 32 deletions(-) diff --git a/internal/app/cli.go b/internal/app/cli.go index c20a2a0e..57a9a12f 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -7,7 +7,6 @@ import ( "sort" "strings" - "github.com/imdario/mergo" "github.com/joho/godotenv" ) @@ -279,36 +278,7 @@ func (c *cli) readState(s *state) error { } // read the TOML/YAML desired state file - for _, f := range c.files { - var fileState state - - if err := fileState.fromFile(f.name); err != nil { - return err - } - - log.Infof("Parsed [[ %s ]] successfully and found [ %d ] apps", f.name, len(fileState.Apps)) - // Merge Apps that already existed in the state - for appName, app := range fileState.Apps { - if _, ok := s.Apps[appName]; ok { - if err := mergo.Merge(s.Apps[appName], app, mergo.WithAppendSlice, mergo.WithOverride); err != nil { - return fmt.Errorf("failed to merge %s from desired state file %s: %w", appName, f.name, err) - } - } - } - - // Merge the remaining Apps - if err := mergo.Merge(&s.Apps, &fileState.Apps); err != nil { - return fmt.Errorf("failed to merge desired state file %s: %w", f.name, err) - } - // All the apps are already merged, make fileState.Apps empty to avoid conflicts in the final merge - fileState.Apps = make(map[string]*release) - - if err := mergo.Merge(s, &fileState, mergo.WithAppendSlice, mergo.WithOverride); err != nil { - return fmt.Errorf("failed to merge desired state file %s: %w", f.name, err) - } - } - - s.init() // Set defaults + s.build(c.files) s.disableUntargetedApps(c.group, c.target) if !c.skipValidation { diff --git a/internal/app/state_files.go b/internal/app/state_files.go index 024438a2..bb06d1cc 100644 --- a/internal/app/state_files.go +++ b/internal/app/state_files.go @@ -9,12 +9,13 @@ import ( "strings" "github.com/BurntSushi/toml" + "github.com/imdario/mergo" "gopkg.in/yaml.v2" ) // invokes either yaml or toml parser considering file extension func (s *state) fromFile(file string) error { - if isOfType(file, []string{".toml"}) { + if isOfType(file, []string{".toml", ".tml"}) { return s.fromTOML(file) } else if isOfType(file, []string{".yaml", ".yml"}) { return s.fromYAML(file) @@ -137,6 +138,40 @@ func (s *state) toYAML(file string) { newFile.Close() } +func (s *state) build(files fileOptionArray) error { + for _, f := range files { + var fileState state + + if err := fileState.fromFile(f.name); err != nil { + return err + } + + log.Infof("Parsed [[ %s ]] successfully and found [ %d ] apps", f.name, len(fileState.Apps)) + // Merge Apps that already existed in the state + for appName, app := range fileState.Apps { + if _, ok := s.Apps[appName]; ok { + if err := mergo.Merge(s.Apps[appName], app, mergo.WithAppendSlice, mergo.WithOverride); err != nil { + return fmt.Errorf("failed to merge %s from desired state file %s: %w", appName, f.name, err) + } + } + } + + // Merge the remaining Apps + if err := mergo.Merge(&s.Apps, &fileState.Apps); err != nil { + return fmt.Errorf("failed to merge desired state file %s: %w", f.name, err) + } + // All the apps are already merged, make fileState.Apps empty to avoid conflicts in the final merge + fileState.Apps = make(map[string]*release) + + if err := mergo.Merge(s, &fileState, mergo.WithAppendSlice, mergo.WithOverride); err != nil { + return fmt.Errorf("failed to merge desired state file %s: %w", f.name, err) + } + } + + s.init() // Set defaults + return nil +} + // expand resolves relative paths of certs/keys/chart/value file/secret files/etc and replace them with a absolute paths // it also loops through the values/secrets files and substitutes variables into them. func (s *state) expand(relativeToFile string) { From 792b65a0f87a905211433b197b29c062c2b68e4c Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sun, 27 Mar 2022 20:09:43 +0100 Subject: [PATCH 0837/1127] test: adding a test for the state building function --- examples/composition/README.md | 13 +++++++++++ examples/composition/argo.yaml | 31 ++++++++++++++++++++++++++ examples/composition/artifactory.yaml | 32 +++++++++++++++++++++++++++ examples/composition/main.yaml | 10 +++++++++ examples/composition/spec.yaml | 5 +++++ internal/app/state_files_test.go | 24 ++++++++++++++++++++ 6 files changed, 115 insertions(+) create mode 100644 examples/composition/README.md create mode 100644 examples/composition/argo.yaml create mode 100644 examples/composition/artifactory.yaml create mode 100644 examples/composition/main.yaml create mode 100644 examples/composition/spec.yaml diff --git a/examples/composition/README.md b/examples/composition/README.md new file mode 100644 index 00000000..8bfb08bd --- /dev/null +++ b/examples/composition/README.md @@ -0,0 +1,13 @@ +# Composition + +Desired state configuration can be split into multiplle files and applied with: + +```sh +helmsman --apply -f main.yaml -f argo.yaml -f artifactory.yaml +``` + +or using a spec file: + +```sh +helmsman --apply --spec spec.yaml +``` diff --git a/examples/composition/argo.yaml b/examples/composition/argo.yaml new file mode 100644 index 00000000..c1de1997 --- /dev/null +++ b/examples/composition/argo.yaml @@ -0,0 +1,31 @@ +namespaces: + staging: + protected: false + labels: + env: "staging" + quotas: + limits.cpu: "10" + limits.memory: "20Gi" + pods: 25 + requests.cpu: "10" + requests.memory: "30Gi" + customQuotas: + - name: "requests.nvidia.com/gpu" + value: "2" + +helmRepos: + argo: "https://argoproj.github.io/argo-helm" + +apps: + argo: + namespace: "staging" + enabled: true + chart: "argo/argo" + version: "0.8.5" + valuesFile: "" + test: false + protected: true + priority: -3 + wait: true + set: + "images.tag": latest diff --git a/examples/composition/artifactory.yaml b/examples/composition/artifactory.yaml new file mode 100644 index 00000000..610f7156 --- /dev/null +++ b/examples/composition/artifactory.yaml @@ -0,0 +1,32 @@ +namespaces: + production: + protected: true + limits: + - type: Container + default: + cpu: "300m" + memory: "200Mi" + defaultRequest: + cpu: "200m" + memory: "100Mi" + - type: Pod + max: + memory: "300Mi" + +helmRepos: + jfrog: "https://charts.jfrog.io" + +apps: + artifactory: + namespace: "production" + enabled: true + chart: "jfrog/artifactory" + version: "8.3.2" + valuesFile: "" + test: false + priority: -2 + noHooks: false + timeout: 300 + maxHistory: 4 + helmFlags: + - "--devel" diff --git a/examples/composition/main.yaml b/examples/composition/main.yaml new file mode 100644 index 00000000..7de34155 --- /dev/null +++ b/examples/composition/main.yaml @@ -0,0 +1,10 @@ +context: test-infra + +metadata: + org: "example.com/my_org/" + maintainer: "k8s-admin (me@example.com)" + description: "example Desired State File for demo purposes." + +settings: + kubeContext: "minikube" + globalMaxHistory: 5 diff --git a/examples/composition/spec.yaml b/examples/composition/spec.yaml new file mode 100644 index 00000000..c894bb85 --- /dev/null +++ b/examples/composition/spec.yaml @@ -0,0 +1,5 @@ +--- +stateFiles: + - path: main.yaml + - path: argo.yaml + - path: artifactory.yaml diff --git a/internal/app/state_files_test.go b/internal/app/state_files_test.go index 235cda6c..99f54b02 100644 --- a/internal/app/state_files_test.go +++ b/internal/app/state_files_test.go @@ -353,3 +353,27 @@ func Test_fromYAML_Expand(t *testing.T) { os.Unsetenv("SET_URI") os.Unsetenv("VALUE") } + +func Test_build(t *testing.T) { + teardownTestCase, err := setupStateFileTestCase(t) + if err != nil { + t.Errorf("setupStateFileTestCase(), got: %v", err) + } + defer teardownTestCase(t) + s := new(state) + files := fileOptionArray{ + fileOption{name: "../../examples/composition/main.yaml"}, + fileOption{name: "../../examples/composition/argo.yaml"}, + fileOption{name: "../../examples/composition/artifactory.yaml"}, + } + err = s.build(files) + if err != nil { + t.Errorf("build() - unexpected error: %v", err) + } + if len(s.Apps) != 2 { + t.Errorf("build() - unexpected number of apps, wanted 2 got %d", len(s.Apps)) + } + if len(s.HelmRepos) != 2 { + t.Errorf("build() - unexpected number of repos, wanted 2 got %d", len(s.Apps)) + } +} From 59ea1ad506e7560edd743495eb6fcfd2b6633d2d Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sun, 3 Apr 2022 00:21:59 +0100 Subject: [PATCH 0838/1127] refactor: improve plan message consistency --- internal/app/command.go | 2 ++ internal/app/decision_maker.go | 45 +++++++++++++++++------------ internal/app/decision_maker_test.go | 12 ++++++-- 3 files changed, 38 insertions(+), 21 deletions(-) diff --git a/internal/app/command.go b/internal/app/command.go index e8b660eb..a50ad9dc 100644 --- a/internal/app/command.go +++ b/internal/app/command.go @@ -70,6 +70,7 @@ func (c *Command) RetryExec(attempts int) (ExitStatus, error) { return c.RetryExecWithThreshold(attempts, 0) } +// RetryExecWithThreshold runs exec command with retry and allows specifying the threshold for the exit code to be considered erroneous func (c *Command) RetryExecWithThreshold(attempts, exitCodeThreshold int) (ExitStatus, error) { var ( result ExitStatus @@ -182,6 +183,7 @@ func (p CmdPipe) RetryExec(attempts int) (ExitStatus, error) { return p.RetryExecWithThreshold(attempts, 0) } +// RetryExecWithThreshold runs piped commands with retry and allows specifying the threshold for the exit code to be considered erroneous func (p CmdPipe) RetryExecWithThreshold(attempts, exitCodeThreshold int) (ExitStatus, error) { var ( result ExitStatus diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index 76133846..29e8d8b2 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -77,7 +77,9 @@ func (cs *currentState) makePlan(s *state) *plan { <-sem }() r.checkChartDepUpdate() - cs.decide(r, s.Namespaces[r.Namespace], p, c, s.Settings) + if err := cs.decide(r, s.Namespaces[r.Namespace], p, c, s.Settings); err != nil { + log.Fatal(err.Error()) + } }(r, s.ChartInfo[r.Chart][r.Version]) } wg.Wait() @@ -87,68 +89,75 @@ func (cs *currentState) makePlan(s *state) *plan { // decide makes a decision about what commands (actions) need to be executed // to make a release section of the desired state come true. -func (cs *currentState) decide(r *release, n *namespace, p *plan, c *chartInfo, settings config) { +func (cs *currentState) decide(r *release, n *namespace, p *plan, c *chartInfo, settings config) error { + prefix := "Release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ]" // check for presence in defined targets or groups if !r.isConsideredToRun() { if !settings.SkipIgnoredApps { - p.addDecision("Release [ "+r.Name+" ] ignored", r.Priority, ignored) + p.addDecision(prefix+" ignored", r.Priority, ignored) } - return + return nil } if r.isProtected(cs, n) { - p.addDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is PROTECTED. Operations are not allowed on this release until "+ + p.addDecision(prefix+" is PROTECTED. Operations are not allowed on this release until "+ "protection is removed.", r.Priority, noop) - return + return nil } if flags.destroy { if ok := cs.releaseExists(r, ""); ok { - p.addDecision("Release [ "+r.Name+" ] will be DELETED (destroy flag enabled).", r.Priority, remove) + p.addDecision(prefix+" will be DELETED (destroy flag enabled).", r.Priority, remove) r.uninstall(p) } - return + return nil } if !r.Enabled { if ok := cs.releaseExists(r, ""); ok { - p.addDecision("Release [ "+r.Name+" ] is desired to be DELETED.", r.Priority, remove) + p.addDecision(prefix+" is desired to be DELETED.", r.Priority, remove) r.uninstall(p) } else { - p.addDecision("Release [ "+r.Name+" ] disabled", r.Priority, noop) + p.addDecision(prefix+"is disabled", r.Priority, noop) } - return + return nil } switch cs.releaseStatus(r) { case helmStatusDeployed: if err := cs.inspectUpgradeScenario(r, p, c); err != nil { // upgrade or move - log.Fatal(err.Error()) + return err } case helmStatusUninstalled: r.rollback(cs, p) // rollback case helmStatusFailed: - p.addDecision("Release [ "+r.Name+" ] in namespace [ "+r.Namespace+" ] is in FAILED state. Upgrade is scheduled!", r.Priority, change) + p.addDecision(prefix+" is in FAILED state. Upgrade is scheduled!", r.Priority, change) r.upgrade(p) - return + return nil - case helmStatusPendingInstall, helmStatusPendingUpgrade, helmStatusPendingRollback, helmStatusUninstalling: - log.Fatal("Release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ] is in a pending (install/upgrade/rollback or uninstalling) state. " + + case helmStatusPendingInstall, helmStatusPendingUpgrade, helmStatusPendingRollback: + return fmt.Errorf(prefix + " is in a pending (install/upgrade/rollback) state. " + "This means application is being operated on outside of this Helmsman invocation's scope." + "Exiting, as this may cause issues when continuing...") + + case helmStatusUninstalling: + return fmt.Errorf(prefix + " is being uninstalled. " + + "Exiting, as this may cause issues when continuing...") + default: // If there is no release in the cluster with this name and in this namespace, then install it! if _, ok := cs.releases[r.key()]; !ok { - p.addDecision("Release [ "+r.Name+" ] version [ "+r.Version+" ] will be installed in [ "+r.Namespace+" ] namespace", r.Priority, create) + p.addDecision(prefix+" will be installed using version [ "+r.Version+" ]", r.Priority, create) r.install(p) } else { // A release with the same name and in the same namespace exists, but it has a different context label (managed by another DSF) - log.Fatal("Release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ] already exists but is not managed by the" + + return fmt.Errorf(prefix + " already exists but is not managed by the" + " current context. Applying changes will likely cause conflicts. Change the release name or namespace.") } } + return nil } // releaseStatus returns the status of a release in the Current State. diff --git a/internal/app/decision_maker_test.go b/internal/app/decision_maker_test.go index 6f148741..76156d87 100644 --- a/internal/app/decision_maker_test.go +++ b/internal/app/decision_maker_test.go @@ -120,7 +120,7 @@ func Test_inspectUpgradeScenario(t *testing.T) { // Assert if got != tt.want { - t.Errorf("decide() = %s, want %s", got, tt.want) + t.Errorf("inspectUpgradeScenario() = %s, want %s", got, tt.want) } }) } @@ -231,7 +231,10 @@ func Test_decide(t *testing.T) { settings := config{} outcome := plan{} // Act - cs.decide(tt.args.s.Apps[tt.args.r], tt.args.s.Namespaces[tt.args.s.Apps[tt.args.r].Namespace], &outcome, &chartInfo{}, settings) + err := cs.decide(tt.args.s.Apps[tt.args.r], tt.args.s.Namespaces[tt.args.s.Apps[tt.args.r].Namespace], &outcome, &chartInfo{}, settings) + if err != nil { + t.Errorf("decide() - unexpected error: %v", err) + } got := outcome.Decisions[0].Type t.Log(outcome.Decisions[0].Description) @@ -310,7 +313,10 @@ func Test_decide_skip_ignored_apps(t *testing.T) { outcome := plan{} // Act for _, r := range tt.args.rs { - cs.decide(tt.args.s.Apps[r], tt.args.s.Namespaces[tt.args.s.Apps[r].Namespace], &outcome, &chartInfo{}, settings) + err := cs.decide(tt.args.s.Apps[r], tt.args.s.Namespaces[tt.args.s.Apps[r].Namespace], &outcome, &chartInfo{}, settings) + if err != nil { + t.Errorf("decide() - unexpected error: %v", err) + } } got := outcome.Decisions t.Log(outcome.Decisions) From d46944a6e6fbff4dc2eb33329f38136ad50abf2a Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sun, 3 Apr 2022 00:46:16 +0100 Subject: [PATCH 0839/1127] feat: allow ignoring apps in pending states --- docs/desired_state_specification.md | 3 ++- internal/app/cli.go | 11 +++++++++++ internal/app/decision_maker.go | 8 ++++++++ internal/app/state.go | 1 + 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 19ca034b..cfede7d6 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -1,5 +1,5 @@ --- -version: v3.2.0 +version: v3.8.2 --- # Helmsman desired state specification @@ -119,6 +119,7 @@ The following options can be skipped if your kubectl context is already created - **globalHooks** : defines global lifecycle hooks to apply yaml manifest before and/or after different helmsman operations. Check [here](how_to/apps/lifecycle_hooks.md) for more details. - **globalMaxHistory** : defines the **global** maximum number of helm revisions state (secrets/configmap) to keep. Releases can override this global value by setting `maxHistory`. If both are not set or are set to `0`, it is defaulted to 10. - **skipIgnoredApps** : if set to true apps, that would normally be listed in the plan as `ignored`, will be skipped. They won't show up on the plan output and won't be considered in decisions. This is especially useful when using `-target` or `-group` flags with significant amount of apps where most of them show up as `ignored` in the plan output making it hard to read. +- **skipPendingApps** : if set to true apps that are in a pending (install/upgrade/rollback) state or being deleted, will be ignored, when set to false Helmsman will stop if apps are found in these states. Example: diff --git a/internal/app/cli.go b/internal/app/cli.go index 57a9a12f..97b5449d 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -102,6 +102,8 @@ type cli struct { kubectlDiff bool downloadCharts bool checkForChartUpdates bool + skipIgnoredApps bool + skipPendingApps bool } func printUsage() { @@ -153,6 +155,8 @@ func (c *cli) parse() { flag.BoolVar(&c.kubectlDiff, "kubectl-diff", false, "use kubectl diff instead of helm diff. Defalts to false if the helm diff plugin is installed.") flag.BoolVar(&c.checkForChartUpdates, "check-for-chart-updates", false, "compares the chart versions in the state file to the latest versions in the chart repositories and shows available updates") flag.BoolVar(&c.downloadCharts, "download-charts", false, "download charts referenced by URLs in the state file") + flag.BoolVar(&c.skipIgnoredApps, "skip-ignored", false, "skip ignored apps") + flag.BoolVar(&c.skipPendingApps, "skip-pending", false, "skip pending helm releases") flag.Usage = printUsage flag.Parse() @@ -281,6 +285,13 @@ func (c *cli) readState(s *state) error { s.build(c.files) s.disableUntargetedApps(c.group, c.target) + if c.skipIgnoredApps { + s.Settings.SkipIgnoredApps = true + } + if c.skipPendingApps { + s.Settings.SkipPendingApps = true + } + if !c.skipValidation { // validate the desired state content if len(c.files) > 0 { diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index 29e8d8b2..81c9d1bf 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -138,11 +138,19 @@ func (cs *currentState) decide(r *release, n *namespace, p *plan, c *chartInfo, return nil case helmStatusPendingInstall, helmStatusPendingUpgrade, helmStatusPendingRollback: + if settings.SkipPendingApps { + p.addDecision(prefix+"is in a pending state and will be ignored", r.Priority, ignored) + return nil + } return fmt.Errorf(prefix + " is in a pending (install/upgrade/rollback) state. " + "This means application is being operated on outside of this Helmsman invocation's scope." + "Exiting, as this may cause issues when continuing...") case helmStatusUninstalling: + if settings.SkipPendingApps { + p.addDecision(prefix+"is in being uninstalled and will be ignored", r.Priority, ignored) + return nil + } return fmt.Errorf(prefix + " is being uninstalled. " + "Exiting, as this may cause issues when continuing...") diff --git a/internal/app/state.go b/internal/app/state.go index 6cd02fc7..5d4caad4 100644 --- a/internal/app/state.go +++ b/internal/app/state.go @@ -30,6 +30,7 @@ type config struct { GlobalHooks map[string]interface{} `yaml:"globalHooks"` GlobalMaxHistory int `yaml:"globalMaxHistory"` SkipIgnoredApps bool `yaml:"skipIgnoredApps"` + SkipPendingApps bool `yaml:"skipPendingApps"` } // state type represents the desired state of applications on a k8s cluster. From 187ba0f2b23684a446daf09eeecfa4387e43b57b Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sun, 3 Apr 2022 01:18:17 +0100 Subject: [PATCH 0840/1127] feat: try waiting for apps in pending state Signed-off-by: Luis Davim --- internal/app/cli.go | 2 ++ internal/app/decision_maker.go | 26 +++++++++++++------------- internal/app/decision_maker_test.go | 4 ++-- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/internal/app/cli.go b/internal/app/cli.go index 97b5449d..4b92364c 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -104,6 +104,7 @@ type cli struct { checkForChartUpdates bool skipIgnoredApps bool skipPendingApps bool + pendingAppRetries int } func printUsage() { @@ -157,6 +158,7 @@ func (c *cli) parse() { flag.BoolVar(&c.downloadCharts, "download-charts", false, "download charts referenced by URLs in the state file") flag.BoolVar(&c.skipIgnoredApps, "skip-ignored", false, "skip ignored apps") flag.BoolVar(&c.skipPendingApps, "skip-pending", false, "skip pending helm releases") + flag.IntVar(&c.pendingAppRetries, "pending-max-retries", 0, "max number of retries for pending helm releases") flag.Usage = printUsage flag.Parse() diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index 81c9d1bf..38e5d493 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -2,9 +2,11 @@ package app import ( "fmt" + "math" "regexp" "strings" "sync" + "time" ) type currentState struct { @@ -77,7 +79,7 @@ func (cs *currentState) makePlan(s *state) *plan { <-sem }() r.checkChartDepUpdate() - if err := cs.decide(r, s.Namespaces[r.Namespace], p, c, s.Settings); err != nil { + if err := cs.decide(r, s.Namespaces[r.Namespace], p, c, s.Settings, flags.pendingAppRetries); err != nil { log.Fatal(err.Error()) } }(r, s.ChartInfo[r.Chart][r.Version]) @@ -89,7 +91,7 @@ func (cs *currentState) makePlan(s *state) *plan { // decide makes a decision about what commands (actions) need to be executed // to make a release section of the desired state come true. -func (cs *currentState) decide(r *release, n *namespace, p *plan, c *chartInfo, settings config) error { +func (cs *currentState) decide(r *release, n *namespace, p *plan, c *chartInfo, settings config, retries int) error { prefix := "Release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ]" // check for presence in defined targets or groups if !r.isConsideredToRun() { @@ -137,22 +139,20 @@ func (cs *currentState) decide(r *release, n *namespace, p *plan, c *chartInfo, r.upgrade(p) return nil - case helmStatusPendingInstall, helmStatusPendingUpgrade, helmStatusPendingRollback: + case helmStatusPendingInstall, helmStatusPendingUpgrade, helmStatusPendingRollback, helmStatusUninstalling: if settings.SkipPendingApps { p.addDecision(prefix+"is in a pending state and will be ignored", r.Priority, ignored) return nil } - return fmt.Errorf(prefix + " is in a pending (install/upgrade/rollback) state. " + - "This means application is being operated on outside of this Helmsman invocation's scope." + - "Exiting, as this may cause issues when continuing...") - - case helmStatusUninstalling: - if settings.SkipPendingApps { - p.addDecision(prefix+"is in being uninstalled and will be ignored", r.Priority, ignored) - return nil + if retries == 0 { + return fmt.Errorf(prefix + " is in a pending (install/upgrade/rollback or uninstalling) state. " + + "This means application is being operated on outside of this Helmsman invocation's scope." + + "Exiting, as this may cause issues when continuing...") + } else { + retries-- + time.Sleep(time.Duration(math.Pow(2, float64(2+retries))) * time.Second) + return cs.decide(r, n, p, c, settings, retries) } - return fmt.Errorf(prefix + " is being uninstalled. " + - "Exiting, as this may cause issues when continuing...") default: // If there is no release in the cluster with this name and in this namespace, then install it! diff --git a/internal/app/decision_maker_test.go b/internal/app/decision_maker_test.go index 76156d87..2c7d1a3b 100644 --- a/internal/app/decision_maker_test.go +++ b/internal/app/decision_maker_test.go @@ -231,7 +231,7 @@ func Test_decide(t *testing.T) { settings := config{} outcome := plan{} // Act - err := cs.decide(tt.args.s.Apps[tt.args.r], tt.args.s.Namespaces[tt.args.s.Apps[tt.args.r].Namespace], &outcome, &chartInfo{}, settings) + err := cs.decide(tt.args.s.Apps[tt.args.r], tt.args.s.Namespaces[tt.args.s.Apps[tt.args.r].Namespace], &outcome, &chartInfo{}, settings, 0) if err != nil { t.Errorf("decide() - unexpected error: %v", err) } @@ -313,7 +313,7 @@ func Test_decide_skip_ignored_apps(t *testing.T) { outcome := plan{} // Act for _, r := range tt.args.rs { - err := cs.decide(tt.args.s.Apps[r], tt.args.s.Namespaces[tt.args.s.Apps[r].Namespace], &outcome, &chartInfo{}, settings) + err := cs.decide(tt.args.s.Apps[r], tt.args.s.Namespaces[tt.args.s.Apps[r].Namespace], &outcome, &chartInfo{}, settings, 0) if err != nil { t.Errorf("decide() - unexpected error: %v", err) } From 6a6c927a8f3387de17e36f08e85d6354264c2503 Mon Sep 17 00:00:00 2001 From: Justin Menga Date: Mon, 18 Apr 2022 07:21:12 -0700 Subject: [PATCH 0841/1127] Add arm64 arch option to goreleaser --- .goreleaser.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.goreleaser.yml b/.goreleaser.yml index 20e0f8ac..68ede9d3 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -11,4 +11,5 @@ builds: - windows goarch: - amd64 + - arm64 main: ./cmd/helmsman/main.go \ No newline at end of file From c0dae9ecf32f16292485f38382f775e9f4e255ab Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sun, 24 Apr 2022 21:34:14 +0100 Subject: [PATCH 0842/1127] ci: enable dependabot --- .github/dependabot.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..920bbb7f --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: + - package-ecosystem: "gomod" + directory: "/" + schedule: + interval: "weekly" + day: "sunday" From 22a3b741cd8a5b02b182d4b47a4d961680c6d0df Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 24 Apr 2022 20:34:49 +0000 Subject: [PATCH 0843/1127] chore(deps): bump github.com/BurntSushi/toml from 1.0.0 to 1.1.0 Bumps [github.com/BurntSushi/toml](https://github.com/BurntSushi/toml) from 1.0.0 to 1.1.0. - [Release notes](https://github.com/BurntSushi/toml/releases) - [Commits](https://github.com/BurntSushi/toml/compare/v1.0.0...v1.1.0) --- updated-dependencies: - dependency-name: github.com/BurntSushi/toml dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index cb70419f..b7f6a828 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.14.0 github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect - github.com/BurntSushi/toml v1.0.0 + github.com/BurntSushi/toml v1.1.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 github.com/aws/aws-sdk-go v1.43.17 diff --git a/go.sum b/go.sum index fbec28bf..6c0ce7e5 100644 --- a/go.sum +++ b/go.sum @@ -74,8 +74,8 @@ github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU= -github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= +github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= From 0de5001b03fb1a5161904aeea3c35a81450d0037 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 24 Apr 2022 20:35:04 +0000 Subject: [PATCH 0844/1127] chore(deps): bump cloud.google.com/go/storage from 1.21.0 to 1.22.0 Bumps [cloud.google.com/go/storage](https://github.com/googleapis/google-cloud-go) from 1.21.0 to 1.22.0. - [Release notes](https://github.com/googleapis/google-cloud-go/releases) - [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/CHANGES.md) - [Commits](https://github.com/googleapis/google-cloud-go/compare/spanner/v1.21.0...spanner/v1.22.0) --- updated-dependencies: - dependency-name: cloud.google.com/go/storage dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 15 ++++++++------- go.sum | 40 ++++++++++++++++++---------------------- 2 files changed, 26 insertions(+), 29 deletions(-) diff --git a/go.mod b/go.mod index cb70419f..409cf5b5 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Praqma/helmsman go 1.17 require ( - cloud.google.com/go/storage v1.21.0 + cloud.google.com/go/storage v1.22.0 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.14.0 github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect @@ -19,10 +19,10 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/logrusorgru/aurora v2.0.3+incompatible golang.org/x/crypto v0.0.0-20220312131142-6068a2e6cfdc // indirect - golang.org/x/net v0.0.0-20220225172249-27dd8689420f + golang.org/x/net v0.0.0-20220325170049-de3da57026de golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a // indirect - google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6 // indirect - google.golang.org/protobuf v1.27.1 // indirect + google.golang.org/genproto v0.0.0-20220405205423-9d709892a2bf // indirect + google.golang.org/protobuf v1.28.0 // indirect gopkg.in/yaml.v2 v2.4.0 ) @@ -33,14 +33,15 @@ require ( github.com/golang-jwt/jwt/v4 v4.3.0 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-cmp v0.5.7 // indirect - github.com/googleapis/gax-go/v2 v2.1.1 // indirect + github.com/googleapis/gax-go/v2 v2.2.0 // indirect + github.com/googleapis/go-type-adapters v1.0.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/mattn/go-ieproxy v0.0.1 // indirect go.opencensus.io v0.23.0 // indirect - golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect + golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect - google.golang.org/api v0.71.0 // indirect + google.golang.org/api v0.74.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/grpc v1.45.0 // indirect ) diff --git a/go.sum b/go.sum index fbec28bf..58f5aa59 100644 --- a/go.sum +++ b/go.sum @@ -26,7 +26,6 @@ cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+Y cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= -cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U= cloud.google.com/go v0.100.2 h1:t9Iw5QH5v4XtlEQaCtUY7x6sCABps8sW0acw7e2WQ6Y= cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= @@ -36,13 +35,11 @@ cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUM cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= -cloud.google.com/go/compute v1.2.0/go.mod h1:xlogom/6gr8RJGBe7nT2eGsQYAFUbbv8dbC29qE3Xmw= cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= cloud.google.com/go/compute v1.5.0 h1:b1zWmYuuHz7gO9kDcM/EpHGr06UgsYNRpNJzI2kFiLM= cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/iam v0.1.1/go.mod h1:CKqrcnI/suGpybEHxZ7BMehL0oA4LpdyJdUlTl9jVMw= cloud.google.com/go/iam v0.3.0 h1:exkAomrVUuzx9kWFI1wm3KI0uoDeUFPB4kKGzx6x+Gc= cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= @@ -54,8 +51,8 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.21.0 h1:HwnT2u2D309SFDHQII6m18HlrCi3jAXhUMTLOWXYH14= -cloud.google.com/go/storage v1.21.0/go.mod h1:XmRlxkgPjlBONznT2dDUU/5XlpU2OjMnKuqnZI01LAA= +cloud.google.com/go/storage v1.22.0 h1:NUV0NNp9nkBuW66BFRLuMgldN60C57ET3dhbwLIYio8= +cloud.google.com/go/storage v1.22.0/go.mod h1:GbaLEoMqbVm6sx3Z0R++gSiBlgMv6yUi2q1DeGFKQgE= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= @@ -199,8 +196,11 @@ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= -github.com/googleapis/gax-go/v2 v2.1.1 h1:dp3bWCh+PPO1zjRRiCSczJav13sBvG4UhNyVTa1KqdU= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gax-go/v2 v2.2.0 h1:s7jOdKSaksJVOxE0Y/S32otcfiP+UQ0cL8/GTKaONwE= +github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= +github.com/googleapis/go-type-adapters v1.0.0 h1:9XdMn+d/G57qq1s8dNc5IesGCXHf6V2HZ2JwRxfA2tA= +github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -337,8 +337,9 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220325170049-de3da57026de h1:pZB1TWnKi+o4bENlbzAgLrEbY4RMYmUIRobMcSmfeYc= +golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -420,12 +421,11 @@ golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng= -golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886 h1:eJv7u3ksNXoLbGSKuv2s/SIO4tJVxc/A+MTpzxDgz/Q= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -528,13 +528,11 @@ google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqiv google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= -google.golang.org/api v0.64.0/go.mod h1:931CdxA8Rm4t6zqTFGSsgwbAEZ2+GMYurbndwSimebM= -google.golang.org/api v0.66.0/go.mod h1:I1dmXYpX7HGwz/ejRxwQp2qj5bFAz93HiCU1C1oYd9M= google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= -google.golang.org/api v0.69.0/go.mod h1:boanBiw+h5c3s+tBPgEzLDRHfFLWV0qXxRHz3ws7C80= google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= -google.golang.org/api v0.71.0 h1:SgWof18M8V2NylsX7bL4fM28j+nFdRopHZbdipaaw20= google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= +google.golang.org/api v0.74.0 h1:ExR2D+5TYIrMphWgs5JCgwRhEDlPDXXrLwHHMgPHTXE= +google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -582,6 +580,7 @@ google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= @@ -603,19 +602,15 @@ google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220111164026-67b88f271998/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220114231437-d2e6a121cae0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220201184016-50beb8ab5c44/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220211171837-173942840c17/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220216160803-4663080d8bc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6 h1:FglFEfyj61zP3c6LgjmVHxYxZWXYul9oiS1EZqD5gLc= google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220405205423-9d709892a2bf h1:JTjwKJX9erVpsw17w+OIPP7iAgEkN/r8urhWSunEDTs= +google.golang.org/genproto v0.0.0-20220405205423-9d709892a2bf/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -658,8 +653,9 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= From fa1eb54588c15238bffd5855c1f5d99d98aea396 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 24 Apr 2022 22:27:22 +0000 Subject: [PATCH 0845/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.43.17 to 1.43.45 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.43.17 to 1.43.45. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.43.17...v1.43.45) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index df56e6a9..85a1a4fa 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/BurntSushi/toml v1.1.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.43.17 + github.com/aws/aws-sdk-go v1.43.45 github.com/davecgh/go-spew v1.1.1 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/uuid v1.3.0 // indirect diff --git a/go.sum b/go.sum index 1ba5cf6f..c9785782 100644 --- a/go.sum +++ b/go.sum @@ -80,8 +80,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.43.17 h1:jDPBz1UuTxmyRo0eLgaRiro0fiI1zL7lkscqYxoEDLM= -github.com/aws/aws-sdk-go v1.43.17/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.43.45 h1:2708Bj4uV+ym62MOtBnErm/CDX61C4mFe9V2gXy1caE= +github.com/aws/aws-sdk-go v1.43.45/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= From 2580649cb9f545c85094e328e5e44bba77337ad2 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sat, 23 Apr 2022 12:54:23 +0100 Subject: [PATCH 0846/1127] fix: .env files should overwrite env vars Signed-off-by: Luis Davim --- docs/cmd_reference.md | 3 ++- internal/app/cli.go | 19 +++++++------------ 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/docs/cmd_reference.md b/docs/cmd_reference.md index 28ab73e3..0bf30f42 100644 --- a/docs/cmd_reference.md +++ b/docs/cmd_reference.md @@ -36,7 +36,8 @@ This lists available CMD options in Helmsman: apply the dry-run (do not update) option for helm commands. `-e value` - file(s) to load environment variables from (default .env), may be supplied more than once. + additional file(s) to load environment variables from, may be supplied more than once, it extends default .env file lookup, every next file takes precedence over previous ones in case of having the same environment variables defined. + If a `.env` file exists, it will be loaded by default, if additional env files are specified using the `-e` flag, the environment file will be loaded in order where the last file will take precedence. `-f value` desired state file name(s), may be supplied more than once to merge state files. diff --git a/internal/app/cli.go b/internal/app/cli.go index 4b92364c..4163d451 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -120,7 +120,7 @@ func printUsage() { func (c *cli) parse() { // parsing command line flags flag.Var(&c.files, "f", "desired state file name(s), may be supplied more than once to merge state files") - flag.Var(&c.envFiles, "e", "file(s) to load environment variables from (default .env), may be supplied more than once") + flag.Var(&c.envFiles, "e", "additional file(s) to load environment variables from, may be supplied more than once, it extends default .env file lookup, every next file takes precedence over previous ones in case of having the same environment variables defined") flag.Var(&c.target, "target", "limit execution to specific app.") flag.Var(&c.group, "group", "limit execution to specific group of apps.") flag.IntVar(&c.diffContext, "diff-context", -1, "number of lines of context to show around changes in helm diff output") @@ -244,20 +244,15 @@ func (c *cli) parse() { // readState gets the desired state from files func (c *cli) readState(s *state) error { - // read the env file - if len(c.envFiles) == 0 { - if _, err := os.Stat(".env"); err == nil { - err = godotenv.Load() - if err != nil { - return fmt.Errorf("error loading .env file: %w", err) - } - } + // read the env file if it exists + if _, err := os.Stat(".env"); err == nil { + c.envFiles = append([]string{".env"}, c.envFiles...) } - for _, e := range c.envFiles { - err := godotenv.Load(e) + if len(c.envFiles) != 0 { + err := godotenv.Overload(c.envFiles...) if err != nil { - return fmt.Errorf("error loading %s env file: %w", e, err) + return fmt.Errorf("error loading env file: %w", err) } } From e6fe19bdf6d16f44777281acca512c3986b1ea85 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Wed, 27 Apr 2022 10:02:50 +0100 Subject: [PATCH 0847/1127] chore: prepare release v3.9.0 --- .version | 2 +- README.md | 6 +++--- internal/app/main.go | 2 +- release-notes.md | 22 +++++++++++++++++++--- 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/.version b/.version index 32f8572e..5f22788f 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.8.1 +v3.9.0 diff --git a/README.md b/README.md index c2e90f4f..97278882 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.8.1&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.9.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -76,9 +76,9 @@ Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the d ```sh # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.8.1/helmsman_3.8.1_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.9.0/helmsman_3.9.0_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.8.1/helmsman_3.8.1_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.9.0/helmsman_3.9.0_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/internal/app/main.go b/internal/app/main.go index ec40e38b..1db9ff32 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.8.1" + appVersion = "v3.9.0" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index 7fb4eff2..3b01e418 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,6 +1,22 @@ -# v3.8.1 +# v3.9.0 + +## New features + +- Added Option for checking for available updates for app charts (#640) +- Added option for waiting for pending helm releases (#646) +- Added `arm64` builds (#642) (#647) ## Fixes and improvements -- fix: kubectl diff may return 1 on success (#637) -- refactor: add missing error checks (#637) +- Updated dependencies (#641) +- Avoid the extra chart download step for OCI charts (#643) +- Code refactoring (#644) +- Enabled automatic dependency updates through dependabot + +## Breaking changes ⚠ + +- env files loading is now more intuitive (#649) + - Before the default .env file would only be loaded if no env files were explicitly passed through the -e flag, now it will always be loaded first if present + - Before loading env files would not overwrite any env variable that had already been set before, now it does so when loading multiple files if a variable is set more than once the value from the last file to be loaded will take precedence. + - Before the first file would take precedence, now, the last one will. + From dba4c31b8a076f50588f3493925e32422d62f287 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 May 2022 14:20:30 +0000 Subject: [PATCH 0848/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.43.45 to 1.44.4 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.43.45 to 1.44.4. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.43.45...v1.44.4) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 85a1a4fa..243ccfe8 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/BurntSushi/toml v1.1.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.43.45 + github.com/aws/aws-sdk-go v1.44.4 github.com/davecgh/go-spew v1.1.1 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/uuid v1.3.0 // indirect diff --git a/go.sum b/go.sum index c9785782..91504681 100644 --- a/go.sum +++ b/go.sum @@ -80,8 +80,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.43.45 h1:2708Bj4uV+ym62MOtBnErm/CDX61C4mFe9V2gXy1caE= -github.com/aws/aws-sdk-go v1.43.45/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.4 h1:ePN0CVJMdiz2vYUcJH96eyxRrtKGSDMgyhP6rah2OgE= +github.com/aws/aws-sdk-go v1.44.4/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= From 060bc1ed69cf880ed46b539f1c7c4e3d8c91ceec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 May 2022 14:20:40 +0000 Subject: [PATCH 0849/1127] chore(deps): bump github.com/Azure/azure-storage-blob-go Bumps [github.com/Azure/azure-storage-blob-go](https://github.com/Azure/azure-storage-blob-go) from 0.14.0 to 0.15.0. - [Release notes](https://github.com/Azure/azure-storage-blob-go/releases) - [Changelog](https://github.com/Azure/azure-storage-blob-go/blob/master/ChangeLog.md) - [Commits](https://github.com/Azure/azure-storage-blob-go/compare/v0.14.0...v0.15.0) --- updated-dependencies: - dependency-name: github.com/Azure/azure-storage-blob-go dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 85a1a4fa..90eee932 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.17 require ( cloud.google.com/go/storage v1.22.0 github.com/Azure/azure-pipeline-go v0.2.3 - github.com/Azure/azure-storage-blob-go v0.14.0 + github.com/Azure/azure-storage-blob-go v0.15.0 github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect github.com/BurntSushi/toml v1.1.0 github.com/Masterminds/semver v1.5.0 diff --git a/go.sum b/go.sum index c9785782..b2464350 100644 --- a/go.sum +++ b/go.sum @@ -56,8 +56,8 @@ cloud.google.com/go/storage v1.22.0/go.mod h1:GbaLEoMqbVm6sx3Z0R++gSiBlgMv6yUi2q dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= -github.com/Azure/azure-storage-blob-go v0.14.0 h1:1BCg74AmVdYwO3dlKwtFU1V0wU2PZdREkXvAmZJRUlM= -github.com/Azure/azure-storage-blob-go v0.14.0/go.mod h1:SMqIBi+SuiQH32bvyjngEewEeXoPfKMgWlBDaYf6fck= +github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk= +github.com/Azure/azure-storage-blob-go v0.15.0/go.mod h1:vbjsVbX0dlxnRc4FFMPsS9BsJWPcne7GB7onqlPvz58= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= @@ -261,6 +261,7 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220312131142-6068a2e6cfdc h1:i6Z9eOQAdM7lvsbkT3fwFNtSAAC+A59TYilFj53HW+E= golang.org/x/crypto v0.0.0-20220312131142-6068a2e6cfdc/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -335,6 +336,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= @@ -396,7 +398,6 @@ golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= From 43c5fb39ae36f9b469c08e8d5b559498c26c6e71 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 8 May 2022 14:22:31 +0000 Subject: [PATCH 0850/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.4 to 1.44.9 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.4 to 1.44.9. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.4...v1.44.9) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b94ae5c8..26d5ac1b 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/BurntSushi/toml v1.1.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.4 + github.com/aws/aws-sdk-go v1.44.9 github.com/davecgh/go-spew v1.1.1 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/uuid v1.3.0 // indirect diff --git a/go.sum b/go.sum index ee7fa3eb..4baf14e5 100644 --- a/go.sum +++ b/go.sum @@ -80,8 +80,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.4 h1:ePN0CVJMdiz2vYUcJH96eyxRrtKGSDMgyhP6rah2OgE= -github.com/aws/aws-sdk-go v1.44.4/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.9 h1:s3lsEFbc8i7ghQmcEpcdyvoO/WMwyCVa9pUq3Lq//Ok= +github.com/aws/aws-sdk-go v1.44.9/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= From 4583158bf6ccb606f466ac38cad4b638fc901b5f Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Fri, 13 May 2022 11:18:43 +0100 Subject: [PATCH 0851/1127] fix: avoid loading the .env file twice --- internal/app/cli.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/app/cli.go b/internal/app/cli.go index 4163d451..93cce664 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -246,7 +246,9 @@ func (c *cli) parse() { func (c *cli) readState(s *state) error { // read the env file if it exists if _, err := os.Stat(".env"); err == nil { - c.envFiles = append([]string{".env"}, c.envFiles...) + if !stringInSlice(".env", c.envFiles) { + c.envFiles = append([]string{".env"}, c.envFiles...) + } } if len(c.envFiles) != 0 { From 78b6069caa1e60af9f546fe6a83bd8f5193cbb50 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sat, 14 May 2022 14:13:11 +0100 Subject: [PATCH 0852/1127] feat: recursively expad env variables Signed-off-by: Luis Davim --- internal/app/cli.go | 9 ++---- internal/app/state_files.go | 2 +- internal/app/utils.go | 39 +++++++++++++++++++++++--- internal/app/utils_test.go | 56 +++++++++++++++++++++++++++++++++++++ 4 files changed, 94 insertions(+), 12 deletions(-) diff --git a/internal/app/cli.go b/internal/app/cli.go index 93cce664..f016c386 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -6,8 +6,6 @@ import ( "os" "sort" "strings" - - "github.com/joho/godotenv" ) const ( @@ -251,11 +249,8 @@ func (c *cli) readState(s *state) error { } } - if len(c.envFiles) != 0 { - err := godotenv.Overload(c.envFiles...) - if err != nil { - return fmt.Errorf("error loading env file: %w", err) - } + if err := prepareEnv(c.envFiles); err != nil { + return err } // wipe & create a temporary directory diff --git a/internal/app/state_files.go b/internal/app/state_files.go index bb06d1cc..d501248e 100644 --- a/internal/app/state_files.go +++ b/internal/app/state_files.go @@ -189,7 +189,7 @@ func (s *state) expand(relativeToFile string) { if r.Chart != "" { var download bool // support env vars in path - r.Chart = os.ExpandEnv(r.Chart) + r.Chart = os.Expand(r.Chart, getEnv) repoName := strings.Split(r.Chart, "/")[0] _, isRepo := s.HelmRepos[repoName] isRepo = isRepo || stringInSlice(repoName, s.PreconfiguredHelmRepos) diff --git a/internal/app/utils.go b/internal/app/utils.go index a5573600..87bccc4b 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -18,6 +18,8 @@ import ( "unicode/utf8" "github.com/Masterminds/semver" + "github.com/joho/godotenv" + "github.com/Praqma/helmsman/internal/aws" "github.com/Praqma/helmsman/internal/azure" "github.com/Praqma/helmsman/internal/gcs" @@ -140,16 +142,45 @@ func readFile(filepath string) string { return string(data) } +// getEnv fetches the value for an environment variable +// recusively expanding the variable's value +func getEnv(key string) string { + value := os.Getenv(key) + for envVar.MatchString(value) { + value = os.ExpandEnv(value) + } + return value +} + +// prepareEnv loads dotenv files and recusively expands all environment variables +func prepareEnv(envFiles []string) error { + if len(envFiles) != 0 { + err := godotenv.Overload(envFiles...) + if err != nil { + return fmt.Errorf("error loading env file: %w", err) + } + } + for _, e := range os.Environ() { + if !strings.Contains(e, "$") { + continue + } + e = os.Expand(e, getEnv) + pair := strings.SplitN(e, "=", 2) + os.Setenv(pair[0], pair[1]) + } + return nil +} + // substituteEnv checks if a string has an env variable (contains '$'), then it returns its value // if the env variable is empty or unset, an empty string is returned // if the string does not contain '$', it is returned as is. -func substituteEnv(name string) string { - if strings.Contains(name, "$") { +func substituteEnv(str string) string { + if strings.Contains(str, "$") { // add $$ escaping for $ strings os.Setenv("HELMSMAN_DOLLAR", "$") - return os.ExpandEnv(strings.ReplaceAll(name, "$$", "${HELMSMAN_DOLLAR}")) + return os.ExpandEnv(strings.ReplaceAll(str, "$$", "${HELMSMAN_DOLLAR}")) } - return name + return str } // validateEnvVars parses a string line-by-line and detect env variables in diff --git a/internal/app/utils_test.go b/internal/app/utils_test.go index 1e282016..57a0228f 100644 --- a/internal/app/utils_test.go +++ b/internal/app/utils_test.go @@ -5,6 +5,62 @@ import ( "testing" ) +func TestGetEnv(t *testing.T) { + tests := []struct { + name string + expected func(key string) string + key string + }{ + { + name: "direct", + key: "BAR", + expected: func(key string) string { + expected := "myValue" + os.Setenv(key, expected) + return expected + }, + }, + { + name: "string_with_var", + key: "BAR", + expected: func(key string) string { + expected := "contains myValue" + os.Setenv("FOO", "myValue") + os.Setenv(key, "contains ${FOO}") + return expected + }, + }, + { + name: "nested_one_level", + key: "BAR", + expected: func(key string) string { + expected := "myValue" + os.Setenv("FOO", expected) + os.Setenv(key, "${FOO}") + return expected + }, + }, + { + name: "nested_two_levels", + key: "BAR", + expected: func(key string) string { + expected := "myValue" + os.Setenv("FOZ", expected) + os.Setenv("FOO", "$FOZ") + os.Setenv(key, "${FOO}") + return expected + }, + }, + } + for _, tt := range tests { + expected := tt.expected(tt.key) + value := getEnv(tt.key) + if value != expected { + t.Errorf("getEnv() - unexpected value: wanted: %s got: %s", expected, value) + } + } +} + func TestOciRefToFilename(t *testing.T) { tests := []struct { name string From 383006934c506ea811226e0d5fd6f59497b243a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 15 May 2022 14:21:20 +0000 Subject: [PATCH 0853/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.9 to 1.44.14 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.9 to 1.44.14. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.9...v1.44.14) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 26d5ac1b..f900bcc1 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/BurntSushi/toml v1.1.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.9 + github.com/aws/aws-sdk-go v1.44.14 github.com/davecgh/go-spew v1.1.1 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/uuid v1.3.0 // indirect diff --git a/go.sum b/go.sum index 4baf14e5..b95bcae8 100644 --- a/go.sum +++ b/go.sum @@ -80,8 +80,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.9 h1:s3lsEFbc8i7ghQmcEpcdyvoO/WMwyCVa9pUq3Lq//Ok= -github.com/aws/aws-sdk-go v1.44.9/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.14 h1:qd7/muV1rElsbvkK9D1nHUzBoDlEw2etfeo4IE82eSQ= +github.com/aws/aws-sdk-go v1.44.14/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= From d3bde9bbbc2bbf16bed6bfc37e1ae1764df3da04 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 17 May 2022 22:56:37 +0100 Subject: [PATCH 0854/1127] release: v3.9.0 --- .version | 2 +- README.md | 6 +++--- internal/app/main.go | 2 +- release-notes.md | 18 +++--------------- 4 files changed, 8 insertions(+), 20 deletions(-) diff --git a/.version b/.version index 5f22788f..a4dae046 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.9.0 +v3.9.1 diff --git a/README.md b/README.md index 97278882..a4244907 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.9.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.9.1&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -76,9 +76,9 @@ Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the d ```sh # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.9.0/helmsman_3.9.0_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.9.1/helmsman_3.9.1_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.9.0/helmsman_3.9.0_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.9.1/helmsman_3.9.1_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/internal/app/main.go b/internal/app/main.go index 1db9ff32..c99dda87 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.9.0" + appVersion = "v3.9.1" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index 3b01e418..4653d1d1 100644 --- a/release-notes.md +++ b/release-notes.md @@ -2,21 +2,9 @@ ## New features -- Added Option for checking for available updates for app charts (#640) -- Added option for waiting for pending helm releases (#646) -- Added `arm64` builds (#642) (#647) +- Recursively expand environment variables (#657) ## Fixes and improvements -- Updated dependencies (#641) -- Avoid the extra chart download step for OCI charts (#643) -- Code refactoring (#644) -- Enabled automatic dependency updates through dependabot - -## Breaking changes ⚠ - -- env files loading is now more intuitive (#649) - - Before the default .env file would only be loaded if no env files were explicitly passed through the -e flag, now it will always be loaded first if present - - Before loading env files would not overwrite any env variable that had already been set before, now it does so when loading multiple files if a variable is set more than once the value from the last file to be loaded will take precedence. - - Before the first file would take precedence, now, the last one will. - +- Updated dependencies to their latest versions (#653; #654; #655; #658) +- avoid loading the `.env` file twice From bba659fcb0c6155b9b45bff9294ad5221e4a8964 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 17 May 2022 22:58:59 +0100 Subject: [PATCH 0855/1127] docs: fix release notes title --- release-notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release-notes.md b/release-notes.md index 4653d1d1..0466fc94 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,4 +1,4 @@ -# v3.9.0 +# v3.9.1 ## New features From 3eea25de110cf9500862dd036858cb1c2d81a76c Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Thu, 19 May 2022 14:32:29 +0200 Subject: [PATCH 0856/1127] Add -exclude-target and -exclude-groups cli options --- docs/cmd_reference.md | 6 ++ docs/how_to/README.md | 1 + .../exclude-apps-or-groups-from-deployment.md | 50 +++++++++++ internal/app/cli.go | 6 +- internal/app/decision_maker_test.go | 85 +++++++++++++++---- internal/app/state.go | 20 ++++- internal/app/state_test.go | 2 +- 7 files changed, 149 insertions(+), 21 deletions(-) create mode 100644 docs/how_to/misc/exclude-apps-or-groups-from-deployment.md diff --git a/docs/cmd_reference.md b/docs/cmd_reference.md index 0bf30f42..92e10036 100644 --- a/docs/cmd_reference.md +++ b/docs/cmd_reference.md @@ -96,9 +96,15 @@ This lists available CMD options in Helmsman: `--target` limit execution to specific app. + `--exclude-target` + exclude specific app from execution. + `--group` limit execution to specific group of apps. + `--exclude-group` + exclude specific group of apps from execution. + `--update-deps` run 'helm dep up' for local chart diff --git a/docs/how_to/README.md b/docs/how_to/README.md index ffa85185..0ea15853 100644 --- a/docs/how_to/README.md +++ b/docs/how_to/README.md @@ -54,5 +54,6 @@ It is recommended that you also check the [DSF spec](../desired_state_specificat - [Merge multiple desired state files](misc/merge_desired_state_files.md) - [Limit Helmsman deployment to specific apps](misc/limit-deployment-to-specific-apps.md) - [Limit Helmsman deployment to specific group of apps](misc/limit-deployment-to-specific-group-of-apps.md) + - [Exclude apps or groups from Helmsman deployment](misc/exclude-apps-or-groups-from-deployment.md) - [Use hiera-eyaml as secrets encryption backend](settings/use-hiera-eyaml-as-secrets-encryption.md) - [Use DRY-ed code](misc/use-dry-code.md) diff --git a/docs/how_to/misc/exclude-apps-or-groups-from-deployment.md b/docs/how_to/misc/exclude-apps-or-groups-from-deployment.md new file mode 100644 index 00000000..3c84a815 --- /dev/null +++ b/docs/how_to/misc/exclude-apps-or-groups-from-deployment.md @@ -0,0 +1,50 @@ +--- +version: v3.10.0 +--- + +# Exclude specific apps or groups from execution + +Starting from v3.10.0, Helmsman allows you to pass the `--exclude-target` or `--exclude-group` flag multiple times +to specify which apps or groups should be excluded from execution. +Thanks to this one can exclude specific applications among all defined for an environment. + +## Example + +Having environment defined with such apps: + +example.yaml: + +```yaml +# ... +apps: + jenkins: + namespace: "staging" # maps to the namespace as defined in namespaces above + enabled: true # change to false if you want to delete this app release empty: false: + chart: "jenkins/jenkins" # changing the chart name means delete and recreate this chart + version: "2.15.1" # chart version + + artifactory: + namespace: "production" # maps to the namespace as defined in namespaces above + enabled: true # change to false if you want to delete this app release empty: false: + chart: "jfrog/artifactory" # changing the chart name means delete and recreate this chart + version: "11.4.2" # chart version +# ... +``` + +running Helmsman with `-f example.yaml` would result in checking state and invoking deployment for both jenkins and artifactory application. + +With `--exclude-target` flag in command like + +```shell +helmsman -f example.yaml --exclude-target artifactory ... +``` + +one can execute Helmsman's environment defined with example.yaml limited to only one `jenkins` app by excluding second one - `artifactory` from the execution. + +Multiple applications can be excluded with `--exclude-target`, like + +```shell +helmsman -f example.yaml --exclude-target artifactory --exclude-target jenkins ... +``` + +Same rules apply for `--exclude-groups`. diff --git a/internal/app/cli.go b/internal/app/cli.go index f016c386..c016b5f2 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -68,7 +68,9 @@ type cli struct { spec string envFiles stringArray target stringArray + targetExcluded stringArray group stringArray + groupExcluded stringArray kubeconfig string apply bool destroy bool @@ -121,6 +123,8 @@ func (c *cli) parse() { flag.Var(&c.envFiles, "e", "additional file(s) to load environment variables from, may be supplied more than once, it extends default .env file lookup, every next file takes precedence over previous ones in case of having the same environment variables defined") flag.Var(&c.target, "target", "limit execution to specific app.") flag.Var(&c.group, "group", "limit execution to specific group of apps.") + flag.Var(&c.targetExcluded, "exclude-target", "exclude specific app from execution.") + flag.Var(&c.groupExcluded, "exclude-group", "exclude specific group of apps from execution.") flag.IntVar(&c.diffContext, "diff-context", -1, "number of lines of context to show around changes in helm diff output") flag.IntVar(&c.parallel, "p", 1, "max number of concurrent helm releases to run") flag.StringVar(&c.spec, "spec", "", "specification file name, contains locations of desired state files to be merged") @@ -277,7 +281,7 @@ func (c *cli) readState(s *state) error { // read the TOML/YAML desired state file s.build(c.files) - s.disableUntargetedApps(c.group, c.target) + s.disableApps(c.group, c.target, c.groupExcluded, c.targetExcluded) if c.skipIgnoredApps { s.Settings.SkipIgnoredApps = true diff --git a/internal/app/decision_maker_test.go b/internal/app/decision_maker_test.go index 2c7d1a3b..75165e47 100644 --- a/internal/app/decision_maker_test.go +++ b/internal/app/decision_maker_test.go @@ -132,14 +132,18 @@ func Test_decide(t *testing.T) { s *state } tests := []struct { - name string - targetFlag []string - args args - want decisionType + name string + targetFlag []string + excludedTargetFlag []string + excludedGroupFlag []string + args args + want decisionType }{ { - name: "decide() - targetMap does not contain this service - skip", - targetFlag: []string{"someOtherRelease"}, + name: "decide() - targetMap does not contain this service - skip", + targetFlag: []string{"someOtherRelease"}, + excludedTargetFlag: []string{}, + excludedGroupFlag: []string{}, args: args{ r: "release1", s: &state{ @@ -155,8 +159,10 @@ func Test_decide(t *testing.T) { want: ignored, }, { - name: "decide() - targetMap does not contain this service either - skip", - targetFlag: []string{"someOtherRelease", "norThisOne"}, + name: "decide() - targetMap does not contain this service either - skip", + targetFlag: []string{"someOtherRelease", "norThisOne"}, + excludedTargetFlag: []string{}, + excludedGroupFlag: []string{}, args: args{ r: "release1", s: &state{ @@ -172,8 +178,10 @@ func Test_decide(t *testing.T) { want: ignored, }, { - name: "decide() - targetMap is empty - will install", - targetFlag: []string{}, + name: "decide() - targetMap is empty - will install", + targetFlag: []string{}, + excludedTargetFlag: []string{}, + excludedGroupFlag: []string{}, args: args{ r: "release4", s: &state{ @@ -189,8 +197,10 @@ func Test_decide(t *testing.T) { want: create, }, { - name: "decide() - targetMap is exactly this service - will install", - targetFlag: []string{"thisRelease"}, + name: "decide() - targetMap is exactly this service - will install", + targetFlag: []string{"thisRelease"}, + excludedTargetFlag: []string{}, + excludedGroupFlag: []string{}, args: args{ r: "thisRelease", s: &state{ @@ -206,8 +216,10 @@ func Test_decide(t *testing.T) { want: create, }, { - name: "decide() - targetMap contains this service - will install", - targetFlag: []string{"notThisOne", "thisRelease"}, + name: "decide() - targetMap contains this service - will install", + targetFlag: []string{"notThisOne", "thisRelease"}, + excludedTargetFlag: []string{}, + excludedGroupFlag: []string{}, args: args{ r: "thisRelease", s: &state{ @@ -222,12 +234,51 @@ func Test_decide(t *testing.T) { }, want: create, }, + { + name: "decide() - targetMap contains this service, but it's excluded by name - will not install", + targetFlag: []string{"notThisOne", "thisRelease"}, + excludedTargetFlag: []string{"thisRelease"}, + excludedGroupFlag: []string{}, + args: args{ + r: "thisRelease", + s: &state{ + Apps: map[string]*release{ + "thisRelease": { + Name: "thisRelease", + Namespace: "namespace", + Enabled: true, + }, + }, + }, + }, + want: ignored, + }, + { + name: "decide() - targetMap contains this service, but its group is excluded - will not install", + targetFlag: []string{"notThisOne", "thisRelease"}, + excludedTargetFlag: []string{"thisRelease"}, + excludedGroupFlag: []string{"myGroup"}, + args: args{ + r: "thisRelease", + s: &state{ + Apps: map[string]*release{ + "thisRelease": { + Name: "thisRelease", + Namespace: "namespace", + Enabled: true, + Group: "myGroup", + }, + }, + }, + }, + want: ignored, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { cs := newCurrentState() - tt.args.s.disableUntargetedApps([]string{}, tt.targetFlag) + tt.args.s.disableApps([]string{}, tt.targetFlag, tt.excludedGroupFlag, tt.excludedTargetFlag) settings := config{} outcome := plan{} // Act @@ -306,7 +357,7 @@ func Test_decide_skip_ignored_apps(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { cs := newCurrentState() - tt.args.s.disableUntargetedApps([]string{}, tt.targetFlag) + tt.args.s.disableApps([]string{}, tt.targetFlag, []string{}, []string{}) settings := config{ SkipIgnoredApps: true, } @@ -391,7 +442,7 @@ func Test_decide_group(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - tt.args.s.disableUntargetedApps(tt.groupFlag, []string{}) + tt.args.s.disableApps(tt.groupFlag, []string{}, []string{}, []string{}) if len(tt.args.s.TargetMap) != len(tt.want) { t.Errorf("decide() = %d, want %d", len(tt.args.s.TargetMap), len(tt.want)) } diff --git a/internal/app/state.go b/internal/app/state.go index 5d4caad4..0171af05 100644 --- a/internal/app/state.go +++ b/internal/app/state.go @@ -295,8 +295,24 @@ func (s *state) overrideAppsNamespace(newNs string) { } } -// get only those Apps that exist in TargetMap -func (s *state) disableUntargetedApps(groups, targets []string) { +// disable Apps defined as excluded by either their name or their group +// then get only those Apps that exist in TargetMap +func (s *state) disableApps(groups, targets, groupsExcluded, targetsExcluded []string) { +excludeAppsLoop: + for appName, app := range s.Apps { + for _, groupExcluded := range groupsExcluded { + if app.Group == groupExcluded { + app.Disable() + continue excludeAppsLoop + } + } + for _, targetExcluded := range targetsExcluded { + if appName == targetExcluded { + app.Disable() + continue excludeAppsLoop + } + } + } if s.TargetMap == nil { s.TargetMap = make(map[string]bool) } diff --git a/internal/app/state_test.go b/internal/app/state_test.go index 26cbe03d..e827a8bf 100644 --- a/internal/app/state_test.go +++ b/internal/app/state_test.go @@ -509,7 +509,7 @@ func Test_state_getReleaseChartsInfo(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { stt := &state{Apps: tt.args.apps} - stt.disableUntargetedApps(tt.groupFlag, tt.targetFlag) + stt.disableApps(tt.groupFlag, tt.targetFlag, []string{}, []string{}) err := stt.getReleaseChartsInfo() switch err.(type) { case nil: From a8b436b86e09c125d1b98594a92f163c2bf56e15 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Fri, 20 May 2022 11:02:39 +0200 Subject: [PATCH 0857/1127] Update helm, helm plugins, alpine and golang versions --- .circleci/config.yml | 2 +- Dockerfile | 28 ++++++++++++++++++---------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 05324173..5f8ca953 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -36,7 +36,7 @@ jobs: - run: name: build docker images and push them to dockerhub command: | - helm_versions=( "v3.6.3" "v3.7.2" "v3.8.1" ) + helm_versions=( "v3.7.2" "v3.8.2" "v3.9.0" ) TAG=$(git describe --abbrev=0 --tags) docker login -u $DOCKER_USER -p $DOCKER_PASS diff --git a/Dockerfile b/Dockerfile index f1a7dacb..b4633221 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,19 +1,28 @@ -ARG GO_VERSION="1.17.0" -ARG ALPINE_VERSION="3.14" -ARG GLOBAL_KUBE_VERSION="v1.22.1" -ARG GLOBAL_HELM_VERSION="v3.8.1" -ARG GLOBAL_HELM_DIFF_VERSION="v3.1.3" -ARG GLOBAL_SOPS_VERSION="v3.7.1" +ARG GO_VERSION="1.17.10" +ARG ALPINE_VERSION="3.15" +ARG GLOBAL_KUBE_VERSION="v1.23.6" +ARG GLOBAL_HELM_VERSION="v3.9.0" +ARG GLOBAL_HELM_DIFF_VERSION="v3.5.0" +ARG GLOBAL_HELM_GCS_VERSION="0.3.21" +ARG GLOBAL_HELM_S3_VERSION="v0.10.0" +ARG GLOBAL_HELM_SECRETS_VERSION="v3.13.0" +ARG GLOBAL_SOPS_VERSION="v3.7.3" ### Helm Installer ### FROM alpine:${ALPINE_VERSION} as helm-installer ARG GLOBAL_KUBE_VERSION ARG GLOBAL_HELM_VERSION ARG GLOBAL_HELM_DIFF_VERSION +ARG GLOBAL_HELM_GCS_VERSION +ARG GLOBAL_HELM_S3_VERSION +ARG GLOBAL_HELM_SECRETS_VERSION ARG GLOBAL_SOPS_VERSION ENV KUBE_VERSION=$GLOBAL_KUBE_VERSION ENV HELM_VERSION=$GLOBAL_HELM_VERSION ENV HELM_DIFF_VERSION=$GLOBAL_HELM_DIFF_VERSION +ENV HELM_GCS_VERSION=$GLOBAL_HELM_GCS_VERSION +ENV HELM_S3_VERSION=$GLOBAL_HELM_S3_VERSION +ENV HELM_SECRETS_VERSION=$GLOBAL_HELM_SECRETS_VERSION ENV SOPS_VERSION=$GLOBAL_SOPS_VERSION RUN apk add --update --no-cache ca-certificates git openssh openssl ruby curl wget tar gzip make bash @@ -28,11 +37,10 @@ RUN curl --retry 5 -Lk https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar. RUN mv /tmp/linux-amd64/helm /usr/local/bin/helm && rm -rf /tmp/linux-amd64 RUN chmod +x /usr/local/bin/helm -RUN helm plugin install https://github.com/hypnoglow/helm-s3.git -RUN helm plugin install https://github.com/nouney/helm-gcs +RUN helm plugin install https://github.com/hypnoglow/helm-s3.git --version ${HELM_S3_VERSION} +RUN helm plugin install https://github.com/nouney/helm-gcs --version ${HELM_GCS_VERSION} RUN helm plugin install https://github.com/databus23/helm-diff --version ${HELM_DIFF_VERSION} -RUN helm plugin install https://github.com/jkroepke/helm-secrets -RUN rm -r /tmp/helm-diff /tmp/helm-diff.tgz +RUN helm plugin install https://github.com/jkroepke/helm-secrets --version ${HELM_SECRETS_VERSION} ### Go Builder & Tester ### FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} as builder From 6e03d14cca754e2a458359a1db66413d4271ee49 Mon Sep 17 00:00:00 2001 From: Alex Sears Date: Fri, 20 May 2022 11:41:33 -0400 Subject: [PATCH 0858/1127] Add error handling to state building from files --- internal/app/cli.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/app/cli.go b/internal/app/cli.go index c016b5f2..917db710 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -262,7 +262,6 @@ func (c *cli) readState(s *state) error { _ = os.MkdirAll(tempFilesDir, 0o755) if len(c.spec) > 0 { - sp := new(StateFiles) if err := sp.specFromYAML(c.spec); err != nil { return fmt.Errorf("error parsing spec file: %w", err) @@ -280,7 +279,10 @@ func (c *cli) readState(s *state) error { } // read the TOML/YAML desired state file - s.build(c.files) + if err := s.build(c.files); err != nil { + return fmt.Errorf("error building the state from files: %w", err) + } + s.disableApps(c.group, c.target, c.groupExcluded, c.targetExcluded) if c.skipIgnoredApps { From 50da751100da919cef2e18630c2af02f421b3368 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 22 May 2022 14:23:24 +0000 Subject: [PATCH 0859/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.14 to 1.44.19 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.14 to 1.44.19. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.14...v1.44.19) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f900bcc1..adf96f85 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/BurntSushi/toml v1.1.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.14 + github.com/aws/aws-sdk-go v1.44.19 github.com/davecgh/go-spew v1.1.1 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/uuid v1.3.0 // indirect diff --git a/go.sum b/go.sum index b95bcae8..895e2fe3 100644 --- a/go.sum +++ b/go.sum @@ -80,8 +80,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.14 h1:qd7/muV1rElsbvkK9D1nHUzBoDlEw2etfeo4IE82eSQ= -github.com/aws/aws-sdk-go v1.44.14/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.19 h1:dhI6p4l6kisnA7gBAM8sP5YIk0bZ9HNAj7yrK7kcfdU= +github.com/aws/aws-sdk-go v1.44.19/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= From d271b173e8dc9d3d9ffc1e0c5315753890b20ed5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 22 May 2022 14:23:44 +0000 Subject: [PATCH 0860/1127] chore(deps): bump cloud.google.com/go/storage from 1.22.0 to 1.22.1 Bumps [cloud.google.com/go/storage](https://github.com/googleapis/google-cloud-go) from 1.22.0 to 1.22.1. - [Release notes](https://github.com/googleapis/google-cloud-go/releases) - [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/CHANGES.md) - [Commits](https://github.com/googleapis/google-cloud-go/compare/spanner/v1.22.0...storage/v1.22.1) --- updated-dependencies: - dependency-name: cloud.google.com/go/storage dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 14 +++++++------- go.sum | 27 ++++++++++++++++++--------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/go.mod b/go.mod index f900bcc1..7ae27558 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Praqma/helmsman go 1.17 require ( - cloud.google.com/go/storage v1.22.0 + cloud.google.com/go/storage v1.22.1 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.15.0 github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect @@ -20,28 +20,28 @@ require ( github.com/logrusorgru/aurora v2.0.3+incompatible golang.org/x/crypto v0.0.0-20220312131142-6068a2e6cfdc // indirect golang.org/x/net v0.0.0-20220325170049-de3da57026de - golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a // indirect - google.golang.org/genproto v0.0.0-20220405205423-9d709892a2bf // indirect + golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect + google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335 // indirect google.golang.org/protobuf v1.28.0 // indirect gopkg.in/yaml.v2 v2.4.0 ) require ( cloud.google.com/go v0.100.2 // indirect - cloud.google.com/go/compute v1.5.0 // indirect + cloud.google.com/go/compute v1.6.0 // indirect cloud.google.com/go/iam v0.3.0 // indirect github.com/golang-jwt/jwt/v4 v4.3.0 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-cmp v0.5.7 // indirect - github.com/googleapis/gax-go/v2 v2.2.0 // indirect + github.com/googleapis/gax-go/v2 v2.3.0 // indirect github.com/googleapis/go-type-adapters v1.0.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/mattn/go-ieproxy v0.0.1 // indirect go.opencensus.io v0.23.0 // indirect golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886 // indirect golang.org/x/text v0.3.7 // indirect - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect google.golang.org/api v0.74.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/grpc v1.45.0 // indirect + google.golang.org/grpc v1.46.0 // indirect ) diff --git a/go.sum b/go.sum index b95bcae8..57aeb9cf 100644 --- a/go.sum +++ b/go.sum @@ -36,8 +36,9 @@ cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4g cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= -cloud.google.com/go/compute v1.5.0 h1:b1zWmYuuHz7gO9kDcM/EpHGr06UgsYNRpNJzI2kFiLM= cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= +cloud.google.com/go/compute v1.6.0 h1:XdQIN5mdPTSBVwSIVDuY5e8ZzVAccsHvD3qTEz4zIps= +cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/iam v0.3.0 h1:exkAomrVUuzx9kWFI1wm3KI0uoDeUFPB4kKGzx6x+Gc= @@ -51,8 +52,8 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.22.0 h1:NUV0NNp9nkBuW66BFRLuMgldN60C57ET3dhbwLIYio8= -cloud.google.com/go/storage v1.22.0/go.mod h1:GbaLEoMqbVm6sx3Z0R++gSiBlgMv6yUi2q1DeGFKQgE= +cloud.google.com/go/storage v1.22.1 h1:F6IlQJZrZM++apn9V5/VfS3gbTUYg98PS3EMQAzqtfg= +cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= @@ -96,6 +97,7 @@ github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XP github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -109,6 +111,7 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -197,8 +200,9 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= -github.com/googleapis/gax-go/v2 v2.2.0 h1:s7jOdKSaksJVOxE0Y/S32otcfiP+UQ0cL8/GTKaONwE= github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= +github.com/googleapis/gax-go/v2 v2.3.0 h1:nRJtk3y8Fm770D42QV6T90ZnvFZyk7agSo3Q+Z9p3WI= +github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= github.com/googleapis/go-type-adapters v1.0.0 h1:9XdMn+d/G57qq1s8dNc5IesGCXHf6V2HZ2JwRxfA2tA= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= @@ -359,8 +363,9 @@ golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a h1:qfl7ob3DIEs3Ml9oLuPwY2N04gymzAW04WsUQHIClgM= golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 h1:OSnWWcOd/CtWQC2cYSBgbTSJv3ciqd8r54ySIW2y3RE= +golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -496,8 +501,9 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U= +golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -610,8 +616,10 @@ google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2 google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= -google.golang.org/genproto v0.0.0-20220405205423-9d709892a2bf h1:JTjwKJX9erVpsw17w+OIPP7iAgEkN/r8urhWSunEDTs= -google.golang.org/genproto v0.0.0-20220405205423-9d709892a2bf/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335 h1:2D0OT6tPVdrQTOnVe1VQjfJPTED6EZ7fdJ/f6Db6OsY= +google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -639,8 +647,9 @@ google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.46.0 h1:oCjezcn6g6A75TGoKYBPgKmVBLexhYLM6MebdrPApP8= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= From c84bb18a648d33cdff6ac8b6c161a9b68219df9a Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sun, 22 May 2022 19:23:44 +0100 Subject: [PATCH 0861/1127] chore: update to go 1.18 --- Dockerfile | 2 +- go.mod | 2 +- go.sum | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index b4633221..159a58a7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -ARG GO_VERSION="1.17.10" +ARG GO_VERSION="1.18.2" ARG ALPINE_VERSION="3.15" ARG GLOBAL_KUBE_VERSION="v1.23.6" ARG GLOBAL_HELM_VERSION="v3.9.0" diff --git a/go.mod b/go.mod index c1aba1ee..74ebf308 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/Praqma/helmsman -go 1.17 +go 1.18 require ( cloud.google.com/go/storage v1.22.1 diff --git a/go.sum b/go.sum index 983d82db..4115087e 100644 --- a/go.sum +++ b/go.sum @@ -341,7 +341,6 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220325170049-de3da57026de h1:pZB1TWnKi+o4bENlbzAgLrEbY4RMYmUIRobMcSmfeYc= From cd92a1d391bd202930d3744b8d28b960d9ffe477 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Mon, 23 May 2022 11:33:35 +0200 Subject: [PATCH 0862/1127] Release v3.10.0 --- README.md | 2 +- internal/app/main.go | 2 +- release-notes.md | 11 ++++++----- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index a4244907..7bb6dca5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.9.1&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.10.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) diff --git a/internal/app/main.go b/internal/app/main.go index c99dda87..f05b1eb6 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.9.1" + appVersion = "v3.10.0" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index 0466fc94..18c9cb17 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,10 +1,11 @@ -# v3.9.1 +# v3.10.0 -## New features +## New features -- Recursively expand environment variables (#657) +- Add -exclude-target and -exclude-groups CLI options (#659) ## Fixes and improvements -- Updated dependencies to their latest versions (#653; #654; #655; #658) -- avoid loading the `.env` file twice +- Update go to 1.18 (#664) +- Recursively expand env variables to fix #656 (#657) +- Pin versions of helm plugins in Dockerfile (#660) From 2d3679fef198bdfc62146834c33007392c3a565f Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Mon, 23 May 2022 20:05:13 +0100 Subject: [PATCH 0863/1127] fix: replace godotenv with gotenv --- go.mod | 20 ++++++++++---------- go.sum | 6 ++++-- internal/app/utils.go | 4 ++-- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index 74ebf308..b02f8b4a 100644 --- a/go.mod +++ b/go.mod @@ -6,23 +6,14 @@ require ( cloud.google.com/go/storage v1.22.1 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.15.0 - github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect github.com/BurntSushi/toml v1.1.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 github.com/aws/aws-sdk-go v1.44.19 - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/google/uuid v1.3.0 // indirect github.com/imdario/mergo v0.3.12 - github.com/joho/godotenv v1.4.0 - github.com/kr/text v0.2.0 // indirect github.com/logrusorgru/aurora v2.0.3+incompatible - golang.org/x/crypto v0.0.0-20220312131142-6068a2e6cfdc // indirect + github.com/subosito/gotenv v1.3.0 golang.org/x/net v0.0.0-20220325170049-de3da57026de - golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect - google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335 // indirect - google.golang.org/protobuf v1.28.0 // indirect gopkg.in/yaml.v2 v2.4.0 ) @@ -30,18 +21,27 @@ require ( cloud.google.com/go v0.100.2 // indirect cloud.google.com/go/compute v1.6.0 // indirect cloud.google.com/go/iam v0.3.0 // indirect + github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/golang-jwt/jwt/v4 v4.3.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-cmp v0.5.7 // indirect + github.com/google/uuid v1.3.0 // indirect github.com/googleapis/gax-go/v2 v2.3.0 // indirect github.com/googleapis/go-type-adapters v1.0.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/kr/text v0.2.0 // indirect github.com/mattn/go-ieproxy v0.0.1 // indirect go.opencensus.io v0.23.0 // indirect + golang.org/x/crypto v0.0.0-20220312131142-6068a2e6cfdc // indirect + golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect google.golang.org/api v0.74.0 // indirect google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335 // indirect google.golang.org/grpc v1.46.0 // indirect + google.golang.org/protobuf v1.28.0 // indirect ) diff --git a/go.sum b/go.sum index 4115087e..60df94d5 100644 --- a/go.sum +++ b/go.sum @@ -216,8 +216,6 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= -github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -244,7 +242,10 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI= +github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -676,6 +677,7 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/app/utils.go b/internal/app/utils.go index 87bccc4b..f64564f4 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -18,7 +18,7 @@ import ( "unicode/utf8" "github.com/Masterminds/semver" - "github.com/joho/godotenv" + "github.com/subosito/gotenv" "github.com/Praqma/helmsman/internal/aws" "github.com/Praqma/helmsman/internal/azure" @@ -155,7 +155,7 @@ func getEnv(key string) string { // prepareEnv loads dotenv files and recusively expands all environment variables func prepareEnv(envFiles []string) error { if len(envFiles) != 0 { - err := godotenv.Overload(envFiles...) + err := gotenv.OverLoad(envFiles...) if err != nil { return fmt.Errorf("error loading env file: %w", err) } From 6dfe575e52ba93884e696b46ccdacc92b181108f Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 24 May 2022 14:07:12 +0100 Subject: [PATCH 0864/1127] release: v3.10.1 --- .version | 2 +- README.md | 6 +++--- internal/app/main.go | 2 +- release-notes.md | 10 ++-------- 4 files changed, 7 insertions(+), 13 deletions(-) diff --git a/.version b/.version index a4dae046..d82ff335 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.9.1 +v3.10.1 diff --git a/README.md b/README.md index 7bb6dca5..f7cd13fb 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.10.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.10.1&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -76,9 +76,9 @@ Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the d ```sh # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.9.1/helmsman_3.9.1_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.10.1/helmsman_3.10.1_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.9.1/helmsman_3.9.1_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.10.1/helmsman_3.10.1_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/internal/app/main.go b/internal/app/main.go index f05b1eb6..4bb791fd 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.10.0" + appVersion = "v3.10.1" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index 18c9cb17..30116847 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,11 +1,5 @@ -# v3.10.0 - -## New features - -- Add -exclude-target and -exclude-groups CLI options (#659) +# v3.10.1 ## Fixes and improvements -- Update go to 1.18 (#664) -- Recursively expand env variables to fix #656 (#657) -- Pin versions of helm plugins in Dockerfile (#660) +- Replaced `godotenv` with `gotenv` to fix the environment variable resolution (#665) From 77c26ab1ad90e82e4e6dc4756f84f7102ed53734 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 24 May 2022 14:25:45 +0100 Subject: [PATCH 0865/1127] refactor: remove dead code --- internal/app/utils.go | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/internal/app/utils.go b/internal/app/utils.go index f64564f4..91037167 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -233,16 +233,6 @@ func substituteSSM(name string) string { return name } -// sliceContains checks if a string slice contains a given string -func sliceContains(slice []string, s string) bool { - for _, a := range slice { - if strings.TrimSpace(a) == s { - return true - } - } - return false -} - // replaceAtIndex replaces the charecter at the given index in the string with the given rune func replaceAtIndex(in string, r rune, i int) (string, error) { if i < 0 || i >= utf8.RuneCountInString(in) { @@ -427,15 +417,20 @@ func notifySlack(content string, url string, failure bool, executing bool) bool // replaceStringInFile takes a map of keys and values and replaces the keys with values within a given file. // It saves the modified content in a new file -func replaceStringInFile(input []byte, outfile string, replacements map[string]string) { - output := input +func replaceStringInFile(filename string, replacements map[string]string) error { + output, err := ioutil.ReadFile(filename) + if err != nil { + return fmt.Errorf("failed to read file: %w", err) + } + for k, v := range replacements { output = bytes.ReplaceAll(output, []byte(k), []byte(v)) } - if err := ioutil.WriteFile(outfile, output, 0o666); err != nil { - log.Fatal(err.Error()) + if err := ioutil.WriteFile(filename, output, 0o666); err != nil { + return fmt.Errorf("failed to write file: %w", err) } + return nil } // Indent inserts prefix at the beginning of each non-empty line of s. The From 34e0d334118347d40c14d61f75521d21d7281fd9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 29 May 2022 14:41:47 +0000 Subject: [PATCH 0866/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.19 to 1.44.24 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.19 to 1.44.24. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.19...v1.44.24) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b02f8b4a..abcbc883 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.1.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.19 + github.com/aws/aws-sdk-go v1.44.24 github.com/imdario/mergo v0.3.12 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.3.0 diff --git a/go.sum b/go.sum index 60df94d5..04045145 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.19 h1:dhI6p4l6kisnA7gBAM8sP5YIk0bZ9HNAj7yrK7kcfdU= -github.com/aws/aws-sdk-go v1.44.19/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.24 h1:3nOkwJBJLiGBmJKWp3z0utyXuBkxyGkRRwWjrTItJaY= +github.com/aws/aws-sdk-go v1.44.24/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= From 59e3db834e45625592dd9d8677d935f7d16b3eb4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 29 May 2022 15:38:12 +0000 Subject: [PATCH 0867/1127] chore(deps): bump github.com/imdario/mergo from 0.3.12 to 0.3.13 Bumps [github.com/imdario/mergo](https://github.com/imdario/mergo) from 0.3.12 to 0.3.13. - [Release notes](https://github.com/imdario/mergo/releases) - [Commits](https://github.com/imdario/mergo/compare/0.3.12...v0.3.13) --- updated-dependencies: - dependency-name: github.com/imdario/mergo dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index abcbc883..0c13e21c 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 github.com/aws/aws-sdk-go v1.44.24 - github.com/imdario/mergo v0.3.12 + github.com/imdario/mergo v0.3.13 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.3.0 golang.org/x/net v0.0.0-20220325170049-de3da57026de diff --git a/go.sum b/go.sum index 04045145..13915534 100644 --- a/go.sum +++ b/go.sum @@ -210,8 +210,8 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= +github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= @@ -674,11 +674,11 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From 4ee7ced671cad59c1e4d4338ba648441a4bcebe1 Mon Sep 17 00:00:00 2001 From: prizov Date: Fri, 3 Jun 2022 23:24:50 +0300 Subject: [PATCH 0868/1127] #527 add detailed exit code feature --- docs/cmd_reference.md | 3 +++ internal/app/cli.go | 2 ++ internal/app/main.go | 13 +++++++++++++ 3 files changed, 18 insertions(+) diff --git a/docs/cmd_reference.md b/docs/cmd_reference.md index 92e10036..ea8aaff2 100644 --- a/docs/cmd_reference.md +++ b/docs/cmd_reference.md @@ -26,6 +26,9 @@ This lists available CMD options in Helmsman: `--destroy` delete all deployed releases. + `-detailed-exit-code` + returns a detailed exit code (0 - no changes, 1 - error, 2 - changes present) + `--diff-context num` number of lines of context to show around changes in helm diff output. diff --git a/internal/app/cli.go b/internal/app/cli.go index 917db710..6257663f 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -90,6 +90,7 @@ type cli struct { substEnvValues bool noSSMSubst bool substSSMValues bool + detailedExitCode bool updateDeps bool forceUpgrades bool renameReplace bool @@ -144,6 +145,7 @@ func (c *cli) parse() { flag.BoolVar(&c.skipValidation, "skip-validation", false, "skip desired state validation") flag.BoolVar(&c.keepUntrackedReleases, "keep-untracked-releases", false, "keep releases that are managed by Helmsman from the used DSFs in the command, and are no longer tracked in your desired state.") flag.BoolVar(&c.showDiff, "show-diff", false, "show helm diff results. Can expose sensitive information.") + flag.BoolVar(&c.detailedExitCode, "detailed-exit-code", false, "returns a detailed exit code (0 - no changes, 1 - error, 2 - changes present)") flag.BoolVar(&c.noEnvSubst, "no-env-subst", false, "turn off environment substitution globally") flag.BoolVar(&c.substEnvValues, "subst-env-values", false, "turn on environment substitution in values files.") flag.BoolVar(&c.noSSMSubst, "no-ssm-subst", false, "turn off SSM parameter substitution globally") diff --git a/internal/app/main.go b/internal/app/main.go index 4bb791fd..f843de44 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -14,6 +14,11 @@ const ( resourcePool = 10 ) +const ( + exitCodeSucceed = 0 + exitCodeSucceedWithChanges = 2 +) + var ( flags cli settings *config @@ -127,4 +132,12 @@ func Main() { if flags.apply || flags.dryRun || flags.destroy { p.exec() } + + exitCode := exitCodeSucceed + + if flags.detailedExitCode && len(p.Commands) > 0 { + exitCode = exitCodeSucceedWithChanges + } + + os.Exit(exitCode) } From fa19160116d2acfa185093a59adb3b2718fa7eaa Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sun, 5 Jun 2022 13:21:15 +0100 Subject: [PATCH 0869/1127] ci: add trimpath build flag --- .goreleaser.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index 68ede9d3..6bb2ac6b 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -3,6 +3,8 @@ builds: - binary: helmsman ldflags: -s -w -X main.build={{.Version}} -extldflags "-static" + flags: + - -trimpath env: - CGO_ENABLED=0 goos: @@ -12,4 +14,4 @@ builds: goarch: - amd64 - arm64 - main: ./cmd/helmsman/main.go \ No newline at end of file + main: ./cmd/helmsman/main.go From 19cd0bb76ab640421c8d3eb64899fd969db65b83 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 5 Jun 2022 14:21:15 +0000 Subject: [PATCH 0870/1127] chore(deps): bump github.com/subosito/gotenv from 1.3.0 to 1.4.0 Bumps [github.com/subosito/gotenv](https://github.com/subosito/gotenv) from 1.3.0 to 1.4.0. - [Release notes](https://github.com/subosito/gotenv/releases) - [Changelog](https://github.com/subosito/gotenv/blob/master/CHANGELOG.md) - [Commits](https://github.com/subosito/gotenv/compare/v1.3.0...v1.4.0) --- updated-dependencies: - dependency-name: github.com/subosito/gotenv dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 3 +-- go.sum | 9 ++++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 0c13e21c..a2aa84dd 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/aws/aws-sdk-go v1.44.24 github.com/imdario/mergo v0.3.13 github.com/logrusorgru/aurora v2.0.3+incompatible - github.com/subosito/gotenv v1.3.0 + github.com/subosito/gotenv v1.4.0 golang.org/x/net v0.0.0-20220325170049-de3da57026de gopkg.in/yaml.v2 v2.4.0 ) @@ -22,7 +22,6 @@ require ( cloud.google.com/go/compute v1.6.0 // indirect cloud.google.com/go/iam v0.3.0 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect github.com/golang-jwt/jwt/v4 v4.3.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect diff --git a/go.sum b/go.sum index 13915534..9cc322b8 100644 --- a/go.sum +++ b/go.sum @@ -102,7 +102,6 @@ github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWH github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -242,10 +241,10 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI= -github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/subosito/gotenv v1.4.0 h1:yAzM1+SmVcz5R4tXGsNMu1jUl2aOJXoiWUCEwwnGrvs= +github.com/subosito/gotenv v1.4.0/go.mod h1:mZd6rFysKEcUhUHXJk0C/08wAgyDBFuwEYL7vWWGaGo= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -677,8 +676,8 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From c7fcf8cc9f6657d3914c3d39cc2ba78cf55054ed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 5 Jun 2022 15:51:54 +0000 Subject: [PATCH 0871/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.24 to 1.44.27 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.24 to 1.44.27. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.24...v1.44.27) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a2aa84dd..8f7d6b88 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.1.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.24 + github.com/aws/aws-sdk-go v1.44.27 github.com/imdario/mergo v0.3.13 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.4.0 diff --git a/go.sum b/go.sum index 9cc322b8..d3e40449 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.24 h1:3nOkwJBJLiGBmJKWp3z0utyXuBkxyGkRRwWjrTItJaY= -github.com/aws/aws-sdk-go v1.44.24/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.27 h1:8CMspeZSrewnbvAwgl8qo5R7orDLwQnTGBf/OKPiHxI= +github.com/aws/aws-sdk-go v1.44.27/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= From 0cca50efdaf3c1d1595f1d1fa3477d66ddd477ee Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sun, 5 Jun 2022 16:51:43 +0100 Subject: [PATCH 0872/1127] fix: check charts in all previous helmRepos Signed-off-by: Luis Davim --- examples/composition/argo.yaml | 3 --- examples/composition/artifactory.yaml | 3 --- examples/composition/kyverno.yaml | 23 ++++++++++++++++++++ examples/composition/main.yaml | 4 ++++ examples/composition/spec.yaml | 1 + internal/app/state_files.go | 30 +++++++++++++++++++++------ internal/app/state_files_test.go | 9 ++++---- 7 files changed, 57 insertions(+), 16 deletions(-) create mode 100644 examples/composition/kyverno.yaml diff --git a/examples/composition/argo.yaml b/examples/composition/argo.yaml index c1de1997..5bd96e07 100644 --- a/examples/composition/argo.yaml +++ b/examples/composition/argo.yaml @@ -13,9 +13,6 @@ namespaces: - name: "requests.nvidia.com/gpu" value: "2" -helmRepos: - argo: "https://argoproj.github.io/argo-helm" - apps: argo: namespace: "staging" diff --git a/examples/composition/artifactory.yaml b/examples/composition/artifactory.yaml index 610f7156..5cdde836 100644 --- a/examples/composition/artifactory.yaml +++ b/examples/composition/artifactory.yaml @@ -13,9 +13,6 @@ namespaces: max: memory: "300Mi" -helmRepos: - jfrog: "https://charts.jfrog.io" - apps: artifactory: namespace: "production" diff --git a/examples/composition/kyverno.yaml b/examples/composition/kyverno.yaml new file mode 100644 index 00000000..406ccd90 --- /dev/null +++ b/examples/composition/kyverno.yaml @@ -0,0 +1,23 @@ +helmRepos: + kyverno: https://kyverno.github.io/kyverno/ + +namespaces: + kyverno: + protected: false + +apps: + kyverno: + namespace: kyverno + enabled: true + chart: kyverno/kyverno + version: 2.4.1 + kyverno-policies: + namespace: kyverno + enabled: true + chart: kyverno/kyverno-policies + version: 2.4.0 + kyverno-reporter: + namespace: kyverno + enabled: true + chart: kyverno/kyverno-reporter + version: 2.9.0 diff --git a/examples/composition/main.yaml b/examples/composition/main.yaml index 7de34155..b0f7fb2a 100644 --- a/examples/composition/main.yaml +++ b/examples/composition/main.yaml @@ -8,3 +8,7 @@ metadata: settings: kubeContext: "minikube" globalMaxHistory: 5 + +helmRepos: + argo: "https://argoproj.github.io/argo-helm" + jfrog: "https://charts.jfrog.io" diff --git a/examples/composition/spec.yaml b/examples/composition/spec.yaml index c894bb85..ea419699 100644 --- a/examples/composition/spec.yaml +++ b/examples/composition/spec.yaml @@ -3,3 +3,4 @@ stateFiles: - path: main.yaml - path: argo.yaml - path: artifactory.yaml + - path: kyverno.yaml diff --git a/internal/app/state_files.go b/internal/app/state_files.go index d501248e..97da8117 100644 --- a/internal/app/state_files.go +++ b/internal/app/state_files.go @@ -55,7 +55,6 @@ func (s *state) fromTOML(file string) error { if _, err := toml.Decode(tomlFile, s); err != nil { return err } - s.expand(file) return nil } @@ -108,7 +107,6 @@ func (s *state) fromYAML(file string) error { if err = yaml.UnmarshalStrict([]byte(yamlFile), s); err != nil { return err } - s.expand(file) return nil } @@ -147,6 +145,20 @@ func (s *state) build(files fileOptionArray) error { } log.Infof("Parsed [[ %s ]] successfully and found [ %d ] apps", f.name, len(fileState.Apps)) + + // Add all known repos to the fileState + fileState.PreconfiguredHelmRepos = append(fileState.PreconfiguredHelmRepos, s.PreconfiguredHelmRepos...) + for n, r := range s.HelmRepos { + if fileState.HelmRepos == nil { + fileState.HelmRepos = s.HelmRepos + break + } + if _, ok := fileState.HelmRepos[n]; !ok { + fileState.HelmRepos[n] = r + } + } + fileState.expand(f.name) + // Merge Apps that already existed in the state for appName, app := range fileState.Apps { if _, ok := s.Apps[appName]; ok { @@ -190,11 +202,8 @@ func (s *state) expand(relativeToFile string) { var download bool // support env vars in path r.Chart = os.Expand(r.Chart, getEnv) - repoName := strings.Split(r.Chart, "/")[0] - _, isRepo := s.HelmRepos[repoName] - isRepo = isRepo || stringInSlice(repoName, s.PreconfiguredHelmRepos) // if there is no repo for the chart, we assume it's intended to be a local path or url - if !isRepo { + if !s.isChartFromRepo(r.Chart) { // unless explicitly requested by the user, we don't need to download if the protocol is natively supported by helm download = flags.downloadCharts || !isSupportedProtocol(r.Chart, validProtocols) } @@ -233,6 +242,15 @@ func (s *state) expand(relativeToFile string) { } } +// isChartFromRepo checks if the chart is from a known repo +func (s *state) isChartFromRepo(chart string) bool { + repoName := strings.Split(chart, "/")[0] + if _, isRepo := s.HelmRepos[repoName]; isRepo { + return true + } + return stringInSlice(repoName, s.PreconfiguredHelmRepos) +} + // cleanup deletes the k8s certificates and keys files // It also deletes any Tiller TLS certs and keys // and secret files diff --git a/internal/app/state_files_test.go b/internal/app/state_files_test.go index 99f54b02..610d9d48 100644 --- a/internal/app/state_files_test.go +++ b/internal/app/state_files_test.go @@ -363,6 +363,7 @@ func Test_build(t *testing.T) { s := new(state) files := fileOptionArray{ fileOption{name: "../../examples/composition/main.yaml"}, + fileOption{name: "../../examples/composition/kyverno.yaml"}, fileOption{name: "../../examples/composition/argo.yaml"}, fileOption{name: "../../examples/composition/artifactory.yaml"}, } @@ -370,10 +371,10 @@ func Test_build(t *testing.T) { if err != nil { t.Errorf("build() - unexpected error: %v", err) } - if len(s.Apps) != 2 { - t.Errorf("build() - unexpected number of apps, wanted 2 got %d", len(s.Apps)) + if len(s.Apps) != 5 { + t.Errorf("build() - unexpected number of apps, wanted 5 got %d", len(s.Apps)) } - if len(s.HelmRepos) != 2 { - t.Errorf("build() - unexpected number of repos, wanted 2 got %d", len(s.Apps)) + if len(s.HelmRepos) != 3 { + t.Errorf("build() - unexpected number of repos, wanted 3 got %d", len(s.Apps)) } } From 2f501108622f33ec859e1dcd3123d950a3accaae Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sun, 5 Jun 2022 18:53:31 +0100 Subject: [PATCH 0873/1127] refactor: linting Signed-off-by: Luis Davim --- examples/composition/argo.yaml | 3 +++ internal/app/spec_state.go | 8 ++------ internal/app/spec_state_test.go | 13 ++++++++++--- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/examples/composition/argo.yaml b/examples/composition/argo.yaml index 5bd96e07..c1de1997 100644 --- a/examples/composition/argo.yaml +++ b/examples/composition/argo.yaml @@ -13,6 +13,9 @@ namespaces: - name: "requests.nvidia.com/gpu" value: "2" +helmRepos: + argo: "https://argoproj.github.io/argo-helm" + apps: argo: namespace: "staging" diff --git a/internal/app/spec_state.go b/internal/app/spec_state.go index 3a7b5144..5927a7d5 100644 --- a/internal/app/spec_state.go +++ b/internal/app/spec_state.go @@ -14,7 +14,7 @@ type StateFiles struct { StateFiles []StatePath `yaml:"stateFiles"` } -// fromYAML reads a yaml file and decodes it to a state type. +// specFromYAML reads a yaml file and decodes it to a state type. // parser which throws an error if the YAML file is not valid. func (pc *StateFiles) specFromYAML(file string) error { rawYamlFile, err := ioutil.ReadFile(file) @@ -25,9 +25,5 @@ func (pc *StateFiles) specFromYAML(file string) error { yamlFile := string(rawYamlFile) - if err = yaml.UnmarshalStrict([]byte(yamlFile), pc); err != nil { - return err - } - - return nil + return yaml.UnmarshalStrict([]byte(yamlFile), pc) } diff --git a/internal/app/spec_state_test.go b/internal/app/spec_state_test.go index 80fa3686..ac514fa3 100644 --- a/internal/app/spec_state_test.go +++ b/internal/app/spec_state_test.go @@ -28,6 +28,13 @@ func Test_specFromYAML(t *testing.T) { s: new(StateFiles), }, want: false, + }, { + name: "test case 3 -- Commposition example", + args: args{ + file: "../../examples/composition/spec.yaml", + s: new(StateFiles), + }, + want: true, }, } @@ -66,9 +73,9 @@ func Test_specFileSort(t *testing.T) { args: args{ files: fileOptionArray( []fileOption{ - fileOption{"third.yaml", 0}, - fileOption{"first.yaml", -20}, - fileOption{"second.yaml", -10}, + {"third.yaml", 0}, + {"first.yaml", -20}, + {"second.yaml", -10}, }), }, want: [3]int{-20, -10, 0}, From 68cc7f999a8ca1684133292b07c97bec7c4148ed Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Mon, 6 Jun 2022 14:03:13 +0100 Subject: [PATCH 0874/1127] release: v3.11.0 --- .version | 2 +- README.md | 6 +++--- internal/app/main.go | 2 +- release-notes.md | 9 +++++++-- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/.version b/.version index d82ff335..41e615f3 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.10.1 +v3.11.0 diff --git a/README.md b/README.md index f7cd13fb..073ccb2a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.10.1&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.11.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -76,9 +76,9 @@ Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the d ```sh # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.10.1/helmsman_3.10.1_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.11.0/helmsman_3.11.0_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.10.1/helmsman_3.10.1_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.11.0/helmsman_3.11.0_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/internal/app/main.go b/internal/app/main.go index f843de44..09b8f4b1 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.10.1" + appVersion = "v3.11.0" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index 30116847..b1d2c97e 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,5 +1,10 @@ -# v3.10.1 +# v3.11.0 + +## New feature + +- Added detailed exit codes (#668) ## Fixes and improvements -- Replaced `godotenv` with `gotenv` to fix the environment variable resolution (#665) +- Updated dependencies (#666; #667) +- Check charts using helm repos from previouly merged DSFs (#673) From 1dee744339607a853b30af3c37254ae651944e2c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 12 Jun 2022 14:29:47 +0000 Subject: [PATCH 0875/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.27 to 1.44.32 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.27 to 1.44.32. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.27...v1.44.32) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8f7d6b88..dbf78156 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.1.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.27 + github.com/aws/aws-sdk-go v1.44.32 github.com/imdario/mergo v0.3.13 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.4.0 diff --git a/go.sum b/go.sum index d3e40449..dfe2ff80 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.27 h1:8CMspeZSrewnbvAwgl8qo5R7orDLwQnTGBf/OKPiHxI= -github.com/aws/aws-sdk-go v1.44.27/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.32 h1:x5hBtpY/02sgRL158zzTclcCLwh3dx3YlSl1rAH4Op0= +github.com/aws/aws-sdk-go v1.44.32/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= From 83e428823011897d4ad788b076b05bf953c86f2c Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sat, 11 Jun 2022 12:11:40 +0100 Subject: [PATCH 0876/1127] refactor: export state definition --- internal/app/cli.go | 14 +-- internal/app/cli_test.go | 6 +- internal/app/decision_maker.go | 24 ++--- internal/app/decision_maker_test.go | 74 +++++++------- internal/app/helm_helpers.go | 6 +- internal/app/helm_helpers_test.go | 16 +-- internal/app/helm_release.go | 8 +- internal/app/kube_helpers.go | 10 +- internal/app/main.go | 12 ++- internal/app/namespace.go | 81 +++++++++------ internal/app/plan.go | 6 +- internal/app/plan_test.go | 2 +- internal/app/release.go | 153 ++++++++++++++++------------ internal/app/release_files.go | 6 +- internal/app/release_test.go | 72 ++++++------- internal/app/state.go | 140 +++++++++++++++---------- internal/app/state_files.go | 24 ++--- internal/app/state_files_test.go | 32 +++--- internal/app/state_test.go | 152 +++++++++++++-------------- internal/app/utils.go | 2 +- internal/app/utils_test.go | 18 ++-- 21 files changed, 470 insertions(+), 388 deletions(-) diff --git a/internal/app/cli.go b/internal/app/cli.go index 6257663f..6e465a2d 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -110,15 +110,14 @@ type cli struct { func printUsage() { fmt.Print(banner) - fmt.Printf("Helmsman version: " + appVersion + "\n") - fmt.Printf("Helmsman is a Helm Charts as Code tool which allows you to automate the deployment/management of your Helm charts.") - fmt.Printf("") + fmt.Println("Helmsman version: " + appVersion) + fmt.Println("Helmsman is a Helm Charts as Code tool which allows you to automate the deployment/management of your Helm charts.") + fmt.Println("") fmt.Printf("Usage: helmsman [options]\n") flag.PrintDefaults() } -// Cli parses cmd flags, validates them and performs some initializations -func (c *cli) parse() { +func (c *cli) setup() { // parsing command line flags flag.Var(&c.files, "f", "desired state file name(s), may be supplied more than once to merge state files") flag.Var(&c.envFiles, "e", "additional file(s) to load environment variables from, may be supplied more than once, it extends default .env file lookup, every next file takes precedence over previous ones in case of having the same environment variables defined") @@ -165,7 +164,10 @@ func (c *cli) parse() { flag.IntVar(&c.pendingAppRetries, "pending-max-retries", 0, "max number of retries for pending helm releases") flag.Usage = printUsage flag.Parse() +} +// Cli parses cmd flags, validates them and performs some initializations +func (c *cli) parse() { if c.version { fmt.Println("Helmsman version: " + appVersion) os.Exit(0) @@ -247,7 +249,7 @@ func (c *cli) parse() { } // readState gets the desired state from files -func (c *cli) readState(s *state) error { +func (c *cli) readState(s *State) error { // read the env file if it exists if _, err := os.Stat(".env"); err == nil { if !stringInSlice(".env", c.envFiles) { diff --git a/internal/app/cli_test.go b/internal/app/cli_test.go index 9b34399f..77def04f 100644 --- a/internal/app/cli_test.go +++ b/internal/app/cli_test.go @@ -7,6 +7,10 @@ var _ = func() bool { return true }() +func init() { + flags.parse() +} + func Test_readState(t *testing.T) { type result struct { numApps int @@ -89,7 +93,7 @@ func Test_readState(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s := state{} + s := State{} if err := tt.flags.readState(&s); err != nil { t.Errorf("readState() = Unexpected error: %v", err) } diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index 38e5d493..23e4bc24 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -22,7 +22,7 @@ func newCurrentState() *currentState { } // getCurrentState builds the currentState map containing information about all releases existing in a k8s cluster -func (s *state) getCurrentState() *currentState { +func (s *State) getCurrentState() *currentState { log.Info("Acquiring current Helm state from cluster") cs := newCurrentState() @@ -57,7 +57,7 @@ func (s *state) getCurrentState() *currentState { } // makePlan creates a plan of the actions needed to make the desired state come true. -func (cs *currentState) makePlan(s *state) *plan { +func (cs *currentState) makePlan(s *State) *plan { p := createPlan() p.StorageBackend = s.Settings.StorageBackend p.ReverseDelete = s.Settings.ReverseDelete @@ -73,7 +73,7 @@ func (cs *currentState) makePlan(s *state) *plan { // It would make more sense than parallelising *some of the workload* like we do here with r.checkChartDepUpdate(), leaving some helm commands outside the concurrent part. sem <- struct{}{} wg.Add(1) - go func(r *release, c *chartInfo) { + go func(r *Release, c *ChartInfo) { defer func() { wg.Done() <-sem @@ -82,7 +82,7 @@ func (cs *currentState) makePlan(s *state) *plan { if err := cs.decide(r, s.Namespaces[r.Namespace], p, c, s.Settings, flags.pendingAppRetries); err != nil { log.Fatal(err.Error()) } - }(r, s.ChartInfo[r.Chart][r.Version]) + }(r, s.chartInfo[r.Chart][r.Version]) } wg.Wait() @@ -91,7 +91,7 @@ func (cs *currentState) makePlan(s *state) *plan { // decide makes a decision about what commands (actions) need to be executed // to make a release section of the desired state come true. -func (cs *currentState) decide(r *release, n *namespace, p *plan, c *chartInfo, settings config, retries int) error { +func (cs *currentState) decide(r *Release, n *Namespace, p *plan, c *ChartInfo, settings Config, retries int) error { prefix := "Release [ " + r.Name + " ] in namespace [ " + r.Namespace + " ]" // check for presence in defined targets or groups if !r.isConsideredToRun() { @@ -169,7 +169,7 @@ func (cs *currentState) decide(r *release, n *namespace, p *plan, c *chartInfo, } // releaseStatus returns the status of a release in the Current State. -func (cs *currentState) releaseStatus(r *release) string { +func (cs *currentState) releaseStatus(r *Release) string { v, ok := cs.releases[r.key()] if !ok || v.HelmsmanContext != curContext { return helmStatusMissing @@ -181,7 +181,7 @@ func (cs *currentState) releaseStatus(r *release) string { // It searches the Current State for releases. // The key format for releases uniqueness is: // If status is provided as an input [deployed, deleted, failed], then the search will verify the release status matches the search status. -func (cs *currentState) releaseExists(r *release, status string) bool { +func (cs *currentState) releaseExists(r *Release, status string) bool { currentState := cs.releaseStatus(r) if status != "" { @@ -203,7 +203,7 @@ var ( // getHelmsmanReleases returns a map of all releases that are labeled with "MANAGED-BY=HELMSMAN" // The releases are categorized by the namespaces in which they are deployed // The returned map format is: map[:map[:true]] -func (cs *currentState) getHelmsmanReleases(s *state) map[string]map[string]bool { +func (cs *currentState) getHelmsmanReleases(s *State) map[string]map[string]bool { const outputFmt = "custom-columns=NAME:.metadata.name,CTX:.metadata.labels.HELMSMAN_CONTEXT" var ( wg sync.WaitGroup @@ -250,8 +250,8 @@ func (cs *currentState) getHelmsmanReleases(s *state) map[string]map[string]bool if len(flds) > 1 { rctx = flds[1] } - if len(s.TargetMap) > 0 { - if use, ok := s.TargetMap[name]; !ok || !use { + if len(s.targetMap) > 0 { + if use, ok := s.targetMap[name]; !ok || !use { continue } } @@ -286,7 +286,7 @@ func (cs *currentState) getHelmsmanReleases(s *state) map[string]map[string]bool // For all untracked releases found, a decision is made to uninstall them and is added to the Helmsman plan // NOTE: Untracked releases don't benefit from either namespace or application protection. // NOTE: Removing/Commenting out an app from the desired state makes it untracked. -func (cs *currentState) cleanUntrackedReleases(s *state, p *plan) { +func (cs *currentState) cleanUntrackedReleases(s *State, p *plan) { toDelete := 0 log.Info("Checking if any Helmsman managed releases are no longer tracked by your desired state ...") for ns, hr := range cs.getHelmsmanReleases(s) { @@ -311,7 +311,7 @@ func (cs *currentState) cleanUntrackedReleases(s *state, p *plan) { // it will be uninstalled and installed in the same namespace using the new chart. // - If the release is NOT in the same namespace specified in the input, // it will be purge deleted and installed in the new namespace. -func (cs *currentState) inspectUpgradeScenario(r *release, p *plan, c *chartInfo) error { +func (cs *currentState) inspectUpgradeScenario(r *Release, p *plan, c *ChartInfo) error { if c == nil || c.Name == "" || c.Version == "" { return nil } diff --git a/internal/app/decision_maker_test.go b/internal/app/decision_maker_test.go index 75165e47..f625c179 100644 --- a/internal/app/decision_maker_test.go +++ b/internal/app/decision_maker_test.go @@ -7,7 +7,7 @@ import ( func Test_getValuesFiles(t *testing.T) { type args struct { - r *release + r *Release } tests := []struct { name string @@ -17,7 +17,7 @@ func Test_getValuesFiles(t *testing.T) { { name: "test case 1", args: args{ - r: &release{ + r: &Release{ Name: "release1", Description: "", Namespace: "namespace", @@ -34,7 +34,7 @@ func Test_getValuesFiles(t *testing.T) { { name: "test case 2", args: args{ - r: &release{ + r: &Release{ Name: "release1", Description: "", Namespace: "namespace", @@ -51,7 +51,7 @@ func Test_getValuesFiles(t *testing.T) { { name: "test case 1", args: args{ - r: &release{ + r: &Release{ Name: "release1", Description: "", Namespace: "namespace", @@ -77,7 +77,7 @@ func Test_getValuesFiles(t *testing.T) { func Test_inspectUpgradeScenario(t *testing.T) { type args struct { - r *release + r *Release s *map[string]helmRelease } tests := []struct { @@ -88,7 +88,7 @@ func Test_inspectUpgradeScenario(t *testing.T) { { name: "inspectUpgradeScenario() - local chart with different chart name should change", args: args{ - r: &release{ + r: &Release{ Name: "release1", Namespace: "namespace", Version: "1.0.0", @@ -129,7 +129,7 @@ func Test_inspectUpgradeScenario(t *testing.T) { func Test_decide(t *testing.T) { type args struct { r string - s *state + s *State } tests := []struct { name string @@ -146,8 +146,8 @@ func Test_decide(t *testing.T) { excludedGroupFlag: []string{}, args: args{ r: "release1", - s: &state{ - Apps: map[string]*release{ + s: &State{ + Apps: map[string]*Release{ "release1": { Name: "release1", Namespace: "namespace", @@ -165,8 +165,8 @@ func Test_decide(t *testing.T) { excludedGroupFlag: []string{}, args: args{ r: "release1", - s: &state{ - Apps: map[string]*release{ + s: &State{ + Apps: map[string]*Release{ "release1": { Name: "release1", Namespace: "namespace", @@ -184,8 +184,8 @@ func Test_decide(t *testing.T) { excludedGroupFlag: []string{}, args: args{ r: "release4", - s: &state{ - Apps: map[string]*release{ + s: &State{ + Apps: map[string]*Release{ "release4": { Name: "release4", Namespace: "namespace", @@ -203,8 +203,8 @@ func Test_decide(t *testing.T) { excludedGroupFlag: []string{}, args: args{ r: "thisRelease", - s: &state{ - Apps: map[string]*release{ + s: &State{ + Apps: map[string]*Release{ "thisRelease": { Name: "thisRelease", Namespace: "namespace", @@ -222,8 +222,8 @@ func Test_decide(t *testing.T) { excludedGroupFlag: []string{}, args: args{ r: "thisRelease", - s: &state{ - Apps: map[string]*release{ + s: &State{ + Apps: map[string]*Release{ "thisRelease": { Name: "thisRelease", Namespace: "namespace", @@ -241,8 +241,8 @@ func Test_decide(t *testing.T) { excludedGroupFlag: []string{}, args: args{ r: "thisRelease", - s: &state{ - Apps: map[string]*release{ + s: &State{ + Apps: map[string]*Release{ "thisRelease": { Name: "thisRelease", Namespace: "namespace", @@ -260,8 +260,8 @@ func Test_decide(t *testing.T) { excludedGroupFlag: []string{"myGroup"}, args: args{ r: "thisRelease", - s: &state{ - Apps: map[string]*release{ + s: &State{ + Apps: map[string]*Release{ "thisRelease": { Name: "thisRelease", Namespace: "namespace", @@ -279,10 +279,10 @@ func Test_decide(t *testing.T) { t.Run(tt.name, func(t *testing.T) { cs := newCurrentState() tt.args.s.disableApps([]string{}, tt.targetFlag, tt.excludedGroupFlag, tt.excludedTargetFlag) - settings := config{} + settings := Config{} outcome := plan{} // Act - err := cs.decide(tt.args.s.Apps[tt.args.r], tt.args.s.Namespaces[tt.args.s.Apps[tt.args.r].Namespace], &outcome, &chartInfo{}, settings, 0) + err := cs.decide(tt.args.s.Apps[tt.args.r], tt.args.s.Namespaces[tt.args.s.Apps[tt.args.r].Namespace], &outcome, &ChartInfo{}, settings, 0) if err != nil { t.Errorf("decide() - unexpected error: %v", err) } @@ -300,7 +300,7 @@ func Test_decide(t *testing.T) { func Test_decide_skip_ignored_apps(t *testing.T) { type args struct { rs []string - s *state + s *State } tests := []struct { name string @@ -313,8 +313,8 @@ func Test_decide_skip_ignored_apps(t *testing.T) { targetFlag: []string{"service1", "service2"}, args: args{ rs: []string{"service1", "service2"}, - s: &state{ - Apps: map[string]*release{ + s: &State{ + Apps: map[string]*Release{ "service1": { Name: "service1", Namespace: "namespace", @@ -335,8 +335,8 @@ func Test_decide_skip_ignored_apps(t *testing.T) { targetFlag: []string{"service1"}, args: args{ rs: []string{"service1", "service2"}, - s: &state{ - Apps: map[string]*release{ + s: &State{ + Apps: map[string]*Release{ "service1": { Name: "service1", Namespace: "namespace", @@ -358,13 +358,13 @@ func Test_decide_skip_ignored_apps(t *testing.T) { t.Run(tt.name, func(t *testing.T) { cs := newCurrentState() tt.args.s.disableApps([]string{}, tt.targetFlag, []string{}, []string{}) - settings := config{ + settings := Config{ SkipIgnoredApps: true, } outcome := plan{} // Act for _, r := range tt.args.rs { - err := cs.decide(tt.args.s.Apps[r], tt.args.s.Namespaces[tt.args.s.Apps[r].Namespace], &outcome, &chartInfo{}, settings, 0) + err := cs.decide(tt.args.s.Apps[r], tt.args.s.Namespaces[tt.args.s.Apps[r].Namespace], &outcome, &ChartInfo{}, settings, 0) if err != nil { t.Errorf("decide() - unexpected error: %v", err) } @@ -382,7 +382,7 @@ func Test_decide_skip_ignored_apps(t *testing.T) { func Test_decide_group(t *testing.T) { type args struct { - s *state + s *State } tests := []struct { name string @@ -394,8 +394,8 @@ func Test_decide_group(t *testing.T) { name: "decide() - groupMap does not contain this service - skip", groupFlag: []string{"some-group"}, args: args{ - s: &state{ - Apps: map[string]*release{ + s: &State{ + Apps: map[string]*Release{ "release1": { Name: "release1", Namespace: "namespace", @@ -411,8 +411,8 @@ func Test_decide_group(t *testing.T) { name: "decide() - groupMap contains this service - proceed", groupFlag: []string{"run-me"}, args: args{ - s: &state{ - Apps: map[string]*release{ + s: &State{ + Apps: map[string]*Release{ "release1": { Name: "release1", Namespace: "namespace", @@ -443,8 +443,8 @@ func Test_decide_group(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.args.s.disableApps(tt.groupFlag, []string{}, []string{}, []string{}) - if len(tt.args.s.TargetMap) != len(tt.want) { - t.Errorf("decide() = %d, want %d", len(tt.args.s.TargetMap), len(tt.want)) + if len(tt.args.s.targetMap) != len(tt.want) { + t.Errorf("decide() = %d, want %d", len(tt.args.s.targetMap), len(tt.want)) } }) } diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index 9635ba5b..51a56309 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -18,7 +18,7 @@ type helmRepo struct { URL string `json:"url"` } -type chartInfo struct { +type ChartInfo struct { Name string `yaml:"name"` Version string `yaml:"version"` } @@ -33,7 +33,7 @@ func helmCmd(args []string, desc string) Command { } // getChartInfo fetches the latest chart information (name, version) matching the semantic versioning constraints. -func getChartInfo(chartName, chartVersion string) (*chartInfo, error) { +func getChartInfo(chartName, chartVersion string) (*ChartInfo, error) { if isLocalChart(chartName) { log.Info("Chart [ " + chartName + " ] with version [ " + chartVersion + " ] was found locally.") } @@ -46,7 +46,7 @@ func getChartInfo(chartName, chartVersion string) (*chartInfo, error) { return nil, fmt.Errorf("chart [ %s ] version [ %s ] can't be found. If this is not a local chart, add the repo [ %s ] in your helmRepos stanza. Error output: %w", chartName, chartVersion, maybeRepo, err) } - c := &chartInfo{} + c := &ChartInfo{} if err := yaml.Unmarshal([]byte(res.output), &c); err != nil { log.Fatal(fmt.Sprint(err)) } diff --git a/internal/app/helm_helpers_test.go b/internal/app/helm_helpers_test.go index f816f891..49491ab0 100644 --- a/internal/app/helm_helpers_test.go +++ b/internal/app/helm_helpers_test.go @@ -8,17 +8,17 @@ import ( func Test_getChartInfo(t *testing.T) { // version string = the first semver-valid string after the last hypen in the chart string. type args struct { - r *release + r *Release } tests := []struct { name string args args - want *chartInfo + want *ChartInfo }{ { name: "getChartInfo - local chart should return given release info", args: args{ - r: &release{ + r: &Release{ Name: "release1", Namespace: "namespace", Version: "1.0.0", @@ -26,12 +26,12 @@ func Test_getChartInfo(t *testing.T) { Enabled: true, }, }, - want: &chartInfo{Name: "chart-test", Version: "1.0.0"}, + want: &ChartInfo{Name: "chart-test", Version: "1.0.0"}, }, { name: "getChartInfo - local chart semver should return latest matching release", args: args{ - r: &release{ + r: &Release{ Name: "release1", Namespace: "namespace", Version: "1.0.*", @@ -39,12 +39,12 @@ func Test_getChartInfo(t *testing.T) { Enabled: true, }, }, - want: &chartInfo{Name: "chart-test", Version: "1.0.0"}, + want: &ChartInfo{Name: "chart-test", Version: "1.0.0"}, }, { name: "getChartInfo - unknown chart should error", args: args{ - r: &release{ + r: &Release{ Name: "release1", Namespace: "namespace", Version: "1.0.0", @@ -57,7 +57,7 @@ func Test_getChartInfo(t *testing.T) { { name: "getChartInfo - wrong local version should error", args: args{ - r: &release{ + r: &Release{ Name: "release1", Namespace: "namespace", Version: "0.9.0", diff --git a/internal/app/helm_release.go b/internal/app/helm_release.go index f25ace5b..595d01a6 100644 --- a/internal/app/helm_release.go +++ b/internal/app/helm_release.go @@ -34,7 +34,7 @@ type helmRelease struct { } // getHelmReleases fetches a list of all releases in a k8s cluster -func getHelmReleases(s *state) []helmRelease { +func getHelmReleases(s *State) []helmRelease { var ( allReleases []helmRelease wg sync.WaitGroup @@ -57,9 +57,9 @@ func getHelmReleases(s *state) []helmRelease { if err := json.Unmarshal([]byte(res.output), &releases); err != nil { log.Fatal(fmt.Sprintf("failed to unmarshal Helm CLI output: %s", err)) } - if len(s.TargetMap) > 0 { + if len(s.targetMap) > 0 { for _, r := range releases { - if use, ok := s.TargetMap[r.Name]; ok && use { + if use, ok := s.targetMap[r.Name]; ok && use { targetReleases = append(targetReleases, r) } } @@ -115,6 +115,6 @@ func (r *helmRelease) getChartVersion() string { // getCurrentNamespaceProtection returns the protection state for the namespace where a release is currently installed. // It returns true if a namespace is defined as protected in the desired state file, false otherwise. -func (r *helmRelease) getCurrentNamespaceProtection(s *state) bool { +func (r *helmRelease) getCurrentNamespaceProtection(s *State) bool { return s.Namespaces[r.Namespace].Protected } diff --git a/internal/app/kube_helpers.go b/internal/app/kube_helpers.go index e7771a63..fd28c378 100644 --- a/internal/app/kube_helpers.go +++ b/internal/app/kube_helpers.go @@ -15,14 +15,14 @@ import ( // addNamespaces creates a set of namespaces in your k8s cluster. // If a namespace with the same name exists, it will skip it. // If --ns-override flag is used, it only creates the provided namespace in that flag -func addNamespaces(s *state) { +func addNamespaces(s *State) { var wg sync.WaitGroup for nsName, ns := range s.Namespaces { if ns.disabled { continue } wg.Add(1) - go func(name string, cfg *namespace, wg *sync.WaitGroup) { + go func(name string, cfg *Namespace, wg *sync.WaitGroup) { defer wg.Done() createNamespace(name) labelNamespace(name, cfg.Labels, s.Settings.NamespaceLabelsAuthoritative) @@ -117,7 +117,7 @@ func annotateNamespace(ns string, annotations map[string]string) { } // setLimits creates a LimitRange resource in the provided Namespace -func setLimits(ns string, lims limits) { +func setLimits(ns string, lims Limits) { if len(lims) == 0 { return } @@ -143,7 +143,7 @@ spec: } } -func setQuotas(ns string, quotas *quotas) { +func setQuotas(ns string, quotas *Quotas) { if quotas == nil { return } @@ -200,7 +200,7 @@ func apply(definition, ns, kind string) error { // createContext creates a context -connecting to a k8s cluster- in kubectl config. // It returns true if successful, false otherwise -func createContext(s *state) error { +func createContext(s *State) error { if s.Settings.BearerToken && s.Settings.BearerTokenPath == "" { log.Info("Creating kube context with bearer token from K8S service account.") s.Settings.BearerTokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token" diff --git a/internal/app/main.go b/internal/app/main.go index 09b8f4b1..7fa607d5 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -21,19 +21,21 @@ const ( var ( flags cli - settings *config + settings *Config curContext string log = &Logger{} ) func init() { // Parse cli flags and read config files - flags.parse() + flags.setup() } // Main is the app main function func Main() { - var s state + var s State + + flags.parse() // delete temp files with substituted env vars when the program terminates defer os.RemoveAll(tempFilesDir) @@ -45,12 +47,12 @@ func Main() { log.Fatal(err.Error()) } - if len(flags.target) > 0 && len(s.TargetMap) == 0 { + if len(flags.target) > 0 && len(s.targetMap) == 0 { log.Info("No apps defined with -target flag were found, exiting") os.Exit(0) } - if len(flags.group) > 0 && len(s.TargetMap) == 0 { + if len(flags.group) > 0 && len(s.targetMap) == 0 { log.Info("No apps defined with -group flag were found, exiting") os.Exit(0) } diff --git a/internal/app/namespace.go b/internal/app/namespace.go index 5086f25f..a5080d59 100644 --- a/internal/app/namespace.go +++ b/internal/app/namespace.go @@ -4,54 +4,77 @@ import ( "fmt" ) -// resources type -type resources struct { - CPU string `yaml:"cpu,omitempty"` +// Resources type +type Resources struct { + // CPU is the number of CPU cores + CPU string `yaml:"cpu,omitempty"` + // Memory is the amount of memory Memory string `yaml:"memory,omitempty"` } // custom resource type -type customResource struct { - Name string `yaml:"name,omitempty"` +type CustomResource struct { + // Name of the custom resource + Name string `yaml:"name,omitempty"` + // Value of the custom resource Value string `yaml:"value,omitempty"` } -// limits type -type limits []struct { - Max resources `yaml:"max,omitempty"` - Min resources `yaml:"min,omitempty"` - Default resources `yaml:"default,omitempty"` - DefaultRequest resources `yaml:"defaultRequest,omitempty"` - MaxLimitRequestRatio resources `yaml:"maxLimitRequestRatio,omitempty"` +// Limit represents a resource limit +type Limit struct { + // Max defines the resource limits + Max Resources `yaml:"max,omitempty"` + // Min defines the resource request + Min Resources `yaml:"min,omitempty"` + // Default stes resource limits to pods without defined resource limits + Default Resources `yaml:"default,omitempty"` + // DefaultRequest sets the resource requests for pods without defined resource requests + DefaultRequest Resources `yaml:"defaultRequest,omitempty"` + // MaxLimitRequestRatio set the max limit request ratio + MaxLimitRequestRatio Resources `yaml:"maxLimitRequestRatio,omitempty"` Type string `yaml:"type"` } +// Limits type +type Limits []Limit + // quota type -type quotas struct { - Pods string `yaml:"pods,omitempty"` - CPULimits string `yaml:"limits.cpu,omitempty"` - CPURequests string `yaml:"requests.cpu,omitempty"` - MemoryLimits string `yaml:"limits.memory,omitempty"` - MemoryRequests string `yaml:"requests.memory,omitempty"` - CustomQuotas []customResource `yaml:"customQuotas,omitempty"` +type Quotas struct { + // Pods is the pods quota + Pods string `yaml:"pods,omitempty"` + // CPULimits is the CPU quota + CPULimits string `yaml:"limits.cpu,omitempty"` + // CPURequests is the CPU requests quota + CPURequests string `yaml:"requests.cpu,omitempty"` + // MemoryLimits is the memory quota + MemoryLimits string `yaml:"limits.memory,omitempty"` + // MemoryRequests is the memory requests quota + MemoryRequests string `yaml:"requests.memory,omitempty"` + // CustomResource is a list of custom resource quotas + CustomQuotas []CustomResource `yaml:"customQuotas,omitempty"` } -// namespace type represents the fields of a namespace -type namespace struct { - Protected bool `yaml:"protected"` - Limits limits `yaml:"limits,omitempty"` - Labels map[string]string `yaml:"labels"` - Annotations map[string]string `yaml:"annotations"` - Quotas *quotas `yaml:"quotas,omitempty"` - disabled bool +// Namespace type represents the fields of a Namespace +type Namespace struct { + // Protected if set to true no changes can be applied to the namespace + Protected bool `yaml:"protected"` + // Limits to set on the namespace + Limits Limits `yaml:"limits,omitempty"` + // Labels to set to the namespace + Labels map[string]string `yaml:"labels,omitempty"` + // Annotations to set on the namespace + Annotations map[string]string `yaml:"annotations,omitempty"` + // Quotas to set on the namespace + Quotas *Quotas `yaml:"quotas,omitempty"` + disabled bool } -func (n *namespace) Disable() { +func (n *Namespace) Disable() { n.disabled = true } // print prints the namespace -func (n *namespace) print() { +func (n *Namespace) print() { fmt.Println("\tprotected: ", n.Protected) fmt.Println("\tdisabled: ", n.disabled) fmt.Println("\tlabels:") diff --git a/internal/app/plan.go b/internal/app/plan.go index 1c8e6757..323939b5 100644 --- a/internal/app/plan.go +++ b/internal/app/plan.go @@ -32,7 +32,7 @@ type orderedDecision struct { type orderedCommand struct { Command Command Priority int - targetRelease *release + targetRelease *Release beforeCommands []hookCmd afterCommands []hookCmd } @@ -57,7 +57,7 @@ func createPlan() *plan { } // addCommand adds a command type to the plan -func (p *plan) addCommand(cmd Command, priority int, r *release, beforeCommands []hookCmd, afterCommands []hookCmd) { +func (p *plan) addCommand(cmd Command, priority int, r *Release, beforeCommands []hookCmd, afterCommands []hookCmd) { p.Lock() defer p.Unlock() oc := orderedCommand{ @@ -178,7 +178,7 @@ func releaseWithHooks(cmd orderedCommand, storageBackend string, wg *sync.WaitGr } // execOne executes a single ordered command -func execOne(cmd Command, targetRelease *release) error { +func execOne(cmd Command, targetRelease *Release) error { log.Notice(cmd.Description) res, err := cmd.Exec() if err != nil { diff --git a/internal/app/plan_test.go b/internal/app/plan_test.go index 89d7faa3..0b89ae54 100644 --- a/internal/app/plan_test.go +++ b/internal/app/plan_test.go @@ -62,7 +62,7 @@ func Test_plan_addCommand(t *testing.T) { Decisions: tt.fields.Decisions, Created: tt.fields.Created, } - r := &release{} + r := &Release{} p.addCommand(tt.args.c, 0, r, []hookCmd{}, []hookCmd{}) if got := len(p.Commands); got != 1 { t.Errorf("addCommand(): got %v, want 1", got) diff --git a/internal/app/release.go b/internal/app/release.go index 4993ea9a..5c8a3739 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -8,46 +8,69 @@ import ( "strings" ) -// release type representing Helm releases which are described in the desired state -type release struct { - Name string `yaml:"name"` - Description string `yaml:"description"` - Namespace string `yaml:"namespace"` - Enabled bool `yaml:"enabled"` - Group string `yaml:"group"` - Chart string `yaml:"chart"` - Version string `yaml:"version"` - ValuesFile string `yaml:"valuesFile"` - ValuesFiles []string `yaml:"valuesFiles"` - SecretsFile string `yaml:"secretsFile"` - SecretsFiles []string `yaml:"secretsFiles"` - PostRenderer string `yaml:"postRenderer"` - Test bool `yaml:"test"` - Protected bool `yaml:"protected"` - Wait bool `yaml:"wait"` - Priority int `yaml:"priority"` - Set map[string]string `yaml:"set"` - SetString map[string]string `yaml:"setString"` - SetFile map[string]string `yaml:"setFile"` - HelmFlags []string `yaml:"helmFlags"` - HelmDiffFlags []string `yaml:"helmDiffFlags"` - NoHooks bool `yaml:"noHooks"` - Timeout int `yaml:"timeout"` - Hooks map[string]interface{} `yaml:"hooks"` - MaxHistory int `yaml:"maxHistory"` - disabled bool -} - -func (r *release) key() string { +// Release type representing Helm releases which are described in the desired state +type Release struct { + // Name is the helm release name + Name string `yaml:"name"` + // Description is a user friendly description of the helm release + Description string `yaml:"description,omitempty"` + // Namespace where to deploy the helm release + Namespace string `yaml:"namespace"` + // Enabled can be used to togle a helm release + Enabled bool `yaml:"enabled"` + Group string `yaml:"group,omitempty"` + Chart string `yaml:"chart"` + // Version of the helm chart to deploy + Version string `yaml:"version"` + // ValuesFile is the path for a values file for the helm release + ValuesFile string `yaml:"valuesFile,omitempty"` + // ValuesFiles is a list of paths a values files for the helm release + ValuesFiles []string `yaml:"valuesFiles,omitempty"` + // SecretsFile is the path for an encrypted values file for the helm release + SecretsFile string `yaml:"secretsFile,omitempty"` + // SecretsFiles is a list of paths for encrypted values files for the helm release + SecretsFiles []string `yaml:"secretsFiles,omitempty"` + // PostRenderer is the path to an executable to be used for post rendering + PostRenderer string `yaml:"postRenderer,omitempty"` + // Test indicates if the chart tests should be executed + Test bool `yaml:"test,omitempty"` + // Protected defines if the release should be protected against changes + Protected bool `yaml:"protected,omitempty"` + // Wait defines whether helm should block execution until all k8s resources are in a ready state + Wait bool `yaml:"wait,omitempty"` + // Priority allows defining the execution order, releases with the same priority can be executed in parallel + Priority int `yaml:"priority,omitempty"` + // Set can be used to overwrite the chart values + Set map[string]string `yaml:"set,omitempty"` + // SetString can be used to overwrite string values + SetString map[string]string `yaml:"setString,omitempty"` + // SetFile can be used to overwrite the chart values + SetFile map[string]string `yaml:"setFile,omitempty"` + // HelmFlags is a list of additional flags to pass to the helm command + HelmFlags []string `yaml:"helmFlags,omitempty"` + // HelmDiffFlags is a list of cli flags to pass to helm diff + HelmDiffFlags []string `yaml:"helmDiffFlags,omitempty"` + // NoHooks can be used to disable the execution of helm hooks + NoHooks bool `yaml:"noHooks,omitempty"` + // Timeout is the number of seconds to wait for the release to complete + Timeout int `yaml:"timeout,omitempty"` + // Hooks can be used to define lifecycle hooks specific to this release + Hooks map[string]interface{} `yaml:"hooks,omitempty"` + // MaxHistory is the maximum number of histoical releases to keep + MaxHistory int `yaml:"maxHistory,omitempty"` + disabled bool +} + +func (r *Release) key() string { return fmt.Sprintf("%s-%s", r.Name, r.Namespace) } -func (r *release) Disable() { +func (r *Release) Disable() { r.disabled = true } // isReleaseConsideredToRun checks if a release is being targeted for operations as specified by user cmd flags (--group or --target) -func (r *release) isConsideredToRun() bool { +func (r *Release) isConsideredToRun() bool { if r == nil { return false } @@ -56,7 +79,7 @@ func (r *release) isConsideredToRun() bool { // validate validates if a release inside a desired state meets the specifications or not. // check the full specification @ https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md -func (r *release) validate(appLabel string, seen map[string]map[string]bool, s *state) error { +func (r *Release) validate(appLabel string, seen map[string]map[string]bool, s *State) error { if seen[r.Name][r.Namespace] { return errors.New("release name must be unique within a given namespace") } @@ -126,7 +149,7 @@ func (r *release) validate(appLabel string, seen map[string]map[string]bool, s * } // testRelease creates a Helm command to test a particular release. -func (r *release) test(afterCommands *[]hookCmd) { +func (r *Release) test(afterCommands *[]hookCmd) { if flags.dryRun { log.Verbose("Dry-run, skipping tests: " + r.Name) return @@ -136,7 +159,7 @@ func (r *release) test(afterCommands *[]hookCmd) { } // installRelease creates a Helm command to install a particular release in a particular namespace using a particular Tiller. -func (r *release) install(p *plan) { +func (r *Release) install(p *plan) { before, after := r.checkHooks("install") if r.Test { @@ -148,7 +171,7 @@ func (r *release) install(p *plan) { } // uninstall uninstalls a release -func (r *release) uninstall(p *plan, optionalNamespace ...string) { +func (r *Release) uninstall(p *plan, optionalNamespace ...string) { ns := r.Namespace if len(optionalNamespace) > 0 { ns = optionalNamespace[0] @@ -165,7 +188,7 @@ func (r *release) uninstall(p *plan, optionalNamespace ...string) { } // diffRelease diffs an existing release with the specified values.yaml -func (r *release) diff() (string, error) { +func (r *Release) diff() (string, error) { var ( args []string maxExitCode int @@ -208,7 +231,7 @@ func (r *release) diff() (string, error) { } // upgradeRelease upgrades an existing release with the specified values.yaml -func (r *release) upgrade(p *plan) { +func (r *Release) upgrade(p *plan) { before, after := r.checkHooks("upgrade") if r.Test { @@ -224,7 +247,7 @@ func (r *release) upgrade(p *plan) { // This is used when moving a release to another namespace or when changing the chart used for it. // When the release is being moved to another namespace, the optionalOldNamespace is used to provide // the namespace from which the release is deleted. -func (r *release) reInstall(p *plan, optionalOldNamespace ...string) { +func (r *Release) reInstall(p *plan, optionalOldNamespace ...string) { oldNamespace := "" if len(optionalOldNamespace) > 0 { oldNamespace = optionalOldNamespace[0] @@ -236,7 +259,7 @@ func (r *release) reInstall(p *plan, optionalOldNamespace ...string) { // rollbackRelease evaluates if a rollback action needs to be taken for a given release. // if the release is already deleted but from a different namespace than the one specified in input, // it purge deletes it and create it in the specified namespace. -func (r *release) rollback(cs *currentState, p *plan) { +func (r *Release) rollback(cs *currentState, p *plan) { rs, ok := cs.releases[r.key()] if !ok { return @@ -260,12 +283,12 @@ func (r *release) rollback(cs *currentState, p *plan) { } // mark applies Helmsman specific labels to Helm's state resources (secrets/configmaps) -func (r *release) mark(storageBackend string) { +func (r *Release) mark(storageBackend string) { r.label(storageBackend, "MANAGED-BY=HELMSMAN", "NAMESPACE="+r.Namespace, "HELMSMAN_CONTEXT="+curContext) } // label labels Helm's state resources (secrets/configmaps) -func (r *release) label(storageBackend string, labels ...string) { +func (r *Release) label(storageBackend string, labels ...string) { if len(labels) == 0 { return } @@ -282,7 +305,7 @@ func (r *release) label(storageBackend string, labels ...string) { } // annotate annotates Helm's state resources (secrets/configmaps) -func (r *release) annotate(storageBackend string, annotations ...string) { +func (r *Release) annotate(storageBackend string, annotations ...string) { if len(annotations) == 0 { return } @@ -302,7 +325,7 @@ func (r *release) annotate(storageBackend string, annotations ...string) { // A protected is release is either: a) deployed in a protected namespace b) flagged as protected in the desired state file // Any release in a protected namespace is protected by default regardless of its flag // returns true if a release is protected, false otherwise -func (r *release) isProtected(cs *currentState, n *namespace) bool { +func (r *Release) isProtected(cs *currentState, n *Namespace) bool { // if the release does not exist in the cluster, it is not protected if ok := cs.releaseExists(r, ""); !ok { return false @@ -314,7 +337,7 @@ func (r *release) isProtected(cs *currentState, n *namespace) bool { } // getNoHooks returns the no-hooks flag for install/upgrade commands -func (r *release) getNoHooks() []string { +func (r *Release) getNoHooks() []string { if r.NoHooks { return []string{"--no-hooks"} } @@ -322,7 +345,7 @@ func (r *release) getNoHooks() []string { } // getTimeout returns the timeout flag for install/upgrade commands -func (r *release) getTimeout() []string { +func (r *Release) getTimeout() []string { if r.Timeout != 0 { return []string{"--timeout", strconv.Itoa(r.Timeout) + "s"} } @@ -330,7 +353,7 @@ func (r *release) getTimeout() []string { } // getSetValues returns --set params to be used with helm install/upgrade commands -func (r *release) getSetValues() []string { +func (r *Release) getSetValues() []string { res := []string{} for k, v := range r.Set { res = append(res, "--set", k+"="+strings.ReplaceAll(v, ",", "\\,")+"") @@ -339,7 +362,7 @@ func (r *release) getSetValues() []string { } // getSetStringValues returns --set-string params to be used with helm install/upgrade commands -func (r *release) getSetStringValues() []string { +func (r *Release) getSetStringValues() []string { res := []string{} for k, v := range r.SetString { res = append(res, "--set-string", k+"="+strings.ReplaceAll(v, ",", "\\,")+"") @@ -348,7 +371,7 @@ func (r *release) getSetStringValues() []string { } // getSetFileValues returns --set-file params to be used with helm install/upgrade commands -func (r *release) getSetFileValues() []string { +func (r *Release) getSetFileValues() []string { res := []string{} for k, v := range r.SetFile { res = append(res, "--set-file", k+"="+strings.ReplaceAll(v, ",", "\\,")+"") @@ -358,7 +381,7 @@ func (r *release) getSetFileValues() []string { // getWait returns a partial helm command containing the helm wait flag (--wait) if the wait flag for the release was set to true // Otherwise, retruns an empty string -func (r *release) getWait() []string { +func (r *Release) getWait() []string { res := []string{} if r.Wait { res = append(res, "--wait") @@ -367,12 +390,12 @@ func (r *release) getWait() []string { } // getDesiredNamespace returns the namespace of a release -func (r *release) getDesiredNamespace() string { +func (r *Release) getDesiredNamespace() string { return r.Namespace } // getMaxHistory returns the max-history flag for upgrade commands -func (r *release) getMaxHistory() []string { +func (r *Release) getMaxHistory() []string { if r.MaxHistory != 0 { return []string{"--history-max", strconv.Itoa(r.MaxHistory)} } @@ -380,7 +403,7 @@ func (r *release) getMaxHistory() []string { } // getHelmFlags returns helm flags -func (r *release) getHelmFlags() []string { +func (r *Release) getHelmFlags() []string { var flgs []string if flags.forceUpgrades { flgs = append(flgs, "--force") @@ -390,7 +413,7 @@ func (r *release) getHelmFlags() []string { } // getPostRenderer returns the post-renderer Helm flag -func (r *release) getPostRenderer() []string { +func (r *Release) getPostRenderer() []string { args := []string{} if r.PostRenderer != "" { args = append(args, "--post-renderer", r.PostRenderer) @@ -399,7 +422,7 @@ func (r *release) getPostRenderer() []string { } // getHelmArgsFor returns helm arguments for a specific helm operation -func (r *release) getHelmArgsFor(action string, optionalNamespaceOverride ...string) []string { +func (r *Release) getHelmArgsFor(action string, optionalNamespaceOverride ...string) []string { ns := r.Namespace if len(optionalNamespaceOverride) > 0 { ns = optionalNamespaceOverride[0] @@ -418,7 +441,7 @@ func (r *release) getHelmArgsFor(action string, optionalNamespaceOverride ...str } } -func (r *release) checkChartDepUpdate() { +func (r *Release) checkChartDepUpdate() { if !r.isConsideredToRun() { return } @@ -429,7 +452,7 @@ func (r *release) checkChartDepUpdate() { } } -func (r *release) checkChartForUpdates() { +func (r *Release) checkChartForUpdates() { if !r.isConsideredToRun() { return } @@ -447,14 +470,14 @@ func (r *release) checkChartForUpdates() { } // overrideNamespace overrides a release defined namespace with a new given one -func (r *release) overrideNamespace(newNs string) { +func (r *Release) overrideNamespace(newNs string) { log.Info("Overriding namespace for app: " + r.Name) r.Namespace = newNs } // inheritHooks passes global hooks config from the state to the release hooks if they are unset // release hooks override the global ones -func (r *release) inheritHooks(s *state) { +func (r *Release) inheritHooks(s *State) { if len(s.Settings.GlobalHooks) != 0 { if len(r.Hooks) == 0 { r.Hooks = s.Settings.GlobalHooks @@ -469,7 +492,7 @@ func (r *release) inheritHooks(s *state) { } // inheritMaxHistory passes global max history from the state to the release if it is unset -func (r *release) inheritMaxHistory(s *state) { +func (r *Release) inheritMaxHistory(s *State) { if s.Settings.GlobalMaxHistory != 0 { if r.MaxHistory == 0 { r.MaxHistory = s.Settings.GlobalMaxHistory @@ -480,7 +503,7 @@ func (r *release) inheritMaxHistory(s *state) { // checkHooks checks if a hook of certain type exists and creates its command // if success condition for the hook is defined, a "kubectl wait" command is created // returns two slices of before and after commands -func (r *release) checkHooks(action string, optionalNamespace ...string) ([]hookCmd, []hookCmd) { +func (r *Release) checkHooks(action string, optionalNamespace ...string) ([]hookCmd, []hookCmd) { ns := r.Namespace if len(optionalNamespace) > 0 { ns = optionalNamespace[0] @@ -519,7 +542,7 @@ func (r *release) checkHooks(action string, optionalNamespace ...string) ([]hook return beforeCmds, afterCmds } -func (r *release) getHookCommands(hookType, ns string) []hookCmd { +func (r *Release) getHookCommands(hookType, ns string) []hookCmd { var cmds []hookCmd if _, ok := r.Hooks[hookType]; ok { hook := r.Hooks[hookType].(string) @@ -546,7 +569,7 @@ func (r *release) getHookCommands(hookType, ns string) []hookCmd { // shouldWaitForHook checks if there is a success condition to wait for after applying a hook // returns a boolean and the wait command if applicable -func (r *release) shouldWaitForHook(hookFile string, hookType string, namespace string) (bool, []hookCmd) { +func (r *Release) shouldWaitForHook(hookFile string, hookType string, namespace string) (bool, []hookCmd) { var cmds []hookCmd if flags.dryRun { return false, cmds @@ -567,7 +590,7 @@ func (r *release) shouldWaitForHook(hookFile string, hookType string, namespace } // print prints the details of the release -func (r release) print() { +func (r Release) print() { fmt.Println("") fmt.Println("\tname: ", r.Name) fmt.Println("\tdescription: ", r.Description) diff --git a/internal/app/release_files.go b/internal/app/release_files.go index b3b10174..3d62fe76 100644 --- a/internal/app/release_files.go +++ b/internal/app/release_files.go @@ -5,7 +5,7 @@ import ( ) // substituteVarsInStaticFiles loops through the values/secrets files and substitutes variables into them. -func (r *release) substituteVarsInStaticFiles() { +func (r *Release) substituteVarsInStaticFiles() { if r.ValuesFile != "" { r.ValuesFile = substituteVarsInYaml(r.ValuesFile) } @@ -31,7 +31,7 @@ func (r *release) substituteVarsInStaticFiles() { } // resolvePaths resolves relative paths of certs/keys/chart/value file/secret files/etc and replace them with a absolute paths -func (r *release) resolvePaths(dir, downloadDest string) { +func (r *Release) resolvePaths(dir, downloadDest string) { if r.ValuesFile != "" { r.ValuesFile, _ = resolveOnePath(r.ValuesFile, dir, downloadDest) } @@ -58,7 +58,7 @@ func (r *release) resolvePaths(dir, downloadDest string) { } // getValuesFiles return partial install/upgrade release command to substitute the -f flag in Helm. -func (r *release) getValuesFiles() []string { +func (r *Release) getValuesFiles() []string { var fileList []string if r.ValuesFile != "" { diff --git a/internal/app/release_test.go b/internal/app/release_test.go index 253f0233..3b6a4b35 100644 --- a/internal/app/release_test.go +++ b/internal/app/release_test.go @@ -5,18 +5,18 @@ import ( ) func Test_release_validate(t *testing.T) { - st := state{ + st := State{ Metadata: make(map[string]string), Certificates: make(map[string]string), - Settings: (config{}), - Namespaces: map[string]*namespace{"namespace": {false, limits{}, make(map[string]string), make(map[string]string), "as{}, false}}, + Settings: (Config{}), + Namespaces: map[string]*Namespace{"namespace": {false, Limits{}, make(map[string]string), make(map[string]string), &Quotas{}, false}}, HelmRepos: make(map[string]string), - Apps: make(map[string]*release), + Apps: make(map[string]*Release), } type args struct { - s state - r *release + s State + r *Release } tests := []struct { name string @@ -26,7 +26,7 @@ func Test_release_validate(t *testing.T) { { name: "test case 1", args: args{ - r: &release{ + r: &Release{ Name: "release1", Description: "", Namespace: "namespace", @@ -42,7 +42,7 @@ func Test_release_validate(t *testing.T) { }, { name: "test case 2", args: args{ - r: &release{ + r: &Release{ Name: "release2", Description: "", Namespace: "namespace", @@ -58,7 +58,7 @@ func Test_release_validate(t *testing.T) { }, { name: "test case 3", args: args{ - r: &release{ + r: &Release{ Name: "release3", Description: "", Namespace: "namespace", @@ -74,7 +74,7 @@ func Test_release_validate(t *testing.T) { }, { name: "test case 4", args: args{ - r: &release{ + r: &Release{ Name: "release1", Description: "", Namespace: "namespace", @@ -90,7 +90,7 @@ func Test_release_validate(t *testing.T) { }, { name: "test case 5", args: args{ - r: &release{ + r: &Release{ Name: "", Description: "", Namespace: "namespace", @@ -106,7 +106,7 @@ func Test_release_validate(t *testing.T) { }, { name: "test case 6", args: args{ - r: &release{ + r: &Release{ Name: "release6", Description: "", Namespace: "", @@ -122,7 +122,7 @@ func Test_release_validate(t *testing.T) { }, { name: "test case 7", args: args{ - r: &release{ + r: &Release{ Name: "release7", Description: "", Namespace: "namespace", @@ -138,7 +138,7 @@ func Test_release_validate(t *testing.T) { }, { name: "test case 8", args: args{ - r: &release{ + r: &Release{ Name: "release8", Description: "", Namespace: "namespace", @@ -154,7 +154,7 @@ func Test_release_validate(t *testing.T) { }, { name: "test case 9", args: args{ - r: &release{ + r: &Release{ Name: "release9", Description: "", Namespace: "namespace", @@ -170,7 +170,7 @@ func Test_release_validate(t *testing.T) { }, { name: "test case 10", args: args{ - r: &release{ + r: &Release{ Name: "release10", Description: "", Namespace: "namespace", @@ -186,7 +186,7 @@ func Test_release_validate(t *testing.T) { }, { name: "test case 11", args: args{ - r: &release{ + r: &Release{ Name: "release11", Description: "", Namespace: "namespace", @@ -203,7 +203,7 @@ func Test_release_validate(t *testing.T) { }, { name: "test case 12", args: args{ - r: &release{ + r: &Release{ Name: "release12", Description: "", Namespace: "namespace", @@ -219,7 +219,7 @@ func Test_release_validate(t *testing.T) { }, { name: "test case 13", args: args{ - r: &release{ + r: &Release{ Name: "release13", Description: "", Namespace: "namespace", @@ -235,7 +235,7 @@ func Test_release_validate(t *testing.T) { }, { name: "test case 14 - non-existing hook file", args: args{ - r: &release{ + r: &Release{ Name: "release14", Description: "", Namespace: "namespace", @@ -251,7 +251,7 @@ func Test_release_validate(t *testing.T) { }, { name: "test case 15 - invalid hook file type", args: args{ - r: &release{ + r: &Release{ Name: "release15", Description: "", Namespace: "namespace", @@ -267,7 +267,7 @@ func Test_release_validate(t *testing.T) { }, { name: "test case 16 - valid hook file type", args: args{ - r: &release{ + r: &Release{ Name: "release16", Description: "", Namespace: "namespace", @@ -283,7 +283,7 @@ func Test_release_validate(t *testing.T) { }, { name: "test case 17 - valid hook file URL", args: args{ - r: &release{ + r: &Release{ Name: "release17", Description: "", Namespace: "namespace", @@ -299,7 +299,7 @@ func Test_release_validate(t *testing.T) { }, { name: "test case 18 - invalid hook file URL", args: args{ - r: &release{ + r: &Release{ Name: "release18", Description: "", Namespace: "namespace", @@ -315,7 +315,7 @@ func Test_release_validate(t *testing.T) { }, { name: "test case 19 - invalid hook type 1", args: args{ - r: &release{ + r: &Release{ Name: "release19", Description: "", Namespace: "namespace", @@ -331,7 +331,7 @@ func Test_release_validate(t *testing.T) { }, { name: "test case 20 - invalid hook type 2", args: args{ - r: &release{ + r: &Release{ Name: "release20", Description: "", Namespace: "namespace", @@ -347,7 +347,7 @@ func Test_release_validate(t *testing.T) { }, { name: "test case 21", args: args{ - r: &release{ + r: &Release{ Name: "release21", Description: "", Namespace: "namespace", @@ -364,7 +364,7 @@ func Test_release_validate(t *testing.T) { }, { name: "test case 22", args: args{ - r: &release{ + r: &Release{ Name: "release22", Description: "", Namespace: "namespace", @@ -381,7 +381,7 @@ func Test_release_validate(t *testing.T) { }, { name: "test case 23 - executable hook type", args: args{ - r: &release{ + r: &Release{ Name: "release20", Description: "", Namespace: "namespace", @@ -411,10 +411,10 @@ func Test_release_validate(t *testing.T) { } func Test_release_inheritHooks(t *testing.T) { - st := state{ + st := State{ Metadata: make(map[string]string), Certificates: make(map[string]string), - Settings: config{ + Settings: Config{ GlobalHooks: map[string]interface{}{ preInstall: "https://raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml", postInstall: "https://raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml", @@ -422,14 +422,14 @@ func Test_release_inheritHooks(t *testing.T) { "successTimeout": "60s", }, }, - Namespaces: map[string]*namespace{"namespace": {false, limits{}, make(map[string]string), make(map[string]string), "as{}, false}}, + Namespaces: map[string]*Namespace{"namespace": {false, Limits{}, make(map[string]string), make(map[string]string), &Quotas{}, false}}, HelmRepos: make(map[string]string), - Apps: make(map[string]*release), + Apps: make(map[string]*Release), } type args struct { - s state - r *release + s State + r *Release } tests := []struct { name string @@ -439,7 +439,7 @@ func Test_release_inheritHooks(t *testing.T) { { name: "test case 1", args: args{ - r: &release{ + r: &Release{ Name: "release1 - Global hooks correctly inherited", Description: "", Namespace: "namespace", diff --git a/internal/app/state.go b/internal/app/state.go index 0171af05..d6b80f5b 100644 --- a/internal/app/state.go +++ b/internal/app/state.go @@ -10,50 +10,78 @@ import ( "sync" ) -// config type represents the settings fields -type config struct { - KubeContext string `yaml:"kubeContext"` - Username string `yaml:"username"` - Password string `yaml:"password"` - ClusterURI string `yaml:"clusterURI"` - ServiceAccount string `yaml:"serviceAccount"` - StorageBackend string `yaml:"storageBackend"` - SlackWebhook string `yaml:"slackWebhook"` - MSTeamsWebhook string `yaml:"msTeamsWebhook"` - ReverseDelete bool `yaml:"reverseDelete"` - BearerToken bool `yaml:"bearerToken"` - BearerTokenPath string `yaml:"bearerTokenPath"` - NamespaceLabelsAuthoritative bool `yaml:"namespaceLabelsAuthoritative"` - EyamlEnabled bool `yaml:"eyamlEnabled"` - EyamlPrivateKeyPath string `yaml:"eyamlPrivateKeyPath"` - EyamlPublicKeyPath string `yaml:"eyamlPublicKeyPath"` - GlobalHooks map[string]interface{} `yaml:"globalHooks"` - GlobalMaxHistory int `yaml:"globalMaxHistory"` - SkipIgnoredApps bool `yaml:"skipIgnoredApps"` - SkipPendingApps bool `yaml:"skipPendingApps"` +// Config type represents the settings fields +type Config struct { + // KubeContext is the kube context you want Helmsman to use or create + KubeContext string `yaml:"kubeContext,omitempty"` + // Username to be used for kubectl credentials + Username string `yaml:"username,omitempty"` + // Password to be used for kubectl credentials + Password string `yaml:"password,omitempty"` + // ClusterURI is the URI for your cluster API or the name of an environment variable (starting with `$`) containing the URI + ClusterURI string `yaml:"clusterURI,omitempty"` + // ServiceAccount to be used for tiller (deprecated) + ServiceAccount string `yaml:"serviceAccount,omitempty"` + // StorageBackend indicates the storage backened used by helm, defaults to secret + StorageBackend string `yaml:"storageBackend,omitempty"` + // SlackWebhook is the slack webhook URL for slack notifications + SlackWebhook string `yaml:"slackWebhook,omitempty"` + // MSTeamsWebhook is the Microsoft teams webhook URL for teams notifications + MSTeamsWebhook string `yaml:"msTeamsWebhook,omitempty"` + // ReverseDelete indicates if the applications should be deleted in reverse orderin relation to the installation order + ReverseDelete bool `yaml:"reverseDelete,omitempty"` + // BearerToken indicates whether you want helmsman to connect to the cluster using a bearer token + BearerToken bool `yaml:"bearerToken,omitempty"` + // BearerTokenPath allows specifying a custom path for the token + BearerTokenPath string `yaml:"bearerTokenPath,omitempty"` + // NamespaceLabelsAuthoritativei indicates whether helmsman should remove namespace labels that are not in the DSF + NamespaceLabelsAuthoritative bool `yaml:"namespaceLabelsAuthoritative,omitempty"` + // EyamlEnabled indicates whether eyaml is used for encrypted files + EyamlEnabled bool `yaml:"eyamlEnabled,omitempty"` + // EyamlPrivateKeyPath is the path to the eyaml private key + EyamlPrivateKeyPath string `yaml:"eyamlPrivateKeyPath,omitempty"` + // EyamlPublicKeyPath is the path to the eyaml public key + EyamlPublicKeyPath string `yaml:"eyamlPublicKeyPath,omitempty"` + // GlobalHooks is a set of global lifecycle hooks + GlobalHooks map[string]interface{} `yaml:"globalHooks,omitempty"` + // GlobalMaxHistory sets the global max number of historical release revisions to keep + GlobalMaxHistory int `yaml:"globalMaxHistory,omitempty"` + // SkipIgnoredApps if set to true, ignored apps will not be considered in the plan + SkipIgnoredApps bool `yaml:"skipIgnoredApps,omitempty"` + // SkipPendingApps is set to true,apps in a pending state will be ignored + SkipPendingApps bool `yaml:"skipPendingApps,omitempty"` } -// state type represents the desired state of applications on a k8s cluster. -type state struct { - Metadata map[string]string `yaml:"metadata"` - Certificates map[string]string `yaml:"certificates"` - Settings config `yaml:"settings"` - Context string `yaml:"context"` - Namespaces map[string]*namespace `yaml:"namespaces"` - HelmRepos map[string]string `yaml:"helmRepos"` - PreconfiguredHelmRepos []string `yaml:"preconfiguredHelmRepos"` - Apps map[string]*release `yaml:"apps"` - AppsTemplates map[string]*release `yaml:"appsTemplates,omitempty"` - TargetMap map[string]bool - ChartInfo map[string]map[string]*chartInfo +// State type represents the desired State of applications on a k8s cluster. +type State struct { + // Metadata for human reader of the desired state file + Metadata map[string]string `yaml:"metadata,omitempty"` + // Certificates are used to connect kubectl to a cluster + Certificates map[string]string `yaml:"certificates,omitempty"` + // Settings for configuring helmsman + Settings Config `yaml:"settings,omitempty"` + // Context defines an helmsman scope + Context string `yaml:"context,omitempty"` + // HelmRepos from where to find the application helm charts + HelmRepos map[string]string `yaml:"helmRepos,omitempty"` + // PreconfiguredHelmRepos is a list of helm repos that are configured outside of the DSF + PreconfiguredHelmRepos []string `yaml:"preconfiguredHelmRepos,omitempty"` + // Namespaces where helmsman will deploy applications + Namespaces map[string]*Namespace `yaml:"namespaces"` + // Apps holds the configuration for each helm release managed by helmsman + Apps map[string]*Release `yaml:"apps"` + // AppsTemplates allow defining YAML objects thatcan be used as a reference with YAML anchors to keep the configuration DRY + AppsTemplates map[string]*Release `yaml:"appsTemplates,omitempty"` + targetMap map[string]bool + chartInfo map[string]map[string]*ChartInfo } -func (s *state) init() { +func (s *State) init() { s.setDefaults() s.initializeNamespaces() } -func (s *state) setDefaults() { +func (s *State) setDefaults() { if s.Settings.StorageBackend != "" { os.Setenv("HELM_DRIVER", s.Settings.StorageBackend) } else { @@ -77,17 +105,17 @@ func (s *state) setDefaults() { } } -func (s *state) initializeNamespaces() { +func (s *State) initializeNamespaces() { for nsName, ns := range s.Namespaces { if ns == nil { - s.Namespaces[nsName] = &namespace{} + s.Namespaces[nsName] = &Namespace{} } } } // validate validates that the values specified in the desired state are valid according to the desired state spec. // check https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md for the detailed specification -func (s *state) validate() error { +func (s *State) validate() error { // apps if s.Apps == nil { log.Info("No apps specified. Nothing to be executed.") @@ -96,7 +124,7 @@ func (s *state) validate() error { // settings // use reflect.DeepEqual to compare Settings are empty, since it contains a map - if (reflect.DeepEqual(s.Settings, config{}) || s.Settings.KubeContext == "") && !getKubeContext() { + if (reflect.DeepEqual(s.Settings, Config{}) || s.Settings.KubeContext == "") && !getKubeContext() { return errors.New("settings validation failed -- you have not defined a " + "kubeContext to use. Either define it in the desired state file or pass a kubeconfig with --kubeconfig to use an existing context") } @@ -210,7 +238,7 @@ func (s *state) validate() error { // getReleaseChartsInfo retrieves valid chart information. // Valid charts are the ones that can be found in the defined repos. -func (s *state) getReleaseChartsInfo() error { +func (s *State) getReleaseChartsInfo() error { var fail bool wg := sync.WaitGroup{} mutex := sync.Mutex{} @@ -218,7 +246,7 @@ func (s *state) getReleaseChartsInfo() error { chartErrors := make(chan error, len(s.Apps)) charts := make(map[string]map[string][]string) - s.ChartInfo = make(map[string]map[string]*chartInfo) + s.chartInfo = make(map[string]map[string]*ChartInfo) for app, r := range s.Apps { if !r.isConsideredToRun() { @@ -233,8 +261,8 @@ func (s *state) getReleaseChartsInfo() error { charts[r.Chart][r.Version] = make([]string, 0) } - if s.ChartInfo[r.Chart] == nil { - s.ChartInfo[r.Chart] = make(map[string]*chartInfo) + if s.chartInfo[r.Chart] == nil { + s.chartInfo[r.Chart] = make(map[string]*ChartInfo) } charts[r.Chart][r.Version] = append(charts[r.Chart][r.Version], app) @@ -258,7 +286,7 @@ func (s *state) getReleaseChartsInfo() error { } else { log.Verbose(fmt.Sprintf("Extracted chart information from chart [ %s ] with version [ %s ]: %s %s", chart, version, info.Name, info.Version)) mutex.Lock() - s.ChartInfo[chart][version] = info + s.chartInfo[chart][version] = info mutex.Unlock() } @@ -282,13 +310,13 @@ func (s *state) getReleaseChartsInfo() error { } // isNamespaceDefined checks if a given namespace is defined in the namespaces section of the desired state file -func (s *state) isNamespaceDefined(ns string) bool { +func (s *State) isNamespaceDefined(ns string) bool { _, ok := s.Namespaces[ns] return ok } // overrideAppsNamespace replaces all apps namespaces with one specific namespace -func (s *state) overrideAppsNamespace(newNs string) { +func (s *State) overrideAppsNamespace(newNs string) { log.Info("Overriding apps namespaces with [ " + newNs + " ] ...") for _, r := range s.Apps { r.overrideNamespace(newNs) @@ -297,7 +325,7 @@ func (s *state) overrideAppsNamespace(newNs string) { // disable Apps defined as excluded by either their name or their group // then get only those Apps that exist in TargetMap -func (s *state) disableApps(groups, targets, groupsExcluded, targetsExcluded []string) { +func (s *State) disableApps(groups, targets, groupsExcluded, targetsExcluded []string) { excludeAppsLoop: for appName, app := range s.Apps { for _, groupExcluded := range groupsExcluded { @@ -313,14 +341,14 @@ excludeAppsLoop: } } } - if s.TargetMap == nil { - s.TargetMap = make(map[string]bool) + if s.targetMap == nil { + s.targetMap = make(map[string]bool) } if len(targets) == 0 && len(groups) == 0 { return } for _, t := range targets { - s.TargetMap[t] = true + s.targetMap[t] = true } groupMap := make(map[string]struct{}) namespaces := make(map[string]struct{}) @@ -328,12 +356,12 @@ excludeAppsLoop: groupMap[g] = struct{}{} } for appName, app := range s.Apps { - if _, ok := s.TargetMap[appName]; ok { + if _, ok := s.targetMap[appName]; ok { namespaces[app.Namespace] = struct{}{} continue } if _, ok := groupMap[app.Group]; ok { - s.TargetMap[appName] = true + s.targetMap[appName] = true namespaces[app.Namespace] = struct{}{} } else { app.Disable() @@ -351,7 +379,7 @@ excludeAppsLoop: } // updateContextLabels applies Helmsman labels including overriding any previously-set context with the one found in the DSF -func (s *state) updateContextLabels() { +func (s *State) updateContextLabels() { for _, r := range s.Apps { if r.isConsideredToRun() { log.Info("Updating context and reapplying Helmsman labels for release [ " + r.Name + " ]") @@ -363,7 +391,7 @@ func (s *state) updateContextLabels() { } // print prints the desired state -func (s *state) print() { +func (s *State) print() { fmt.Println("\nMetadata: ") fmt.Println("--------- ") printMap(s.Metadata, 0) @@ -389,7 +417,7 @@ func (s *state) print() { } fmt.Println("\nTargets: ") fmt.Println("--------------- ") - for t := range s.TargetMap { + for t := range s.targetMap { fmt.Println(t) } } diff --git a/internal/app/state_files.go b/internal/app/state_files.go index 97da8117..c2236451 100644 --- a/internal/app/state_files.go +++ b/internal/app/state_files.go @@ -14,7 +14,7 @@ import ( ) // invokes either yaml or toml parser considering file extension -func (s *state) fromFile(file string) error { +func (s *State) fromFile(file string) error { if isOfType(file, []string{".toml", ".tml"}) { return s.fromTOML(file) } else if isOfType(file, []string{".yaml", ".yml"}) { @@ -24,7 +24,7 @@ func (s *state) fromFile(file string) error { } } -func (s *state) toFile(file string) { +func (s *State) toFile(file string) { if isOfType(file, []string{".toml"}) { s.toTOML(file) } else if isOfType(file, []string{".yaml", ".yml"}) { @@ -36,7 +36,7 @@ func (s *state) toFile(file string) { // fromTOML reads a toml file and decodes it to a state type. // It uses the BurntSuchi TOML parser which throws an error if the TOML file is not valid. -func (s *state) fromTOML(file string) error { +func (s *State) fromTOML(file string) error { rawTomlFile, err := ioutil.ReadFile(file) if err != nil { return err @@ -61,7 +61,7 @@ func (s *state) fromTOML(file string) error { // toTOML encodes a state type into a TOML file. // It uses the BurntSuchi TOML parser. -func (s *state) toTOML(file string) { +func (s *State) toTOML(file string) { log.Info("Printing generated toml ... ") var buff bytes.Buffer var ( @@ -87,7 +87,7 @@ func (s *state) toTOML(file string) { // fromYAML reads a yaml file and decodes it to a state type. // parser which throws an error if the YAML file is not valid. -func (s *state) fromYAML(file string) error { +func (s *State) fromYAML(file string) error { rawYamlFile, err := ioutil.ReadFile(file) if err != nil { return err @@ -112,7 +112,7 @@ func (s *state) fromYAML(file string) error { } // toYaml encodes a state type into a YAML file -func (s *state) toYAML(file string) { +func (s *State) toYAML(file string) { log.Info("Printing generated yaml ... ") var buff bytes.Buffer var ( @@ -136,9 +136,9 @@ func (s *state) toYAML(file string) { newFile.Close() } -func (s *state) build(files fileOptionArray) error { +func (s *State) build(files fileOptionArray) error { for _, f := range files { - var fileState state + var fileState State if err := fileState.fromFile(f.name); err != nil { return err @@ -173,7 +173,7 @@ func (s *state) build(files fileOptionArray) error { return fmt.Errorf("failed to merge desired state file %s: %w", f.name, err) } // All the apps are already merged, make fileState.Apps empty to avoid conflicts in the final merge - fileState.Apps = make(map[string]*release) + fileState.Apps = make(map[string]*Release) if err := mergo.Merge(s, &fileState, mergo.WithAppendSlice, mergo.WithOverride); err != nil { return fmt.Errorf("failed to merge desired state file %s: %w", f.name, err) @@ -186,7 +186,7 @@ func (s *state) build(files fileOptionArray) error { // expand resolves relative paths of certs/keys/chart/value file/secret files/etc and replace them with a absolute paths // it also loops through the values/secrets files and substitutes variables into them. -func (s *state) expand(relativeToFile string) { +func (s *State) expand(relativeToFile string) { dir := filepath.Dir(relativeToFile) downloadDest, _ := filepath.Abs(createTempDir(tempFilesDir, "tmp")) validProtocols := []string{"http", "https"} @@ -243,7 +243,7 @@ func (s *state) expand(relativeToFile string) { } // isChartFromRepo checks if the chart is from a known repo -func (s *state) isChartFromRepo(chart string) bool { +func (s *State) isChartFromRepo(chart string) bool { repoName := strings.Split(chart, "/")[0] if _, isRepo := s.HelmRepos[repoName]; isRepo { return true @@ -254,7 +254,7 @@ func (s *state) isChartFromRepo(chart string) bool { // cleanup deletes the k8s certificates and keys files // It also deletes any Tiller TLS certs and keys // and secret files -func (s *state) cleanup() { +func (s *State) cleanup() { log.Verbose("Cleaning up sensitive and temp files") if _, err := os.Stat("ca.crt"); err == nil { deleteFile("ca.crt") diff --git a/internal/app/state_files_test.go b/internal/app/state_files_test.go index 610d9d48..b7ec6b22 100644 --- a/internal/app/state_files_test.go +++ b/internal/app/state_files_test.go @@ -22,7 +22,7 @@ func setupStateFileTestCase(t *testing.T) (func(t *testing.T), error) { func Test_fromTOML(t *testing.T) { type args struct { file string - s *state + s *State } tests := []struct { name string @@ -33,14 +33,14 @@ func Test_fromTOML(t *testing.T) { name: "test case 1 -- invalid TOML", args: args{ file: "../../../tests/invalid_example.toml", - s: new(state), + s: new(State), }, want: false, }, { name: "test case 2 -- valid TOML", args: args{ file: "../../examples/example.toml", - s: new(state), + s: new(State), }, want: true, }, @@ -69,7 +69,7 @@ func Test_fromTOML(t *testing.T) { func Test_fromTOML_Expand(t *testing.T) { type args struct { file string - s *state + s *State } tests := []struct { name string @@ -82,7 +82,7 @@ func Test_fromTOML_Expand(t *testing.T) { name: "test case 1 -- valid TOML expand ClusterURI", args: args{ file: "../../examples/example.toml", - s: new(state), + s: new(State), }, section: "Settings", field: "ClusterURI", @@ -92,7 +92,7 @@ func Test_fromTOML_Expand(t *testing.T) { name: "test case 2 -- valid TOML expand org", args: args{ file: "../../examples/example.toml", - s: new(state), + s: new(State), }, section: "Metadata", field: "org", @@ -162,7 +162,7 @@ func Test_fromTOML_Expand(t *testing.T) { func Test_fromYAML(t *testing.T) { type args struct { file string - s *state + s *State } tests := []struct { name string @@ -173,14 +173,14 @@ func Test_fromYAML(t *testing.T) { name: "test case 1 -- invalid YAML", args: args{ file: "../../tests/invalid_example.yaml", - s: new(state), + s: new(State), }, want: false, }, { name: "test case 2 -- valid TOML", args: args{ file: "../../examples/example.yaml", - s: new(state), + s: new(State), }, want: true, }, @@ -209,7 +209,7 @@ func Test_fromYAML(t *testing.T) { func Test_fromYAML_UnsetVars(t *testing.T) { type args struct { file string - s *state + s *State } tests := []struct { name string @@ -221,7 +221,7 @@ func Test_fromYAML_UnsetVars(t *testing.T) { name: "test case 1 -- unset ORG_PATH env var", args: args{ file: "../../examples/example.yaml", - s: new(state), + s: new(State), }, targetVar: "ORG_PATH", want: false, @@ -230,7 +230,7 @@ func Test_fromYAML_UnsetVars(t *testing.T) { name: "test case 2 -- unset VALUE var", args: args{ file: "../../examples/example.yaml", - s: new(state), + s: new(State), }, targetVar: "VALUE", want: false, @@ -264,7 +264,7 @@ func Test_fromYAML_UnsetVars(t *testing.T) { func Test_fromYAML_Expand(t *testing.T) { type args struct { file string - s *state + s *State } tests := []struct { name string @@ -277,7 +277,7 @@ func Test_fromYAML_Expand(t *testing.T) { name: "test case 1 -- valid YAML expand ClusterURI", args: args{ file: "../../examples/example.yaml", - s: new(state), + s: new(State), }, section: "Settings", field: "ClusterURI", @@ -287,7 +287,7 @@ func Test_fromYAML_Expand(t *testing.T) { name: "test case 2 -- valid YAML expand org", args: args{ file: "../../examples/example.yaml", - s: new(state), + s: new(State), }, section: "Metadata", field: "org", @@ -360,7 +360,7 @@ func Test_build(t *testing.T) { t.Errorf("setupStateFileTestCase(), got: %v", err) } defer teardownTestCase(t) - s := new(state) + s := new(State) files := fileOptionArray{ fileOption{name: "../../examples/composition/main.yaml"}, fileOption{name: "../../examples/composition/kyverno.yaml"}, diff --git a/internal/app/state_test.go b/internal/app/state_test.go index e827a8bf..57f71277 100644 --- a/internal/app/state_test.go +++ b/internal/app/state_test.go @@ -32,10 +32,10 @@ func Test_state_validate(t *testing.T) { type fields struct { Metadata map[string]string Certificates map[string]string - Settings config - Namespaces map[string]*namespace + Settings Config + Namespaces map[string]*Namespace HelmRepos map[string]string - Apps map[string]*release + Apps map[string]*Release } tests := []struct { name string @@ -50,20 +50,20 @@ func Test_state_validate(t *testing.T) { "caCrt": "s3://some-bucket/12345.crt", "caKey": "s3://some-bucket/12345.key", }, - Settings: config{ + Settings: Config{ KubeContext: "minikube", Username: "admin", Password: "$K8S_PASSWORD", ClusterURI: "https://192.168.99.100:8443", }, - Namespaces: map[string]*namespace{ - "staging": {false, limits{}, make(map[string]string), make(map[string]string), "as{}, false}, + Namespaces: map[string]*Namespace{ + "staging": {false, Limits{}, make(map[string]string), make(map[string]string), &Quotas{}, false}, }, HelmRepos: map[string]string{ "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", "myrepo": "s3://my-repo/charts", }, - Apps: make(map[string]*release), + Apps: make(map[string]*Release), }, want: true, }, { @@ -74,20 +74,20 @@ func Test_state_validate(t *testing.T) { "caCrt": "s3://some-bucket/12345.crt", "caKey": "s3://some-bucket/12345.key", }, - Settings: config{ + Settings: Config{ KubeContext: "", Username: "admin", Password: "$K8S_PASSWORD", ClusterURI: "https://192.168.99.100:8443", }, - Namespaces: map[string]*namespace{ - "staging": {false, limits{}, make(map[string]string), make(map[string]string), "as{}, false}, + Namespaces: map[string]*Namespace{ + "staging": {false, Limits{}, make(map[string]string), make(map[string]string), &Quotas{}, false}, }, HelmRepos: map[string]string{ "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", "myrepo": "s3://my-repo/charts", }, - Apps: make(map[string]*release), + Apps: make(map[string]*Release), }, want: false, }, { @@ -98,17 +98,17 @@ func Test_state_validate(t *testing.T) { "caCrt": "s3://some-bucket/12345.crt", "caKey": "s3://some-bucket/12345.key", }, - Settings: config{ + Settings: Config{ KubeContext: "minikube", }, - Namespaces: map[string]*namespace{ - "staging": {false, limits{}, make(map[string]string), make(map[string]string), "as{}, false}, + Namespaces: map[string]*Namespace{ + "staging": {false, Limits{}, make(map[string]string), make(map[string]string), &Quotas{}, false}, }, HelmRepos: map[string]string{ "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", "myrepo": "s3://my-repo/charts", }, - Apps: make(map[string]*release), + Apps: make(map[string]*Release), }, want: true, }, { @@ -119,20 +119,20 @@ func Test_state_validate(t *testing.T) { "caCrt": "s3://some-bucket/12345.crt", "caKey": "s3://some-bucket/12345.key", }, - Settings: config{ + Settings: Config{ KubeContext: "minikube", Username: "admin", Password: "K8S_PASSWORD", ClusterURI: "https://192.168.99.100:8443", }, - Namespaces: map[string]*namespace{ - "staging": {false, limits{}, make(map[string]string), make(map[string]string), "as{}, false}, + Namespaces: map[string]*Namespace{ + "staging": {false, Limits{}, make(map[string]string), make(map[string]string), &Quotas{}, false}, }, HelmRepos: map[string]string{ "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", "myrepo": "s3://my-repo/charts", }, - Apps: make(map[string]*release), + Apps: make(map[string]*Release), }, want: true, }, { @@ -143,20 +143,20 @@ func Test_state_validate(t *testing.T) { "caCrt": "s3://some-bucket/12345.crt", "caKey": "s3://some-bucket/12345.key", }, - Settings: config{ + Settings: Config{ KubeContext: "minikube", Username: "admin", Password: "K8S_PASSWORD", ClusterURI: "$URI", // unset env }, - Namespaces: map[string]*namespace{ - "staging": {false, limits{}, make(map[string]string), make(map[string]string), "as{}, false}, + Namespaces: map[string]*Namespace{ + "staging": {false, Limits{}, make(map[string]string), make(map[string]string), &Quotas{}, false}, }, HelmRepos: map[string]string{ "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", "myrepo": "s3://my-repo/charts", }, - Apps: make(map[string]*release), + Apps: make(map[string]*Release), }, want: false, }, { @@ -167,20 +167,20 @@ func Test_state_validate(t *testing.T) { "caCrt": "s3://some-bucket/12345.crt", "caKey": "s3://some-bucket/12345.key", }, - Settings: config{ + Settings: Config{ KubeContext: "minikube", Username: "admin", Password: "K8S_PASSWORD", ClusterURI: "https//192.168.99.100:8443", // invalid url }, - Namespaces: map[string]*namespace{ - "staging": {false, limits{}, make(map[string]string), make(map[string]string), "as{}, false}, + Namespaces: map[string]*Namespace{ + "staging": {false, Limits{}, make(map[string]string), make(map[string]string), &Quotas{}, false}, }, HelmRepos: map[string]string{ "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", "myrepo": "s3://my-repo/charts", }, - Apps: make(map[string]*release), + Apps: make(map[string]*Release), }, want: false, }, { @@ -190,20 +190,20 @@ func Test_state_validate(t *testing.T) { Certificates: map[string]string{ "caCrt": "s3://some-bucket/12345.crt", }, - Settings: config{ + Settings: Config{ KubeContext: "minikube", Username: "admin", Password: "$K8S_PASSWORD", ClusterURI: "https://192.168.99.100:8443", }, - Namespaces: map[string]*namespace{ - "staging": {false, limits{}, make(map[string]string), make(map[string]string), "as{}, false}, + Namespaces: map[string]*Namespace{ + "staging": {false, Limits{}, make(map[string]string), make(map[string]string), &Quotas{}, false}, }, HelmRepos: map[string]string{ "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", "myrepo": "s3://my-repo/charts", }, - Apps: make(map[string]*release), + Apps: make(map[string]*Release), }, want: false, }, { @@ -211,20 +211,20 @@ func Test_state_validate(t *testing.T) { fields: fields{ Metadata: make(map[string]string), Certificates: nil, - Settings: config{ + Settings: Config{ KubeContext: "minikube", Username: "admin", Password: "$K8S_PASSWORD", ClusterURI: "https://192.168.99.100:8443", }, - Namespaces: map[string]*namespace{ - "staging": {false, limits{}, make(map[string]string), make(map[string]string), "as{}, false}, + Namespaces: map[string]*Namespace{ + "staging": {false, Limits{}, make(map[string]string), make(map[string]string), &Quotas{}, false}, }, HelmRepos: map[string]string{ "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", "myrepo": "s3://my-repo/charts", }, - Apps: make(map[string]*release), + Apps: make(map[string]*Release), }, want: false, }, { @@ -235,20 +235,20 @@ func Test_state_validate(t *testing.T) { "caCrt": "s3://some-bucket/12345.crt", "caKey": "http://someurl.com/", }, - Settings: config{ + Settings: Config{ KubeContext: "minikube", Username: "admin", Password: "$K8S_PASSWORD", ClusterURI: "https://192.168.99.100:8443", }, - Namespaces: map[string]*namespace{ - "staging": {false, limits{}, make(map[string]string), make(map[string]string), "as{}, false}, + Namespaces: map[string]*Namespace{ + "staging": {false, Limits{}, make(map[string]string), make(map[string]string), &Quotas{}, false}, }, HelmRepos: map[string]string{ "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", "myrepo": "s3://my-repo/charts", }, - Apps: make(map[string]*release), + Apps: make(map[string]*Release), }, want: false, }, { @@ -256,17 +256,17 @@ func Test_state_validate(t *testing.T) { fields: fields{ Metadata: make(map[string]string), Certificates: nil, - Settings: config{ + Settings: Config{ KubeContext: "minikube", }, - Namespaces: map[string]*namespace{ - "staging": {false, limits{}, make(map[string]string), make(map[string]string), "as{}, false}, + Namespaces: map[string]*Namespace{ + "staging": {false, Limits{}, make(map[string]string), make(map[string]string), &Quotas{}, false}, }, HelmRepos: map[string]string{ "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", "myrepo": "s3://my-repo/charts", }, - Apps: make(map[string]*release), + Apps: make(map[string]*Release), }, want: true, }, { @@ -274,7 +274,7 @@ func Test_state_validate(t *testing.T) { fields: fields{ Metadata: make(map[string]string), Certificates: nil, - Settings: config{ + Settings: Config{ KubeContext: "minikube", }, Namespaces: nil, @@ -282,7 +282,7 @@ func Test_state_validate(t *testing.T) { "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", "myrepo": "s3://my-repo/charts", }, - Apps: make(map[string]*release), + Apps: make(map[string]*Release), }, want: false, }, { @@ -290,15 +290,15 @@ func Test_state_validate(t *testing.T) { fields: fields{ Metadata: make(map[string]string), Certificates: nil, - Settings: config{ + Settings: Config{ KubeContext: "minikube", }, - Namespaces: map[string]*namespace{}, + Namespaces: map[string]*Namespace{}, HelmRepos: map[string]string{ "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", "myrepo": "s3://my-repo/charts", }, - Apps: make(map[string]*release), + Apps: make(map[string]*Release), }, want: false, }, { @@ -306,14 +306,14 @@ func Test_state_validate(t *testing.T) { fields: fields{ Metadata: make(map[string]string), Certificates: nil, - Settings: config{ + Settings: Config{ KubeContext: "minikube", }, - Namespaces: map[string]*namespace{ - "staging": {false, limits{}, make(map[string]string), make(map[string]string), "as{}, false}, + Namespaces: map[string]*Namespace{ + "staging": {false, Limits{}, make(map[string]string), make(map[string]string), &Quotas{}, false}, }, HelmRepos: nil, - Apps: make(map[string]*release), + Apps: make(map[string]*Release), }, want: true, }, { @@ -321,14 +321,14 @@ func Test_state_validate(t *testing.T) { fields: fields{ Metadata: make(map[string]string), Certificates: nil, - Settings: config{ + Settings: Config{ KubeContext: "minikube", }, - Namespaces: map[string]*namespace{ - "staging": {false, limits{}, make(map[string]string), make(map[string]string), "as{}, false}, + Namespaces: map[string]*Namespace{ + "staging": {false, Limits{}, make(map[string]string), make(map[string]string), &Quotas{}, false}, }, HelmRepos: map[string]string{}, - Apps: make(map[string]*release), + Apps: make(map[string]*Release), }, want: true, }, { @@ -336,17 +336,17 @@ func Test_state_validate(t *testing.T) { fields: fields{ Metadata: make(map[string]string), Certificates: nil, - Settings: config{ + Settings: Config{ KubeContext: "minikube", }, - Namespaces: map[string]*namespace{ - "staging": {false, limits{}, make(map[string]string), make(map[string]string), "as{}, false}, + Namespaces: map[string]*Namespace{ + "staging": {false, Limits{}, make(map[string]string), make(map[string]string), &Quotas{}, false}, }, HelmRepos: map[string]string{ "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", "myrepo": "", }, - Apps: make(map[string]*release), + Apps: make(map[string]*Release), }, want: false, }, { @@ -354,17 +354,17 @@ func Test_state_validate(t *testing.T) { fields: fields{ Metadata: make(map[string]string), Certificates: nil, - Settings: config{ + Settings: Config{ KubeContext: "minikube", }, - Namespaces: map[string]*namespace{ - "staging": {false, limits{}, make(map[string]string), make(map[string]string), "as{}, false}, + Namespaces: map[string]*Namespace{ + "staging": {false, Limits{}, make(map[string]string), make(map[string]string), &Quotas{}, false}, }, HelmRepos: map[string]string{ "deprecated-stable": "https://kubernetes-charts.storage.googleapis.com", "myrepo": "s3//my-repo/charts", }, - Apps: make(map[string]*release), + Apps: make(map[string]*Release), }, want: false, }, @@ -373,7 +373,7 @@ func Test_state_validate(t *testing.T) { os.Setenv("SET_URI", "https://192.168.99.100:8443") for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s := state{ + s := State{ Metadata: tt.fields.Metadata, Certificates: tt.fields.Certificates, Settings: tt.fields.Settings, @@ -396,8 +396,8 @@ func Test_state_validate(t *testing.T) { } } -func createFullReleasePointer(chart, version string) *release { - return &release{ +func createFullReleasePointer(chart, version string) *Release { + return &Release{ Name: "", Description: "", Namespace: "", @@ -424,7 +424,7 @@ func createFullReleasePointer(chart, version string) *release { func Test_state_getReleaseChartsInfo(t *testing.T) { type args struct { - apps map[string]*release + apps map[string]*Release } tests := []struct { @@ -437,7 +437,7 @@ func Test_state_getReleaseChartsInfo(t *testing.T) { { name: "test case 1: valid local path with no chart", args: args{ - apps: map[string]*release{ + apps: map[string]*Release{ "app": createFullReleasePointer(os.TempDir()+"/helmsman-tests/myapp", ""), }, }, @@ -445,7 +445,7 @@ func Test_state_getReleaseChartsInfo(t *testing.T) { }, { name: "test case 2: invalid local path", args: args{ - apps: map[string]*release{ + apps: map[string]*Release{ "app": createFullReleasePointer(os.TempDir()+"/does-not-exist/myapp", ""), }, }, @@ -453,7 +453,7 @@ func Test_state_getReleaseChartsInfo(t *testing.T) { }, { name: "test case 3: valid chart local path with whitespace", args: args{ - apps: map[string]*release{ + apps: map[string]*Release{ "app": createFullReleasePointer(os.TempDir()+"/helmsman-tests/dir-with space/myapp", "0.1.0"), }, }, @@ -461,7 +461,7 @@ func Test_state_getReleaseChartsInfo(t *testing.T) { }, { name: "test case 4: valid chart from repo", args: args{ - apps: map[string]*release{ + apps: map[string]*Release{ "app": createFullReleasePointer("prometheus-community/prometheus", "11.16.5"), }, }, @@ -470,7 +470,7 @@ func Test_state_getReleaseChartsInfo(t *testing.T) { name: "test case 5: invalid local path for chart ignored with -target flag, while other app was targeted", targetFlag: []string{"notThisOne"}, args: args{ - apps: map[string]*release{ + apps: map[string]*Release{ "app": createFullReleasePointer(os.TempDir()+"/does-not-exist/myapp", ""), }, }, @@ -479,7 +479,7 @@ func Test_state_getReleaseChartsInfo(t *testing.T) { name: "test case 6: invalid local path for chart included with -target flag", targetFlag: []string{"app"}, args: args{ - apps: map[string]*release{ + apps: map[string]*Release{ "app": createFullReleasePointer(os.TempDir()+"/does-not-exist/myapp", ""), }, }, @@ -488,7 +488,7 @@ func Test_state_getReleaseChartsInfo(t *testing.T) { name: "test case 7: multiple valid local apps with the same chart version", targetFlag: []string{"app"}, args: args{ - apps: map[string]*release{ + apps: map[string]*Release{ "app1": createFullReleasePointer(os.TempDir()+"/helmsman-tests/dir-with space/myapp", "0.1.0"), "app2": createFullReleasePointer(os.TempDir()+"/helmsman-tests/dir-with space/myapp", "0.1.0"), "app3": createFullReleasePointer(os.TempDir()+"/helmsman-tests/dir-with space/myapp", "0.1.0"), @@ -508,7 +508,7 @@ func Test_state_getReleaseChartsInfo(t *testing.T) { defer teardownTestCase(t) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - stt := &state{Apps: tt.args.apps} + stt := &State{Apps: tt.args.apps} stt.disableApps(tt.groupFlag, tt.targetFlag, []string{}, []string{}) err := stt.getReleaseChartsInfo() switch err.(type) { diff --git a/internal/app/utils.go b/internal/app/utils.go index 91037167..8e05f2e4 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -39,7 +39,7 @@ func printMap(m map[string]string, indent int) { } // printObjectMap prints to the console any map of string keys and object values. -func printNamespacesMap(m map[string]*namespace) { +func printNamespacesMap(m map[string]*Namespace) { for name, ns := range m { fmt.Println(name, ":") ns.print() diff --git a/internal/app/utils_test.go b/internal/app/utils_test.go index 57a0228f..109e0821 100644 --- a/internal/app/utils_test.go +++ b/internal/app/utils_test.go @@ -178,8 +178,8 @@ func Test_readFile(t *testing.T) { func Test_eyamlSecrets(t *testing.T) { type args struct { - r *release - s *config + r *Release + s *Config } tests := []struct { name string @@ -189,12 +189,12 @@ func Test_eyamlSecrets(t *testing.T) { { name: "decryptSecrets - valid eyaml-based secrets decryption", args: args{ - s: &config{ + s: &Config{ EyamlEnabled: true, EyamlPublicKeyPath: "./../../tests/keys/public_key.pkcs7.pem", EyamlPrivateKeyPath: "./../../tests/keys/private_key.pkcs7.pem", }, - r: &release{ + r: &Release{ Name: "release1", Namespace: "namespace", Version: "1.0.0", @@ -207,12 +207,12 @@ func Test_eyamlSecrets(t *testing.T) { { name: "decryptSecrets - not existing eyaml-based secrets file", args: args{ - s: &config{ + s: &Config{ EyamlEnabled: true, EyamlPublicKeyPath: "./../../tests/keys/public_key.pkcs7.pem", EyamlPrivateKeyPath: "./../../tests/keys/private_key.pkcs7.pem", }, - r: &release{ + r: &Release{ Name: "release1", Namespace: "namespace", Version: "1.0.0", @@ -225,12 +225,12 @@ func Test_eyamlSecrets(t *testing.T) { { name: "decryptSecrets - not existing eyaml key", args: args{ - s: &config{ + s: &Config{ EyamlEnabled: true, EyamlPublicKeyPath: "./../../tests/keys/public_key.pkcs7.pem2", EyamlPrivateKeyPath: "./../../tests/keys/private_key.pkcs7.pem", }, - r: &release{ + r: &Release{ Name: "release1", Namespace: "namespace", Version: "1.0.0", @@ -244,7 +244,7 @@ func Test_eyamlSecrets(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Log(tt.want) - settings = &config{} + settings = &Config{} settings.EyamlEnabled = tt.args.s.EyamlEnabled settings.EyamlPublicKeyPath = tt.args.s.EyamlPublicKeyPath settings.EyamlPrivateKeyPath = tt.args.s.EyamlPrivateKeyPath From bfb9f1b735e8b8ef17d27c8af79ceb46714eed02 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sat, 11 Jun 2022 17:30:25 +0100 Subject: [PATCH 0877/1127] feat: add json schema generator --- Makefile | 7 +- go.mod | 2 + go.sum | 5 + schema.go | 22 +++ schema.json | 441 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 475 insertions(+), 2 deletions(-) create mode 100644 schema.go create mode 100644 schema.json diff --git a/Makefile b/Makefile index 592035e6..37cc39ea 100644 --- a/Makefile +++ b/Makefile @@ -60,19 +60,22 @@ update-deps: ## Update depdendencies. Runs `go get -u` internally. @GOFLAGS="" go mod tidy @GOFLAGS="" go mod vendor -build: deps vet ## Build the package +build: deps vet schema ## Build the package @go build -o helmsman -ldflags '-X main.version="${TAG}-${DATE}" -extldflags "-static"' cmd/helmsman/main.go generate: @go generate #${PKGS} .PHONY: generate +schema: ## Generate the schema.json file + @go run schema.go + repo: @helm repo list | grep -q "^prometheus-community " || helm repo add prometheus-community https://prometheus-community.github.io/helm-charts @helm repo update .PHONY: repo -test: deps vet repo ## Run unit tests +test: deps vet schema repo ## Run unit tests @go test -v -cover -p=1 ./... -args -f ../../examples/example.toml .PHONY: test diff --git a/go.mod b/go.mod index dbf78156..95f6b06d 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 github.com/aws/aws-sdk-go v1.44.32 github.com/imdario/mergo v0.3.13 + github.com/invopop/jsonschema v0.4.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.4.0 golang.org/x/net v0.0.0-20220325170049-de3da57026de @@ -29,6 +30,7 @@ require ( github.com/google/uuid v1.3.0 // indirect github.com/googleapis/gax-go/v2 v2.3.0 // indirect github.com/googleapis/go-type-adapters v1.0.0 // indirect + github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/mattn/go-ieproxy v0.0.1 // indirect diff --git a/go.sum b/go.sum index dfe2ff80..e800c3ff 100644 --- a/go.sum +++ b/go.sum @@ -207,10 +207,14 @@ github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+ github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 h1:i462o439ZjprVSFSZLZxcsoAe592sZB1rci2Z8j4wdk= +github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= +github.com/invopop/jsonschema v0.4.0 h1:Yuy/unfgCnfV5Wl7H0HgFufp/rlurqPOOuacqyByrws= +github.com/invopop/jsonschema v0.4.0/go.mod h1:O9uiLokuu0+MGFlyiaqtWxwqJm41/+8Nj0lD7A36YH0= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= @@ -238,6 +242,7 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= diff --git a/schema.go b/schema.go new file mode 100644 index 00000000..089517a3 --- /dev/null +++ b/schema.go @@ -0,0 +1,22 @@ +// go:build exclude + +package main + +import ( + "encoding/json" + "os" + + "github.com/Praqma/helmsman/internal/app" + "github.com/invopop/jsonschema" +) + +func main() { + r := new(jsonschema.Reflector) + r.AllowAdditionalProperties = true + if err := r.AddGoComments("github.com/Praqma/helmsman", "./internal/app"); err != nil { + panic(err) + } + s := r.Reflect(&app.State{}) + data, _ := json.MarshalIndent(s, "", " ") + os.WriteFile("schema.json", data, 0o644) +} diff --git a/schema.json b/schema.json new file mode 100644 index 00000000..bee0bf37 --- /dev/null +++ b/schema.json @@ -0,0 +1,441 @@ +{ + "$schema": "http://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/Praqma/helmsman/internal/app/state", + "$ref": "#/$defs/State", + "$defs": { + "Config": { + "properties": { + "kubeContext": { + "type": "string", + "description": "KubeContext is the kube context you want Helmsman to use or create" + }, + "username": { + "type": "string", + "description": "Username to be used for kubectl credentials" + }, + "password": { + "type": "string", + "description": "Password to be used for kubectl credentials" + }, + "clusterURI": { + "type": "string", + "description": "ClusterURI is the URI for your cluster API or the name of an environment variable (starting with `$`) containing the URI" + }, + "serviceAccount": { + "type": "string", + "description": "ServiceAccount to be used for tiller (deprecated)" + }, + "storageBackend": { + "type": "string", + "description": "StorageBackend indicates the storage backened used by helm, defaults to secret" + }, + "slackWebhook": { + "type": "string", + "description": "SlackWebhook is the slack webhook URL for slack notifications" + }, + "msTeamsWebhook": { + "type": "string", + "description": "MSTeamsWebhook is the Microsoft teams webhook URL for teams notifications" + }, + "reverseDelete": { + "type": "boolean", + "description": "ReverseDelete indicates if the applications should be deleted in reverse orderin relation to the installation order" + }, + "bearerToken": { + "type": "boolean", + "description": "BearerToken indicates whether you want helmsman to connect to the cluster using a bearer token" + }, + "bearerTokenPath": { + "type": "string", + "description": "BearerTokenPath allows specifying a custom path for the token" + }, + "namespaceLabelsAuthoritative": { + "type": "boolean", + "description": "NamespaceLabelsAuthoritativei indicates whether helmsman should remove namespace labels that are not in the DSF" + }, + "eyamlEnabled": { + "type": "boolean", + "description": "EyamlEnabled indicates whether eyaml is used for encrypted files" + }, + "eyamlPrivateKeyPath": { + "type": "string", + "description": "EyamlPrivateKeyPath is the path to the eyaml private key" + }, + "eyamlPublicKeyPath": { + "type": "string", + "description": "EyamlPublicKeyPath is the path to the eyaml public key" + }, + "globalHooks": { + "type": "object", + "description": "GlobalHooks is a set of global lifecycle hooks" + }, + "globalMaxHistory": { + "type": "integer", + "description": "GlobalMaxHistory sets the global max number of historical release revisions to keep" + }, + "skipIgnoredApps": { + "type": "boolean", + "description": "SkipIgnoredApps if set to true, ignored apps will not be considered in the plan" + }, + "skipPendingApps": { + "type": "boolean", + "description": "SkipPendingApps is set to true,apps in a pending state will be ignored" + } + }, + "type": "object", + "description": "Config type represents the settings fields" + }, + "CustomResource": { + "properties": { + "name": { + "type": "string", + "description": "Name of the custom resource" + }, + "value": { + "type": "string", + "description": "Value of the custom resource" + } + }, + "type": "object", + "description": "custom resource type" + }, + "Limit": { + "properties": { + "max": { + "$ref": "#/$defs/Resources", + "description": "Max defines the resource limits" + }, + "min": { + "$ref": "#/$defs/Resources", + "description": "Min defines the resource request" + }, + "default": { + "$ref": "#/$defs/Resources", + "description": "Default stes resource limits to pods without defined resource limits" + }, + "defaultRequest": { + "$ref": "#/$defs/Resources", + "description": "DefaultRequest sets the resource requests for pods without defined resource requests" + }, + "maxLimitRequestRatio": { + "$ref": "#/$defs/Resources", + "description": "MaxLimitRequestRatio set the max limit request ratio" + }, + "type": { + "type": "string" + } + }, + "type": "object", + "required": [ + "type" + ], + "description": "Limit represents a resource limit" + }, + "Namespace": { + "properties": { + "protected": { + "type": "boolean", + "description": "Protected if set to true no changes can be applied to the namespace" + }, + "limits": { + "items": { + "$ref": "#/$defs/Limit" + }, + "type": "array", + "description": "Limits to set on the namespace" + }, + "labels": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object", + "description": "Labels to set to the namespace" + }, + "annotations": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object", + "description": "Annotations to set on the namespace" + }, + "quotas": { + "$ref": "#/$defs/Quotas", + "description": "Quotas to set on the namespace" + } + }, + "type": "object", + "required": [ + "protected" + ], + "description": "Namespace type represents the fields of a Namespace" + }, + "Quotas": { + "properties": { + "pods": { + "type": "string", + "description": "Pods is the pods quota" + }, + "limits.cpu": { + "type": "string", + "description": "CPULimits is the CPU quota" + }, + "requests.cpu": { + "type": "string", + "description": "CPURequests is the CPU requests quota" + }, + "limits.memory": { + "type": "string", + "description": "MemoryLimits is the memory quota" + }, + "requests.memory": { + "type": "string", + "description": "MemoryRequests is the memory requests quota" + }, + "customQuotas": { + "items": { + "$ref": "#/$defs/CustomResource" + }, + "type": "array", + "description": "CustomResource is a list of custom resource quotas" + } + }, + "type": "object", + "description": "quota type" + }, + "Release": { + "properties": { + "name": { + "type": "string", + "description": "Name is the helm release name" + }, + "description": { + "type": "string", + "description": "Description is a user friendly description of the helm release" + }, + "namespace": { + "type": "string", + "description": "Namespace where to deploy the helm release" + }, + "enabled": { + "type": "boolean", + "description": "Enabled can be used to togle a helm release" + }, + "group": { + "type": "string" + }, + "chart": { + "type": "string" + }, + "version": { + "type": "string", + "description": "Version of the helm chart to deploy" + }, + "valuesFile": { + "type": "string", + "description": "ValuesFile is the path for a values file for the helm release" + }, + "valuesFiles": { + "items": { + "type": "string" + }, + "type": "array", + "description": "ValuesFiles is a list of paths a values files for the helm release" + }, + "secretsFile": { + "type": "string", + "description": "SecretsFile is the path for an encrypted values file for the helm release" + }, + "secretsFiles": { + "items": { + "type": "string" + }, + "type": "array", + "description": "SecretsFiles is a list of paths for encrypted values files for the helm release" + }, + "postRenderer": { + "type": "string", + "description": "PostRenderer is the path to an executable to be used for post rendering" + }, + "test": { + "type": "boolean", + "description": "Test indicates if the chart tests should be executed" + }, + "protected": { + "type": "boolean", + "description": "Protected defines if the release should be protected against changes" + }, + "wait": { + "type": "boolean", + "description": "Wait defines whether helm should block execution until all k8s resources are in a ready state" + }, + "priority": { + "type": "integer", + "description": "Priority allows defining the execution order, releases with the same priority can be executed in parallel" + }, + "set": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object", + "description": "Set can be used to overwrite the chart values" + }, + "setString": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object", + "description": "SetString can be used to overwrite string values" + }, + "setFile": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object", + "description": "SetFile can be used to overwrite the chart values" + }, + "helmFlags": { + "items": { + "type": "string" + }, + "type": "array", + "description": "HelmFlags is a list of additional flags to pass to the helm command" + }, + "helmDiffFlags": { + "items": { + "type": "string" + }, + "type": "array", + "description": "HelmDiffFlags is a list of cli flags to pass to helm diff" + }, + "noHooks": { + "type": "boolean", + "description": "NoHooks can be used to disable the execution of helm hooks" + }, + "timeout": { + "type": "integer", + "description": "Timeout is the number of seconds to wait for the release to complete" + }, + "hooks": { + "type": "object", + "description": "Hooks can be used to define lifecycle hooks specific to this release" + }, + "maxHistory": { + "type": "integer", + "description": "MaxHistory is the maximum number of histoical releases to keep" + } + }, + "type": "object", + "required": [ + "name", + "namespace", + "enabled", + "chart", + "version" + ], + "description": "Release type representing Helm releases which are described in the desired state" + }, + "Resources": { + "properties": { + "cpu": { + "type": "string", + "description": "CPU is the number of CPU cores" + }, + "memory": { + "type": "string", + "description": "Memory is the amount of memory" + } + }, + "type": "object", + "description": "Resources type" + }, + "State": { + "properties": { + "metadata": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object", + "description": "Metadata for human reader of the desired state file" + }, + "certificates": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object", + "description": "Certificates are used to connect kubectl to a cluster" + }, + "settings": { + "$ref": "#/$defs/Config", + "description": "Settings for configuring helmsman" + }, + "context": { + "type": "string", + "description": "Context defines an helmsman scope" + }, + "helmRepos": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object", + "description": "HelmRepos from where to find the application helm charts" + }, + "preconfiguredHelmRepos": { + "items": { + "type": "string" + }, + "type": "array", + "description": "PreconfiguredHelmRepos is a list of helm repos that are configured outside of the DSF" + }, + "namespaces": { + "patternProperties": { + ".*": { + "$ref": "#/$defs/Namespace" + } + }, + "type": "object", + "description": "Namespaces where helmsman will deploy applications" + }, + "apps": { + "patternProperties": { + ".*": { + "$ref": "#/$defs/Release" + } + }, + "type": "object", + "description": "Apps holds the configuration for each helm release managed by helmsman" + }, + "appsTemplates": { + "patternProperties": { + ".*": { + "$ref": "#/$defs/Release" + } + }, + "type": "object", + "description": "AppsTemplates allow defining YAML objects thatcan be used as a reference with YAML anchors to keep the configuration DRY" + } + }, + "type": "object", + "required": [ + "namespaces", + "apps" + ], + "description": "State type represents the desired State of applications on a k8s cluster." + } + } +} \ No newline at end of file From f1972664f5153f41a897413749e540a8167846a9 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Tue, 14 Jun 2022 12:47:28 +0100 Subject: [PATCH 0878/1127] docs: update the readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 073ccb2a..0373f03a 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ helmsman --debug --dry-run --target artifactory -f example.toml - **Portable**: Helmsman can be used to manage charts deployments on any k8s cluster. - **Protect Namespaces/Releases**: you can define certain namespaces/releases to be protected against accidental human mistakes. - **Define the order of managing releases**: you can define the priorities at which releases are managed by helmsman (useful for dependencies). +- **Parallelise**: Releases with the same priority can be executed in parallel. - **Idempotency**: As long your desired state file does not change, you can execute Helmsman several times and get the same result. - **Continue from failures**: In the case of partial deployment due to a specific chart deployment failure, fix your helm chart and execute Helmsman again without needing to rollback the partial successes first. From c23adf9f8587de3fc7b4d80f0cd5a98c8ba872eb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 19 Jun 2022 14:20:29 +0000 Subject: [PATCH 0879/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.32 to 1.44.37 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.32 to 1.44.37. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.32...v1.44.37) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 95f6b06d..22145a9c 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.1.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.32 + github.com/aws/aws-sdk-go v1.44.37 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.4.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index e800c3ff..a7e15389 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.32 h1:x5hBtpY/02sgRL158zzTclcCLwh3dx3YlSl1rAH4Op0= -github.com/aws/aws-sdk-go v1.44.32/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.37 h1:KvDxCX6dfJeEDC77U5GPGSP0ErecmNnhDHFxw+NIvlI= +github.com/aws/aws-sdk-go v1.44.37/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= From 7a01b74a9c457eb5c2b7efb3d894eb3b43347229 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Fri, 24 Jun 2022 10:50:05 +0200 Subject: [PATCH 0880/1127] Make -target consistent with app release name --- internal/app/state.go | 10 +++++----- internal/app/state_test.go | 26 +++++++++++++------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/internal/app/state.go b/internal/app/state.go index d6b80f5b..37b1083a 100644 --- a/internal/app/state.go +++ b/internal/app/state.go @@ -327,7 +327,7 @@ func (s *State) overrideAppsNamespace(newNs string) { // then get only those Apps that exist in TargetMap func (s *State) disableApps(groups, targets, groupsExcluded, targetsExcluded []string) { excludeAppsLoop: - for appName, app := range s.Apps { + for _, app := range s.Apps { for _, groupExcluded := range groupsExcluded { if app.Group == groupExcluded { app.Disable() @@ -335,7 +335,7 @@ excludeAppsLoop: } } for _, targetExcluded := range targetsExcluded { - if appName == targetExcluded { + if app.Name == targetExcluded { app.Disable() continue excludeAppsLoop } @@ -355,13 +355,13 @@ excludeAppsLoop: for _, g := range groups { groupMap[g] = struct{}{} } - for appName, app := range s.Apps { - if _, ok := s.targetMap[appName]; ok { + for _, app := range s.Apps { + if _, ok := s.targetMap[app.Name]; ok { namespaces[app.Namespace] = struct{}{} continue } if _, ok := groupMap[app.Group]; ok { - s.targetMap[appName] = true + s.targetMap[app.Name] = true namespaces[app.Namespace] = struct{}{} } else { app.Disable() diff --git a/internal/app/state_test.go b/internal/app/state_test.go index 57f71277..5340b6e4 100644 --- a/internal/app/state_test.go +++ b/internal/app/state_test.go @@ -396,9 +396,9 @@ func Test_state_validate(t *testing.T) { } } -func createFullReleasePointer(chart, version string) *Release { +func createFullReleasePointer(name, chart, version string) *Release { return &Release{ - Name: "", + Name: name, Description: "", Namespace: "", Enabled: true, @@ -438,7 +438,7 @@ func Test_state_getReleaseChartsInfo(t *testing.T) { name: "test case 1: valid local path with no chart", args: args{ apps: map[string]*Release{ - "app": createFullReleasePointer(os.TempDir()+"/helmsman-tests/myapp", ""), + "app": createFullReleasePointer("app", os.TempDir()+"/helmsman-tests/myapp", ""), }, }, want: false, @@ -446,7 +446,7 @@ func Test_state_getReleaseChartsInfo(t *testing.T) { name: "test case 2: invalid local path", args: args{ apps: map[string]*Release{ - "app": createFullReleasePointer(os.TempDir()+"/does-not-exist/myapp", ""), + "app": createFullReleasePointer("app", os.TempDir()+"/does-not-exist/myapp", ""), }, }, want: false, @@ -454,7 +454,7 @@ func Test_state_getReleaseChartsInfo(t *testing.T) { name: "test case 3: valid chart local path with whitespace", args: args{ apps: map[string]*Release{ - "app": createFullReleasePointer(os.TempDir()+"/helmsman-tests/dir-with space/myapp", "0.1.0"), + "app": createFullReleasePointer("app", os.TempDir()+"/helmsman-tests/dir-with space/myapp", "0.1.0"), }, }, want: true, @@ -462,7 +462,7 @@ func Test_state_getReleaseChartsInfo(t *testing.T) { name: "test case 4: valid chart from repo", args: args{ apps: map[string]*Release{ - "app": createFullReleasePointer("prometheus-community/prometheus", "11.16.5"), + "app": createFullReleasePointer("app", "prometheus-community/prometheus", "11.16.5"), }, }, want: true, @@ -471,7 +471,7 @@ func Test_state_getReleaseChartsInfo(t *testing.T) { targetFlag: []string{"notThisOne"}, args: args{ apps: map[string]*Release{ - "app": createFullReleasePointer(os.TempDir()+"/does-not-exist/myapp", ""), + "app": createFullReleasePointer("app", os.TempDir()+"/does-not-exist/myapp", ""), }, }, want: true, @@ -480,7 +480,7 @@ func Test_state_getReleaseChartsInfo(t *testing.T) { targetFlag: []string{"app"}, args: args{ apps: map[string]*Release{ - "app": createFullReleasePointer(os.TempDir()+"/does-not-exist/myapp", ""), + "app": createFullReleasePointer("app", os.TempDir()+"/does-not-exist/myapp", ""), }, }, want: false, @@ -489,11 +489,11 @@ func Test_state_getReleaseChartsInfo(t *testing.T) { targetFlag: []string{"app"}, args: args{ apps: map[string]*Release{ - "app1": createFullReleasePointer(os.TempDir()+"/helmsman-tests/dir-with space/myapp", "0.1.0"), - "app2": createFullReleasePointer(os.TempDir()+"/helmsman-tests/dir-with space/myapp", "0.1.0"), - "app3": createFullReleasePointer(os.TempDir()+"/helmsman-tests/dir-with space/myapp", "0.1.0"), - "app4": createFullReleasePointer(os.TempDir()+"/helmsman-tests/dir-with space/myapp", "0.1.0"), - "app5": createFullReleasePointer(os.TempDir()+"/helmsman-tests/dir-with space/myapp", "0.1.0"), + "app1": createFullReleasePointer("app2", os.TempDir()+"/helmsman-tests/dir-with space/myapp", "0.1.0"), + "app2": createFullReleasePointer("app3", os.TempDir()+"/helmsman-tests/dir-with space/myapp", "0.1.0"), + "app3": createFullReleasePointer("app4", os.TempDir()+"/helmsman-tests/dir-with space/myapp", "0.1.0"), + "app4": createFullReleasePointer("app5", os.TempDir()+"/helmsman-tests/dir-with space/myapp", "0.1.0"), + "app5": createFullReleasePointer("app5", os.TempDir()+"/helmsman-tests/dir-with space/myapp", "0.1.0"), }, }, want: true, From c49aa2836958504cb35b1cab5c6ca2fcc658445e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 26 Jun 2022 11:50:41 +0000 Subject: [PATCH 0881/1127] chore(deps): bump cloud.google.com/go/storage from 1.22.1 to 1.23.0 Bumps [cloud.google.com/go/storage](https://github.com/googleapis/google-cloud-go) from 1.22.1 to 1.23.0. - [Release notes](https://github.com/googleapis/google-cloud-go/releases) - [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/CHANGES.md) - [Commits](https://github.com/googleapis/google-cloud-go/compare/pubsub/v1.22.1...pubsub/v1.23.0) --- updated-dependencies: - dependency-name: cloud.google.com/go/storage dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 25 ++++++++++++----------- go.sum | 63 +++++++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 64 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index 22145a9c..da85cc20 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Praqma/helmsman go 1.18 require ( - cloud.google.com/go/storage v1.22.1 + cloud.google.com/go/storage v1.23.0 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.15.0 github.com/BurntSushi/toml v1.1.0 @@ -14,21 +14,22 @@ require ( github.com/invopop/jsonschema v0.4.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.4.0 - golang.org/x/net v0.0.0-20220325170049-de3da57026de + golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 gopkg.in/yaml.v2 v2.4.0 ) require ( - cloud.google.com/go v0.100.2 // indirect - cloud.google.com/go/compute v1.6.0 // indirect + cloud.google.com/go v0.102.1 // indirect + cloud.google.com/go/compute v1.7.0 // indirect cloud.google.com/go/iam v0.3.0 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect github.com/golang-jwt/jwt/v4 v4.3.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect - github.com/google/go-cmp v0.5.7 // indirect + github.com/google/go-cmp v0.5.8 // indirect github.com/google/uuid v1.3.0 // indirect - github.com/googleapis/gax-go/v2 v2.3.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.1.0 // indirect + github.com/googleapis/gax-go/v2 v2.4.0 // indirect github.com/googleapis/go-type-adapters v1.0.0 // indirect github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect @@ -36,13 +37,13 @@ require ( github.com/mattn/go-ieproxy v0.0.1 // indirect go.opencensus.io v0.23.0 // indirect golang.org/x/crypto v0.0.0-20220312131142-6068a2e6cfdc // indirect - golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect - golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886 // indirect + golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb // indirect + golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c // indirect golang.org/x/text v0.3.7 // indirect - golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect - google.golang.org/api v0.74.0 // indirect + golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect + google.golang.org/api v0.85.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335 // indirect - google.golang.org/grpc v1.46.0 // indirect + google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad // indirect + google.golang.org/grpc v1.47.0 // indirect google.golang.org/protobuf v1.28.0 // indirect ) diff --git a/go.sum b/go.sum index a7e15389..5538812f 100644 --- a/go.sum +++ b/go.sum @@ -26,8 +26,10 @@ cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+Y cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= -cloud.google.com/go v0.100.2 h1:t9Iw5QH5v4XtlEQaCtUY7x6sCABps8sW0acw7e2WQ6Y= cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= +cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= +cloud.google.com/go v0.102.1 h1:vpK6iQWv/2uUeFJth4/cBHsQAGjn1iIE6AAlxipRaA0= +cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -37,8 +39,10 @@ cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM7 cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= -cloud.google.com/go/compute v1.6.0 h1:XdQIN5mdPTSBVwSIVDuY5e8ZzVAccsHvD3qTEz4zIps= cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= +cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= +cloud.google.com/go/compute v1.7.0 h1:v/k9Eueb8aAJ0vZuxKMrgm6kPhCLZU9HxFU+AFDs9Uk= +cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/iam v0.3.0 h1:exkAomrVUuzx9kWFI1wm3KI0uoDeUFPB4kKGzx6x+Gc= @@ -52,8 +56,9 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.22.1 h1:F6IlQJZrZM++apn9V5/VfS3gbTUYg98PS3EMQAzqtfg= cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= +cloud.google.com/go/storage v1.23.0 h1:wWRIaDURQA8xxHguFCshYepGlrWIrbBnAmc7wfg07qY= +cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= @@ -168,8 +173,9 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -195,13 +201,17 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.1.0 h1:zO8WHNx/MYiAKJ3d5spxZXZE6KHmIQGQcAzwUzV7qQw= +github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= -github.com/googleapis/gax-go/v2 v2.3.0 h1:nRJtk3y8Fm770D42QV6T90ZnvFZyk7agSo3Q+Z9p3WI= github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= +github.com/googleapis/gax-go/v2 v2.4.0 h1:dS9eYAjhrE2RjmzYw2XAPvcXfmcQLtFEQWn0CR82awk= +github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= github.com/googleapis/go-type-adapters v1.0.0 h1:9XdMn+d/G57qq1s8dNc5IesGCXHf6V2HZ2JwRxfA2tA= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= @@ -348,8 +358,12 @@ golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220325170049-de3da57026de h1:pZB1TWnKi+o4bENlbzAgLrEbY4RMYmUIRobMcSmfeYc= golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 h1:Yqz/iviulwKwAREEeUd3nbBFn0XuyJqkoft2IlrvOhc= +golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -368,8 +382,9 @@ golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 h1:OSnWWcOd/CtWQC2cYSBgbTSJv3ciqd8r54ySIW2y3RE= golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb h1:8tDJ3aechhddbdPAxpycgXHJRMLpk/Ab+aa4OgdN5/g= +golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -381,6 +396,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -434,8 +450,14 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886 h1:eJv7u3ksNXoLbGSKuv2s/SIO4tJVxc/A+MTpzxDgz/Q= golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c h1:aFV+BgZ4svzjfabn8ERpuB4JI4N6/rdy1iusx77G3oU= +golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -506,8 +528,10 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U= golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f h1:uF6paiQQebLeSXkrTqHqz0MXhXXS1KgF41eUdBNvxK0= +golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -542,8 +566,13 @@ google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tD google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= -google.golang.org/api v0.74.0 h1:ExR2D+5TYIrMphWgs5JCgwRhEDlPDXXrLwHHMgPHTXE= google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= +google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= +google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= +google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= +google.golang.org/api v0.85.0 h1:8rJoHuRxx+vCmZtAO/3k1dRLvYNVyTJtZ5oaFZvhgvc= +google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -622,8 +651,16 @@ google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2 google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335 h1:2D0OT6tPVdrQTOnVe1VQjfJPTED6EZ7fdJ/f6Db6OsY= +google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad h1:kqrS+lhvaMHCxul6sKQvKJ8nAAhlVItmZV822hYFH/U= +google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -652,8 +689,10 @@ google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9K google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= -google.golang.org/grpc v1.46.0 h1:oCjezcn6g6A75TGoKYBPgKmVBLexhYLM6MebdrPApP8= google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8= +google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= From 9f6a8a546c02ec8375b054cc92ae6597c0eed363 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 26 Jun 2022 11:51:13 +0000 Subject: [PATCH 0882/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.37 to 1.44.42 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.37 to 1.44.42. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.37...v1.44.42) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 22145a9c..ad721128 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.1.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.37 + github.com/aws/aws-sdk-go v1.44.42 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.4.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index a7e15389..3d934dea 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.37 h1:KvDxCX6dfJeEDC77U5GPGSP0ErecmNnhDHFxw+NIvlI= -github.com/aws/aws-sdk-go v1.44.37/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.42 h1:sPkafCTLh2diZtDojetwbhU7QWQljYvc3PRjnrgKFlE= +github.com/aws/aws-sdk-go v1.44.42/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= From ebdb2df5d0c32ce7802cf35393158b1f74ea6c8f Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Tue, 28 Jun 2022 13:07:51 +0200 Subject: [PATCH 0883/1127] Release v3.12.0 --- .version | 2 +- README.md | 2 +- internal/app/main.go | 2 +- release-notes.md | 7 +++---- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.version b/.version index 41e615f3..b0ab92d4 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.11.0 +v3.12.0 diff --git a/README.md b/README.md index 0373f03a..23560742 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.11.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.12.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) diff --git a/internal/app/main.go b/internal/app/main.go index 7fa607d5..c34ce17d 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.11.0" + appVersion = "v3.12.0" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index b1d2c97e..d4dd9865 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,10 +1,9 @@ -# v3.11.0 +# v3.12.0 ## New feature -- Added detailed exit codes (#668) +- Added JSON schema generator for DSF (#675) ## Fixes and improvements -- Updated dependencies (#666; #667) -- Check charts using helm repos from previouly merged DSFs (#673) +- Fix -target flag inconsistency between app yaml key and app release name (#678) From ca718ea8882ee6fe5e8c72fe3a00d7f2488c0215 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 3 Jul 2022 14:11:46 +0000 Subject: [PATCH 0884/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.42 to 1.44.47 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.42 to 1.44.47. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.42...v1.44.47) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f464f822..16fb3fa0 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.1.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.42 + github.com/aws/aws-sdk-go v1.44.47 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.4.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index a56ec5d6..ee5836dd 100644 --- a/go.sum +++ b/go.sum @@ -86,8 +86,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.42 h1:sPkafCTLh2diZtDojetwbhU7QWQljYvc3PRjnrgKFlE= -github.com/aws/aws-sdk-go v1.44.42/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.47 h1:uyiNvoR4wfZ8Bp4ghgbyzGFIg5knjZMUAd5S9ba9qNU= +github.com/aws/aws-sdk-go v1.44.47/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= From 546ad70d1e67e32a05c35fa3eef859de78ace6af Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 10 Jul 2022 14:25:36 +0000 Subject: [PATCH 0885/1127] chore(deps): bump github.com/invopop/jsonschema from 0.4.0 to 0.5.0 Bumps [github.com/invopop/jsonschema](https://github.com/invopop/jsonschema) from 0.4.0 to 0.5.0. - [Release notes](https://github.com/invopop/jsonschema/releases) - [Commits](https://github.com/invopop/jsonschema/compare/v0.4.0...v0.5.0) --- updated-dependencies: - dependency-name: github.com/invopop/jsonschema dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 16fb3fa0..4b3252e9 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 github.com/aws/aws-sdk-go v1.44.47 github.com/imdario/mergo v0.3.13 - github.com/invopop/jsonschema v0.4.0 + github.com/invopop/jsonschema v0.5.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.4.0 golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 diff --git a/go.sum b/go.sum index ee5836dd..4ed86efb 100644 --- a/go.sum +++ b/go.sum @@ -223,8 +223,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= -github.com/invopop/jsonschema v0.4.0 h1:Yuy/unfgCnfV5Wl7H0HgFufp/rlurqPOOuacqyByrws= -github.com/invopop/jsonschema v0.4.0/go.mod h1:O9uiLokuu0+MGFlyiaqtWxwqJm41/+8Nj0lD7A36YH0= +github.com/invopop/jsonschema v0.5.0 h1:6tvpBcwTGxzvx3M9f3IfzqQVyZvoH+0NRUtBcsgyfrU= +github.com/invopop/jsonschema v0.5.0/go.mod h1:O9uiLokuu0+MGFlyiaqtWxwqJm41/+8Nj0lD7A36YH0= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= From 2723513a22e8bf055ce7931408f5c268b882f04c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 10 Jul 2022 14:25:53 +0000 Subject: [PATCH 0886/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.47 to 1.44.51 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.47 to 1.44.51. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.47...v1.44.51) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 16fb3fa0..e437274d 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.1.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.47 + github.com/aws/aws-sdk-go v1.44.51 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.4.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index ee5836dd..ded10c29 100644 --- a/go.sum +++ b/go.sum @@ -86,8 +86,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.47 h1:uyiNvoR4wfZ8Bp4ghgbyzGFIg5knjZMUAd5S9ba9qNU= -github.com/aws/aws-sdk-go v1.44.47/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.51 h1:jO9hoLynZOrMM4dj0KjeKIK+c6PA+HQbKoHOkAEye2Y= +github.com/aws/aws-sdk-go v1.44.51/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= From 7061ae01c20aaa81ccec2e3a1f27cfc2027d91fd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 17 Jul 2022 14:20:53 +0000 Subject: [PATCH 0887/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.51 to 1.44.56 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.51 to 1.44.56. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.51...v1.44.56) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 58e59966..d8433ea7 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.1.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.51 + github.com/aws/aws-sdk-go v1.44.56 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.5.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 4553dccf..ed148481 100644 --- a/go.sum +++ b/go.sum @@ -86,8 +86,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.51 h1:jO9hoLynZOrMM4dj0KjeKIK+c6PA+HQbKoHOkAEye2Y= -github.com/aws/aws-sdk-go v1.44.51/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.56 h1:bT+lExwagH7djxb6InKUVkEKGPAj5aAPnV85/m1fKro= +github.com/aws/aws-sdk-go v1.44.56/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= From 4c51c8826640a430d610acc031d6427b1e3d3155 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sun, 17 Jul 2022 20:04:32 +0100 Subject: [PATCH 0888/1127] refactor: use sigs.k8s.io/yaml and json tags --- go.mod | 3 +- go.sum | 3 ++ internal/app/helm_helpers.go | 6 ++-- internal/app/kube_helpers.go | 2 +- internal/app/namespace.go | 42 +++++++++++++-------------- internal/app/release.go | 50 ++++++++++++++++---------------- internal/app/spec_state.go | 6 ++-- internal/app/state.go | 56 ++++++++++++++++++------------------ internal/app/state_files.go | 34 +++++----------------- schema.json | 12 +++++--- 10 files changed, 101 insertions(+), 113 deletions(-) diff --git a/go.mod b/go.mod index 58e59966..76e3b323 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.4.0 golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 - gopkg.in/yaml.v2 v2.4.0 + sigs.k8s.io/yaml v1.3.0 ) require ( @@ -46,4 +46,5 @@ require ( google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad // indirect google.golang.org/grpc v1.47.0 // indirect google.golang.org/protobuf v1.28.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 4553dccf..1b6888f6 100644 --- a/go.sum +++ b/go.sum @@ -107,6 +107,7 @@ github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWH github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -732,3 +733,5 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index 51a56309..c7aabd07 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -7,7 +7,7 @@ import ( "path/filepath" "strings" - "gopkg.in/yaml.v2" + "sigs.k8s.io/yaml" "github.com/Masterminds/semver" "github.com/Praqma/helmsman/internal/gcs" @@ -19,8 +19,8 @@ type helmRepo struct { } type ChartInfo struct { - Name string `yaml:"name"` - Version string `yaml:"version"` + Name string `json:"name"` + Version string `json:"version"` } // helmCmd prepares a helm command to be executed diff --git a/internal/app/kube_helpers.go b/internal/app/kube_helpers.go index fd28c378..b554f9f3 100644 --- a/internal/app/kube_helpers.go +++ b/internal/app/kube_helpers.go @@ -9,7 +9,7 @@ import ( "strings" "sync" - "gopkg.in/yaml.v2" + "sigs.k8s.io/yaml" ) // addNamespaces creates a set of namespaces in your k8s cluster. diff --git a/internal/app/namespace.go b/internal/app/namespace.go index a5080d59..e67b3d02 100644 --- a/internal/app/namespace.go +++ b/internal/app/namespace.go @@ -7,32 +7,32 @@ import ( // Resources type type Resources struct { // CPU is the number of CPU cores - CPU string `yaml:"cpu,omitempty"` + CPU string `json:"cpu,omitempty"` // Memory is the amount of memory - Memory string `yaml:"memory,omitempty"` + Memory string `json:"memory,omitempty"` } // custom resource type type CustomResource struct { // Name of the custom resource - Name string `yaml:"name,omitempty"` + Name string `json:"name,omitempty"` // Value of the custom resource - Value string `yaml:"value,omitempty"` + Value string `json:"value,omitempty"` } // Limit represents a resource limit type Limit struct { // Max defines the resource limits - Max Resources `yaml:"max,omitempty"` + Max Resources `json:"max,omitempty"` // Min defines the resource request - Min Resources `yaml:"min,omitempty"` + Min Resources `json:"min,omitempty"` // Default stes resource limits to pods without defined resource limits - Default Resources `yaml:"default,omitempty"` + Default Resources `json:"default,omitempty"` // DefaultRequest sets the resource requests for pods without defined resource requests - DefaultRequest Resources `yaml:"defaultRequest,omitempty"` + DefaultRequest Resources `json:"defaultRequest,omitempty"` // MaxLimitRequestRatio set the max limit request ratio - MaxLimitRequestRatio Resources `yaml:"maxLimitRequestRatio,omitempty"` - Type string `yaml:"type"` + MaxLimitRequestRatio Resources `json:"maxLimitRequestRatio,omitempty"` + Type string `json:"type"` } // Limits type @@ -41,31 +41,31 @@ type Limits []Limit // quota type type Quotas struct { // Pods is the pods quota - Pods string `yaml:"pods,omitempty"` + Pods string `json:"pods,omitempty"` // CPULimits is the CPU quota - CPULimits string `yaml:"limits.cpu,omitempty"` + CPULimits string `json:"limits.cpu,omitempty"` // CPURequests is the CPU requests quota - CPURequests string `yaml:"requests.cpu,omitempty"` + CPURequests string `json:"requests.cpu,omitempty"` // MemoryLimits is the memory quota - MemoryLimits string `yaml:"limits.memory,omitempty"` + MemoryLimits string `json:"limits.memory,omitempty"` // MemoryRequests is the memory requests quota - MemoryRequests string `yaml:"requests.memory,omitempty"` + MemoryRequests string `json:"requests.memory,omitempty"` // CustomResource is a list of custom resource quotas - CustomQuotas []CustomResource `yaml:"customQuotas,omitempty"` + CustomQuotas []CustomResource `json:"customQuotas,omitempty"` } // Namespace type represents the fields of a Namespace type Namespace struct { // Protected if set to true no changes can be applied to the namespace - Protected bool `yaml:"protected"` + Protected bool `json:"protected"` // Limits to set on the namespace - Limits Limits `yaml:"limits,omitempty"` + Limits Limits `json:"limits,omitempty"` // Labels to set to the namespace - Labels map[string]string `yaml:"labels,omitempty"` + Labels map[string]string `json:"labels,omitempty"` // Annotations to set on the namespace - Annotations map[string]string `yaml:"annotations,omitempty"` + Annotations map[string]string `json:"annotations,omitempty"` // Quotas to set on the namespace - Quotas *Quotas `yaml:"quotas,omitempty"` + Quotas *Quotas `json:"quotas,omitempty"` disabled bool } diff --git a/internal/app/release.go b/internal/app/release.go index 5c8a3739..dd2a01dc 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -11,53 +11,53 @@ import ( // Release type representing Helm releases which are described in the desired state type Release struct { // Name is the helm release name - Name string `yaml:"name"` + Name string `json:"name"` // Description is a user friendly description of the helm release - Description string `yaml:"description,omitempty"` + Description string `json:"description,omitempty"` // Namespace where to deploy the helm release - Namespace string `yaml:"namespace"` + Namespace string `json:"namespace"` // Enabled can be used to togle a helm release - Enabled bool `yaml:"enabled"` - Group string `yaml:"group,omitempty"` - Chart string `yaml:"chart"` + Enabled bool `json:"enabled"` + Group string `json:"group,omitempty"` + Chart string `json:"chart"` // Version of the helm chart to deploy - Version string `yaml:"version"` + Version string `json:"version"` // ValuesFile is the path for a values file for the helm release - ValuesFile string `yaml:"valuesFile,omitempty"` + ValuesFile string `json:"valuesFile,omitempty"` // ValuesFiles is a list of paths a values files for the helm release - ValuesFiles []string `yaml:"valuesFiles,omitempty"` + ValuesFiles []string `json:"valuesFiles,omitempty"` // SecretsFile is the path for an encrypted values file for the helm release - SecretsFile string `yaml:"secretsFile,omitempty"` + SecretsFile string `json:"secretsFile,omitempty"` // SecretsFiles is a list of paths for encrypted values files for the helm release - SecretsFiles []string `yaml:"secretsFiles,omitempty"` + SecretsFiles []string `json:"secretsFiles,omitempty"` // PostRenderer is the path to an executable to be used for post rendering - PostRenderer string `yaml:"postRenderer,omitempty"` + PostRenderer string `json:"postRenderer,omitempty"` // Test indicates if the chart tests should be executed - Test bool `yaml:"test,omitempty"` + Test bool `json:"test,omitempty"` // Protected defines if the release should be protected against changes - Protected bool `yaml:"protected,omitempty"` + Protected bool `json:"protected,omitempty"` // Wait defines whether helm should block execution until all k8s resources are in a ready state - Wait bool `yaml:"wait,omitempty"` + Wait bool `json:"wait,omitempty"` // Priority allows defining the execution order, releases with the same priority can be executed in parallel - Priority int `yaml:"priority,omitempty"` + Priority int `json:"priority,omitempty"` // Set can be used to overwrite the chart values - Set map[string]string `yaml:"set,omitempty"` + Set map[string]string `json:"set,omitempty"` // SetString can be used to overwrite string values - SetString map[string]string `yaml:"setString,omitempty"` + SetString map[string]string `json:"setString,omitempty"` // SetFile can be used to overwrite the chart values - SetFile map[string]string `yaml:"setFile,omitempty"` + SetFile map[string]string `json:"setFile,omitempty"` // HelmFlags is a list of additional flags to pass to the helm command - HelmFlags []string `yaml:"helmFlags,omitempty"` + HelmFlags []string `json:"helmFlags,omitempty"` // HelmDiffFlags is a list of cli flags to pass to helm diff - HelmDiffFlags []string `yaml:"helmDiffFlags,omitempty"` + HelmDiffFlags []string `json:"helmDiffFlags,omitempty"` // NoHooks can be used to disable the execution of helm hooks - NoHooks bool `yaml:"noHooks,omitempty"` + NoHooks bool `json:"noHooks,omitempty"` // Timeout is the number of seconds to wait for the release to complete - Timeout int `yaml:"timeout,omitempty"` + Timeout int `json:"timeout,omitempty"` // Hooks can be used to define lifecycle hooks specific to this release - Hooks map[string]interface{} `yaml:"hooks,omitempty"` + Hooks map[string]interface{} `json:"hooks,omitempty"` // MaxHistory is the maximum number of histoical releases to keep - MaxHistory int `yaml:"maxHistory,omitempty"` + MaxHistory int `json:"maxHistory,omitempty"` disabled bool } diff --git a/internal/app/spec_state.go b/internal/app/spec_state.go index 5927a7d5..e068dcbd 100644 --- a/internal/app/spec_state.go +++ b/internal/app/spec_state.go @@ -3,15 +3,15 @@ package app import ( "io/ioutil" - "gopkg.in/yaml.v2" + "sigs.k8s.io/yaml" ) type StatePath struct { - Path string `yaml:"path"` + Path string `json:"path"` } type StateFiles struct { - StateFiles []StatePath `yaml:"stateFiles"` + StateFiles []StatePath `json:"stateFiles"` } // specFromYAML reads a yaml file and decodes it to a state type. diff --git a/internal/app/state.go b/internal/app/state.go index 37b1083a..b1dcf435 100644 --- a/internal/app/state.go +++ b/internal/app/state.go @@ -13,65 +13,65 @@ import ( // Config type represents the settings fields type Config struct { // KubeContext is the kube context you want Helmsman to use or create - KubeContext string `yaml:"kubeContext,omitempty"` + KubeContext string `json:"kubeContext,omitempty"` // Username to be used for kubectl credentials - Username string `yaml:"username,omitempty"` + Username string `json:"username,omitempty"` // Password to be used for kubectl credentials - Password string `yaml:"password,omitempty"` + Password string `json:"password,omitempty"` // ClusterURI is the URI for your cluster API or the name of an environment variable (starting with `$`) containing the URI - ClusterURI string `yaml:"clusterURI,omitempty"` + ClusterURI string `json:"clusterURI,omitempty"` // ServiceAccount to be used for tiller (deprecated) - ServiceAccount string `yaml:"serviceAccount,omitempty"` + ServiceAccount string `json:"serviceAccount,omitempty"` // StorageBackend indicates the storage backened used by helm, defaults to secret - StorageBackend string `yaml:"storageBackend,omitempty"` + StorageBackend string `json:"storageBackend,omitempty"` // SlackWebhook is the slack webhook URL for slack notifications - SlackWebhook string `yaml:"slackWebhook,omitempty"` + SlackWebhook string `json:"slackWebhook,omitempty"` // MSTeamsWebhook is the Microsoft teams webhook URL for teams notifications - MSTeamsWebhook string `yaml:"msTeamsWebhook,omitempty"` + MSTeamsWebhook string `json:"msTeamsWebhook,omitempty"` // ReverseDelete indicates if the applications should be deleted in reverse orderin relation to the installation order - ReverseDelete bool `yaml:"reverseDelete,omitempty"` + ReverseDelete bool `json:"reverseDelete,omitempty"` // BearerToken indicates whether you want helmsman to connect to the cluster using a bearer token - BearerToken bool `yaml:"bearerToken,omitempty"` + BearerToken bool `json:"bearerToken,omitempty"` // BearerTokenPath allows specifying a custom path for the token - BearerTokenPath string `yaml:"bearerTokenPath,omitempty"` + BearerTokenPath string `json:"bearerTokenPath,omitempty"` // NamespaceLabelsAuthoritativei indicates whether helmsman should remove namespace labels that are not in the DSF - NamespaceLabelsAuthoritative bool `yaml:"namespaceLabelsAuthoritative,omitempty"` + NamespaceLabelsAuthoritative bool `json:"namespaceLabelsAuthoritative,omitempty"` // EyamlEnabled indicates whether eyaml is used for encrypted files - EyamlEnabled bool `yaml:"eyamlEnabled,omitempty"` + EyamlEnabled bool `json:"eyamlEnabled,omitempty"` // EyamlPrivateKeyPath is the path to the eyaml private key - EyamlPrivateKeyPath string `yaml:"eyamlPrivateKeyPath,omitempty"` + EyamlPrivateKeyPath string `json:"eyamlPrivateKeyPath,omitempty"` // EyamlPublicKeyPath is the path to the eyaml public key - EyamlPublicKeyPath string `yaml:"eyamlPublicKeyPath,omitempty"` + EyamlPublicKeyPath string `json:"eyamlPublicKeyPath,omitempty"` // GlobalHooks is a set of global lifecycle hooks - GlobalHooks map[string]interface{} `yaml:"globalHooks,omitempty"` + GlobalHooks map[string]interface{} `json:"globalHooks,omitempty"` // GlobalMaxHistory sets the global max number of historical release revisions to keep - GlobalMaxHistory int `yaml:"globalMaxHistory,omitempty"` + GlobalMaxHistory int `json:"globalMaxHistory,omitempty"` // SkipIgnoredApps if set to true, ignored apps will not be considered in the plan - SkipIgnoredApps bool `yaml:"skipIgnoredApps,omitempty"` + SkipIgnoredApps bool `json:"skipIgnoredApps,omitempty"` // SkipPendingApps is set to true,apps in a pending state will be ignored - SkipPendingApps bool `yaml:"skipPendingApps,omitempty"` + SkipPendingApps bool `json:"skipPendingApps,omitempty"` } // State type represents the desired State of applications on a k8s cluster. type State struct { // Metadata for human reader of the desired state file - Metadata map[string]string `yaml:"metadata,omitempty"` + Metadata map[string]string `json:"metadata,omitempty"` // Certificates are used to connect kubectl to a cluster - Certificates map[string]string `yaml:"certificates,omitempty"` + Certificates map[string]string `json:"certificates,omitempty"` // Settings for configuring helmsman - Settings Config `yaml:"settings,omitempty"` + Settings Config `json:"settings,omitempty"` // Context defines an helmsman scope - Context string `yaml:"context,omitempty"` + Context string `json:"context,omitempty"` // HelmRepos from where to find the application helm charts - HelmRepos map[string]string `yaml:"helmRepos,omitempty"` + HelmRepos map[string]string `json:"helmRepos,omitempty"` // PreconfiguredHelmRepos is a list of helm repos that are configured outside of the DSF - PreconfiguredHelmRepos []string `yaml:"preconfiguredHelmRepos,omitempty"` + PreconfiguredHelmRepos []string `json:"preconfiguredHelmRepos,omitempty"` // Namespaces where helmsman will deploy applications - Namespaces map[string]*Namespace `yaml:"namespaces"` + Namespaces map[string]*Namespace `json:"namespaces"` // Apps holds the configuration for each helm release managed by helmsman - Apps map[string]*Release `yaml:"apps"` + Apps map[string]*Release `json:"apps"` // AppsTemplates allow defining YAML objects thatcan be used as a reference with YAML anchors to keep the configuration DRY - AppsTemplates map[string]*Release `yaml:"appsTemplates,omitempty"` + AppsTemplates map[string]*Release `json:"appsTemplates,omitempty"` targetMap map[string]bool chartInfo map[string]map[string]*ChartInfo } diff --git a/internal/app/state_files.go b/internal/app/state_files.go index c2236451..5de157a1 100644 --- a/internal/app/state_files.go +++ b/internal/app/state_files.go @@ -10,7 +10,7 @@ import ( "github.com/BurntSushi/toml" "github.com/imdario/mergo" - "gopkg.in/yaml.v2" + "sigs.k8s.io/yaml" ) // invokes either yaml or toml parser considering file extension @@ -64,25 +64,15 @@ func (s *State) fromTOML(file string) error { func (s *State) toTOML(file string) { log.Info("Printing generated toml ... ") var buff bytes.Buffer - var ( - newFile *os.File - err error - ) if err := toml.NewEncoder(&buff).Encode(s); err != nil { log.Fatal(err.Error()) os.Exit(1) } - newFile, err = os.Create(file) - if err != nil { + if err := os.WriteFile(file, buff.Bytes(), 0644); err != nil { log.Fatal(err.Error()) } - bytesWritten, err := newFile.Write(buff.Bytes()) - if err != nil { - log.Fatal(err.Error()) - } - log.Info(fmt.Sprintf("Wrote %d bytes.\n", bytesWritten)) - newFile.Close() + log.Info(fmt.Sprintf("Wrote to %s.\n", file)) } // fromYAML reads a yaml file and decodes it to a state type. @@ -114,26 +104,16 @@ func (s *State) fromYAML(file string) error { // toYaml encodes a state type into a YAML file func (s *State) toYAML(file string) { log.Info("Printing generated yaml ... ") - var buff bytes.Buffer - var ( - newFile *os.File - err error - ) - if err := yaml.NewEncoder(&buff).Encode(s); err != nil { - log.Fatal(err.Error()) - os.Exit(1) - } - newFile, err = os.Create(file) + ymlBytes, err := yaml.Marshal(s) if err != nil { log.Fatal(err.Error()) + os.Exit(1) } - bytesWritten, err := newFile.Write(buff.Bytes()) - if err != nil { + if err := os.WriteFile(file, ymlBytes, 0644); err != nil { log.Fatal(err.Error()) } - log.Info(fmt.Sprintf("Wrote %d bytes.\n", bytesWritten)) - newFile.Close() + log.Info(fmt.Sprintf("Wrote to %s.\n", file)) } func (s *State) build(files fileOptionArray) error { diff --git a/schema.json b/schema.json index bee0bf37..09e15421 100644 --- a/schema.json +++ b/schema.json @@ -131,6 +131,13 @@ ], "description": "Limit represents a resource limit" }, + "Limits": { + "items": { + "$ref": "#/$defs/Limit" + }, + "type": "array", + "description": "Limits type" + }, "Namespace": { "properties": { "protected": { @@ -138,10 +145,7 @@ "description": "Protected if set to true no changes can be applied to the namespace" }, "limits": { - "items": { - "$ref": "#/$defs/Limit" - }, - "type": "array", + "$ref": "#/$defs/Limits", "description": "Limits to set on the namespace" }, "labels": { From b7a52074d179f52dad8a7981de510f9fc3ce8610 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Thu, 21 Jul 2022 12:26:15 +0200 Subject: [PATCH 0889/1127] Get kubectl version by taking json output instead of parsing string --- internal/app/kube_helpers.go | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/internal/app/kube_helpers.go b/internal/app/kube_helpers.go index fd28c378..4d6d463b 100644 --- a/internal/app/kube_helpers.go +++ b/internal/app/kube_helpers.go @@ -337,17 +337,25 @@ func getReleaseContext(releaseName, namespace, storageBackend string) string { // getKubectlVersion returns kubectl client version func getKubectlVersion() string { - cmd := kubectl([]string{"version", "--short", "--client"}, "Checking kubectl version") + var kubectlVersion struct { + ClientVersion map[string]string `json:"clientVersion"` + } - res, err := cmd.Exec() + cmd := kubectl([]string{"version", "--output=json", "--client"}, "Checking kubectl version") + ver, err := cmd.Exec() if err != nil { log.Fatalf("While checking kubectl version: %v", err) } - version := strings.TrimSpace(res.output) - if !strings.HasPrefix(version, "v") { - version = strings.SplitN(version, ":", 2)[1] + + if err := json.Unmarshal([]byte(ver.output), &kubectlVersion); err != nil { + log.Fatal(fmt.Sprintf("failed to unmarshal kubectl version CLI output: %s", err)) + } + + version := kubectlVersion.ClientVersion["gitVersion"] + if version == "" { + log.Fatal("Could not get 'gitVersion' from kubectl version cmd output") } - return strings.TrimSpace(version) + return version } func checkKubectlVersion(constraint string) bool { From 865d11845fd8c4684bb1de82f9c1daf657f352c8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 24 Jul 2022 14:26:04 +0000 Subject: [PATCH 0890/1127] chore(deps): bump github.com/BurntSushi/toml from 1.1.0 to 1.2.0 Bumps [github.com/BurntSushi/toml](https://github.com/BurntSushi/toml) from 1.1.0 to 1.2.0. - [Release notes](https://github.com/BurntSushi/toml/releases) - [Commits](https://github.com/BurntSushi/toml/compare/v1.1.0...v1.2.0) --- updated-dependencies: - dependency-name: github.com/BurntSushi/toml dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0acb1ae6..053405e6 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( cloud.google.com/go/storage v1.23.0 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.15.0 - github.com/BurntSushi/toml v1.1.0 + github.com/BurntSushi/toml v1.2.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 github.com/aws/aws-sdk-go v1.44.56 diff --git a/go.sum b/go.sum index b4a3fcd6..ede7308d 100644 --- a/go.sum +++ b/go.sum @@ -77,8 +77,8 @@ github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= -github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= +github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= From 9aa870f4839d20f47e2e966da05c3ceb30b3eefa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 24 Jul 2022 14:26:23 +0000 Subject: [PATCH 0891/1127] chore(deps): bump cloud.google.com/go/storage from 1.23.0 to 1.24.0 Bumps [cloud.google.com/go/storage](https://github.com/googleapis/google-cloud-go) from 1.23.0 to 1.24.0. - [Release notes](https://github.com/googleapis/google-cloud-go/releases) - [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/CHANGES.md) - [Commits](https://github.com/googleapis/google-cloud-go/compare/pubsub/v1.23.0...pubsub/v1.24.0) --- updated-dependencies: - dependency-name: cloud.google.com/go/storage dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 5 ++--- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 0acb1ae6..b2ebe96b 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Praqma/helmsman go 1.18 require ( - cloud.google.com/go/storage v1.23.0 + cloud.google.com/go/storage v1.24.0 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.15.0 github.com/BurntSushi/toml v1.1.0 @@ -30,14 +30,13 @@ require ( github.com/google/uuid v1.3.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.1.0 // indirect github.com/googleapis/gax-go/v2 v2.4.0 // indirect - github.com/googleapis/go-type-adapters v1.0.0 // indirect github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/mattn/go-ieproxy v0.0.1 // indirect go.opencensus.io v0.23.0 // indirect golang.org/x/crypto v0.0.0-20220312131142-6068a2e6cfdc // indirect - golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb // indirect + golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2 // indirect golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect diff --git a/go.sum b/go.sum index b4a3fcd6..5ebd4912 100644 --- a/go.sum +++ b/go.sum @@ -57,8 +57,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= -cloud.google.com/go/storage v1.23.0 h1:wWRIaDURQA8xxHguFCshYepGlrWIrbBnAmc7wfg07qY= -cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= +cloud.google.com/go/storage v1.24.0 h1:a4N0gIkx83uoVFGz8B2eAV3OhN90QoWF5OZWLKl39ig= +cloud.google.com/go/storage v1.24.0/go.mod h1:3xrJEFMXBsQLgxwThyjuD3aYlroL0TMRec1ypGUQ0KE= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= @@ -213,7 +213,6 @@ github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/Oth github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= github.com/googleapis/gax-go/v2 v2.4.0 h1:dS9eYAjhrE2RjmzYw2XAPvcXfmcQLtFEQWn0CR82awk= github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= -github.com/googleapis/go-type-adapters v1.0.0 h1:9XdMn+d/G57qq1s8dNc5IesGCXHf6V2HZ2JwRxfA2tA= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -384,8 +383,9 @@ golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb h1:8tDJ3aechhddbdPAxpycgXHJRMLpk/Ab+aa4OgdN5/g= golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2 h1:+jnHzr9VPj32ykQVai5DNahi9+NSp7yYuCsl5eAQtL0= +golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= From 96049e8a0fd9921b414ef32dc72a15de99a7ac39 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 24 Jul 2022 14:26:48 +0000 Subject: [PATCH 0892/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.56 to 1.44.61 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.56 to 1.44.61. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.56...v1.44.61) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0acb1ae6..eba02cd9 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.1.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.56 + github.com/aws/aws-sdk-go v1.44.61 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.5.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index b4a3fcd6..5e055600 100644 --- a/go.sum +++ b/go.sum @@ -86,8 +86,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.56 h1:bT+lExwagH7djxb6InKUVkEKGPAj5aAPnV85/m1fKro= -github.com/aws/aws-sdk-go v1.44.56/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.61 h1:NcpLSS3Z0MiVQIYugx4I40vSIEEAXT0baO684ExNRco= +github.com/aws/aws-sdk-go v1.44.61/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= From bd247448b1178c0f5bd02d4d903eda3ba26b1078 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Mon, 25 Jul 2022 08:32:29 +0200 Subject: [PATCH 0893/1127] Release v3.13.0 --- .circleci/config.yml | 2 +- .version | 2 +- README.md | 2 +- internal/app/main.go | 2 +- release-notes.md | 7 +++---- 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5f8ca953..1b2c8bfe 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -36,7 +36,7 @@ jobs: - run: name: build docker images and push them to dockerhub command: | - helm_versions=( "v3.7.2" "v3.8.2" "v3.9.0" ) + helm_versions=( "v3.7.2" "v3.8.2" "v3.9.2" ) TAG=$(git describe --abbrev=0 --tags) docker login -u $DOCKER_USER -p $DOCKER_PASS diff --git a/.version b/.version index b0ab92d4..017d61fc 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.12.0 +v3.13.0 diff --git a/README.md b/README.md index 23560742..24132b28 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.12.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.13.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) diff --git a/internal/app/main.go b/internal/app/main.go index c34ce17d..8e9e3414 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.12.0" + appVersion = "v3.13.0" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index d4dd9865..fec582c6 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,9 +1,8 @@ -# v3.12.0 +# v3.13.0 ## New feature -- Added JSON schema generator for DSF (#675) - ## Fixes and improvements -- Fix -target flag inconsistency between app yaml key and app release name (#678) +- Replace gopkg.in/yaml.v2 with sigs.k8s.io/yaml (#686) +- Get kubectl version by taking json output instead of parsing a string (#688) From 1d88a6a9e363e2d138be4c5fa71704bca60fe0c6 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Wed, 27 Jul 2022 12:01:10 +0200 Subject: [PATCH 0894/1127] Fix broken .helmsman-tmp cleanup caused by os.Exit not respectng cleanup defer --- cmd/helmsman/main.go | 4 +++- internal/app/main.go | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/cmd/helmsman/main.go b/cmd/helmsman/main.go index 41b75880..ca4a8d8f 100644 --- a/cmd/helmsman/main.go +++ b/cmd/helmsman/main.go @@ -2,8 +2,10 @@ package main import ( "github.com/Praqma/helmsman/internal/app" + "os" ) func main() { - app.Main() + exitCode := app.Main() + os.Exit(exitCode) } diff --git a/internal/app/main.go b/internal/app/main.go index 8e9e3414..1f73872f 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -32,7 +32,7 @@ func init() { } // Main is the app main function -func Main() { +func Main() int { var s State flags.parse() @@ -141,5 +141,5 @@ func Main() { exitCode = exitCodeSucceedWithChanges } - os.Exit(exitCode) + return exitCode } From d7d473dc87d7229298be91ee5408e17e5c295bc8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 31 Jul 2022 14:11:48 +0000 Subject: [PATCH 0895/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.61 to 1.44.66 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.61 to 1.44.66. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.61...v1.44.66) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8e08bd40..f232dfa5 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.61 + github.com/aws/aws-sdk-go v1.44.66 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.5.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 836f8786..20210515 100644 --- a/go.sum +++ b/go.sum @@ -86,8 +86,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.61 h1:NcpLSS3Z0MiVQIYugx4I40vSIEEAXT0baO684ExNRco= -github.com/aws/aws-sdk-go v1.44.61/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.66 h1:xdH4EvHyUnkm4I8d536ui7yMQKYzrkbSDQ2LvRRHqsg= +github.com/aws/aws-sdk-go v1.44.66/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= From a22602d40f543a525fe6acb196b1253eec936275 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Wed, 3 Aug 2022 08:53:18 +0200 Subject: [PATCH 0896/1127] Add stale github action --- .github/workflows/stale.yml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .github/workflows/stale.yml diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 00000000..b36ef0a9 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,29 @@ +# This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time. +# +# You can adjust the behavior by modifying this file. +# For more information, see: +# https://github.com/actions/stale +name: Mark stale issues and pull requests + +on: + schedule: + - cron: '0 5 * * *' + +jobs: + stale: + + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + + steps: + - uses: actions/stale@v5 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + days-before-stale: 60 + days-before-close: 6 + stale-issue-message: 'This issue has been marked stale due to an inactivity.' + stale-pr-message: 'This PR has been marked stale due to an inactivity.' + stale-issue-label: 'stale' + stale-pr-label: 'stale' From dd56a177ce9e3d8b2bbe0942e3211e0ffb6e0bde Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Fri, 5 Aug 2022 09:59:32 +0200 Subject: [PATCH 0897/1127] Release v3.13.1 --- README.md | 2 +- internal/app/main.go | 2 +- release-notes.md | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 24132b28..99cc2efa 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.13.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.13.1&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) diff --git a/internal/app/main.go b/internal/app/main.go index 1f73872f..2a78f534 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.13.0" + appVersion = "v3.13.1" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index fec582c6..f3d13a4a 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,8 +1,7 @@ -# v3.13.0 +# v3.13.1 ## New feature ## Fixes and improvements -- Replace gopkg.in/yaml.v2 with sigs.k8s.io/yaml (#686) -- Get kubectl version by taking json output instead of parsing a string (#688) +- Fix .helmsman-tmp not being removed after execution is done (#693) From ae0a2771d9aa1bfa29667238ede5166095e38190 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 7 Aug 2022 14:13:32 +0000 Subject: [PATCH 0898/1127] chore(deps): bump github.com/invopop/jsonschema from 0.5.0 to 0.6.0 Bumps [github.com/invopop/jsonschema](https://github.com/invopop/jsonschema) from 0.5.0 to 0.6.0. - [Release notes](https://github.com/invopop/jsonschema/releases) - [Commits](https://github.com/invopop/jsonschema/compare/v0.5.0...v0.6.0) --- updated-dependencies: - dependency-name: github.com/invopop/jsonschema dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f232dfa5..ac0ca0a1 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 github.com/aws/aws-sdk-go v1.44.66 github.com/imdario/mergo v0.3.13 - github.com/invopop/jsonschema v0.5.0 + github.com/invopop/jsonschema v0.6.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.4.0 golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 diff --git a/go.sum b/go.sum index 20210515..3145c161 100644 --- a/go.sum +++ b/go.sum @@ -223,8 +223,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= -github.com/invopop/jsonschema v0.5.0 h1:6tvpBcwTGxzvx3M9f3IfzqQVyZvoH+0NRUtBcsgyfrU= -github.com/invopop/jsonschema v0.5.0/go.mod h1:O9uiLokuu0+MGFlyiaqtWxwqJm41/+8Nj0lD7A36YH0= +github.com/invopop/jsonschema v0.6.0 h1:8e+xY8ZEn8gDHUYylSlLHy22P+SLeIRIHv3nM3hCbmY= +github.com/invopop/jsonschema v0.6.0/go.mod h1:O9uiLokuu0+MGFlyiaqtWxwqJm41/+8Nj0lD7A36YH0= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= From 87a964658e55639cfe2ae981424ac4870f13123f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 7 Aug 2022 14:13:43 +0000 Subject: [PATCH 0899/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.66 to 1.44.70 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.66 to 1.44.70. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.66...v1.44.70) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f232dfa5..c8a37af7 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.66 + github.com/aws/aws-sdk-go v1.44.70 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.5.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 20210515..9126c6b8 100644 --- a/go.sum +++ b/go.sum @@ -86,8 +86,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.66 h1:xdH4EvHyUnkm4I8d536ui7yMQKYzrkbSDQ2LvRRHqsg= -github.com/aws/aws-sdk-go v1.44.66/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.70 h1:wrwAbqJqf+ncEK1F/bXTYpgO6zXIgQXi/2ppBgmYI9g= +github.com/aws/aws-sdk-go v1.44.70/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= From e1dd62766648e4cbe266594977600295b8f6e94b Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Tue, 9 Aug 2022 08:13:14 +0200 Subject: [PATCH 0900/1127] Make stale action to start from oldest ones --- .github/workflows/stale.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index b36ef0a9..f1be7c2d 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -27,3 +27,5 @@ jobs: stale-pr-message: 'This PR has been marked stale due to an inactivity.' stale-issue-label: 'stale' stale-pr-label: 'stale' + ascending: true + operations-per-run: '60' From 9633c9285f96bff49905cd30a6079b9ac3ef066a Mon Sep 17 00:00:00 2001 From: Ben Bettridge Date: Tue, 9 Aug 2022 22:03:38 +1200 Subject: [PATCH 0901/1127] Add support for Just-Insane/helm-vault plugin --- internal/app/state.go | 14 ++++++++++++++ internal/app/utils.go | 21 +++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/internal/app/state.go b/internal/app/state.go index b1dcf435..e90922a8 100644 --- a/internal/app/state.go +++ b/internal/app/state.go @@ -36,6 +36,20 @@ type Config struct { BearerTokenPath string `json:"bearerTokenPath,omitempty"` // NamespaceLabelsAuthoritativei indicates whether helmsman should remove namespace labels that are not in the DSF NamespaceLabelsAuthoritative bool `json:"namespaceLabelsAuthoritative,omitempty"` + // VaultEnabled indicates whether the helm vault plugin is used for encrypted files + VaultEnabled bool `json:"vaultEnabled,omitempty"` + // VaultDeliminator allows secret deliminator used when parsing to be overridden + VaultDeliminator string `json:"vaultDeliminator,omitempty"` + // VaultPath allows the secret mount location in Vault to be overridden + VaultPath string `json:"vaultPath,omitempty"` + // VaultMountPoint allows the Vault Mount Point to be overridden + VaultMountPoint string `json:"vaultMountPoint,omitempty"` + // VaultTemplate Substring with path to vault key instead of deliminator + VaultTemplate string `json:"vaultTemplate,omitempty"` + // VaultKvVersion The version of the KV secrets engine in Vault + VaultKvVersion string `json:"vaultKvVersion,omitempty"` + // VaultEnvironment Environment that secrets should be stored under + VaultEnvironment string `json:"vaultEnvironment,omitempty"` // EyamlEnabled indicates whether eyaml is used for encrypted files EyamlEnabled bool `json:"eyamlEnabled,omitempty"` // EyamlPrivateKeyPath is the path to the eyaml private key diff --git a/internal/app/utils.go b/internal/app/utils.go index 8e05f2e4..0b051554 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -482,6 +482,27 @@ func decryptSecret(name string) error { if settings.EyamlPrivateKeyPath != "" && settings.EyamlPublicKeyPath != "" { args = append(args, []string{"--pkcs7-private-key", settings.EyamlPrivateKeyPath, "--pkcs7-public-key", settings.EyamlPublicKeyPath}...) } + } else if settings.VaultEnabled { + args = []string{"vault", "dec"} + if settings.VaultDeliminator != "" { + args = append(args, []string{"--deliminator", settings.VaultDeliminator}...) + } + if settings.VaultPath != "" { + args = append(args, []string{"--vaultpath", settings.VaultPath}...) + } + if settings.VaultMountPoint != "" { + args = append(args, []string{"--mountpoint", settings.VaultMountPoint}...) + } + if settings.VaultTemplate != "" { + args = append(args, []string{"--vaulttemplate", settings.VaultTemplate}...) + } + if settings.VaultKvVersion != "" { + args = append(args, []string{"--kvversion", settings.VaultKvVersion}...) + } + if settings.VaultEnvironment != "" { + args = append(args, []string{"--environment", settings.VaultEnvironment}...) + } + args = append(args, []string{name}...) } command := Command{ From a3d5988cbc9add2d016dc9857994f39628d932b0 Mon Sep 17 00:00:00 2001 From: Ben Bettridge Date: Tue, 9 Aug 2022 22:09:14 +1200 Subject: [PATCH 0902/1127] Update dsf docs --- docs/desired_state_specification.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index cfede7d6..42754538 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -113,6 +113,13 @@ The following options can be skipped if your kubectl context is already created - **msTeamsWebhook** : a [Microsoft Teams](https://www.microsoft.com/pl-pl/microsoft-teams/group-chat-software) Webhook URL to receive Helmsman notifications. This can be passed directly or in an environment variable. - **reverseDelete** : if set to `true` it will reverse the priority order whilst deleting. - **namespaceLabelsAuthoritative** : if set to `true` it will remove all the namespace's labels that are not defined in DSL for particular namespace +- **vaultEnabled**: if set to `true` it will use [helm-vault](https://github.com/Just-Insane/helm-vault) to decrypt secret files instead of using default helm-secrets +- **vaultDeliminator**: secret deliminator used when parsing value files. See [helm-vault](https://github.com/Just-Insane/helm-vault#available-flags) docs +- **vaultPath**: vault path (secret mount location in Vault). See [helm-vault](https://github.com/Just-Insane/helm-vault#available-flags) docs +- **vaultMountPoint**: vault secret engine mount point. See [helm-vault](https://github.com/Just-Insane/helm-vault#available-flags) docs +- **vaultTemplate**: substring with path to vault key instead of deliminator. See [helm-vault](https://github.com/Just-Insane/helm-vault#available-flags) docs +- **vaultKvVersion**: version of the KV secrets engine in Vault. See [helm-vault](https://github.com/Just-Insane/helm-vault#available-flags) docs +- **vaultEnvironment**: environment that secrets should be stored under. See [helm-vault](https://github.com/Just-Insane/helm-vault#available-flags) docs - **eyamlEnabled** : if set to `true` it will use [hiera-eyaml](https://github.com/voxpupuli/hiera-eyaml) to decrypt secret files instead of using default helm-secrets based on sops - **eyamlPrivateKeyPath** : if set with path to the eyaml private key file, it will use it instead of looking for default one in ./keys directory relative to where Helmsman were run. It needs to be defined in conjunction with eyamlPublicKeyPath. - **eyamlPublicKeyPath** : if set with path to the eyaml public key file, it will use it instead of looking for default one in ./keys directory relative to where Helmsman were run. It needs to be defined in conjunction with eyamlPrivateKeyPath. @@ -137,6 +144,7 @@ kubeContext = "minikube" # eyamlEnabled = true # eyamlPrivateKeyPath = "../keys/custom-key.pem" # eyamlPublicKeyPath = "../keys/custom-key.pub" +# vaultEnabled = false # [settings.globalHooks] # successCondition= "Complete" # deleteOnSuccess= true @@ -158,6 +166,7 @@ settings: # eyamlEnabled: true # eyamlPrivateKeyPath: ../keys/custom-key.pem # eyamlPublicKeyPath: ../keys/custom-key.pub + # vaultEnabled: false # globalHooks: # successCondition: "Complete" # deleteOnSuccess: true From dba5cf0f5af5ae3036f9db403e548977c7ace7cd Mon Sep 17 00:00:00 2001 From: Ben Bettridge Date: Wed, 10 Aug 2022 11:17:23 +1200 Subject: [PATCH 0903/1127] Check for helm-vault plugin if it's enabled --- internal/app/release_files.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/app/release_files.go b/internal/app/release_files.go index 3d62fe76..44c3d05b 100644 --- a/internal/app/release_files.go +++ b/internal/app/release_files.go @@ -72,6 +72,10 @@ func (r *Release) getValuesFiles() []string { if !ToolExists("eyaml") { log.Fatal("hiera-eyaml is not installed/configured correctly. Aborting!") } + } else if settings.VaultEnabled { + if !helmPluginExists("vault") { + log.Fatal("helm vault plugin is not installed/configured correctly. Aborting!") + } } else { if !helmPluginExists("secrets") { log.Fatal("helm secrets plugin is not installed/configured correctly. Aborting!") From 886cb0248ab97c867792d71183faa61a16bef2ce Mon Sep 17 00:00:00 2001 From: Ben Bettridge Date: Wed, 10 Aug 2022 11:18:46 +1200 Subject: [PATCH 0904/1127] Regenerate schema --- schema.json | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/schema.json b/schema.json index 09e15421..1b6f22c7 100644 --- a/schema.json +++ b/schema.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft/2020-12/schema", + "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/Praqma/helmsman/internal/app/state", "$ref": "#/$defs/State", "$defs": { @@ -53,6 +53,34 @@ "type": "boolean", "description": "NamespaceLabelsAuthoritativei indicates whether helmsman should remove namespace labels that are not in the DSF" }, + "vaultEnabled": { + "type": "boolean", + "description": "VaultEnabled indicates whether the helm vault plugin is used for encrypted files" + }, + "vaultDeliminator": { + "type": "string", + "description": "VaultDeliminator allows secret deliminator used when parsing to be overridden" + }, + "vaultPath": { + "type": "string", + "description": "VaultPath allows the secret mount location in Vault to be overridden" + }, + "vaultMountPoint": { + "type": "string", + "description": "VaultMountPoint allows the Vault Mount Point to be overridden" + }, + "vaultTemplate": { + "type": "string", + "description": "VaultTemplate Substring with path to vault key instead of deliminator" + }, + "vaultKvVersion": { + "type": "string", + "description": "VaultKvVersion The version of the KV secrets engine in Vault" + }, + "vaultEnvironment": { + "type": "string", + "description": "VaultEnvironment Environment that secrets should be stored under" + }, "eyamlEnabled": { "type": "boolean", "description": "EyamlEnabled indicates whether eyaml is used for encrypted files" From 194b54401d30e14872fe466222a7f8402dfe644b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 14 Aug 2022 14:44:04 +0000 Subject: [PATCH 0905/1127] chore(deps): bump cloud.google.com/go/storage from 1.24.0 to 1.25.0 Bumps [cloud.google.com/go/storage](https://github.com/googleapis/google-cloud-go) from 1.24.0 to 1.25.0. - [Release notes](https://github.com/googleapis/google-cloud-go/releases) - [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/CHANGES.md) - [Commits](https://github.com/googleapis/google-cloud-go/compare/pubsub/v1.24.0...spanner/v1.25.0) --- updated-dependencies: - dependency-name: cloud.google.com/go/storage dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 12 ++++++------ go.sum | 23 +++++++++++++---------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/go.mod b/go.mod index 4bf318a5..bdf20b87 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Praqma/helmsman go 1.18 require ( - cloud.google.com/go/storage v1.24.0 + cloud.google.com/go/storage v1.25.0 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.15.0 github.com/BurntSushi/toml v1.2.0 @@ -14,7 +14,7 @@ require ( github.com/invopop/jsonschema v0.6.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.4.0 - golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 + golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e sigs.k8s.io/yaml v1.3.0 ) @@ -37,13 +37,13 @@ require ( go.opencensus.io v0.23.0 // indirect golang.org/x/crypto v0.0.0-20220312131142-6068a2e6cfdc // indirect golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2 // indirect - golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c // indirect + golang.org/x/sys v0.0.0-20220624220833-87e55d714810 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect - google.golang.org/api v0.85.0 // indirect + google.golang.org/api v0.88.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad // indirect - google.golang.org/grpc v1.47.0 // indirect + google.golang.org/genproto v0.0.0-20220720214146-176da50484ac // indirect + google.golang.org/grpc v1.48.0 // indirect google.golang.org/protobuf v1.28.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 9de9214f..bf06ceae 100644 --- a/go.sum +++ b/go.sum @@ -57,8 +57,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= -cloud.google.com/go/storage v1.24.0 h1:a4N0gIkx83uoVFGz8B2eAV3OhN90QoWF5OZWLKl39ig= -cloud.google.com/go/storage v1.24.0/go.mod h1:3xrJEFMXBsQLgxwThyjuD3aYlroL0TMRec1ypGUQ0KE= +cloud.google.com/go/storage v1.25.0 h1:D2Dn0PslpK7Z3B2AvuUHyIC762bDbGJdlmQlCBR71os= +cloud.google.com/go/storage v1.25.0/go.mod h1:Qys4JU+jeup3QnuKKAosWuxrD95C4MSqxfVDnSirDsI= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= @@ -362,8 +362,8 @@ golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 h1:Yqz/iviulwKwAREEeUd3nbBFn0XuyJqkoft2IlrvOhc= -golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e h1:TsQ7F31D3bUCLeqPT0u+yjp1guoArKaNKmCr22PYgTQ= +golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -457,8 +457,8 @@ golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c h1:aFV+BgZ4svzjfabn8ERpuB4JI4N6/rdy1iusx77G3oU= -golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220624220833-87e55d714810 h1:rHZQSjJdAI4Xf5Qzeh2bBc5YJIkPFVM6oDtMFYmgws0= +golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -572,8 +572,8 @@ google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69 google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= -google.golang.org/api v0.85.0 h1:8rJoHuRxx+vCmZtAO/3k1dRLvYNVyTJtZ5oaFZvhgvc= -google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g= +google.golang.org/api v0.88.0 h1:MPwxQRqpyskYhr2iNyfsQ8R06eeyhe7UEuR30p136ZQ= +google.golang.org/api v0.88.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -660,8 +660,10 @@ google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad h1:kqrS+lhvaMHCxul6sKQvKJ8nAAhlVItmZV822hYFH/U= google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220720214146-176da50484ac h1:EOa+Yrhx1C0O+4pHeXeWrCwdI0tWI6IfUU56Vebs9wQ= +google.golang.org/genproto v0.0.0-20220720214146-176da50484ac/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -692,8 +694,9 @@ google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ5 google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8= google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.48.0 h1:rQOsyJ/8+ufEDJd/Gdsz7HG220Mh9HAhFHRGnIjda0w= +google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= From d53cadfb17ef01348faf666d6389a21f939479b4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 14 Aug 2022 14:44:32 +0000 Subject: [PATCH 0906/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.70 to 1.44.76 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.70 to 1.44.76. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.70...v1.44.76) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 4bf318a5..acc37575 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.70 + github.com/aws/aws-sdk-go v1.44.76 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.6.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 9de9214f..0bb349df 100644 --- a/go.sum +++ b/go.sum @@ -86,8 +86,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.70 h1:wrwAbqJqf+ncEK1F/bXTYpgO6zXIgQXi/2ppBgmYI9g= -github.com/aws/aws-sdk-go v1.44.70/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.76 h1:5e8yGO/XeNYKckOjpBKUd5wStf0So3CrQIiOMCVLpOI= +github.com/aws/aws-sdk-go v1.44.76/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= From 2cb2f161af09b3a2dd51e7ba59d0784d6833f64c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 21 Aug 2022 15:02:53 +0000 Subject: [PATCH 0907/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.76 to 1.44.81 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.76 to 1.44.81. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.76...v1.44.81) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index effc6b34..1f7cea92 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.76 + github.com/aws/aws-sdk-go v1.44.81 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.6.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 040265b1..b5e3b203 100644 --- a/go.sum +++ b/go.sum @@ -86,8 +86,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.76 h1:5e8yGO/XeNYKckOjpBKUd5wStf0So3CrQIiOMCVLpOI= -github.com/aws/aws-sdk-go v1.44.76/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.81 h1:C8oBZ+a+ka0qk3Q24MohQIFq0tkbO8IAu5tfpAMKVWE= +github.com/aws/aws-sdk-go v1.44.81/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= From 43002a38e6bd373dc2b76ed6ab96fded32149806 Mon Sep 17 00:00:00 2001 From: Alex Prizov Date: Mon, 22 Aug 2022 09:53:56 +0300 Subject: [PATCH 0908/1127] exit when env variables substitution fails --- internal/app/logging.go | 5 ----- internal/app/utils.go | 4 ++-- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/internal/app/logging.go b/internal/app/logging.go index 35ee4677..7ba7c866 100644 --- a/internal/app/logging.go +++ b/internal/app/logging.go @@ -30,11 +30,6 @@ func (l *Logger) Error(message string) { l.Logger.Error(message) } -func (l *Logger) Critical(message string) { - l.notifyAboutFailureUsingWebhooks(message) - l.Logger.Critical(message) -} - func (l *Logger) Fatal(message string) { l.notifyAboutFailureUsingWebhooks(message) l.Logger.Fatal(message) diff --git a/internal/app/utils.go b/internal/app/utils.go index 0b051554..a3d5353f 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -57,7 +57,7 @@ func substituteVarsInYaml(file string) string { yamlFile := string(rawYamlFile) if !flags.noEnvSubst && flags.substEnvValues { if err := validateEnvVars(yamlFile, file); err != nil { - log.Critical(err.Error()) + log.Fatal(err.Error()) } yamlFile = substituteEnv(yamlFile) } @@ -207,7 +207,7 @@ func validateEnvVars(s string, filename string) error { } } if err := scanner.Err(); err != nil { - log.Critical(err.Error()) + log.Fatal(err.Error()) } } return nil From 7023340d413839a3e2a4726e4b3b888f3406c385 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 28 Aug 2022 14:17:06 +0000 Subject: [PATCH 0909/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.81 to 1.44.86 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.81 to 1.44.86. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.81...v1.44.86) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 1f7cea92..e8876c88 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.81 + github.com/aws/aws-sdk-go v1.44.86 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.6.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index b5e3b203..bdb1c3b9 100644 --- a/go.sum +++ b/go.sum @@ -86,8 +86,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.81 h1:C8oBZ+a+ka0qk3Q24MohQIFq0tkbO8IAu5tfpAMKVWE= -github.com/aws/aws-sdk-go v1.44.81/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.86 h1:Zls97WY9N2c2H85//B88CmSlYYNxS3Zf3k4ds5zAf5A= +github.com/aws/aws-sdk-go v1.44.86/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= From 638dd73d4545090dca3812c71d47e54599bc9dd3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 28 Aug 2022 14:17:14 +0000 Subject: [PATCH 0910/1127] chore(deps): bump github.com/subosito/gotenv from 1.4.0 to 1.4.1 Bumps [github.com/subosito/gotenv](https://github.com/subosito/gotenv) from 1.4.0 to 1.4.1. - [Release notes](https://github.com/subosito/gotenv/releases) - [Changelog](https://github.com/subosito/gotenv/blob/master/CHANGELOG.md) - [Commits](https://github.com/subosito/gotenv/compare/v1.4.0...v1.4.1) --- updated-dependencies: - dependency-name: github.com/subosito/gotenv dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 1f7cea92..6197892b 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.6.0 github.com/logrusorgru/aurora v2.0.3+incompatible - github.com/subosito/gotenv v1.4.0 + github.com/subosito/gotenv v1.4.1 golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e sigs.k8s.io/yaml v1.3.0 ) diff --git a/go.sum b/go.sum index b5e3b203..1a44fc67 100644 --- a/go.sum +++ b/go.sum @@ -257,9 +257,9 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= -github.com/subosito/gotenv v1.4.0 h1:yAzM1+SmVcz5R4tXGsNMu1jUl2aOJXoiWUCEwwnGrvs= -github.com/subosito/gotenv v1.4.0/go.mod h1:mZd6rFysKEcUhUHXJk0C/08wAgyDBFuwEYL7vWWGaGo= +github.com/stretchr/testify v1.7.5 h1:s5PTfem8p8EbKQOctVV53k6jCJt3UX4IEJzwh+C324Q= +github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= +github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= From 9e68463188937f4ae032fa5f551afd2425dcbb75 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sun, 28 Aug 2022 22:51:30 +0100 Subject: [PATCH 0911/1127] release: v3.14.0 --- .circleci/config.yml | 2 +- .version | 2 +- Dockerfile | 4 ++-- README.md | 2 +- internal/app/main.go | 2 +- release-notes.md | 7 +++++-- 6 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1b2c8bfe..5933c71f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -36,7 +36,7 @@ jobs: - run: name: build docker images and push them to dockerhub command: | - helm_versions=( "v3.7.2" "v3.8.2" "v3.9.2" ) + helm_versions=( "v3.7.2" "v3.8.2" "v3.9.4" ) TAG=$(git describe --abbrev=0 --tags) docker login -u $DOCKER_USER -p $DOCKER_PASS diff --git a/.version b/.version index 017d61fc..5ef3202e 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.13.0 +v3.14.0 diff --git a/Dockerfile b/Dockerfile index 159a58a7..d61de0aa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ -ARG GO_VERSION="1.18.2" +ARG GO_VERSION="1.18.5" ARG ALPINE_VERSION="3.15" ARG GLOBAL_KUBE_VERSION="v1.23.6" -ARG GLOBAL_HELM_VERSION="v3.9.0" +ARG GLOBAL_HELM_VERSION="v3.9.4" ARG GLOBAL_HELM_DIFF_VERSION="v3.5.0" ARG GLOBAL_HELM_GCS_VERSION="0.3.21" ARG GLOBAL_HELM_S3_VERSION="v0.10.0" diff --git a/README.md b/README.md index 99cc2efa..e006144e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.13.1&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.14.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) diff --git a/internal/app/main.go b/internal/app/main.go index 2a78f534..bfe50109 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.13.1" + appVersion = "v3.14.0" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index f3d13a4a..e7310917 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,7 +1,10 @@ -# v3.13.1 +# v3.14.0 ## New feature +- Add support for Just-Insane/helm-vault plugin + ## Fixes and improvements -- Fix .helmsman-tmp not being removed after execution is done (#693) +- Exit when env variables substitution fails (#704) +- Updated dependencies From a06ed92496f4caa818300148308d657abc6f40ce Mon Sep 17 00:00:00 2001 From: prizov Date: Fri, 2 Sep 2022 00:22:37 +0300 Subject: [PATCH 0912/1127] fix(#137): use nullable boolean type for the Release boolean fields (#707) --- examples/minimal-example-overwrite.yaml | 7 + internal/app/custom_types.go | 83 +++++++++++ internal/app/custom_types_test.go | 179 ++++++++++++++++++++++++ internal/app/decision_maker.go | 2 +- internal/app/decision_maker_test.go | 44 +++--- internal/app/helm_helpers_test.go | 8 +- internal/app/release.go | 38 ++--- internal/app/release_test.go | 78 +++++------ internal/app/state_files.go | 5 +- internal/app/state_files_test.go | 26 ++++ internal/app/state_test.go | 10 +- internal/app/utils_test.go | 6 +- schema.json | 13 +- 13 files changed, 400 insertions(+), 99 deletions(-) create mode 100644 examples/minimal-example-overwrite.yaml create mode 100644 internal/app/custom_types.go create mode 100644 internal/app/custom_types_test.go diff --git a/examples/minimal-example-overwrite.yaml b/examples/minimal-example-overwrite.yaml new file mode 100644 index 00000000..d9bf732f --- /dev/null +++ b/examples/minimal-example-overwrite.yaml @@ -0,0 +1,7 @@ +## This is a minimal example. +## It will use your current kube context and will deploy Tiller without RBAC service account. +## For the full config spec and options, check https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md + +apps: + jenkins: + enabled: false diff --git a/internal/app/custom_types.go b/internal/app/custom_types.go new file mode 100644 index 00000000..e682673e --- /dev/null +++ b/internal/app/custom_types.go @@ -0,0 +1,83 @@ +package app + +import ( + "encoding/json" + "github.com/invopop/jsonschema" + "reflect" + "strconv" +) + +// truthy and falsy NullBool values +var ( + True = NullBool{HasValue: true, Value: true} + False = NullBool{HasValue: true, Value: false} +) + +// NullBool represents a bool that may be null. +type NullBool struct { + Value bool + HasValue bool // true if bool is not null +} + +func (b NullBool) MarshalJSON() ([]byte, error) { + value := b.HasValue && b.Value + return json.Marshal(value) +} + +func (b *NullBool) UnmarshalJSON(data []byte) error { + var unmarshalledJson bool + + err := json.Unmarshal(data, &unmarshalledJson) + if err != nil { + return err + } + + b.Value = unmarshalledJson + b.HasValue = true + + return nil +} + +func (b *NullBool) UnmarshalText(text []byte) error { + str := string(text) + if len(str) < 1 { + return nil + } + + value, err := strconv.ParseBool(str) + if err != nil { + return err + } + + b.HasValue = true + b.Value = value + + return nil +} + +// JSONSchema instructs the jsonschema generator to represent NullBool type as boolean +func (NullBool) JSONSchema() *jsonschema.Schema { + return &jsonschema.Schema{ + Type: "boolean", + } +} + +type MergoTransformer func(typ reflect.Type) func(dst, src reflect.Value) error + +func (m MergoTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error { + return m(typ) +} + +// NullBoolTransformer is a custom imdario/mergo transformer for the NullBool type +func NullBoolTransformer(typ reflect.Type) func(dst, src reflect.Value) error { + if typ != reflect.TypeOf(NullBool{}) { + return nil + } + + return func(dst, src reflect.Value) error { + if src.FieldByName("HasValue").Bool() { + dst.Set(src) + } + return nil + } +} diff --git a/internal/app/custom_types_test.go b/internal/app/custom_types_test.go new file mode 100644 index 00000000..079071e4 --- /dev/null +++ b/internal/app/custom_types_test.go @@ -0,0 +1,179 @@ +package app + +import ( + "bytes" + "encoding/json" + "reflect" + "testing" +) + +func TestNullBool_MarshalJSON(t *testing.T) { + tests := []struct { + name string + value NullBool + want []byte + wantErr bool + }{ + { + name: "should be false", + want: []byte(`false`), + wantErr: false, + }, + { + name: "should be true", + want: []byte(`true`), + value: NullBool{HasValue: true, Value: true}, + wantErr: false, + }, + { + name: "should be false when HasValue is false", + want: []byte(`false`), + value: NullBool{HasValue: false, Value: true}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.value.MarshalJSON() + if (err != nil) != tt.wantErr { + t.Errorf("MarshalJSON() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("MarshalJSON() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNullBool_UnmarshalJSON(t *testing.T) { + type output struct { + Value NullBool `json:"value"` + } + tests := []struct { + name string + data []byte + want output + wantErr bool + }{ + { + name: "should have value set to false", + data: []byte(`{"value": false}`), + want: output{NullBool{HasValue: true, Value: false}}, + }, + { + name: "should have value set to true", + data: []byte(`{"value": true}`), + want: output{NullBool{HasValue: true, Value: true}}, + }, + { + name: "should have value unset", + data: []byte("{}"), + want: output{NullBool{HasValue: false, Value: false}}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var got output + if err := json.NewDecoder(bytes.NewReader(tt.data)).Decode(&got); (err != nil) != tt.wantErr { + t.Errorf("UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("UnmarshalJSON() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNullBool_UnmarshalText(t *testing.T) { + tests := []struct { + name string + text []byte + want NullBool + wantErr bool + }{ + { + name: "should have the value set to false", + text: []byte("false"), + want: NullBool{HasValue: true, Value: false}, + }, + { + name: "should have the value set to true", + text: []byte("false"), + want: NullBool{HasValue: true, Value: false}, + }, + { + name: "should have the value unset", + text: []byte(""), + want: NullBool{HasValue: false, Value: false}, + }, + { + name: "should return an error on wrong input", + text: []byte("wrong_input"), + wantErr: true, + want: NullBool{HasValue: false, Value: false}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var got NullBool + if err := got.UnmarshalText(tt.text); (err != nil) != tt.wantErr { + t.Errorf("UnmarshalText() error = %v, wantErr %v", err, tt.wantErr) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("UnmarshalText() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNullBoolTransformer(t *testing.T) { + type args struct { + dst NullBool + src NullBool + } + tests := []struct { + name string + args args + want NullBool + }{ + { + name: "should overwrite true to false when the dst has the value", + args: args{ + dst: NullBool{HasValue: true, Value: true}, + src: NullBool{HasValue: true, Value: false}, + }, + want: NullBool{HasValue: true, Value: false}, + }, + { + name: "shouldn't overwrite when the value is unset", + args: args{ + dst: NullBool{HasValue: true, Value: true}, + src: NullBool{HasValue: false, Value: false}, + }, + want: NullBool{HasValue: true, Value: true}, + }, + { + name: "shouldn overwrite when the value is set and equal true", + args: args{ + dst: NullBool{HasValue: true, Value: false}, + src: NullBool{HasValue: true, Value: true}, + }, + want: NullBool{HasValue: true, Value: true}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dst := tt.args.dst + src := tt.args.src + + transformer := NullBoolTransformer(reflect.TypeOf(NullBool{})) + + transformer(reflect.ValueOf(&dst).Elem(), reflect.ValueOf(src)) + + if !reflect.DeepEqual(dst, tt.want) { + t.Errorf("NullBoolTransformer() = %v, want %v", dst, tt.want) + } + }) + } +} diff --git a/internal/app/decision_maker.go b/internal/app/decision_maker.go index 23e4bc24..2413824a 100644 --- a/internal/app/decision_maker.go +++ b/internal/app/decision_maker.go @@ -115,7 +115,7 @@ func (cs *currentState) decide(r *Release, n *Namespace, p *plan, c *ChartInfo, return nil } - if !r.Enabled { + if !r.Enabled.Value { if ok := cs.releaseExists(r, ""); ok { p.addDecision(prefix+" is desired to be DELETED.", r.Priority, remove) r.uninstall(p) diff --git a/internal/app/decision_maker_test.go b/internal/app/decision_maker_test.go index f625c179..7a4ab16c 100644 --- a/internal/app/decision_maker_test.go +++ b/internal/app/decision_maker_test.go @@ -21,11 +21,11 @@ func Test_getValuesFiles(t *testing.T) { Name: "release1", Description: "", Namespace: "namespace", - Enabled: true, + Enabled: True, Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", - Test: true, + Test: True, }, // s: st, }, @@ -38,11 +38,11 @@ func Test_getValuesFiles(t *testing.T) { Name: "release1", Description: "", Namespace: "namespace", - Enabled: true, + Enabled: True, Chart: "repo/chartX", Version: "1.0", ValuesFiles: []string{"../../tests/values.yaml"}, - Test: true, + Test: True, }, // s: st, }, @@ -55,11 +55,11 @@ func Test_getValuesFiles(t *testing.T) { Name: "release1", Description: "", Namespace: "namespace", - Enabled: true, + Enabled: True, Chart: "repo/chartX", Version: "1.0", ValuesFiles: []string{"../../tests/values.yaml", "../../tests/values2.yaml"}, - Test: true, + Test: True, }, // s: st, }, @@ -93,7 +93,7 @@ func Test_inspectUpgradeScenario(t *testing.T) { Namespace: "namespace", Version: "1.0.0", Chart: "./../../tests/chart-test", - Enabled: true, + Enabled: True, }, s: &map[string]helmRelease{ "release1-namespace": { @@ -151,7 +151,7 @@ func Test_decide(t *testing.T) { "release1": { Name: "release1", Namespace: "namespace", - Enabled: true, + Enabled: True, }, }, }, @@ -170,7 +170,7 @@ func Test_decide(t *testing.T) { "release1": { Name: "release1", Namespace: "namespace", - Enabled: true, + Enabled: True, }, }, }, @@ -189,7 +189,7 @@ func Test_decide(t *testing.T) { "release4": { Name: "release4", Namespace: "namespace", - Enabled: true, + Enabled: True, }, }, }, @@ -208,7 +208,7 @@ func Test_decide(t *testing.T) { "thisRelease": { Name: "thisRelease", Namespace: "namespace", - Enabled: true, + Enabled: True, }, }, }, @@ -227,7 +227,7 @@ func Test_decide(t *testing.T) { "thisRelease": { Name: "thisRelease", Namespace: "namespace", - Enabled: true, + Enabled: True, }, }, }, @@ -246,7 +246,7 @@ func Test_decide(t *testing.T) { "thisRelease": { Name: "thisRelease", Namespace: "namespace", - Enabled: true, + Enabled: True, }, }, }, @@ -265,7 +265,7 @@ func Test_decide(t *testing.T) { "thisRelease": { Name: "thisRelease", Namespace: "namespace", - Enabled: true, + Enabled: True, Group: "myGroup", }, }, @@ -318,12 +318,12 @@ func Test_decide_skip_ignored_apps(t *testing.T) { "service1": { Name: "service1", Namespace: "namespace", - Enabled: true, + Enabled: True, }, "service2": { Name: "service2", Namespace: "namespace", - Enabled: true, + Enabled: True, }, }, }, @@ -340,12 +340,12 @@ func Test_decide_skip_ignored_apps(t *testing.T) { "service1": { Name: "service1", Namespace: "namespace", - Enabled: true, + Enabled: True, }, "service2": { Name: "service2", Namespace: "namespace", - Enabled: true, + Enabled: True, }, }, }, @@ -400,7 +400,7 @@ func Test_decide_group(t *testing.T) { Name: "release1", Namespace: "namespace", Group: "run-me-not", - Enabled: true, + Enabled: True, }, }, }, @@ -417,19 +417,19 @@ func Test_decide_group(t *testing.T) { Name: "release1", Namespace: "namespace", Group: "run-me", - Enabled: true, + Enabled: True, }, "release2": { Name: "release2", Namespace: "namespace", Group: "run-me-not", - Enabled: true, + Enabled: True, }, "release3": { Name: "release3", Namespace: "namespace2", Group: "run-me-not", - Enabled: true, + Enabled: True, }, }, }, diff --git a/internal/app/helm_helpers_test.go b/internal/app/helm_helpers_test.go index 49491ab0..56fb4f15 100644 --- a/internal/app/helm_helpers_test.go +++ b/internal/app/helm_helpers_test.go @@ -23,7 +23,7 @@ func Test_getChartInfo(t *testing.T) { Namespace: "namespace", Version: "1.0.0", Chart: "./../../tests/chart-test", - Enabled: true, + Enabled: True, }, }, want: &ChartInfo{Name: "chart-test", Version: "1.0.0"}, @@ -36,7 +36,7 @@ func Test_getChartInfo(t *testing.T) { Namespace: "namespace", Version: "1.0.*", Chart: "./../../tests/chart-test", - Enabled: true, + Enabled: True, }, }, want: &ChartInfo{Name: "chart-test", Version: "1.0.0"}, @@ -49,7 +49,7 @@ func Test_getChartInfo(t *testing.T) { Namespace: "namespace", Version: "1.0.0", Chart: "random-chart-name-1f8147", - Enabled: true, + Enabled: True, }, }, want: nil, @@ -62,7 +62,7 @@ func Test_getChartInfo(t *testing.T) { Namespace: "namespace", Version: "0.9.0", Chart: "./../../tests/chart-test", - Enabled: true, + Enabled: True, }, }, want: nil, diff --git a/internal/app/release.go b/internal/app/release.go index dd2a01dc..8695329f 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -17,9 +17,9 @@ type Release struct { // Namespace where to deploy the helm release Namespace string `json:"namespace"` // Enabled can be used to togle a helm release - Enabled bool `json:"enabled"` - Group string `json:"group,omitempty"` - Chart string `json:"chart"` + Enabled NullBool `json:"enabled"` + Group string `json:"group,omitempty"` + Chart string `json:"chart"` // Version of the helm chart to deploy Version string `json:"version"` // ValuesFile is the path for a values file for the helm release @@ -33,11 +33,11 @@ type Release struct { // PostRenderer is the path to an executable to be used for post rendering PostRenderer string `json:"postRenderer,omitempty"` // Test indicates if the chart tests should be executed - Test bool `json:"test,omitempty"` + Test NullBool `json:"test,omitempty"` // Protected defines if the release should be protected against changes - Protected bool `json:"protected,omitempty"` + Protected NullBool `json:"protected,omitempty"` // Wait defines whether helm should block execution until all k8s resources are in a ready state - Wait bool `json:"wait,omitempty"` + Wait NullBool `json:"wait,omitempty"` // Priority allows defining the execution order, releases with the same priority can be executed in parallel Priority int `json:"priority,omitempty"` // Set can be used to overwrite the chart values @@ -51,7 +51,7 @@ type Release struct { // HelmDiffFlags is a list of cli flags to pass to helm diff HelmDiffFlags []string `json:"helmDiffFlags,omitempty"` // NoHooks can be used to disable the execution of helm hooks - NoHooks bool `json:"noHooks,omitempty"` + NoHooks NullBool `json:"noHooks,omitempty"` // Timeout is the number of seconds to wait for the release to complete Timeout int `json:"timeout,omitempty"` // Hooks can be used to define lifecycle hooks specific to this release @@ -162,7 +162,7 @@ func (r *Release) test(afterCommands *[]hookCmd) { func (r *Release) install(p *plan) { before, after := r.checkHooks("install") - if r.Test { + if r.Test.Value { r.test(&after) } @@ -234,7 +234,7 @@ func (r *Release) diff() (string, error) { func (r *Release) upgrade(p *plan) { before, after := r.checkHooks("upgrade") - if r.Test { + if r.Test.Value { r.test(&after) } @@ -292,7 +292,7 @@ func (r *Release) label(storageBackend string, labels ...string) { if len(labels) == 0 { return } - if r.Enabled { + if r.Enabled.Value { args := []string{"label", "--overwrite", storageBackend, "-n", r.Namespace, "-l", "owner=helm,name=" + r.Name} args = append(args, labels...) @@ -309,7 +309,7 @@ func (r *Release) annotate(storageBackend string, annotations ...string) { if len(annotations) == 0 { return } - if r.Enabled { + if r.Enabled.Value { args := []string{"annotate", "--overwrite", storageBackend, "-n", r.Namespace, "-l", "owner=helm,name=" + r.Name} args = append(args, annotations...) @@ -330,7 +330,7 @@ func (r *Release) isProtected(cs *currentState, n *Namespace) bool { if ok := cs.releaseExists(r, ""); !ok { return false } - if n.Protected || r.Protected { + if n.Protected || r.Protected.Value { return true } return false @@ -338,7 +338,7 @@ func (r *Release) isProtected(cs *currentState, n *Namespace) bool { // getNoHooks returns the no-hooks flag for install/upgrade commands func (r *Release) getNoHooks() []string { - if r.NoHooks { + if r.NoHooks.Value { return []string{"--no-hooks"} } return []string{} @@ -383,7 +383,7 @@ func (r *Release) getSetFileValues() []string { // Otherwise, retruns an empty string func (r *Release) getWait() []string { res := []string{} - if r.Wait { + if r.Wait.Value { res = append(res, "--wait") } return res @@ -595,15 +595,15 @@ func (r Release) print() { fmt.Println("\tname: ", r.Name) fmt.Println("\tdescription: ", r.Description) fmt.Println("\tnamespace: ", r.Namespace) - fmt.Println("\tenabled: ", r.Enabled) + fmt.Println("\tenabled: ", r.Enabled.Value) fmt.Println("\tchart: ", r.Chart) fmt.Println("\tversion: ", r.Version) fmt.Println("\tvaluesFile: ", r.ValuesFile) fmt.Println("\tvaluesFiles: ", strings.Join(r.ValuesFiles, ",")) fmt.Println("\tpostRenderer: ", r.PostRenderer) - fmt.Println("\ttest: ", r.Test) - fmt.Println("\tprotected: ", r.Protected) - fmt.Println("\twait: ", r.Wait) + fmt.Println("\ttest: ", r.Test.Value) + fmt.Println("\tprotected: ", r.Protected.Value) + fmt.Println("\twait: ", r.Wait.Value) fmt.Println("\tpriority: ", r.Priority) fmt.Println("\tSuccessCondition: ", r.Hooks["successCondition"]) fmt.Println("\tSuccessTimeout: ", r.Hooks["successTimeout"]) @@ -614,7 +614,7 @@ func (r Release) print() { fmt.Println("\tpostUpgrade: ", r.Hooks[postUpgrade]) fmt.Println("\tpreDelete: ", r.Hooks[preDelete]) fmt.Println("\tpostDelete: ", r.Hooks[postDelete]) - fmt.Println("\tno-hooks: ", r.NoHooks) + fmt.Println("\tno-hooks: ", r.NoHooks.Value) fmt.Println("\ttimeout: ", r.Timeout) fmt.Println("\tvalues to override from env:") printMap(r.Set, 2) diff --git a/internal/app/release_test.go b/internal/app/release_test.go index 3b6a4b35..e03b5834 100644 --- a/internal/app/release_test.go +++ b/internal/app/release_test.go @@ -30,11 +30,11 @@ func Test_release_validate(t *testing.T) { Name: "release1", Description: "", Namespace: "namespace", - Enabled: true, + Enabled: True, Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", - Test: true, + Test: True, }, s: st, }, @@ -46,11 +46,11 @@ func Test_release_validate(t *testing.T) { Name: "release2", Description: "", Namespace: "namespace", - Enabled: true, + Enabled: True, Chart: "repo/chartX", Version: "1.0", ValuesFile: "xyz.yaml", - Test: true, + Test: True, }, s: st, }, @@ -62,11 +62,11 @@ func Test_release_validate(t *testing.T) { Name: "release3", Description: "", Namespace: "namespace", - Enabled: true, + Enabled: True, Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.xml", - Test: true, + Test: True, }, s: st, }, @@ -78,11 +78,11 @@ func Test_release_validate(t *testing.T) { Name: "release1", Description: "", Namespace: "namespace", - Enabled: true, + Enabled: True, Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", - Test: true, + Test: True, }, s: st, }, @@ -94,11 +94,11 @@ func Test_release_validate(t *testing.T) { Name: "", Description: "", Namespace: "namespace", - Enabled: true, + Enabled: True, Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", - Test: true, + Test: True, }, s: st, }, @@ -110,11 +110,11 @@ func Test_release_validate(t *testing.T) { Name: "release6", Description: "", Namespace: "", - Enabled: true, + Enabled: True, Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", - Test: true, + Test: True, }, s: st, }, @@ -126,11 +126,11 @@ func Test_release_validate(t *testing.T) { Name: "release7", Description: "", Namespace: "namespace", - Enabled: true, + Enabled: True, Chart: "chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", - Test: true, + Test: True, }, s: st, }, @@ -142,11 +142,11 @@ func Test_release_validate(t *testing.T) { Name: "release8", Description: "", Namespace: "namespace", - Enabled: true, + Enabled: True, Chart: "", Version: "1.0", ValuesFile: "../../tests/values.yaml", - Test: true, + Test: True, }, s: st, }, @@ -158,11 +158,11 @@ func Test_release_validate(t *testing.T) { Name: "release9", Description: "", Namespace: "namespace", - Enabled: true, + Enabled: True, Chart: "repo/chartX", Version: "", ValuesFile: "../../tests/values.yaml", - Test: true, + Test: True, }, s: st, }, @@ -174,11 +174,11 @@ func Test_release_validate(t *testing.T) { Name: "release10", Description: "", Namespace: "namespace", - Enabled: true, + Enabled: True, Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", - Test: true, + Test: True, }, s: st, }, @@ -190,12 +190,12 @@ func Test_release_validate(t *testing.T) { Name: "release11", Description: "", Namespace: "namespace", - Enabled: true, + Enabled: True, Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", ValuesFiles: []string{"xyz.yaml"}, - Test: true, + Test: True, }, s: st, }, @@ -207,11 +207,11 @@ func Test_release_validate(t *testing.T) { Name: "release12", Description: "", Namespace: "namespace", - Enabled: true, + Enabled: True, Chart: "repo/chartX", Version: "1.0", ValuesFiles: []string{"xyz.yaml"}, - Test: true, + Test: True, }, s: st, }, @@ -223,11 +223,11 @@ func Test_release_validate(t *testing.T) { Name: "release13", Description: "", Namespace: "namespace", - Enabled: true, + Enabled: True, Chart: "repo/chartX", Version: "1.0", ValuesFiles: []string{"./../../tests/values.yaml", "../../tests/values2.yaml"}, - Test: true, + Test: True, }, s: st, }, @@ -239,7 +239,7 @@ func Test_release_validate(t *testing.T) { Name: "release14", Description: "", Namespace: "namespace", - Enabled: true, + Enabled: True, Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", @@ -255,7 +255,7 @@ func Test_release_validate(t *testing.T) { Name: "release15", Description: "", Namespace: "namespace", - Enabled: true, + Enabled: True, Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", @@ -271,7 +271,7 @@ func Test_release_validate(t *testing.T) { Name: "release16", Description: "", Namespace: "namespace", - Enabled: true, + Enabled: True, Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", @@ -287,7 +287,7 @@ func Test_release_validate(t *testing.T) { Name: "release17", Description: "", Namespace: "namespace", - Enabled: true, + Enabled: True, Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", @@ -303,7 +303,7 @@ func Test_release_validate(t *testing.T) { Name: "release18", Description: "", Namespace: "namespace", - Enabled: true, + Enabled: True, Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", @@ -319,7 +319,7 @@ func Test_release_validate(t *testing.T) { Name: "release19", Description: "", Namespace: "namespace", - Enabled: true, + Enabled: True, Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", @@ -335,7 +335,7 @@ func Test_release_validate(t *testing.T) { Name: "release20", Description: "", Namespace: "namespace", - Enabled: true, + Enabled: True, Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", @@ -351,12 +351,12 @@ func Test_release_validate(t *testing.T) { Name: "release21", Description: "", Namespace: "namespace", - Enabled: true, + Enabled: True, Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", PostRenderer: "../../tests/post-renderer.sh", - Test: true, + Test: True, }, s: st, }, @@ -368,12 +368,12 @@ func Test_release_validate(t *testing.T) { Name: "release22", Description: "", Namespace: "namespace", - Enabled: true, + Enabled: True, Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", PostRenderer: "doesnt-exist.sh", - Test: true, + Test: True, }, s: st, }, @@ -385,7 +385,7 @@ func Test_release_validate(t *testing.T) { Name: "release20", Description: "", Namespace: "namespace", - Enabled: true, + Enabled: True, Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", @@ -443,7 +443,7 @@ func Test_release_inheritHooks(t *testing.T) { Name: "release1 - Global hooks correctly inherited", Description: "", Namespace: "namespace", - Enabled: true, + Enabled: True, Chart: "repo/chartX", Version: "1.0", ValuesFile: "../../tests/values.yaml", diff --git a/internal/app/state_files.go b/internal/app/state_files.go index 5de157a1..495d8a26 100644 --- a/internal/app/state_files.go +++ b/internal/app/state_files.go @@ -142,7 +142,10 @@ func (s *State) build(files fileOptionArray) error { // Merge Apps that already existed in the state for appName, app := range fileState.Apps { if _, ok := s.Apps[appName]; ok { - if err := mergo.Merge(s.Apps[appName], app, mergo.WithAppendSlice, mergo.WithOverride); err != nil { + if err := mergo.Merge(s.Apps[appName], app, + mergo.WithAppendSlice, + mergo.WithOverride, + mergo.WithTransformers(MergoTransformer(NullBoolTransformer))); err != nil { return fmt.Errorf("failed to merge %s from desired state file %s: %w", appName, f.name, err) } } diff --git a/internal/app/state_files_test.go b/internal/app/state_files_test.go index b7ec6b22..80a9fd3b 100644 --- a/internal/app/state_files_test.go +++ b/internal/app/state_files_test.go @@ -378,3 +378,29 @@ func Test_build(t *testing.T) { t.Errorf("build() - unexpected number of repos, wanted 3 got %d", len(s.Apps)) } } + +func Test_DSFMergeWithOverwrite(t *testing.T) { + teardownTestCase, err := setupStateFileTestCase(t) + if err != nil { + t.Errorf("setupStateFileTestCase(), got: %v", err) + } + defer teardownTestCase(t) + s := new(State) + files := fileOptionArray{ + fileOption{name: "../../examples/minimal-example.yaml"}, + fileOption{name: "../../examples/minimal-example-overwrite.yaml"}, + } + err = s.build(files) + if err != nil { + t.Errorf("build() - unexpected error: %v", err) + } + if len(s.Apps) != 2 { + t.Errorf("build() - unexpected number of apps, wanted 5 got %d", len(s.Apps)) + } + if len(s.HelmRepos) != 2 { + t.Errorf("build() - unexpected number of repos, wanted 3 got %d", len(s.Apps)) + } + if s.Apps["jenkins"].Enabled.Value != false { + t.Errorf("build() - unexpected status of a release, wanted 'enabled'=false got %v", s.Apps["jenkins"].Enabled.Value) + } +} diff --git a/internal/app/state_test.go b/internal/app/state_test.go index 5340b6e4..fbbf93d5 100644 --- a/internal/app/state_test.go +++ b/internal/app/state_test.go @@ -401,22 +401,22 @@ func createFullReleasePointer(name, chart, version string) *Release { Name: name, Description: "", Namespace: "", - Enabled: true, + Enabled: True, Chart: chart, Version: version, ValuesFile: "", ValuesFiles: []string{}, SecretsFile: "", SecretsFiles: []string{}, - Test: false, - Protected: false, - Wait: false, + Test: False, + Protected: False, + Wait: False, Priority: 0, Set: make(map[string]string), SetString: make(map[string]string), HelmFlags: []string{}, HelmDiffFlags: []string{}, - NoHooks: false, + NoHooks: False, Timeout: 0, PostRenderer: "", } diff --git a/internal/app/utils_test.go b/internal/app/utils_test.go index 109e0821..d9b725f5 100644 --- a/internal/app/utils_test.go +++ b/internal/app/utils_test.go @@ -198,7 +198,7 @@ func Test_eyamlSecrets(t *testing.T) { Name: "release1", Namespace: "namespace", Version: "1.0.0", - Enabled: true, + Enabled: True, SecretsFile: "./../../tests/secrets/valid_eyaml_secrets.yaml", }, }, @@ -216,7 +216,7 @@ func Test_eyamlSecrets(t *testing.T) { Name: "release1", Namespace: "namespace", Version: "1.0.0", - Enabled: true, + Enabled: True, SecretsFile: "./../../tests/secrets/invalid_eyaml_secrets.yaml", }, }, @@ -234,7 +234,7 @@ func Test_eyamlSecrets(t *testing.T) { Name: "release1", Namespace: "namespace", Version: "1.0.0", - Enabled: true, + Enabled: True, SecretsFile: "./../../tests/secrets/valid_eyaml_secrets.yaml", }, }, diff --git a/schema.json b/schema.json index 1b6f22c7..bb10499d 100644 --- a/schema.json +++ b/schema.json @@ -205,6 +205,9 @@ ], "description": "Namespace type represents the fields of a Namespace" }, + "NullBool": { + "type": "boolean" + }, "Quotas": { "properties": { "pods": { @@ -253,7 +256,7 @@ "description": "Namespace where to deploy the helm release" }, "enabled": { - "type": "boolean", + "$ref": "#/$defs/NullBool", "description": "Enabled can be used to togle a helm release" }, "group": { @@ -293,15 +296,15 @@ "description": "PostRenderer is the path to an executable to be used for post rendering" }, "test": { - "type": "boolean", + "$ref": "#/$defs/NullBool", "description": "Test indicates if the chart tests should be executed" }, "protected": { - "type": "boolean", + "$ref": "#/$defs/NullBool", "description": "Protected defines if the release should be protected against changes" }, "wait": { - "type": "boolean", + "$ref": "#/$defs/NullBool", "description": "Wait defines whether helm should block execution until all k8s resources are in a ready state" }, "priority": { @@ -350,7 +353,7 @@ "description": "HelmDiffFlags is a list of cli flags to pass to helm diff" }, "noHooks": { - "type": "boolean", + "$ref": "#/$defs/NullBool", "description": "NoHooks can be used to disable the execution of helm hooks" }, "timeout": { From 8d2764ae178767fa8960533cc9a25032b9f43588 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 4 Sep 2022 17:47:30 +0000 Subject: [PATCH 0913/1127] chore(deps): bump cloud.google.com/go/storage from 1.25.0 to 1.26.0 Bumps [cloud.google.com/go/storage](https://github.com/googleapis/google-cloud-go) from 1.25.0 to 1.26.0. - [Release notes](https://github.com/googleapis/google-cloud-go/releases) - [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/CHANGES.md) - [Commits](https://github.com/googleapis/google-cloud-go/compare/pubsub/v1.25.0...spanner/v1.26.0) --- updated-dependencies: - dependency-name: cloud.google.com/go/storage dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 10 +++++----- go.sum | 19 ++++++++++--------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index e0b530f4..55555d60 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Praqma/helmsman go 1.18 require ( - cloud.google.com/go/storage v1.25.0 + cloud.google.com/go/storage v1.26.0 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.15.0 github.com/BurntSushi/toml v1.2.0 @@ -36,14 +36,14 @@ require ( github.com/mattn/go-ieproxy v0.0.1 // indirect go.opencensus.io v0.23.0 // indirect golang.org/x/crypto v0.0.0-20220312131142-6068a2e6cfdc // indirect - golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2 // indirect + golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 // indirect golang.org/x/sys v0.0.0-20220624220833-87e55d714810 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect - google.golang.org/api v0.88.0 // indirect + google.golang.org/api v0.94.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20220720214146-176da50484ac // indirect + google.golang.org/genproto v0.0.0-20220810155839-1856144b1d9c // indirect google.golang.org/grpc v1.48.0 // indirect - google.golang.org/protobuf v1.28.0 // indirect + google.golang.org/protobuf v1.28.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index ca507924..ee71ec53 100644 --- a/go.sum +++ b/go.sum @@ -57,8 +57,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= -cloud.google.com/go/storage v1.25.0 h1:D2Dn0PslpK7Z3B2AvuUHyIC762bDbGJdlmQlCBR71os= -cloud.google.com/go/storage v1.25.0/go.mod h1:Qys4JU+jeup3QnuKKAosWuxrD95C4MSqxfVDnSirDsI= +cloud.google.com/go/storage v1.26.0 h1:lYAGjknyDJirSzfwUlkv4Nsnj7od7foxQNH/fqZqles= +cloud.google.com/go/storage v1.26.0/go.mod h1:mk/N7YwIKEWyTvXAWQCIeiCTdLoRH6Pd5xmSnolQLTI= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= @@ -384,8 +384,8 @@ golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= -golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2 h1:+jnHzr9VPj32ykQVai5DNahi9+NSp7yYuCsl5eAQtL0= -golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 h1:2o1E+E8TpNLklK9nHiPiK1uzIYrIHt+cQx3ynCwq9V8= +golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -572,8 +572,8 @@ google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69 google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= -google.golang.org/api v0.88.0 h1:MPwxQRqpyskYhr2iNyfsQ8R06eeyhe7UEuR30p136ZQ= -google.golang.org/api v0.88.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.94.0 h1:KtKM9ru3nzQioV1HLlUf1cR7vMYJIpgls5VhAYQXIwA= +google.golang.org/api v0.94.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -662,8 +662,8 @@ google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljW google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220720214146-176da50484ac h1:EOa+Yrhx1C0O+4pHeXeWrCwdI0tWI6IfUU56Vebs9wQ= -google.golang.org/genproto v0.0.0-20220720214146-176da50484ac/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE= +google.golang.org/genproto v0.0.0-20220810155839-1856144b1d9c h1:IooGDWedfLC6KLczH/uduUsKQP42ZZYhKx+zd50L1Sk= +google.golang.org/genproto v0.0.0-20220810155839-1856144b1d9c/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -711,8 +711,9 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= From eec364a26cd6f456c8a345e32de99f1964e5f844 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 4 Sep 2022 17:48:04 +0000 Subject: [PATCH 0914/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.86 to 1.44.91 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.86 to 1.44.91. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.86...v1.44.91) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e0b530f4..a39cd47a 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.86 + github.com/aws/aws-sdk-go v1.44.91 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.6.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index ca507924..2594449e 100644 --- a/go.sum +++ b/go.sum @@ -86,8 +86,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.86 h1:Zls97WY9N2c2H85//B88CmSlYYNxS3Zf3k4ds5zAf5A= -github.com/aws/aws-sdk-go v1.44.86/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.91 h1:SRWmuX7PTyhBdLuvSfM7KWrWISJsrRsUPcFDSFduRxY= +github.com/aws/aws-sdk-go v1.44.91/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= From e51cb355450afb621cf9caa4d4b780eb21bec972 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Mon, 5 Sep 2022 10:14:43 +0200 Subject: [PATCH 0915/1127] Release v3.15.0 --- .version | 2 +- README.md | 2 +- internal/app/main.go | 2 +- release-notes.md | 6 ++---- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.version b/.version index 5ef3202e..64d4aab1 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.14.0 +v3.15.0 diff --git a/README.md b/README.md index e006144e..9963adfd 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.14.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.15.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) diff --git a/internal/app/main.go b/internal/app/main.go index bfe50109..0d1cf041 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.14.0" + appVersion = "v3.15.0" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index e7310917..2a7802de 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,10 +1,8 @@ -# v3.14.0 +# v3.15.0 ## New feature -- Add support for Just-Insane/helm-vault plugin +- Use nullable boolean type for the Release boolean fields (#707) fixes (#137) ## Fixes and improvements -- Exit when env variables substitution fails (#704) -- Updated dependencies From 1fd304749aa452e89a84010d1f17629f020b65e4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 11 Sep 2022 12:35:42 +0000 Subject: [PATCH 0916/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.91 to 1.44.95 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.91 to 1.44.95. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.91...v1.44.95) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0e612b9f..0c2fd343 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.91 + github.com/aws/aws-sdk-go v1.44.95 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.6.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 322a5f20..d3ca4fa9 100644 --- a/go.sum +++ b/go.sum @@ -86,8 +86,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.91 h1:SRWmuX7PTyhBdLuvSfM7KWrWISJsrRsUPcFDSFduRxY= -github.com/aws/aws-sdk-go v1.44.91/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.95 h1:QwmA+PeR6v4pF0f/dPHVPWGAshAhb9TnGZBTM5uKuI8= +github.com/aws/aws-sdk-go v1.44.95/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= From 013cccd6404190e638a5cfb7e152d5a37bb2e5b9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 18 Sep 2022 14:28:20 +0000 Subject: [PATCH 0917/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.95 to 1.44.100 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.95 to 1.44.100. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.95...v1.44.100) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0c2fd343..a41c7254 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.95 + github.com/aws/aws-sdk-go v1.44.100 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.6.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index d3ca4fa9..a47da9bf 100644 --- a/go.sum +++ b/go.sum @@ -86,8 +86,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.95 h1:QwmA+PeR6v4pF0f/dPHVPWGAshAhb9TnGZBTM5uKuI8= -github.com/aws/aws-sdk-go v1.44.95/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.100 h1:7I86bWNQB+HGDT5z/dJy61J7qgbgLoZ7O51C9eL6hrA= +github.com/aws/aws-sdk-go v1.44.100/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= From 6af60893183cad8d643b9758e24b59a5d5aec7c2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 25 Sep 2022 14:13:42 +0000 Subject: [PATCH 0918/1127] chore(deps): bump cloud.google.com/go/storage from 1.26.0 to 1.27.0 Bumps [cloud.google.com/go/storage](https://github.com/googleapis/google-cloud-go) from 1.26.0 to 1.27.0. - [Release notes](https://github.com/googleapis/google-cloud-go/releases) - [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/CHANGES.md) - [Commits](https://github.com/googleapis/google-cloud-go/compare/spanner/v1.26.0...spanner/v1.27.0) --- updated-dependencies: - dependency-name: cloud.google.com/go/storage dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 18 +++++++++--------- go.sum | 34 ++++++++++++++++++---------------- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/go.mod b/go.mod index a41c7254..439f768c 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Praqma/helmsman go 1.18 require ( - cloud.google.com/go/storage v1.26.0 + cloud.google.com/go/storage v1.27.0 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.15.0 github.com/BurntSushi/toml v1.2.0 @@ -14,12 +14,12 @@ require ( github.com/invopop/jsonschema v0.6.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.4.1 - golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e + golang.org/x/net v0.0.0-20220909164309-bea034e7d591 sigs.k8s.io/yaml v1.3.0 ) require ( - cloud.google.com/go v0.102.1 // indirect + cloud.google.com/go v0.104.0 // indirect cloud.google.com/go/compute v1.7.0 // indirect cloud.google.com/go/iam v0.3.0 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect @@ -29,21 +29,21 @@ require ( github.com/google/go-cmp v0.5.8 // indirect github.com/google/uuid v1.3.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.1.0 // indirect - github.com/googleapis/gax-go/v2 v2.4.0 // indirect + github.com/googleapis/gax-go/v2 v2.5.1 // indirect github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/mattn/go-ieproxy v0.0.1 // indirect go.opencensus.io v0.23.0 // indirect golang.org/x/crypto v0.0.0-20220312131142-6068a2e6cfdc // indirect - golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 // indirect - golang.org/x/sys v0.0.0-20220624220833-87e55d714810 // indirect + golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 // indirect + golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect - google.golang.org/api v0.94.0 // indirect + google.golang.org/api v0.97.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20220810155839-1856144b1d9c // indirect - google.golang.org/grpc v1.48.0 // indirect + google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006 // indirect + google.golang.org/grpc v1.49.0 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index a47da9bf..5abf7744 100644 --- a/go.sum +++ b/go.sum @@ -28,8 +28,8 @@ cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Ud cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= -cloud.google.com/go v0.102.1 h1:vpK6iQWv/2uUeFJth4/cBHsQAGjn1iIE6AAlxipRaA0= -cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= +cloud.google.com/go v0.104.0 h1:gSmWO7DY1vOm0MVU6DNXM11BWHHsTUmsC5cv1fuW5X8= +cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -57,8 +57,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= -cloud.google.com/go/storage v1.26.0 h1:lYAGjknyDJirSzfwUlkv4Nsnj7od7foxQNH/fqZqles= -cloud.google.com/go/storage v1.26.0/go.mod h1:mk/N7YwIKEWyTvXAWQCIeiCTdLoRH6Pd5xmSnolQLTI= +cloud.google.com/go/storage v1.27.0 h1:YOO045NZI9RKfCj1c5A/ZtuuENUc8OAW+gHdGnDgyMQ= +cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= @@ -211,8 +211,9 @@ github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pf github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= -github.com/googleapis/gax-go/v2 v2.4.0 h1:dS9eYAjhrE2RjmzYw2XAPvcXfmcQLtFEQWn0CR82awk= github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= +github.com/googleapis/gax-go/v2 v2.5.1 h1:kBRZU0PSuI7PspsSb/ChWoVResUcwNVIdpB049pKTiw= +github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -362,8 +363,9 @@ golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e h1:TsQ7F31D3bUCLeqPT0u+yjp1guoArKaNKmCr22PYgTQ= golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220909164309-bea034e7d591 h1:D0B/7al0LLrVC8aWF4+oxpv/m8bc7ViFfVS8/gXGdqI= +golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -384,8 +386,9 @@ golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= -golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 h1:2o1E+E8TpNLklK9nHiPiK1uzIYrIHt+cQx3ynCwq9V8= golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 h1:lxqLZaMad/dJHMFZH0NiNpiEZI/nhgWhe4wgzpE+MuA= +golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -457,8 +460,8 @@ golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220624220833-87e55d714810 h1:rHZQSjJdAI4Xf5Qzeh2bBc5YJIkPFVM6oDtMFYmgws0= -golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -572,8 +575,8 @@ google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69 google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= -google.golang.org/api v0.94.0 h1:KtKM9ru3nzQioV1HLlUf1cR7vMYJIpgls5VhAYQXIwA= -google.golang.org/api v0.94.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI= +google.golang.org/api v0.97.0 h1:x/vEL1XDF/2V4xzdNgFPaKHluRESo2aTsL7QzHnBtGQ= +google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -660,10 +663,9 @@ google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220810155839-1856144b1d9c h1:IooGDWedfLC6KLczH/uduUsKQP42ZZYhKx+zd50L1Sk= -google.golang.org/genproto v0.0.0-20220810155839-1856144b1d9c/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006 h1:mmbq5q8M1t7dhkLw320YK4PsOXm6jdnUAkErImaIqOg= +google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -695,8 +697,8 @@ google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11 google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.48.0 h1:rQOsyJ/8+ufEDJd/Gdsz7HG220Mh9HAhFHRGnIjda0w= -google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw= +google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= From 3151697584d4854a9f24a4ec4d5bb44c22d3ab49 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 25 Sep 2022 14:13:58 +0000 Subject: [PATCH 0919/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.100 to 1.44.105 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.100 to 1.44.105. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.100...v1.44.105) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a41c7254..97b9c452 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.100 + github.com/aws/aws-sdk-go v1.44.105 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.6.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index a47da9bf..56b06e31 100644 --- a/go.sum +++ b/go.sum @@ -86,8 +86,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.100 h1:7I86bWNQB+HGDT5z/dJy61J7qgbgLoZ7O51C9eL6hrA= -github.com/aws/aws-sdk-go v1.44.100/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.105 h1:UUwoD1PRKIj3ltrDUYTDQj5fOTK3XsnqolLpRTMmSEM= +github.com/aws/aws-sdk-go v1.44.105/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= From ff7257319c21cda8273ccd75c38d126a7c5431a9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 2 Oct 2022 14:20:29 +0000 Subject: [PATCH 0920/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.105 to 1.44.109 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.105 to 1.44.109. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.105...v1.44.109) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index efd73c01..0fde4dce 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.105 + github.com/aws/aws-sdk-go v1.44.109 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.6.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index e7f33b22..50624b4e 100644 --- a/go.sum +++ b/go.sum @@ -86,8 +86,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.105 h1:UUwoD1PRKIj3ltrDUYTDQj5fOTK3XsnqolLpRTMmSEM= -github.com/aws/aws-sdk-go v1.44.105/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.109 h1:+Na5JPeS0kiEHoBp5Umcuuf+IDqXqD0lXnM920E31YI= +github.com/aws/aws-sdk-go v1.44.109/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= From 4a87b53326436ca9192753a4a641ac3d1963e9ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 9 Oct 2022 14:52:51 +0000 Subject: [PATCH 0921/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.109 to 1.44.114 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.109 to 1.44.114. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.109...v1.44.114) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0fde4dce..25b6031d 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.109 + github.com/aws/aws-sdk-go v1.44.114 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.6.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 50624b4e..200bd48a 100644 --- a/go.sum +++ b/go.sum @@ -86,8 +86,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.109 h1:+Na5JPeS0kiEHoBp5Umcuuf+IDqXqD0lXnM920E31YI= -github.com/aws/aws-sdk-go v1.44.109/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.114 h1:plIkWc/RsHr3DXBj4MEw9sEW4CcL/e2ryokc+CKyq1I= +github.com/aws/aws-sdk-go v1.44.114/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= From 2090ca85125e61b7e798ae00584bdc5dbdddd52e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 16 Oct 2022 14:17:30 +0000 Subject: [PATCH 0922/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.114 to 1.44.116 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.114 to 1.44.116. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.114...v1.44.116) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 25b6031d..32e99e83 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.114 + github.com/aws/aws-sdk-go v1.44.116 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.6.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 200bd48a..dbb269a5 100644 --- a/go.sum +++ b/go.sum @@ -86,8 +86,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.114 h1:plIkWc/RsHr3DXBj4MEw9sEW4CcL/e2ryokc+CKyq1I= -github.com/aws/aws-sdk-go v1.44.114/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.116 h1:NpLIhcvLWXJZAEwvPj3TDHeqp7DleK6ZUVYyW01WNHY= +github.com/aws/aws-sdk-go v1.44.116/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= From d855ef5c0867f5cd80722cb477f4479d5c662e95 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Mon, 17 Oct 2022 11:37:15 +0100 Subject: [PATCH 0923/1127] fix: check helm plugin version (#721) --- internal/app/helm_helpers.go | 23 +++++++++++++++++++++++ internal/app/utils.go | 3 +++ 2 files changed, 26 insertions(+) diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index c7aabd07..a28e0978 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -100,6 +100,29 @@ func helmPluginExists(plugin string) bool { return strings.Contains(res.output, plugin) } +func getHelmPlugVersion(plugin string) string { + cmd := helmCmd([]string{"plugin", "list"}, "Validating that [ "+plugin+" ] is installed") + + res, err := cmd.Exec() + if err != nil { + return "0.0.0" + } + for _, line := range strings.Split(strings.TrimSuffix(res.output, "\n"), "\n") { + info := strings.Fields(line) + if len(info) < 2 { + continue + } + if strings.TrimSpace(info[0]) == plugin { + return strings.TrimSpace(info[1]) + } + } + return "0.0.0" +} + +func checkHelmPlugVersion(plugin, constraint string) bool { + return checkVersion(getHelmPlugVersion(plugin), constraint) +} + // updateChartDep updates dependencies for a local chart func updateChartDep(chartPath string) error { cmd := helmCmd([]string{"dependency", "update", "--skip-refresh", chartPath}, "Updating dependency for local chart [ "+chartPath+" ]") diff --git a/internal/app/utils.go b/internal/app/utils.go index a3d5353f..a7bea83c 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -475,6 +475,9 @@ func writeStringToFile(filename string, data string) error { func decryptSecret(name string) error { cmd := helmBin args := []string{"secrets", "dec", name} + if checkHelmPlugVersion("secrets", ">=4.0.0") { + args[1] = "decrypt" + } if settings.EyamlEnabled { cmd = "eyaml" From a5eeddf602749d12142f01b7119f028b3b032a10 Mon Sep 17 00:00:00 2001 From: amadeusjposchmann <98402125+amadeusjposchmann@users.noreply.github.com> Date: Mon, 17 Oct 2022 12:52:00 +0200 Subject: [PATCH 0924/1127] fix: use helm-secrets stdout on newer versions (#723) Co-authored-by: Luis Davim Co-authored-by: Luis Davim --- internal/app/utils.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/app/utils.go b/internal/app/utils.go index a7bea83c..756a1048 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -475,7 +475,9 @@ func writeStringToFile(filename string, data string) error { func decryptSecret(name string) error { cmd := helmBin args := []string{"secrets", "dec", name} - if checkHelmPlugVersion("secrets", ">=4.0.0") { + // helm-secrets >=4.0.0 decrypts to stdout, not to a .dec-file + useHelmOutput := checkHelmPlugVersion("secrets", ">=4.0.0") || settings.EyamlEnabled + if useHelmOutput { args[1] = "decrypt" } @@ -518,14 +520,12 @@ func decryptSecret(name string) error { if err != nil { return err } - if !settings.EyamlEnabled { + if !useHelmOutput { _, fileNotFound := os.Stat(name + ".dec") if fileNotFound != nil && !isOfType(name, []string{".dec"}) { return fmt.Errorf(res.String()) } - } - - if settings.EyamlEnabled { + } else { var outfile string if isOfType(name, []string{".dec"}) { outfile = name From 6da3a105819779aa7e50d2859f155c14a7524ba5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 23 Oct 2022 14:20:37 +0000 Subject: [PATCH 0925/1127] chore(deps): bump github.com/BurntSushi/toml from 1.2.0 to 1.2.1 Bumps [github.com/BurntSushi/toml](https://github.com/BurntSushi/toml) from 1.2.0 to 1.2.1. - [Release notes](https://github.com/BurntSushi/toml/releases) - [Commits](https://github.com/BurntSushi/toml/compare/v1.2.0...v1.2.1) --- updated-dependencies: - dependency-name: github.com/BurntSushi/toml dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 32e99e83..1cabf31f 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( cloud.google.com/go/storage v1.27.0 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.15.0 - github.com/BurntSushi/toml v1.2.0 + github.com/BurntSushi/toml v1.2.1 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 github.com/aws/aws-sdk-go v1.44.116 diff --git a/go.sum b/go.sum index dbb269a5..00d52b70 100644 --- a/go.sum +++ b/go.sum @@ -77,8 +77,8 @@ github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= -github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= From 561caf3a27d68837bd2dbda461c33366a82624be Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 23 Oct 2022 18:10:55 +0000 Subject: [PATCH 0926/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.116 to 1.44.121 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.116 to 1.44.121. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.116...v1.44.121) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 1cabf31f..e077cb07 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.116 + github.com/aws/aws-sdk-go v1.44.121 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.6.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 00d52b70..6413d7eb 100644 --- a/go.sum +++ b/go.sum @@ -86,8 +86,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.116 h1:NpLIhcvLWXJZAEwvPj3TDHeqp7DleK6ZUVYyW01WNHY= -github.com/aws/aws-sdk-go v1.44.116/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.121 h1:ahBRUqUp4qLyGmSM5KKn+TVpZkRmtuLxTWw+6Hq/ebs= +github.com/aws/aws-sdk-go v1.44.121/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= From 3d994c7d452de10d385c673d7dc4b29f77e1472f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 30 Oct 2022 14:55:01 +0000 Subject: [PATCH 0927/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.121 to 1.44.126 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.121 to 1.44.126. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.121...v1.44.126) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e077cb07..052dbe97 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.121 + github.com/aws/aws-sdk-go v1.44.126 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.6.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 6413d7eb..ae2215c0 100644 --- a/go.sum +++ b/go.sum @@ -86,8 +86,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.121 h1:ahBRUqUp4qLyGmSM5KKn+TVpZkRmtuLxTWw+6Hq/ebs= -github.com/aws/aws-sdk-go v1.44.121/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.126 h1:7HQJw2DNiwpxqMe2H7odGNT2rhO4SRrUe5/8dYXl0Jk= +github.com/aws/aws-sdk-go v1.44.126/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= From dd8ded93367339954693cea22167677aa334450e Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Fri, 4 Nov 2022 14:37:10 +0100 Subject: [PATCH 0928/1127] Release v3.15.1 --- .circleci/config.yml | 2 +- .version | 2 +- README.md | 2 +- internal/app/main.go | 2 +- release-notes.md | 5 ++--- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5933c71f..4f65de68 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -36,7 +36,7 @@ jobs: - run: name: build docker images and push them to dockerhub command: | - helm_versions=( "v3.7.2" "v3.8.2" "v3.9.4" ) + helm_versions=( "v3.7.2" "v3.8.2" "v3.9.4" "v3.10.1" ) TAG=$(git describe --abbrev=0 --tags) docker login -u $DOCKER_USER -p $DOCKER_PASS diff --git a/.version b/.version index 64d4aab1..dcfd6fbf 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.15.0 +v3.15.1 diff --git a/README.md b/README.md index 9963adfd..2eafff22 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.15.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.15.1&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) diff --git a/internal/app/main.go b/internal/app/main.go index 0d1cf041..4ad7d798 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.15.0" + appVersion = "v3.15.1" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index 2a7802de..db447839 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,8 +1,7 @@ -# v3.15.0 +# v3.15.1 ## New feature -- Use nullable boolean type for the Release boolean fields (#707) fixes (#137) - ## Fixes and improvements +- Fix broken helm-secrets support for plugin version >= 4.x (#721 & #723) fixes (#715) From fdc10f7f5d271b7f583678154ada3e2793f32f6b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 6 Nov 2022 14:01:00 +0000 Subject: [PATCH 0929/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.126 to 1.44.131 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.126 to 1.44.131. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.126...v1.44.131) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 20 +++++++++++++++----- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 052dbe97..1bd2cd65 100644 --- a/go.mod +++ b/go.mod @@ -9,12 +9,12 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.126 + github.com/aws/aws-sdk-go v1.44.131 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.6.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.4.1 - golang.org/x/net v0.0.0-20220909164309-bea034e7d591 + golang.org/x/net v0.1.0 sigs.k8s.io/yaml v1.3.0 ) @@ -37,8 +37,8 @@ require ( go.opencensus.io v0.23.0 // indirect golang.org/x/crypto v0.0.0-20220312131142-6068a2e6cfdc // indirect golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 // indirect - golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect - golang.org/x/text v0.3.7 // indirect + golang.org/x/sys v0.1.0 // indirect + golang.org/x/text v0.4.0 // indirect golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect google.golang.org/api v0.97.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index ae2215c0..1f754d9d 100644 --- a/go.sum +++ b/go.sum @@ -86,8 +86,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.126 h1:7HQJw2DNiwpxqMe2H7odGNT2rhO4SRrUe5/8dYXl0Jk= -github.com/aws/aws-sdk-go v1.44.126/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.131 h1:kd61x79ax0vyiC/SZ9X1hKh8E0pt1BUOOcVBJEFhxkg= +github.com/aws/aws-sdk-go v1.44.131/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -266,6 +266,7 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -320,6 +321,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -364,8 +366,10 @@ golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220909164309-bea034e7d591 h1:D0B/7al0LLrVC8aWF4+oxpv/m8bc7ViFfVS8/gXGdqI= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -401,6 +405,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -460,10 +465,13 @@ golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -472,8 +480,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -528,6 +537,7 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From b1c3efd6fbd599ecd6b980ca40ef74c2ea60469e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 13 Nov 2022 14:01:21 +0000 Subject: [PATCH 0930/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.131 to 1.44.136 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.131 to 1.44.136. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.131...v1.44.136) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 1bd2cd65..2424175e 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.131 + github.com/aws/aws-sdk-go v1.44.136 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.6.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 1f754d9d..a9ecfebf 100644 --- a/go.sum +++ b/go.sum @@ -86,8 +86,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.131 h1:kd61x79ax0vyiC/SZ9X1hKh8E0pt1BUOOcVBJEFhxkg= -github.com/aws/aws-sdk-go v1.44.131/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.136 h1:J1KJJssa8pjU8jETYUxwRS37KTcxjACfKd9GK8t+5ZU= +github.com/aws/aws-sdk-go v1.44.136/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= From c2762579191a5af304289d81eee6d031ff7416f1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 13 Nov 2022 14:01:55 +0000 Subject: [PATCH 0931/1127] chore(deps): bump cloud.google.com/go/storage from 1.27.0 to 1.28.0 Bumps [cloud.google.com/go/storage](https://github.com/googleapis/google-cloud-go) from 1.27.0 to 1.28.0. - [Release notes](https://github.com/googleapis/google-cloud-go/releases) - [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/CHANGES.md) - [Commits](https://github.com/googleapis/google-cloud-go/compare/spanner/v1.27.0...spanner/v1.28.0) --- updated-dependencies: - dependency-name: cloud.google.com/go/storage dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 23 +-- go.sum | 570 +++------------------------------------------------------ 2 files changed, 36 insertions(+), 557 deletions(-) diff --git a/go.mod b/go.mod index 1bd2cd65..9d6ecab9 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Praqma/helmsman go 1.18 require ( - cloud.google.com/go/storage v1.27.0 + cloud.google.com/go/storage v1.28.0 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.15.0 github.com/BurntSushi/toml v1.2.1 @@ -20,30 +20,31 @@ require ( require ( cloud.google.com/go v0.104.0 // indirect - cloud.google.com/go/compute v1.7.0 // indirect - cloud.google.com/go/iam v0.3.0 // indirect + cloud.google.com/go/compute v1.12.1 // indirect + cloud.google.com/go/compute/metadata v0.2.1 // indirect + cloud.google.com/go/iam v0.5.0 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect github.com/golang-jwt/jwt/v4 v4.3.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect - github.com/google/go-cmp v0.5.8 // indirect + github.com/google/go-cmp v0.5.9 // indirect github.com/google/uuid v1.3.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.1.0 // indirect - github.com/googleapis/gax-go/v2 v2.5.1 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect + github.com/googleapis/gax-go/v2 v2.6.0 // indirect github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/mattn/go-ieproxy v0.0.1 // indirect go.opencensus.io v0.23.0 // indirect golang.org/x/crypto v0.0.0-20220312131142-6068a2e6cfdc // indirect - golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 // indirect + golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect golang.org/x/sys v0.1.0 // indirect golang.org/x/text v0.4.0 // indirect - golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect - google.golang.org/api v0.97.0 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect + google.golang.org/api v0.102.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006 // indirect - google.golang.org/grpc v1.49.0 // indirect + google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e // indirect + google.golang.org/grpc v1.50.1 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 1f754d9d..d09d69f8 100644 --- a/go.sum +++ b/go.sum @@ -1,65 +1,14 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= -cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= -cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= -cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= -cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= -cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= -cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= -cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= -cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= -cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= cloud.google.com/go v0.104.0 h1:gSmWO7DY1vOm0MVU6DNXM11BWHHsTUmsC5cv1fuW5X8= cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= -cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= -cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= -cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= -cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= -cloud.google.com/go/compute v1.7.0 h1:v/k9Eueb8aAJ0vZuxKMrgm6kPhCLZU9HxFU+AFDs9Uk= -cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/iam v0.3.0 h1:exkAomrVUuzx9kWFI1wm3KI0uoDeUFPB4kKGzx6x+Gc= -cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= -cloud.google.com/go/storage v1.27.0 h1:YOO045NZI9RKfCj1c5A/ZtuuENUc8OAW+gHdGnDgyMQ= -cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +cloud.google.com/go/compute v1.12.1 h1:gKVJMEyqV5c/UnpzjjQbo3Rjvvqpr9B1DFSbJC4OXr0= +cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute/metadata v0.2.1 h1:efOwf5ymceDhK6PKMnnrTHP4pppY5L22mle96M1yP48= +cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= +cloud.google.com/go/iam v0.5.0 h1:fz9X5zyTWBmamZsqvqZqD7khbifcZF/q+Z1J8pfhIUg= +cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= +cloud.google.com/go/storage v1.28.0 h1:DLrIZ6xkeZX6K70fU/boWx5INJumt6f+nwwWSHXzzGY= +cloud.google.com/go/storage v1.28.0/go.mod h1:qlgZML35PXA3zoEnIkiPLY4/TOkUleufRlu6qmcf7sI= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk= @@ -79,31 +28,15 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= github.com/aws/aws-sdk-go v1.44.131 h1:kd61x79ax0vyiC/SZ9X1hKh8E0pt1BUOOcVBJEFhxkg= github.com/aws/aws-sdk-go v1.44.131/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -111,117 +44,49 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog= github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1VLQ= -github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= -github.com/googleapis/enterprise-certificate-proxy v0.1.0 h1:zO8WHNx/MYiAKJ3d5spxZXZE6KHmIQGQcAzwUzV7qQw= -github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= -github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= -github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= -github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= -github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= -github.com/googleapis/gax-go/v2 v2.5.1 h1:kBRZU0PSuI7PspsSb/ChWoVResUcwNVIdpB049pKTiw= -github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= -github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/googleapis/enterprise-certificate-proxy v0.2.0 h1:y8Yozv7SZtlU//QXbezB6QkpuE6jMD2/gfzk4AftXjs= +github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= +github.com/googleapis/gax-go/v2 v2.6.0 h1:SXk3ABtQYDT/OH8jAyvEOQ58mgawq5C4o/4/89qN2ZU= +github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 h1:i462o439ZjprVSFSZLZxcsoAe592sZB1rci2Z8j4wdk= github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/invopop/jsonschema v0.6.0 h1:8e+xY8ZEn8gDHUYylSlLHy22P+SLeIRIHv3nM3hCbmY= @@ -230,10 +95,6 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -249,37 +110,16 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.5 h1:s5PTfem8p8EbKQOctVV53k6jCJt3UX4IEJzwh+C324Q= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -287,429 +127,82 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220312131142-6068a2e6cfdc h1:i6Z9eOQAdM7lvsbkT3fwFNtSAAC+A59TYilFj53HW+E= golang.org/x/crypto v0.0.0-20220312131142-6068a2e6cfdc/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= -golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 h1:lxqLZaMad/dJHMFZH0NiNpiEZI/nhgWhe4wgzpE+MuA= -golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 h1:nt+Q6cXKz4MosCSpnbMtqiQ8Oz0pxTef2B4Vca2lvfk= +golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f h1:uF6paiQQebLeSXkrTqHqz0MXhXXS1KgF41eUdBNvxK0= -golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= -google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= -google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= -google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= -google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= -google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= -google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= -google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= -google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= -google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= -google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= -google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= -google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= -google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= -google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= -google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= -google.golang.org/api v0.97.0 h1:x/vEL1XDF/2V4xzdNgFPaKHluRESo2aTsL7QzHnBtGQ= -google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +google.golang.org/api v0.102.0 h1:JxJl2qQ85fRMPNvlZY/enexbxpCjLwGhZUtgfGeQ51I= +google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= -google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= -google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006 h1:mmbq5q8M1t7dhkLw320YK4PsOXm6jdnUAkErImaIqOg= -google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw= +google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e h1:S9GbmC1iCgvbLyAokVCwiO6tVIrU9Y7c5oMx1V/ki/Y= +google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= -google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw= -google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY= +google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -718,21 +211,14 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= @@ -740,14 +226,6 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= From 59790fca6959daa6c7e1925cab81ef291bdcf308 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 13 Nov 2022 18:28:14 +0000 Subject: [PATCH 0932/1127] chore(deps): bump github.com/invopop/jsonschema from 0.6.0 to 0.7.0 Bumps [github.com/invopop/jsonschema](https://github.com/invopop/jsonschema) from 0.6.0 to 0.7.0. - [Release notes](https://github.com/invopop/jsonschema/releases) - [Commits](https://github.com/invopop/jsonschema/compare/v0.6.0...v0.7.0) --- updated-dependencies: - dependency-name: github.com/invopop/jsonschema dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 2424175e..17ac5d29 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 github.com/aws/aws-sdk-go v1.44.136 github.com/imdario/mergo v0.3.13 - github.com/invopop/jsonschema v0.6.0 + github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.4.1 golang.org/x/net v0.1.0 diff --git a/go.sum b/go.sum index a9ecfebf..ccdaa8d1 100644 --- a/go.sum +++ b/go.sum @@ -224,8 +224,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= -github.com/invopop/jsonschema v0.6.0 h1:8e+xY8ZEn8gDHUYylSlLHy22P+SLeIRIHv3nM3hCbmY= -github.com/invopop/jsonschema v0.6.0/go.mod h1:O9uiLokuu0+MGFlyiaqtWxwqJm41/+8Nj0lD7A36YH0= +github.com/invopop/jsonschema v0.7.0 h1:2vgQcBz1n256N+FpX3Jq7Y17AjYt46Ig3zIWyy770So= +github.com/invopop/jsonschema v0.7.0/go.mod h1:O9uiLokuu0+MGFlyiaqtWxwqJm41/+8Nj0lD7A36YH0= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= From b1beb2e99f8fb607789a9966fae22023c22a5e06 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 13 Nov 2022 18:47:53 +0000 Subject: [PATCH 0933/1127] chore(deps): bump golang.org/x/net from 0.1.0 to 0.2.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.1.0 to 0.2.0. - [Release notes](https://github.com/golang/net/releases) - [Commits](https://github.com/golang/net/compare/v0.1.0...v0.2.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 206c8f72..bddc1dd7 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.4.1 - golang.org/x/net v0.1.0 + golang.org/x/net v0.2.0 sigs.k8s.io/yaml v1.3.0 ) @@ -38,7 +38,7 @@ require ( go.opencensus.io v0.23.0 // indirect golang.org/x/crypto v0.0.0-20220312131142-6068a2e6cfdc // indirect golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect - golang.org/x/sys v0.1.0 // indirect + golang.org/x/sys v0.2.0 // indirect golang.org/x/text v0.4.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.102.0 // indirect diff --git a/go.sum b/go.sum index ff4fc8d3..4744936f 100644 --- a/go.sum +++ b/go.sum @@ -143,8 +143,9 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 h1:nt+Q6cXKz4MosCSpnbMtqiQ8Oz0pxTef2B4Vca2lvfk= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= @@ -162,8 +163,9 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= From 1800d5b8fb267128eee7ceefbdeb3bc21728fe64 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 20 Nov 2022 14:23:10 +0000 Subject: [PATCH 0934/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.136 to 1.44.142 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.136 to 1.44.142. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.136...v1.44.142) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index bddc1dd7..e54b6d06 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.136 + github.com/aws/aws-sdk-go v1.44.142 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 4744936f..7be531f5 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.136 h1:J1KJJssa8pjU8jETYUxwRS37KTcxjACfKd9GK8t+5ZU= -github.com/aws/aws-sdk-go v1.44.136/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.142 h1:KZ1/FDwCSft1DuNllFaBtWpcG0CW2NgQjvOrE1TdlXE= +github.com/aws/aws-sdk-go v1.44.142/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= From 6a4b3ced8e1c1242410364e8c94e2a4e07da991c Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Wed, 23 Nov 2022 08:44:53 +0000 Subject: [PATCH 0935/1127] fix: allow YAML anchors (#734) --- internal/app/spec_state.go | 2 +- internal/app/state_files.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/app/spec_state.go b/internal/app/spec_state.go index e068dcbd..a9e4d430 100644 --- a/internal/app/spec_state.go +++ b/internal/app/spec_state.go @@ -25,5 +25,5 @@ func (pc *StateFiles) specFromYAML(file string) error { yamlFile := string(rawYamlFile) - return yaml.UnmarshalStrict([]byte(yamlFile), pc) + return yaml.Unmarshal([]byte(yamlFile), pc) } diff --git a/internal/app/state_files.go b/internal/app/state_files.go index 495d8a26..76a25508 100644 --- a/internal/app/state_files.go +++ b/internal/app/state_files.go @@ -94,7 +94,7 @@ func (s *State) fromYAML(file string) error { yamlFile = substituteSSM(yamlFile) } - if err = yaml.UnmarshalStrict([]byte(yamlFile), s); err != nil { + if err = yaml.Unmarshal([]byte(yamlFile), s); err != nil { return err } From 08aa7eade9d7a0c249b98a8fdb869c12f5bcbdbb Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Wed, 23 Nov 2022 08:45:07 +0000 Subject: [PATCH 0936/1127] chore: bump helm and helm-diff versions (#733) --- .circleci/config.yml | 2 +- Dockerfile | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4f65de68..7f6b1755 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -36,7 +36,7 @@ jobs: - run: name: build docker images and push them to dockerhub command: | - helm_versions=( "v3.7.2" "v3.8.2" "v3.9.4" "v3.10.1" ) + helm_versions=( "v3.7.2" "v3.8.2" "v3.9.4" "v3.10.2" ) TAG=$(git describe --abbrev=0 --tags) docker login -u $DOCKER_USER -p $DOCKER_PASS diff --git a/Dockerfile b/Dockerfile index d61de0aa..c787705a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ ARG GO_VERSION="1.18.5" ARG ALPINE_VERSION="3.15" ARG GLOBAL_KUBE_VERSION="v1.23.6" -ARG GLOBAL_HELM_VERSION="v3.9.4" -ARG GLOBAL_HELM_DIFF_VERSION="v3.5.0" +ARG GLOBAL_HELM_VERSION="v3.10.2" +ARG GLOBAL_HELM_DIFF_VERSION="v3.6.0" ARG GLOBAL_HELM_GCS_VERSION="0.3.21" ARG GLOBAL_HELM_S3_VERSION="v0.10.0" ARG GLOBAL_HELM_SECRETS_VERSION="v3.13.0" @@ -24,6 +24,7 @@ ENV HELM_GCS_VERSION=$GLOBAL_HELM_GCS_VERSION ENV HELM_S3_VERSION=$GLOBAL_HELM_S3_VERSION ENV HELM_SECRETS_VERSION=$GLOBAL_HELM_SECRETS_VERSION ENV SOPS_VERSION=$GLOBAL_SOPS_VERSION +ENV HELM_DIFF_THREE_WAY_MERGE=true RUN apk add --update --no-cache ca-certificates git openssh openssl ruby curl wget tar gzip make bash From e218e7d9569fd11899794472c8eb1cdb6baefbc2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 27 Nov 2022 14:01:19 +0000 Subject: [PATCH 0937/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.142 to 1.44.145 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.142 to 1.44.145. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.142...v1.44.145) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e54b6d06..39133df0 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.142 + github.com/aws/aws-sdk-go v1.44.145 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 7be531f5..308f5616 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.142 h1:KZ1/FDwCSft1DuNllFaBtWpcG0CW2NgQjvOrE1TdlXE= -github.com/aws/aws-sdk-go v1.44.142/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.145 h1:KMVRrIyjBsNz3xGPuHIRnhIuKlb5h3Ii5e5jbi3cgnc= +github.com/aws/aws-sdk-go v1.44.145/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= From 164ee93d5a6484f845abcecb05d8ebc5e4c49544 Mon Sep 17 00:00:00 2001 From: Tim Condit Date: Sun, 4 Dec 2022 08:35:34 -0500 Subject: [PATCH 0938/1127] Update desired_state_specification.md (#740) --- docs/desired_state_specification.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/desired_state_specification.md b/docs/desired_state_specification.md index 42754538..f1cbb160 100644 --- a/docs/desired_state_specification.md +++ b/docs/desired_state_specification.md @@ -392,7 +392,7 @@ Options: - **timeout** : helm timeout in seconds. Default 300 seconds. - **noHooks** : helm noHooks option. If true, it will disable pre/post upgrade hooks. Default is false. - **priority** : defines the priority of applying operations on this release. Only negative values allowed and the lower the value, the higher the priority. Default priority is 0. Apps with equal priorities will be applied in the order they were added in your state file (DSF). -- **set** : is used to override certain values from values.yaml with values from environment variables (or ,starting from v1.3.0-rc, directly provided in the Desired State File). This is particularly useful for passing secrets to charts. If the an environment variable with the same name as the provided value exists, the environment variable value will be used, otherwise, the provided value will be used as is. The TOML stanza for this is `[apps..set]` +- **set** : is used to override certain values from values.yaml with values from environment variables (or, starting from v1.3.0-rc, directly provided in the Desired State File). This is particularly useful for passing secrets to charts. If an environment variable with the same name as the provided value exists, the environment variable value will be used, otherwise, the provided value will be used as-is. The TOML stanza for this is `[apps..set]` - **setString** : is used to override String values from values.yaml or chart's defaults. This uses the `--set-string` flag in helm which is available only in helm >v2.9.0. This option is useful for image tags and the like. The TOML stanza for this is `[apps..setString]` - **setFile** : is used to override values from values.yaml or chart's defaults from provided file. This uses the `--set-file` flag in helm. This option is useful for embedding file contents in the values. The TOML stanza for this is `[apps..setFile]` > set, setString and setFile can't take nested elements. If you need to provide nested values, you can combine them in one line with dots e.g. `TOML: "image.tag"=some\_value` `YAML: "image.tag": some\_value` From ece0da0f9a5125b4718c536ae86bfbe1bfeee0da Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 4 Dec 2022 14:01:31 +0000 Subject: [PATCH 0939/1127] chore(deps): bump cloud.google.com/go/storage from 1.28.0 to 1.28.1 Bumps [cloud.google.com/go/storage](https://github.com/googleapis/google-cloud-go) from 1.28.0 to 1.28.1. - [Release notes](https://github.com/googleapis/google-cloud-go/releases) - [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/CHANGES.md) - [Commits](https://github.com/googleapis/google-cloud-go/compare/spanner/v1.28.0...storage/v1.28.1) --- updated-dependencies: - dependency-name: cloud.google.com/go/storage dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 14 +++++++------- go.sum | 38 ++++++++++++++++++++++---------------- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/go.mod b/go.mod index 39133df0..76401d6d 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Praqma/helmsman go 1.18 require ( - cloud.google.com/go/storage v1.28.0 + cloud.google.com/go/storage v1.28.1 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.15.0 github.com/BurntSushi/toml v1.2.1 @@ -19,10 +19,10 @@ require ( ) require ( - cloud.google.com/go v0.104.0 // indirect + cloud.google.com/go v0.105.0 // indirect cloud.google.com/go/compute v1.12.1 // indirect cloud.google.com/go/compute/metadata v0.2.1 // indirect - cloud.google.com/go/iam v0.5.0 // indirect + cloud.google.com/go/iam v0.7.0 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect github.com/golang-jwt/jwt/v4 v4.3.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -30,20 +30,20 @@ require ( github.com/google/go-cmp v0.5.9 // indirect github.com/google/uuid v1.3.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect - github.com/googleapis/gax-go/v2 v2.6.0 // indirect + github.com/googleapis/gax-go/v2 v2.7.0 // indirect github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/mattn/go-ieproxy v0.0.1 // indirect - go.opencensus.io v0.23.0 // indirect + go.opencensus.io v0.24.0 // indirect golang.org/x/crypto v0.0.0-20220312131142-6068a2e6cfdc // indirect golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect golang.org/x/sys v0.2.0 // indirect golang.org/x/text v0.4.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/api v0.102.0 // indirect + google.golang.org/api v0.103.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e // indirect + google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c // indirect google.golang.org/grpc v1.50.1 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 308f5616..9512a5ab 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,15 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.104.0 h1:gSmWO7DY1vOm0MVU6DNXM11BWHHsTUmsC5cv1fuW5X8= -cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= +cloud.google.com/go v0.105.0 h1:DNtEKRBAAzeS4KyIory52wWHuClNaXJ5x1F7xa4q+5Y= +cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= cloud.google.com/go/compute v1.12.1 h1:gKVJMEyqV5c/UnpzjjQbo3Rjvvqpr9B1DFSbJC4OXr0= cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= cloud.google.com/go/compute/metadata v0.2.1 h1:efOwf5ymceDhK6PKMnnrTHP4pppY5L22mle96M1yP48= cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= -cloud.google.com/go/iam v0.5.0 h1:fz9X5zyTWBmamZsqvqZqD7khbifcZF/q+Z1J8pfhIUg= -cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= -cloud.google.com/go/storage v1.28.0 h1:DLrIZ6xkeZX6K70fU/boWx5INJumt6f+nwwWSHXzzGY= -cloud.google.com/go/storage v1.28.0/go.mod h1:qlgZML35PXA3zoEnIkiPLY4/TOkUleufRlu6qmcf7sI= +cloud.google.com/go/iam v0.7.0 h1:k4MuwOsS7zGJJ+QfZ5vBK8SgHBAvYN/23BWsiihJ1vs= +cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg= +cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs= +cloud.google.com/go/storage v1.28.1 h1:F5QDG5ChchaAVQhINh24U99OWHURqrW8OmQcGKXcbgI= +cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk= @@ -83,8 +84,8 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.2.0 h1:y8Yozv7SZtlU//QXbezB6QkpuE6jMD2/gfzk4AftXjs= github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= -github.com/googleapis/gax-go/v2 v2.6.0 h1:SXk3ABtQYDT/OH8jAyvEOQ58mgawq5C4o/4/89qN2ZU= -github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= +github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ= +github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 h1:i462o439ZjprVSFSZLZxcsoAe592sZB1rci2Z8j4wdk= github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= @@ -111,14 +112,18 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.5 h1:s5PTfem8p8EbKQOctVV53k6jCJt3UX4IEJzwh+C324Q= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -187,8 +192,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -google.golang.org/api v0.102.0 h1:JxJl2qQ85fRMPNvlZY/enexbxpCjLwGhZUtgfGeQ51I= -google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo= +google.golang.org/api v0.103.0 h1:9yuVqlu2JCvcLg9p8S3fcFLZij8EPSyvODIY1rkMizQ= +google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= @@ -196,8 +201,8 @@ google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e h1:S9GbmC1iCgvbLyAokVCwiO6tVIrU9Y7c5oMx1V/ki/Y= -google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c h1:S34D59DS2GWOEwWNt4fYmTcFrtlOgukG2k9WsomZ7tg= +google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= @@ -227,6 +232,7 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= From 63a9dc5d387dc554820dadb00775df96778392c0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 4 Dec 2022 14:01:49 +0000 Subject: [PATCH 0940/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.145 to 1.44.152 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.145 to 1.44.152. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.145...v1.44.152) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 39133df0..f446b412 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.145 + github.com/aws/aws-sdk-go v1.44.152 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 308f5616..1531469d 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.145 h1:KMVRrIyjBsNz3xGPuHIRnhIuKlb5h3Ii5e5jbi3cgnc= -github.com/aws/aws-sdk-go v1.44.145/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.152 h1:L9aaepO8wHB67gwuGD8VgIYH/cmQDxieCt7FeLa0+fI= +github.com/aws/aws-sdk-go v1.44.152/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= From fa632ac07c896d8514fce22e3296e9f225ef0bfa Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sun, 4 Dec 2022 20:19:04 +0000 Subject: [PATCH 0941/1127] refactor: simplify the decryptSecret code (#738) * refactor: simplify the decryptSecret code * fix: support helm-secrets pre-release --- internal/app/utils.go | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/internal/app/utils.go b/internal/app/utils.go index 756a1048..b6f880f2 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -476,7 +476,7 @@ func decryptSecret(name string) error { cmd := helmBin args := []string{"secrets", "dec", name} // helm-secrets >=4.0.0 decrypts to stdout, not to a .dec-file - useHelmOutput := checkHelmPlugVersion("secrets", ">=4.0.0") || settings.EyamlEnabled + useHelmOutput := checkHelmPlugVersion("secrets", ">=4.0.0-0") || settings.EyamlEnabled if useHelmOutput { args[1] = "decrypt" } @@ -520,22 +520,21 @@ func decryptSecret(name string) error { if err != nil { return err } + + outfile := name + if !isOfType(name, []string{".dec"}) { + outfile += ".dec" + } + if !useHelmOutput { - _, fileNotFound := os.Stat(name + ".dec") - if fileNotFound != nil && !isOfType(name, []string{".dec"}) { - return fmt.Errorf(res.String()) - } - } else { - var outfile string - if isOfType(name, []string{".dec"}) { - outfile = name - } else { - outfile = name + ".dec" - } - err := writeStringToFile(outfile, res.output) - if err != nil { - log.Fatal("Can't write [ " + outfile + " ] file") + if _, err := os.Stat(outfile); err != nil { + return fmt.Errorf("decryption failed: %s", res.String()) } + return nil + } + + if err := writeStringToFile(outfile, res.output); err != nil { + return fmt.Errorf("can't write [ %s ] file: %w", outfile, err) } return nil } From 264d962bacd24d78da216691749a83576d72f506 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sun, 4 Dec 2022 20:19:13 +0000 Subject: [PATCH 0942/1127] feat: allow setting the version to latest (#735) --- internal/app/helm_helpers.go | 20 ++++++++++++++------ internal/app/release.go | 10 +++++++--- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index a28e0978..b72cc174 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -38,7 +38,11 @@ func getChartInfo(chartName, chartVersion string) (*ChartInfo, error) { log.Info("Chart [ " + chartName + " ] with version [ " + chartVersion + " ] was found locally.") } - cmd := helmCmd([]string{"show", "chart", chartName, "--version", chartVersion}, "Getting latest non-local chart's version "+chartName+"-"+chartVersion+"") + args := []string{"show", "chart", chartName} + if chartVersion != "latest" { + args = append(args, "--version", chartVersion) + } + cmd := helmCmd(args, "Getting latest non-local chart's version "+chartName+"-"+chartVersion+"") res, err := cmd.Exec() if err != nil { @@ -150,13 +154,17 @@ func helmExportChart(chart, dest string) error { // helmPullChart pulls chart and exports it to the specified destination // this should only be used with helm versions greater or equal to 3.7.0 func helmPullChart(chart, dest string) error { + version := "latest" chartParts := strings.Split(chart, ":") - if len(chartParts) < 2 { - return fmt.Errorf("missing chart version") + if len(chartParts) >= 2 { + version = chartParts[len(chartParts)-1] + chart = strings.Join(chartParts[:len(chartParts)-1], ":") + } + args := []string{"pull", chart, "-d", dest} + if version != "latest" { + args = append(args, "--version", version) } - version := chartParts[len(chartParts)-1] - chart = strings.Join(chartParts[:len(chartParts)-1], ":") - cmd := helmCmd([]string{"pull", chart, "-d", dest, "--version", version}, "Pulling chart [ "+chart+" ] to "+dest) + cmd := helmCmd(args, "Pulling chart [ "+chart+" ] to "+dest) if _, err := cmd.Exec(); err != nil { return err } diff --git a/internal/app/release.go b/internal/app/release.go index 8695329f..331ee5ae 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -427,13 +427,17 @@ func (r *Release) getHelmArgsFor(action string, optionalNamespaceOverride ...str if len(optionalNamespaceOverride) > 0 { ns = optionalNamespaceOverride[0] } + args := []string{r.Name, r.Chart, "--namespace", ns} + if r.Version != "latest" { + args = append(args, "--version", r.Version) + } switch action { case "template": - return concat([]string{"template", r.Name, r.Chart, "--version", r.Version, "--namespace", r.Namespace, "--skip-tests", "--no-hooks"}, r.getValuesFiles(), r.getSetValues(), r.getSetStringValues(), r.getSetFileValues(), r.getPostRenderer()) + return concat([]string{"template"}, args, []string{"--skip-tests", "--no-hooks"}, r.getValuesFiles(), r.getSetValues(), r.getSetStringValues(), r.getSetFileValues(), r.getPostRenderer()) case "install", "upgrade": - return concat([]string{"upgrade", r.Name, r.Chart, "--install", "--version", r.Version, "--namespace", r.Namespace}, r.getValuesFiles(), r.getSetValues(), r.getSetStringValues(), r.getSetFileValues(), r.getHelmFlags(), r.getPostRenderer()) + return concat([]string{"upgrade", "--install"}, args, r.getValuesFiles(), r.getSetValues(), r.getSetStringValues(), r.getSetFileValues(), r.getHelmFlags(), r.getPostRenderer()) case "diff": - return concat([]string{"upgrade", r.Name, r.Chart, "--version", r.Version, "--namespace", r.Namespace}, r.getValuesFiles(), r.getSetValues(), r.getSetStringValues(), r.getSetFileValues(), r.HelmDiffFlags, r.getPostRenderer()) + return concat([]string{"upgrade"}, args, r.getValuesFiles(), r.getSetValues(), r.getSetStringValues(), r.getSetFileValues(), r.HelmDiffFlags, r.getPostRenderer()) case "uninstall": return concat([]string{action, "--namespace", ns, r.Name}, flags.getRunFlags()) default: From 21b4865804e48eb66c23af113c5b4f3bdaebeda6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 11 Dec 2022 14:01:20 +0000 Subject: [PATCH 0943/1127] chore(deps): bump golang.org/x/net from 0.2.0 to 0.4.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.2.0 to 0.4.0. - [Release notes](https://github.com/golang/net/releases) - [Commits](https://github.com/golang/net/compare/v0.2.0...v0.4.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 6 +++--- go.sum | 11 ++++++----- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index b4b1a401..4bfe9aee 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.4.1 - golang.org/x/net v0.2.0 + golang.org/x/net v0.4.0 sigs.k8s.io/yaml v1.3.0 ) @@ -38,8 +38,8 @@ require ( go.opencensus.io v0.24.0 // indirect golang.org/x/crypto v0.0.0-20220312131142-6068a2e6cfdc // indirect golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect - golang.org/x/sys v0.2.0 // indirect - golang.org/x/text v0.4.0 // indirect + golang.org/x/sys v0.3.0 // indirect + golang.org/x/text v0.5.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.103.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index 05b3200d..d3ad0788 100644 --- a/go.sum +++ b/go.sum @@ -149,8 +149,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= +golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 h1:nt+Q6cXKz4MosCSpnbMtqiQ8Oz0pxTef2B4Vca2lvfk= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= @@ -169,8 +169,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -179,8 +179,9 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= From 67a3f7edda9729c041700b8687370f32d2d6b7d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 11 Dec 2022 14:01:33 +0000 Subject: [PATCH 0944/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.152 to 1.44.157 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.152 to 1.44.157. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.152...v1.44.157) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b4b1a401..d21625dd 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.152 + github.com/aws/aws-sdk-go v1.44.157 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 05b3200d..26b56b1f 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.152 h1:L9aaepO8wHB67gwuGD8VgIYH/cmQDxieCt7FeLa0+fI= -github.com/aws/aws-sdk-go v1.44.152/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.157 h1:JVBPpEWC8+yA7CbfAuTl/ZFFlHS3yoqWFqxFyTCISwg= +github.com/aws/aws-sdk-go v1.44.157/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= From 7c963f359057b50c23cc2193f27d4fe0fc810140 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 18 Dec 2022 14:01:15 +0000 Subject: [PATCH 0945/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.157 to 1.44.162 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.157 to 1.44.162. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.157...v1.44.162) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 36e7deae..2efdda10 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.157 + github.com/aws/aws-sdk-go v1.44.162 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index b038eb65..258f3cc3 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.157 h1:JVBPpEWC8+yA7CbfAuTl/ZFFlHS3yoqWFqxFyTCISwg= -github.com/aws/aws-sdk-go v1.44.157/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.162 h1:hKAd+X+/BLxVMzH+4zKxbQcQQGrk2UhFX0OTu1Mhon8= +github.com/aws/aws-sdk-go v1.44.162/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= From 4b6c197dddaf0424e5d91b5c9291aa273b22e05a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 25 Dec 2022 14:01:18 +0000 Subject: [PATCH 0946/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.162 to 1.44.167 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.162 to 1.44.167. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.162...v1.44.167) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 2efdda10..643b44ec 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.162 + github.com/aws/aws-sdk-go v1.44.167 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 258f3cc3..f812a4e3 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.162 h1:hKAd+X+/BLxVMzH+4zKxbQcQQGrk2UhFX0OTu1Mhon8= -github.com/aws/aws-sdk-go v1.44.162/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.167 h1:kQmBhGdZkQLU7AiHShSkBJ15zr8agy0QeaxXduvyp2E= +github.com/aws/aws-sdk-go v1.44.167/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= From d502f8220784ca085fdb70f0eca86ec29a5bc9c9 Mon Sep 17 00:00:00 2001 From: Chris Sawarzynski Date: Wed, 28 Dec 2022 11:59:19 +0100 Subject: [PATCH 0947/1127] allow to use metadata server for gcs authentication inside GCP (#746) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Krzysztof Sawarzyński --- docs/how_to/helm_repos/gcs.md | 2 ++ docs/how_to/misc/auth_to_storage_providers.md | 2 ++ internal/gcs/gcs.go | 13 +++++++++++++ 3 files changed, 17 insertions(+) diff --git a/docs/how_to/helm_repos/gcs.md b/docs/how_to/helm_repos/gcs.md index 25bfc054..cf5c119c 100644 --- a/docs/how_to/helm_repos/gcs.md +++ b/docs/how_to/helm_repos/gcs.md @@ -11,6 +11,8 @@ You need to provide one of the following env variables: - `GOOGLE_APPLICATION_CREDENTIALS` environment variable to contain the absolute path to your Google cloud credentials.json file. - Or, `GCLOUD_CREDENTIALS` environment variable to contain the content of the credentials.json file. +If running inside GCP helmsman can use metadata server to use Service Account permissions. + Helmsman uses the [helm GCS](https://github.com/nouney/helm-gcs) plugin to work with GCS helm repos. ```toml diff --git a/docs/how_to/misc/auth_to_storage_providers.md b/docs/how_to/misc/auth_to_storage_providers.md index 3d980771..3959d749 100644 --- a/docs/how_to/misc/auth_to_storage_providers.md +++ b/docs/how_to/misc/auth_to_storage_providers.md @@ -21,6 +21,8 @@ You need to provide ONE of the following env variables: - `GOOGLE_APPLICATION_CREDENTIALS` the absolute path to your Google cloud credentials.json file. - Or, `GCLOUD_CREDENTIALS` the content of the credentials.json file. +If running inside GCP helmsman can use metadata server to use Service Account permissions. + ## Microsoft Azure You need to provide ALL of the following env variables: diff --git a/internal/gcs/gcs.go b/internal/gcs/gcs.go index 71846bb8..1c230033 100644 --- a/internal/gcs/gcs.go +++ b/internal/gcs/gcs.go @@ -4,6 +4,7 @@ import ( "fmt" "io" "io/ioutil" + "net/http" "os" // Imports the Google Cloud Storage client package. @@ -15,8 +16,15 @@ import ( // colorizer var style aurora.Aurora +func IsRunningInGCP() bool { + resp, err := http.Get("http://metadata.google.internal") + resp.Body.Close() + return err == nil +} + // Auth checks for GCLOUD_CREDENTIALS in the environment // returns true if they exist and creates a json credentials file and sets the GOOGLE_APPLICATION_CREDENTIALS env var +// returns true if GCP metadata server is present // returns false if credentials are not found func Auth() (string, error) { if os.Getenv("GOOGLE_APPLICATION_CREDENTIALS") != "" { @@ -35,6 +43,11 @@ func Auth() (string, error) { os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", credFile) return "ok", nil } + + if IsRunningInGCP() { + return "Metadata server present, running in GCP", nil + } + return "can't authenticate", fmt.Errorf("can't authenticate") } From 800597b9bb457b6548935bb6998b2036c2fe46db Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Jan 2023 14:01:54 +0000 Subject: [PATCH 0948/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.167 to 1.44.171 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.167 to 1.44.171. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.167...v1.44.171) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 643b44ec..5d02f291 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.167 + github.com/aws/aws-sdk-go v1.44.171 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index f812a4e3..b855c905 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.167 h1:kQmBhGdZkQLU7AiHShSkBJ15zr8agy0QeaxXduvyp2E= -github.com/aws/aws-sdk-go v1.44.167/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.171 h1:maREiPAmibvuONMOEZIkCH2OTosLRnDelceTtH3SYfo= +github.com/aws/aws-sdk-go v1.44.171/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= From 47425abcc7a9896cb668bba8c09354fa2adf2145 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 8 Jan 2023 14:01:36 +0000 Subject: [PATCH 0949/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.171 to 1.44.175 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.171 to 1.44.175. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.171...v1.44.175) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 5d02f291..82c3f743 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.171 + github.com/aws/aws-sdk-go v1.44.175 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index b855c905..44c4d0b0 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.171 h1:maREiPAmibvuONMOEZIkCH2OTosLRnDelceTtH3SYfo= -github.com/aws/aws-sdk-go v1.44.171/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.175 h1:c0NzHHnPXV5kJoTUFQxFN5cUPpX1SxO635XnwL5/oIY= +github.com/aws/aws-sdk-go v1.44.175/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= From e28e775bf82a766bcf456288026365818bd8f781 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 8 Jan 2023 14:01:50 +0000 Subject: [PATCH 0950/1127] chore(deps): bump golang.org/x/net from 0.4.0 to 0.5.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.4.0 to 0.5.0. - [Release notes](https://github.com/golang/net/releases) - [Commits](https://github.com/golang/net/compare/v0.4.0...v0.5.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 5d02f291..679c9275 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.4.1 - golang.org/x/net v0.4.0 + golang.org/x/net v0.5.0 sigs.k8s.io/yaml v1.3.0 ) @@ -38,8 +38,8 @@ require ( go.opencensus.io v0.24.0 // indirect golang.org/x/crypto v0.0.0-20220312131142-6068a2e6cfdc // indirect golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect - golang.org/x/sys v0.3.0 // indirect - golang.org/x/text v0.5.0 // indirect + golang.org/x/sys v0.4.0 // indirect + golang.org/x/text v0.6.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.103.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index b855c905..7a366f5a 100644 --- a/go.sum +++ b/go.sum @@ -149,8 +149,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= -golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 h1:nt+Q6cXKz4MosCSpnbMtqiQ8Oz0pxTef2B4Vca2lvfk= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= @@ -169,8 +169,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -180,8 +180,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= From 7a1548a470990112cc10557babbbf35d55573ecc Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sun, 8 Jan 2023 19:32:43 +0000 Subject: [PATCH 0951/1127] release: v3.16.0 --- .version | 2 +- README.md | 2 +- internal/app/main.go | 2 +- release-notes.md | 10 ++++++++-- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/.version b/.version index dcfd6fbf..3490df66 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.15.1 +v3.16.0 diff --git a/README.md b/README.md index 2eafff22..f1b74ac0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.15.1&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.16.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) diff --git a/internal/app/main.go b/internal/app/main.go index 4ad7d798..8ad6e89b 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.15.1" + appVersion = "v3.16.0" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index db447839..b65e8335 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,7 +1,13 @@ -# v3.15.1 +# v3.16.0 ## New feature +- Allow setting the chart version to latest (#735) + ## Fixes and improvements -- Fix broken helm-secrets support for plugin version >= 4.x (#721 & #723) fixes (#715) +- Dependency version upgrades (dependabot) +- Fix for YAML anchors (#734) +- Simplified decrypt secret code (#738) +- Suport using the metadata seerver for GCS auth in GCP (#746) +- Documentation fixes (#740) From 88792cf35944987f2af98d32eb35173a28fce2d5 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sun, 15 Jan 2023 16:59:52 +0000 Subject: [PATCH 0952/1127] fix: get chart version --- internal/app/helm_helpers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index b72cc174..27e04332 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -39,7 +39,7 @@ func getChartInfo(chartName, chartVersion string) (*ChartInfo, error) { } args := []string{"show", "chart", chartName} - if chartVersion != "latest" { + if chartVersion != "latest" && chartVersion != "" { args = append(args, "--version", chartVersion) } cmd := helmCmd(args, "Getting latest non-local chart's version "+chartName+"-"+chartVersion+"") From 98867d5726e125936ba3808da5e58c2ca382c17e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 15 Jan 2023 17:02:54 +0000 Subject: [PATCH 0953/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.175 to 1.44.180 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.175 to 1.44.180. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.175...v1.44.180) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 069e11a8..cdefb222 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.175 + github.com/aws/aws-sdk-go v1.44.180 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 61414541..51238e6e 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.175 h1:c0NzHHnPXV5kJoTUFQxFN5cUPpX1SxO635XnwL5/oIY= -github.com/aws/aws-sdk-go v1.44.175/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.180 h1:VLZuAHI9fa/3WME5JjpVjcPCNfpGHVMiHx8sLHWhMgI= +github.com/aws/aws-sdk-go v1.44.180/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= From 87fdf1cfc80d1f4c236af7d71cda840c60043962 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 15 Jan 2023 17:03:03 +0000 Subject: [PATCH 0954/1127] chore(deps): bump github.com/subosito/gotenv from 1.4.1 to 1.4.2 Bumps [github.com/subosito/gotenv](https://github.com/subosito/gotenv) from 1.4.1 to 1.4.2. - [Release notes](https://github.com/subosito/gotenv/releases) - [Changelog](https://github.com/subosito/gotenv/blob/master/CHANGELOG.md) - [Commits](https://github.com/subosito/gotenv/compare/v1.4.1...v1.4.2) --- updated-dependencies: - dependency-name: github.com/subosito/gotenv dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 069e11a8..971109ec 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible - github.com/subosito/gotenv v1.4.1 + github.com/subosito/gotenv v1.4.2 golang.org/x/net v0.5.0 sigs.k8s.io/yaml v1.3.0 ) diff --git a/go.sum b/go.sum index 61414541..55dee262 100644 --- a/go.sum +++ b/go.sum @@ -119,8 +119,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= -github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= +github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= From ec22c55687ddc6a2b2a87c6182ba70067fb70172 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sun, 15 Jan 2023 17:11:53 +0000 Subject: [PATCH 0955/1127] release: v3.16.1 --- .version | 2 +- README.md | 2 +- internal/app/main.go | 2 +- release-notes.md | 12 ++---------- 4 files changed, 5 insertions(+), 13 deletions(-) diff --git a/.version b/.version index 3490df66..7f207151 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.16.0 +v3.16.1 diff --git a/README.md b/README.md index f1b74ac0..9095b0a4 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.16.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.16.1&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) diff --git a/internal/app/main.go b/internal/app/main.go index 8ad6e89b..b4654b74 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.16.0" + appVersion = "v3.16.1" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index b65e8335..5aac0166 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,13 +1,5 @@ -# v3.16.0 - -## New feature - -- Allow setting the chart version to latest (#735) +# v3.16.1 ## Fixes and improvements -- Dependency version upgrades (dependabot) -- Fix for YAML anchors (#734) -- Simplified decrypt secret code (#738) -- Suport using the metadata seerver for GCS auth in GCP (#746) -- Documentation fixes (#740) +- Fix issue with checking for local chart updates From d30465f8baccaddfcc7e6e49babe50b614b744b5 Mon Sep 17 00:00:00 2001 From: Aswin T <42780141+AswinT22@users.noreply.github.com> Date: Tue, 17 Jan 2023 22:22:54 +0000 Subject: [PATCH 0956/1127] Update cmd_reference.md (#753) * Update cmd_reference.md * Update cli.go * Update cmd_reference.md --- docs/cmd_reference.md | 3 +++ internal/app/cli.go | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/cmd_reference.md b/docs/cmd_reference.md index ea8aaff2..af4337b8 100644 --- a/docs/cmd_reference.md +++ b/docs/cmd_reference.md @@ -80,6 +80,9 @@ This lists available CMD options in Helmsman: `--no-ssm-subst` turn off SSM parameter substitution globally. + + `--replace-on-rename` + uninstall the existing release when a chart with a different name is used. `--spec string` specification file name, contains locations of desired state files to be merged diff --git a/internal/app/cli.go b/internal/app/cli.go index 6e465a2d..24a2f009 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -151,7 +151,7 @@ func (c *cli) setup() { flag.BoolVar(&c.substSSMValues, "subst-ssm-values", false, "turn on SSM parameter substitution in values files.") flag.BoolVar(&c.updateDeps, "update-deps", false, "run 'helm dep up' for local charts") flag.BoolVar(&c.forceUpgrades, "force-upgrades", false, "use --force when upgrading helm releases. May cause resources to be recreated.") - flag.BoolVar(&c.renameReplace, "replace-on-rename", false, "Uninstall the existing release when a chart with a different name is used.") + flag.BoolVar(&c.renameReplace, "replace-on-rename", false, "uninstall the existing release when a chart with a different name is used.") flag.BoolVar(&c.noCleanup, "no-cleanup", false, "keeps any credentials files that has been downloaded on the host where helmsman runs.") flag.BoolVar(&c.migrateContext, "migrate-context", false, "updates the context name for all apps defined in the DSF and applies Helmsman labels. Using this flag is required if you want to change context name after it has been set.") flag.BoolVar(&c.alwaysUpgrade, "always-upgrade", false, "upgrade release even if no changes are found") From 10df8c331b0b3e664b1854ae2a778a9f06cf23b2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 22 Jan 2023 14:01:37 +0000 Subject: [PATCH 0957/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.180 to 1.44.184 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.180 to 1.44.184. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.180...v1.44.184) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 91ac0e50..0fd50849 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.180 + github.com/aws/aws-sdk-go v1.44.184 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 28c1ecbd..9119cb55 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.180 h1:VLZuAHI9fa/3WME5JjpVjcPCNfpGHVMiHx8sLHWhMgI= -github.com/aws/aws-sdk-go v1.44.180/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.184 h1:/MggyE66rOImXJKl1HqhLQITvWvqIV7w1Q4MaG6FHUo= +github.com/aws/aws-sdk-go v1.44.184/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= From 38e37c5596da9670b218892ecd1fa2178162f265 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 22 Jan 2023 14:02:06 +0000 Subject: [PATCH 0958/1127] chore(deps): bump cloud.google.com/go/storage from 1.28.1 to 1.29.0 Bumps [cloud.google.com/go/storage](https://github.com/googleapis/google-cloud-go) from 1.28.1 to 1.29.0. - [Release notes](https://github.com/googleapis/google-cloud-go/releases) - [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/CHANGES.md) - [Commits](https://github.com/googleapis/google-cloud-go/compare/storage/v1.28.1...spanner/v1.29.0) --- updated-dependencies: - dependency-name: cloud.google.com/go/storage dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 18 +++++++++--------- go.sum | 36 ++++++++++++++++++------------------ 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/go.mod b/go.mod index 91ac0e50..1160cb70 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Praqma/helmsman go 1.18 require ( - cloud.google.com/go/storage v1.28.1 + cloud.google.com/go/storage v1.29.0 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.15.0 github.com/BurntSushi/toml v1.2.1 @@ -19,17 +19,17 @@ require ( ) require ( - cloud.google.com/go v0.105.0 // indirect - cloud.google.com/go/compute v1.12.1 // indirect - cloud.google.com/go/compute/metadata v0.2.1 // indirect - cloud.google.com/go/iam v0.7.0 // indirect + cloud.google.com/go v0.107.0 // indirect + cloud.google.com/go/compute v1.14.0 // indirect + cloud.google.com/go/compute/metadata v0.2.3 // indirect + cloud.google.com/go/iam v0.8.0 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect github.com/golang-jwt/jwt/v4 v4.3.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/uuid v1.3.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.2.1 // indirect github.com/googleapis/gax-go/v2 v2.7.0 // indirect github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect @@ -41,10 +41,10 @@ require ( golang.org/x/sys v0.4.0 // indirect golang.org/x/text v0.6.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/api v0.103.0 // indirect + google.golang.org/api v0.106.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c // indirect - google.golang.org/grpc v1.50.1 // indirect + google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect + google.golang.org/grpc v1.51.0 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 28c1ecbd..e4f828e1 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,15 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.105.0 h1:DNtEKRBAAzeS4KyIory52wWHuClNaXJ5x1F7xa4q+5Y= -cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= -cloud.google.com/go/compute v1.12.1 h1:gKVJMEyqV5c/UnpzjjQbo3Rjvvqpr9B1DFSbJC4OXr0= -cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= -cloud.google.com/go/compute/metadata v0.2.1 h1:efOwf5ymceDhK6PKMnnrTHP4pppY5L22mle96M1yP48= -cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= -cloud.google.com/go/iam v0.7.0 h1:k4MuwOsS7zGJJ+QfZ5vBK8SgHBAvYN/23BWsiihJ1vs= -cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg= +cloud.google.com/go v0.107.0 h1:qkj22L7bgkl6vIeZDlOY2po43Mx/TIa2Wsa7VR+PEww= +cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= +cloud.google.com/go/compute v1.14.0 h1:hfm2+FfxVmnRlh6LpB7cg1ZNU+5edAHmW679JePztk0= +cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/iam v0.8.0 h1:E2osAkZzxI/+8pZcxVLcDtAQx/u+hZXVryUaYQ5O0Kk= +cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs= -cloud.google.com/go/storage v1.28.1 h1:F5QDG5ChchaAVQhINh24U99OWHURqrW8OmQcGKXcbgI= -cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= +cloud.google.com/go/storage v1.29.0 h1:6weCgzRvMg7lzuUurI4697AqIRPU1SvzHhynwpW31jI= +cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk= @@ -82,8 +82,8 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.2.0 h1:y8Yozv7SZtlU//QXbezB6QkpuE6jMD2/gfzk4AftXjs= -github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= +github.com/googleapis/enterprise-certificate-proxy v0.2.1 h1:RY7tHKZcRlk788d5WSo/e83gOyyy742E8GSs771ySpg= +github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ= github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 h1:i462o439ZjprVSFSZLZxcsoAe592sZB1rci2Z8j4wdk= @@ -193,8 +193,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -google.golang.org/api v0.103.0 h1:9yuVqlu2JCvcLg9p8S3fcFLZij8EPSyvODIY1rkMizQ= -google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= +google.golang.org/api v0.106.0 h1:ffmW0faWCwKkpbbtvlY/K/8fUl+JKvNS5CVzRoyfCv8= +google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= @@ -202,15 +202,15 @@ google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c h1:S34D59DS2GWOEwWNt4fYmTcFrtlOgukG2k9WsomZ7tg= -google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w= +google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY= -google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U= +google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= From f46e02351bb7b61128bef0c027836193d55c59db Mon Sep 17 00:00:00 2001 From: Ben Bettridge Date: Tue, 24 Jan 2023 21:11:29 +1300 Subject: [PATCH 0959/1127] Fix helm-vault secret decryption (#759) Co-authored-by: Ben Bettridge --- internal/app/utils.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/internal/app/utils.go b/internal/app/utils.go index b6f880f2..b1cd6ccf 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -526,6 +526,19 @@ func decryptSecret(name string) error { outfile += ".dec" } + if settings.VaultEnabled { + // helm-vault plugin doesn't write to stdout + useHelmOutput = false + if settings.VaultEnvironment != "" { + // helm-vault decryption with an environment interpolate the environment name into the output filename + vaultOutFile := name + "." + settings.VaultEnvironment + ".dec" + if _, err := os.Stat(vaultOutFile); err != nil { + return fmt.Errorf("decrypted vault file not found: %s", vaultOutFile) + } + os.Rename(vaultOutFile, outfile) + } + } + if !useHelmOutput { if _, err := os.Stat(outfile); err != nil { return fmt.Errorf("decryption failed: %s", res.String()) From fd64beb99056e4b530a347dba39857a5698f78ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 29 Jan 2023 14:02:22 +0000 Subject: [PATCH 0960/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.184 to 1.44.189 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.184 to 1.44.189. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.184...v1.44.189) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index bee53a20..eebee853 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.184 + github.com/aws/aws-sdk-go v1.44.189 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 6ce384fe..b3eaad34 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.184 h1:/MggyE66rOImXJKl1HqhLQITvWvqIV7w1Q4MaG6FHUo= -github.com/aws/aws-sdk-go v1.44.184/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.189 h1:9PBrjndH1uL5AN8818qI3duhQ4hgkMuLvqkJlg9MRyk= +github.com/aws/aws-sdk-go v1.44.189/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= From 1a556d7030b831de9bd7f92fa7c71206ca6cf6ed Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sat, 4 Feb 2023 12:53:28 +0000 Subject: [PATCH 0961/1127] release: v3.16.2 --- .circleci/config.yml | 2 +- .version | 2 +- Dockerfile | 6 +++--- README.md | 2 +- internal/app/main.go | 2 +- release-notes.md | 6 ++++-- 6 files changed, 11 insertions(+), 9 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7f6b1755..6e96eef2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -36,7 +36,7 @@ jobs: - run: name: build docker images and push them to dockerhub command: | - helm_versions=( "v3.7.2" "v3.8.2" "v3.9.4" "v3.10.2" ) + helm_versions=( "v3.8.2" "v3.9.4" "v3.10.3" "v3.11.0" ) TAG=$(git describe --abbrev=0 --tags) docker login -u $DOCKER_USER -p $DOCKER_PASS diff --git a/.version b/.version index 7f207151..53c1b1c9 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.16.1 +v3.16.2 diff --git a/Dockerfile b/Dockerfile index c787705a..72d6f243 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ ARG GO_VERSION="1.18.5" -ARG ALPINE_VERSION="3.15" -ARG GLOBAL_KUBE_VERSION="v1.23.6" -ARG GLOBAL_HELM_VERSION="v3.10.2" +ARG ALPINE_VERSION="3.17" +ARG GLOBAL_KUBE_VERSION="v1.24.10" +ARG GLOBAL_HELM_VERSION="v3.11.0" ARG GLOBAL_HELM_DIFF_VERSION="v3.6.0" ARG GLOBAL_HELM_GCS_VERSION="0.3.21" ARG GLOBAL_HELM_S3_VERSION="v0.10.0" diff --git a/README.md b/README.md index 9095b0a4..2dc24a0b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.16.1&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.16.2&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) diff --git a/internal/app/main.go b/internal/app/main.go index b4654b74..c6914ad4 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.16.1" + appVersion = "v3.16.2" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index 5aac0166..ad5a6d4d 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,5 +1,7 @@ -# v3.16.1 +# v3.16.2 ## Fixes and improvements -- Fix issue with checking for local chart updates +- Dependency version upgrades +- Fixed helm-vault decryption (#759) +- Documentation fixes (#753) From a8a9a2a5f5c57aaa07f1298e8a092e9068f5826c Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sat, 4 Feb 2023 12:59:58 +0000 Subject: [PATCH 0962/1127] fix: alpine version --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 72d6f243..0ce278f5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ ARG GO_VERSION="1.18.5" -ARG ALPINE_VERSION="3.17" +ARG ALPINE_VERSION="3.16" ARG GLOBAL_KUBE_VERSION="v1.24.10" ARG GLOBAL_HELM_VERSION="v3.11.0" ARG GLOBAL_HELM_DIFF_VERSION="v3.6.0" From 5800df117e3f7dc2db3ec3c84f2fd2089c8aba92 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sat, 4 Feb 2023 13:02:27 +0000 Subject: [PATCH 0963/1127] release: v3.16.3 --- .version | 2 +- README.md | 2 +- internal/app/main.go | 2 +- release-notes.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.version b/.version index 53c1b1c9..fb201f70 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.16.2 +v3.16.3 diff --git a/README.md b/README.md index 2dc24a0b..3093585e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.16.2&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.16.3&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) diff --git a/internal/app/main.go b/internal/app/main.go index c6914ad4..90002ed2 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.16.2" + appVersion = "v3.16.3" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index ad5a6d4d..603e8f61 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,4 +1,4 @@ -# v3.16.2 +# v3.16.3 ## Fixes and improvements From 76a57d64c243299931db737d58b0cb770acdf674 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sat, 4 Feb 2023 13:54:14 +0000 Subject: [PATCH 0964/1127] fix: install openssh-client instead of openssh --- .circleci/config.yml | 2 +- Dockerfile | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6e96eef2..b962b90f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -21,7 +21,7 @@ jobs: steps: - run: name: install git - command: apk update && apk add --no-cache git openssh + command: apk update && apk add --no-cache git openssh-client - checkout - run: name: release diff --git a/Dockerfile b/Dockerfile index 0ce278f5..09894ac5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,7 +26,7 @@ ENV HELM_SECRETS_VERSION=$GLOBAL_HELM_SECRETS_VERSION ENV SOPS_VERSION=$GLOBAL_SOPS_VERSION ENV HELM_DIFF_THREE_WAY_MERGE=true -RUN apk add --update --no-cache ca-certificates git openssh openssl ruby curl wget tar gzip make bash +RUN apk add --update --no-cache ca-certificates git openssh-client openssl ruby curl wget tar gzip make bash ADD https://github.com/mozilla/sops/releases/download/${SOPS_VERSION}/sops-${SOPS_VERSION}.linux /usr/local/bin/sops RUN chmod +x /usr/local/bin/sops @@ -46,7 +46,7 @@ RUN helm plugin install https://github.com/jkroepke/helm-secrets --version ${HEL ### Go Builder & Tester ### FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} as builder -RUN apk add --update --no-cache ca-certificates git openssh ruby bash make curl +RUN apk add --update --no-cache ca-certificates git openssh-client ruby bash make curl RUN gem install hiera-eyaml --no-doc RUN update-ca-certificates @@ -69,7 +69,7 @@ RUN make test \ ### Final Image ### FROM alpine:${ALPINE_VERSION} as base -RUN apk add --update --no-cache ca-certificates git openssh ruby curl bash gnupg +RUN apk add --update --no-cache ca-certificates git openssh-client ruby curl bash gnupg RUN gem install hiera-eyaml --no-doc RUN update-ca-certificates From 2acc1df240a4815eb56d97dab951308d8bde8b12 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sat, 4 Feb 2023 14:22:16 +0000 Subject: [PATCH 0965/1127] release: v3.16.4 --- .version | 2 +- README.md | 2 +- internal/app/main.go | 2 +- release-notes.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.version b/.version index fb201f70..5aeb0abe 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.16.3 +v3.16.4 diff --git a/README.md b/README.md index 3093585e..e1f86a99 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.16.3&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.16.4&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) diff --git a/internal/app/main.go b/internal/app/main.go index 90002ed2..423a1a27 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.16.3" + appVersion = "v3.16.4" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index 603e8f61..22cc5638 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,4 +1,4 @@ -# v3.16.3 +# v3.16.4 ## Fixes and improvements From bcc4ebdcc2e5528df99391ca5dced7372d412c02 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 5 Feb 2023 14:02:27 +0000 Subject: [PATCH 0966/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.189 to 1.44.194 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.189 to 1.44.194. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.189...v1.44.194) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index eebee853..7051d24b 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.189 + github.com/aws/aws-sdk-go v1.44.194 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index b3eaad34..ee20b804 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.189 h1:9PBrjndH1uL5AN8818qI3duhQ4hgkMuLvqkJlg9MRyk= -github.com/aws/aws-sdk-go v1.44.189/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.194 h1:1ZDK+QDcc5oRbZGgRZSz561eR8XVizXCeGpoZKo33NU= +github.com/aws/aws-sdk-go v1.44.194/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= From 034a472b4777763b83df3add25e8b651fc299246 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 12 Feb 2023 14:58:37 +0000 Subject: [PATCH 0967/1127] chore(deps): bump golang.org/x/net from 0.5.0 to 0.6.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.5.0 to 0.6.0. - [Release notes](https://github.com/golang/net/releases) - [Commits](https://github.com/golang/net/compare/v0.5.0...v0.6.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 7051d24b..eaf3095e 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.4.2 - golang.org/x/net v0.5.0 + golang.org/x/net v0.6.0 sigs.k8s.io/yaml v1.3.0 ) @@ -38,8 +38,8 @@ require ( go.opencensus.io v0.24.0 // indirect golang.org/x/crypto v0.0.0-20220312131142-6068a2e6cfdc // indirect golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect - golang.org/x/sys v0.4.0 // indirect - golang.org/x/text v0.6.0 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/text v0.7.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.106.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index ee20b804..a0102703 100644 --- a/go.sum +++ b/go.sum @@ -149,8 +149,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= -golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 h1:nt+Q6cXKz4MosCSpnbMtqiQ8Oz0pxTef2B4Vca2lvfk= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= @@ -169,8 +169,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -180,8 +180,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= -golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= From 48c664696b4fc6a6ca2cc94c6212a4e61df32afc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 12 Feb 2023 14:59:00 +0000 Subject: [PATCH 0968/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.194 to 1.44.199 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.194 to 1.44.199. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.194...v1.44.199) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 7051d24b..602c14b0 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.194 + github.com/aws/aws-sdk-go v1.44.199 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index ee20b804..fdfbe502 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.194 h1:1ZDK+QDcc5oRbZGgRZSz561eR8XVizXCeGpoZKo33NU= -github.com/aws/aws-sdk-go v1.44.194/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.199 h1:hYuQmS4zLMJR9v2iOp2UOD6Vi/0V+nwyR/Uhrkrtlbc= +github.com/aws/aws-sdk-go v1.44.199/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= From b3f54d8a0cdb6366ea9d2072beb9726d2dd2b7a3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 18 Feb 2023 04:27:32 +0000 Subject: [PATCH 0969/1127] chore(deps): bump golang.org/x/net from 0.6.0 to 0.7.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.6.0 to 0.7.0. - [Release notes](https://github.com/golang/net/releases) - [Commits](https://github.com/golang/net/compare/v0.6.0...v0.7.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index d5ee97fe..3e8d5f63 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.4.2 - golang.org/x/net v0.6.0 + golang.org/x/net v0.7.0 sigs.k8s.io/yaml v1.3.0 ) diff --git a/go.sum b/go.sum index 1bec09a6..8b68bee0 100644 --- a/go.sum +++ b/go.sum @@ -149,8 +149,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 h1:nt+Q6cXKz4MosCSpnbMtqiQ8Oz0pxTef2B4Vca2lvfk= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= From a091af3240a7e661de8e4cec6bead7f32f0dcf72 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 19 Feb 2023 14:58:20 +0000 Subject: [PATCH 0970/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.199 to 1.44.204 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.199 to 1.44.204. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.199...v1.44.204) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 3e8d5f63..1787493d 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.199 + github.com/aws/aws-sdk-go v1.44.204 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 8b68bee0..c336f9d6 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.199 h1:hYuQmS4zLMJR9v2iOp2UOD6Vi/0V+nwyR/Uhrkrtlbc= -github.com/aws/aws-sdk-go v1.44.199/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.204 h1:7/tPUXfNOHB390A63t6fJIwmlwVQAkAwcbzKsU2/6OQ= +github.com/aws/aws-sdk-go v1.44.204/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= From 148af6e73271cd467ac004d18c34a4b01ec6e14b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 26 Feb 2023 14:58:55 +0000 Subject: [PATCH 0971/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.204 to 1.44.209 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.204 to 1.44.209. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.204...v1.44.209) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 1787493d..a8449260 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.204 + github.com/aws/aws-sdk-go v1.44.209 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index c336f9d6..fb72723c 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.204 h1:7/tPUXfNOHB390A63t6fJIwmlwVQAkAwcbzKsU2/6OQ= -github.com/aws/aws-sdk-go v1.44.204/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.209 h1:wZuiaA4eaqYZmoZXqGgNHqVD7y7kUGFvACDGBgowTps= +github.com/aws/aws-sdk-go v1.44.209/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= From dc7094fa0a3c2e5ad1c91095f70dc411baec17e3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 5 Mar 2023 14:58:41 +0000 Subject: [PATCH 0972/1127] chore(deps): bump golang.org/x/net from 0.7.0 to 0.8.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.7.0 to 0.8.0. - [Release notes](https://github.com/golang/net/releases) - [Commits](https://github.com/golang/net/compare/v0.7.0...v0.8.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index a8449260..febcd8a7 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.4.2 - golang.org/x/net v0.7.0 + golang.org/x/net v0.8.0 sigs.k8s.io/yaml v1.3.0 ) @@ -38,8 +38,8 @@ require ( go.opencensus.io v0.24.0 // indirect golang.org/x/crypto v0.0.0-20220312131142-6068a2e6cfdc // indirect golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/text v0.7.0 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/text v0.8.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.106.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index fb72723c..c72a3564 100644 --- a/go.sum +++ b/go.sum @@ -149,8 +149,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 h1:nt+Q6cXKz4MosCSpnbMtqiQ8Oz0pxTef2B4Vca2lvfk= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= @@ -169,8 +169,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -180,8 +180,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= From b7530f053a7ea65c171ab9942e1a137db81f7e3c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 5 Mar 2023 14:59:22 +0000 Subject: [PATCH 0973/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.209 to 1.44.214 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.209 to 1.44.214. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.209...v1.44.214) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a8449260..c7fb44f3 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.209 + github.com/aws/aws-sdk-go v1.44.214 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index fb72723c..25ed11c4 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.209 h1:wZuiaA4eaqYZmoZXqGgNHqVD7y7kUGFvACDGBgowTps= -github.com/aws/aws-sdk-go v1.44.209/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.214 h1:YzDuC+9UtrAOUkItlK7l3BvKI9o6qAog9X8i289HORc= +github.com/aws/aws-sdk-go v1.44.214/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= From 7d49fd4c665e6e9ea5464476c1ec4168afd085c4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 11 Mar 2023 23:14:55 +0000 Subject: [PATCH 0974/1127] chore(deps): bump golang.org/x/crypto Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.0.0-20220312131142-6068a2e6cfdc to 0.1.0. - [Release notes](https://github.com/golang/crypto/releases) - [Commits](https://github.com/golang/crypto/commits/v0.1.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e3546f09..4e1a0b02 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,7 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/mattn/go-ieproxy v0.0.1 // indirect go.opencensus.io v0.24.0 // indirect - golang.org/x/crypto v0.0.0-20220312131142-6068a2e6cfdc // indirect + golang.org/x/crypto v0.1.0 // indirect golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect golang.org/x/sys v0.6.0 // indirect golang.org/x/text v0.8.0 // indirect diff --git a/go.sum b/go.sum index 9b525181..19daae82 100644 --- a/go.sum +++ b/go.sum @@ -129,8 +129,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220312131142-6068a2e6cfdc h1:i6Z9eOQAdM7lvsbkT3fwFNtSAAC+A59TYilFj53HW+E= -golang.org/x/crypto v0.0.0-20220312131142-6068a2e6cfdc/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= From e3f8f2222105851b80b59f9a4bce7544ad519217 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 12 Mar 2023 14:58:23 +0000 Subject: [PATCH 0975/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.214 to 1.44.219 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.214 to 1.44.219. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.214...v1.44.219) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 4e1a0b02..31895f3c 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.214 + github.com/aws/aws-sdk-go v1.44.219 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 19daae82..57dd31ff 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.214 h1:YzDuC+9UtrAOUkItlK7l3BvKI9o6qAog9X8i289HORc= -github.com/aws/aws-sdk-go v1.44.214/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.219 h1:YOFxTUQZvdRzgwb6XqLFRwNHxoUdKBuunITC7IFhvbc= +github.com/aws/aws-sdk-go v1.44.219/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= From 5daf1451860fc2ac64e2a2a89bdcb81b727d38c3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 19 Mar 2023 15:01:08 +0000 Subject: [PATCH 0976/1127] chore(deps): bump cloud.google.com/go/storage from 1.29.0 to 1.30.0 Bumps [cloud.google.com/go/storage](https://github.com/googleapis/google-cloud-go) from 1.29.0 to 1.30.0. - [Release notes](https://github.com/googleapis/google-cloud-go/releases) - [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/CHANGES.md) - [Commits](https://github.com/googleapis/google-cloud-go/compare/pubsub/v1.29.0...spanner/v1.30.0) --- updated-dependencies: - dependency-name: cloud.google.com/go/storage dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 18 +++++++++--------- go.sum | 38 +++++++++++++++++++------------------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/go.mod b/go.mod index 31895f3c..d7cacbf7 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Praqma/helmsman go 1.18 require ( - cloud.google.com/go/storage v1.29.0 + cloud.google.com/go/storage v1.30.0 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.15.0 github.com/BurntSushi/toml v1.2.1 @@ -19,17 +19,17 @@ require ( ) require ( - cloud.google.com/go v0.107.0 // indirect - cloud.google.com/go/compute v1.14.0 // indirect + cloud.google.com/go v0.110.0 // indirect + cloud.google.com/go/compute v1.18.0 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v0.8.0 // indirect + cloud.google.com/go/iam v0.12.0 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect github.com/golang-jwt/jwt/v4 v4.3.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/uuid v1.3.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.2.1 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect github.com/googleapis/gax-go/v2 v2.7.0 // indirect github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect @@ -37,14 +37,14 @@ require ( github.com/mattn/go-ieproxy v0.0.1 // indirect go.opencensus.io v0.24.0 // indirect golang.org/x/crypto v0.1.0 // indirect - golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect + golang.org/x/oauth2 v0.5.0 // indirect golang.org/x/sys v0.6.0 // indirect golang.org/x/text v0.8.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/api v0.106.0 // indirect + google.golang.org/api v0.110.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect - google.golang.org/grpc v1.51.0 // indirect + google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec // indirect + google.golang.org/grpc v1.53.0 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 57dd31ff..a84ea3c5 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,15 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.107.0 h1:qkj22L7bgkl6vIeZDlOY2po43Mx/TIa2Wsa7VR+PEww= -cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= -cloud.google.com/go/compute v1.14.0 h1:hfm2+FfxVmnRlh6LpB7cg1ZNU+5edAHmW679JePztk0= -cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= +cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys= +cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= +cloud.google.com/go/compute v1.18.0 h1:FEigFqoDbys2cvFkZ9Fjq4gnHBP55anJ0yQyau2f9oY= +cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/iam v0.8.0 h1:E2osAkZzxI/+8pZcxVLcDtAQx/u+hZXVryUaYQ5O0Kk= -cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= +cloud.google.com/go/iam v0.12.0 h1:DRtTY29b75ciH6Ov1PHb4/iat2CLCvrOm40Q0a6DFpE= +cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs= -cloud.google.com/go/storage v1.29.0 h1:6weCgzRvMg7lzuUurI4697AqIRPU1SvzHhynwpW31jI= -cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= +cloud.google.com/go/storage v1.30.0 h1:g1yrbxAWOrvg/594228pETWkOi00MLTrOWfh56veU5o= +cloud.google.com/go/storage v1.30.0/go.mod h1:xAVretHSROm1BQX4IIsoVgJqw0LqOyX+I/O2GzRAzdE= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk= @@ -77,13 +77,13 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1VLQ= +github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.2.1 h1:RY7tHKZcRlk788d5WSo/e83gOyyy742E8GSs771ySpg= -github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= +github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ= github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 h1:i462o439ZjprVSFSZLZxcsoAe592sZB1rci2Z8j4wdk= @@ -152,8 +152,8 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 h1:nt+Q6cXKz4MosCSpnbMtqiQ8Oz0pxTef2B4Vca2lvfk= -golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s= +golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -193,8 +193,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -google.golang.org/api v0.106.0 h1:ffmW0faWCwKkpbbtvlY/K/8fUl+JKvNS5CVzRoyfCv8= -google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.110.0 h1:l+rh0KYUooe9JGbGVx71tbFo4SMbMTXK3I3ia2QSEeU= +google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= @@ -202,15 +202,15 @@ google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w= -google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec h1:6rwgChOSUfpzJF2/KnLgo+gMaxGpujStSkPWrbhXArU= +google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U= -google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= +google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= +google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= From 109e4f00c9a32add7943c8c5d968bc3cf9c2ae69 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 19 Mar 2023 15:02:22 +0000 Subject: [PATCH 0977/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.219 to 1.44.224 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.219 to 1.44.224. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.219...v1.44.224) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 31895f3c..8f86863b 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.219 + github.com/aws/aws-sdk-go v1.44.224 github.com/imdario/mergo v0.3.13 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 57dd31ff..c2c8c5bb 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.219 h1:YOFxTUQZvdRzgwb6XqLFRwNHxoUdKBuunITC7IFhvbc= -github.com/aws/aws-sdk-go v1.44.219/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.224 h1:09CiaaF35nRmxrzWZ2uRq5v6Ghg/d2RiPjZnSgtt+RQ= +github.com/aws/aws-sdk-go v1.44.224/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= From c060876664af6d3291de0177fcb064368f7ead8a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 19 Mar 2023 17:28:14 +0000 Subject: [PATCH 0978/1127] chore(deps): bump github.com/imdario/mergo from 0.3.13 to 0.3.14 Bumps [github.com/imdario/mergo](https://github.com/imdario/mergo) from 0.3.13 to 0.3.14. - [Release notes](https://github.com/imdario/mergo/releases) - [Commits](https://github.com/imdario/mergo/compare/v0.3.13...v0.3.14) --- updated-dependencies: - dependency-name: github.com/imdario/mergo dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index e446a7d8..83d9104a 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 github.com/aws/aws-sdk-go v1.44.224 - github.com/imdario/mergo v0.3.13 + github.com/imdario/mergo v0.3.14 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.4.2 diff --git a/go.sum b/go.sum index 5d570952..4631b973 100644 --- a/go.sum +++ b/go.sum @@ -88,8 +88,8 @@ github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1Yu github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 h1:i462o439ZjprVSFSZLZxcsoAe592sZB1rci2Z8j4wdk= github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= -github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= -github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= +github.com/imdario/mergo v0.3.14 h1:fOqeC1+nCuuk6PKQdg9YmosXX7Y7mHX6R/0ZldI9iHo= +github.com/imdario/mergo v0.3.14/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/invopop/jsonschema v0.7.0 h1:2vgQcBz1n256N+FpX3Jq7Y17AjYt46Ig3zIWyy770So= github.com/invopop/jsonschema v0.7.0/go.mod h1:O9uiLokuu0+MGFlyiaqtWxwqJm41/+8Nj0lD7A36YH0= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= @@ -231,7 +231,6 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From 2f12988d4399d8c56a647a84b5240c0d55f74818 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 26 Mar 2023 15:00:13 +0000 Subject: [PATCH 0979/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.224 to 1.44.229 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.224 to 1.44.229. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.224...v1.44.229) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 83d9104a..ac424519 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.224 + github.com/aws/aws-sdk-go v1.44.229 github.com/imdario/mergo v0.3.14 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 4631b973..ca6220d5 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.224 h1:09CiaaF35nRmxrzWZ2uRq5v6Ghg/d2RiPjZnSgtt+RQ= -github.com/aws/aws-sdk-go v1.44.224/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.229 h1:lku0ZSHRzj/qtFVM//QE8VjV6kvJ6CFijDZSsjNaD9A= +github.com/aws/aws-sdk-go v1.44.229/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= From 192793987068b1d16fdd345704bd76a86de860eb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 26 Mar 2023 15:01:24 +0000 Subject: [PATCH 0980/1127] chore(deps): bump cloud.google.com/go/storage from 1.30.0 to 1.30.1 Bumps [cloud.google.com/go/storage](https://github.com/googleapis/google-cloud-go) from 1.30.0 to 1.30.1. - [Release notes](https://github.com/googleapis/google-cloud-go/releases) - [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/CHANGES.md) - [Commits](https://github.com/googleapis/google-cloud-go/compare/pubsub/v1.30.0...spanner/v1.30.1) --- updated-dependencies: - dependency-name: cloud.google.com/go/storage dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 12 ++++++------ go.sum | 26 +++++++++++++------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/go.mod b/go.mod index 83d9104a..bf441424 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Praqma/helmsman go 1.18 require ( - cloud.google.com/go/storage v1.30.0 + cloud.google.com/go/storage v1.30.1 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.15.0 github.com/BurntSushi/toml v1.2.1 @@ -30,21 +30,21 @@ require ( github.com/google/go-cmp v0.5.9 // indirect github.com/google/uuid v1.3.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect - github.com/googleapis/gax-go/v2 v2.7.0 // indirect + github.com/googleapis/gax-go/v2 v2.7.1 // indirect github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/mattn/go-ieproxy v0.0.1 // indirect go.opencensus.io v0.24.0 // indirect golang.org/x/crypto v0.1.0 // indirect - golang.org/x/oauth2 v0.5.0 // indirect + golang.org/x/oauth2 v0.6.0 // indirect golang.org/x/sys v0.6.0 // indirect golang.org/x/text v0.8.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/api v0.110.0 // indirect + google.golang.org/api v0.114.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec // indirect + google.golang.org/genproto v0.0.0-20230320184635-7606e756e683 // indirect google.golang.org/grpc v1.53.0 // indirect - google.golang.org/protobuf v1.28.1 // indirect + google.golang.org/protobuf v1.29.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 4631b973..bdedc673 100644 --- a/go.sum +++ b/go.sum @@ -7,9 +7,9 @@ cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGB cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/iam v0.12.0 h1:DRtTY29b75ciH6Ov1PHb4/iat2CLCvrOm40Q0a6DFpE= cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= -cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs= -cloud.google.com/go/storage v1.30.0 h1:g1yrbxAWOrvg/594228pETWkOi00MLTrOWfh56veU5o= -cloud.google.com/go/storage v1.30.0/go.mod h1:xAVretHSROm1BQX4IIsoVgJqw0LqOyX+I/O2GzRAzdE= +cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM= +cloud.google.com/go/storage v1.30.1 h1:uOdMxAs8HExqBlnLtnQyP0YkvbiDpdGShGKtx6U/oNM= +cloud.google.com/go/storage v1.30.1/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7BiccwkR7+P7gN8E= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk= @@ -84,8 +84,8 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= -github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ= -github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= +github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6cvPr8A= +github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 h1:i462o439ZjprVSFSZLZxcsoAe592sZB1rci2Z8j4wdk= github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= github.com/imdario/mergo v0.3.14 h1:fOqeC1+nCuuk6PKQdg9YmosXX7Y7mHX6R/0ZldI9iHo= @@ -152,8 +152,8 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s= -golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= +golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= +golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -193,8 +193,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -google.golang.org/api v0.110.0 h1:l+rh0KYUooe9JGbGVx71tbFo4SMbMTXK3I3ia2QSEeU= -google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= +google.golang.org/api v0.114.0 h1:1xQPji6cO2E2vLiI+C/XiFAnsn1WV3mjaEwGLhi3grE= +google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= @@ -202,8 +202,8 @@ google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec h1:6rwgChOSUfpzJF2/KnLgo+gMaxGpujStSkPWrbhXArU= -google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= +google.golang.org/genproto v0.0.0-20230320184635-7606e756e683 h1:khxVcsk/FhnzxMKOyD+TDGwjbEOpcPuIpmafPGFmhMA= +google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= @@ -222,8 +222,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.29.1 h1:7QBf+IK2gx70Ap/hDsOmam3GE0v9HicjfEdAxE62UoM= +google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From 3235489c6f670e8dc1bdf3331709a2af18777461 Mon Sep 17 00:00:00 2001 From: RJ Date: Mon, 27 Mar 2023 10:08:21 +0200 Subject: [PATCH 0981/1127] Add support for hiera-eyaml-gkms (#776) * Add support for hiera-eyaml-gkms * Add hiera-eyaml-gkms to Dockerifle --------- Co-authored-by: Rafal Jendraszak --- Dockerfile | 4 ++-- internal/app/state.go | 17 ++++++++++++++++- internal/app/utils.go | 6 +++++- schema.json | 20 ++++++++++++++++++++ 4 files changed, 43 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 09894ac5..f83424f4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -47,7 +47,7 @@ RUN helm plugin install https://github.com/jkroepke/helm-secrets --version ${HEL FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} as builder RUN apk add --update --no-cache ca-certificates git openssh-client ruby bash make curl -RUN gem install hiera-eyaml --no-doc +RUN gem install hiera-eyaml hiera-eyaml-gkms --no-doc RUN update-ca-certificates COPY --from=helm-installer /usr/local/bin/kubectl /usr/local/bin/kubectl @@ -70,7 +70,7 @@ RUN make test \ FROM alpine:${ALPINE_VERSION} as base RUN apk add --update --no-cache ca-certificates git openssh-client ruby curl bash gnupg -RUN gem install hiera-eyaml --no-doc +RUN gem install hiera-eyaml hiera-eyaml-gkms --no-doc RUN update-ca-certificates COPY --from=helm-installer /usr/local/bin/kubectl /usr/local/bin/kubectl diff --git a/internal/app/state.go b/internal/app/state.go index e90922a8..b4504324 100644 --- a/internal/app/state.go +++ b/internal/app/state.go @@ -56,6 +56,16 @@ type Config struct { EyamlPrivateKeyPath string `json:"eyamlPrivateKeyPath,omitempty"` // EyamlPublicKeyPath is the path to the eyaml public key EyamlPublicKeyPath string `json:"eyamlPublicKeyPath,omitempty"` + // EyamlGkms indicates whether to use GKMS for eyaml + EyamlGkms bool `json:"eyamlGkms,omitepty"` + // EyamlGkmsProject is the GCP project where GKMS keys are stored + EyamlGkmsProject string `json:"eyamlGkmsProject,omitempty"` + // EyamlGkmsLocation is the KMS location + EyamlGkmsLocation string `json:"eyamlGkmsLocation,omitempty"` + // EyamlGkmsKeyring is the ID of the Cloud KMS key ring + EyamlGkmsKeyring string `json:"eyamlGkmsKeyring,omitempty"` + // EyamlGkmsCryptoKey is the ID of the key to use + EyamlGkmsCryptoKey string `json:"eyamlGkmsCryptoKey,omitempty"` // GlobalHooks is a set of global lifecycle hooks GlobalHooks map[string]interface{} `json:"globalHooks,omitempty"` // GlobalMaxHistory sets the global max number of historical release revisions to keep @@ -221,7 +231,12 @@ func (s *State) validate() error { if (s.Settings.EyamlPrivateKeyPath != "" && s.Settings.EyamlPublicKeyPath == "") || (s.Settings.EyamlPrivateKeyPath == "" && s.Settings.EyamlPublicKeyPath != "") { return errors.New("both EyamlPrivateKeyPath and EyamlPublicKeyPath are required") } - + + if s.Settings.EyamlGkms { + if (s.Settings.EyamlGkmsProject == "" || s.Settings.EyamlGkmsLocation == "" || s.Settings.EyamlGkmsKeyring == "" || s.Settings.EyamlGkmsCryptoKey == "") { + return errors.New("all arguments are required: EyamlGkmsProject, EyamlGkmsLocation, EyamlGkmsKeyring, EyamlGkmsCryptoKey") + } + } // namespaces if flags.nsOverride == "" { if s.Namespaces == nil || len(s.Namespaces) == 0 { diff --git a/internal/app/utils.go b/internal/app/utils.go index b1cd6ccf..84db25c8 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -484,7 +484,11 @@ func decryptSecret(name string) error { if settings.EyamlEnabled { cmd = "eyaml" args = []string{"decrypt", "-f", name} - if settings.EyamlPrivateKeyPath != "" && settings.EyamlPublicKeyPath != "" { + if settings.EyamlGkms { + if settings.EyamlGkmsProject != "" && settings.EyamlGkmsLocation != "" && settings.EyamlGkmsKeyring != "" && settings.EyamlGkmsCryptoKey != "" { + args = append(args, []string{"--gkms-auth-type", "default", "--gkms-project", settings.EyamlGkmsProject, "--gkms-location", settings.EyamlGkmsLocation, "--gkms-keyring", settings.EyamlGkmsKeyring, "--gkms-crypto-key", settings.EyamlGkmsCryptoKey}...) + } + } else if settings.EyamlPrivateKeyPath != "" && settings.EyamlPublicKeyPath != "" { args = append(args, []string{"--pkcs7-private-key", settings.EyamlPrivateKeyPath, "--pkcs7-public-key", settings.EyamlPublicKeyPath}...) } } else if settings.VaultEnabled { diff --git a/schema.json b/schema.json index bb10499d..f9272b9e 100644 --- a/schema.json +++ b/schema.json @@ -93,6 +93,26 @@ "type": "string", "description": "EyamlPublicKeyPath is the path to the eyaml public key" }, + "eyamlGkms": { + "type": "boolean", + "description": "EyamlGkms indicates whether to use GKMS for eyaml" + }, + "eyamlGkmsProject": { + "type": "string", + "description": "EyamlGkmsProject is the GCP project where GKMS keys are stored" + }, + "eyamlGkmsLocation": { + "type": "string", + "description": "EyamlGkmsLocation is the KMS location" + }, + "eyamlGkmsKeyring": { + "type": "string", + "description": "EyamlGkmsKeyring is the ID of the Cloud KMS key ring" + }, + "eyamlGkmsCryptoKey": { + "type": "string", + "description": "EyamlGkmsCryptoKey is the ID of the key to use" + }, "globalHooks": { "type": "object", "description": "GlobalHooks is a set of global lifecycle hooks" From b646ce1e19460fc129319d3476b72e9fd7eb76b2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Mar 2023 08:20:25 +0000 Subject: [PATCH 0982/1127] chore(deps): bump github.com/imdario/mergo from 0.3.14 to 0.3.15 Bumps [github.com/imdario/mergo](https://github.com/imdario/mergo) from 0.3.14 to 0.3.15. - [Release notes](https://github.com/imdario/mergo/releases) - [Commits](https://github.com/imdario/mergo/compare/v0.3.14...v0.3.15) --- updated-dependencies: - dependency-name: github.com/imdario/mergo dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6dc3d5c2..08737307 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 github.com/aws/aws-sdk-go v1.44.229 - github.com/imdario/mergo v0.3.14 + github.com/imdario/mergo v0.3.15 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.4.2 diff --git a/go.sum b/go.sum index f570e9a9..d5185866 100644 --- a/go.sum +++ b/go.sum @@ -88,8 +88,8 @@ github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6c github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 h1:i462o439ZjprVSFSZLZxcsoAe592sZB1rci2Z8j4wdk= github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= -github.com/imdario/mergo v0.3.14 h1:fOqeC1+nCuuk6PKQdg9YmosXX7Y7mHX6R/0ZldI9iHo= -github.com/imdario/mergo v0.3.14/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= +github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/invopop/jsonschema v0.7.0 h1:2vgQcBz1n256N+FpX3Jq7Y17AjYt46Ig3zIWyy770So= github.com/invopop/jsonschema v0.7.0/go.mod h1:O9uiLokuu0+MGFlyiaqtWxwqJm41/+8Nj0lD7A36YH0= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= From fecf7c79b116fefd7ee184b946d71b20d58d3599 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Fri, 31 Mar 2023 10:45:25 +0200 Subject: [PATCH 0983/1127] Remove 'priority' field from -spec docs --- ...ltiple_desired_state_files_specification.md | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/docs/how_to/misc/multiple_desired_state_files_specification.md b/docs/how_to/misc/multiple_desired_state_files_specification.md index 8167ec79..2da97e6f 100644 --- a/docs/how_to/misc/multiple_desired_state_files_specification.md +++ b/docs/how_to/misc/multiple_desired_state_files_specification.md @@ -5,7 +5,7 @@ version: v3.8.1 # Specification file Starting from v3.8.0, Helmsman allows you to use Specification file passed with `--spec ` flag -in order to define multiple Desired State Files to be merged in particular order and with specific priorities. +in order to define multiple Desired State Files to be merged together. An example Specification file `spec.yaml`: @@ -14,9 +14,7 @@ An example Specification file `spec.yaml`: stateFiles: - path: examples/example.yaml - path: examples/minimal-example.yaml - priority: -10 - path: examples/minimal-example.toml - priority: -20 ``` @@ -26,18 +24,8 @@ This file can be then run with: helmsman --spec spec.yaml ... ``` -What it does is it takes the files from `stateFiles` list and orders them based on their priorities same way it does with the apps in DSF file. -In an example above the result order would be: - -```yaml - - path: examples/minimal-example.toml - - path: examples/minimal-example.yaml - - path: examples/example.yaml -``` - -with priorities being `-20, -10, 0` after ordering. - -Once ordering is done, Helmsman will read each file one by one and merge the previous states with the current file it goes through. +It takes the files from `stateFiles` list in the same order they are defined. +Then Helmsman will read each file one by one and merge the previous states with the current file it goes through. One can take advantage of that and define the state of the environment starting with more general definitions and then reaching more specific cases in the end, which would overwrite or extend things from previous files. From 6365f1086fe2445ab18042602d78e22804fde699 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Fri, 31 Mar 2023 11:01:56 +0200 Subject: [PATCH 0984/1127] Remove fileOptionArray sort() function as there's no priorities anymore --- internal/app/cli.go | 10 -------- internal/app/spec_state_test.go | 43 --------------------------------- 2 files changed, 53 deletions(-) diff --git a/internal/app/cli.go b/internal/app/cli.go index 24a2f009..d959304f 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -4,7 +4,6 @@ import ( "flag" "fmt" "os" - "sort" "strings" ) @@ -45,14 +44,6 @@ func (f *fileOptionArray) Set(value string) error { return nil } -func (f fileOptionArray) sort() { - log.Verbose("Sorting files listed in the -spec file based on their priorities... ") - - sort.SliceStable(f, func(i, j int) bool { - return (f)[i].priority < (f)[j].priority - }) -} - func (i *stringArray) String() string { return strings.Join(*i, " ") } @@ -279,7 +270,6 @@ func (c *cli) readState(s *State) error { } c.files = append(c.files, fo) } - c.files.sort() } // read the TOML/YAML desired state file diff --git a/internal/app/spec_state_test.go b/internal/app/spec_state_test.go index ac514fa3..728a834d 100644 --- a/internal/app/spec_state_test.go +++ b/internal/app/spec_state_test.go @@ -58,46 +58,3 @@ func Test_specFromYAML(t *testing.T) { }) } } - -func Test_specFileSort(t *testing.T) { - type args struct { - files fileOptionArray - } - tests := []struct { - name string - args args - want [3]int - }{ - { - name: "test case 1 -- Files sorted by priority", - args: args{ - files: fileOptionArray( - []fileOption{ - {"third.yaml", 0}, - {"first.yaml", -20}, - {"second.yaml", -10}, - }), - }, - want: [3]int{-20, -10, 0}, - }, - } - - teardownTestCase, err := setupStateFileTestCase(t) - if err != nil { - t.Fatal(err) - } - defer teardownTestCase(t) - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.args.files.sort() - - got := [3]int{} - for i, f := range tt.args.files { - got[i] = f.priority - } - if got != tt.want { - t.Errorf("files from spec file are not sorted by priority = %v, want %v", got, tt.want) - } - }) - } -} From 6721d9a773485abf6f2c20f135ed083a9d6333f9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 2 Apr 2023 15:00:32 +0000 Subject: [PATCH 0985/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.229 to 1.44.234 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.229 to 1.44.234. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.229...v1.44.234) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 08737307..0a7acdf5 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.229 + github.com/aws/aws-sdk-go v1.44.234 github.com/imdario/mergo v0.3.15 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index d5185866..882c79af 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.229 h1:lku0ZSHRzj/qtFVM//QE8VjV6kvJ6CFijDZSsjNaD9A= -github.com/aws/aws-sdk-go v1.44.229/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.234 h1:8YbQ5AhpgV/cC7jYX8qS34Am/vcn2ZoIFJ1qIgwOL+0= +github.com/aws/aws-sdk-go v1.44.234/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= From 7232f5dc54c80958106dfc74db6c9f3a43324ae1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 9 Apr 2023 14:58:02 +0000 Subject: [PATCH 0986/1127] chore(deps): bump golang.org/x/net from 0.8.0 to 0.9.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.8.0 to 0.9.0. - [Release notes](https://github.com/golang/net/releases) - [Commits](https://github.com/golang/net/compare/v0.8.0...v0.9.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 0a7acdf5..8757b8ec 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.4.2 - golang.org/x/net v0.8.0 + golang.org/x/net v0.9.0 sigs.k8s.io/yaml v1.3.0 ) @@ -38,8 +38,8 @@ require ( go.opencensus.io v0.24.0 // indirect golang.org/x/crypto v0.1.0 // indirect golang.org/x/oauth2 v0.6.0 // indirect - golang.org/x/sys v0.6.0 // indirect - golang.org/x/text v0.8.0 // indirect + golang.org/x/sys v0.7.0 // indirect + golang.org/x/text v0.9.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.114.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index 882c79af..6fd26016 100644 --- a/go.sum +++ b/go.sum @@ -149,8 +149,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= @@ -169,8 +169,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -180,8 +180,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= From 2e89d37d30b6d9eb907e4185bf0b152d391520f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 9 Apr 2023 14:58:21 +0000 Subject: [PATCH 0987/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.234 to 1.44.239 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.234 to 1.44.239. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.234...v1.44.239) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0a7acdf5..c532ea11 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.234 + github.com/aws/aws-sdk-go v1.44.239 github.com/imdario/mergo v0.3.15 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 882c79af..d71e79e2 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.234 h1:8YbQ5AhpgV/cC7jYX8qS34Am/vcn2ZoIFJ1qIgwOL+0= -github.com/aws/aws-sdk-go v1.44.234/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.239 h1:AenB6byCYGSBb30q99CGYqFbqpLpWrTidzm7MzxtuPo= +github.com/aws/aws-sdk-go v1.44.239/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= From 29d7ce4ee642000da22ff7431184b4c22f229caa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 16 Apr 2023 14:57:36 +0000 Subject: [PATCH 0988/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.239 to 1.44.244 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.239 to 1.44.244. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.239...v1.44.244) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 2fa21b5d..f5823fa5 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.239 + github.com/aws/aws-sdk-go v1.44.244 github.com/imdario/mergo v0.3.15 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 78b1cd91..5eefc3f7 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.239 h1:AenB6byCYGSBb30q99CGYqFbqpLpWrTidzm7MzxtuPo= -github.com/aws/aws-sdk-go v1.44.239/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.244 h1:QzBWLD5HjZHdRZyTMTOWtD9Pobzf1n8/CeTJB4giXi0= +github.com/aws/aws-sdk-go v1.44.244/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= From dc0d23a7ac6525e92fd165612075ad981bb84bd9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 23 Apr 2023 14:57:49 +0000 Subject: [PATCH 0989/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.244 to 1.44.248 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.244 to 1.44.248. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.244...v1.44.248) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f5823fa5..112edd7a 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.244 + github.com/aws/aws-sdk-go v1.44.248 github.com/imdario/mergo v0.3.15 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 5eefc3f7..845e2e63 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.244 h1:QzBWLD5HjZHdRZyTMTOWtD9Pobzf1n8/CeTJB4giXi0= -github.com/aws/aws-sdk-go v1.44.244/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.248 h1:GvkxpgsxqNc03LmhXiaxKpzbyxndnex7V+OThLx4g5M= +github.com/aws/aws-sdk-go v1.44.248/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= From 809a525dacbdb864f922fef18450197543615a39 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 30 Apr 2023 14:57:32 +0000 Subject: [PATCH 0990/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.248 to 1.44.253 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.248 to 1.44.253. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.248...v1.44.253) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 112edd7a..64db1482 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.248 + github.com/aws/aws-sdk-go v1.44.253 github.com/imdario/mergo v0.3.15 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 845e2e63..0cc75253 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.248 h1:GvkxpgsxqNc03LmhXiaxKpzbyxndnex7V+OThLx4g5M= -github.com/aws/aws-sdk-go v1.44.248/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.253 h1:iqDd0okcH4ShfFexz2zzf4VmeDFf6NOMm07pHnEb8iY= +github.com/aws/aws-sdk-go v1.44.253/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= From e9dfe8fba1f5f4508f41807b597fae21bad01066 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 7 May 2023 14:57:15 +0000 Subject: [PATCH 0991/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.253 to 1.44.258 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.253 to 1.44.258. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.253...v1.44.258) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 64db1482..90971952 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.253 + github.com/aws/aws-sdk-go v1.44.258 github.com/imdario/mergo v0.3.15 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 0cc75253..f17898fa 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.253 h1:iqDd0okcH4ShfFexz2zzf4VmeDFf6NOMm07pHnEb8iY= -github.com/aws/aws-sdk-go v1.44.253/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.258 h1:JVk1lgpsTnb1kvUw3eGhPLcTpEBp6HeSf1fxcYDs2Ho= +github.com/aws/aws-sdk-go v1.44.258/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= From 3296a1233b9090fec16e93b114afa4f07fa5fa1a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 14 May 2023 14:59:42 +0000 Subject: [PATCH 0992/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.258 to 1.44.262 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.258 to 1.44.262. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.258...v1.44.262) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 90971952..9697f11f 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.258 + github.com/aws/aws-sdk-go v1.44.262 github.com/imdario/mergo v0.3.15 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index f17898fa..c406955c 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.258 h1:JVk1lgpsTnb1kvUw3eGhPLcTpEBp6HeSf1fxcYDs2Ho= -github.com/aws/aws-sdk-go v1.44.258/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.262 h1:gyXpcJptWoNkK+DiAiaBltlreoWKQXjAIh6FRh60F+I= +github.com/aws/aws-sdk-go v1.44.262/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= From 26fc3f75a0e7c6ab5ac45e3886c168827be05970 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 14 May 2023 15:00:02 +0000 Subject: [PATCH 0993/1127] chore(deps): bump golang.org/x/net from 0.9.0 to 0.10.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.9.0 to 0.10.0. - [Commits](https://github.com/golang/net/compare/v0.9.0...v0.10.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 90971952..40bcf4c4 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.4.2 - golang.org/x/net v0.9.0 + golang.org/x/net v0.10.0 sigs.k8s.io/yaml v1.3.0 ) @@ -38,7 +38,7 @@ require ( go.opencensus.io v0.24.0 // indirect golang.org/x/crypto v0.1.0 // indirect golang.org/x/oauth2 v0.6.0 // indirect - golang.org/x/sys v0.7.0 // indirect + golang.org/x/sys v0.8.0 // indirect golang.org/x/text v0.9.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.114.0 // indirect diff --git a/go.sum b/go.sum index f17898fa..5bbeb07d 100644 --- a/go.sum +++ b/go.sum @@ -149,8 +149,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= @@ -169,8 +169,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= From 27347cb6aaea8800c8f6f58d8c1c27140d8a27f7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 21 May 2023 14:58:36 +0000 Subject: [PATCH 0994/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.262 to 1.44.266 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.262 to 1.44.266. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.262...v1.44.266) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ce185e4e..79eb2351 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.262 + github.com/aws/aws-sdk-go v1.44.266 github.com/imdario/mergo v0.3.15 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index f0ee8273..884eafe1 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.262 h1:gyXpcJptWoNkK+DiAiaBltlreoWKQXjAIh6FRh60F+I= -github.com/aws/aws-sdk-go v1.44.262/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.266 h1:MWd775dcYf7NrwgcHLtlsIbWoWkX8p4vomfNHr88zH0= +github.com/aws/aws-sdk-go v1.44.266/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= From f0ddf48c5568739d0d9bfe39864a077e6078f2ac Mon Sep 17 00:00:00 2001 From: Velichko Anton Date: Fri, 26 May 2023 10:59:55 +0400 Subject: [PATCH 0995/1127] Optioned recursive environment variables expansion (#793) * feat: add flag for disabling recursive environment variables expansion * chore: fill the cli documentation about new flag * chore: optimize flags --- docs/cmd_reference.md | 3 +++ internal/app/cli.go | 7 +++++++ internal/app/main.go | 2 +- internal/app/utils.go | 22 +++++++++++++--------- 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/docs/cmd_reference.md b/docs/cmd_reference.md index af4337b8..3180df83 100644 --- a/docs/cmd_reference.md +++ b/docs/cmd_reference.md @@ -69,6 +69,9 @@ This lists available CMD options in Helmsman: `--no-env-subst` turn off environment substitution globally. + `--no-recursive-env-expand` + disable recursive environment variables expansion. + `--subst-env-values` turn on environment substitution in values files. diff --git a/internal/app/cli.go b/internal/app/cli.go index d959304f..6b58edb0 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -79,6 +79,7 @@ type cli struct { diffContext int noEnvSubst bool substEnvValues bool + noRecursiveEnvExpand bool noSSMSubst bool substSSMValues bool detailedExitCode bool @@ -138,6 +139,7 @@ func (c *cli) setup() { flag.BoolVar(&c.detailedExitCode, "detailed-exit-code", false, "returns a detailed exit code (0 - no changes, 1 - error, 2 - changes present)") flag.BoolVar(&c.noEnvSubst, "no-env-subst", false, "turn off environment substitution globally") flag.BoolVar(&c.substEnvValues, "subst-env-values", false, "turn on environment substitution in values files.") + flag.BoolVar(&c.noRecursiveEnvExpand, "no-recursive-env-expand", false, "disable recursive environment values expansion") flag.BoolVar(&c.noSSMSubst, "no-ssm-subst", false, "turn off SSM parameter substitution globally") flag.BoolVar(&c.substSSMValues, "subst-ssm-values", false, "turn on SSM parameter substitution in values files.") flag.BoolVar(&c.updateDeps, "update-deps", false, "run 'helm dep up' for local charts") @@ -231,6 +233,11 @@ func (c *cli) parse() { log.Verbose("Substitution of env variables in values enabled") } } + + if !c.noRecursiveEnvExpand { + log.Verbose("Recursive environment variables expansion is enabled") + } + if !c.noSSMSubst { log.Verbose("Substitution of SSM variables enabled") if c.substSSMValues { diff --git a/internal/app/main.go b/internal/app/main.go index 423a1a27..ec9a31b0 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.16.4" + appVersion = "v3.16.4-fix" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/internal/app/utils.go b/internal/app/utils.go index 84db25c8..8adef9b0 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -146,8 +146,10 @@ func readFile(filepath string) string { // recusively expanding the variable's value func getEnv(key string) string { value := os.Getenv(key) - for envVar.MatchString(value) { - value = os.ExpandEnv(value) + if !flags.noRecursiveEnvExpand { + for envVar.MatchString(value) { + value = os.ExpandEnv(value) + } } return value } @@ -160,13 +162,15 @@ func prepareEnv(envFiles []string) error { return fmt.Errorf("error loading env file: %w", err) } } - for _, e := range os.Environ() { - if !strings.Contains(e, "$") { - continue + if !flags.noRecursiveEnvExpand { + for _, e := range os.Environ() { + if !strings.Contains(e, "$") { + continue + } + e = os.Expand(e, getEnv) + pair := strings.SplitN(e, "=", 2) + os.Setenv(pair[0], pair[1]) } - e = os.Expand(e, getEnv) - pair := strings.SplitN(e, "=", 2) - os.Setenv(pair[0], pair[1]) } return nil } @@ -178,7 +182,7 @@ func substituteEnv(str string) string { if strings.Contains(str, "$") { // add $$ escaping for $ strings os.Setenv("HELMSMAN_DOLLAR", "$") - return os.ExpandEnv(strings.ReplaceAll(str, "$$", "${HELMSMAN_DOLLAR}")) + return os.Expand(strings.ReplaceAll(str, "$$", "${HELMSMAN_DOLLAR}"), getEnv) } return str } From f1ea816e4990cb20b0754324a7538c789151bee2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 28 May 2023 15:09:06 +0000 Subject: [PATCH 0996/1127] chore(deps): bump github.com/imdario/mergo from 0.3.15 to 0.3.16 Bumps [github.com/imdario/mergo](https://github.com/imdario/mergo) from 0.3.15 to 0.3.16. - [Release notes](https://github.com/imdario/mergo/releases) - [Commits](https://github.com/imdario/mergo/compare/v0.3.15...v0.3.16) --- updated-dependencies: - dependency-name: github.com/imdario/mergo dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 79eb2351..580d5cc3 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 github.com/aws/aws-sdk-go v1.44.266 - github.com/imdario/mergo v0.3.15 + github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.4.2 diff --git a/go.sum b/go.sum index 884eafe1..0ff1f332 100644 --- a/go.sum +++ b/go.sum @@ -88,8 +88,8 @@ github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6c github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 h1:i462o439ZjprVSFSZLZxcsoAe592sZB1rci2Z8j4wdk= github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= -github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= -github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/invopop/jsonschema v0.7.0 h1:2vgQcBz1n256N+FpX3Jq7Y17AjYt46Ig3zIWyy770So= github.com/invopop/jsonschema v0.7.0/go.mod h1:O9uiLokuu0+MGFlyiaqtWxwqJm41/+8Nj0lD7A36YH0= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= From f86274b529e009c62bdccc784360ee9bc488454d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 28 May 2023 19:10:10 +0000 Subject: [PATCH 0997/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.266 to 1.44.271 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.266 to 1.44.271. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.266...v1.44.271) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 580d5cc3..07e0982a 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.266 + github.com/aws/aws-sdk-go v1.44.271 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 0ff1f332..c90ebdfe 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.266 h1:MWd775dcYf7NrwgcHLtlsIbWoWkX8p4vomfNHr88zH0= -github.com/aws/aws-sdk-go v1.44.266/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.271 h1:aa+Nu2JcnFmW1TLIz/67SS7KPq1I1Adl4RmExSMjGVo= +github.com/aws/aws-sdk-go v1.44.271/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= From a0784843d3c56a163cb19ac9b61c0ac49709f1f8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 4 Jun 2023 15:03:34 +0000 Subject: [PATCH 0998/1127] chore(deps): bump github.com/BurntSushi/toml from 1.2.1 to 1.3.0 Bumps [github.com/BurntSushi/toml](https://github.com/BurntSushi/toml) from 1.2.1 to 1.3.0. - [Release notes](https://github.com/BurntSushi/toml/releases) - [Commits](https://github.com/BurntSushi/toml/compare/v1.2.1...v1.3.0) --- updated-dependencies: - dependency-name: github.com/BurntSushi/toml dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 07e0982a..609d15b4 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( cloud.google.com/go/storage v1.30.1 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.15.0 - github.com/BurntSushi/toml v1.2.1 + github.com/BurntSushi/toml v1.3.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 github.com/aws/aws-sdk-go v1.44.271 diff --git a/go.sum b/go.sum index c90ebdfe..7ac1c990 100644 --- a/go.sum +++ b/go.sum @@ -27,8 +27,8 @@ github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= -github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.3.0 h1:Ws8e5YmnrGEHzZEzg0YvK/7COGYtTC5PbaH9oSSbgfA= +github.com/BurntSushi/toml v1.3.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= From 0c86f3313ec940fe0d46faa5a1856975523d6eb2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 4 Jun 2023 17:36:23 +0000 Subject: [PATCH 0999/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.271 to 1.44.275 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.271 to 1.44.275. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.271...v1.44.275) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 609d15b4..0e7f53dc 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.271 + github.com/aws/aws-sdk-go v1.44.275 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 7ac1c990..c34daf59 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.271 h1:aa+Nu2JcnFmW1TLIz/67SS7KPq1I1Adl4RmExSMjGVo= -github.com/aws/aws-sdk-go v1.44.271/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.275 h1:VqRULgqrigvQLll4e4hXuc568EQAtZQ6jmBzLlQHzSI= +github.com/aws/aws-sdk-go v1.44.275/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= From 836537779b677a0131dd3c80f2ace4589f11ae0e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 11 Jun 2023 14:59:21 +0000 Subject: [PATCH 1000/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.275 to 1.44.280 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.275 to 1.44.280. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.275...v1.44.280) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0e7f53dc..879386e7 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.275 + github.com/aws/aws-sdk-go v1.44.280 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index c34daf59..6cf22d79 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.275 h1:VqRULgqrigvQLll4e4hXuc568EQAtZQ6jmBzLlQHzSI= -github.com/aws/aws-sdk-go v1.44.275/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.280 h1:UYl/yxhDxP8naok6ftWyQ9/9ZzNwjC9dvEs/j8BkGhw= +github.com/aws/aws-sdk-go v1.44.280/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= From 6ecda9d784cc758a5996fbb6191ae535f0ab2d8e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 11 Jun 2023 14:59:44 +0000 Subject: [PATCH 1001/1127] chore(deps): bump github.com/BurntSushi/toml from 1.3.0 to 1.3.2 Bumps [github.com/BurntSushi/toml](https://github.com/BurntSushi/toml) from 1.3.0 to 1.3.2. - [Release notes](https://github.com/BurntSushi/toml/releases) - [Commits](https://github.com/BurntSushi/toml/compare/v1.3.0...v1.3.2) --- updated-dependencies: - dependency-name: github.com/BurntSushi/toml dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0e7f53dc..8b0f02ad 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( cloud.google.com/go/storage v1.30.1 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.15.0 - github.com/BurntSushi/toml v1.3.0 + github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 github.com/aws/aws-sdk-go v1.44.275 diff --git a/go.sum b/go.sum index c34daf59..dbf30262 100644 --- a/go.sum +++ b/go.sum @@ -27,8 +27,8 @@ github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.3.0 h1:Ws8e5YmnrGEHzZEzg0YvK/7COGYtTC5PbaH9oSSbgfA= -github.com/BurntSushi/toml v1.3.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= From ec58ddb3208155213198a81dd07292dac9ad168c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 18 Jun 2023 15:01:27 +0000 Subject: [PATCH 1002/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.280 to 1.44.284 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.280 to 1.44.284. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.280...v1.44.284) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index fca5c2a9..5eb8ae73 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.280 + github.com/aws/aws-sdk-go v1.44.284 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index d4e07bef..e5a08914 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.280 h1:UYl/yxhDxP8naok6ftWyQ9/9ZzNwjC9dvEs/j8BkGhw= -github.com/aws/aws-sdk-go v1.44.280/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.284 h1:Oc5Kubi43/VCkerlt3ZU3KpBju6BpNkoG3s7E8vj/O8= +github.com/aws/aws-sdk-go v1.44.284/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= From 4dd252d6c3e11684b5f3d4f9f82ac018c2e451fc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 18 Jun 2023 15:01:51 +0000 Subject: [PATCH 1003/1127] chore(deps): bump golang.org/x/net from 0.10.0 to 0.11.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.10.0 to 0.11.0. - [Commits](https://github.com/golang/net/compare/v0.10.0...v0.11.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 7 +++---- go.sum | 15 +++++++-------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index fca5c2a9..9248dcb7 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.4.2 - golang.org/x/net v0.10.0 + golang.org/x/net v0.11.0 sigs.k8s.io/yaml v1.3.0 ) @@ -36,10 +36,9 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/mattn/go-ieproxy v0.0.1 // indirect go.opencensus.io v0.24.0 // indirect - golang.org/x/crypto v0.1.0 // indirect golang.org/x/oauth2 v0.6.0 // indirect - golang.org/x/sys v0.8.0 // indirect - golang.org/x/text v0.9.0 // indirect + golang.org/x/sys v0.9.0 // indirect + golang.org/x/text v0.10.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.114.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index d4e07bef..0f5e0564 100644 --- a/go.sum +++ b/go.sum @@ -129,8 +129,7 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -149,8 +148,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU= +golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= @@ -169,8 +168,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -180,8 +179,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= +golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= From 8e90e27438894436c8cbb4799a3908cfff72207a Mon Sep 17 00:00:00 2001 From: RJ Date: Mon, 19 Jun 2023 00:05:42 +0200 Subject: [PATCH 1004/1127] Add encryption method for hiera-eyaml-gkms (#801) Co-authored-by: Rafal Jendraszak --- internal/app/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/app/utils.go b/internal/app/utils.go index 8adef9b0..c7b0543a 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -490,7 +490,7 @@ func decryptSecret(name string) error { args = []string{"decrypt", "-f", name} if settings.EyamlGkms { if settings.EyamlGkmsProject != "" && settings.EyamlGkmsLocation != "" && settings.EyamlGkmsKeyring != "" && settings.EyamlGkmsCryptoKey != "" { - args = append(args, []string{"--gkms-auth-type", "default", "--gkms-project", settings.EyamlGkmsProject, "--gkms-location", settings.EyamlGkmsLocation, "--gkms-keyring", settings.EyamlGkmsKeyring, "--gkms-crypto-key", settings.EyamlGkmsCryptoKey}...) + args = append(args, []string{"--encrypt-method", "gkms", "--gkms-auth-type", "default", "--gkms-project", settings.EyamlGkmsProject, "--gkms-location", settings.EyamlGkmsLocation, "--gkms-keyring", settings.EyamlGkmsKeyring, "--gkms-crypto-key", settings.EyamlGkmsCryptoKey}...) } } else if settings.EyamlPrivateKeyPath != "" && settings.EyamlPublicKeyPath != "" { args = append(args, []string{"--pkcs7-private-key", settings.EyamlPrivateKeyPath, "--pkcs7-public-key", settings.EyamlPublicKeyPath}...) From 0a7db81a209172ed065dff52b78121dfe60a8218 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 25 Jun 2023 14:59:27 +0000 Subject: [PATCH 1005/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.284 to 1.44.289 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.284 to 1.44.289. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.284...v1.44.289) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 65b19539..8c4ccf97 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.284 + github.com/aws/aws-sdk-go v1.44.289 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 22471df6..264ccf9b 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.284 h1:Oc5Kubi43/VCkerlt3ZU3KpBju6BpNkoG3s7E8vj/O8= -github.com/aws/aws-sdk-go v1.44.284/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.289 h1:5CVEjiHFvdiVlKPBzv0rjG4zH/21W/onT18R5AH/qx0= +github.com/aws/aws-sdk-go v1.44.289/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= From 7c07dce8f02d65a1b1dfb2f8785fba5233412022 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 2 Jul 2023 08:51:43 +0000 Subject: [PATCH 1006/1127] chore(deps): bump cloud.google.com/go/storage from 1.30.1 to 1.31.0 Bumps [cloud.google.com/go/storage](https://github.com/googleapis/google-cloud-go) from 1.30.1 to 1.31.0. - [Release notes](https://github.com/googleapis/google-cloud-go/releases) - [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/CHANGES.md) - [Commits](https://github.com/googleapis/google-cloud-go/compare/pubsub/v1.30.1...pubsub/v1.31.0) --- updated-dependencies: - dependency-name: cloud.google.com/go/storage dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 26 ++++++++++-------- go.sum | 85 +++++++++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 78 insertions(+), 33 deletions(-) diff --git a/go.mod b/go.mod index 8c4ccf97..f77f4a19 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Praqma/helmsman go 1.18 require ( - cloud.google.com/go/storage v1.30.1 + cloud.google.com/go/storage v1.31.0 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.15.0 github.com/BurntSushi/toml v1.3.2 @@ -19,31 +19,35 @@ require ( ) require ( - cloud.google.com/go v0.110.0 // indirect - cloud.google.com/go/compute v1.18.0 // indirect + cloud.google.com/go v0.110.2 // indirect + cloud.google.com/go/compute v1.19.3 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v0.12.0 // indirect + cloud.google.com/go/iam v1.1.0 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect github.com/golang-jwt/jwt/v4 v4.3.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/google/go-cmp v0.5.9 // indirect + github.com/google/s2a-go v0.1.4 // indirect github.com/google/uuid v1.3.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect - github.com/googleapis/gax-go/v2 v2.7.1 // indirect + github.com/googleapis/gax-go/v2 v2.11.0 // indirect github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/mattn/go-ieproxy v0.0.1 // indirect go.opencensus.io v0.24.0 // indirect - golang.org/x/oauth2 v0.6.0 // indirect + golang.org/x/crypto v0.10.0 // indirect + golang.org/x/oauth2 v0.8.0 // indirect golang.org/x/sys v0.9.0 // indirect golang.org/x/text v0.10.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/api v0.114.0 // indirect + google.golang.org/api v0.126.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230320184635-7606e756e683 // indirect - google.golang.org/grpc v1.53.0 // indirect - google.golang.org/protobuf v1.29.1 // indirect + google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect + google.golang.org/grpc v1.55.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 264ccf9b..1d634047 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,15 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys= -cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= -cloud.google.com/go/compute v1.18.0 h1:FEigFqoDbys2cvFkZ9Fjq4gnHBP55anJ0yQyau2f9oY= -cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.110.2 h1:sdFPBr6xG9/wkBbfhmUz/JmZC7X6LavQgcrVINrKiVA= +cloud.google.com/go v0.110.2/go.mod h1:k04UEeEtb6ZBRTv3dZz4CeJC3jKGxyhl0sAiVVquxiw= +cloud.google.com/go/compute v1.19.3 h1:DcTwsFgGev/wV5+q8o2fzgcHOaac+DKGC91ZlvpsQds= +cloud.google.com/go/compute v1.19.3/go.mod h1:qxvISKp/gYnXkSAD1ppcSOveRAmzxicEv/JlizULFrI= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/iam v0.12.0 h1:DRtTY29b75ciH6Ov1PHb4/iat2CLCvrOm40Q0a6DFpE= -cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= -cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM= -cloud.google.com/go/storage v1.30.1 h1:uOdMxAs8HExqBlnLtnQyP0YkvbiDpdGShGKtx6U/oNM= -cloud.google.com/go/storage v1.30.1/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7BiccwkR7+P7gN8E= +cloud.google.com/go/iam v1.1.0 h1:67gSqaPukx7O8WLLHMa0PNs3EBGd2eE4d+psbO/CO94= +cloud.google.com/go/iam v1.1.0/go.mod h1:nxdHjaKfCr7fNYx/HJMM8LgiMugmveWlkatear5gVyk= +cloud.google.com/go/storage v1.31.0 h1:+S3LjjEN2zZ+L5hOwj4+1OkGCsLVe0NzpXKQ1pSdTCI= +cloud.google.com/go/storage v1.31.0/go.mod h1:81ams1PrhW16L4kF7qg+4mTq7SRs5HsbDTM0bWvrwJ0= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk= @@ -31,13 +31,20 @@ github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8 github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= github.com/aws/aws-sdk-go v1.44.289 h1:5CVEjiHFvdiVlKPBzv0rjG4zH/21W/onT18R5AH/qx0= github.com/aws/aws-sdk-go v1.44.289/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -45,8 +52,11 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog= github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= @@ -58,16 +68,19 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -78,14 +91,17 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= +github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= +github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= -github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6cvPr8A= -github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= +github.com/googleapis/gax-go/v2 v2.11.0 h1:9V9PWXEsWnPpQhu/PeQIkS4eGzMlTLGgt80cUUI8Ki4= +github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 h1:i462o439ZjprVSFSZLZxcsoAe592sZB1rci2Z8j4wdk= github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= @@ -111,10 +127,13 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= @@ -124,12 +143,15 @@ github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNG github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= +golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -137,30 +159,37 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU= golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= -golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= +golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -178,6 +207,7 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= @@ -190,26 +220,35 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -google.golang.org/api v0.114.0 h1:1xQPji6cO2E2vLiI+C/XiFAnsn1WV3mjaEwGLhi3grE= -google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= +google.golang.org/api v0.126.0 h1:q4GJq+cAdMAC7XP7njvQ4tvohGLiSlytuL4BQxbIZ+o= +google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20230320184635-7606e756e683 h1:khxVcsk/FhnzxMKOyD+TDGwjbEOpcPuIpmafPGFmhMA= -google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc h1:8DyZCyvI8mE1IdLy/60bS+52xfymkE72wv1asokgtao= +google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64= +google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc h1:kVKPf/IiYSBWEWtkIn6wZXwWGCnLKcC8oWfZvXjsGnM= +google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= -google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag= +google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -221,11 +260,13 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.29.1 h1:7QBf+IK2gx70Ap/hDsOmam3GE0v9HicjfEdAxE62UoM= -google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= From 331597172378b0b9600df6897ebc616ffb2981b7 Mon Sep 17 00:00:00 2001 From: Peter Elmers Date: Sat, 8 Jul 2023 16:50:36 +0200 Subject: [PATCH 1007/1127] Fix small mistakes in function doc comments (#807) --- internal/app/plan.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/app/plan.go b/internal/app/plan.go index 323939b5..1c9b4681 100644 --- a/internal/app/plan.go +++ b/internal/app/plan.go @@ -199,7 +199,7 @@ func execOne(cmd Command, targetRelease *Release) error { return nil } -// printPlanCmds prints the actual commands that will be executed as part of a plan. +// printCmds prints the actual commands that will be executed as part of a plan. func (p *plan) printCmds() { log.Info("Printing the commands of the current plan ...") for _, cmd := range p.Commands { @@ -213,7 +213,7 @@ func (p *plan) printCmds() { } } -// printPlan prints the decisions made in a plan. +// print prints the decisions made in a plan. func (p *plan) print() { log.Notice("-------- PLAN starts here --------------") for _, decision := range p.Decisions { @@ -228,7 +228,7 @@ func (p *plan) print() { log.Notice("-------- PLAN ends here --------------") } -// sendPlanToSlack sends the description of plan commands to slack if a webhook is provided. +// sendToSlack sends the description of plan commands to slack if a webhook is provided. func (p *plan) sendToSlack() { if _, err := url.ParseRequestURI(log.SlackWebhook); err == nil { str := "" @@ -250,7 +250,7 @@ func (p *plan) sendToMSTeams() { } } -// sortPlan sorts the slices of commands and decisions based on priorities +// sort sorts the slices of commands and decisions based on priorities // the lower the priority value the earlier a command should be attempted func (p *plan) sort() { log.Verbose("Sorting the commands in the plan based on priorities (order flags) ... ") From 770c80fd67f56f4b634feb79d2b357ebcb210a28 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 8 Jul 2023 14:52:25 +0000 Subject: [PATCH 1008/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.289 to 1.44.298 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.289 to 1.44.298. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.289...v1.44.298) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f77f4a19..c4f0d1c1 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.289 + github.com/aws/aws-sdk-go v1.44.298 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 1d634047..d32b17a8 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF0 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.289 h1:5CVEjiHFvdiVlKPBzv0rjG4zH/21W/onT18R5AH/qx0= -github.com/aws/aws-sdk-go v1.44.289/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.298 h1:5qTxdubgV7PptZJmp/2qDwD2JL187ePL7VOxsSh1i3g= +github.com/aws/aws-sdk-go v1.44.298/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= From 63b4e31e9d1eee6162ff6fd57034e06293ccc7ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 8 Jul 2023 14:52:36 +0000 Subject: [PATCH 1009/1127] chore(deps): bump golang.org/x/net from 0.11.0 to 0.12.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.11.0 to 0.12.0. - [Commits](https://github.com/golang/net/compare/v0.11.0...v0.12.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index f77f4a19..a86090a7 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.4.2 - golang.org/x/net v0.11.0 + golang.org/x/net v0.12.0 sigs.k8s.io/yaml v1.3.0 ) @@ -37,10 +37,10 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/mattn/go-ieproxy v0.0.1 // indirect go.opencensus.io v0.24.0 // indirect - golang.org/x/crypto v0.10.0 // indirect + golang.org/x/crypto v0.11.0 // indirect golang.org/x/oauth2 v0.8.0 // indirect - golang.org/x/sys v0.9.0 // indirect - golang.org/x/text v0.10.0 // indirect + golang.org/x/sys v0.10.0 // indirect + golang.org/x/text v0.11.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.126.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index 1d634047..c4fbb0e1 100644 --- a/go.sum +++ b/go.sum @@ -150,8 +150,8 @@ golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= -golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= +golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -173,8 +173,8 @@ golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU= -golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= @@ -197,8 +197,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= -golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -209,8 +209,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= -golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= From bf17dc99bd777669a4fd9c100d8670a4db098b01 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 16 Jul 2023 14:15:07 +0000 Subject: [PATCH 1010/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.298 to 1.44.300 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.298 to 1.44.300. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.298...v1.44.300) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c76f0bfa..fbc778bc 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.298 + github.com/aws/aws-sdk-go v1.44.300 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index abd47052..c2e65b79 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF0 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.298 h1:5qTxdubgV7PptZJmp/2qDwD2JL187ePL7VOxsSh1i3g= -github.com/aws/aws-sdk-go v1.44.298/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.300 h1:Zn+3lqgYahIf9yfrwZ+g+hq/c3KzUBaQ8wqY/ZXiAbY= +github.com/aws/aws-sdk-go v1.44.300/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= From d3f887d8196df701531048b458f15237260a3fd5 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Thu, 20 Jul 2023 10:06:50 +0200 Subject: [PATCH 1011/1127] Release v3.17.0 --- .circleci/config.yml | 2 +- .version | 2 +- README.md | 2 +- internal/app/main.go | 2 +- release-notes.md | 11 +++++++---- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b962b90f..010423fd 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -36,7 +36,7 @@ jobs: - run: name: build docker images and push them to dockerhub command: | - helm_versions=( "v3.8.2" "v3.9.4" "v3.10.3" "v3.11.0" ) + helm_versions=( "v3.12.2" "v3.11.3" "v3.10.3" ) TAG=$(git describe --abbrev=0 --tags) docker login -u $DOCKER_USER -p $DOCKER_PASS diff --git a/.version b/.version index 5aeb0abe..597e63fe 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.16.4 +v3.17.0 diff --git a/README.md b/README.md index e1f86a99..8b008717 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.16.4&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.17.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) diff --git a/internal/app/main.go b/internal/app/main.go index ec9a31b0..8bc0852c 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.16.4-fix" + appVersion = "v3.17.0" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index 22cc5638..be70974b 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,7 +1,10 @@ -# v3.16.4 +# v3.17.0 + +## New feature + +- Add support for hiera-eyaml-gkms (#776) +- Add optioned recursive environment variables expansion (#793) ## Fixes and improvements -- Dependency version upgrades -- Fixed helm-vault decryption (#759) -- Documentation fixes (#753) +- Remove 'priority' field from -spec docs (#781) From ff0ad9af06f61042484cca10be4c26d410778af0 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Thu, 20 Jul 2023 10:12:20 +0200 Subject: [PATCH 1012/1127] Revert "Release v3.17.0" This reverts commit d3f887d8196df701531048b458f15237260a3fd5. --- .circleci/config.yml | 2 +- .version | 2 +- README.md | 2 +- internal/app/main.go | 2 +- release-notes.md | 11 ++++------- 5 files changed, 8 insertions(+), 11 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 010423fd..b962b90f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -36,7 +36,7 @@ jobs: - run: name: build docker images and push them to dockerhub command: | - helm_versions=( "v3.12.2" "v3.11.3" "v3.10.3" ) + helm_versions=( "v3.8.2" "v3.9.4" "v3.10.3" "v3.11.0" ) TAG=$(git describe --abbrev=0 --tags) docker login -u $DOCKER_USER -p $DOCKER_PASS diff --git a/.version b/.version index 597e63fe..5aeb0abe 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.17.0 +v3.16.4 diff --git a/README.md b/README.md index 8b008717..e1f86a99 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.17.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.16.4&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) diff --git a/internal/app/main.go b/internal/app/main.go index 8bc0852c..ec9a31b0 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.17.0" + appVersion = "v3.16.4-fix" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index be70974b..22cc5638 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,10 +1,7 @@ -# v3.17.0 - -## New feature - -- Add support for hiera-eyaml-gkms (#776) -- Add optioned recursive environment variables expansion (#793) +# v3.16.4 ## Fixes and improvements -- Remove 'priority' field from -spec docs (#781) +- Dependency version upgrades +- Fixed helm-vault decryption (#759) +- Documentation fixes (#753) From 49087e8d2b7a6cf3baab26007e419be962df8d88 Mon Sep 17 00:00:00 2001 From: Mateusz Kubaczyk <12384536+mkubaczyk@users.noreply.github.com> Date: Thu, 20 Jul 2023 10:06:50 +0200 Subject: [PATCH 1013/1127] Release v3.17.0 --- .circleci/config.yml | 2 +- .version | 2 +- README.md | 2 +- internal/app/main.go | 2 +- release-notes.md | 11 +++++++---- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b962b90f..010423fd 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -36,7 +36,7 @@ jobs: - run: name: build docker images and push them to dockerhub command: | - helm_versions=( "v3.8.2" "v3.9.4" "v3.10.3" "v3.11.0" ) + helm_versions=( "v3.12.2" "v3.11.3" "v3.10.3" ) TAG=$(git describe --abbrev=0 --tags) docker login -u $DOCKER_USER -p $DOCKER_PASS diff --git a/.version b/.version index 5aeb0abe..597e63fe 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.16.4 +v3.17.0 diff --git a/README.md b/README.md index e1f86a99..8b008717 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.16.4&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.17.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) diff --git a/internal/app/main.go b/internal/app/main.go index ec9a31b0..8bc0852c 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.16.4-fix" + appVersion = "v3.17.0" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index 22cc5638..be70974b 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,7 +1,10 @@ -# v3.16.4 +# v3.17.0 + +## New feature + +- Add support for hiera-eyaml-gkms (#776) +- Add optioned recursive environment variables expansion (#793) ## Fixes and improvements -- Dependency version upgrades -- Fixed helm-vault decryption (#759) -- Documentation fixes (#753) +- Remove 'priority' field from -spec docs (#781) From 5c9636c40889a64d0ceedfaaae55cb1584316db9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 23 Jul 2023 14:58:05 +0000 Subject: [PATCH 1014/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.300 to 1.44.306 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.300 to 1.44.306. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.300...v1.44.306) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index fbc778bc..21a661dc 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.300 + github.com/aws/aws-sdk-go v1.44.306 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index c2e65b79..2af57941 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF0 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.300 h1:Zn+3lqgYahIf9yfrwZ+g+hq/c3KzUBaQ8wqY/ZXiAbY= -github.com/aws/aws-sdk-go v1.44.300/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.306 h1:H487V/1N09BDxeGR7oR+LloC2uUpmf4atmqJaBgQOIs= +github.com/aws/aws-sdk-go v1.44.306/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= From dd061671cbef185f62f0d4a14bbbbf0150319e11 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Jul 2023 07:10:44 +0000 Subject: [PATCH 1015/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.306 to 1.44.307 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.306 to 1.44.307. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.306...v1.44.307) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 21a661dc..0f4a90f7 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.306 + github.com/aws/aws-sdk-go v1.44.307 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 2af57941..e205a761 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF0 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.306 h1:H487V/1N09BDxeGR7oR+LloC2uUpmf4atmqJaBgQOIs= -github.com/aws/aws-sdk-go v1.44.306/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.307 h1:2R0/EPgpZcFSUwZhYImq/srjaOrOfLv5MNRzrFyAM38= +github.com/aws/aws-sdk-go v1.44.307/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= From 5cf7f4eb2babccf8e65975a2fd13bac870d24ee9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 29 Jul 2023 10:37:49 +0000 Subject: [PATCH 1016/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.307 to 1.44.312 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.307 to 1.44.312. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.307...v1.44.312) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0f4a90f7..9e67a745 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.307 + github.com/aws/aws-sdk-go v1.44.312 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index e205a761..99d04ea8 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF0 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.307 h1:2R0/EPgpZcFSUwZhYImq/srjaOrOfLv5MNRzrFyAM38= -github.com/aws/aws-sdk-go v1.44.307/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.312 h1:llrElfzeqG/YOLFFKjg1xNpZCFJ2xraIi3PqSuP+95k= +github.com/aws/aws-sdk-go v1.44.312/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= From 64ec763caad3232dcf7425b8a25ead7f6869f136 Mon Sep 17 00:00:00 2001 From: RJ Date: Sat, 5 Aug 2023 11:42:02 +0000 Subject: [PATCH 1017/1127] Fix issue with hiera-eyaml-gkms encrypt/decrypt actions (#814) Co-authored-by: Rafal Jendraszak --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index f83424f4..683a1923 100644 --- a/Dockerfile +++ b/Dockerfile @@ -69,7 +69,7 @@ RUN make test \ ### Final Image ### FROM alpine:${ALPINE_VERSION} as base -RUN apk add --update --no-cache ca-certificates git openssh-client ruby curl bash gnupg +RUN apk add --update --no-cache ca-certificates git openssh-client ruby curl bash gnupg gcompat RUN gem install hiera-eyaml hiera-eyaml-gkms --no-doc RUN update-ca-certificates From eb4018dc9bf8ab2cd6bc2f223d87fa069634bff5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 6 Aug 2023 14:56:19 +0000 Subject: [PATCH 1018/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.312 to 1.44.317 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.312 to 1.44.317. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.312...v1.44.317) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9e67a745..eaac617f 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.312 + github.com/aws/aws-sdk-go v1.44.317 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 99d04ea8..ea080b0f 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF0 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.312 h1:llrElfzeqG/YOLFFKjg1xNpZCFJ2xraIi3PqSuP+95k= -github.com/aws/aws-sdk-go v1.44.312/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.317 h1:+8XWrLmGMwPPXSRSLPzhgcGnzJ2mYkgkrcB9C/GnSOU= +github.com/aws/aws-sdk-go v1.44.317/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= From 4853f4d169163da53d0cccb67b8b4431db14c07d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 6 Aug 2023 14:56:29 +0000 Subject: [PATCH 1019/1127] chore(deps): bump golang.org/x/net from 0.12.0 to 0.14.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.12.0 to 0.14.0. - [Commits](https://github.com/golang/net/compare/v0.12.0...v0.14.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 9e67a745..0610f1d3 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.4.2 - golang.org/x/net v0.12.0 + golang.org/x/net v0.14.0 sigs.k8s.io/yaml v1.3.0 ) @@ -37,10 +37,10 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/mattn/go-ieproxy v0.0.1 // indirect go.opencensus.io v0.24.0 // indirect - golang.org/x/crypto v0.11.0 // indirect + golang.org/x/crypto v0.12.0 // indirect golang.org/x/oauth2 v0.8.0 // indirect - golang.org/x/sys v0.10.0 // indirect - golang.org/x/text v0.11.0 // indirect + golang.org/x/sys v0.11.0 // indirect + golang.org/x/text v0.12.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.126.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index 99d04ea8..a349894c 100644 --- a/go.sum +++ b/go.sum @@ -150,8 +150,8 @@ golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= -golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -173,8 +173,8 @@ golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= -golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= @@ -197,8 +197,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -209,8 +209,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= From 951458ff9a6c5e9e5e78b69238a62914bbea4a09 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 13 Aug 2023 14:13:40 +0000 Subject: [PATCH 1020/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.317 to 1.44.322 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.317 to 1.44.322. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.317...v1.44.322) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ae9cf932..405b76a9 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.317 + github.com/aws/aws-sdk-go v1.44.322 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index b5a26bb7..cbfcf57a 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF0 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.317 h1:+8XWrLmGMwPPXSRSLPzhgcGnzJ2mYkgkrcB9C/GnSOU= -github.com/aws/aws-sdk-go v1.44.317/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.322 h1:7JfwifGRGQMHd99PvfXqxBaZsjuRaOF6e3X9zRx2uYo= +github.com/aws/aws-sdk-go v1.44.322/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= From 12ee31968b49ad191b987e5255e89695ff0d8377 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Aug 2023 09:04:37 +0000 Subject: [PATCH 1021/1127] chore(deps): bump cloud.google.com/go/storage from 1.31.0 to 1.32.0 Bumps [cloud.google.com/go/storage](https://github.com/googleapis/google-cloud-go) from 1.31.0 to 1.32.0. - [Release notes](https://github.com/googleapis/google-cloud-go/releases) - [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/CHANGES.md) - [Commits](https://github.com/googleapis/google-cloud-go/compare/pubsub/v1.31.0...pubsub/v1.32.0) --- updated-dependencies: - dependency-name: cloud.google.com/go/storage dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 24 ++++++++++++------------ go.sum | 50 +++++++++++++++++++++++++------------------------- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/go.mod b/go.mod index 405b76a9..ee20096d 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Praqma/helmsman go 1.18 require ( - cloud.google.com/go/storage v1.31.0 + cloud.google.com/go/storage v1.32.0 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.15.0 github.com/BurntSushi/toml v1.3.2 @@ -19,8 +19,8 @@ require ( ) require ( - cloud.google.com/go v0.110.2 // indirect - cloud.google.com/go/compute v1.19.3 // indirect + cloud.google.com/go v0.110.4 // indirect + cloud.google.com/go/compute v1.20.1 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/iam v1.1.0 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect @@ -30,24 +30,24 @@ require ( github.com/google/go-cmp v0.5.9 // indirect github.com/google/s2a-go v0.1.4 // indirect github.com/google/uuid v1.3.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect - github.com/googleapis/gax-go/v2 v2.11.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect + github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/mattn/go-ieproxy v0.0.1 // indirect go.opencensus.io v0.24.0 // indirect golang.org/x/crypto v0.12.0 // indirect - golang.org/x/oauth2 v0.8.0 // indirect + golang.org/x/oauth2 v0.10.0 // indirect golang.org/x/sys v0.11.0 // indirect golang.org/x/text v0.12.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/api v0.126.0 // indirect + google.golang.org/api v0.132.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect - google.golang.org/grpc v1.55.0 // indirect - google.golang.org/protobuf v1.30.0 // indirect + google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect + google.golang.org/grpc v1.56.2 // indirect + google.golang.org/protobuf v1.31.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index cbfcf57a..ca61ae6b 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,15 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.110.2 h1:sdFPBr6xG9/wkBbfhmUz/JmZC7X6LavQgcrVINrKiVA= -cloud.google.com/go v0.110.2/go.mod h1:k04UEeEtb6ZBRTv3dZz4CeJC3jKGxyhl0sAiVVquxiw= -cloud.google.com/go/compute v1.19.3 h1:DcTwsFgGev/wV5+q8o2fzgcHOaac+DKGC91ZlvpsQds= -cloud.google.com/go/compute v1.19.3/go.mod h1:qxvISKp/gYnXkSAD1ppcSOveRAmzxicEv/JlizULFrI= +cloud.google.com/go v0.110.4 h1:1JYyxKMN9hd5dR2MYTPWkGUgcoxVVhg0LKNKEo0qvmk= +cloud.google.com/go v0.110.4/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI= +cloud.google.com/go/compute v1.20.1 h1:6aKEtlUiwEpJzM001l0yFkpXmUVXaN8W+fbkb2AZNbg= +cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/iam v1.1.0 h1:67gSqaPukx7O8WLLHMa0PNs3EBGd2eE4d+psbO/CO94= cloud.google.com/go/iam v1.1.0/go.mod h1:nxdHjaKfCr7fNYx/HJMM8LgiMugmveWlkatear5gVyk= -cloud.google.com/go/storage v1.31.0 h1:+S3LjjEN2zZ+L5hOwj4+1OkGCsLVe0NzpXKQ1pSdTCI= -cloud.google.com/go/storage v1.31.0/go.mod h1:81ams1PrhW16L4kF7qg+4mTq7SRs5HsbDTM0bWvrwJ0= +cloud.google.com/go/storage v1.32.0 h1:5w6DxEGOnktmJHarxAOUywxVW9lbNWIzlzzUltG/3+o= +cloud.google.com/go/storage v1.32.0/go.mod h1:Hhh/dogNRGca7IWv1RC2YqEn0c0G77ctA/OxflYkiD8= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk= @@ -97,10 +97,10 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= -github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= -github.com/googleapis/gax-go/v2 v2.11.0 h1:9V9PWXEsWnPpQhu/PeQIkS4eGzMlTLGgt80cUUI8Ki4= -github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= +github.com/googleapis/enterprise-certificate-proxy v0.2.5 h1:UR4rDjcgpgEnqpIEvkiqTYKBCKLNmlge2eVjoZfySzM= +github.com/googleapis/enterprise-certificate-proxy v0.2.5/go.mod h1:RxW0N9901Cko1VOCW3SXCpWP+mlIEkk2tP7jnHy9a3w= +github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= +github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 h1:i462o439ZjprVSFSZLZxcsoAe592sZB1rci2Z8j4wdk= github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= @@ -177,14 +177,14 @@ golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= -golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= +golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= +golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -223,8 +223,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -google.golang.org/api v0.126.0 h1:q4GJq+cAdMAC7XP7njvQ4tvohGLiSlytuL4BQxbIZ+o= -google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw= +google.golang.org/api v0.132.0 h1:8t2/+qZ26kAOGSmOiHwVycqVaDg7q3JDILrNi/Z6rvc= +google.golang.org/api v0.132.0/go.mod h1:AeTBC6GpJnJSRJjktDcPX0QwtS8pGYZOV6MSuSCusw0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= @@ -233,12 +233,12 @@ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoA google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc h1:8DyZCyvI8mE1IdLy/60bS+52xfymkE72wv1asokgtao= -google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64= -google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc h1:kVKPf/IiYSBWEWtkIn6wZXwWGCnLKcC8oWfZvXjsGnM= -google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= +google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 h1:Au6te5hbKUV8pIYWHqOUZ1pva5qK/rwbIhoXEUB9Lu8= +google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:O9kGHb51iE/nOGvQaDUuadVYqovW56s5emA88lQnj6Y= +google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130 h1:XVeBY8d/FaK4848myy41HBqnDwvxeV3zMZhwN1TvAMU= +google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:mPBs5jNgx2GuQGvFwUvVKqtn6HsUw9nP64BedgvqEsQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= @@ -247,8 +247,8 @@ google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTp google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= -google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag= -google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= +google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI= +google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -260,8 +260,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From a26cc2021084ee90fde25f9a4aada70a3e874496 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Aug 2023 09:05:06 +0000 Subject: [PATCH 1022/1127] chore(deps): bump github.com/subosito/gotenv from 1.4.2 to 1.6.0 Bumps [github.com/subosito/gotenv](https://github.com/subosito/gotenv) from 1.4.2 to 1.6.0. - [Changelog](https://github.com/subosito/gotenv/blob/master/CHANGELOG.md) - [Commits](https://github.com/subosito/gotenv/compare/v1.4.2...v1.6.0) --- updated-dependencies: - dependency-name: github.com/subosito/gotenv dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 405b76a9..5173c5ba 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible - github.com/subosito/gotenv v1.4.2 + github.com/subosito/gotenv v1.6.0 golang.org/x/net v0.14.0 sigs.k8s.io/yaml v1.3.0 ) diff --git a/go.sum b/go.sum index cbfcf57a..2373af49 100644 --- a/go.sum +++ b/go.sum @@ -138,8 +138,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= -github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= From 6093cfb7d050834d1daf11e9d483926abc6fc3fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Aug 2023 09:05:40 +0000 Subject: [PATCH 1023/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.322 to 1.44.324 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.322 to 1.44.324. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.322...v1.44.324) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 405b76a9..df82cfef 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.322 + github.com/aws/aws-sdk-go v1.44.324 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index cbfcf57a..02bc10eb 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF0 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.322 h1:7JfwifGRGQMHd99PvfXqxBaZsjuRaOF6e3X9zRx2uYo= -github.com/aws/aws-sdk-go v1.44.322/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.324 h1:/uja9PtgeeqrZCPOJTenjMLNpciIMuzaRKooq+erG4A= +github.com/aws/aws-sdk-go v1.44.324/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= From 0299b732993a2f77ba558fadb4d336062e0d183c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 18 Aug 2023 17:50:54 +0000 Subject: [PATCH 1024/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.324 to 1.44.326 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.324 to 1.44.326. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG_PENDING.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.324...v1.44.326) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 16cf8418..db3a7f61 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.324 + github.com/aws/aws-sdk-go v1.44.326 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 342509e7..78da836f 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF0 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.324 h1:/uja9PtgeeqrZCPOJTenjMLNpciIMuzaRKooq+erG4A= -github.com/aws/aws-sdk-go v1.44.324/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.326 h1:/6xD/9mKZ2RMTDfbhh9qCxw+CaTbJRvfHJ/NHPFbI38= +github.com/aws/aws-sdk-go v1.44.326/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= From a5fa9228a0b9c7f980f6beff896fb94f318301c8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 20 Aug 2023 14:09:07 +0000 Subject: [PATCH 1025/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.326 to 1.44.327 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.326 to 1.44.327. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.326...v1.44.327) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 14981587..bdb5fbf6 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.326 + github.com/aws/aws-sdk-go v1.44.327 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index d4cd6f07..461d6598 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF0 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.326 h1:/6xD/9mKZ2RMTDfbhh9qCxw+CaTbJRvfHJ/NHPFbI38= -github.com/aws/aws-sdk-go v1.44.326/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.327 h1:ZS8oO4+7MOBLhkdwIhgtVeDzCeWOlTfKJS7EgggbIEY= +github.com/aws/aws-sdk-go v1.44.327/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= From 9e9daacc709ce00c3a4907e6141f116c2c415113 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 27 Aug 2023 14:40:55 +0000 Subject: [PATCH 1026/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.327 to 1.44.332 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.327 to 1.44.332. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.327...v1.44.332) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index bdb5fbf6..7759ac44 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.327 + github.com/aws/aws-sdk-go v1.44.332 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 461d6598..c65f3d48 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF0 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.327 h1:ZS8oO4+7MOBLhkdwIhgtVeDzCeWOlTfKJS7EgggbIEY= -github.com/aws/aws-sdk-go v1.44.327/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.332 h1:Ze+98F41+LxoJUdsisAFThV+0yYYLYw17/Vt0++nFYM= +github.com/aws/aws-sdk-go v1.44.332/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= From f5a67d00ae12f86d5be5a85e4b27591b5e6967b8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 3 Sep 2023 14:54:06 +0000 Subject: [PATCH 1027/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.44.332 to 1.45.2 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.332 to 1.45.2. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.332...v1.45.2) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 7759ac44..4376c3d1 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.44.332 + github.com/aws/aws-sdk-go v1.45.2 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index c65f3d48..8b0307e6 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF0 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.44.332 h1:Ze+98F41+LxoJUdsisAFThV+0yYYLYw17/Vt0++nFYM= -github.com/aws/aws-sdk-go v1.44.332/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.45.2 h1:hTong9YUklQKqzrGk3WnKABReb5R8GjbG4Y6dEQfjnk= +github.com/aws/aws-sdk-go v1.45.2/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= From c9ea23e9f3603de39c5a6ef9e485817f94330dd0 Mon Sep 17 00:00:00 2001 From: guangwu Date: Sun, 10 Sep 2023 16:27:02 +0800 Subject: [PATCH 1028/1127] fix: information typo (#825) --- docs/how_to/settings/existing_kube_context.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how_to/settings/existing_kube_context.md b/docs/how_to/settings/existing_kube_context.md index f3d8fcb0..9d6d8e9c 100644 --- a/docs/how_to/settings/existing_kube_context.md +++ b/docs/how_to/settings/existing_kube_context.md @@ -16,4 +16,4 @@ settings: kubeContext: "minikube" ``` -In the examples above, Helmsman tries to set the kube context to `minikube`. If that fails, it will attempt to create that kube context. Creating kube context requires more infromation provided. See [this guide](creating_kube_context_with_certs.md) for more details on creating a context with certs or [here](creating_kube_context_with_token.md) for details on creating context with bearer token. \ No newline at end of file +In the examples above, Helmsman tries to set the kube context to `minikube`. If that fails, it will attempt to create that kube context. Creating kube context requires more information provided. See [this guide](creating_kube_context_with_certs.md) for more details on creating a context with certs or [here](creating_kube_context_with_token.md) for details on creating context with bearer token. From 820c738588e181ef02b40af3287a084e2f0603ac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 10 Sep 2023 08:28:43 +0000 Subject: [PATCH 1029/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.45.2 to 1.45.6 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.45.2 to 1.45.6. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.45.2...v1.45.6) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 4376c3d1..c9ee02b7 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.45.2 + github.com/aws/aws-sdk-go v1.45.6 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 8b0307e6..7c90d53c 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF0 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.45.2 h1:hTong9YUklQKqzrGk3WnKABReb5R8GjbG4Y6dEQfjnk= -github.com/aws/aws-sdk-go v1.45.2/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.45.6 h1:Y2isQQBZsnO15dzUQo9YQRThtHgrV200XCH05BRHVJI= +github.com/aws/aws-sdk-go v1.45.6/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= From 7472446193155a0944f668f64590e54b25c04a93 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 10 Sep 2023 08:28:55 +0000 Subject: [PATCH 1030/1127] chore(deps): bump golang.org/x/net from 0.14.0 to 0.15.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.14.0 to 0.15.0. - [Commits](https://github.com/golang/net/compare/v0.14.0...v0.15.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 4376c3d1..9779039c 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/invopop/jsonschema v0.7.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.6.0 - golang.org/x/net v0.14.0 + golang.org/x/net v0.15.0 sigs.k8s.io/yaml v1.3.0 ) @@ -37,10 +37,10 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/mattn/go-ieproxy v0.0.1 // indirect go.opencensus.io v0.24.0 // indirect - golang.org/x/crypto v0.12.0 // indirect + golang.org/x/crypto v0.13.0 // indirect golang.org/x/oauth2 v0.10.0 // indirect - golang.org/x/sys v0.11.0 // indirect - golang.org/x/text v0.12.0 // indirect + golang.org/x/sys v0.12.0 // indirect + golang.org/x/text v0.13.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.132.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index 8b0307e6..725c7be5 100644 --- a/go.sum +++ b/go.sum @@ -150,8 +150,8 @@ golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -173,8 +173,8 @@ golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= @@ -197,8 +197,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -209,8 +209,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= From 40a30843e19f0446ffcebaee9e4c020d750d87fc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 10 Sep 2023 08:29:14 +0000 Subject: [PATCH 1031/1127] chore(deps): bump cloud.google.com/go/storage from 1.32.0 to 1.33.0 Bumps [cloud.google.com/go/storage](https://github.com/googleapis/google-cloud-go) from 1.32.0 to 1.33.0. - [Release notes](https://github.com/googleapis/google-cloud-go/releases) - [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/CHANGES.md) - [Commits](https://github.com/googleapis/google-cloud-go/compare/pubsub/v1.32.0...pubsub/v1.33.0) --- updated-dependencies: - dependency-name: cloud.google.com/go/storage dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 4376c3d1..40be15b5 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Praqma/helmsman go 1.18 require ( - cloud.google.com/go/storage v1.32.0 + cloud.google.com/go/storage v1.33.0 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.15.0 github.com/BurntSushi/toml v1.3.2 diff --git a/go.sum b/go.sum index 8b0307e6..024ca3a4 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,8 @@ cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGB cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/iam v1.1.0 h1:67gSqaPukx7O8WLLHMa0PNs3EBGd2eE4d+psbO/CO94= cloud.google.com/go/iam v1.1.0/go.mod h1:nxdHjaKfCr7fNYx/HJMM8LgiMugmveWlkatear5gVyk= -cloud.google.com/go/storage v1.32.0 h1:5w6DxEGOnktmJHarxAOUywxVW9lbNWIzlzzUltG/3+o= -cloud.google.com/go/storage v1.32.0/go.mod h1:Hhh/dogNRGca7IWv1RC2YqEn0c0G77ctA/OxflYkiD8= +cloud.google.com/go/storage v1.33.0 h1:PVrDOkIC8qQVa1P3SXGpQvfuJhN2LHOoyZvWs8D2X5M= +cloud.google.com/go/storage v1.33.0/go.mod h1:Hhh/dogNRGca7IWv1RC2YqEn0c0G77ctA/OxflYkiD8= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk= From a5ecde7a0a9b666b30bb2f11b255b7e15021f40d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 10 Sep 2023 09:34:28 +0000 Subject: [PATCH 1032/1127] chore(deps): bump github.com/invopop/jsonschema from 0.7.0 to 0.8.0 Bumps [github.com/invopop/jsonschema](https://github.com/invopop/jsonschema) from 0.7.0 to 0.8.0. - [Commits](https://github.com/invopop/jsonschema/compare/v0.7.0...v0.8.0) --- updated-dependencies: - dependency-name: github.com/invopop/jsonschema dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 67f4c52f..86e94578 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 github.com/aws/aws-sdk-go v1.45.6 github.com/imdario/mergo v0.3.16 - github.com/invopop/jsonschema v0.7.0 + github.com/invopop/jsonschema v0.8.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.6.0 golang.org/x/net v0.15.0 diff --git a/go.sum b/go.sum index 4ba6f171..65bc13c3 100644 --- a/go.sum +++ b/go.sum @@ -106,8 +106,8 @@ github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 h1:i462o439Z github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= -github.com/invopop/jsonschema v0.7.0 h1:2vgQcBz1n256N+FpX3Jq7Y17AjYt46Ig3zIWyy770So= -github.com/invopop/jsonschema v0.7.0/go.mod h1:O9uiLokuu0+MGFlyiaqtWxwqJm41/+8Nj0lD7A36YH0= +github.com/invopop/jsonschema v0.8.0 h1:9Vblm5uNqURXUSaX0QUYcI/Hcu5rrvOz5MbpWgw0VkM= +github.com/invopop/jsonschema v0.8.0/go.mod h1:O9uiLokuu0+MGFlyiaqtWxwqJm41/+8Nj0lD7A36YH0= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= From 01ebf208d42063922a3738b225e75852bedf4c03 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 17 Sep 2023 14:04:58 +0000 Subject: [PATCH 1033/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.45.6 to 1.45.11 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.45.6 to 1.45.11. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.45.6...v1.45.11) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b7a07e42..e8f1f64d 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.45.6 + github.com/aws/aws-sdk-go v1.45.11 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.8.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 61a995e0..f7368cba 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF0 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.45.6 h1:Y2isQQBZsnO15dzUQo9YQRThtHgrV200XCH05BRHVJI= -github.com/aws/aws-sdk-go v1.45.6/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.45.11 h1:8qiSrA12+NRr+2MVpMApi3JxtiFFjDVU1NeWe+80bYg= +github.com/aws/aws-sdk-go v1.45.11/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= From 32ff946fe3050e31ee2587a447da6621e72d2291 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 24 Sep 2023 14:57:47 +0000 Subject: [PATCH 1034/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.45.11 to 1.45.15 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.45.11 to 1.45.15. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.45.11...v1.45.15) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e8f1f64d..f6161171 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.45.11 + github.com/aws/aws-sdk-go v1.45.15 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.8.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index f7368cba..5b2b958f 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF0 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.45.11 h1:8qiSrA12+NRr+2MVpMApi3JxtiFFjDVU1NeWe+80bYg= -github.com/aws/aws-sdk-go v1.45.11/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.45.15 h1:gYBTVSYuhXdatrLbsPaRgVcc637zzdgThWmsDRwXLOo= +github.com/aws/aws-sdk-go v1.45.15/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= From e0ce115b05c706847591f8876cc422811ba65d2a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 24 Sep 2023 15:19:11 +0000 Subject: [PATCH 1035/1127] chore(deps): bump github.com/invopop/jsonschema from 0.8.0 to 0.9.0 Bumps [github.com/invopop/jsonschema](https://github.com/invopop/jsonschema) from 0.8.0 to 0.9.0. - [Commits](https://github.com/invopop/jsonschema/compare/v0.8.0...v0.9.0) --- updated-dependencies: - dependency-name: github.com/invopop/jsonschema dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++++-- go.sum | 16 +++++++++++----- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index f6161171..0db74888 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 github.com/aws/aws-sdk-go v1.45.15 github.com/imdario/mergo v0.3.16 - github.com/invopop/jsonschema v0.8.0 + github.com/invopop/jsonschema v0.9.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.6.0 golang.org/x/net v0.15.0 @@ -24,6 +24,8 @@ require ( cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/iam v1.1.0 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect + github.com/bahlo/generic-list-go v0.2.0 // indirect + github.com/buger/jsonparser v1.1.1 // indirect github.com/golang-jwt/jwt/v4 v4.3.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect @@ -32,10 +34,11 @@ require ( github.com/google/uuid v1.3.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect github.com/googleapis/gax-go/v2 v2.12.0 // indirect - github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/kr/text v0.2.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-ieproxy v0.0.1 // indirect + github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect go.opencensus.io v0.24.0 // indirect golang.org/x/crypto v0.13.0 // indirect golang.org/x/oauth2 v0.10.0 // indirect @@ -50,4 +53,5 @@ require ( google.golang.org/grpc v1.56.2 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 5b2b958f..c80a8a5c 100644 --- a/go.sum +++ b/go.sum @@ -36,6 +36,10 @@ github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjD github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= github.com/aws/aws-sdk-go v1.45.15 h1:gYBTVSYuhXdatrLbsPaRgVcc637zzdgThWmsDRwXLOo= github.com/aws/aws-sdk-go v1.45.15/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= +github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= +github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= @@ -102,16 +106,15 @@ github.com/googleapis/enterprise-certificate-proxy v0.2.5/go.mod h1:RxW0N9901Cko github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 h1:i462o439ZjprVSFSZLZxcsoAe592sZB1rci2Z8j4wdk= -github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= -github.com/invopop/jsonschema v0.8.0 h1:9Vblm5uNqURXUSaX0QUYcI/Hcu5rrvOz5MbpWgw0VkM= -github.com/invopop/jsonschema v0.8.0/go.mod h1:O9uiLokuu0+MGFlyiaqtWxwqJm41/+8Nj0lD7A36YH0= +github.com/invopop/jsonschema v0.9.0 h1:m1Fe5PN4X9V7P1TCF+pA8Xly3Vj3pY905klC++8oOpM= +github.com/invopop/jsonschema v0.9.0/go.mod h1:uMhbTEOXoPcOKzdYRfk914W6UTGA/cVcgEQxXh1MJ7g= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -120,6 +123,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI= github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -131,7 +136,6 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -140,6 +144,8 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= +github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= From 08bef9484be49cfce8cb6f7e2e82331c7fec6545 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Oct 2023 14:39:09 +0000 Subject: [PATCH 1036/1127] chore(deps): bump github.com/invopop/jsonschema from 0.9.0 to 0.10.0 Bumps [github.com/invopop/jsonschema](https://github.com/invopop/jsonschema) from 0.9.0 to 0.10.0. - [Commits](https://github.com/invopop/jsonschema/compare/v0.9.0...v0.10.0) --- updated-dependencies: - dependency-name: github.com/invopop/jsonschema dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0db74888..eec207f5 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 github.com/aws/aws-sdk-go v1.45.15 github.com/imdario/mergo v0.3.16 - github.com/invopop/jsonschema v0.9.0 + github.com/invopop/jsonschema v0.10.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.6.0 golang.org/x/net v0.15.0 diff --git a/go.sum b/go.sum index c80a8a5c..88a57c48 100644 --- a/go.sum +++ b/go.sum @@ -108,8 +108,8 @@ github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qK github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= -github.com/invopop/jsonschema v0.9.0 h1:m1Fe5PN4X9V7P1TCF+pA8Xly3Vj3pY905klC++8oOpM= -github.com/invopop/jsonschema v0.9.0/go.mod h1:uMhbTEOXoPcOKzdYRfk914W6UTGA/cVcgEQxXh1MJ7g= +github.com/invopop/jsonschema v0.10.0 h1:c1ktzNLBun3LyQQhyty5WE3lulbOdIIyOVlkmDLehcE= +github.com/invopop/jsonschema v0.10.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= From 519ea4355a074e8529af3b0a5eb0357058d62d26 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Oct 2023 19:11:11 +0000 Subject: [PATCH 1037/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.45.15 to 1.45.19 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.45.15 to 1.45.19. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.45.15...v1.45.19) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index eec207f5..9df4fe3c 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.45.15 + github.com/aws/aws-sdk-go v1.45.19 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.10.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 88a57c48..c3888335 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF0 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.45.15 h1:gYBTVSYuhXdatrLbsPaRgVcc637zzdgThWmsDRwXLOo= -github.com/aws/aws-sdk-go v1.45.15/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.45.19 h1:+4yXWhldhCVXWFOQRF99ZTJ92t4DtoHROZIbN7Ujk/U= +github.com/aws/aws-sdk-go v1.45.19/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From 89461a286ab1f0e64d98eded1c4bf8081f82acfc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 8 Oct 2023 14:44:42 +0000 Subject: [PATCH 1038/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.45.19 to 1.45.24 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.45.19 to 1.45.24. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.45.19...v1.45.24) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9df4fe3c..17caa642 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.45.19 + github.com/aws/aws-sdk-go v1.45.24 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.10.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index c3888335..4bf4d4ac 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF0 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.45.19 h1:+4yXWhldhCVXWFOQRF99ZTJ92t4DtoHROZIbN7Ujk/U= -github.com/aws/aws-sdk-go v1.45.19/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.45.24 h1:TZx/CizkmCQn8Rtsb11iLYutEQVGK5PK9wAhwouELBo= +github.com/aws/aws-sdk-go v1.45.24/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From 3a97ecd5129ea45418834d8e7f12ba8d4dc2c860 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 8 Oct 2023 14:44:48 +0000 Subject: [PATCH 1039/1127] chore(deps): bump golang.org/x/net from 0.15.0 to 0.16.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.15.0 to 0.16.0. - [Commits](https://github.com/golang/net/compare/v0.15.0...v0.16.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 9df4fe3c..3f26b880 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/invopop/jsonschema v0.10.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.6.0 - golang.org/x/net v0.15.0 + golang.org/x/net v0.16.0 sigs.k8s.io/yaml v1.3.0 ) @@ -40,9 +40,9 @@ require ( github.com/mattn/go-ieproxy v0.0.1 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect go.opencensus.io v0.24.0 // indirect - golang.org/x/crypto v0.13.0 // indirect + golang.org/x/crypto v0.14.0 // indirect golang.org/x/oauth2 v0.10.0 // indirect - golang.org/x/sys v0.12.0 // indirect + golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.132.0 // indirect diff --git a/go.sum b/go.sum index c3888335..effa176b 100644 --- a/go.sum +++ b/go.sum @@ -156,8 +156,8 @@ golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -179,8 +179,8 @@ golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos= +golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= @@ -203,8 +203,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= From 8c5ff94ec15b82d53cbb99077f7831f75ea8c91c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 8 Oct 2023 16:18:26 +0000 Subject: [PATCH 1040/1127] chore(deps): bump github.com/invopop/jsonschema from 0.10.0 to 0.12.0 Bumps [github.com/invopop/jsonschema](https://github.com/invopop/jsonschema) from 0.10.0 to 0.12.0. - [Commits](https://github.com/invopop/jsonschema/compare/v0.10.0...v0.12.0) --- updated-dependencies: - dependency-name: github.com/invopop/jsonschema dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a236eea5..c5845a14 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 github.com/aws/aws-sdk-go v1.45.24 github.com/imdario/mergo v0.3.16 - github.com/invopop/jsonschema v0.10.0 + github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.6.0 golang.org/x/net v0.16.0 diff --git a/go.sum b/go.sum index 526c4b90..a6db59c4 100644 --- a/go.sum +++ b/go.sum @@ -108,8 +108,8 @@ github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qK github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= -github.com/invopop/jsonschema v0.10.0 h1:c1ktzNLBun3LyQQhyty5WE3lulbOdIIyOVlkmDLehcE= -github.com/invopop/jsonschema v0.10.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= +github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI= +github.com/invopop/jsonschema v0.12.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= From b511c04e02c63be8c246ac0bd99c883a2c3d9d06 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Oct 2023 23:21:40 +0000 Subject: [PATCH 1041/1127] chore(deps): bump golang.org/x/net from 0.16.0 to 0.17.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.16.0 to 0.17.0. - [Commits](https://github.com/golang/net/compare/v0.16.0...v0.17.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c5845a14..23135af8 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.6.0 - golang.org/x/net v0.16.0 + golang.org/x/net v0.17.0 sigs.k8s.io/yaml v1.3.0 ) diff --git a/go.sum b/go.sum index a6db59c4..0290ca0e 100644 --- a/go.sum +++ b/go.sum @@ -179,8 +179,8 @@ golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos= -golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= From 4a0b2674679e11c300a1f0c6c551cbc1743a0f60 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 15 Oct 2023 20:43:23 +0000 Subject: [PATCH 1042/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.45.24 to 1.45.25 (#841) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 23135af8..28cfb451 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.45.24 + github.com/aws/aws-sdk-go v1.45.25 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 0290ca0e..bf882d1e 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF0 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.45.24 h1:TZx/CizkmCQn8Rtsb11iLYutEQVGK5PK9wAhwouELBo= -github.com/aws/aws-sdk-go v1.45.24/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.45.25 h1:c4fLlh5sLdK2DCRTY1z0hyuJZU4ygxX8m1FswL6/nF4= +github.com/aws/aws-sdk-go v1.45.25/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From cec4db07bab58b0f5f58da27cf709f439c9df064 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 22 Oct 2023 17:59:54 +0000 Subject: [PATCH 1043/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.45.25 to 1.46.1 (#842) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 28cfb451..fc1ca740 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.45.25 + github.com/aws/aws-sdk-go v1.46.1 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index bf882d1e..1122b0a3 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF0 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.45.25 h1:c4fLlh5sLdK2DCRTY1z0hyuJZU4ygxX8m1FswL6/nF4= -github.com/aws/aws-sdk-go v1.45.25/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.46.1 h1:U26quvBWFZMQuultLw5tloW4GnmWaChEwMZNq8uYatw= +github.com/aws/aws-sdk-go v1.46.1/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From 8209047d1ea160d039ffb13d364d919486f1c220 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Oct 2023 22:46:56 +0000 Subject: [PATCH 1044/1127] chore(deps): bump google.golang.org/grpc from 1.56.2 to 1.56.3 Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.56.2 to 1.56.3. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.56.2...v1.56.3) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index fc1ca740..c47d2724 100644 --- a/go.mod +++ b/go.mod @@ -50,7 +50,7 @@ require ( google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect - google.golang.org/grpc v1.56.2 // indirect + google.golang.org/grpc v1.56.3 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 1122b0a3..77d00cc2 100644 --- a/go.sum +++ b/go.sum @@ -253,8 +253,8 @@ google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTp google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= -google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI= -google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc= +google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= From 000c198aadff5e7118c15355788168c9c532a9ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 29 Oct 2023 16:29:41 +0000 Subject: [PATCH 1045/1127] chore(deps): bump sigs.k8s.io/yaml from 1.3.0 to 1.4.0 (#846) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c47d2724..69a1b1d3 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.6.0 golang.org/x/net v0.17.0 - sigs.k8s.io/yaml v1.3.0 + sigs.k8s.io/yaml v1.4.0 ) require ( diff --git a/go.sum b/go.sum index 77d00cc2..6742e6be 100644 --- a/go.sum +++ b/go.sum @@ -281,5 +281,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= From 63ef6c2fbadf9e7f51feab4dc57837b2ae0c3d4e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 29 Oct 2023 16:30:06 +0000 Subject: [PATCH 1046/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.46.1 to 1.46.6 (#845) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 69a1b1d3..193527a9 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.46.1 + github.com/aws/aws-sdk-go v1.46.6 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 6742e6be..7eecad37 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF0 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.46.1 h1:U26quvBWFZMQuultLw5tloW4GnmWaChEwMZNq8uYatw= -github.com/aws/aws-sdk-go v1.46.1/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.46.6 h1:6wFnNC9hETIZLMf6SOTN7IcclrOGwp/n9SLp8Pjt6E8= +github.com/aws/aws-sdk-go v1.46.6/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From 03498d0812fa28cc4d418f82b7efed4a09242e75 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 5 Nov 2023 17:00:52 +0000 Subject: [PATCH 1047/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.46.6 to 1.47.3 (#847) Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.46.6 to 1.47.3. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.46.6...v1.47.3) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 193527a9..cdfddfe2 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.46.6 + github.com/aws/aws-sdk-go v1.47.3 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 7eecad37..3dc422d2 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF0 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.46.6 h1:6wFnNC9hETIZLMf6SOTN7IcclrOGwp/n9SLp8Pjt6E8= -github.com/aws/aws-sdk-go v1.46.6/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.47.3 h1:e0H6NFXiniCpR8Lu3lTphVdRaeRCDLAeRyTHd1tJSd8= +github.com/aws/aws-sdk-go v1.47.3/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= @@ -178,7 +178,6 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -202,19 +201,16 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From 5aaab47a4f7291d73aaa720f49eb894c4668f5ee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 12 Nov 2023 13:18:05 +0000 Subject: [PATCH 1048/1127] chore(deps): bump cloud.google.com/go/storage from 1.33.0 to 1.34.1 (#848) --- Dockerfile | 4 +- go.mod | 30 +++++++-------- go.sum | 105 ++++++++++++++++------------------------------------- 3 files changed, 48 insertions(+), 91 deletions(-) diff --git a/Dockerfile b/Dockerfile index 683a1923..d4f9d725 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ -ARG GO_VERSION="1.18.5" -ARG ALPINE_VERSION="3.16" +ARG GO_VERSION="1.21.4" +ARG ALPINE_VERSION="3.18" ARG GLOBAL_KUBE_VERSION="v1.24.10" ARG GLOBAL_HELM_VERSION="v3.11.0" ARG GLOBAL_HELM_DIFF_VERSION="v3.6.0" diff --git a/go.mod b/go.mod index cdfddfe2..1ea59049 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,9 @@ module github.com/Praqma/helmsman -go 1.18 +go 1.21 require ( - cloud.google.com/go/storage v1.33.0 + cloud.google.com/go/storage v1.34.1 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.15.0 github.com/BurntSushi/toml v1.3.2 @@ -19,20 +19,19 @@ require ( ) require ( - cloud.google.com/go v0.110.4 // indirect - cloud.google.com/go/compute v1.20.1 // indirect + cloud.google.com/go v0.110.8 // indirect + cloud.google.com/go/compute v1.23.1 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v1.1.0 // indirect + cloud.google.com/go/iam v1.1.3 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/buger/jsonparser v1.1.1 // indirect github.com/golang-jwt/jwt/v4 v4.3.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/google/go-cmp v0.5.9 // indirect - github.com/google/s2a-go v0.1.4 // indirect - github.com/google/uuid v1.3.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect + github.com/google/s2a-go v0.1.7 // indirect + github.com/google/uuid v1.4.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/kr/text v0.2.0 // indirect @@ -41,16 +40,17 @@ require ( github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect go.opencensus.io v0.24.0 // indirect golang.org/x/crypto v0.14.0 // indirect - golang.org/x/oauth2 v0.10.0 // indirect + golang.org/x/oauth2 v0.13.0 // indirect + golang.org/x/sync v0.4.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/api v0.132.0 // indirect + google.golang.org/api v0.149.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect - google.golang.org/grpc v1.56.3 // indirect + google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect + google.golang.org/grpc v1.59.0 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 3dc422d2..6d1ce7c9 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,14 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.110.4 h1:1JYyxKMN9hd5dR2MYTPWkGUgcoxVVhg0LKNKEo0qvmk= -cloud.google.com/go v0.110.4/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI= -cloud.google.com/go/compute v1.20.1 h1:6aKEtlUiwEpJzM001l0yFkpXmUVXaN8W+fbkb2AZNbg= -cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go v0.110.8 h1:tyNdfIxjzaWctIiLYOTalaLKZ17SI44SKFW26QbOhME= +cloud.google.com/go v0.110.8/go.mod h1:Iz8AkXJf1qmxC3Oxoep8R1T36w8B92yU29PcBhHO5fk= +cloud.google.com/go/compute v1.23.1 h1:V97tBoDaZHb6leicZ1G6DLK2BAaZLJ/7+9BB/En3hR0= +cloud.google.com/go/compute v1.23.1/go.mod h1:CqB3xpmPKKt3OJpW2ndFIXnA9A4xAy/F3Xp1ixncW78= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/iam v1.1.0 h1:67gSqaPukx7O8WLLHMa0PNs3EBGd2eE4d+psbO/CO94= -cloud.google.com/go/iam v1.1.0/go.mod h1:nxdHjaKfCr7fNYx/HJMM8LgiMugmveWlkatear5gVyk= -cloud.google.com/go/storage v1.33.0 h1:PVrDOkIC8qQVa1P3SXGpQvfuJhN2LHOoyZvWs8D2X5M= -cloud.google.com/go/storage v1.33.0/go.mod h1:Hhh/dogNRGca7IWv1RC2YqEn0c0G77ctA/OxflYkiD8= +cloud.google.com/go/iam v1.1.3 h1:18tKG7DzydKWUnLjonWcJO6wjSCAtzh4GcRKlH/Hrzc= +cloud.google.com/go/iam v1.1.3/go.mod h1:3khUlaBXfPKKe7huYgEpDn6FtgRyMEqbkvBxrQyY5SE= +cloud.google.com/go/storage v1.34.1 h1:H2Af2dU5J0PF7A5B+ECFIce+RqxVnrVilO+cu0TS3MI= +cloud.google.com/go/storage v1.34.1/go.mod h1:VN1ElqqvR9adg1k9xlkUJ55cMOP1/QjnNNuT5xQL6dY= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk= @@ -31,7 +30,6 @@ github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8 github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= github.com/aws/aws-sdk-go v1.47.3 h1:e0H6NFXiniCpR8Lu3lTphVdRaeRCDLAeRyTHd1tJSd8= @@ -41,14 +39,8 @@ github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xW github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -56,11 +48,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog= github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= @@ -72,17 +61,14 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -92,20 +78,21 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= -github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= -github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= +github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.2.5 h1:UR4rDjcgpgEnqpIEvkiqTYKBCKLNmlge2eVjoZfySzM= -github.com/googleapis/enterprise-certificate-proxy v0.2.5/go.mod h1:RxW0N9901Cko1VOCW3SXCpWP+mlIEkk2tP7jnHy9a3w= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI= @@ -132,12 +119,9 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= @@ -146,71 +130,54 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= -golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= +golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= +golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -218,39 +185,31 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -google.golang.org/api v0.132.0 h1:8t2/+qZ26kAOGSmOiHwVycqVaDg7q3JDILrNi/Z6rvc= -google.golang.org/api v0.132.0/go.mod h1:AeTBC6GpJnJSRJjktDcPX0QwtS8pGYZOV6MSuSCusw0= +google.golang.org/api v0.149.0 h1:b2CqT6kG+zqJIVKRQ3ELJVLN1PwHZ6DJ3dW8yl82rgY= +google.golang.org/api v0.149.0/go.mod h1:Mwn1B7JTXrzXtnvmzQE2BD6bYZQ8DShKZDZbeN9I7qI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 h1:Au6te5hbKUV8pIYWHqOUZ1pva5qK/rwbIhoXEUB9Lu8= -google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:O9kGHb51iE/nOGvQaDUuadVYqovW56s5emA88lQnj6Y= -google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130 h1:XVeBY8d/FaK4848myy41HBqnDwvxeV3zMZhwN1TvAMU= -google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:mPBs5jNgx2GuQGvFwUvVKqtn6HsUw9nP64BedgvqEsQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= +google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b h1:+YaDE2r2OG8t/z5qmsh7Y+XXwCbvadxxZ0YY6mTdrVA= +google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:CgAqfJo+Xmu0GwA0411Ht3OU3OntXwsGmrmjI8ioGXI= +google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b h1:CIC2YMXmIhYw6evmhPxBKJ4fmLbOFtXQN/GV3XOZR8k= +google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:IBQ646DjkDkvUIsVq/cc03FUFQ9wbZu7yE396YcL870= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b h1:ZlWIi1wSK56/8hn4QcBp/j9M7Gt3U/3hZw3mC7vDICo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= -google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc= -google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= +google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -267,8 +226,6 @@ google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= From abffe14087cffc725c8e4e853a1e1ff52ebc55ef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 12 Nov 2023 16:11:46 +0000 Subject: [PATCH 1049/1127] chore(deps): bump cloud.google.com/go/storage from 1.34.1 to 1.35.1 (#851) --- go.mod | 9 +++++---- go.sum | 18 ++++++++++-------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 1ea59049..e6d7d7bc 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Praqma/helmsman go 1.21 require ( - cloud.google.com/go/storage v1.34.1 + cloud.google.com/go/storage v1.35.1 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.15.0 github.com/BurntSushi/toml v1.3.2 @@ -41,15 +41,16 @@ require ( go.opencensus.io v0.24.0 // indirect golang.org/x/crypto v0.14.0 // indirect golang.org/x/oauth2 v0.13.0 // indirect - golang.org/x/sync v0.4.0 // indirect + golang.org/x/sync v0.5.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect + golang.org/x/time v0.3.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/api v0.149.0 // indirect + google.golang.org/api v0.150.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b // indirect google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 // indirect google.golang.org/grpc v1.59.0 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 6d1ce7c9..c7676a0b 100644 --- a/go.sum +++ b/go.sum @@ -7,8 +7,8 @@ cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGB cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/iam v1.1.3 h1:18tKG7DzydKWUnLjonWcJO6wjSCAtzh4GcRKlH/Hrzc= cloud.google.com/go/iam v1.1.3/go.mod h1:3khUlaBXfPKKe7huYgEpDn6FtgRyMEqbkvBxrQyY5SE= -cloud.google.com/go/storage v1.34.1 h1:H2Af2dU5J0PF7A5B+ECFIce+RqxVnrVilO+cu0TS3MI= -cloud.google.com/go/storage v1.34.1/go.mod h1:VN1ElqqvR9adg1k9xlkUJ55cMOP1/QjnNNuT5xQL6dY= +cloud.google.com/go/storage v1.35.1 h1:B59ahL//eDfx2IIKFBeT5Atm9wnNmj3+8xG/W4WB//w= +cloud.google.com/go/storage v1.35.1/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk= @@ -161,8 +161,8 @@ golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= -golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -180,6 +180,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -188,8 +190,8 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -google.golang.org/api v0.149.0 h1:b2CqT6kG+zqJIVKRQ3ELJVLN1PwHZ6DJ3dW8yl82rgY= -google.golang.org/api v0.149.0/go.mod h1:Mwn1B7JTXrzXtnvmzQE2BD6bYZQ8DShKZDZbeN9I7qI= +google.golang.org/api v0.150.0 h1:Z9k22qD289SZ8gCJrk4DrWXkNjtfvKAUo/l1ma8eBYE= +google.golang.org/api v0.150.0/go.mod h1:ccy+MJ6nrYFgE3WgRx/AMXOxOmU8Q4hSa+jjibzhxcg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= @@ -201,8 +203,8 @@ google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b h1:+YaDE2r2OG8t/z5 google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:CgAqfJo+Xmu0GwA0411Ht3OU3OntXwsGmrmjI8ioGXI= google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b h1:CIC2YMXmIhYw6evmhPxBKJ4fmLbOFtXQN/GV3XOZR8k= google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:IBQ646DjkDkvUIsVq/cc03FUFQ9wbZu7yE396YcL870= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b h1:ZlWIi1wSK56/8hn4QcBp/j9M7Gt3U/3hZw3mC7vDICo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 h1:AB/lmRny7e2pLhFEYIbl5qkDAUt2h0ZRO4wGPhZf+ik= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405/go.mod h1:67X1fPuzjcrkymZzZV1vvkFeTn2Rvc6lYF9MYFGCcwE= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= From e13aa67343607d84269bae1cb3865db089c93364 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 12 Nov 2023 16:12:40 +0000 Subject: [PATCH 1050/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.47.3 to 1.47.9 (#849) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e6d7d7bc..b5bb86af 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.47.3 + github.com/aws/aws-sdk-go v1.47.9 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index c7676a0b..ac699ab2 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.47.3 h1:e0H6NFXiniCpR8Lu3lTphVdRaeRCDLAeRyTHd1tJSd8= -github.com/aws/aws-sdk-go v1.47.3/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.47.9 h1:rarTsos0mA16q+huicGx0e560aYRtOucV5z2Mw23JRY= +github.com/aws/aws-sdk-go v1.47.9/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From 97dfccb1c5ca4aec7073874ec005b0bf8295de4a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 12 Nov 2023 16:17:54 +0000 Subject: [PATCH 1051/1127] chore(deps): bump golang.org/x/net from 0.17.0 to 0.18.0 (#850) --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index b5bb86af..6468a5f4 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.6.0 - golang.org/x/net v0.17.0 + golang.org/x/net v0.18.0 sigs.k8s.io/yaml v1.4.0 ) @@ -39,11 +39,11 @@ require ( github.com/mattn/go-ieproxy v0.0.1 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect go.opencensus.io v0.24.0 // indirect - golang.org/x/crypto v0.14.0 // indirect + golang.org/x/crypto v0.15.0 // indirect golang.org/x/oauth2 v0.13.0 // indirect golang.org/x/sync v0.5.0 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect + golang.org/x/sys v0.14.0 // indirect + golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.150.0 // indirect diff --git a/go.sum b/go.sum index ac699ab2..b6c6c212 100644 --- a/go.sum +++ b/go.sum @@ -137,8 +137,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= +golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -153,8 +153,8 @@ golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= @@ -171,15 +171,15 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From 2dccc81476a88d7089f2e9fd9764c1dd41e524cd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 19 Nov 2023 18:45:23 +0000 Subject: [PATCH 1052/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.47.9 to 1.48.0 (#852) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6468a5f4..6d2e8479 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.47.9 + github.com/aws/aws-sdk-go v1.48.0 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index b6c6c212..1e16fe79 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.47.9 h1:rarTsos0mA16q+huicGx0e560aYRtOucV5z2Mw23JRY= -github.com/aws/aws-sdk-go v1.47.9/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.48.0 h1:1SeJ8agckRDQvnSCt1dGZYAwUaoD2Ixj6IaXB4LCv8Q= +github.com/aws/aws-sdk-go v1.48.0/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From d8213cda4ac46d2e4dabdf5cec0434d4f0f98566 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 26 Nov 2023 23:15:45 +0000 Subject: [PATCH 1053/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.48.0 to 1.48.3 (#854) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6d2e8479..613323a7 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.48.0 + github.com/aws/aws-sdk-go v1.48.3 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 1e16fe79..819ee76d 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.48.0 h1:1SeJ8agckRDQvnSCt1dGZYAwUaoD2Ixj6IaXB4LCv8Q= -github.com/aws/aws-sdk-go v1.48.0/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.48.3 h1:btYjT+opVFxUbRz+qSCjJe07cdX82BHmMX/FXYmoL7g= +github.com/aws/aws-sdk-go v1.48.3/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From eab418fab5a0aac97155e07ba85a27989c8ceac1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 3 Dec 2023 23:22:56 +0000 Subject: [PATCH 1054/1127] chore(deps): bump golang.org/x/net from 0.18.0 to 0.19.0 (#856) --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 613323a7..35f255cf 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.6.0 - golang.org/x/net v0.18.0 + golang.org/x/net v0.19.0 sigs.k8s.io/yaml v1.4.0 ) @@ -39,10 +39,10 @@ require ( github.com/mattn/go-ieproxy v0.0.1 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect go.opencensus.io v0.24.0 // indirect - golang.org/x/crypto v0.15.0 // indirect + golang.org/x/crypto v0.16.0 // indirect golang.org/x/oauth2 v0.13.0 // indirect golang.org/x/sync v0.5.0 // indirect - golang.org/x/sys v0.14.0 // indirect + golang.org/x/sys v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect diff --git a/go.sum b/go.sum index 819ee76d..e0e30e4f 100644 --- a/go.sum +++ b/go.sum @@ -137,8 +137,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= -golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= +golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -153,8 +153,8 @@ golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= -golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= @@ -171,8 +171,8 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= From e2ca88eb995e9ec97c90b9d50bfaa9df5e06c271 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 3 Dec 2023 23:23:25 +0000 Subject: [PATCH 1055/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.48.3 to 1.48.11 (#855) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 35f255cf..0a644a35 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.48.3 + github.com/aws/aws-sdk-go v1.48.11 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index e0e30e4f..0b3986d3 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.48.3 h1:btYjT+opVFxUbRz+qSCjJe07cdX82BHmMX/FXYmoL7g= -github.com/aws/aws-sdk-go v1.48.3/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.48.11 h1:9YbiSbaF/jWi+qLRl+J5dEhr2mcbDYHmKg2V7RBcD5M= +github.com/aws/aws-sdk-go v1.48.11/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From c9ad3a8e379b37f2af6c1d7e1614b057cf95d37d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 10 Dec 2023 17:29:56 +0000 Subject: [PATCH 1056/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.48.11 to 1.48.16 (#857) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0a644a35..dca9c56f 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.48.11 + github.com/aws/aws-sdk-go v1.48.16 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 0b3986d3..02efb15b 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.48.11 h1:9YbiSbaF/jWi+qLRl+J5dEhr2mcbDYHmKg2V7RBcD5M= -github.com/aws/aws-sdk-go v1.48.11/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.48.16 h1:mcj2/9J/MJ55Dov+ocMevhR8Jv6jW/fAxbrn4a1JFc8= +github.com/aws/aws-sdk-go v1.48.16/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From f0c9ac0d871c8974341d7c1bfac95ac6a495c560 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 24 Dec 2023 00:33:09 +0000 Subject: [PATCH 1057/1127] chore(deps): bump golang.org/x/crypto from 0.16.0 to 0.17.0 (#860) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index dca9c56f..11760e96 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,7 @@ require ( github.com/mattn/go-ieproxy v0.0.1 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect go.opencensus.io v0.24.0 // indirect - golang.org/x/crypto v0.16.0 // indirect + golang.org/x/crypto v0.17.0 // indirect golang.org/x/oauth2 v0.13.0 // indirect golang.org/x/sync v0.5.0 // indirect golang.org/x/sys v0.15.0 // indirect diff --git a/go.sum b/go.sum index 02efb15b..c26404b7 100644 --- a/go.sum +++ b/go.sum @@ -137,8 +137,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= -golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= From a675517908de5108fd8fa6735789912d3f065fa0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 24 Dec 2023 00:33:25 +0000 Subject: [PATCH 1058/1127] chore(deps): bump cloud.google.com/go/storage from 1.35.1 to 1.36.0 (#859) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 11760e96..82331ff9 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Praqma/helmsman go 1.21 require ( - cloud.google.com/go/storage v1.35.1 + cloud.google.com/go/storage v1.36.0 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.15.0 github.com/BurntSushi/toml v1.3.2 diff --git a/go.sum b/go.sum index c26404b7..33237b2a 100644 --- a/go.sum +++ b/go.sum @@ -7,8 +7,8 @@ cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGB cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/iam v1.1.3 h1:18tKG7DzydKWUnLjonWcJO6wjSCAtzh4GcRKlH/Hrzc= cloud.google.com/go/iam v1.1.3/go.mod h1:3khUlaBXfPKKe7huYgEpDn6FtgRyMEqbkvBxrQyY5SE= -cloud.google.com/go/storage v1.35.1 h1:B59ahL//eDfx2IIKFBeT5Atm9wnNmj3+8xG/W4WB//w= -cloud.google.com/go/storage v1.35.1/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= +cloud.google.com/go/storage v1.36.0 h1:P0mOkAcaJxhCTvAkMhxMfrTKiNcub4YmmPBtlhAyTr8= +cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk= From 8168b0bebdf104d4e9eb19851687f3e9351dec17 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 24 Dec 2023 00:33:39 +0000 Subject: [PATCH 1059/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.48.16 to 1.49.4 (#858) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 82331ff9..f7db715b 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.48.16 + github.com/aws/aws-sdk-go v1.49.4 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 33237b2a..bfcc81a4 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.48.16 h1:mcj2/9J/MJ55Dov+ocMevhR8Jv6jW/fAxbrn4a1JFc8= -github.com/aws/aws-sdk-go v1.48.16/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.49.4 h1:qiXsqEeLLhdLgUIyfr5ot+N/dGPWALmtM1SetRmbUlY= +github.com/aws/aws-sdk-go v1.49.4/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From 312e23c369f42c9bd0e3824193869521daf035f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 24 Dec 2023 14:15:52 +0000 Subject: [PATCH 1060/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.49.4 to 1.49.9 (#861) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f7db715b..ab55a309 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.49.4 + github.com/aws/aws-sdk-go v1.49.9 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index bfcc81a4..94b85303 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.49.4 h1:qiXsqEeLLhdLgUIyfr5ot+N/dGPWALmtM1SetRmbUlY= -github.com/aws/aws-sdk-go v1.49.4/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.49.9 h1:4xoyi707rsifB1yMsd5vGbAH21aBzwpL3gNRMSmjIyc= +github.com/aws/aws-sdk-go v1.49.9/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From b334572e9bc315d185a3ebfbf4b7d3030edf7e34 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jan 2024 00:05:10 +0000 Subject: [PATCH 1061/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.49.9 to 1.49.13 (#862) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ab55a309..4a49ca37 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.49.9 + github.com/aws/aws-sdk-go v1.49.13 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 94b85303..32e08361 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.49.9 h1:4xoyi707rsifB1yMsd5vGbAH21aBzwpL3gNRMSmjIyc= -github.com/aws/aws-sdk-go v1.49.9/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.49.13 h1:f4mGztsgnx2dR9r8FQYa9YW/RsKb+N7bgef4UGrOW1Y= +github.com/aws/aws-sdk-go v1.49.13/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From b4c8f0808a3c31918d115ac6d0db0433c4f3e47d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Jan 2024 22:52:11 +0000 Subject: [PATCH 1062/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.49.13 to 1.49.16 (#863) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 4a49ca37..1da29995 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.49.13 + github.com/aws/aws-sdk-go v1.49.16 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 32e08361..0afe5caf 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.49.13 h1:f4mGztsgnx2dR9r8FQYa9YW/RsKb+N7bgef4UGrOW1Y= -github.com/aws/aws-sdk-go v1.49.13/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.49.16 h1:KAQwhLg296hfffRdh+itA9p7Nx/3cXS/qOa3uF9ssig= +github.com/aws/aws-sdk-go v1.49.16/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From 2d79cc7a433bfb8ba9a8356486668ad25d91cf06 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Jan 2024 22:37:52 +0000 Subject: [PATCH 1063/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.49.16 to 1.49.21 (#865) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 1da29995..61990aef 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.49.16 + github.com/aws/aws-sdk-go v1.49.21 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 0afe5caf..c74f647b 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.49.16 h1:KAQwhLg296hfffRdh+itA9p7Nx/3cXS/qOa3uF9ssig= -github.com/aws/aws-sdk-go v1.49.16/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.49.21 h1:Rl8KW6HqkwzhATwvXhyr7vD4JFUMi7oXGAw9SrxxIFY= +github.com/aws/aws-sdk-go v1.49.21/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From d46f8860f2ac74fedf148e2cd13beb279c163ffd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Jan 2024 22:38:07 +0000 Subject: [PATCH 1064/1127] chore(deps): bump golang.org/x/net from 0.19.0 to 0.20.0 (#864) --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 61990aef..85b7055b 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.6.0 - golang.org/x/net v0.19.0 + golang.org/x/net v0.20.0 sigs.k8s.io/yaml v1.4.0 ) @@ -39,10 +39,10 @@ require ( github.com/mattn/go-ieproxy v0.0.1 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect go.opencensus.io v0.24.0 // indirect - golang.org/x/crypto v0.17.0 // indirect + golang.org/x/crypto v0.18.0 // indirect golang.org/x/oauth2 v0.13.0 // indirect golang.org/x/sync v0.5.0 // indirect - golang.org/x/sys v0.15.0 // indirect + golang.org/x/sys v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect diff --git a/go.sum b/go.sum index c74f647b..0bbde90c 100644 --- a/go.sum +++ b/go.sum @@ -137,8 +137,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -153,8 +153,8 @@ golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= @@ -171,8 +171,8 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= From 8da8cde61df6a4c743158ffb2d847ab8ddfe48e2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 21:06:10 +0000 Subject: [PATCH 1065/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.49.21 to 1.50.0 (#866) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 85b7055b..4d230d2b 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.49.21 + github.com/aws/aws-sdk-go v1.50.0 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 0bbde90c..ed88225b 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.49.21 h1:Rl8KW6HqkwzhATwvXhyr7vD4JFUMi7oXGAw9SrxxIFY= -github.com/aws/aws-sdk-go v1.49.21/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.50.0 h1:HBtrLeO+QyDKnc3t1+5DR1RxodOHCGr8ZcrHudpv7jI= +github.com/aws/aws-sdk-go v1.50.0/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From 7b2532f20d60d42597b51a5d8b6003b76ab778d3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 28 Jan 2024 18:18:23 +0000 Subject: [PATCH 1066/1127] chore(deps): bump cloud.google.com/go/storage from 1.36.0 to 1.37.0 (#868) --- go.mod | 39 ++++++++++++--------- go.sum | 106 ++++++++++++++++++++++++++++++++++++++------------------- 2 files changed, 94 insertions(+), 51 deletions(-) diff --git a/go.mod b/go.mod index 4d230d2b..859dcfea 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Praqma/helmsman go 1.21 require ( - cloud.google.com/go/storage v1.36.0 + cloud.google.com/go/storage v1.37.0 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.15.0 github.com/BurntSushi/toml v1.3.2 @@ -19,18 +19,21 @@ require ( ) require ( - cloud.google.com/go v0.110.8 // indirect - cloud.google.com/go/compute v1.23.1 // indirect + cloud.google.com/go v0.112.0 // indirect + cloud.google.com/go/compute v1.23.3 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v1.1.3 // indirect + cloud.google.com/go/iam v1.1.5 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/buger/jsonparser v1.1.1 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.3.0 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/golang-jwt/jwt/v4 v4.3.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/s2a-go v0.1.7 // indirect - github.com/google/uuid v1.4.0 // indirect + github.com/google/uuid v1.5.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect @@ -39,20 +42,24 @@ require ( github.com/mattn/go-ieproxy v0.0.1 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect + go.opentelemetry.io/otel v1.21.0 // indirect + go.opentelemetry.io/otel/metric v1.21.0 // indirect + go.opentelemetry.io/otel/trace v1.21.0 // indirect golang.org/x/crypto v0.18.0 // indirect - golang.org/x/oauth2 v0.13.0 // indirect - golang.org/x/sync v0.5.0 // indirect + golang.org/x/oauth2 v0.16.0 // indirect + golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect - golang.org/x/time v0.3.0 // indirect - golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/api v0.150.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 // indirect - google.golang.org/grpc v1.59.0 // indirect - google.golang.org/protobuf v1.31.0 // indirect + golang.org/x/time v0.5.0 // indirect + google.golang.org/api v0.157.0 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240122161410-6c6643bf1457 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac // indirect + google.golang.org/grpc v1.60.1 // indirect + google.golang.org/protobuf v1.32.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index ed88225b..ee25b1cd 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,14 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.110.8 h1:tyNdfIxjzaWctIiLYOTalaLKZ17SI44SKFW26QbOhME= -cloud.google.com/go v0.110.8/go.mod h1:Iz8AkXJf1qmxC3Oxoep8R1T36w8B92yU29PcBhHO5fk= -cloud.google.com/go/compute v1.23.1 h1:V97tBoDaZHb6leicZ1G6DLK2BAaZLJ/7+9BB/En3hR0= -cloud.google.com/go/compute v1.23.1/go.mod h1:CqB3xpmPKKt3OJpW2ndFIXnA9A4xAy/F3Xp1ixncW78= +cloud.google.com/go v0.112.0 h1:tpFCD7hpHFlQ8yPwT3x+QeXqc2T6+n6T+hmABHfDUSM= +cloud.google.com/go v0.112.0/go.mod h1:3jEEVwZ/MHU4djK5t5RHuKOA/GbLddgTdVubX1qnPD4= +cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= +cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/iam v1.1.3 h1:18tKG7DzydKWUnLjonWcJO6wjSCAtzh4GcRKlH/Hrzc= -cloud.google.com/go/iam v1.1.3/go.mod h1:3khUlaBXfPKKe7huYgEpDn6FtgRyMEqbkvBxrQyY5SE= -cloud.google.com/go/storage v1.36.0 h1:P0mOkAcaJxhCTvAkMhxMfrTKiNcub4YmmPBtlhAyTr8= -cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= +cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI= +cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= +cloud.google.com/go/storage v1.37.0 h1:WI8CsaFO8Q9KjPVtsZ5Cmi0dXV25zMoX0FklT7c3Jm4= +cloud.google.com/go/storage v1.37.0/go.mod h1:i34TiT2IhiNDmcj65PqwCjcoUX7Z5pLzS8DEmoiFq1k= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk= @@ -41,6 +41,8 @@ github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx2 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -49,7 +51,16 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= +github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog= github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= @@ -59,7 +70,6 @@ github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -69,6 +79,7 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -87,8 +98,8 @@ github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= -github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= @@ -124,14 +135,28 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 h1:SpGay3w+nEwMpfVnbqOLH5gY52/foP8RE8UzTZ1pdSE= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= +go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= +go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= +go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= +go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= +go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= +go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= +go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= +go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -143,26 +168,29 @@ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= -golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= +golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= +golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -171,47 +199,55 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -google.golang.org/api v0.150.0 h1:Z9k22qD289SZ8gCJrk4DrWXkNjtfvKAUo/l1ma8eBYE= -google.golang.org/api v0.150.0/go.mod h1:ccy+MJ6nrYFgE3WgRx/AMXOxOmU8Q4hSa+jjibzhxcg= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +google.golang.org/api v0.157.0 h1:ORAeqmbrrozeyw5NjnMxh7peHO0UzV4wWYSwZeCUb20= +google.golang.org/api v0.157.0/go.mod h1:+z4v4ufbZ1WEpld6yMGHyggs+PmAHiaLNj5ytP3N01g= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b h1:+YaDE2r2OG8t/z5qmsh7Y+XXwCbvadxxZ0YY6mTdrVA= -google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:CgAqfJo+Xmu0GwA0411Ht3OU3OntXwsGmrmjI8ioGXI= -google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b h1:CIC2YMXmIhYw6evmhPxBKJ4fmLbOFtXQN/GV3XOZR8k= -google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:IBQ646DjkDkvUIsVq/cc03FUFQ9wbZu7yE396YcL870= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 h1:AB/lmRny7e2pLhFEYIbl5qkDAUt2h0ZRO4wGPhZf+ik= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405/go.mod h1:67X1fPuzjcrkymZzZV1vvkFeTn2Rvc6lYF9MYFGCcwE= +google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac h1:ZL/Teoy/ZGnzyrqK/Optxxp2pmVh+fmJ97slxSRyzUg= +google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:+Rvu7ElI+aLzyDQhpHMFMMltsD6m7nqpuWDd2CwJw3k= +google.golang.org/genproto/googleapis/api v0.0.0-20240122161410-6c6643bf1457 h1:KHBtwE+eQc3+NxpjmRFlQ3pJQ2FNnhhgB9xOV8kyBuU= +google.golang.org/genproto/googleapis/api v0.0.0-20240122161410-6c6643bf1457/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac h1:nUQEQmH/csSvFECKYRv6HWEyypysidKl2I6Qpsglq/0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= -google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= +google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -223,8 +259,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From b098934432763d6a3a07d8c9c0423f603cff127f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 28 Jan 2024 18:18:51 +0000 Subject: [PATCH 1067/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.50.0 to 1.50.5 (#867) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 859dcfea..795307f0 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.50.0 + github.com/aws/aws-sdk-go v1.50.5 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index ee25b1cd..9cdcf124 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.50.0 h1:HBtrLeO+QyDKnc3t1+5DR1RxodOHCGr8ZcrHudpv7jI= -github.com/aws/aws-sdk-go v1.50.0/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.50.5 h1:H2Aadcgwr7a2aqS6ZwcE+l1mA6ZrTseYCvjw2QLmxIA= +github.com/aws/aws-sdk-go v1.50.5/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From ddd7f59cbe4c91d651ba6eb1c3fe094973ab4894 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 4 Feb 2024 19:02:07 +0000 Subject: [PATCH 1068/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.50.5 to 1.50.10 (#869) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 795307f0..0a159ad9 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.50.5 + github.com/aws/aws-sdk-go v1.50.10 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 9cdcf124..387b748b 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.50.5 h1:H2Aadcgwr7a2aqS6ZwcE+l1mA6ZrTseYCvjw2QLmxIA= -github.com/aws/aws-sdk-go v1.50.5/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.50.10 h1:H3NQvqRUKG+9oysCKTIyylpkqfPA7MiBtzTnu/cIGqE= +github.com/aws/aws-sdk-go v1.50.10/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From 28682dacae9c545c44102a695210442e10ee97ee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 11 Feb 2024 18:34:40 +0000 Subject: [PATCH 1069/1127] chore(deps): bump golang.org/x/net from 0.20.0 to 0.21.0 (#870) --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 0a159ad9..3e263535 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.6.0 - golang.org/x/net v0.20.0 + golang.org/x/net v0.21.0 sigs.k8s.io/yaml v1.4.0 ) @@ -47,10 +47,10 @@ require ( go.opentelemetry.io/otel v1.21.0 // indirect go.opentelemetry.io/otel/metric v1.21.0 // indirect go.opentelemetry.io/otel/trace v1.21.0 // indirect - golang.org/x/crypto v0.18.0 // indirect + golang.org/x/crypto v0.19.0 // indirect golang.org/x/oauth2 v0.16.0 // indirect golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.16.0 // indirect + golang.org/x/sys v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect google.golang.org/api v0.157.0 // indirect diff --git a/go.sum b/go.sum index 387b748b..daff3712 100644 --- a/go.sum +++ b/go.sum @@ -162,8 +162,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -180,8 +180,8 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= @@ -201,8 +201,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From 0550eadd5471cc29a629b5515d0d7a3a54aef82d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 11 Feb 2024 18:34:51 +0000 Subject: [PATCH 1070/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.50.10 to 1.50.15 (#871) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 3e263535..0e37f526 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.50.10 + github.com/aws/aws-sdk-go v1.50.15 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index daff3712..531689e4 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.50.10 h1:H3NQvqRUKG+9oysCKTIyylpkqfPA7MiBtzTnu/cIGqE= -github.com/aws/aws-sdk-go v1.50.10/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.50.15 h1:wEMnPfEQQFaoIJwuO18zq/vtG4Ft7NxQ3r9xlEi/8zg= +github.com/aws/aws-sdk-go v1.50.15/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From 0b59e4f84ad57a788da1e1b6e592d939ffc1fcf5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 18 Feb 2024 17:08:13 +0000 Subject: [PATCH 1071/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.50.15 to 1.50.20 (#872) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0e37f526..227fb727 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.50.15 + github.com/aws/aws-sdk-go v1.50.20 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 531689e4..da650277 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.50.15 h1:wEMnPfEQQFaoIJwuO18zq/vtG4Ft7NxQ3r9xlEi/8zg= -github.com/aws/aws-sdk-go v1.50.15/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.50.20 h1:xfAnSDVf/azIWTVQXQODp89bubvCS85r70O3nuQ4dnE= +github.com/aws/aws-sdk-go v1.50.20/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From 9ef74d421e83515cb2611672cf8e48c12f3c7613 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 18 Feb 2024 17:08:29 +0000 Subject: [PATCH 1072/1127] chore(deps): bump cloud.google.com/go/storage from 1.37.0 to 1.38.0 (#873) --- go.mod | 28 +++++++++++++-------------- go.sum | 60 +++++++++++++++++++++++++++++----------------------------- 2 files changed, 44 insertions(+), 44 deletions(-) diff --git a/go.mod b/go.mod index 227fb727..9eb19859 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Praqma/helmsman go 1.21 require ( - cloud.google.com/go/storage v1.37.0 + cloud.google.com/go/storage v1.38.0 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.15.0 github.com/BurntSushi/toml v1.3.2 @@ -22,18 +22,18 @@ require ( cloud.google.com/go v0.112.0 // indirect cloud.google.com/go/compute v1.23.3 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v1.1.5 // indirect + cloud.google.com/go/iam v1.1.6 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/buger/jsonparser v1.1.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/go-logr/logr v1.3.0 // indirect + github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang-jwt/jwt/v4 v4.3.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/s2a-go v0.1.7 // indirect - github.com/google/uuid v1.5.0 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect @@ -42,23 +42,23 @@ require ( github.com/mattn/go-ieproxy v0.0.1 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect - go.opentelemetry.io/otel v1.21.0 // indirect - go.opentelemetry.io/otel/metric v1.21.0 // indirect - go.opentelemetry.io/otel/trace v1.21.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 // indirect + go.opentelemetry.io/otel v1.22.0 // indirect + go.opentelemetry.io/otel/metric v1.22.0 // indirect + go.opentelemetry.io/otel/trace v1.22.0 // indirect golang.org/x/crypto v0.19.0 // indirect golang.org/x/oauth2 v0.16.0 // indirect golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect - google.golang.org/api v0.157.0 // indirect + google.golang.org/api v0.162.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240122161410-6c6643bf1457 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac // indirect - google.golang.org/grpc v1.60.1 // indirect + google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240205150955-31a09d347014 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe // indirect + google.golang.org/grpc v1.61.0 // indirect google.golang.org/protobuf v1.32.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index da650277..6c2bb022 100644 --- a/go.sum +++ b/go.sum @@ -5,10 +5,10 @@ cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiV cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI= -cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= -cloud.google.com/go/storage v1.37.0 h1:WI8CsaFO8Q9KjPVtsZ5Cmi0dXV25zMoX0FklT7c3Jm4= -cloud.google.com/go/storage v1.37.0/go.mod h1:i34TiT2IhiNDmcj65PqwCjcoUX7Z5pLzS8DEmoiFq1k= +cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc= +cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI= +cloud.google.com/go/storage v1.38.0 h1:Az68ZRGlnNTpIBbLjSMIV2BDcwwXYlRlQzis0llkpJg= +cloud.google.com/go/storage v1.38.0/go.mod h1:tlUADB0mAb9BgYls9lq+8MGkfzOXuLrnHXlpHmvFJoY= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk= @@ -41,8 +41,8 @@ github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx2 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= -github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101 h1:7To3pQ+pZo0i3dsWEbinPNFs5gPSBOsJtx3wTT94VBY= +github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -57,8 +57,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= -github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= @@ -98,8 +98,8 @@ github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= -github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= @@ -145,18 +145,18 @@ github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+x github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 h1:SpGay3w+nEwMpfVnbqOLH5gY52/foP8RE8UzTZ1pdSE= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= -go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= -go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= -go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= -go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 h1:UNQQKPfTDe1J81ViolILjTKPr9WetKW6uei2hFgJmFs= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 h1:sv9kVfal0MK0wBMCOGr+HeJm9v803BkJxGrk2au7j08= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw= +go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y= +go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= +go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg= +go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= -go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= -go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= +go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0= +go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -226,8 +226,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= -google.golang.org/api v0.157.0 h1:ORAeqmbrrozeyw5NjnMxh7peHO0UzV4wWYSwZeCUb20= -google.golang.org/api v0.157.0/go.mod h1:+z4v4ufbZ1WEpld6yMGHyggs+PmAHiaLNj5ytP3N01g= +google.golang.org/api v0.162.0 h1:Vhs54HkaEpkMBdgGdOT2P6F0csGG/vxDS0hWHJzmmps= +google.golang.org/api v0.162.0/go.mod h1:6SulDkfoBIg4NFmCuZ39XeeAgSHCPecfSUuDyYlAHs0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= @@ -235,19 +235,19 @@ google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac h1:ZL/Teoy/ZGnzyrqK/Optxxp2pmVh+fmJ97slxSRyzUg= -google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:+Rvu7ElI+aLzyDQhpHMFMMltsD6m7nqpuWDd2CwJw3k= -google.golang.org/genproto/googleapis/api v0.0.0-20240122161410-6c6643bf1457 h1:KHBtwE+eQc3+NxpjmRFlQ3pJQ2FNnhhgB9xOV8kyBuU= -google.golang.org/genproto/googleapis/api v0.0.0-20240122161410-6c6643bf1457/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac h1:nUQEQmH/csSvFECKYRv6HWEyypysidKl2I6Qpsglq/0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA= +google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe h1:USL2DhxfgRchafRvt/wYyyQNzwgL7ZiURcozOE/Pkvo= +google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= +google.golang.org/genproto/googleapis/api v0.0.0-20240205150955-31a09d347014 h1:x9PwdEgd11LgK+orcck69WVRo7DezSO4VUMPI4xpc8A= +google.golang.org/genproto/googleapis/api v0.0.0-20240205150955-31a09d347014/go.mod h1:rbHMSEDyoYX62nRVLOCc4Qt1HbsdytAYoVwgjiOhF3I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe h1:bQnxqljG/wqi4NTXu2+DJ3n7APcEA882QZ1JvhQAq9o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= -google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0= +google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= From 7e2d16d7ff3168333b933678c6a7ccf0af3737c5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 25 Feb 2024 17:13:48 +0000 Subject: [PATCH 1073/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.50.20 to 1.50.25 (#876) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9eb19859..09e835d7 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.50.20 + github.com/aws/aws-sdk-go v1.50.25 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 6c2bb022..77f7c5ee 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.50.20 h1:xfAnSDVf/azIWTVQXQODp89bubvCS85r70O3nuQ4dnE= -github.com/aws/aws-sdk-go v1.50.20/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.50.25 h1:vhiHtLYybv1Nhx3Kv18BBC6L0aPJHaG9aeEsr92W99c= +github.com/aws/aws-sdk-go v1.50.25/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From 8a7919613441847769471338b843002bba765844 Mon Sep 17 00:00:00 2001 From: Michielvk <16121929+Michielvk@users.noreply.github.com> Date: Sun, 25 Feb 2024 18:14:28 +0100 Subject: [PATCH 1074/1127] Update README.md v3.17.0 (#874) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8b008717..3ed1f6e9 100644 --- a/README.md +++ b/README.md @@ -77,9 +77,9 @@ Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the d ```sh # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.11.0/helmsman_3.11.0_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.17.0/helmsman_3.17.0_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.11.0/helmsman_3.11.0_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.17.0/helmsman_3.17.0_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` From 7c291b9b94c46e04c4059f415b40eb4dfc38e01b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 3 Mar 2024 17:35:07 +0000 Subject: [PATCH 1075/1127] chore(deps): bump cloud.google.com/go/storage from 1.38.0 to 1.39.0 (#877) --- go.mod | 28 ++++++++++++++-------------- go.sum | 56 ++++++++++++++++++++++++++++---------------------------- 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/go.mod b/go.mod index 09e835d7..3cbda486 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Praqma/helmsman go 1.21 require ( - cloud.google.com/go/storage v1.38.0 + cloud.google.com/go/storage v1.39.0 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.15.0 github.com/BurntSushi/toml v1.3.2 @@ -20,7 +20,7 @@ require ( require ( cloud.google.com/go v0.112.0 // indirect - cloud.google.com/go/compute v1.23.3 // indirect + cloud.google.com/go/compute v1.24.0 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/iam v1.1.6 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect @@ -35,30 +35,30 @@ require ( github.com/google/s2a-go v0.1.7 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect - github.com/googleapis/gax-go/v2 v2.12.0 // indirect + github.com/googleapis/gax-go/v2 v2.12.1 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-ieproxy v0.0.1 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 // indirect - go.opentelemetry.io/otel v1.22.0 // indirect - go.opentelemetry.io/otel/metric v1.22.0 // indirect - go.opentelemetry.io/otel/trace v1.22.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.48.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0 // indirect + go.opentelemetry.io/otel v1.23.0 // indirect + go.opentelemetry.io/otel/metric v1.23.0 // indirect + go.opentelemetry.io/otel/trace v1.23.0 // indirect golang.org/x/crypto v0.19.0 // indirect - golang.org/x/oauth2 v0.16.0 // indirect + golang.org/x/oauth2 v0.17.0 // indirect golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect - google.golang.org/api v0.162.0 // indirect + google.golang.org/api v0.166.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240205150955-31a09d347014 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe // indirect - google.golang.org/grpc v1.61.0 // indirect + google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240221002015-b0ce06bbee7c // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240213162025-012b6fc9bca9 // indirect + google.golang.org/grpc v1.61.1 // indirect google.golang.org/protobuf v1.32.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 77f7c5ee..f67bcf57 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,14 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.112.0 h1:tpFCD7hpHFlQ8yPwT3x+QeXqc2T6+n6T+hmABHfDUSM= cloud.google.com/go v0.112.0/go.mod h1:3jEEVwZ/MHU4djK5t5RHuKOA/GbLddgTdVubX1qnPD4= -cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= -cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= +cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= +cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc= cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI= -cloud.google.com/go/storage v1.38.0 h1:Az68ZRGlnNTpIBbLjSMIV2BDcwwXYlRlQzis0llkpJg= -cloud.google.com/go/storage v1.38.0/go.mod h1:tlUADB0mAb9BgYls9lq+8MGkfzOXuLrnHXlpHmvFJoY= +cloud.google.com/go/storage v1.39.0 h1:brbjUa4hbDHhpQf48tjqMaXEV+f1OGoaTmQau9tmCsA= +cloud.google.com/go/storage v1.39.0/go.mod h1:OAEj/WZwUYjA3YHQ10/YcN9ttGuEpLwvaoyBXIPikEk= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk= @@ -102,8 +102,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= -github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= -github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= +github.com/googleapis/gax-go/v2 v2.12.1 h1:9F8GV9r9ztXyAi00gsMQHNoF51xPZm8uj1dpYt2ZETM= +github.com/googleapis/gax-go/v2 v2.12.1/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI= @@ -145,18 +145,18 @@ github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+x github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 h1:UNQQKPfTDe1J81ViolILjTKPr9WetKW6uei2hFgJmFs= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 h1:sv9kVfal0MK0wBMCOGr+HeJm9v803BkJxGrk2au7j08= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw= -go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y= -go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= -go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg= -go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.48.0 h1:P+/g8GpuJGYbOp2tAdKrIPUX9JO02q8Q0YNlHolpibA= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.48.0/go.mod h1:tIKj3DbO8N9Y2xo52og3irLsPI4GW02DSMtrVgNMgxg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0 h1:doUP+ExOpH3spVTLS0FcWGLnQrPct/hD/bCPbDRUEAU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0/go.mod h1:rdENBZMT2OE6Ne/KLwpiXudnAsbdrdBaqBvTN8M8BgA= +go.opentelemetry.io/otel v1.23.0 h1:Df0pqjqExIywbMCMTxkAwzjLZtRf+bBKLbUcpxO2C9E= +go.opentelemetry.io/otel v1.23.0/go.mod h1:YCycw9ZeKhcJFrb34iVSkyT0iczq/zYDtZYFufObyB0= +go.opentelemetry.io/otel/metric v1.23.0 h1:pazkx7ss4LFVVYSxYew7L5I6qvLXHA0Ap2pwV+9Cnpo= +go.opentelemetry.io/otel/metric v1.23.0/go.mod h1:MqUW2X2a6Q8RN96E2/nqNoT+z9BSms20Jb7Bbp+HiTo= go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= -go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0= -go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= +go.opentelemetry.io/otel/trace v1.23.0 h1:37Ik5Ib7xfYVb4V1UtnT97T1jI+AoIYkJyPkuL4iJgI= +go.opentelemetry.io/otel/trace v1.23.0/go.mod h1:GSGTbIClEsuZrGIzoEHqsVfxgn5UkggkflQwDScNUsk= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -183,8 +183,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= -golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= +golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= +golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -226,8 +226,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= -google.golang.org/api v0.162.0 h1:Vhs54HkaEpkMBdgGdOT2P6F0csGG/vxDS0hWHJzmmps= -google.golang.org/api v0.162.0/go.mod h1:6SulDkfoBIg4NFmCuZ39XeeAgSHCPecfSUuDyYlAHs0= +google.golang.org/api v0.166.0 h1:6m4NUwrZYhAaVIHZWxaKjw1L1vNAjtMwORmKRyEEo24= +google.golang.org/api v0.166.0/go.mod h1:4FcBc686KFi7QI/U51/2GKKevfZMpM17sCdibqe/bSA= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= @@ -235,19 +235,19 @@ google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe h1:USL2DhxfgRchafRvt/wYyyQNzwgL7ZiURcozOE/Pkvo= -google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= -google.golang.org/genproto/googleapis/api v0.0.0-20240205150955-31a09d347014 h1:x9PwdEgd11LgK+orcck69WVRo7DezSO4VUMPI4xpc8A= -google.golang.org/genproto/googleapis/api v0.0.0-20240205150955-31a09d347014/go.mod h1:rbHMSEDyoYX62nRVLOCc4Qt1HbsdytAYoVwgjiOhF3I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe h1:bQnxqljG/wqi4NTXu2+DJ3n7APcEA882QZ1JvhQAq9o= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= +google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= +google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= +google.golang.org/genproto/googleapis/api v0.0.0-20240221002015-b0ce06bbee7c h1:9g7erC9qu44ks7UK4gDNlnk4kOxZG707xKm4jVniy6o= +google.golang.org/genproto/googleapis/api v0.0.0-20240221002015-b0ce06bbee7c/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240213162025-012b6fc9bca9 h1:hZB7eLIaYlW9qXRfCq/qDaPdbeY3757uARz5Vvfv+cY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:YUWgXUFRPfoYK1IHMuxH5K6nPEXSCzIMljnQ59lLRCk= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0= -google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= +google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY= +google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= From 493bf949b148d1ab4dd2ff52fd530587a2c1392a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 3 Mar 2024 17:35:22 +0000 Subject: [PATCH 1076/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.50.25 to 1.50.30 (#878) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 3cbda486..a27477ca 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.50.25 + github.com/aws/aws-sdk-go v1.50.30 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index f67bcf57..57d83694 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.50.25 h1:vhiHtLYybv1Nhx3Kv18BBC6L0aPJHaG9aeEsr92W99c= -github.com/aws/aws-sdk-go v1.50.25/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.50.30 h1:2OelKH1eayeaH7OuL1Y9Ombfw4HK+/k0fEnJNWjyLts= +github.com/aws/aws-sdk-go v1.50.30/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From 0de3e9fc50c8a287e740fe32e42ba55d4f4ced25 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 19:18:39 +0000 Subject: [PATCH 1077/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.50.30 to 1.50.35 (#880) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a27477ca..73464960 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.50.30 + github.com/aws/aws-sdk-go v1.50.35 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 57d83694..ae7786c5 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.50.30 h1:2OelKH1eayeaH7OuL1Y9Ombfw4HK+/k0fEnJNWjyLts= -github.com/aws/aws-sdk-go v1.50.30/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.50.35 h1:llQnNddBI/64pK7pwUFBoWYmg8+XGQUCs214eMbSDZc= +github.com/aws/aws-sdk-go v1.50.35/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From 38787c991388ee7ef5a5b6c7549ae5c851309f73 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 19:18:56 +0000 Subject: [PATCH 1078/1127] chore(deps): bump golang.org/x/net from 0.21.0 to 0.22.0 (#879) --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 73464960..c55a8d38 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.6.0 - golang.org/x/net v0.21.0 + golang.org/x/net v0.22.0 sigs.k8s.io/yaml v1.4.0 ) @@ -47,10 +47,10 @@ require ( go.opentelemetry.io/otel v1.23.0 // indirect go.opentelemetry.io/otel/metric v1.23.0 // indirect go.opentelemetry.io/otel/trace v1.23.0 // indirect - golang.org/x/crypto v0.19.0 // indirect + golang.org/x/crypto v0.21.0 // indirect golang.org/x/oauth2 v0.17.0 // indirect golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.17.0 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect google.golang.org/api v0.166.0 // indirect diff --git a/go.sum b/go.sum index ae7786c5..84389012 100644 --- a/go.sum +++ b/go.sum @@ -162,8 +162,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -180,8 +180,8 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= @@ -201,8 +201,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From 0ce7645598396516ea403382580b2d4c6cb83100 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Mar 2024 23:39:30 +0000 Subject: [PATCH 1079/1127] chore(deps): bump google.golang.org/protobuf from 1.32.0 to 1.33.0 (#881) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c55a8d38..36ef12d6 100644 --- a/go.mod +++ b/go.mod @@ -59,7 +59,7 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20240221002015-b0ce06bbee7c // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240213162025-012b6fc9bca9 // indirect google.golang.org/grpc v1.61.1 // indirect - google.golang.org/protobuf v1.32.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 84389012..e52e3774 100644 --- a/go.sum +++ b/go.sum @@ -259,8 +259,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= -google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From 7e6233e4d07c0c8c0f4a0a0b5d83a72cce455f8a Mon Sep 17 00:00:00 2001 From: Eric Chu Date: Sun, 17 Mar 2024 03:24:16 -0700 Subject: [PATCH 1080/1127] feat: add show-secrets option (#882) Co-authored-by: Eric Chu --- internal/app/cli.go | 2 ++ internal/app/release.go | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/internal/app/cli.go b/internal/app/cli.go index 6b58edb0..e16fbfb5 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -98,6 +98,7 @@ type cli struct { skipIgnoredApps bool skipPendingApps bool pendingAppRetries int + showSecrets bool } func printUsage() { @@ -155,6 +156,7 @@ func (c *cli) setup() { flag.BoolVar(&c.skipIgnoredApps, "skip-ignored", false, "skip ignored apps") flag.BoolVar(&c.skipPendingApps, "skip-pending", false, "skip pending helm releases") flag.IntVar(&c.pendingAppRetries, "pending-max-retries", 0, "max number of retries for pending helm releases") + flag.BoolVar(&c.showSecrets, "show-secrets", false, "show helm diff results with secrets.") flag.Usage = printUsage flag.Parse() } diff --git a/internal/app/release.go b/internal/app/release.go index 331ee5ae..ae5d8e8d 100644 --- a/internal/app/release.go +++ b/internal/app/release.go @@ -195,7 +195,10 @@ func (r *Release) diff() (string, error) { ) if !flags.kubectlDiff { - args = []string{"diff", "--suppress-secrets"} + args = []string{"diff"} + if !flags.showSecrets { + args = append(args, "--suppress-secrets") + } if flags.noColors { args = append(args, "--no-color") } From a5d5f8f426d2a3b422d70c4f72ab06032aa4ac2e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 20:52:59 +0000 Subject: [PATCH 1081/1127] chore(deps): bump cloud.google.com/go/storage from 1.39.0 to 1.39.1 (#883) --- go.mod | 14 +++++++------- go.sum | 40 ++++++++++++++++++++-------------------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/go.mod b/go.mod index 36ef12d6..61a9550b 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Praqma/helmsman go 1.21 require ( - cloud.google.com/go/storage v1.39.0 + cloud.google.com/go/storage v1.39.1 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.15.0 github.com/BurntSushi/toml v1.3.2 @@ -19,7 +19,7 @@ require ( ) require ( - cloud.google.com/go v0.112.0 // indirect + cloud.google.com/go v0.112.1 // indirect cloud.google.com/go/compute v1.24.0 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/iam v1.1.6 // indirect @@ -35,7 +35,7 @@ require ( github.com/google/s2a-go v0.1.7 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect - github.com/googleapis/gax-go/v2 v2.12.1 // indirect + github.com/googleapis/gax-go/v2 v2.12.2 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect @@ -53,12 +53,12 @@ require ( golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect - google.golang.org/api v0.166.0 // indirect + google.golang.org/api v0.167.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240221002015-b0ce06bbee7c // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240213162025-012b6fc9bca9 // indirect - google.golang.org/grpc v1.61.1 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240304161311-37d4d3c04a78 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240228224816-df926f6c8641 // indirect + google.golang.org/grpc v1.62.0 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index e52e3774..e159cb1f 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,14 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.112.0 h1:tpFCD7hpHFlQ8yPwT3x+QeXqc2T6+n6T+hmABHfDUSM= -cloud.google.com/go v0.112.0/go.mod h1:3jEEVwZ/MHU4djK5t5RHuKOA/GbLddgTdVubX1qnPD4= +cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= +cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4= cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc= cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI= -cloud.google.com/go/storage v1.39.0 h1:brbjUa4hbDHhpQf48tjqMaXEV+f1OGoaTmQau9tmCsA= -cloud.google.com/go/storage v1.39.0/go.mod h1:OAEj/WZwUYjA3YHQ10/YcN9ttGuEpLwvaoyBXIPikEk= +cloud.google.com/go/storage v1.39.1 h1:MvraqHKhogCOTXTlct/9C3K3+Uy2jBmFYb3/Sp6dVtY= +cloud.google.com/go/storage v1.39.1/go.mod h1:xK6xZmxZmo+fyP7+DEF6FhNc24/JAe95OLyOHCXFH1o= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk= @@ -41,8 +41,8 @@ github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx2 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101 h1:7To3pQ+pZo0i3dsWEbinPNFs5gPSBOsJtx3wTT94VBY= -github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa h1:jQCWAUqqlij9Pgj2i/PB79y4KOPYVyFYdROxgaCwdTQ= +github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -51,8 +51,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= -github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= +github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= +github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= @@ -102,8 +102,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= -github.com/googleapis/gax-go/v2 v2.12.1 h1:9F8GV9r9ztXyAi00gsMQHNoF51xPZm8uj1dpYt2ZETM= -github.com/googleapis/gax-go/v2 v2.12.1/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc= +github.com/googleapis/gax-go/v2 v2.12.2 h1:mhN09QQW1jEWeMF74zGR81R30z4VJzjZsfkUhuHF+DA= +github.com/googleapis/gax-go/v2 v2.12.2/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI= @@ -153,8 +153,8 @@ go.opentelemetry.io/otel v1.23.0 h1:Df0pqjqExIywbMCMTxkAwzjLZtRf+bBKLbUcpxO2C9E= go.opentelemetry.io/otel v1.23.0/go.mod h1:YCycw9ZeKhcJFrb34iVSkyT0iczq/zYDtZYFufObyB0= go.opentelemetry.io/otel/metric v1.23.0 h1:pazkx7ss4LFVVYSxYew7L5I6qvLXHA0Ap2pwV+9Cnpo= go.opentelemetry.io/otel/metric v1.23.0/go.mod h1:MqUW2X2a6Q8RN96E2/nqNoT+z9BSms20Jb7Bbp+HiTo= -go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= -go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= +go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= +go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= go.opentelemetry.io/otel/trace v1.23.0 h1:37Ik5Ib7xfYVb4V1UtnT97T1jI+AoIYkJyPkuL4iJgI= go.opentelemetry.io/otel/trace v1.23.0/go.mod h1:GSGTbIClEsuZrGIzoEHqsVfxgn5UkggkflQwDScNUsk= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -226,8 +226,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= -google.golang.org/api v0.166.0 h1:6m4NUwrZYhAaVIHZWxaKjw1L1vNAjtMwORmKRyEEo24= -google.golang.org/api v0.166.0/go.mod h1:4FcBc686KFi7QI/U51/2GKKevfZMpM17sCdibqe/bSA= +google.golang.org/api v0.167.0 h1:CKHrQD1BLRii6xdkatBDXyKzM0mkawt2QP+H3LtPmSE= +google.golang.org/api v0.167.0/go.mod h1:4FcBc686KFi7QI/U51/2GKKevfZMpM17sCdibqe/bSA= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= @@ -237,17 +237,17 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= -google.golang.org/genproto/googleapis/api v0.0.0-20240221002015-b0ce06bbee7c h1:9g7erC9qu44ks7UK4gDNlnk4kOxZG707xKm4jVniy6o= -google.golang.org/genproto/googleapis/api v0.0.0-20240221002015-b0ce06bbee7c/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240213162025-012b6fc9bca9 h1:hZB7eLIaYlW9qXRfCq/qDaPdbeY3757uARz5Vvfv+cY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:YUWgXUFRPfoYK1IHMuxH5K6nPEXSCzIMljnQ59lLRCk= +google.golang.org/genproto/googleapis/api v0.0.0-20240304161311-37d4d3c04a78 h1:SzXBGiWM1LNVYLCRP3e0/Gsze804l4jGoJ5lYysEO5I= +google.golang.org/genproto/googleapis/api v0.0.0-20240304161311-37d4d3c04a78/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240228224816-df926f6c8641 h1:DKU1r6Tj5s1vlU/moGhuGz7E3xRfwjdAfDzbsaQJtEY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240228224816-df926f6c8641/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY= -google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= +google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk= +google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= From d6869187ca22bb5f3f1be21a43aea00398fa512a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 20:53:18 +0000 Subject: [PATCH 1082/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.50.35 to 1.51.1 (#884) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 61a9550b..c937fbe4 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.50.35 + github.com/aws/aws-sdk-go v1.51.1 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index e159cb1f..dea2d147 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.50.35 h1:llQnNddBI/64pK7pwUFBoWYmg8+XGQUCs214eMbSDZc= -github.com/aws/aws-sdk-go v1.50.35/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.51.1 h1:AFvTihcDPanvptoKS09a4yYmNtPm3+pXlk6uYHmZiFk= +github.com/aws/aws-sdk-go v1.51.1/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From d85b19f3d23b95e83bf8b6df057f7fe6bd85676b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 30 Mar 2024 19:33:03 +0000 Subject: [PATCH 1083/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.51.1 to 1.51.6 (#886) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c937fbe4..c179ee90 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.51.1 + github.com/aws/aws-sdk-go v1.51.6 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index dea2d147..b0c307c6 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.51.1 h1:AFvTihcDPanvptoKS09a4yYmNtPm3+pXlk6uYHmZiFk= -github.com/aws/aws-sdk-go v1.51.1/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.51.6 h1:Ld36dn9r7P9IjU8WZSaswQ8Y/XUCRpewim5980DwYiU= +github.com/aws/aws-sdk-go v1.51.6/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From c2b375bb78741a0ad5555d6b1dbe8ddf107d1e7a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 13:29:16 +0000 Subject: [PATCH 1084/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.51.6 to 1.51.11 (#887) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c179ee90..8b67e21f 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.51.6 + github.com/aws/aws-sdk-go v1.51.11 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index b0c307c6..09d8915c 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.51.6 h1:Ld36dn9r7P9IjU8WZSaswQ8Y/XUCRpewim5980DwYiU= -github.com/aws/aws-sdk-go v1.51.6/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.51.11 h1:El5VypsMIz7sFwAAj/j06JX9UGs4KAbAIEaZ57bNY4s= +github.com/aws/aws-sdk-go v1.51.11/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From 6b112c5ac74e563f5c50e0007ce5180ddfbf47c0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 13:29:40 +0000 Subject: [PATCH 1085/1127] chore(deps): bump cloud.google.com/go/storage from 1.39.1 to 1.40.0 (#888) --- go.mod | 28 +++++++++++++-------------- go.sum | 60 +++++++++++++++++++++++++++------------------------------- 2 files changed, 42 insertions(+), 46 deletions(-) diff --git a/go.mod b/go.mod index 8b67e21f..51b4c0ee 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Praqma/helmsman go 1.21 require ( - cloud.google.com/go/storage v1.39.1 + cloud.google.com/go/storage v1.40.0 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.15.0 github.com/BurntSushi/toml v1.3.2 @@ -22,7 +22,7 @@ require ( cloud.google.com/go v0.112.1 // indirect cloud.google.com/go/compute v1.24.0 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v1.1.6 // indirect + cloud.google.com/go/iam v1.1.7 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/buger/jsonparser v1.1.1 // indirect @@ -31,34 +31,34 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/golang-jwt/jwt/v4 v4.3.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/google/s2a-go v0.1.7 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect - github.com/googleapis/gax-go/v2 v2.12.2 // indirect + github.com/googleapis/gax-go/v2 v2.12.3 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-ieproxy v0.0.1 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.48.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0 // indirect - go.opentelemetry.io/otel v1.23.0 // indirect - go.opentelemetry.io/otel/metric v1.23.0 // indirect - go.opentelemetry.io/otel/trace v1.23.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect + go.opentelemetry.io/otel v1.24.0 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect + go.opentelemetry.io/otel/trace v1.24.0 // indirect golang.org/x/crypto v0.21.0 // indirect - golang.org/x/oauth2 v0.17.0 // indirect + golang.org/x/oauth2 v0.18.0 // indirect golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect - google.golang.org/api v0.167.0 // indirect + google.golang.org/api v0.170.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240304161311-37d4d3c04a78 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240228224816-df926f6c8641 // indirect - google.golang.org/grpc v1.62.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240314234333-6e1732d8331c // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240311132316-a219d84964c2 // indirect + google.golang.org/grpc v1.62.1 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 09d8915c..430d8895 100644 --- a/go.sum +++ b/go.sum @@ -5,10 +5,10 @@ cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1Yl cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc= -cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI= -cloud.google.com/go/storage v1.39.1 h1:MvraqHKhogCOTXTlct/9C3K3+Uy2jBmFYb3/Sp6dVtY= -cloud.google.com/go/storage v1.39.1/go.mod h1:xK6xZmxZmo+fyP7+DEF6FhNc24/JAe95OLyOHCXFH1o= +cloud.google.com/go/iam v1.1.7 h1:z4VHOhwKLF/+UYXAJDFwGtNF0b6gjsW1Pk9Ml0U/IoM= +cloud.google.com/go/iam v1.1.7/go.mod h1:J4PMPg8TtyurAUvSmPj8FF3EDgY1SPRZxcUGrn7WXGA= +cloud.google.com/go/storage v1.40.0 h1:VEpDQV5CJxFmJ6ueWNsKxcr1QAYOXEgxDa+sBbJahPw= +cloud.google.com/go/storage v1.40.0/go.mod h1:Rrj7/hKlG87BLqDJYtwR0fbPld8uJPbQ2ucUMY7Ir0g= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk= @@ -41,8 +41,6 @@ github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx2 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa h1:jQCWAUqqlij9Pgj2i/PB79y4KOPYVyFYdROxgaCwdTQ= -github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -51,8 +49,6 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= -github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= @@ -80,8 +76,8 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -102,8 +98,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= -github.com/googleapis/gax-go/v2 v2.12.2 h1:mhN09QQW1jEWeMF74zGR81R30z4VJzjZsfkUhuHF+DA= -github.com/googleapis/gax-go/v2 v2.12.2/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc= +github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA= +github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI= @@ -145,18 +141,18 @@ github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+x github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.48.0 h1:P+/g8GpuJGYbOp2tAdKrIPUX9JO02q8Q0YNlHolpibA= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.48.0/go.mod h1:tIKj3DbO8N9Y2xo52og3irLsPI4GW02DSMtrVgNMgxg= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0 h1:doUP+ExOpH3spVTLS0FcWGLnQrPct/hD/bCPbDRUEAU= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0/go.mod h1:rdENBZMT2OE6Ne/KLwpiXudnAsbdrdBaqBvTN8M8BgA= -go.opentelemetry.io/otel v1.23.0 h1:Df0pqjqExIywbMCMTxkAwzjLZtRf+bBKLbUcpxO2C9E= -go.opentelemetry.io/otel v1.23.0/go.mod h1:YCycw9ZeKhcJFrb34iVSkyT0iczq/zYDtZYFufObyB0= -go.opentelemetry.io/otel/metric v1.23.0 h1:pazkx7ss4LFVVYSxYew7L5I6qvLXHA0Ap2pwV+9Cnpo= -go.opentelemetry.io/otel/metric v1.23.0/go.mod h1:MqUW2X2a6Q8RN96E2/nqNoT+z9BSms20Jb7Bbp+HiTo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= -go.opentelemetry.io/otel/trace v1.23.0 h1:37Ik5Ib7xfYVb4V1UtnT97T1jI+AoIYkJyPkuL4iJgI= -go.opentelemetry.io/otel/trace v1.23.0/go.mod h1:GSGTbIClEsuZrGIzoEHqsVfxgn5UkggkflQwDScNUsk= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -183,8 +179,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= -golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= +golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= +golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -226,8 +222,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= -google.golang.org/api v0.167.0 h1:CKHrQD1BLRii6xdkatBDXyKzM0mkawt2QP+H3LtPmSE= -google.golang.org/api v0.167.0/go.mod h1:4FcBc686KFi7QI/U51/2GKKevfZMpM17sCdibqe/bSA= +google.golang.org/api v0.170.0 h1:zMaruDePM88zxZBG+NG8+reALO2rfLhe/JShitLyT48= +google.golang.org/api v0.170.0/go.mod h1:/xql9M2btF85xac/VAm4PsLMTLVGUOpq4BE9R8jyNy8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= @@ -237,17 +233,17 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= -google.golang.org/genproto/googleapis/api v0.0.0-20240304161311-37d4d3c04a78 h1:SzXBGiWM1LNVYLCRP3e0/Gsze804l4jGoJ5lYysEO5I= -google.golang.org/genproto/googleapis/api v0.0.0-20240304161311-37d4d3c04a78/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240228224816-df926f6c8641 h1:DKU1r6Tj5s1vlU/moGhuGz7E3xRfwjdAfDzbsaQJtEY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240228224816-df926f6c8641/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs= +google.golang.org/genproto/googleapis/api v0.0.0-20240314234333-6e1732d8331c h1:kaI7oewGK5YnVwj+Y+EJBO/YN1ht8iTL9XkFHtVZLsc= +google.golang.org/genproto/googleapis/api v0.0.0-20240314234333-6e1732d8331c/go.mod h1:VQW3tUculP/D4B+xVCo+VgSq8As6wA9ZjHl//pmk+6s= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240311132316-a219d84964c2 h1:9IZDv+/GcI6u+a4jRFRLxQs0RUCfavGfoOgEW6jpkI0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240311132316-a219d84964c2/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk= -google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= +google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= From b38d8ffff7e18df7ad21a9a8e2575823d9bc19d7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 14 Apr 2024 09:28:38 +0000 Subject: [PATCH 1086/1127] chore(deps): bump golang.org/x/net from 0.22.0 to 0.24.0 (#890) --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 51b4c0ee..1f6c3c75 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.6.0 - golang.org/x/net v0.22.0 + golang.org/x/net v0.24.0 sigs.k8s.io/yaml v1.4.0 ) @@ -47,10 +47,10 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.21.0 // indirect + golang.org/x/crypto v0.22.0 // indirect golang.org/x/oauth2 v0.18.0 // indirect golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.18.0 // indirect + golang.org/x/sys v0.19.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect google.golang.org/api v0.170.0 // indirect diff --git a/go.sum b/go.sum index 430d8895..edac8910 100644 --- a/go.sum +++ b/go.sum @@ -158,8 +158,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -176,8 +176,8 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= @@ -197,8 +197,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From e445fc1421d63b320f29dd4b9bead84f305a9cac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 14 Apr 2024 09:28:52 +0000 Subject: [PATCH 1087/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.51.11 to 1.51.16 (#889) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 1f6c3c75..9168393f 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.51.11 + github.com/aws/aws-sdk-go v1.51.16 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index edac8910..e148fd64 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.51.11 h1:El5VypsMIz7sFwAAj/j06JX9UGs4KAbAIEaZ57bNY4s= -github.com/aws/aws-sdk-go v1.51.11/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.51.16 h1:vnWKK8KjbftEkuPX8bRj3WHsLy1uhotn0eXptpvrxJI= +github.com/aws/aws-sdk-go v1.51.16/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From fa020a5ad0a951f1d8ce12301bd3052792b9c6ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 14 Apr 2024 20:21:24 +0000 Subject: [PATCH 1088/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.51.16 to 1.51.21 (#891) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9168393f..2f667d33 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.51.16 + github.com/aws/aws-sdk-go v1.51.21 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index e148fd64..65e515fd 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.51.16 h1:vnWKK8KjbftEkuPX8bRj3WHsLy1uhotn0eXptpvrxJI= -github.com/aws/aws-sdk-go v1.51.16/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.51.21 h1:UrT6JC9R9PkYYXDZBV0qDKTualMr+bfK2eboTknMgbs= +github.com/aws/aws-sdk-go v1.51.21/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From 5dd5bd93872d0a27e0dbdceda44eff088fb225d4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Apr 2024 22:12:21 +0000 Subject: [PATCH 1089/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.51.21 to 1.51.25 (#892) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 2f667d33..be729d8c 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.51.21 + github.com/aws/aws-sdk-go v1.51.25 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 65e515fd..fa4e1bcf 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.51.21 h1:UrT6JC9R9PkYYXDZBV0qDKTualMr+bfK2eboTknMgbs= -github.com/aws/aws-sdk-go v1.51.21/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.51.25 h1:DjTT8mtmsachhV6yrXR8+yhnG6120dazr720nopRsls= +github.com/aws/aws-sdk-go v1.51.25/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From 5d18de4c89e79b4afb725775dad78bb59ddb1b68 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Apr 2024 23:34:29 +0000 Subject: [PATCH 1090/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.51.25 to 1.51.30 (#893) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index be729d8c..dfdfee96 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.51.25 + github.com/aws/aws-sdk-go v1.51.30 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index fa4e1bcf..26a6c4f8 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.51.25 h1:DjTT8mtmsachhV6yrXR8+yhnG6120dazr720nopRsls= -github.com/aws/aws-sdk-go v1.51.25/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.51.30 h1:RVFkjn9P0JMwnuZCVH0TlV5k9zepHzlbc4943eZMhGw= +github.com/aws/aws-sdk-go v1.51.30/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From 3cc3940dad15d9349bd805b33552eed835402583 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 5 May 2024 22:31:54 +0000 Subject: [PATCH 1091/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.51.30 to 1.52.2 (#894) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index dfdfee96..de23041c 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.51.30 + github.com/aws/aws-sdk-go v1.52.2 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 26a6c4f8..6d13b930 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.51.30 h1:RVFkjn9P0JMwnuZCVH0TlV5k9zepHzlbc4943eZMhGw= -github.com/aws/aws-sdk-go v1.51.30/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.52.2 h1:l4g9wBXRBlvCtScvv4iLZCzLCtR7BFJcXOnOGQ20orw= +github.com/aws/aws-sdk-go v1.52.2/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From 34f8f332ccd320c3b2dd774acd9d254f3811b61d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 19 May 2024 10:38:25 +0000 Subject: [PATCH 1092/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.52.2 to 1.53.0 (#895) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index de23041c..7de32ffa 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.52.2 + github.com/aws/aws-sdk-go v1.53.0 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 6d13b930..3a43ee1f 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.52.2 h1:l4g9wBXRBlvCtScvv4iLZCzLCtR7BFJcXOnOGQ20orw= -github.com/aws/aws-sdk-go v1.52.2/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.53.0 h1:MMo1x1ggPPxDfHMXJnQudTbGXYlD4UigUAud1DJxPVo= +github.com/aws/aws-sdk-go v1.53.0/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From 4827fde003b8263cb2c839f9edd4ddb1aeb76dd9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 19 May 2024 10:38:41 +0000 Subject: [PATCH 1093/1127] chore(deps): bump golang.org/x/net from 0.24.0 to 0.25.0 (#896) --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 7de32ffa..f73a3610 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.6.0 - golang.org/x/net v0.24.0 + golang.org/x/net v0.25.0 sigs.k8s.io/yaml v1.4.0 ) @@ -47,11 +47,11 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect + golang.org/x/crypto v0.23.0 // indirect golang.org/x/oauth2 v0.18.0 // indirect golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect golang.org/x/time v0.5.0 // indirect google.golang.org/api v0.170.0 // indirect google.golang.org/appengine v1.6.8 // indirect diff --git a/go.sum b/go.sum index 3a43ee1f..aa00de7e 100644 --- a/go.sum +++ b/go.sum @@ -158,8 +158,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -176,8 +176,8 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= @@ -197,8 +197,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -207,8 +207,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From d73b65487b52d5b0e5b69a1e7a772532da7fb405 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sun, 19 May 2024 22:07:30 +0100 Subject: [PATCH 1094/1127] chore: update auto-generated files --- cmd/helmsman/main.go | 3 +- internal/app/cli_test.go | 4 +-- internal/app/custom_types.go | 3 +- internal/app/state.go | 4 +-- internal/app/state_files.go | 4 +-- schema.json | 69 +++++++++++++----------------------- 6 files changed, 35 insertions(+), 52 deletions(-) diff --git a/cmd/helmsman/main.go b/cmd/helmsman/main.go index ca4a8d8f..d9e82dc6 100644 --- a/cmd/helmsman/main.go +++ b/cmd/helmsman/main.go @@ -1,8 +1,9 @@ package main import ( - "github.com/Praqma/helmsman/internal/app" "os" + + "github.com/Praqma/helmsman/internal/app" ) func main() { diff --git a/internal/app/cli_test.go b/internal/app/cli_test.go index 77def04f..af71740a 100644 --- a/internal/app/cli_test.go +++ b/internal/app/cli_test.go @@ -26,7 +26,7 @@ func Test_readState(t *testing.T) { { name: "yaml minimal example; no validation", flags: cli{ - files: fileOptionArray([]fileOption{fileOption{"../../examples/minimal-example.yaml", 0}}), + files: fileOptionArray([]fileOption{{"../../examples/minimal-example.yaml", 0}}), skipValidation: true, }, want: result{ @@ -39,7 +39,7 @@ func Test_readState(t *testing.T) { { name: "toml minimal example; no validation", flags: cli{ - files: fileOptionArray([]fileOption{fileOption{"../../examples/minimal-example.toml", 0}}), + files: fileOptionArray([]fileOption{{"../../examples/minimal-example.toml", 0}}), skipValidation: true, }, want: result{ diff --git a/internal/app/custom_types.go b/internal/app/custom_types.go index e682673e..3c82412a 100644 --- a/internal/app/custom_types.go +++ b/internal/app/custom_types.go @@ -2,9 +2,10 @@ package app import ( "encoding/json" - "github.com/invopop/jsonschema" "reflect" "strconv" + + "github.com/invopop/jsonschema" ) // truthy and falsy NullBool values diff --git a/internal/app/state.go b/internal/app/state.go index b4504324..4c3cd5f4 100644 --- a/internal/app/state.go +++ b/internal/app/state.go @@ -231,9 +231,9 @@ func (s *State) validate() error { if (s.Settings.EyamlPrivateKeyPath != "" && s.Settings.EyamlPublicKeyPath == "") || (s.Settings.EyamlPrivateKeyPath == "" && s.Settings.EyamlPublicKeyPath != "") { return errors.New("both EyamlPrivateKeyPath and EyamlPublicKeyPath are required") } - + if s.Settings.EyamlGkms { - if (s.Settings.EyamlGkmsProject == "" || s.Settings.EyamlGkmsLocation == "" || s.Settings.EyamlGkmsKeyring == "" || s.Settings.EyamlGkmsCryptoKey == "") { + if s.Settings.EyamlGkmsProject == "" || s.Settings.EyamlGkmsLocation == "" || s.Settings.EyamlGkmsKeyring == "" || s.Settings.EyamlGkmsCryptoKey == "" { return errors.New("all arguments are required: EyamlGkmsProject, EyamlGkmsLocation, EyamlGkmsKeyring, EyamlGkmsCryptoKey") } } diff --git a/internal/app/state_files.go b/internal/app/state_files.go index 76a25508..bec914af 100644 --- a/internal/app/state_files.go +++ b/internal/app/state_files.go @@ -69,7 +69,7 @@ func (s *State) toTOML(file string) { log.Fatal(err.Error()) os.Exit(1) } - if err := os.WriteFile(file, buff.Bytes(), 0644); err != nil { + if err := os.WriteFile(file, buff.Bytes(), 0o644); err != nil { log.Fatal(err.Error()) } log.Info(fmt.Sprintf("Wrote to %s.\n", file)) @@ -110,7 +110,7 @@ func (s *State) toYAML(file string) { log.Fatal(err.Error()) os.Exit(1) } - if err := os.WriteFile(file, ymlBytes, 0644); err != nil { + if err := os.WriteFile(file, ymlBytes, 0o644); err != nil { log.Fatal(err.Error()) } log.Info(fmt.Sprintf("Wrote to %s.\n", file)) diff --git a/schema.json b/schema.json index f9272b9e..79f9fef3 100644 --- a/schema.json +++ b/schema.json @@ -131,6 +131,9 @@ } }, "type": "object", + "required": [ + "eyamlGkms" + ], "description": "Config type represents the settings fields" }, "CustomResource": { @@ -197,19 +200,15 @@ "description": "Limits to set on the namespace" }, "labels": { - "patternProperties": { - ".*": { - "type": "string" - } + "additionalProperties": { + "type": "string" }, "type": "object", "description": "Labels to set to the namespace" }, "annotations": { - "patternProperties": { - ".*": { - "type": "string" - } + "additionalProperties": { + "type": "string" }, "type": "object", "description": "Annotations to set on the namespace" @@ -332,28 +331,22 @@ "description": "Priority allows defining the execution order, releases with the same priority can be executed in parallel" }, "set": { - "patternProperties": { - ".*": { - "type": "string" - } + "additionalProperties": { + "type": "string" }, "type": "object", "description": "Set can be used to overwrite the chart values" }, "setString": { - "patternProperties": { - ".*": { - "type": "string" - } + "additionalProperties": { + "type": "string" }, "type": "object", "description": "SetString can be used to overwrite string values" }, "setFile": { - "patternProperties": { - ".*": { - "type": "string" - } + "additionalProperties": { + "type": "string" }, "type": "object", "description": "SetFile can be used to overwrite the chart values" @@ -416,19 +409,15 @@ "State": { "properties": { "metadata": { - "patternProperties": { - ".*": { - "type": "string" - } + "additionalProperties": { + "type": "string" }, "type": "object", "description": "Metadata for human reader of the desired state file" }, "certificates": { - "patternProperties": { - ".*": { - "type": "string" - } + "additionalProperties": { + "type": "string" }, "type": "object", "description": "Certificates are used to connect kubectl to a cluster" @@ -442,10 +431,8 @@ "description": "Context defines an helmsman scope" }, "helmRepos": { - "patternProperties": { - ".*": { - "type": "string" - } + "additionalProperties": { + "type": "string" }, "type": "object", "description": "HelmRepos from where to find the application helm charts" @@ -458,28 +445,22 @@ "description": "PreconfiguredHelmRepos is a list of helm repos that are configured outside of the DSF" }, "namespaces": { - "patternProperties": { - ".*": { - "$ref": "#/$defs/Namespace" - } + "additionalProperties": { + "$ref": "#/$defs/Namespace" }, "type": "object", "description": "Namespaces where helmsman will deploy applications" }, "apps": { - "patternProperties": { - ".*": { - "$ref": "#/$defs/Release" - } + "additionalProperties": { + "$ref": "#/$defs/Release" }, "type": "object", "description": "Apps holds the configuration for each helm release managed by helmsman" }, "appsTemplates": { - "patternProperties": { - ".*": { - "$ref": "#/$defs/Release" - } + "additionalProperties": { + "$ref": "#/$defs/Release" }, "type": "object", "description": "AppsTemplates allow defining YAML objects thatcan be used as a reference with YAML anchors to keep the configuration DRY" From 8c1b962ce9ada2834e3b77659cb609280dce6d80 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sun, 19 May 2024 22:58:27 +0100 Subject: [PATCH 1095/1127] chore: update tool versions --- .circleci/config.yml | 6 +++--- Dockerfile | 22 +++++++++++----------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 010423fd..f058a02f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,11 +2,11 @@ version: 2 jobs: build: docker: - - image: docker:20.10.6-git + - image: docker:25.0.5-git steps: - checkout - setup_remote_docker: - version: 20.10.6 + version: default - run: name: build docker image command: | @@ -36,7 +36,7 @@ jobs: - run: name: build docker images and push them to dockerhub command: | - helm_versions=( "v3.12.2" "v3.11.3" "v3.10.3" ) + helm_versions=( "v3.15.0" "v3.14.4" "v3.13.3" ) TAG=$(git describe --abbrev=0 --tags) docker login -u $DOCKER_USER -p $DOCKER_PASS diff --git a/Dockerfile b/Dockerfile index d4f9d725..007cbdf7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,12 @@ -ARG GO_VERSION="1.21.4" -ARG ALPINE_VERSION="3.18" -ARG GLOBAL_KUBE_VERSION="v1.24.10" -ARG GLOBAL_HELM_VERSION="v3.11.0" -ARG GLOBAL_HELM_DIFF_VERSION="v3.6.0" -ARG GLOBAL_HELM_GCS_VERSION="0.3.21" -ARG GLOBAL_HELM_S3_VERSION="v0.10.0" -ARG GLOBAL_HELM_SECRETS_VERSION="v3.13.0" -ARG GLOBAL_SOPS_VERSION="v3.7.3" +ARG GO_VERSION="1.22.3" +ARG ALPINE_VERSION="3.19" +ARG GLOBAL_KUBE_VERSION="v1.29.2" +ARG GLOBAL_HELM_VERSION="v3.15.0" +ARG GLOBAL_HELM_DIFF_VERSION="v3.9.6" +ARG GLOBAL_HELM_GCS_VERSION="0.4.2" +ARG GLOBAL_HELM_S3_VERSION="v0.16.0" +ARG GLOBAL_HELM_SECRETS_VERSION="v4.6.0" +ARG GLOBAL_SOPS_VERSION="v3.8.1" ### Helm Installer ### FROM alpine:${ALPINE_VERSION} as helm-installer @@ -28,8 +28,8 @@ ENV HELM_DIFF_THREE_WAY_MERGE=true RUN apk add --update --no-cache ca-certificates git openssh-client openssl ruby curl wget tar gzip make bash -ADD https://github.com/mozilla/sops/releases/download/${SOPS_VERSION}/sops-${SOPS_VERSION}.linux /usr/local/bin/sops -RUN chmod +x /usr/local/bin/sops +RUN curl -L https://github.com/getsops/sops/releases/download/${SOPS_VERSION}/sops-${SOPS_VERSION}.linux.amd64 -o /usr/local/bin/sops \ + && chmod +x /usr/local/bin/sops RUN curl --retry 5 -L https://storage.googleapis.com/kubernetes-release/release/${KUBE_VERSION}/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl RUN chmod +x /usr/local/bin/kubectl From a2440a7fe12390c1ead7e57ecf3c88a5bafe7689 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Fri, 31 May 2024 11:45:34 +0200 Subject: [PATCH 1096/1127] fix: eyaml tests - pub key is no longer required --- internal/app/utils_test.go | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/internal/app/utils_test.go b/internal/app/utils_test.go index d9b725f5..688e29ee 100644 --- a/internal/app/utils_test.go +++ b/internal/app/utils_test.go @@ -223,12 +223,12 @@ func Test_eyamlSecrets(t *testing.T) { want: false, }, { - name: "decryptSecrets - not existing eyaml key", + name: "decryptSecrets - not existing private eyaml key", args: args{ s: &Config{ EyamlEnabled: true, - EyamlPublicKeyPath: "./../../tests/keys/public_key.pkcs7.pem2", - EyamlPrivateKeyPath: "./../../tests/keys/private_key.pkcs7.pem", + EyamlPublicKeyPath: "./../../tests/keys/public_key.pkcs7.pem", + EyamlPrivateKeyPath: "./../../tests/keys/invalid_private_key.pkcs7.pem", }, r: &Release{ Name: "release1", @@ -240,6 +240,25 @@ func Test_eyamlSecrets(t *testing.T) { }, want: false, }, + { + name: "decryptSecrets - not existing public eyaml key", + args: args{ + s: &Config{ + EyamlEnabled: true, + // https://github.com/voxpupuli/hiera-eyaml/commit/760fd05e7cbb34b5380f87a87290deb790ae0aaf + EyamlPublicKeyPath: "./../../tests/keys/invalid_public_key.pkcs7.pem", + EyamlPrivateKeyPath: "./../../tests/keys/private_key.pkcs7.pem", + }, + r: &Release{ + Name: "release1", + Namespace: "namespace", + Version: "1.0.0", + Enabled: True, + SecretsFile: "./../../tests/secrets/valid_eyaml_secrets.yaml", + }, + }, + want: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From d64632c5acf809189465531b42494663a45969fe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 31 May 2024 09:50:42 +0000 Subject: [PATCH 1097/1127] chore(deps): bump cloud.google.com/go/storage from 1.40.0 to 1.41.0 (#897) --- go.mod | 30 ++++++++++---------- go.sum | 90 +++++++++++++++++++++++----------------------------------- 2 files changed, 51 insertions(+), 69 deletions(-) diff --git a/go.mod b/go.mod index f73a3610..9cdb47df 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Praqma/helmsman go 1.21 require ( - cloud.google.com/go/storage v1.40.0 + cloud.google.com/go/storage v1.41.0 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.15.0 github.com/BurntSushi/toml v1.3.2 @@ -19,10 +19,11 @@ require ( ) require ( - cloud.google.com/go v0.112.1 // indirect - cloud.google.com/go/compute v1.24.0 // indirect - cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v1.1.7 // indirect + cloud.google.com/go v0.112.2 // indirect + cloud.google.com/go/auth v0.3.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect + cloud.google.com/go/compute/metadata v0.3.0 // indirect + cloud.google.com/go/iam v1.1.8 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/buger/jsonparser v1.1.1 // indirect @@ -35,7 +36,7 @@ require ( github.com/google/s2a-go v0.1.7 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect - github.com/googleapis/gax-go/v2 v2.12.3 // indirect + github.com/googleapis/gax-go/v2 v2.12.4 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect @@ -48,18 +49,17 @@ require ( go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect golang.org/x/crypto v0.23.0 // indirect - golang.org/x/oauth2 v0.18.0 // indirect - golang.org/x/sync v0.6.0 // indirect + golang.org/x/oauth2 v0.20.0 // indirect + golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect golang.org/x/time v0.5.0 // indirect - google.golang.org/api v0.170.0 // indirect - google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240314234333-6e1732d8331c // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240311132316-a219d84964c2 // indirect - google.golang.org/grpc v1.62.1 // indirect - google.golang.org/protobuf v1.33.0 // indirect + google.golang.org/api v0.178.0 // indirect + google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 // indirect + google.golang.org/grpc v1.63.2 // indirect + google.golang.org/protobuf v1.34.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index aa00de7e..9ab97400 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,16 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= -cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4= -cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= -cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40= -cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/iam v1.1.7 h1:z4VHOhwKLF/+UYXAJDFwGtNF0b6gjsW1Pk9Ml0U/IoM= -cloud.google.com/go/iam v1.1.7/go.mod h1:J4PMPg8TtyurAUvSmPj8FF3EDgY1SPRZxcUGrn7WXGA= -cloud.google.com/go/storage v1.40.0 h1:VEpDQV5CJxFmJ6ueWNsKxcr1QAYOXEgxDa+sBbJahPw= -cloud.google.com/go/storage v1.40.0/go.mod h1:Rrj7/hKlG87BLqDJYtwR0fbPld8uJPbQ2ucUMY7Ir0g= +cloud.google.com/go v0.112.2 h1:ZaGT6LiG7dBzi6zNOvVZwacaXlmf3lRqnC4DQzqyRQw= +cloud.google.com/go v0.112.2/go.mod h1:iEqjp//KquGIJV/m+Pk3xecgKNhV+ry+vVTsy4TbDms= +cloud.google.com/go/auth v0.3.0 h1:PRyzEpGfx/Z9e8+lHsbkoUVXD0gnu4MNmm7Gp8TQNIs= +cloud.google.com/go/auth v0.3.0/go.mod h1:lBv6NKTWp8E3LPzmO1TbiiRKc4drLOfHsgmlH9ogv5w= +cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= +cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= +cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0= +cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE= +cloud.google.com/go/storage v1.41.0 h1:RusiwatSu6lHeEXe3kglxakAmAbfV+rhtPqA6i8RBx0= +cloud.google.com/go/storage v1.41.0/go.mod h1:J1WCa/Z2FcgdEDuPUY8DxT5I+d9mFKsCepp5vR6Sq80= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk= @@ -74,8 +76,6 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -84,12 +84,11 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= -github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= +github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -98,8 +97,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= -github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA= -github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4= +github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg= +github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI= @@ -132,13 +131,12 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= @@ -149,8 +147,8 @@ go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= -go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= -go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= +go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= +go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -164,29 +162,25 @@ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= -golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= +golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= +golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -195,18 +189,13 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= @@ -216,34 +205,29 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= -google.golang.org/api v0.170.0 h1:zMaruDePM88zxZBG+NG8+reALO2rfLhe/JShitLyT48= -google.golang.org/api v0.170.0/go.mod h1:/xql9M2btF85xac/VAm4PsLMTLVGUOpq4BE9R8jyNy8= +google.golang.org/api v0.178.0 h1:yoW/QMI4bRVCHF+NWOTa4cL8MoWL3Jnuc7FlcFF91Ok= +google.golang.org/api v0.178.0/go.mod h1:84/k2v8DFpDRebpGcooklv/lais3MEfqpaBLA12gl2U= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= -google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= -google.golang.org/genproto/googleapis/api v0.0.0-20240314234333-6e1732d8331c h1:kaI7oewGK5YnVwj+Y+EJBO/YN1ht8iTL9XkFHtVZLsc= -google.golang.org/genproto/googleapis/api v0.0.0-20240314234333-6e1732d8331c/go.mod h1:VQW3tUculP/D4B+xVCo+VgSq8As6wA9ZjHl//pmk+6s= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240311132316-a219d84964c2 h1:9IZDv+/GcI6u+a4jRFRLxQs0RUCfavGfoOgEW6jpkI0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240311132316-a219d84964c2/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs= +google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda h1:wu/KJm9KJwpfHWhkkZGohVC6KRrc1oJNr4jwtQMOQXw= +google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda/go.mod h1:g2LLCvCeCSir/JJSWosk19BR4NVxGqHUC6rxIRsd7Aw= +google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae h1:AH34z6WAGVNkllnKs5raNq3yRq93VnjBG6rpfub/jYk= +google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae/go.mod h1:FfiGhwUm6CJviekPrc0oJ+7h29e+DmWU6UtjX0ZvI7Y= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 h1:DujSIu+2tC9Ht0aPNA7jgj23Iq8Ewi5sgkQ++wdvonE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= -google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= +google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -253,10 +237,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From 73d85e016d27125bb5b34e73509e9c3929a777fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 31 May 2024 09:56:55 +0000 Subject: [PATCH 1098/1127] chore(deps): bump github.com/BurntSushi/toml from 1.3.2 to 1.4.0 (#899) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9cdb47df..3cf963a7 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( cloud.google.com/go/storage v1.41.0 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.15.0 - github.com/BurntSushi/toml v1.3.2 + github.com/BurntSushi/toml v1.4.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 github.com/aws/aws-sdk-go v1.53.0 diff --git a/go.sum b/go.sum index 9ab97400..618ee48d 100644 --- a/go.sum +++ b/go.sum @@ -28,8 +28,8 @@ github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= From fc99f4cf225d0c3273b646b8ae5aea062fad040c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 31 May 2024 10:05:10 +0000 Subject: [PATCH 1099/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.53.0 to 1.53.13 (#901) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 3cf963a7..f73fd4c4 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.4.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.53.0 + github.com/aws/aws-sdk-go v1.53.13 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 618ee48d..19befd7e 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.53.0 h1:MMo1x1ggPPxDfHMXJnQudTbGXYlD4UigUAud1DJxPVo= -github.com/aws/aws-sdk-go v1.53.0/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.53.13 h1:CA5bBq3w5tbIsi3LuAmqPfbtC+YJnx2YdLBNqiETVqk= +github.com/aws/aws-sdk-go v1.53.13/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From 85e4edf24416a56c9de99925a69dc4a1cc3c3bbf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 9 Jun 2024 09:11:12 +0000 Subject: [PATCH 1100/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.53.13 to 1.53.14 (#902) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f73fd4c4..ef4f8cf1 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.4.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.53.13 + github.com/aws/aws-sdk-go v1.53.14 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 19befd7e..029bf5c9 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.53.13 h1:CA5bBq3w5tbIsi3LuAmqPfbtC+YJnx2YdLBNqiETVqk= -github.com/aws/aws-sdk-go v1.53.13/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.53.14 h1:SzhkC2Pzag0iRW8WBb80RzKdGXDydJR9LAMs2GyKJ2M= +github.com/aws/aws-sdk-go v1.53.14/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From bb3fa281690ee326aec3de5e32f3c315a22bc6ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Jun 2024 22:26:12 +0000 Subject: [PATCH 1101/1127] chore(deps): bump golang.org/x/net from 0.25.0 to 0.26.0 (#903) --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index ef4f8cf1..035ff4b3 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.6.0 - golang.org/x/net v0.25.0 + golang.org/x/net v0.26.0 sigs.k8s.io/yaml v1.4.0 ) @@ -48,11 +48,11 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.23.0 // indirect + golang.org/x/crypto v0.24.0 // indirect golang.org/x/oauth2 v0.20.0 // indirect golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.20.0 // indirect - golang.org/x/text v0.15.0 // indirect + golang.org/x/sys v0.21.0 // indirect + golang.org/x/text v0.16.0 // indirect golang.org/x/time v0.5.0 // indirect google.golang.org/api v0.178.0 // indirect google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda // indirect diff --git a/go.sum b/go.sum index 029bf5c9..d761b35a 100644 --- a/go.sum +++ b/go.sum @@ -156,8 +156,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -171,8 +171,8 @@ golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= @@ -189,15 +189,15 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From 15bbc1309a50282c3e21632fdd179954d5fc789a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Jun 2024 22:26:37 +0000 Subject: [PATCH 1102/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.53.14 to 1.53.19 (#904) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 035ff4b3..6c60199b 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.4.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.53.14 + github.com/aws/aws-sdk-go v1.53.19 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index d761b35a..dae9e885 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.53.14 h1:SzhkC2Pzag0iRW8WBb80RzKdGXDydJR9LAMs2GyKJ2M= -github.com/aws/aws-sdk-go v1.53.14/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.53.19 h1:WEuWc918RXlIaPCyU11F7hH9H1ItK+8m2c/uoQNRUok= +github.com/aws/aws-sdk-go v1.53.19/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From f86a054fb68874fe3216cce0df9e4f2fb1aa37fd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Jun 2024 21:26:29 +0000 Subject: [PATCH 1103/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.53.19 to 1.54.2 (#905) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6c60199b..2353e431 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.4.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.53.19 + github.com/aws/aws-sdk-go v1.54.2 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index dae9e885..e39ac480 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.53.19 h1:WEuWc918RXlIaPCyU11F7hH9H1ItK+8m2c/uoQNRUok= -github.com/aws/aws-sdk-go v1.53.19/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.54.2 h1:Wo6AVWcleNHrYa48YzfYz60hzxGRqsJrK5s/qePe+3I= +github.com/aws/aws-sdk-go v1.54.2/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From f05cf3b29ef781adc4238c6f6443044229fa926e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Jun 2024 21:26:43 +0000 Subject: [PATCH 1104/1127] chore(deps): bump cloud.google.com/go/storage from 1.41.0 to 1.42.0 (#906) --- go.mod | 18 +++++++++--------- go.sum | 38 ++++++++++++++++++++------------------ 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/go.mod b/go.mod index 2353e431..1147d1f1 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Praqma/helmsman go 1.21 require ( - cloud.google.com/go/storage v1.41.0 + cloud.google.com/go/storage v1.42.0 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.15.0 github.com/BurntSushi/toml v1.4.0 @@ -19,8 +19,8 @@ require ( ) require ( - cloud.google.com/go v0.112.2 // indirect - cloud.google.com/go/auth v0.3.0 // indirect + cloud.google.com/go v0.114.0 // indirect + cloud.google.com/go/auth v0.5.1 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect cloud.google.com/go/compute/metadata v0.3.0 // indirect cloud.google.com/go/iam v1.1.8 // indirect @@ -49,16 +49,16 @@ require ( go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect golang.org/x/crypto v0.24.0 // indirect - golang.org/x/oauth2 v0.20.0 // indirect + golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.21.0 // indirect golang.org/x/text v0.16.0 // indirect golang.org/x/time v0.5.0 // indirect - google.golang.org/api v0.178.0 // indirect - google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 // indirect - google.golang.org/grpc v1.63.2 // indirect + google.golang.org/api v0.183.0 // indirect + google.golang.org/genproto v0.0.0-20240528184218-531527333157 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect + google.golang.org/grpc v1.64.0 // indirect google.golang.org/protobuf v1.34.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index e39ac480..d1b73453 100644 --- a/go.sum +++ b/go.sum @@ -1,16 +1,18 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.112.2 h1:ZaGT6LiG7dBzi6zNOvVZwacaXlmf3lRqnC4DQzqyRQw= -cloud.google.com/go v0.112.2/go.mod h1:iEqjp//KquGIJV/m+Pk3xecgKNhV+ry+vVTsy4TbDms= -cloud.google.com/go/auth v0.3.0 h1:PRyzEpGfx/Z9e8+lHsbkoUVXD0gnu4MNmm7Gp8TQNIs= -cloud.google.com/go/auth v0.3.0/go.mod h1:lBv6NKTWp8E3LPzmO1TbiiRKc4drLOfHsgmlH9ogv5w= +cloud.google.com/go v0.114.0 h1:OIPFAdfrFDFO2ve2U7r/H5SwSbBzEdrBdE7xkgwc+kY= +cloud.google.com/go v0.114.0/go.mod h1:ZV9La5YYxctro1HTPug5lXH/GefROyW8PPD4T8n9J8E= +cloud.google.com/go/auth v0.5.1 h1:0QNO7VThG54LUzKiQxv8C6x1YX7lUrzlAa1nVLF8CIw= +cloud.google.com/go/auth v0.5.1/go.mod h1:vbZT8GjzDf3AVqCcQmqeeM32U9HBFc32vVVAbwDsa6s= cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0= cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE= -cloud.google.com/go/storage v1.41.0 h1:RusiwatSu6lHeEXe3kglxakAmAbfV+rhtPqA6i8RBx0= -cloud.google.com/go/storage v1.41.0/go.mod h1:J1WCa/Z2FcgdEDuPUY8DxT5I+d9mFKsCepp5vR6Sq80= +cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU= +cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng= +cloud.google.com/go/storage v1.42.0 h1:4QtGpplCVt1wz6g5o1ifXd656P5z+yNgzdw1tVfp0cU= +cloud.google.com/go/storage v1.42.0/go.mod h1:HjMXRFq65pGKFn6hxj6x3HCyR41uSB72Z0SO/Vn6JFQ= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk= @@ -174,8 +176,8 @@ golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= -golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= +golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -208,26 +210,26 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= -google.golang.org/api v0.178.0 h1:yoW/QMI4bRVCHF+NWOTa4cL8MoWL3Jnuc7FlcFF91Ok= -google.golang.org/api v0.178.0/go.mod h1:84/k2v8DFpDRebpGcooklv/lais3MEfqpaBLA12gl2U= +google.golang.org/api v0.183.0 h1:PNMeRDwo1pJdgNcFQ9GstuLe/noWKIc89pRWRLMvLwE= +google.golang.org/api v0.183.0/go.mod h1:q43adC5/pHoSZTx5h2mSmdF7NcyfW9JuDyIOJAgS9ZQ= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda h1:wu/KJm9KJwpfHWhkkZGohVC6KRrc1oJNr4jwtQMOQXw= -google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda/go.mod h1:g2LLCvCeCSir/JJSWosk19BR4NVxGqHUC6rxIRsd7Aw= -google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae h1:AH34z6WAGVNkllnKs5raNq3yRq93VnjBG6rpfub/jYk= -google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae/go.mod h1:FfiGhwUm6CJviekPrc0oJ+7h29e+DmWU6UtjX0ZvI7Y= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 h1:DujSIu+2tC9Ht0aPNA7jgj23Iq8Ewi5sgkQ++wdvonE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/genproto v0.0.0-20240528184218-531527333157 h1:u7WMYrIrVvs0TF5yaKwKNbcJyySYf+HAIFXxWltJOXE= +google.golang.org/genproto v0.0.0-20240528184218-531527333157/go.mod h1:ubQlAQnzejB8uZzszhrTCU2Fyp6Vi7ZE5nn0c3W8+qQ= +google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 h1:+rdxYoE3E5htTEWIe15GlN6IfvbURM//Jt0mmkmm6ZU= +google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117/go.mod h1:OimBR/bc1wPO9iV4NC2bpyjy3VnAwZh5EBPQdtaE5oo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= -google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= +google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= From 29cd9f5c26f79cc6d483a329de96937529c67417 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 23 Jun 2024 19:49:37 +0000 Subject: [PATCH 1105/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.54.2 to 1.54.6 (#907) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 1147d1f1..2bc27775 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.4.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.54.2 + github.com/aws/aws-sdk-go v1.54.6 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index d1b73453..487a7f84 100644 --- a/go.sum +++ b/go.sum @@ -36,8 +36,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.54.2 h1:Wo6AVWcleNHrYa48YzfYz60hzxGRqsJrK5s/qePe+3I= -github.com/aws/aws-sdk-go v1.54.2/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/aws/aws-sdk-go v1.54.6 h1:HEYUib3yTt8E6vxjMWM3yAq5b+qjj/6aKA62mkgux9g= +github.com/aws/aws-sdk-go v1.54.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From 957e0cc88711ea5d76c7cc1ba04da0b8194841a7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 30 Jun 2024 18:32:05 +0000 Subject: [PATCH 1106/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.54.6 to 1.54.11 (#908) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 2bc27775..f95b851a 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.4.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.54.6 + github.com/aws/aws-sdk-go v1.54.11 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 487a7f84..5cd2d78a 100644 --- a/go.sum +++ b/go.sum @@ -36,8 +36,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.54.6 h1:HEYUib3yTt8E6vxjMWM3yAq5b+qjj/6aKA62mkgux9g= -github.com/aws/aws-sdk-go v1.54.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/aws/aws-sdk-go v1.54.11 h1:Zxuv/R+IVS0B66yz4uezhxH9FN9/G2nbxejYqAMFjxk= +github.com/aws/aws-sdk-go v1.54.11/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From 5d35047825d61e8e6430388406b6e756eb83c8ab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 7 Jul 2024 21:30:08 +0000 Subject: [PATCH 1107/1127] chore(deps): bump cloud.google.com/go/storage from 1.42.0 to 1.43.0 (#910) --- go.mod | 18 +++++++++--------- go.sum | 38 ++++++++++++++++++-------------------- 2 files changed, 27 insertions(+), 29 deletions(-) diff --git a/go.mod b/go.mod index f95b851a..11673fd7 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Praqma/helmsman go 1.21 require ( - cloud.google.com/go/storage v1.42.0 + cloud.google.com/go/storage v1.43.0 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.15.0 github.com/BurntSushi/toml v1.4.0 @@ -19,8 +19,8 @@ require ( ) require ( - cloud.google.com/go v0.114.0 // indirect - cloud.google.com/go/auth v0.5.1 // indirect + cloud.google.com/go v0.115.0 // indirect + cloud.google.com/go/auth v0.6.1 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect cloud.google.com/go/compute/metadata v0.3.0 // indirect cloud.google.com/go/iam v1.1.8 // indirect @@ -36,7 +36,7 @@ require ( github.com/google/s2a-go v0.1.7 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect - github.com/googleapis/gax-go/v2 v2.12.4 // indirect + github.com/googleapis/gax-go/v2 v2.12.5 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect @@ -54,12 +54,12 @@ require ( golang.org/x/sys v0.21.0 // indirect golang.org/x/text v0.16.0 // indirect golang.org/x/time v0.5.0 // indirect - google.golang.org/api v0.183.0 // indirect - google.golang.org/genproto v0.0.0-20240528184218-531527333157 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect + google.golang.org/api v0.187.0 // indirect + google.golang.org/genproto v0.0.0-20240624140628-dc46fd24d27d // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d // indirect google.golang.org/grpc v1.64.0 // indirect - google.golang.org/protobuf v1.34.1 // indirect + google.golang.org/protobuf v1.34.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 5cd2d78a..6a38b013 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.114.0 h1:OIPFAdfrFDFO2ve2U7r/H5SwSbBzEdrBdE7xkgwc+kY= -cloud.google.com/go v0.114.0/go.mod h1:ZV9La5YYxctro1HTPug5lXH/GefROyW8PPD4T8n9J8E= -cloud.google.com/go/auth v0.5.1 h1:0QNO7VThG54LUzKiQxv8C6x1YX7lUrzlAa1nVLF8CIw= -cloud.google.com/go/auth v0.5.1/go.mod h1:vbZT8GjzDf3AVqCcQmqeeM32U9HBFc32vVVAbwDsa6s= +cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14= +cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU= +cloud.google.com/go/auth v0.6.1 h1:T0Zw1XM5c1GlpN2HYr2s+m3vr1p2wy+8VN+Z1FKxW38= +cloud.google.com/go/auth v0.6.1/go.mod h1:eFHG7zDzbXHKmjJddFG/rBlcGp6t25SwRUiEQSlO4x4= cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= @@ -11,8 +11,8 @@ cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0= cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE= cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU= cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng= -cloud.google.com/go/storage v1.42.0 h1:4QtGpplCVt1wz6g5o1ifXd656P5z+yNgzdw1tVfp0cU= -cloud.google.com/go/storage v1.42.0/go.mod h1:HjMXRFq65pGKFn6hxj6x3HCyR41uSB72Z0SO/Vn6JFQ= +cloud.google.com/go/storage v1.43.0 h1:CcxnSohZwizt4LCzQHWvBf1/kvtHUn7gk9QERXPyXFs= +cloud.google.com/go/storage v1.43.0/go.mod h1:ajvxEa7WmZS1PxvKRq4bq0tFT3vMd502JwstCcYv0Q0= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk= @@ -99,8 +99,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= -github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg= -github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI= +github.com/googleapis/gax-go/v2 v2.12.5 h1:8gw9KZK8TiVKB6q3zHY3SBzLnrGp6HQjyfYBYGmXdxA= +github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI= @@ -208,21 +208,19 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= -google.golang.org/api v0.183.0 h1:PNMeRDwo1pJdgNcFQ9GstuLe/noWKIc89pRWRLMvLwE= -google.golang.org/api v0.183.0/go.mod h1:q43adC5/pHoSZTx5h2mSmdF7NcyfW9JuDyIOJAgS9ZQ= +google.golang.org/api v0.187.0 h1:Mxs7VATVC2v7CY+7Xwm4ndkX71hpElcvx0D1Ji/p1eo= +google.golang.org/api v0.187.0/go.mod h1:KIHlTc4x7N7gKKuVsdmfBXN13yEEWXWFURWY6SBp2gk= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240528184218-531527333157 h1:u7WMYrIrVvs0TF5yaKwKNbcJyySYf+HAIFXxWltJOXE= -google.golang.org/genproto v0.0.0-20240528184218-531527333157/go.mod h1:ubQlAQnzejB8uZzszhrTCU2Fyp6Vi7ZE5nn0c3W8+qQ= -google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 h1:+rdxYoE3E5htTEWIe15GlN6IfvbURM//Jt0mmkmm6ZU= -google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117/go.mod h1:OimBR/bc1wPO9iV4NC2bpyjy3VnAwZh5EBPQdtaE5oo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/genproto v0.0.0-20240624140628-dc46fd24d27d h1:PksQg4dV6Sem3/HkBX+Ltq8T0ke0PKIRBNBatoDTVls= +google.golang.org/genproto v0.0.0-20240624140628-dc46fd24d27d/go.mod h1:s7iA721uChleev562UJO2OYB0PPT9CMFjV+Ce7VJH5M= +google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 h1:MuYw1wJzT+ZkybKfaOXKp5hJiZDn2iHaXRw0mRYdHSc= +google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4/go.mod h1:px9SlOOZBg1wM1zdnr8jEL4CNGUBZ+ZKYtNPApNQc4c= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d h1:k3zyW3BYYR30e8v3x0bTDdE9vpYFjZHK+HcyqkrppWk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= @@ -239,8 +237,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= -google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From f136f6aeea1a08d8d207a27d4b2f60e2dd5b36ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 7 Jul 2024 21:30:26 +0000 Subject: [PATCH 1108/1127] chore(deps): bump golang.org/x/net from 0.26.0 to 0.27.0 (#911) --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 11673fd7..d27e520b 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.6.0 - golang.org/x/net v0.26.0 + golang.org/x/net v0.27.0 sigs.k8s.io/yaml v1.4.0 ) @@ -48,10 +48,10 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.24.0 // indirect + golang.org/x/crypto v0.25.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.21.0 // indirect + golang.org/x/sys v0.22.0 // indirect golang.org/x/text v0.16.0 // indirect golang.org/x/time v0.5.0 // indirect google.golang.org/api v0.187.0 // indirect diff --git a/go.sum b/go.sum index 6a38b013..2d72c420 100644 --- a/go.sum +++ b/go.sum @@ -158,8 +158,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -173,8 +173,8 @@ golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= @@ -191,8 +191,8 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= From e58de6bbe47ab5a89b8e49256f7f169aa6083fe0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 7 Jul 2024 21:30:39 +0000 Subject: [PATCH 1109/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.54.11 to 1.54.15 (#912) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index d27e520b..6a5b22c4 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.4.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.54.11 + github.com/aws/aws-sdk-go v1.54.15 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 2d72c420..a20a9e63 100644 --- a/go.sum +++ b/go.sum @@ -36,8 +36,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.54.11 h1:Zxuv/R+IVS0B66yz4uezhxH9FN9/G2nbxejYqAMFjxk= -github.com/aws/aws-sdk-go v1.54.11/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/aws/aws-sdk-go v1.54.15 h1:ErgCEVbzuSfuZl9nR+g8FFnzjgeJ/AqAGOEWn6tgAHo= +github.com/aws/aws-sdk-go v1.54.15/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From ced16de04475906ab5139c575416d47c57c1c897 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 14 Jul 2024 18:54:08 +0000 Subject: [PATCH 1110/1127] chore(deps): bump google.golang.org/grpc from 1.64.0 to 1.64.1 (#913) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6a5b22c4..941e3d9b 100644 --- a/go.mod +++ b/go.mod @@ -58,7 +58,7 @@ require ( google.golang.org/genproto v0.0.0-20240624140628-dc46fd24d27d // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d // indirect - google.golang.org/grpc v1.64.0 // indirect + google.golang.org/grpc v1.64.1 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index a20a9e63..875c34ee 100644 --- a/go.sum +++ b/go.sum @@ -226,8 +226,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= -google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= From f48b4973c087f555ae981ecc7601eef62b992c89 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 14 Jul 2024 18:57:02 +0000 Subject: [PATCH 1111/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.54.15 to 1.54.19 (#915) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 941e3d9b..9a9029c9 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.4.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.54.15 + github.com/aws/aws-sdk-go v1.54.19 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 875c34ee..1494fc5a 100644 --- a/go.sum +++ b/go.sum @@ -36,8 +36,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.54.15 h1:ErgCEVbzuSfuZl9nR+g8FFnzjgeJ/AqAGOEWn6tgAHo= -github.com/aws/aws-sdk-go v1.54.15/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/aws/aws-sdk-go v1.54.19 h1:tyWV+07jagrNiCcGRzRhdtVjQs7Vy41NwsuOcl0IbVI= +github.com/aws/aws-sdk-go v1.54.19/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From 8eec094bd595ce724294fe5d62750679bb5dc843 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 21 Jul 2024 19:36:31 +0000 Subject: [PATCH 1112/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.54.19 to 1.54.20 (#916) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9a9029c9..02cc1c7d 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.4.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.54.19 + github.com/aws/aws-sdk-go v1.54.20 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 1494fc5a..ced8bea4 100644 --- a/go.sum +++ b/go.sum @@ -36,8 +36,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.54.19 h1:tyWV+07jagrNiCcGRzRhdtVjQs7Vy41NwsuOcl0IbVI= -github.com/aws/aws-sdk-go v1.54.19/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/aws/aws-sdk-go v1.54.20 h1:FZ2UcXya7bUkvkpf7TaPmiL7EubK0go1nlXGLRwEsoo= +github.com/aws/aws-sdk-go v1.54.20/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From cce7e314ec22077a36f18f3a59c405b57c607708 Mon Sep 17 00:00:00 2001 From: ksawarzynski <142413096+ksawarzynski@users.noreply.github.com> Date: Sun, 21 Jul 2024 21:37:13 +0200 Subject: [PATCH 1113/1127] fix segfault on gcp metadata server check (#914) --- internal/gcs/gcs.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/internal/gcs/gcs.go b/internal/gcs/gcs.go index 1c230033..bb3f0c36 100644 --- a/internal/gcs/gcs.go +++ b/internal/gcs/gcs.go @@ -18,7 +18,11 @@ var style aurora.Aurora func IsRunningInGCP() bool { resp, err := http.Get("http://metadata.google.internal") - resp.Body.Close() + + if resp != nil && resp.Body != nil { + defer resp.Body.Close() + } + return err == nil } From 4276330cf77ebfb00ad1f9ff6d16f3e63206622d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jul 2024 20:54:31 +0000 Subject: [PATCH 1114/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.54.20 to 1.55.3 (#917) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 02cc1c7d..c5c92ada 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.4.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.54.20 + github.com/aws/aws-sdk-go v1.55.3 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index ced8bea4..fc435de7 100644 --- a/go.sum +++ b/go.sum @@ -36,8 +36,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.54.20 h1:FZ2UcXya7bUkvkpf7TaPmiL7EubK0go1nlXGLRwEsoo= -github.com/aws/aws-sdk-go v1.54.20/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/aws/aws-sdk-go v1.55.3 h1:0B5hOX+mIx7I5XPOrjrHlKSDQV/+ypFZpIHOx5LOk3E= +github.com/aws/aws-sdk-go v1.55.3/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From dbda09803f2eb2b6d6696a112d4e9060df259899 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 10 Aug 2024 23:36:01 +0000 Subject: [PATCH 1115/1127] chore(deps): bump github.com/aws/aws-sdk-go from 1.55.3 to 1.55.5 (#918) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c5c92ada..fe606b50 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.4.0 github.com/Masterminds/semver v1.5.0 github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 - github.com/aws/aws-sdk-go v1.55.3 + github.com/aws/aws-sdk-go v1.55.5 github.com/imdario/mergo v0.3.16 github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index fc435de7..76ae44e6 100644 --- a/go.sum +++ b/go.sum @@ -36,8 +36,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= -github.com/aws/aws-sdk-go v1.55.3 h1:0B5hOX+mIx7I5XPOrjrHlKSDQV/+ypFZpIHOx5LOk3E= -github.com/aws/aws-sdk-go v1.55.3/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= +github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= From 5390ea8761d28c21a9c1dd9fc8bbb8db2e7f1f11 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 11 Aug 2024 21:54:23 +0000 Subject: [PATCH 1116/1127] chore(deps): bump golang.org/x/net from 0.27.0 to 0.28.0 (#919) --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index fe606b50..e76d89a6 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.6.0 - golang.org/x/net v0.27.0 + golang.org/x/net v0.28.0 sigs.k8s.io/yaml v1.4.0 ) @@ -48,11 +48,11 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.23.0 // indirect + golang.org/x/text v0.17.0 // indirect golang.org/x/time v0.5.0 // indirect google.golang.org/api v0.187.0 // indirect google.golang.org/genproto v0.0.0-20240624140628-dc46fd24d27d // indirect diff --git a/go.sum b/go.sum index 76ae44e6..39ab54e2 100644 --- a/go.sum +++ b/go.sum @@ -158,8 +158,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -173,16 +173,16 @@ golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -191,15 +191,15 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From d5cca01751dfd61207b845cee08a753add8a5a84 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Sep 2024 10:23:03 +0000 Subject: [PATCH 1117/1127] chore(deps): bump golang.org/x/net from 0.28.0 to 0.29.0 (#920) --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index e76d89a6..b356c624 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.6.0 - golang.org/x/net v0.28.0 + golang.org/x/net v0.29.0 sigs.k8s.io/yaml v1.4.0 ) @@ -48,11 +48,11 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.26.0 // indirect + golang.org/x/crypto v0.27.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.23.0 // indirect - golang.org/x/text v0.17.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/text v0.18.0 // indirect golang.org/x/time v0.5.0 // indirect google.golang.org/api v0.187.0 // indirect google.golang.org/genproto v0.0.0-20240624140628-dc46fd24d27d // indirect diff --git a/go.sum b/go.sum index 39ab54e2..2e8f85bc 100644 --- a/go.sum +++ b/go.sum @@ -158,8 +158,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -173,8 +173,8 @@ golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= @@ -191,15 +191,15 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= -golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From 9f1ea20e04d3ddf2e0974f2e1114aa25d71f7f4d Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Sun, 29 Sep 2024 11:34:28 +0100 Subject: [PATCH 1118/1127] release: prepare v3.17.1 --- .version | 2 +- README.md | 6 +++--- internal/app/main.go | 2 +- release-notes.md | 9 +++++---- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.version b/.version index 597e63fe..794341d1 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v3.17.0 +v3.17.1 diff --git a/README.md b/README.md index 3ed1f6e9..c360485b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.17.0&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) +[![GitHub version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=gh&type=6&v=v3.17.1&x2=0)](https://github.com/Praqma/helmsman/releases) [![CircleCI](https://circleci.com/gh/Praqma/helmsman/tree/master.svg?style=svg)](https://circleci.com/gh/Praqma/helmsman/tree/master) ![helmsman-logo](docs/images/helmsman.png) @@ -77,9 +77,9 @@ Check the [releases page](https://github.com/Praqma/Helmsman/releases) for the d ```sh # on Linux -curl -L https://github.com/Praqma/helmsman/releases/download/v3.17.0/helmsman_3.17.0_linux_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.17.1/helmsman_3.17.0_linux_amd64.tar.gz | tar zx # on MacOS -curl -L https://github.com/Praqma/helmsman/releases/download/v3.17.0/helmsman_3.17.0_darwin_amd64.tar.gz | tar zx +curl -L https://github.com/Praqma/helmsman/releases/download/v3.17.1/helmsman_3.17.0_darwin_amd64.tar.gz | tar zx mv helmsman /usr/local/bin/helmsman ``` diff --git a/internal/app/main.go b/internal/app/main.go index 8bc0852c..92ef8f2b 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -8,7 +8,7 @@ import ( const ( helmBin = "helm" kubectlBin = "kubectl" - appVersion = "v3.17.0" + appVersion = "v3.17.1" tempFilesDir = ".helmsman-tmp" defaultContextName = "default" resourcePool = 10 diff --git a/release-notes.md b/release-notes.md index be70974b..88f7cf25 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,10 +1,11 @@ -# v3.17.0 +# v3.17.1 ## New feature -- Add support for hiera-eyaml-gkms (#776) -- Add optioned recursive environment variables expansion (#793) +- Allow recursive environment variable expansion (#793) ## Fixes and improvements -- Remove 'priority' field from -spec docs (#781) +- Updated dependencies (dependabot) +- Fixed priorities when loading multiple spec files (#781) +- Improved docs (#807) From 31c10fcdb1b4ba1a319b3f7939bca5bded85f671 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 6 Oct 2024 16:14:42 +0000 Subject: [PATCH 1119/1127] chore(deps): bump golang.org/x/net from 0.29.0 to 0.30.0 (#922) --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index b356c624..adae7183 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.6.0 - golang.org/x/net v0.29.0 + golang.org/x/net v0.30.0 sigs.k8s.io/yaml v1.4.0 ) @@ -48,11 +48,11 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/time v0.5.0 // indirect google.golang.org/api v0.187.0 // indirect google.golang.org/genproto v0.0.0-20240624140628-dc46fd24d27d // indirect diff --git a/go.sum b/go.sum index 2e8f85bc..57384ee8 100644 --- a/go.sum +++ b/go.sum @@ -158,8 +158,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -173,8 +173,8 @@ golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= @@ -191,15 +191,15 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From 862dcfb1035ef1c52a8a55a9042ba55fa776bcd9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 6 Oct 2024 16:19:00 +0000 Subject: [PATCH 1120/1127] chore(deps): bump cloud.google.com/go/storage from 1.43.0 to 1.44.0 (#921) --- go.mod | 61 ++++++++++++++++---------- go.sum | 136 ++++++++++++++++++++++++++++++++++++--------------------- 2 files changed, 123 insertions(+), 74 deletions(-) diff --git a/go.mod b/go.mod index adae7183..fda807ae 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Praqma/helmsman go 1.21 require ( - cloud.google.com/go/storage v1.43.0 + cloud.google.com/go/storage v1.44.0 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.15.0 github.com/BurntSushi/toml v1.4.0 @@ -19,46 +19,59 @@ require ( ) require ( - cloud.google.com/go v0.115.0 // indirect - cloud.google.com/go/auth v0.6.1 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect - cloud.google.com/go/compute/metadata v0.3.0 // indirect - cloud.google.com/go/iam v1.1.8 // indirect + cel.dev/expr v0.16.1 // indirect + cloud.google.com/go v0.115.1 // indirect + cloud.google.com/go/auth v0.9.3 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect + cloud.google.com/go/compute/metadata v0.5.1 // indirect + cloud.google.com/go/iam v1.2.1 // indirect + cloud.google.com/go/monitoring v1.21.0 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.24.1 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/buger/jsonparser v1.1.1 // indirect + github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 // indirect + github.com/envoyproxy/go-control-plane v0.13.0 // indirect + github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang-jwt/jwt/v4 v4.3.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.4 // indirect - github.com/google/s2a-go v0.1.7 // indirect + github.com/google/s2a-go v0.1.8 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect - github.com/googleapis/gax-go/v2 v2.12.5 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect + github.com/googleapis/gax-go/v2 v2.13.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/kr/text v0.2.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-ieproxy v0.0.1 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect - go.opentelemetry.io/otel v1.24.0 // indirect - go.opentelemetry.io/otel/metric v1.24.0 // indirect - go.opentelemetry.io/otel/trace v1.24.0 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.29.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect + go.opentelemetry.io/otel v1.29.0 // indirect + go.opentelemetry.io/otel/metric v1.29.0 // indirect + go.opentelemetry.io/otel/sdk v1.29.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect + go.opentelemetry.io/otel/trace v1.29.0 // indirect golang.org/x/crypto v0.28.0 // indirect - golang.org/x/oauth2 v0.21.0 // indirect + golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.26.0 // indirect golang.org/x/text v0.19.0 // indirect - golang.org/x/time v0.5.0 // indirect - google.golang.org/api v0.187.0 // indirect - google.golang.org/genproto v0.0.0-20240624140628-dc46fd24d27d // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d // indirect - google.golang.org/grpc v1.64.1 // indirect + golang.org/x/time v0.6.0 // indirect + google.golang.org/api v0.197.0 // indirect + google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/grpc v1.66.2 // indirect + google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 57384ee8..7530c660 100644 --- a/go.sum +++ b/go.sum @@ -1,18 +1,26 @@ +cel.dev/expr v0.16.1 h1:NR0+oFYzR1CqLFhTAqg3ql59G9VfN8fKq1TCHJ6gq1g= +cel.dev/expr v0.16.1/go.mod h1:AsGA5zb3WruAEQeQng1RZdGEXmBj0jvMWh6l5SnNuC8= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14= -cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU= -cloud.google.com/go/auth v0.6.1 h1:T0Zw1XM5c1GlpN2HYr2s+m3vr1p2wy+8VN+Z1FKxW38= -cloud.google.com/go/auth v0.6.1/go.mod h1:eFHG7zDzbXHKmjJddFG/rBlcGp6t25SwRUiEQSlO4x4= -cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= -cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= -cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= -cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= -cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0= -cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE= -cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU= -cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng= -cloud.google.com/go/storage v1.43.0 h1:CcxnSohZwizt4LCzQHWvBf1/kvtHUn7gk9QERXPyXFs= -cloud.google.com/go/storage v1.43.0/go.mod h1:ajvxEa7WmZS1PxvKRq4bq0tFT3vMd502JwstCcYv0Q0= +cloud.google.com/go v0.115.1 h1:Jo0SM9cQnSkYfp44+v+NQXHpcHqlnRJk2qxh6yvxxxQ= +cloud.google.com/go v0.115.1/go.mod h1:DuujITeaufu3gL68/lOFIirVNJwQeyf5UXyi+Wbgknc= +cloud.google.com/go/auth v0.9.3 h1:VOEUIAADkkLtyfr3BLa3R8Ed/j6w1jTBmARx+wb5w5U= +cloud.google.com/go/auth v0.9.3/go.mod h1:7z6VY+7h3KUdRov5F1i8NDP5ZzWKYmEPO842BgCsmTk= +cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY= +cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc= +cloud.google.com/go/compute/metadata v0.5.1 h1:NM6oZeZNlYjiwYje+sYFjEpP0Q0zCan1bmQW/KmIrGs= +cloud.google.com/go/compute/metadata v0.5.1/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k= +cloud.google.com/go/iam v1.2.1 h1:QFct02HRb7H12J/3utj0qf5tobFh9V4vR6h9eX5EBRU= +cloud.google.com/go/iam v1.2.1/go.mod h1:3VUIJDPpwT6p/amXRC5GY8fCCh70lxPygguVtI0Z4/g= +cloud.google.com/go/logging v1.11.0 h1:v3ktVzXMV7CwHq1MBF65wcqLMA7i+z3YxbUsoK7mOKs= +cloud.google.com/go/logging v1.11.0/go.mod h1:5LDiJC/RxTt+fHc1LAt20R9TKiUTReDg6RuuFOZ67+A= +cloud.google.com/go/longrunning v0.6.1 h1:lOLTFxYpr8hcRtcwWir5ITh1PAKUD/sG2lKrTSYjyMc= +cloud.google.com/go/longrunning v0.6.1/go.mod h1:nHISoOZpBcmlwbJmiVk5oDRz0qG/ZxPynEGs1iZ79s0= +cloud.google.com/go/monitoring v1.21.0 h1:EMc0tB+d3lUewT2NzKC/hr8cSR9WsUieVywzIHetGro= +cloud.google.com/go/monitoring v1.21.0/go.mod h1:tuJ+KNDdJbetSsbSGTqnaBvbauS5kr3Q/koy3Up6r+4= +cloud.google.com/go/storage v1.44.0 h1:abBzXf4UJKMmQ04xxJf9dYM/fNl24KHoTuBjyJDX2AI= +cloud.google.com/go/storage v1.44.0/go.mod h1:wpPblkIuMP5jCB/E48Pz9zIo2S/zD8g+ITmxKkPCITE= +cloud.google.com/go/trace v1.11.0 h1:UHX6cOJm45Zw/KIbqHe4kII8PupLt/V5tscZUkeiJVI= +cloud.google.com/go/trace v1.11.0/go.mod h1:Aiemdi52635dBR7o3zuc9lLjXo3BwGaChEjCa3tJNmM= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk= @@ -32,6 +40,14 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.24.1 h1:pB2F2JKCj1Znmp2rwxxt1J0Fg0wezTMgWYk5Mpbi1kg= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.24.1/go.mod h1:itPGVDKf9cC/ov4MdvJ2QZ0khw4bfoo9jzwTJlaxy2k= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 h1:UQ0AhxogsIRZDkElkblfnwjc3IaltCm2HUMvezQaL7s= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1/go.mod h1:jyqM3eLpJ3IbIFDTKVz2rF9T/xWGW0rIriGwnz8l9Tk= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.48.1 h1:oTX4vsorBZo/Zdum6OKPA4o7544hm6smoRv1QjpTwGo= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.48.1/go.mod h1:0wEl7vrAD8mehJyohS9HZy+WyEOaQO2mJx86Cvh93kM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 h1:8nn+rsCvTq9axyEh382S0PFLBeaFwNsT43IrPWzctRU= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1/go.mod h1:viRWSEhtMZqz1rhwmOVKkWl6SwmVowfL9O2YR5gI2PE= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU= @@ -43,22 +59,31 @@ github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xW github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= +github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 h1:QVw89YDxXxEe+l8gU8ETbOasdwEV+avkR75ZzsVV9WI= +github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.13.0 h1:HzkeUz1Knt+3bK+8LG1bxOO/jzWZmdxpwC51i202les= +github.com/envoyproxy/go-control-plane v0.13.0/go.mod h1:GRaKG3dwvFoTg4nj7aXdZnvMg4d7nvT/wl9WgVXn3Q8= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM= +github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= @@ -91,16 +116,16 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= -github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= -github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= +github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= -github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= -github.com/googleapis/gax-go/v2 v2.12.5 h1:8gw9KZK8TiVKB6q3zHY3SBzLnrGp6HQjyfYBYGmXdxA= -github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E= +github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= +github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= +github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s= +github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI= @@ -110,8 +135,9 @@ github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHW github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -124,9 +150,13 @@ github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqf github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -141,18 +171,22 @@ github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/ github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= -go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= -go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= -go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= -go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= -go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= -go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= -go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= -go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +go.opentelemetry.io/contrib/detectors/gcp v1.29.0 h1:TiaiXB4DpGD3sdzNlYQxruQngn5Apwzi1X0DRhuGvDQ= +go.opentelemetry.io/contrib/detectors/gcp v1.29.0/go.mod h1:GW2aWZNwR2ZxDLdv8OyC2G8zkRoQBuURgV7RPQgcPoU= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= +go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= +go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= +go.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY= +go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= +go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -176,8 +210,8 @@ golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= -golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -200,34 +234,36 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.187.0 h1:Mxs7VATVC2v7CY+7Xwm4ndkX71hpElcvx0D1Ji/p1eo= -google.golang.org/api v0.187.0/go.mod h1:KIHlTc4x7N7gKKuVsdmfBXN13yEEWXWFURWY6SBp2gk= +google.golang.org/api v0.197.0 h1:x6CwqQLsFiA5JKAiGyGBjc2bNtHtLddhJCE2IKuhhcQ= +google.golang.org/api v0.197.0/go.mod h1:AuOuo20GoQ331nq7DquGHlU6d+2wN2fZ8O0ta60nRNw= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240624140628-dc46fd24d27d h1:PksQg4dV6Sem3/HkBX+Ltq8T0ke0PKIRBNBatoDTVls= -google.golang.org/genproto v0.0.0-20240624140628-dc46fd24d27d/go.mod h1:s7iA721uChleev562UJO2OYB0PPT9CMFjV+Ce7VJH5M= -google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 h1:MuYw1wJzT+ZkybKfaOXKp5hJiZDn2iHaXRw0mRYdHSc= -google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4/go.mod h1:px9SlOOZBg1wM1zdnr8jEL4CNGUBZ+ZKYtNPApNQc4c= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d h1:k3zyW3BYYR30e8v3x0bTDdE9vpYFjZHK+HcyqkrppWk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 h1:BulPr26Jqjnd4eYDVe+YvyR7Yc2vJGkO5/0UxD0/jZU= +google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:hL97c3SYopEHblzpxRL4lSs523++l8DYxGM1FQiYmb4= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= -google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= +google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo= +google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a h1:UIpYSuWdWHSzjwcAFRLjKcPXFZVVLXGEM23W+NWqipw= +google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a/go.mod h1:9i1T9n4ZinTUZGgzENMi8MDDgbGC5mqTS75JAv6xN3A= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= From fff9d1322b8770be016e3cb1dbcedca89cb5787e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 20 Oct 2024 15:46:17 +0000 Subject: [PATCH 1121/1127] chore(deps): bump cloud.google.com/go/storage from 1.44.0 to 1.45.0 (#923) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index fda807ae..fe0fc392 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Praqma/helmsman go 1.21 require ( - cloud.google.com/go/storage v1.44.0 + cloud.google.com/go/storage v1.45.0 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.15.0 github.com/BurntSushi/toml v1.4.0 diff --git a/go.sum b/go.sum index 7530c660..2556aa81 100644 --- a/go.sum +++ b/go.sum @@ -17,8 +17,8 @@ cloud.google.com/go/longrunning v0.6.1 h1:lOLTFxYpr8hcRtcwWir5ITh1PAKUD/sG2lKrTS cloud.google.com/go/longrunning v0.6.1/go.mod h1:nHISoOZpBcmlwbJmiVk5oDRz0qG/ZxPynEGs1iZ79s0= cloud.google.com/go/monitoring v1.21.0 h1:EMc0tB+d3lUewT2NzKC/hr8cSR9WsUieVywzIHetGro= cloud.google.com/go/monitoring v1.21.0/go.mod h1:tuJ+KNDdJbetSsbSGTqnaBvbauS5kr3Q/koy3Up6r+4= -cloud.google.com/go/storage v1.44.0 h1:abBzXf4UJKMmQ04xxJf9dYM/fNl24KHoTuBjyJDX2AI= -cloud.google.com/go/storage v1.44.0/go.mod h1:wpPblkIuMP5jCB/E48Pz9zIo2S/zD8g+ITmxKkPCITE= +cloud.google.com/go/storage v1.45.0 h1:5av0QcIVj77t+44mV4gffFC/LscFRUhto6UBMB5SimM= +cloud.google.com/go/storage v1.45.0/go.mod h1:wpPblkIuMP5jCB/E48Pz9zIo2S/zD8g+ITmxKkPCITE= cloud.google.com/go/trace v1.11.0 h1:UHX6cOJm45Zw/KIbqHe4kII8PupLt/V5tscZUkeiJVI= cloud.google.com/go/trace v1.11.0/go.mod h1:Aiemdi52635dBR7o3zuc9lLjXo3BwGaChEjCa3tJNmM= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= From 7ce11cc32b5861510c2c15949ab15f6bb43cde28 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 3 Nov 2024 22:42:52 +0000 Subject: [PATCH 1122/1127] chore(deps): bump cloud.google.com/go/storage from 1.45.0 to 1.46.0 (#926) --- go.mod | 26 +++++++++++++------------- go.sum | 56 ++++++++++++++++++++++++++++---------------------------- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/go.mod b/go.mod index fe0fc392..d8a1cbca 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Praqma/helmsman go 1.21 require ( - cloud.google.com/go/storage v1.45.0 + cloud.google.com/go/storage v1.46.0 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.15.0 github.com/BurntSushi/toml v1.4.0 @@ -20,12 +20,12 @@ require ( require ( cel.dev/expr v0.16.1 // indirect - cloud.google.com/go v0.115.1 // indirect - cloud.google.com/go/auth v0.9.3 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect - cloud.google.com/go/compute/metadata v0.5.1 // indirect + cloud.google.com/go v0.116.0 // indirect + cloud.google.com/go/auth v0.10.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.5 // indirect + cloud.google.com/go/compute/metadata v0.5.2 // indirect cloud.google.com/go/iam v1.2.1 // indirect - cloud.google.com/go/monitoring v1.21.0 // indirect + cloud.google.com/go/monitoring v1.21.1 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.24.1 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 // indirect @@ -65,14 +65,14 @@ require ( golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.26.0 // indirect golang.org/x/text v0.19.0 // indirect - golang.org/x/time v0.6.0 // indirect - google.golang.org/api v0.197.0 // indirect - google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect - google.golang.org/grpc v1.66.2 // indirect + golang.org/x/time v0.7.0 // indirect + google.golang.org/api v0.203.0 // indirect + google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect + google.golang.org/grpc v1.67.1 // indirect google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a // indirect - google.golang.org/protobuf v1.34.2 // indirect + google.golang.org/protobuf v1.35.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 2556aa81..229e925f 100644 --- a/go.sum +++ b/go.sum @@ -1,26 +1,26 @@ cel.dev/expr v0.16.1 h1:NR0+oFYzR1CqLFhTAqg3ql59G9VfN8fKq1TCHJ6gq1g= cel.dev/expr v0.16.1/go.mod h1:AsGA5zb3WruAEQeQng1RZdGEXmBj0jvMWh6l5SnNuC8= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.115.1 h1:Jo0SM9cQnSkYfp44+v+NQXHpcHqlnRJk2qxh6yvxxxQ= -cloud.google.com/go v0.115.1/go.mod h1:DuujITeaufu3gL68/lOFIirVNJwQeyf5UXyi+Wbgknc= -cloud.google.com/go/auth v0.9.3 h1:VOEUIAADkkLtyfr3BLa3R8Ed/j6w1jTBmARx+wb5w5U= -cloud.google.com/go/auth v0.9.3/go.mod h1:7z6VY+7h3KUdRov5F1i8NDP5ZzWKYmEPO842BgCsmTk= -cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY= -cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc= -cloud.google.com/go/compute/metadata v0.5.1 h1:NM6oZeZNlYjiwYje+sYFjEpP0Q0zCan1bmQW/KmIrGs= -cloud.google.com/go/compute/metadata v0.5.1/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k= +cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= +cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= +cloud.google.com/go/auth v0.10.0 h1:tWlkvFAh+wwTOzXIjrwM64karR1iTBZ/GRr0S/DULYo= +cloud.google.com/go/auth v0.10.0/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= +cloud.google.com/go/auth/oauth2adapt v0.2.5 h1:2p29+dePqsCHPP1bqDJcKj4qxRyYCcbzKpFyKGt3MTk= +cloud.google.com/go/auth/oauth2adapt v0.2.5/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= +cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo= +cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k= cloud.google.com/go/iam v1.2.1 h1:QFct02HRb7H12J/3utj0qf5tobFh9V4vR6h9eX5EBRU= cloud.google.com/go/iam v1.2.1/go.mod h1:3VUIJDPpwT6p/amXRC5GY8fCCh70lxPygguVtI0Z4/g= cloud.google.com/go/logging v1.11.0 h1:v3ktVzXMV7CwHq1MBF65wcqLMA7i+z3YxbUsoK7mOKs= cloud.google.com/go/logging v1.11.0/go.mod h1:5LDiJC/RxTt+fHc1LAt20R9TKiUTReDg6RuuFOZ67+A= cloud.google.com/go/longrunning v0.6.1 h1:lOLTFxYpr8hcRtcwWir5ITh1PAKUD/sG2lKrTSYjyMc= cloud.google.com/go/longrunning v0.6.1/go.mod h1:nHISoOZpBcmlwbJmiVk5oDRz0qG/ZxPynEGs1iZ79s0= -cloud.google.com/go/monitoring v1.21.0 h1:EMc0tB+d3lUewT2NzKC/hr8cSR9WsUieVywzIHetGro= -cloud.google.com/go/monitoring v1.21.0/go.mod h1:tuJ+KNDdJbetSsbSGTqnaBvbauS5kr3Q/koy3Up6r+4= -cloud.google.com/go/storage v1.45.0 h1:5av0QcIVj77t+44mV4gffFC/LscFRUhto6UBMB5SimM= -cloud.google.com/go/storage v1.45.0/go.mod h1:wpPblkIuMP5jCB/E48Pz9zIo2S/zD8g+ITmxKkPCITE= -cloud.google.com/go/trace v1.11.0 h1:UHX6cOJm45Zw/KIbqHe4kII8PupLt/V5tscZUkeiJVI= -cloud.google.com/go/trace v1.11.0/go.mod h1:Aiemdi52635dBR7o3zuc9lLjXo3BwGaChEjCa3tJNmM= +cloud.google.com/go/monitoring v1.21.1 h1:zWtbIoBMnU5LP9A/fz8LmWMGHpk4skdfeiaa66QdFGc= +cloud.google.com/go/monitoring v1.21.1/go.mod h1:Rj++LKrlht9uBi8+Eb530dIrzG/cU/lB8mt+lbeFK1c= +cloud.google.com/go/storage v1.46.0 h1:OTXISBpFd8KaA2ClT3K3oRk8UGOcTHtrZ1bW88xKiic= +cloud.google.com/go/storage v1.46.0/go.mod h1:lM+gMAW91EfXIeMTBmixRsKL/XCxysytoAgduVikjMk= +cloud.google.com/go/trace v1.11.1 h1:UNqdP+HYYtnm6lb91aNA5JQ0X14GnxkABGlfz2PzPew= +cloud.google.com/go/trace v1.11.1/go.mod h1:IQKNQuBzH72EGaXEodKlNJrWykGZxet2zgjtS60OtjA= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk= @@ -234,34 +234,34 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= -golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= +golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.197.0 h1:x6CwqQLsFiA5JKAiGyGBjc2bNtHtLddhJCE2IKuhhcQ= -google.golang.org/api v0.197.0/go.mod h1:AuOuo20GoQ331nq7DquGHlU6d+2wN2fZ8O0ta60nRNw= +google.golang.org/api v0.203.0 h1:SrEeuwU3S11Wlscsn+LA1kb/Y5xT8uggJSkIhD08NAU= +google.golang.org/api v0.203.0/go.mod h1:BuOVyCSYEPwJb3npWvDnNmFI92f3GeRnHNkETneT3SI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 h1:BulPr26Jqjnd4eYDVe+YvyR7Yc2vJGkO5/0UxD0/jZU= -google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:hL97c3SYopEHblzpxRL4lSs523++l8DYxGM1FQiYmb4= -google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= -google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53 h1:Df6WuGvthPzc+JiQ/G+m+sNX24kc0aTBqoDN/0yyykE= +google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53/go.mod h1:fheguH3Am2dGp1LfXkrvwqC/KlFq8F0nLq3LryOMrrE= +google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg= +google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo= -google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a h1:UIpYSuWdWHSzjwcAFRLjKcPXFZVVLXGEM23W+NWqipw= google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a/go.mod h1:9i1T9n4ZinTUZGgzENMi8MDDgbGC5mqTS75JAv6xN3A= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -273,8 +273,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From dadf5fa910fb25e48b617cb71f7f380d53970ea1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 10 Nov 2024 19:53:29 +0000 Subject: [PATCH 1123/1127] chore(deps): bump golang.org/x/net from 0.30.0 to 0.31.0 (#927) --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index d8a1cbca..c2e266cc 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.6.0 - golang.org/x/net v0.30.0 + golang.org/x/net v0.31.0 sigs.k8s.io/yaml v1.4.0 ) @@ -60,11 +60,11 @@ require ( go.opentelemetry.io/otel/sdk v1.29.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect go.opentelemetry.io/otel/trace v1.29.0 // indirect - golang.org/x/crypto v0.28.0 // indirect + golang.org/x/crypto v0.29.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/text v0.19.0 // indirect + golang.org/x/sync v0.9.0 // indirect + golang.org/x/sys v0.27.0 // indirect + golang.org/x/text v0.20.0 // indirect golang.org/x/time v0.7.0 // indirect google.golang.org/api v0.203.0 // indirect google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53 // indirect diff --git a/go.sum b/go.sum index 229e925f..58db6e89 100644 --- a/go.sum +++ b/go.sum @@ -192,8 +192,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -207,16 +207,16 @@ golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -225,15 +225,15 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From 19a7f340a96d815774af7e2e6394bb851e014c05 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Nov 2024 22:40:11 +0000 Subject: [PATCH 1124/1127] chore(deps): bump github.com/golang-jwt/jwt/v4 from 4.3.0 to 4.5.1 (#928) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c2e266cc..54df56c4 100644 --- a/go.mod +++ b/go.mod @@ -40,7 +40,7 @@ require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/golang-jwt/jwt/v4 v4.3.0 // indirect + github.com/golang-jwt/jwt/v4 v4.5.1 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/s2a-go v0.1.8 // indirect github.com/google/uuid v1.6.0 // indirect diff --git a/go.sum b/go.sum index 58db6e89..6dce2041 100644 --- a/go.sum +++ b/go.sum @@ -87,8 +87,8 @@ github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ4 github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog= -github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= +github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= From cc7f6e2d421124fecdc45c3a337e354df5a53280 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Nov 2024 00:11:41 +0000 Subject: [PATCH 1125/1127] chore(deps): bump cloud.google.com/go/storage from 1.46.0 to 1.47.0 (#929) --- go.mod | 4 ++-- go.sum | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 54df56c4..a7476695 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Praqma/helmsman go 1.21 require ( - cloud.google.com/go/storage v1.46.0 + cloud.google.com/go/storage v1.47.0 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.15.0 github.com/BurntSushi/toml v1.4.0 @@ -21,7 +21,7 @@ require ( require ( cel.dev/expr v0.16.1 // indirect cloud.google.com/go v0.116.0 // indirect - cloud.google.com/go/auth v0.10.0 // indirect + cloud.google.com/go/auth v0.10.2 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.5 // indirect cloud.google.com/go/compute/metadata v0.5.2 // indirect cloud.google.com/go/iam v1.2.1 // indirect diff --git a/go.sum b/go.sum index 6dce2041..fb50e948 100644 --- a/go.sum +++ b/go.sum @@ -3,8 +3,8 @@ cel.dev/expr v0.16.1/go.mod h1:AsGA5zb3WruAEQeQng1RZdGEXmBj0jvMWh6l5SnNuC8= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= -cloud.google.com/go/auth v0.10.0 h1:tWlkvFAh+wwTOzXIjrwM64karR1iTBZ/GRr0S/DULYo= -cloud.google.com/go/auth v0.10.0/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= +cloud.google.com/go/auth v0.10.2 h1:oKF7rgBfSHdp/kuhXtqU/tNDr0mZqhYbEh+6SiqzkKo= +cloud.google.com/go/auth v0.10.2/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= cloud.google.com/go/auth/oauth2adapt v0.2.5 h1:2p29+dePqsCHPP1bqDJcKj4qxRyYCcbzKpFyKGt3MTk= cloud.google.com/go/auth/oauth2adapt v0.2.5/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo= @@ -17,8 +17,8 @@ cloud.google.com/go/longrunning v0.6.1 h1:lOLTFxYpr8hcRtcwWir5ITh1PAKUD/sG2lKrTS cloud.google.com/go/longrunning v0.6.1/go.mod h1:nHISoOZpBcmlwbJmiVk5oDRz0qG/ZxPynEGs1iZ79s0= cloud.google.com/go/monitoring v1.21.1 h1:zWtbIoBMnU5LP9A/fz8LmWMGHpk4skdfeiaa66QdFGc= cloud.google.com/go/monitoring v1.21.1/go.mod h1:Rj++LKrlht9uBi8+Eb530dIrzG/cU/lB8mt+lbeFK1c= -cloud.google.com/go/storage v1.46.0 h1:OTXISBpFd8KaA2ClT3K3oRk8UGOcTHtrZ1bW88xKiic= -cloud.google.com/go/storage v1.46.0/go.mod h1:lM+gMAW91EfXIeMTBmixRsKL/XCxysytoAgduVikjMk= +cloud.google.com/go/storage v1.47.0 h1:ajqgt30fnOMmLfWfu1PWcb+V9Dxz6n+9WKjdNg5R4HM= +cloud.google.com/go/storage v1.47.0/go.mod h1:Ks0vP374w0PW6jOUameJbapbQKXqkjGd/OJRp2fb9IQ= cloud.google.com/go/trace v1.11.1 h1:UNqdP+HYYtnm6lb91aNA5JQ0X14GnxkABGlfz2PzPew= cloud.google.com/go/trace v1.11.1/go.mod h1:IQKNQuBzH72EGaXEodKlNJrWykGZxet2zgjtS60OtjA= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= @@ -179,6 +179,8 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+n go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I= go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= From 3dc75547caa7814d8a3ca757341040b63a069114 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 8 Dec 2024 23:20:44 +0000 Subject: [PATCH 1126/1127] chore(deps): bump cloud.google.com/go/storage from 1.47.0 to 1.48.0 (#930) --- go.mod | 28 ++++++++++++------------ go.sum | 68 +++++++++++++++++++++++++++++----------------------------- 2 files changed, 48 insertions(+), 48 deletions(-) diff --git a/go.mod b/go.mod index a7476695..97d7e314 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Praqma/helmsman go 1.21 require ( - cloud.google.com/go/storage v1.47.0 + cloud.google.com/go/storage v1.48.0 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.15.0 github.com/BurntSushi/toml v1.4.0 @@ -21,11 +21,11 @@ require ( require ( cel.dev/expr v0.16.1 // indirect cloud.google.com/go v0.116.0 // indirect - cloud.google.com/go/auth v0.10.2 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.5 // indirect + cloud.google.com/go/auth v0.11.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect cloud.google.com/go/compute/metadata v0.5.2 // indirect - cloud.google.com/go/iam v1.2.1 // indirect - cloud.google.com/go/monitoring v1.21.1 // indirect + cloud.google.com/go/iam v1.2.2 // indirect + cloud.google.com/go/monitoring v1.21.2 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.24.1 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 // indirect @@ -45,7 +45,7 @@ require ( github.com/google/s2a-go v0.1.8 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect - github.com/googleapis/gax-go/v2 v2.13.0 // indirect + github.com/googleapis/gax-go/v2 v2.14.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-ieproxy v0.0.1 // indirect @@ -61,18 +61,18 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect go.opentelemetry.io/otel/trace v1.29.0 // indirect golang.org/x/crypto v0.29.0 // indirect - golang.org/x/oauth2 v0.23.0 // indirect + golang.org/x/oauth2 v0.24.0 // indirect golang.org/x/sync v0.9.0 // indirect golang.org/x/sys v0.27.0 // indirect golang.org/x/text v0.20.0 // indirect - golang.org/x/time v0.7.0 // indirect - google.golang.org/api v0.203.0 // indirect - google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect - google.golang.org/grpc v1.67.1 // indirect + golang.org/x/time v0.8.0 // indirect + google.golang.org/api v0.210.0 // indirect + google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241113202542-65e8d215514f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 // indirect + google.golang.org/grpc v1.67.2 // indirect google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a // indirect - google.golang.org/protobuf v1.35.1 // indirect + google.golang.org/protobuf v1.35.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index fb50e948..ff294f48 100644 --- a/go.sum +++ b/go.sum @@ -3,24 +3,24 @@ cel.dev/expr v0.16.1/go.mod h1:AsGA5zb3WruAEQeQng1RZdGEXmBj0jvMWh6l5SnNuC8= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= -cloud.google.com/go/auth v0.10.2 h1:oKF7rgBfSHdp/kuhXtqU/tNDr0mZqhYbEh+6SiqzkKo= -cloud.google.com/go/auth v0.10.2/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= -cloud.google.com/go/auth/oauth2adapt v0.2.5 h1:2p29+dePqsCHPP1bqDJcKj4qxRyYCcbzKpFyKGt3MTk= -cloud.google.com/go/auth/oauth2adapt v0.2.5/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= +cloud.google.com/go/auth v0.11.0 h1:Ic5SZz2lsvbYcWT5dfjNWgw6tTlGi2Wc8hyQSC9BstA= +cloud.google.com/go/auth v0.11.0/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= +cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU= +cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo= cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k= -cloud.google.com/go/iam v1.2.1 h1:QFct02HRb7H12J/3utj0qf5tobFh9V4vR6h9eX5EBRU= -cloud.google.com/go/iam v1.2.1/go.mod h1:3VUIJDPpwT6p/amXRC5GY8fCCh70lxPygguVtI0Z4/g= -cloud.google.com/go/logging v1.11.0 h1:v3ktVzXMV7CwHq1MBF65wcqLMA7i+z3YxbUsoK7mOKs= -cloud.google.com/go/logging v1.11.0/go.mod h1:5LDiJC/RxTt+fHc1LAt20R9TKiUTReDg6RuuFOZ67+A= -cloud.google.com/go/longrunning v0.6.1 h1:lOLTFxYpr8hcRtcwWir5ITh1PAKUD/sG2lKrTSYjyMc= -cloud.google.com/go/longrunning v0.6.1/go.mod h1:nHISoOZpBcmlwbJmiVk5oDRz0qG/ZxPynEGs1iZ79s0= -cloud.google.com/go/monitoring v1.21.1 h1:zWtbIoBMnU5LP9A/fz8LmWMGHpk4skdfeiaa66QdFGc= -cloud.google.com/go/monitoring v1.21.1/go.mod h1:Rj++LKrlht9uBi8+Eb530dIrzG/cU/lB8mt+lbeFK1c= -cloud.google.com/go/storage v1.47.0 h1:ajqgt30fnOMmLfWfu1PWcb+V9Dxz6n+9WKjdNg5R4HM= -cloud.google.com/go/storage v1.47.0/go.mod h1:Ks0vP374w0PW6jOUameJbapbQKXqkjGd/OJRp2fb9IQ= -cloud.google.com/go/trace v1.11.1 h1:UNqdP+HYYtnm6lb91aNA5JQ0X14GnxkABGlfz2PzPew= -cloud.google.com/go/trace v1.11.1/go.mod h1:IQKNQuBzH72EGaXEodKlNJrWykGZxet2zgjtS60OtjA= +cloud.google.com/go/iam v1.2.2 h1:ozUSofHUGf/F4tCNy/mu9tHLTaxZFLOUiKzjcgWHGIA= +cloud.google.com/go/iam v1.2.2/go.mod h1:0Ys8ccaZHdI1dEUilwzqng/6ps2YB6vRsjIe00/+6JY= +cloud.google.com/go/logging v1.12.0 h1:ex1igYcGFd4S/RZWOCU51StlIEuey5bjqwH9ZYjHibk= +cloud.google.com/go/logging v1.12.0/go.mod h1:wwYBt5HlYP1InnrtYI0wtwttpVU1rifnMT7RejksUAM= +cloud.google.com/go/longrunning v0.6.2 h1:xjDfh1pQcWPEvnfjZmwjKQEcHnpz6lHjfy7Fo0MK+hc= +cloud.google.com/go/longrunning v0.6.2/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI= +cloud.google.com/go/monitoring v1.21.2 h1:FChwVtClH19E7pJ+e0xUhJPGksctZNVOk2UhMmblmdU= +cloud.google.com/go/monitoring v1.21.2/go.mod h1:hS3pXvaG8KgWTSz+dAdyzPrGUYmi2Q+WFX8g2hqVEZU= +cloud.google.com/go/storage v1.48.0 h1:FhBDHACbVtdPx7S/AbcKujPWiHvfO6F8OXGgCEbB2+o= +cloud.google.com/go/storage v1.48.0/go.mod h1:aFoDYNMAjv67lp+xcuZqjUKv/ctmplzQ3wJgodA7b+M= +cloud.google.com/go/trace v1.11.2 h1:4ZmaBdL8Ng/ajrgKqY5jfvzqMXbrDcBsUGXOT9aqTtI= +cloud.google.com/go/trace v1.11.2/go.mod h1:bn7OwXd4pd5rFuAnTrzBuoZ4ax2XQeG3qNgYmfCy0Io= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk= @@ -124,8 +124,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= -github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s= -github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= +github.com/googleapis/gax-go/v2 v2.14.0 h1:f+jMrjBPl+DL9nI4IQzLUxMq7XrAqFYB7hBPqMNIe8o= +github.com/googleapis/gax-go/v2 v2.14.0/go.mod h1:lhBCnjdLrWRaPvLWhmc8IS24m9mr07qSYnHncrgo+zk= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI= @@ -212,8 +212,8 @@ golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= -golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= +golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -236,34 +236,34 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= -golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= -golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= +golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.203.0 h1:SrEeuwU3S11Wlscsn+LA1kb/Y5xT8uggJSkIhD08NAU= -google.golang.org/api v0.203.0/go.mod h1:BuOVyCSYEPwJb3npWvDnNmFI92f3GeRnHNkETneT3SI= +google.golang.org/api v0.210.0 h1:HMNffZ57OoZCRYSbdWVRoqOa8V8NIHLL0CzdBPLztWk= +google.golang.org/api v0.210.0/go.mod h1:B9XDZGnx2NtyjzVkOVTGrFSAVZgPcbedzKg/gTLwqBs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53 h1:Df6WuGvthPzc+JiQ/G+m+sNX24kc0aTBqoDN/0yyykE= -google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53/go.mod h1:fheguH3Am2dGp1LfXkrvwqC/KlFq8F0nLq3LryOMrrE= -google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg= -google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk= +google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc= +google.golang.org/genproto/googleapis/api v0.0.0-20241113202542-65e8d215514f h1:M65LEviCfuZTfrfzwwEoxVtgvfkFkBUbFnRbxCXuXhU= +google.golang.org/genproto/googleapis/api v0.0.0-20241113202542-65e8d215514f/go.mod h1:Yo94eF2nj7igQt+TiJ49KxjIH8ndLYPZMIRSiRcEbg0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 h1:LWZqQOEjDyONlF1H6afSWpAL/znlREo2tHfLoe+8LMA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= -google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/grpc v1.67.2 h1:Lq11HW1nr5m4OYV+ZVy2BjOK78/zqnTx24vyDBP1JcQ= +google.golang.org/grpc v1.67.2/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a h1:UIpYSuWdWHSzjwcAFRLjKcPXFZVVLXGEM23W+NWqipw= google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a/go.mod h1:9i1T9n4ZinTUZGgzENMi8MDDgbGC5mqTS75JAv6xN3A= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -275,8 +275,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= +google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From 2c671fc29a9b6f427abd1f13bce7ea7e7b818c04 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 8 Dec 2024 23:24:50 +0000 Subject: [PATCH 1127/1127] chore(deps): bump golang.org/x/net from 0.31.0 to 0.32.0 (#931) --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 97d7e314..05942102 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/invopop/jsonschema v0.12.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/subosito/gotenv v1.6.0 - golang.org/x/net v0.31.0 + golang.org/x/net v0.32.0 sigs.k8s.io/yaml v1.4.0 ) @@ -60,11 +60,11 @@ require ( go.opentelemetry.io/otel/sdk v1.29.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect go.opentelemetry.io/otel/trace v1.29.0 // indirect - golang.org/x/crypto v0.29.0 // indirect + golang.org/x/crypto v0.30.0 // indirect golang.org/x/oauth2 v0.24.0 // indirect - golang.org/x/sync v0.9.0 // indirect - golang.org/x/sys v0.27.0 // indirect - golang.org/x/text v0.20.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.8.0 // indirect google.golang.org/api v0.210.0 // indirect google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect diff --git a/go.sum b/go.sum index ff294f48..37e2ec43 100644 --- a/go.sum +++ b/go.sum @@ -194,8 +194,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= -golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= +golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY= +golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -209,16 +209,16 @@ golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= -golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= +golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= -golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -227,15 +227,15 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= -golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=