Sign inSign up

cdupuis/sbx-kits:github-clone

Manifest digest

sha256:110c30c90a971fd2ca660b616a915de28d1dc9b6041a3c1c8b37a2fc526b65e3

Last pushed

about 4 hours by cdupuis

Type

Sandbox Kit

Manifest digest

sha256:110c30c90a971fd2ca660b616a915de28d1dc9b6041a3c1c8b37a2fc526b65e3

yaml
schemaVersion: "2"
kind: mixin
name: github-clone
displayName: GitHub Clone
description: Clones a GitHub repository into the sandbox at create time, using the gh CLI with a proxy-managed GH_TOKEN sentinel. Auto-installs gh if the base image doesn't have it.
args:
    dir:
        default: /project
        description: |
            Absolute directory inside the sandbox to clone into. If the directory
            already exists and is non-empty, the clone is skipped (the mixin
            re-runs cleanly on `sbx create` retries).
        pattern: ^/[A-Za-z0-9._/-]+$
    pr:
        default: ""
        description: |
            Optional pull request number to check out with `gh pr checkout` after
            the clone. The PR lands on a local branch, so `git push` from the
            sandbox updates it. Empty (the default) leaves the working copy on
            `ref` or the repo's default branch. Mutually exclusive with `ref`.
        pattern: ^[0-9]*$
    ref:
        default: ""
        description: |
            Optional git ref (branch, tag, or 7–40 hex commit SHA) to check out.
            Empty (the default) clones the repo's default branch. Mutually
            exclusive with `pr`.
        pattern: ^[A-Za-z0-9._/-]*$
    repo:
        required: true
        description: |
            GitHub repository to clone. Accepts the `owner/name` shorthand, a full
            `https://github.com/owner/name[.git]` URL, or an
            `[email protected]:owner/name[.git]` SSH URL. Any other form is rejected
            before the clone shells out.
        pattern: ^(?:[A-Za-z0-9._-]+/[A-Za-z0-9._-]+(?:\.git)?|https://github\.com/[A-Za-z0-9._-]+/[A-Za-z0-9._-]+(?:\.git)?|git@github\.com:[A-Za-z0-9._-]+/[A-Za-z0-9._-]+(?:\.git)?)$
agentInstructions:
    content: |
        ## github-clone

        A GitHub repository (`${{ kit.args.repo }}`) has been cloned into
        `${{ kit.args.dir }}` at sandbox-create time. Treat that directory as
        the working copy for this task rather than `~/workspace`.

        The kit's `pr` arg is `${{ kit.args.pr }}` — empty when no pull request
        was requested. When it holds a number, that PR is already checked out on
        a local branch via `gh pr checkout` and the clone has full history, so
        `git diff <base>...HEAD` shows the PR's changes and `git push` updates
        the PR itself.

        Authentication is proxy-mediated: `GH_TOKEN` in the container is a
        sentinel — the sandbox proxy swaps in the real token bound on the host
        (via `sbx secret set -g github`) when talking to `api.github.com` and
        `github.com`. `gh` and `git` HTTPS operations both use this path, so
        `gh api …`, `gh pr create`, `git push`, and friends work without any
        extra setup.

        Public repos work with no host-side credential bound; only writes and
        private-repo access need one.
permissions:
    network:
        allow:
            - api.github.com
            - github.com
            - codeload.github.com
            - raw.githubusercontent.com
            - cli.github.com
            - archive.ubuntu.com
            - security.ubuntu.com
            - ports.ubuntu.com
            - download.docker.com
credentials:
    - service: github
      description: Token used to clone the repository and for later gh/git operations.
      apiKey:
        name: GH_TOKEN
        proxyManaged: true
        inject:
            - domain: api.github.com
              header: Authorization
              format: Bearer %s
            - domain: github.com
              header: Authorization
              format: '%s'
              username: x-access-token
setup:
    install:
        - command: |
            set -euo pipefail

            NEED=""
            command -v gh  >/dev/null 2>&1 || NEED="$NEED gh"
            command -v git >/dev/null 2>&1 || NEED="$NEED git"

            if [ -z "$NEED" ]; then
              exit 0
            fi

            if ! command -v apt-get >/dev/null 2>&1; then
              echo "github-clone: missing:$NEED and apt-get is not available — this kit only knows how to auto-install on Debian/Ubuntu bases. Layer a mixin that installs the missing tools, or pick a base agent that ships them." >&2
              exit 1
            fi

            export DEBIAN_FRONTEND=noninteractive

            # Add GitHub's official apt source only when gh is what's missing.
            # Recipe follows https://github.com/cli/cli/blob/trunk/docs/install_linux.md.
            case " $NEED " in
              *' gh '*)
                install -m 0755 -d /etc/apt/keyrings
                if command -v curl >/dev/null 2>&1; then
                  curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
                    -o /etc/apt/keyrings/githubcli-archive-keyring.gpg
                elif command -v wget >/dev/null 2>&1; then
                  wget -qO /etc/apt/keyrings/githubcli-archive-keyring.gpg \
                    https://cli.github.com/packages/githubcli-archive-keyring.gpg
                else
                  # Fall back to installing curl first via apt so we can fetch
                  # the keyring; the base at least has ca-certificates for the
                  # https handshake (every *-docker base does).
                  apt-get update -qq
                  apt-get install -y -qq --no-install-recommends curl ca-certificates
                  curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
                    -o /etc/apt/keyrings/githubcli-archive-keyring.gpg
                fi
                chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg
                echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
                  > /etc/apt/sources.list.d/github-cli.list
                ;;
            esac

            apt-get update -qq
            # `$NEED` is intentionally unquoted — it's a space-separated list
            # of package names constructed from the two `command -v` checks
            # above, not user input.
            apt-get install -y -qq --no-install-recommends $NEED
            rm -rf /var/lib/apt/lists/*
          user: "0"
          description: Install gh (and git) via apt if the base image doesn't already ship them
        - command: |
            set -euo pipefail

            REF='${{ kit.args.ref }}'
            PR='${{ kit.args.pr }}'
            DIR='${{ kit.args.dir }}'

            # Both args pick the checked-out commit, so honouring both would mean
            # silently letting one win. Fail at create time instead.
            if [ -n "$REF" ] && [ -n "$PR" ]; then
              echo "github-clone: 'ref' and 'pr' are mutually exclusive — pass one or the other." >&2
              exit 1
            fi

            # Route git HTTPS auth for github.com through gh's credential helper
            # for both the clone below and later agent-run git operations.
            git config --system --unset-all credential."https://github.com".helper 2>/dev/null || true
            git config --system --add       credential."https://github.com".helper ''
            git config --system --add       credential."https://github.com".helper '!gh auth git-credential'

            # Idempotent re-run: don't change ownership of an existing working
            # tree. The agent hook below performs the same check and skips it.
            if [ -d "$DIR" ] && [ -n "$(ls -A "$DIR" 2>/dev/null || true)" ]; then
              exit 0
            fi

            mkdir -p "$DIR"
            chown agent:agent "$DIR"
          user: "0"
          description: Prepare ${{ kit.args.dir }} for an agent-owned clone
        - command: |
            set -euo pipefail

            REPO='${{ kit.args.repo }}'
            REF='${{ kit.args.ref }}'
            PR='${{ kit.args.pr }}'
            DIR='${{ kit.args.dir }}'

            if [ -d "$DIR" ] && [ -n "$(ls -A "$DIR" 2>/dev/null || true)" ]; then
              echo "github-clone: '$DIR' already exists and is non-empty; skipping clone." >&2
              exit 0
            fi

            # gh reads GH_TOKEN itself, so no extra plumbing is needed for
            # the clone. Extra flags after `--` are passed straight to
            # `git clone`.
            if [ -n "$PR" ]; then
              # Full history on purpose. `gh pr checkout` fetches the PR head
              # into the clone, and in a --depth 1 repo that head shares no
              # ancestor with the base branch, so `git diff base...HEAD` and
              # `git log base..HEAD` — the first things anyone reviewing a PR
              # reaches for — fail with "no merge base".
              gh repo clone "$REPO" "$DIR"
            elif [ -n "$REF" ]; then
              # Fast path: shallow-clone a branch or tag directly. Fallback:
              # if REF is a commit SHA (not a branch/tag), --branch fails, so
              # do a full clone and check the SHA out.
              if ! gh repo clone "$REPO" "$DIR" -- --depth 1 --branch "$REF" 2>/dev/null; then
                # Keep the root-created destination itself: uid 1000 may not be
                # able to recreate it beneath a root-owned parent such as `/`.
                find "$DIR" -mindepth 1 -maxdepth 1 -exec rm -rf -- {} +
                gh repo clone "$REPO" "$DIR"
                git -C "$DIR" checkout "$REF"
              fi
            else
              gh repo clone "$REPO" "$DIR" -- --depth 1
            fi

            if [ -n "$PR" ]; then
              # Run from inside the clone so gh resolves the repo from origin —
              # no --repo, which would have to re-parse $REPO's URL forms. gh
              # looks the PR up over api.github.com, fetches its head, and
              # leaves a local branch behind, including for PRs from forks.
              ( cd "$DIR" && gh pr checkout "$PR" )
            fi
          user: "1000"
          description: Clone ${{ kit.args.repo }} into ${{ kit.args.dir }} via gh