DEV Community

Cover image for Shell conditions in Gitlab CI
Pavel Kutáč
Pavel Kutáč

Posted on • Edited on

Shell conditions in Gitlab CI

Scripts in Gitlab CI/CD might contain conditions, and there are multiple options for how to solve that. But which Shell is used and which syntax should be used then? This is not documented.


🇨🇿 V češtině si lze článek přečíst na kutac.cz


If you are using a different executor than Docker, this might not be relevant for you. However, Docker executor is the default one for shared runners in gitlab.com

In gitlab-ci.yml file you specify the Docker image and also commands to execute. You can include conditions or other constructs in those commands. But used Docker image might contain multiple Shell implementations and it is necessary to know which Shell is selected and which syntax to use.

Manually specifying Shell in *.sh file

If the script is not written directly in .gitlab-ci.yml file, but in an external one, you can specify the path to Shell and the path to the file. Then you don't care much which Shell is selected in the executor. The pipeline might look like this:

deploy:
  image: registry.gitlab.com/pavel.kutac/docker-ftp-deployer:php81
  script:
    - /bin/bash /usr/local/bin/execute-deployment.sh
Enter fullscreen mode Exit fullscreen mode

Condition directly inside .gitlab-ci.yml file

You can write conditions directly into YAML with Literal block style. Then the pipeline might look like this. Used script and Docker image are taken from Parallel incremental FTP Deployer. But now you need to know, which syntax you should use.

deploy:
  image: registry.gitlab.com/pavel.kutac/docker-ftp-deployer:php81
  script: |
    if [[ ${CI_COMMIT_MESSAGE,,} != "[skip-maintenance]"* ]]; then
      wget --no-verbose -O - https://kutac.cz/some/path/to/turn/on/maintenance-mode
    fi
    ./.ci/deploy/run-lftp-incremental-sync.sh
Enter fullscreen mode Exit fullscreen mode

But how to get that info? I wasn't able to find it in the documentation. And mentioned Docker image contains Busybox sh and also bash. I had to dig into the source code of Gitlab runner.

And there it is clear, that Gitlab Runner first executes a short script with /bin/sh. BashDetectShellScript below checks different paths for different shell implementation and select one of them. The selected one is then used to execute all scripts from .gitlab-ci.yml file.

const BashDetectShellScript = `if [ -x /usr/local/bin/bash ]; then
    exec /usr/local/bin/bash $@
elif [ -x /usr/bin/bash ]; then
    exec /usr/bin/bash $@
elif [ -x /bin/bash ]; then
    exec /bin/bash $@
elif [ -x /usr/local/bin/sh ]; then
    exec /usr/local/bin/sh $@
elif [ -x /usr/bin/sh ]; then
    exec /usr/bin/sh $@
elif [ -x /bin/sh ]; then
    exec /bin/sh $@
elif [ -x /busybox/sh ]; then
    exec /busybox/sh $@
else
    echo shell not found
    exit 1
fi

`
// ...

func (b *BashShell) GetConfiguration(info common.ShellScriptInfo) (*common.ShellConfiguration, error) {
    // ...
    if info.Type == common.LoginShell {
        script.CmdLine += " -l"
        script.Arguments = []string{"-l"}
        script.DockerCommand = []string{"sh", "-c", strings.ReplaceAll(BashDetectShellScript, "$@", "-l")}
    } else {
        script.DockerCommand = []string{"sh", "-c", strings.ReplaceAll(BashDetectShellScript, "$@", "")}
    }
    // ...
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)