roboll / helmfile

Deploy Kubernetes Helm Charts
MIT License
4.05k stars 566 forks source link

Would like command to run helmfile hooks interactively #961

Open pbecotte opened 4 years ago

pbecotte commented 4 years ago

Hi everyone! Have been using Helmfile a lot recently as we build out SDLC/CD stuff for a few new team/projects. The next step on my to do list is to setup the actual nonprod/prod deployments on top of the dev environments I have set up so far. Have the option of using Argocd since it is already in use for other stuff on our cluster. My problem is that to make it work, argo expects you to provide manifests and let it handle actually applying them. Helmfile template would do that just fine- but I made extensive use of helmfile hooks.

I am using hooks to set up secrets. I store the secrets elsewhere (such as AWS SSM in this case). During deploy, the hook pulls the secret and inserts them into a k8s secret. Consider-

hooks:
      - events: ["presync"]
        showlogs: true
        command: sh
        args:
          - -c
          - |
            {{ if .Values.image.pullSecret }}
            export REPO_USER="{{ requiredEnv "DOCKER_REPO_USER" }}"
            export REPO_PASSWORD="$(aws ssm get-parameter --name {{ requiredEnv "DOCKER_SSM_PARAM" }} --with-decryption --output text --query 'Parameter.Value')"
            cat <<EOF | kubectl apply -f -
            apiVersion: v1
            kind: Secret
            type: kubernetes.io/dockerconfigjson
            metadata:
              name: {{ .Values.image.pullSecret }}
              namespace: {{ .Values.namespace }}
            data:
              .dockerconfigjson: $(echo -n '{"auths":{"{{ .Values.image.registry }}":{"username":"'${REPO_USER}'","password":"'${REPO_PASSWORD}'","auth":"'$(echo -n ${REPO_USER}:${REPO_PASSWORD} | base64 -w 0)'"}}}' | base64 -w 0)
            EOF
            {{ end }}
            {{ range .Values.createSecrets }}
            kubectl -n {{ .Values.namespace }} get secret {{ .Values.project }}-{{ . }} || kubectl -n {{ .Values.namespace }} create secret generic {{ .Values.project }}-{{ .Values.createSecrets }} --from-literal=password=$(openssl rand -base64 15)
            {{ end }}

This gets around secrets in code without sacrificing having the config in one spot. Even better, because of helmfile's layering, I can reuse this logic. We set up a helmchart called defaulthelmchart with a very generic setup. In the next folder there is a helmfiles directory that includes this hook plus some templating for computed values. Things such as service.host.path = {{.Values.hostPath}} when we expect hostPath to be set by the parent project. Then the users just point their own helmfile at that one and provide the values they need to override.

So- in this case I'd really like to be able to run helmfile run-hooks in the init phase, followed by helmfile template to provide the final set of manifests. I can move this logic out of the helmfile and run a script instead, or I can just use helmfile sync in my CI build- but I think that'd be the best of both worlds.

mumoshu commented 4 years ago

@pbecotte Hey! Thanks for raising this.

helmfile run-hooks sounds almost good. I think we need to think about that hooks like presync and postsync happen only in, as you see, sync though.

Especially postsync needs attention as helmfile has a (hidden?) contract/precondition that:

mumoshu commented 4 years ago

So I think we have 2 options here:

  1. Make ithelmfile sync --hooks-only. It will run all the hooks, skipping the sync itself.
  2. Go for helmfile [--selector foobar] run-hook presync, explicitly noting that it's the user's responsibility to check and prepare everything needed for the hook to be successfully run.
mumoshu commented 4 years ago

Maybe the option 1 has more gotchas than I can imagine, so from developer's perspective the latter options sounds more feasible.