GitHub Actions: Accessing content from other repositories 2/2

7 minute read

In the first post of this series I discussed how to access content from other repositories using the actions/checkout and the different authentication methods that can be used to access private/internal repositories.

In this post I will present you a simpler and better method to access content from other repositories but it makes sense for some use cases.

This method is suitable when you need to access content from another repository but you don’t need the content to be placed in the workflow workspace.

What is the workspace? The workspace i the default working directory on the runner for steps, and the default location of your repository when using the checkout action.

Note You can place it on the workspace if you want, but tipically if you need it to be placed on the workspace it’s an indication that you probably want to use checkout.

So what alternative method can you use to get content from another (non public) repository?

Actions download details

Before proceeding let’s see how GitHub Action works, when you reference an action in a workflow step, like this


steps:
  - uses: actions/checkout@v2

Before the steps are executed, the runner dowloads the content of the action from the repository and places it in a directory on the runner, this is done on Set up job step automatically in a transparent manner.

set up job downloading actions/checkou logs

Now here is the gist, the folder where the action is downloaded is outside the workspace and the location is an implementation detail, so we cannot rely on it not to change in the future.

Luckily for use, the GitHub Actions Context contain a property in the github context that points to the location where the action is downloaded. (the property github.action_path).

This is what the docs say about this property For a step executing an action, this is the owner and repository name of the action. For example, actions/checkout.

Making the repository available to other repository

So all we need to do is to make the repository available to the actions, and this can be done the repository settings->actions->general->Access and making the repository available to actions to repositories in the same organization or repositories in the same enterprise depending on your needs.

making the repository available to other repositories in same org

Learn more about Allowing access to components in a private repository and Sharing actions and workflows with your enterprise

Before enabling this, pay attention to the warning below. (as pasted form the [docs]https://docs.github.com/en/enterprise-cloud@latest/actions/creating-actions/sharing-actions-and-workflows-with-your-enterprise#about-github-actions-access-to-internal-and-private-repositories) to fully understand the consequences of enabling this.

Warning If you make an internal or private repository accessible to GitHub Actions workflows in other repositories, outside collaborators on the other repositories can indirectly access the internal or private repository, even though they do not have direct access to these repositories. The outside collaborators can view logs for workflow runs when actions or workflows from the internal or private repository are used.

By using this tecnhique you are opening the repository to be used either in the same organization or the same enterprise, so you need to be aware of the consequences. If you want to apply a more granular access then you need to use the technique from the first part of this series.

Accessing the repository

By now you know how to enable the content of a repository to be available to other repositories, but it is still now enough. If you try to access the repository using the actions/checkout action, this is because the repository you made available is only available to a token that as read access and is scoped to the repository, but it’s not the same token as GITHUB_TOKEN which is what the actions/checkout action uses by default.

This token is not only different from GITHUB_TOKEN but it’s also not available to the workflow, so you cannot use it directly, only the runner has access to it and is used in set up job step to download an action.

Enable repository download

So far we don’t have an action, only a repository that is accessible to actions, so we need to make sure the repository contains (at least) one action.

In order to this we need to create an action in the repository.

The easiest action we can create is a composite action.

So place in the repository a file action action.yml (on the root folder of the repository or on a subfolder) with the following content

name: 'Actionize my repository. I can now be download from actions'
runs:
  using: "composite"
  steps:
    # We need to have at least one step. This will do for now
    - run: echo world
      shell: bash

This is enough to make the repository available to other repositories, but we need to make sure the action is downloaded, so we need to reference it in a workflow (let’s assume we place this in a repository called my-repo in with a octocat owner)

(snippet only)


steps:
  - name: using the surrogate action to download the repository
    uses: octocat/my-repo@main

Tip By using an action, we can have a way to access a repository contents in a versioned matter just like we do for actions.

Off course this is still useless, since we now are able to download the content of the repository to an (still uknown location) but can’t do anything with it.

So let’s make an enhanced to the action to be able to expose the location where the repository is downloaded.

name: 'Actionize my repository. I can now be download from actions'
outputs:
  path:
    description: "the absolute path where the repository is downloaded"
    value: ${{ steps.get-path.outputs.path }}

runs:
  using: "composite"
  steps:
    - id: get-path
      run: echo "path=${{ github.action_path }}" >> $GITHUB_OUTPUT
      shell: bash

and to use it (snippet only)

steps:
  - name: using the surrogate action to download the repository
    id: my-repo
    uses: octocat/my-repo@main

  - name: list repository content
    run: ls -la ${{ steps.my-repo.outputs.path }}

Real world scenarios

Let’s see some real world scenarios where this technique can be useful.

Shared scripts

Let’s assume you have a repository with shared scripts that you want to reuse in other repositories.

Let’s assume all the scripts are stored in a folder called scripts, and you want to use them in a workflow.

You would add this action.yml to the repository

name: 'Shared scripts'
outputs:
  scripts-path:
    description: "the absolute path where the scripts are stored"
    value: ${{ steps.get-path.outputs.path }}

runs:
  using: "composite"
  steps:
    - id: get-path
      run: echo "path=${{ github.action_path }}/scripts" >> $GITHUB_OUTPUT
      shell: bash

and to use it on your workflow (snippet only)

steps:
  - name: Get shared scripts location
    id: my-repo
    uses: octocat/my-scripts@v1

  - name: deploy the application
    run: "${{ steps.my-repo.outputs.scripts-path }}/deploy.sh" $APP_NAME $APP_VERSION $APP_FILE

CodeQL Configuration file

CodeQL code scanning allows you define a configuration in an external repository, so you can reuse the same configuration in multiple repositories. But if this configuration file is stored in an internal/private you will need to provide a token so the CodeQL init action can fetch it.

With this technique you make the configuration file available generally without the need for a token.

Let’s assume you have a repository called codeql-config.yml (you can have more than one) with a file called codeql-config.yml on the root folder, you can add a action.yml file with the following content

name: 'Get CodeQL configuration Path'
outputs:
  config-file-path:
    description: "configuration file path"
    value: ${{ steps.get-path.outputs.path }}
runs:
  using: "composite"
  steps:
    - id: get-path      
      run: echo "path=${{ github.action_path }}" >> $GITHUB_OUTPUT
      shell: bash

and to use it (snippet)

steps:
  - name: Get CodeQL configuration Path
    id: codeql-config
    uses: octocat/codeql-config@v1

  - name: Initialize CodeQL
    uses: github/codeql-action/init@v1
    with:
      languages: javascript
      config-file: ${{ steps.codeql-config.outputs.config-file-path }}/codeql-config.yml

Alternative paths

There are other alternatives to this technique to share content between repositories, but they might have an aditional overhead.

One of such options is to use OCI images to store the content, but this requires you to have a registry available and to build the image.

Luckily GitHub supports OCI images in GitHub Packages so you can use it to store the content, and you can also make it available to other repositories without having to manage tokens.

It has the drawback that you need to build the image and push it to the registry, and you GitHub Actions to do it, but it’s an aditional setup overhead.

But it has the advantage of having more fine grained permissions by defining which repositories can access the image and using GITHUB_TOKEN to get access to it.

See Granular permissions for user/organization-scoped packages

So if the content you want to make available is structured like Helm Carts, Azure Bicep Modules to name a few, using an OCI image may be a better option since they support it nativelly.

On the Universal Packages on GitHub With ORAS post you can see how to use Github Packages to store OCI images and access it from a workflow.

Conclusion

trojan horse delivering files

Using actions as a surrogate (some people might call it a trojan horse) to access content from other (non public) repositories within your account/organization/enterprise in GitHub Actions is a simple technique that can be used to share content between repositories without the need for explicit authentication.

As long as you are with making the content of the repository to people anyone who can create workflows in your organization or enterprise (dependin on your access settings) and on the visiblity of your repository.