runner: Secrets cannot be used to condition job runs

Describe the bug The secrets context is apparently not available to if conditional expressions on jobs.

To Reproduce Create and trigger a workflow with jobs conditioned on the value of a secret:

jobs:
  build_bulky_stuff:
    if: ${{ secrets.BUILD_BULKY_STUFF == 'true' }}
    # ...

Here’s a real world example.

Expected behavior The condition is successfully evaluated and if the secret value is set to 'true', the job is run.

Runner Version and Platform

Version of your runner? No idea, GitHub hosted.

OS of the machine running the runner? Linux

What’s not working?

The workflow fails with:

The workflow is not valid. .github/workflows/release.yml (Line: 11, Col: 9): Unrecognized named-value: 'secrets'. Located at position 1 within expression: secrets.BUILD_RELEASES == 'true',.github/workflows/release.yml (Line: 35, Col: 9): Unrecognized named-value: 'secrets'. Located at position 1 within expression: secrets.BUILD_RELEASES == 'true'

Job Log Output

See this workflow run

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 33
  • Comments: 22 (2 by maintainers)

Commits related to this issue

Most upvoted comments

I don’t think we should support secrets in if conditional expression since you can’t debug the expression evaluation result.

Today we printout debug info for evaluating condition, so when the condition result is not what you expected, you can base on the log to figure out whether it’s a bug in the system or you provide the wrong input.

Ex:

##[debug]Evaluating: (success() && (runner.os != 'Windows'))
##[debug]Evaluating And:
##[debug]..Evaluating success:
##[debug]..=> true
##[debug]..Evaluating NotEqual:
##[debug]....Evaluating Index:
##[debug]......Evaluating runner:
##[debug]......=> Object
##[debug]......Evaluating String:
##[debug]......=> 'os'
##[debug]....=> 'macOS'
##[debug]....Evaluating String:
##[debug]....=> 'Windows'
##[debug]..=> true
##[debug]=> true
##[debug]Expanded: (true && ('macOS' != 'Windows'))
##[debug]Result: true

@TingluoHuang My use case is slightly different: I would like to skip steps that interact with an external service e.g. package repository if the API token is not present. It would be enough to be able to check if the secret exists, without inspecting the value. Any chance of that happening? It would be especially useful for project templates: Users often push their projects to a new GitHub repository before configuring the secrets.

@mzabaluev you can do something like this.

if: ${{ github.repository_owner == 'our_org' || contains(github.event.head_commit.message, 'RUN TEST')}}

then if you want to run those costly steps, just put RUN TEST as part of your commit message.

I hope this can help.

Thanks @ErinHales and @kis9a!

I had to verify multiple secrets so I found it easier to use bash instead of defining each secret as a environment variable and using the jobs.<job_id>.if conditional to know whether the job output has to be set or not:

 check-registry-secrets:
   name: Check if container registry information was set on secrets
   runs-on: ubuntu-latest
   outputs:
     have_secrets: ${{ steps.setvar.outputs.have_secrets }}
   steps:
     - id: setvar
       run: |
         if [[ "${{ secrets.CONTAINER_REPO }}" != "" && \
               "${{ secrets.CONTAINER_REGISTRY }}" != "" && \
               "${{ secrets.CONTAINER_REGISTRY_USERNAME }}" != "" && \
               "${{ secrets.CONTAINER_REGISTRY_PASSWORD }}" != "" ]]; \
         then
           echo "Secrets to use a container registry were configured in the repo"
           echo "::set-output name=have_secrets::true"
         else
           echo "Secrets to use a container registry were not configured in the repo"
           echo "::set-output name=have_secrets::false"
         fi

 # Skip some steps based on secrets
 job1-that-uses-registry:
   needs: [check-registry-secrets]
   steps:
   - name: Build gadget container
     uses: docker/build-push-action@v2
     ...
   - name: Login to Container Registry
     if: needs.check-registry-secrets.outputs.have_secrets == 'true'
     ...
   - name: Push gadget container
     if: needs.check-registry-secrets.outputs.have_secrets == 'true
     ...

 # Skip entire job based on secrets
 job2-that-uses-registry:
   needs: [check-registry-secrets]
   if: needs.check-registry-secrets.outputs.have_secrets == 'true'
   ...

What do you think?

In my opinion there’s still a need for users to be able to conditionally run jobs based on whether they have access to certain secrets or not. I’ve tried to come up with the most concise way to do so within the current limitations, but feel it’s still way too verbose.

I’ve opened an issue here: https://github.com/actions/runner/issues/1138, that illustrates the most concise way I could come up with to check for secrets, as well as a feature request to allow users to check for the existence of secrets in a more concise manner. Hopefully it’ll be of help!

secrets have global scope of visibility just by their nature. Supporting them on step level while not supporting on job level is inconsistency. Telling people that they do not need them while they do is just a way to cover some other reasons for not implementing it.

I do want secrets to be available on job level to avoid failed jobs which are not supposed to run because user decided not to configure or disable a job temporarily while keeping the job configuration in place. Because of not having this feature I will have add if condition to every step of the job.

Did anyone try assigning a secrets value to an output?

      - name: Assign variable
        id: secret
        run: echo '::set-output name=secret::${{secrets.SECRET}}'

      - name: Encode the project the project
        if: steps.secret.outputs.secret

Thank you, it was helpful. However, one point correction was necessary.

  check-env:
      outputs:
          my-key: ${{ my-key.outputs.defined }}
      steps:
          - id: my-key
            env:
                MY_KEY: ${{ secrets.MY_KEY }}
            if: "${{ env.MY_KEY != '' }}"
            run: echo "::set-output name=defined::true"

  next-job:
        needs: [check-env]
        if: needs.check-env.outputs.my-key == 'true'
        ...
  check-env:
      outputs:
--        my-key: ${{ my-key.outputs.defined }}
++       my-key: ${{ steps.my-key.outputs.defined }}
      steps:
          - id: my-key
            env:
                MY_KEY: ${{ secrets.MY_KEY }}
            if: "${{ env.MY_KEY != '' }}"
            run: echo "::set-output name=defined::true"

  next-job:
        needs: [check-env]
        if: needs.check-env.outputs.my-key == 'true'
        ...

I see. I assumed the secrets can be used in lieu of per-repository configuration values settable by the administrator, similar to how the ACTIONS_STEP_DEBUG secret is used by the runner to enable logs.

My need is to disable costly and potentially annoying actions in forks, unless the administrator of the fork enables them. We currently use if: ${{ github.repository_owner == 'our_org' }}, but it is annoying to have to disable these conditions in the workflow file when you need to test the workflows.

This is really inconvenient to not support secrets as I want to do optional logins for docker builds to different registries in a reusable workflow, depending on which secrets were passed into the workflow.

For now I’ve worked around the issue by using a similar technique to above, but secrets should really be supported in expressions, just like env should be supported in with blocks.

We did this by copying the secret to an environment variable first:

- name: Upload coverage results to Codacy
  env:
    CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
  if: "${{ matrix.coverage != 'none' && env.CODACY_PROJECT_TOKEN != '' }}"
  run: |
    ./vendor/bin/codacycoverage clover build/coverage/xml

Took me a while to come up with a solution that worked for my use case. I created a check-env job, hope this helps someone!

  check-env:
      outputs:
          my-key: ${{ steps.my-key.outputs.defined }}
      steps:
          - id: my-key
            env:
                MY_KEY: ${{ secrets.MY_KEY }}
            if: "${{ env.MY_KEY != '' }}"
            run: echo "::set-output name=defined::true"

  next-job:
        needs: [check-env]
        if: needs.check-env.outputs.my-key == 'true'
        ...

We did this by copying the secret to an environment variable first:

- name: Upload coverage results to Codacy
  env:
    CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
  if: "${{ matrix.coverage != 'none' && env.CODACY_PROJECT_TOKEN != '' }}"
  run: |
    ./vendor/bin/codacycoverage clover build/coverage/xml

Yeah so that is on the step level right? In my testing this approach doesn’t work on a job level, which is specifically what I’m after (assuming that your comment was in response to mine btw, if not please ignore).

A disadvantage of this approach is that the value shows up in debug logs

No, this does not expose secrets in debug logs. I just verified this; the secrets are replaced with *** in the debug log as they should be: https://github.com/mixxxdj/vcpkg/runs/3332915402#step:12:1

We did this by copying the secret to an environment variable first

A disadvantage of this approach is that the value shows up in debug logs and may leak out.

Maybe a function like secretExists('MY_SECRET') would be a good middle ground.

Ok, I only tried to use it as an output of a job, not a step, maybe that was the problem. Not sure.

Did anyone try assigning a secrets value to an output?

      - name: Assign variable
        id: secret
        run: echo '::set-output name=secret::${{secrets.SECRET}}'

      - name: Encode the project the project
        if: steps.secret.outputs.secret

You cannot use a secret in an output. Only star characters will be printed