Search code examples
kubernetes-helm

Helm and command with &&


I have the following Helm Job for a Django application to run the migrations and to collect the static files:

apiVersion: batch/v1
kind: Job
metadata:
  name: django-app-job
  labels:
    app.kubernetes.io/name: django-app-job
    helm.sh/chart: django-app
    app.kubernetes.io/instance: staging-admin
    app.kubernetes.io/managed-by: Tiller
  annotations:
    "helm.sh/hook": pre-install,pre-upgrade
    "helm.sh/hook-weight": "1"
    "helm.sh/hook-delete-policy": hook-succeeded,hook-failed
spec:
  template:
    metadata:
      labels:
        app.kubernetes.io/name: django-app-job
        app.kubernetes.io/instance: foobar
    spec:
      restartPolicy: OnFailure
      containers:
        - name: django-app
          command:
          - "/bin/bash"
          - "-c"
          - "python3 ./manage.py migrate"
          - "&&"
          - "python3 ./manage.py collectstatic --noinput"

But this only executes the migrate to update the DB schema but it nevers run the collect static. Even if the migration run ok. The job doesn't fails because if not the upgrade will fail and that doesn't happens.

But if I change the command to this:

      containers:
        - name: django-app
          command:
          - "/bin/bash"
          - "-c"
          - "python3 ./manage.py migrate && python3 ./manage.py collectstatic --noinput"

now the jobs run the migrations and the collect static. What is the difference between the 2 commands?


Solution

  • At a low level, all Unix commands are actually executed as a sequence of words. Normally the shell splits up command lines into words for you, but in a Kubernetes manifest, you have to manually specify one word at a time.

    In your example, the Bourne shell sh -c option reads the next single word only and executes it as a command, applying the normal shell rules. Any remaining words are used as positional parameters if the command happens to use variables like $1.

    You can demonstrate this outside of Kubernetes in your local shell, using quoting to force the shell to break up words the way you want:

    # Option one
    '/bin/sh' '-c' 'echo foo' '&&' 'echo bar'
    # Prints "foo"
    
    # Option two
    '/bin/sh' '-c' 'echo foo && echo bar'
    # Prints "foo", "bar"
    

    One trick that shows up somewhat often is to use YAML block scalars to write a single string across multiple lines, giving something that sort of looks like a shell script but isn't actually.

    command: ['/bin/sh', '-c']
    args: >-
      python3 ./manage.py migrate
      &&
      python3 ./manage.py collectstatic --noinput