1

Summary

One of my defined CI/CD variables value doesn't pass to pipeline job script, though others pass correct.
The variable name I'm talking about is APPSETTINGS, see its usage at pre-deploy_job.

I'm trying to pass its value as a build argument for docker there. Other variables are HOST, USER, ID_RSA and they work fine.
What have I missed?

.gitlab-ci.yml

default:
  image: mcr.microsoft.com/dotnet/sdk:7.0

stages:
  - build
  - test
  - pre-deploy
  - deploy

variables:
  OBJECTS_DIRECTORY: 'obj'
  NUGET_PACKAGES_DIRECTORY: '.nuget'
  SOURCE_CODE_PATH: '*/*/'
  DOCKER_HOST: tcp://docker:2375
  DOCKER_TLS_CERTDIR: ""
  DOCKER_DRIVER: overlay2

cache:
  key: "$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG"
  paths:
    - '$SOURCE_CODE_PATH$OBJECTS_DIRECTORY/project.assets.json'
    - '$SOURCE_CODE_PATH$OBJECTS_DIRECTORY/*.csproj.nuget.*'
    - '$NUGET_PACKAGES_DIRECTORY'
  policy: pull-push

before_script:
  - 'dotnet restore --packages $NUGET_PACKAGES_DIRECTORY'
  - 'echo $APPSETTINGS'


build_job:
  stage: build
  only:
    - branches
  script:
    - 'dotnet build --no-restore'


test_job:
  stage: test
  only:
    - branches
  script:
    - 'dotnet test ./Tests --no-restore --test-adapter-path:. --logger:"junit;LogFilePath=..\artifacts\{assembly}-test-result.xml;MethodFormat=Class;FailureBodyFormat=Verbose"'
  artifacts:
    when: always
    paths: 
     - ./**/*test-result.xml
    reports:
      junit: 
       - ./**/*test-result.xml


pre-deploy_job:
  image: docker:stable
  services:
    - docker:dind
  stage: pre-deploy
  only:
    - master
    - dev
  before_script:
    - docker login ${CI_REGISTRY} -u ${CI_REGISTRY_USER} -p ${CI_REGISTRY_PASSWORD}
  script:
    - docker build -t ${CI_REGISTRY}/${CI_PROJECT_PATH}:latest --build-arg APPSETTINGS=${APPSETTINGS} --no-cache .
    - docker push ${CI_REGISTRY}/${CI_PROJECT_PATH}:latest
  after_script:
    - docker logout ${CI_REGISTRY}


deploy_job:
  stage: deploy
  only:
    - master
    - dev
  script:
    - chmod og= $ID_RSA
    - apt-get update 
    - apt-get install -y openssh-client
    - ssh -i $ID_RSA -o StrictHostKeyChecking=no $USER@$HOST "docker login ${CI_REGISTRY} -u ${CI_REGISTRY_USER} -p ${CI_REGISTRY_PASSWORD}"
    - ssh -i $ID_RSA -o StrictHostKeyChecking=no $USER@$HOST "docker pull ${CI_REGISTRY}/${CI_PROJECT_PATH}:latest"
    - ssh -i $ID_RSA -o StrictHostKeyChecking=no $USER@$HOST "docker container rm -f tech-connect || true"
    - ssh -i $ID_RSA -o StrictHostKeyChecking=no $USER@$HOST "docker run -d -p 80:80 --name tech-connect ${CI_REGISTRY}/${CI_PROJECT_PATH}:latest"
  environment:
    name: staging
    url: $HOST

Dockerfile

FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
ARG APPSETTINGS
RUN [ "sh", "-c", "echo $APPSETTINGS" ]
WORKDIR /src
COPY ["TC/TC.csproj", "TC/"]
RUN dotnet restore "TC/TC.csproj"
COPY . .
WORKDIR "/src/TC"
RUN dotnet build "TC.csproj" -c Release -o /app/build
RUN cp -f $APPSETTINGS /app/build/appsettings.json

FROM build AS publish
RUN dotnet publish "TC.csproj" -c Release -o /app/publish /p:UseAppHost=false

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "TC.dll"]

What is the current behavior?

enter image description here

enter image description here

In plain text:

47 #6 [build 2/9] RUN echo ""
48 #6 0.777
#11 [build 9/9] RUN cp -f /app/build/appsettings.json
#11 0.526 cp: missing destination file operand after "/app/build/appsettings.json
#11 0.526 Try 'cp --help' for more information.
#11 ERROR: process "/bin/sh -c cp -f $APPSETTINGS /app/build/appsettings.json" did not complete successfully: exit code: 1
-----
 > [build 9/9] RUN cp -f /app/build/appsettings.json:
-----
process "/bin/sh -c cp -f $APPSETTINGS /app/build/appsettings.json" did,not complete successfully: exit code: 1

As you can see, the value of $APPSETTINGS is empty inside the Dockerfile.

What is the expected behavior?

Value of $APPSETTINGSshould contain non-empty value, same as defined in GitLab CI/CD variables.

Relevant logs and/or screenshots

enter image description here

In GitLab:

Update variable:

  • Key: "APPSETTINGS",
  • Value: "test",
  • Type: "variable",
  • environment scope: "staging",
  • Flags: "Protect variable".
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
anatol
  • 1,680
  • 2
  • 24
  • 47
  • Maybe something related to `Environment scope` to be set to `staging` – OlegI Jun 09 '23 at 11:25
  • @OlegI I'm sure not. Other variables defined in the same scope – anatol Jun 09 '23 at 11:36
  • Are the other variables also protected? – Nathan Lynch Jun 12 '23 at 21:40
  • @Schmango, yeah – anatol Jun 13 '23 at 03:38
  • can you try with just `--build-arg APPSETTINGS="$APPSETTINGS"` ? According to docker docs, it should also pick up APPSETTINGS variable when specifed as `--build-arg APPSETTINGS`. "You may also use the --build-arg flag without a value, in which case the value from the local environment will be propagated into the Docker container being built". https://docs.docker.com/engine/reference/commandline/build/#build-arg – mayqueen Jun 13 '23 at 13:43

1 Answers1

2

You have mentioned that the environment scope of the APPSETTINGS variable is set to "staging". This could be the reason why the variable is not available in your script.

The APPSETTINGS variable will only be injected into jobs that are run on the staging environment, and from your .gitlab-ci.yml file, it appears that the pre-deploy_job does not specify an environment.

You could try removing the environment scope from the variable, or set the environment for the pre-deploy_job to staging.

I'm sure not. Other variables defined in the same scope

True, but APPSETTINGS is used in pre-deploy_job, again without any environment definition.


Furthermore, your APPSETTINGS variable is also set as a protected variable, which means it will only be provided to protected branches and tags.

If your dev branch is not protected, then the APPSETTINGS variable will not be available to jobs run on that branch.

You could verify if your branches are protected or try removing the protected flag.

See also "GitLab Environment Variables are not Resolved in Your Pipeline — Here are Some Fixes." from Valentin Despa.


Note that you could try setting a default value for the APPSETTINGS variable in your Dockerfile to see if this resolves the issue, like so:

ARG APPSETTINGS=default_value

This would allow you to check if the issue is with the passing of the variable or with the Dockerfile itself.

Or, should you use --build-arg APPSETTINGS="$APPSETTINGS" during your docker build command, you would need to add ARG APPSETTINGS in your Dockerfile like so:

FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
ARG APPSETTINGS
RUN [ "sh", "-c", "echo $APPSETTINGS" ]
...

This ARG APPSETTINGS line makes the APPSETTINGS variable available for use in the Dockerfile, and its value is the one that you provide in the --build-arg option during the docker build command. If no value is provided, APPSETTINGS will be undefined.

Please note that the scope of the ARG instruction is from the line it is declared on to the end of the current build stage. So if you need to use the APPSETTINGS variable in a different stage, you'd need to re-declare it in that stage.


To confirm all this, you could also try echoing the APPSETTINGS variable in the pre-deploy_job to see if the variable is available at that point, like so:

pre-deploy_job:
  # ...
  script:
    - echo $APPSETTINGS
    - docker build -t ${CI_REGISTRY}/${CI_PROJECT_PATH}:latest --build-arg APPSETTINGS=${APPSETTINGS} --no-cache .
    # ...

This should help you figure out where exactly the variable is getting lost.


The OP anatol confirms in the comments:

The issue was on a surface - dev branch is not in the staging env, I added it just to test and overlooked env behavior.

This kind of oversight happens quite often. In GitLab CI/CD, environment variables can be scoped to specific environments, and if the branch you are building is not part of the environment where the variable is scoped, it will not have access to that variable.

Since you have identified that the issue was due to the dev branch not being part of the staging environment, you can either:

  • Change the Environment Scope of the Variable, that is of the APPSETTINGS variable so that it is available to the dev branch.
    For example, you could remove the environment scope, making the variable available to all environments.

  • or, what you did: include the dev branch in the staging environment.
    You can do this by modifying the .gitlab-ci.yml file and configuring the environment section in the pre-deploy_job:

    pre-deploy_job:
    # ...
    environment:
        name: staging
        url: $HOST
        on_stop: stop_environment
    only:
        refs:
        - master
        - dev
    # ...
    

    Make sure that the APPSETTINGS variable is not set as "Protected" if you are using non-protected branches like dev.

Once you make these changes, the APPSETTINGS variable should be passed correctly to the job script.

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • 1
    Note to self: That was my **30000th answer** on Stack Overflow (in 177 months), a bit more than 7 months after the [29000th answer](https://stackoverflow.com/a/74593456/6309). Before that: [28000th answer](https://stackoverflow.com/a/72697130/6309), [27000th answer](https://stackoverflow.com/a/70504269/6309), [26000th answer](https://stackoverflow.com/a/68190336/6309), [25000th answer](https://stackoverflow.com/a/65370954/6309), [24000th answer](https://stackoverflow.com/a/62688871/6309), [23000th answer](https://stackoverflow.com/a/59853332/63099), ... – VonC Jun 15 '23 at 18:09
  • the issue was on a surface - `dev` branch is not in the `staging` env, I added it just to test and overlooked env behavior. thank you for helping me gather one's thoughts – anatol Jun 19 '23 at 04:03
  • @anatol Well done! I have included your comment in the answer for more visibility. – VonC Jun 19 '23 at 09:44