diff --git a/.drone.yml b/.drone.yml index f676471..421f28e 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,51 +1,48 @@ -workspace: - base: /go - path: src/github.com/ipedrazas/drone-helm - pipeline: - test: - image: golang:1.8 - commands: - - go get - - go test -cover -coverprofile=coverage.out - compile: - image: golang:1.8 - commands: - - export PATH=$PATH:/go/bin - - go build -ldflags "-s -w -X main.build=$DRONE_BUILD_NUMBER" -a -tags netgo + docker-build: + image: plugins/docker:17.05 + repo: quay.io/ipedrazas/drone-helm + tags: + - ${DRONE_BRANCH}-${DRONE_COMMIT_SHA:0:7} + - k1.8.1-h2.7.0 + registry: quay.io + email: "info@info.com" + secrets: [ docker_username, docker_password ] + when: + branch: + exclude: [ master] + docker-build-master: + image: plugins/docker:17.05 + repo: quay.io/ipedrazas/drone-helm + tags: + - latest + - k1.8.1-h2.7.0 + - ${DRONE_BRANCH}-${DRONE_COMMIT_SHA:0:7} + registry: quay.io + email: "info@info.com" + secrets: [ docker_username, docker_password ] + when: + branch: [master] - build_docker_image: - image: docker:17.05 - environment: - - DOCKER_HOST=tcp://127.0.0.1:2375 - - TAG=${DRONE_BRANCH}-${DRONE_COMMIT_SHA:0:7} - commands: - - docker login -u="${QUAY_USERNAME}" -p="${QUAY_PASSWORD}" quay.io - - docker build -t image . - - docker tag image quay.io/ipedrazas/drone-helm:latest - - docker tag image quay.io/ipedrazas/drone-helm:${TAG} - - docker push quay.io/ipedrazas/drone-helm - # when: - # event: [push] - # branch: [master] + test_deploy: + image: quay.io/ipedrazas/drone-helm:latest + skip_tls_verify: true + chart: ./charts/plugintest + release: ${DRONE_BRANCH} + prefix: TEST + secrets: [ test_api_server, test_kubernetes_token] + when: + branch: [master] slack: image: plugins/slack channel: deploys username: drone template: > - {{ build.author }} finished building ** of {{ repo.name }} with a {{ build.status }} status - - -services: - dind: - image: docker:17.05-dind - privileged: true - command: - - "-s" - - "overlay" + {{ build.author }} finished building ** of {{ repo.name }} with a {{ build.status }} status + secrets: ["slack_webhook"] plugin: name: drone-helm diff --git a/.drone.yml.sig b/.drone.yml.sig deleted file mode 100644 index d5aced5..0000000 --- a/.drone.yml.sig +++ /dev/null @@ -1 +0,0 @@ -eyJhbGciOiJIUzI1NiJ9.d29ya3NwYWNlOgogIGJhc2U6IC9nbwogIHBhdGg6IHNyYy9naXRodWIuY29tL2lwZWRyYXphcy9kcm9uZS1oZWxtCgpwaXBlbGluZToKICB0ZXN0OgogICAgaW1hZ2U6IGdvbGFuZzoxLjgKICAgIGNvbW1hbmRzOgogICAgICAtIGdvIGdldAogICAgICAtIGdvIHRlc3QgLWNvdmVyIC1jb3ZlcnByb2ZpbGU9Y292ZXJhZ2Uub3V0CgogIGNvbXBpbGU6CiAgICBpbWFnZTogZ29sYW5nOjEuOAogICAgY29tbWFuZHM6CiAgICAgIC0gZXhwb3J0IFBBVEg9JFBBVEg6L2dvL2JpbgogICAgICAtIGdvIGJ1aWxkIC1sZGZsYWdzICItcyAtdyAtWCBtYWluLmJ1aWxkPSREUk9ORV9CVUlMRF9OVU1CRVIiIC1hIC10YWdzIG5ldGdvCgoKICBidWlsZF9kb2NrZXJfaW1hZ2U6CiAgICBpbWFnZTogZG9ja2VyOjEuMTIKICAgIGVudmlyb25tZW50OgogICAgICAtIERPQ0tFUl9IT1NUPXRjcDovLzEyNy4wLjAuMToyMzc1CiAgICAgIC0gVEFHPSR7RFJPTkVfQlJBTkNIfS0ke0RST05FX0NPTU1JVF9TSEE6MDo3fQogICAgY29tbWFuZHM6CiAgICAgIC0gZG9ja2VyIGxvZ2luIC11PSIke1FVQVlfVVNFUk5BTUV9IiAtcD0iJHtRVUFZX1BBU1NXT1JEfSIgcXVheS5pbwogICAgICAtIGRvY2tlciBidWlsZCAtdCBpbWFnZSAuCiAgICAgIC0gZG9ja2VyIHRhZyBpbWFnZSBxdWF5LmlvL2lwZWRyYXphcy9kcm9uZS1oZWxtOmxhdGVzdAogICAgICAtIGRvY2tlciB0YWcgaW1hZ2UgcXVheS5pby9pcGVkcmF6YXMvZHJvbmUtaGVsbToke1RBR30KICAgICAgLSBkb2NrZXIgcHVzaCBxdWF5LmlvL2lwZWRyYXphcy9kcm9uZS1oZWxtCiAgICAjIHdoZW46CiAgICAjICBldmVudDogW3B1c2hdCiAgICAjICBicmFuY2g6IFttYXN0ZXJdCgogIHNsYWNrOgogICAgaW1hZ2U6IHBsdWdpbnMvc2xhY2sKICAgIGNoYW5uZWw6IGRlcGxveXMKICAgIHVzZXJuYW1lOiBkcm9uZQogICAgdGVtcGxhdGU6ID4KICAgICAge3sgYnVpbGQuYXV0aG9yIH19IGZpbmlzaGVkIGJ1aWxkaW5nICAqPGh0dHA6Ly9kcm9uZS5zb2hvaG91c2VkaWdpdGFsLmNvbS9Tb2hvSG91c2Uve3sgcmVwby5uYW1lIH19L3t7IGJ1aWxkLm51bWJlciB9fXx7eyBidWlsZC5icmFuY2ggfX0gKHt7IGJ1aWxkLm51bWJlciB9fSk-KiBvZiB7eyByZXBvLm5hbWUgfX0gIHdpdGggYSB7eyBidWlsZC5zdGF0dXMgfX0gc3RhdHVzCgoKc2VydmljZXM6CiAgZGluZDoKICAgIGltYWdlOiBkb2NrZXI6MS4xMi1kaW5kCiAgICBwcml2aWxlZ2VkOiB0cnVlCiAgICBjb21tYW5kOgogICAgICAtICItcyIKICAgICAgLSAib3ZlcmxheSIKCnBsdWdpbjoKICBuYW1lOiBkcm9uZS1oZWxtCiAgZGVzYzogRXhlY3V0ZSBoZWxtIHRvCiAgdHlwZTogZGVwbG95CiAgaW1hZ2U6IHF1YXkuaW8vaXBlZHJhemFzL2Ryb25lLWhlbG0KICBsYWJlbHM6CiAgICAtIGRlcGxveQogICAgLSBrdWJlcm5ldGVzCiAgICAtIGhlbG0K.TUPmXe_ZEmQLVIFZeglBkqT-iru6H_TKxURXpM-adfU \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index c6b932b..de0b2e3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,13 +28,13 @@ RUN go build -v -o "/drone-helm" FROM alpine:3.6 MAINTAINER Ivan Pedrazas -# Helm version: can be passed at build time (default to v2.5.1) +# Helm version: can be passed at build time (default to v2.6.0) ARG VERSION -ENV VERSION ${VERSION:-v2.5.1} +ENV VERSION ${VERSION:-v2.7.0} ENV FILENAME helm-${VERSION}-linux-amd64.tar.gz ARG KUBECTL -ENV KUBECTL ${KUBECTL:-v1.7.2} +ENV KUBECTL ${KUBECTL:-v1.8.1} RUN set -ex \ && apk add --no-cache curl ca-certificates \ diff --git a/README.md b/README.md index f34f5df..4e12557 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ # Helm (Kubernetes) plugin for drone.io +[![Build Status](https://build.kube.camp/api/badges/ipedrazas/drone-helm/status.svg)](https://build.kube.camp/ipedrazas/drone-helm) + This plugin allows to deploy a [Helm](https://github.com/kubernetes/helm) chart into a [Kubernetes](https://github.com/kubernetes/kubernetes) cluster. -* Current `helm` version: 2.5.1 +* Current `helm` version: 2.6.0 * Current `kubectl` version: 1.6.6 ## Drone Pipeline Usage diff --git a/charts/plugintest/.helmignore b/charts/plugintest/.helmignore new file mode 100644 index 0000000..f0c1319 --- /dev/null +++ b/charts/plugintest/.helmignore @@ -0,0 +1,21 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj diff --git a/charts/plugintest/Chart.yaml b/charts/plugintest/Chart.yaml new file mode 100644 index 0000000..98d23d8 --- /dev/null +++ b/charts/plugintest/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +description: A Helm chart for Kubernetes +name: plugintest +version: 0.1.0 diff --git a/charts/plugintest/templates/NOTES.txt b/charts/plugintest/templates/NOTES.txt new file mode 100644 index 0000000..ccfecb7 --- /dev/null +++ b/charts/plugintest/templates/NOTES.txt @@ -0,0 +1,19 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range .Values.ingress.hosts }} + http://{{ . }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get svc -w {{ template "fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') + echo http://$SERVICE_IP:{{ .Values.service.externalPort }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl port-forward $POD_NAME 8080:{{ .Values.service.externalPort }} +{{- end }} diff --git a/charts/plugintest/templates/_helpers.tpl b/charts/plugintest/templates/_helpers.tpl new file mode 100644 index 0000000..f0d83d2 --- /dev/null +++ b/charts/plugintest/templates/_helpers.tpl @@ -0,0 +1,16 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "fullname" -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} diff --git a/charts/plugintest/templates/deployment.yaml b/charts/plugintest/templates/deployment.yaml new file mode 100644 index 0000000..d15e0f3 --- /dev/null +++ b/charts/plugintest/templates/deployment.yaml @@ -0,0 +1,37 @@ +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: {{ template "fullname" . }} + labels: + app: {{ template "name" . }} + chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +spec: + replicas: {{ .Values.replicaCount }} + template: + metadata: + labels: + app: {{ template "name" . }} + release: {{ .Release.Name }} + spec: + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - containerPort: {{ .Values.service.internalPort }} + livenessProbe: + httpGet: + path: / + port: {{ .Values.service.internalPort }} + readinessProbe: + httpGet: + path: / + port: {{ .Values.service.internalPort }} + resources: +{{ toYaml .Values.resources | indent 12 }} + {{- if .Values.nodeSelector }} + nodeSelector: +{{ toYaml .Values.nodeSelector | indent 8 }} + {{- end }} diff --git a/charts/plugintest/templates/ingress.yaml b/charts/plugintest/templates/ingress.yaml new file mode 100644 index 0000000..b09eb90 --- /dev/null +++ b/charts/plugintest/templates/ingress.yaml @@ -0,0 +1,32 @@ +{{- if .Values.ingress.enabled -}} +{{- $serviceName := include "fullname" . -}} +{{- $servicePort := .Values.service.externalPort -}} +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: {{ template "fullname" . }} + labels: + app: {{ template "name" . }} + chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + annotations: + {{- range $key, $value := .Values.ingress.annotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} +spec: + rules: + {{- range $host := .Values.ingress.hosts }} + - host: {{ $host }} + http: + paths: + - path: / + backend: + serviceName: {{ $serviceName }} + servicePort: {{ $servicePort }} + {{- end -}} + {{- if .Values.ingress.tls }} + tls: +{{ toYaml .Values.ingress.tls | indent 4 }} + {{- end -}} +{{- end -}} diff --git a/charts/plugintest/templates/service.yaml b/charts/plugintest/templates/service.yaml new file mode 100644 index 0000000..f311d10 --- /dev/null +++ b/charts/plugintest/templates/service.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ template "fullname" . }} + labels: + app: {{ template "name" . }} + chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.externalPort }} + targetPort: {{ .Values.service.internalPort }} + protocol: TCP + name: {{ .Values.service.name }} + selector: + app: {{ template "name" . }} + release: {{ .Release.Name }} diff --git a/charts/plugintest/values.yaml b/charts/plugintest/values.yaml new file mode 100644 index 0000000..7eb5fff --- /dev/null +++ b/charts/plugintest/values.yaml @@ -0,0 +1,37 @@ +# Default values for plugintest. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. +replicaCount: 1 +image: + repository: nginx + tag: stable + pullPolicy: IfNotPresent +service: + name: nginx + type: ClusterIP + externalPort: 80 + internalPort: 80 +ingress: + enabled: false + # Used to create an Ingress record. + hosts: + - chart-example.local + annotations: + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + tls: + # Secrets must be manually created in the namespace. + # - secretName: chart-example-tls + # hosts: + # - chart-example.local +resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi diff --git a/main.go b/main.go index 05e3a50..49217ed 100644 --- a/main.go +++ b/main.go @@ -105,12 +105,12 @@ func main() { EnvVar: "PLUGIN_UPGRADE,UPGRADE", }, cli.BoolFlag{ - Name: "client-only", + Name: "canary-image", Usage: "if set, Helm will use the canary tiller image", EnvVar: "PLUGIN_CANARY_IMAGE,CANARY_IMAGE", }, cli.BoolFlag{ - Name: "canary-image", + Name: "client-only", Usage: "if set, it will initilises helm in the client side only", EnvVar: "PLUGIN_CLIENT_ONLY,CLIENT_ONLY", }, diff --git a/plugin.go b/plugin.go index 1b278a2..a82f49f 100644 --- a/plugin.go +++ b/plugin.go @@ -195,6 +195,10 @@ func doHelmInit(p *Plugin) []string { // Exec default method func (p *Plugin) Exec() error { + if p.Config.Debug { + p.debugEnv() + } + // create /root/.kube/config file if not exists if _, err := os.Stat(p.Config.KubeConfig); os.IsNotExist(err) { resolveSecrets(p) @@ -204,6 +208,7 @@ func (p *Plugin) Exec() error { if p.Config.Token == "" { return fmt.Errorf("Error: Token is needed to deploy.") } + initialiseKubeconfig(&p.Config, KUBECONFIG, p.Config.KubeConfig) } @@ -224,6 +229,7 @@ func (p *Plugin) Exec() error { if p.Config.Debug { log.Println("adding helm repo: " + strings.Join(repoAdd[:], " ")) } + if err = runCommand(repoAdd); err != nil { return fmt.Errorf("Error adding helm repo: " + err.Error()) } @@ -238,10 +244,12 @@ func (p *Plugin) Exec() error { if p.Config.Debug { log.Println("helm command: " + strings.Join(p.command, " ")) } + err = runCommand(p.command) if err != nil { return fmt.Errorf("Error running helm command: " + strings.Join(p.command[:], " ")) } + return nil } @@ -269,10 +277,10 @@ func runCommand(params []string) error { } func resolveSecrets(p *Plugin) { - p.Config.Values = resolveEnvVar(p.Config.Values, p.Config.Prefix) - p.Config.APIServer = resolveEnvVar("${API_SERVER}", p.Config.Prefix) - p.Config.Token = resolveEnvVar("${KUBERNETES_TOKEN}", p.Config.Prefix) - p.Config.ServiceAccount = resolveEnvVar("${SERVICE_ACCOUNT}", p.Config.Prefix) + p.Config.Values = resolveEnvVar(p.Config.Values, p.Config.Prefix, p.Config.Debug) + p.Config.APIServer = resolveEnvVar("${API_SERVER}", p.Config.Prefix, p.Config.Debug) + p.Config.Token = resolveEnvVar("${KUBERNETES_TOKEN}", p.Config.Prefix, p.Config.Debug) + p.Config.ServiceAccount = resolveEnvVar("${SERVICE_ACCOUNT}", p.Config.Prefix, p.Config.Debug) if p.Config.ServiceAccount == "" { p.Config.ServiceAccount = "helm" } @@ -285,23 +293,30 @@ func getEnvVars(envvars string) [][]string { return extracted } -func resolveEnvVar(key string, prefix string) string { +func resolveEnvVar(key string, prefix string, debug bool) string { envvars := getEnvVars(key) - return replaceEnvvars(envvars, prefix, key) + return replaceEnvvars(envvars, prefix, key, debug) } -func replaceEnvvars(envvars [][]string, prefix string, s string) string { +func replaceEnvvars(envvars [][]string, prefix string, s string, debug bool) string { for _, envvar := range envvars { envvarName := envvar[0] envvarKey := envvar[2] - envval := os.Getenv(prefix + "_" + envvarKey) + prefixedKey := strings.ToUpper(prefix + "_" + envvarKey) + envval := os.Getenv(prefixedKey) + if debug { + fmt.Printf("-ReplVar: %s => %s-- %s\n", prefixedKey, envvarKey, envval) + } + if envval == "" { envval = os.Getenv(envvarKey) } + if strings.Contains(s, envvarName) { s = strings.Replace(s, envvarName, envval, -1) } } + return s } @@ -315,26 +330,28 @@ func unQuote(s string) string { return unquoted } -func (p *Plugin) debug() { - fmt.Println(p) +func (p *Plugin) debugEnv() { // debug env vars for _, e := range os.Environ() { fmt.Println("-Var:--", e) } +} + +func (p *Plugin) debug() { + fmt.Println(p) // debug plugin obj fmt.Printf("Api server: %s \n", p.Config.APIServer) fmt.Printf("Values: %s \n", p.Config.Values) fmt.Printf("Secrets: %s \n", p.Config.Secrets) fmt.Printf("Helm Repos: %s \n", p.Config.HelmRepos) fmt.Printf("ValuesFiles: %s \n", p.Config.ValuesFiles) - kubeconfig, err := ioutil.ReadFile(KUBECONFIG) if err == nil { fmt.Println(string(kubeconfig)) } + config, err := ioutil.ReadFile(p.Config.KubeConfig) if err == nil { fmt.Println(string(config)) } - } diff --git a/plugin_test.go b/plugin_test.go index 528e308..d9ae9f3 100644 --- a/plugin_test.go +++ b/plugin_test.go @@ -229,7 +229,7 @@ func TestReplaceEnvvars(t *testing.T) { prefix := "MY" testText := "this should be ${TAG} now ${TAG}" result := getEnvVars(testText) - resolved := replaceEnvvars(result, prefix, testText) + resolved := replaceEnvvars(result, prefix, testText, false) if !strings.Contains(resolved, tag) { t.Errorf("EnvVar MY_TAG no replaced by %s -- %s \n", tag, resolved) }