Shell conditions in Gitlab CI
Tags: git, devops, linux
Published: 2022-09-06 16:24:02

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](https://www.kutac.cz/pocitace-a-internety/shell-podminky-v-gitlab-ci)

---

> 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:

```yaml
deploy:
  image: registry.gitlab.com/pavel.kutac/docker-ftp-deployer:php81
  script:
    - /bin/bash /usr/local/bin/execute-deployment.sh
```

## Condition directly inside `.gitlab-ci.yml` file

You can write conditions directly into YAML with [Literal block style](https://yaml.org/spec/1.2-old/spec.html#style/block/literal). Then the pipeline might look like this. Used script and Docker image are taken from [Parallel incremental FTP Deployer](https://dev.to/arxeiss/parallel-incremental-ftp-deploy-in-ci-pipeline-2511). But now you need to know, which syntax you should use.

```yaml
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
```

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.

```go
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, "$@", "")}
	}
    // ...
}
```