Skip to content

Latest commit

 

History

History
159 lines (127 loc) · 6.31 KB

File metadata and controls

159 lines (127 loc) · 6.31 KB

Bring your own registries and credentials

No input of the build action takes a token, a registry or a package server. A project whose dependencies do not resolve from the default registries needs them arranged in a step of its own beforehand, and everything Julia reads from the environment is available to the build through the step's env.

---
name: "Distribution"

on:
  pull_request:

permissions:
  contents: "read"

jobs:
  build-distribution:
    name: "Build the distribution"

    # At job level so that every step resolving packages uses it, the registry
    # step below included rather than the build alone.
    env:
      JULIA_PKG_USE_CLI_GIT: "true"

    runs-on: "ubuntu-latest"

    # A distribution build is long enough that a wedged one is worth failing
    # rather than leaving to the six-hour default.
    timeout-minutes: 60

    steps:
      - name: "Check out the source"
        uses: "actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1" # v7.0.1

      - name: "Install Julia"
        uses: "julia-actions/install-juliaup@1cc1b0ecfb06c04ff09fe24cab1476106460750e" # v3.1.5
        with:
          channel: "1.12.6"

      # `insteadOf` is honored by the `git` command line rather than by the
      # `libgit2` Julia clones with by default, which is what the job's
      # `JULIA_PKG_USE_CLI_GIT` switches. A token in the URL is written into the
      # runner's global git configuration, so it is readable by anything that
      # runs afterwards.
      - name: "Add a private registry"
        env:
          REGISTRY_TOKEN: "${{ secrets.REGISTRY_TOKEN }}"
        run: |
          set -euo pipefail

          git config --global \
            url."https://x-access-token:${REGISTRY_TOKEN}@github.com/".insteadOf \
            "https://github.com/"

          julia -e 'using Pkg
                    Pkg.Registry.add("General")
                    Pkg.Registry.add(Pkg.RegistrySpec(url = "https://github.com/your-org/YourRegistry.git"))'

      - name: "Build the distribution"
        id: "build-distribution"
        uses: "JuliaComputing/create-julia-distribution@958799562b82fcabd2f5508f7ee296e1d91de200"
        with:
          distribution-id: "my-product-linux-x86_64"

      - name: "Pack the distribution"
        id: "pack-distribution"
        uses: "JuliaComputing/create-julia-distribution/pack@958799562b82fcabd2f5508f7ee296e1d91de200"
        with:
          distribution-path: "${{ steps.build-distribution.outputs.distribution-path }}"

Why the environment is enough

The build action is a composite action, so env on the step that calls it is in scope for the steps inside it, and a variable set at job level is in scope for all of them. Nothing needs an input for the build to see JULIA_PKG_USE_CLI_GIT, JULIA_DEPOT_PATH or anything else Julia reads.

Registries are per-depot rather than per-step, so a registry added in one step is still there for the build in a later one as long as the depot is the same — which it is, unless a step changes JULIA_DEPOT_PATH.

A package server instead of a registry

Dependencies served by a package server need no registry step at all, only the variable that names it, on the build step or on the job:

      - name: "Build the distribution"
        id: "build-distribution"
        env:
          JULIA_PKG_SERVER: "packages.example.com"
        uses: "JuliaComputing/create-julia-distribution@958799562b82fcabd2f5508f7ee296e1d91de200"
        with:
          distribution-id: "my-product-linux-x86_64"

A server requiring authentication is reached through the token file Julia expects in the depot, written by a step before the build. If that server also hosts the registry, PumasAI/add-private-registry does both halves in one step, and is shorter than doing either by hand:

      - name: "Add the private registry"
        uses: "PumasAI/add-private-registry@3ee6e5689774ff51631dd6b1fd35bae37b363ae3" # v1.0.1
        env:
          JULIA_PKG_SERVER: "packages.example.com"
        with:
          juliahub_token_encoded: "${{ secrets.PACKAGE_SERVER_TOKEN }}"
          private_registry_name: "YourRegistry"
          private_registry_uuid: "00000000-0000-0000-0000-000000000000"

Its juliahub_token_encoded input reads narrower than the action behaves. What it wants is a base64-encoded auth.toml — the whole document Julia expects, rather than a bare token — and what it does with it is write that file into the depot for whichever server JULIA_PKG_SERVER names, which is where Julia looks for it. Any package server you can produce an auth.toml for is therefore in scope, whatever the input is called.

The step earlier in this workflow is the case that action does not cover: a registry that is a plain git repository, reached with a git credential rather than a package-server token.

Private dependencies reached over SSH

A Manifest.toml referring to a git repository using SSH rather than a registered version needs the clone to succeed rather than the registry lookup. webfactory/ssh-agent starts an agent, loads the key and exports the socket for the steps that follow, which is otherwise a fiddly block of shell:

      - name: "Authorize SSH access to private dependencies"
        uses: "webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555" # v0.10.0
        with:
          ssh-private-key: "${{ secrets.DEPENDENCY_SSH_KEY }}"

The job's JULIA_PKG_USE_CLI_GIT matters here too: the libgit2 Julia clones with by default cannot talk to that agent, while the git command line can.

What the build can reach

Building a system image runs the code being built into it: precompilation, module initialization, and whatever else is evaluated while the image is created. A credential in scope for the build is in scope for that code, whether it is in the environment, in the global git configuration or in a file the earlier step wrote.

Two things follow. Credentials the resolve genuinely needs have no way around being present, so the narrower they are the better — a read-only token, an account that reaches the registry and nothing else. Credentials the build does not need are best granted after it, in the interval between the build and the pack, which is where the signing example writes its signing certificate.