From 57809b72b2053a8885de11abb43bcff9cc45831e Mon Sep 17 00:00:00 2001 From: krogon Date: Tue, 22 May 2018 16:15:32 +0200 Subject: [PATCH] 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