Skip to content

About

Github pull request resource for Concourse, for creating instance-pipelines per PR

Resources

Contributing

Stars

2 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

100 Commits

Folders and files

Repository files navigation

GitHub PR Concourse Resource

CI Status Publish to GHCR Go Version License GHCR Attestations

A modern, feature-rich Concourse CI resource for GitHub Pull Requests with dual-mode support and GitHub App authentication.

🌟 Features

✨ Dual Mode Operation

  • PR List Mode: Track all PRs matching criteria (perfect for instance pipelines)
  • Single PR Mode: Track commits to a specific PR (ideal for commit-based testing)

πŸ” Advanced Security & Authentication

  • GitHub App Authentication (recommended) - No shared tokens, automatic token rotation
  • Personal Access Token support
  • Required review approvals enforcement
  • Draft PR filtering
  • Fork repository filtering

🎯 Smart Filtering & Integration

  • Path-based filtering with glob patterns (include/exclude)
  • Label-based filtering for workflow control
  • Base branch targeting
  • PR state filtering (OPEN, MERGED, CLOSED)
  • Multiple integration strategies (merge, rebase, checkout)
  • Commit status updates
  • PR comments
  • Changed files detection

⚑ Performance & Flexibility

  • Shallow cloning support
  • Git submodules support
  • Git LFS support
  • Configurable git depth
  • Multi-architecture support (amd64, arm64)
  • PR list mode checks up to 10 PRs concurrently by default, configurable via check_concurrency (path filtering, comment scanning) instead of one at a time β€” see Concurrency

πŸ“¦ Installation

Option 1: GitHub Container Registry (Recommended)

resource_types:
  - name: github-pr
    type: registry-image
    source:
      repository: ghcr.io/ujala-singh/github-pr-concourse-resource
      tag: latest
      # Or use a specific version tag:
      # tag: v1.0.0

Option 2: Docker Hub

resource_types:
  - name: github-pr
    type: registry-image
    source:
      repository: jolly3/github-pr-concourse-resource
      tag: latest

Option 3: Building from Source

Requirements:

  • Go 1.27 or higher
  • Docker (for containerization)
# Clone the repository
git clone https://github.com/ujala-singh/github-pr-concourse-resource.git
cd github-pr-concourse-resource

# Build Docker image
docker build -t github-pr-concourse-resource:latest .

# Run tests
go test -v -race -cover ./...

# Push to your registry
docker tag github-pr-concourse-resource:latest your-registry.com/github-pr-concourse-resource:latest
docker push your-registry.com/github-pr-concourse-resource:latest

πŸ“‹ Prerequisites

  • Concourse CI 7.0 or higher
  • GitHub repository with appropriate permissions
  • One of the following authentication methods:
    • GitHub App (recommended) - Setup Guide
    • Personal Access Token with repo scope

πŸš€ Quick Start

See our Quick Start Guide for a step-by-step tutorial.

πŸ“‹ Looking for more examples?

Minimal Working Example

resource_types:
  - name: github-pr
    type: registry-image
    source:
      repository: ghcr.io/ujala-singh/github-pr-concourse-resource

resources:
  - name: pull-requests
    type: github-pr
    source:
      repository: owner/repo
      access_token: ((github-token))

jobs:
  - name: test-pr
    plan:
      - get: pull-requests
        trigger: true
      - task: run-tests
        config:
          platform: linux
          image_resource:
            type: registry-image
            source: {repository: golang, tag: "1.27"}
          inputs:
            - name: pull-requests
          run:
            path: sh
            args:
              - -c
              - |
                cd pull-requests
                go test ./...

🎭 Modes of Operation

Mode 1: PR List (Instance Pipelines)

Track a list of PRs matching your criteria. Perfect for creating instance pipelines where each PR gets its own pipeline.

When to use: You want to create separate pipelines for each PR, or you want to trigger on the creation/closure of PRs rather than individual commits.

resources:
  - name: prs
    type: github-pr
    source:
      repository: owner/repo
      access_token: ((github-token))
      # No 'number' field = PR List mode
      base_branch: main
      labels: ["ready-for-review"]
      required_review_approvals: 1

Mode 2: Single PR (Commit Tracking)

Track commits to a specific PR number. Perfect for running tests on each commit to a PR.

When to use: You have a known PR number and want to run tests on every commit to that PR.

resources:
  - name: pr-123
    type: github-pr
    source:
      repository: owner/repo
      access_token: ((github-token))
      number: 123  # Specific PR number = Single PR mode

Source Configuration

Authentication

You can authenticate with either a personal access token or GitHub App credentials (recommended).

Personal Access Token

source:
  repository: owner/repo
  access_token: ((github-token))

GitHub App (Recommended)

source:
  repository: owner/repo
  github_app_id: ((github-app-id))
  github_app_installation_id: ((github-app-installation-id))
  github_app_private_key: ((github-app-private-key))

See docs/GITHUB_APP_AUTHENTICATION.md for detailed setup instructions.

Common Configuration (Both Modes)

Parameter Required Default Description
repository Yes - Repository in owner/repo format
access_token No* - GitHub personal access token with repo scope
github_app_id No* - GitHub App ID
github_app_installation_id No* - GitHub App Installation ID
github_app_private_key No* - GitHub App private key. Accepts either a raw PEM string or that same PEM base64-encoded β€” useful when storing it in a secrets manager or credential store that mangles multi-line values. Validated at config load, so a malformed key fails fast with a clear error instead of surfacing deep inside JWT signing.
v3_endpoint No https://api.github.com GitHub API v3 endpoint (for GitHub Enterprise)
v4_endpoint No https://api.github.com/graphql GitHub API v4 endpoint (for GitHub Enterprise)
hosting_endpoint No https://github.com GitHub hosting endpoint (for GitHub Enterprise)
paths No [] Only trigger on PRs that change files matching these patterns. Checked against files changed since the last build for an already-tracked PR, or the PR's full base...HEAD diff when there's no previous build to diff from (e.g. a PR appearing for the first time) β€” see Path Filtering and Diff Scope.
ignore_paths No [] Ignore PRs that only change files matching these patterns. Same diff scope as paths above.
disable_ci_skip No false If false, PRs with [ci skip] or [skip ci] in title are skipped
skip_ssl_verification No false Skip SSL certificate verification (use with caution!)
disable_forks No false Only trigger on PRs from the same repository
ignore_drafts No false Skip draft PRs
base_branch No - Only trigger on PRs targeting this branch
labels No [] Only trigger on PRs with at least one of these labels
states No ["OPEN"] PR states to track: OPEN, MERGED, CLOSED, DRAFT. DRAFT is a pseudo-state β€” GitHub's real PR state is only ever OPEN/MERGED/CLOSED, and draft-ness is a separate flag on an open PR β€” so DRAFT fetches open PRs and keeps only the draft ones. states: ["OPEN"] (the default) already includes drafts, same as always; adding "DRAFT" alongside "OPEN" is a no-op β€” you only need "DRAFT" on its own (optionally with MERGED/CLOSED) when you want only drafts, not every open PR.
concourse_url No - Override the public URL used for PR status check "Details" links. Useful when Concourse's externalUrl Helm value is locked to a different hostname (e.g. an STS WebIdentity OIDC issuer) but builds are reached via a proxy or alternate domain. When set, this value replaces ATC_EXTERNAL_URL for the put step.
trigger_comments No [] Comment prefixes (case-insensitive) that re-trigger the job as if a new commit had arrived, without pushing one. E.g. ["concourse plan"] lets a PR comment of "concourse plan" (or "concourse plan --all", etc.) re-run the job against the PR's current HEAD. See Comment Triggers below.
check_concurrency No 10 PR list mode only (ignored in single-PR mode). How many PRs to inspect in parallel per check (path filtering, comment scanning). Must be between 1 and 50 if set. See Concurrency below.

Note: Either access_token OR all three GitHub App parameters (github_app_id, github_app_installation_id, github_app_private_key) must be provided.

PR List Mode Configuration

Parameter Required Default Description
required_review_approvals No 0 Minimum number of approved reviews required
comment_note_key No derived from source Names this resource's marker on the comment-trigger note. Allowed characters: letters, digits, _, ., -. Set it only to tell apart two list-mode resources whose source settings are otherwise identical.

Single PR Mode Configuration

Parameter Required Default Description
number Yes - The PR number to track
required_review_approvals No 0 Minimum number of approved reviews required
git_crypt_key No - Base64 encoded git-crypt key for encrypted repositories
disable_git_lfs No false Disable Git LFS

Comment Triggers

Set trigger_comments to re-run a job from a PR comment instead of a new commit β€” handy for re-running a plan after an unrelated failure, without pushing an empty commit:

source:
  repository: owner/repo
  access_token: ((github-token))
  trigger_comments:
    - "concourse plan"

Commenting concourse plan (case-insensitive, prefix match) on the PR causes the next check to emit a new version for that PR's current HEAD β€” not the commit that existed when the comment was posted. This works the same way in both modes:

  • Single PR mode (number set): tracks that one PR's comment watermark directly, on the resource's own version. No extra setup needed.

  • PR list mode (number unset): tracks every PR's comment watermark independently via a shared table (a PR number -> comment ID map) that survives regardless of which real PR's version Concourse happens to remember as "latest" in between checks. A real PR's own comment_id/comment_baseline still describes only that PR β€” nothing about one PR's version ever depends on another's β€” so a PR closing, or getting a new comment, never perturbs any other PR's version.

    The table only needs recording when it actually changes, and whenever it does, it's always recorded on a dedicated bookkeeping version β€” a reserved version with pr: "0", which never corresponds to a real PR β€” never on a real PR's own version. An earlier design tried attaching the table to whichever real PR's version was already new that cycle for its own reason (a real comment trigger), to skip the extra bookkeeping build β€” but that's unsafe: that PR's version then toggles between carrying the table and not, cycle to cycle, purely based on which PR happens to be picked as the carrier, even though nothing about that PR itself changed. Concourse diffs the full version, so that alone makes an already-built, unchanged PR look like a new version and rebuilds it.

    This means pr-plan-style jobs in PR list mode must recognize and skip the bookkeeping version when one does show up. The resource's in step writes a COMMENT_TRIGGER_BOOKKEEPING marker file at the root of the fetched directory instead of checking out a repository for it (there is no repository to check out β€” it's not a real PR). Add an early exit to your task script:

    if [ -f pull-request/COMMENT_TRIGGER_BOOKKEEPING ]; then
      echo "bookkeeping version, nothing to plan"
      exit 0
    fi

    You'll see one extra, fast no-op build in the Concourse UI any time the table changes β€” not just when a PR closes/merges with nothing else happening. That's the deliberate, bounded cost of never risking a spurious rebuild of a real, unchanged PR.

    Do not use try: to make that no-op build show green. It's tempting, but try: masks the failure of its entire wrapped scope with success β€” wrapping a real put (e.g. to a tofu/terraform resource) in try: to "skip it gracefully" would also silently mask genuine plan/apply failures as green. Use load_var + across instead: have a task always succeed and write an empty list (or a single-element list) to a file based on the marker, load_var it, then wrap the real work in across: { values: ((.:the_var)) } β€” the real work then runs zero times (a trivial, immediate success) for the bookkeeping version, or once for a real PR, with no risk of masking a real failure. See concourse/concourse#7521 for why try: is unsafe here.

    The comment-trigger note (PR list mode). Concourse hands check only the resource's current version, so the bookkeeping version's table is only readable while it's the current version β€” and right after a comment triggers, the current version is that PR's, not the bookkeeping version. To cover that gap, each PR also carries its own record on GitHub that every check can read. When a build's get picks up a trigger comment, it posts (or edits) a single comment on that PR β€” shared by every pipeline using the same GitHub credentials β€” with one row per resource:

    πŸš€ Concourse comment triggers

    Job Picked up
    infra-aws / pr #9 A concourse plan comment by someone
    infra-gcp / pr #4 A concourse plan comment by someone

    Comment again to run them again. This comment updates in place.

    Each resource's row shows the latest build whose get picked up a trigger, and an older comment never takes it back. Hidden in the note, each resource also keeps a signed marker recording the newest trigger comment it has handled. A check that still has the bookkeeping table uses the higher of the table and its marker; a check without the table uses the marker; a resource with no marker on the PR has handled nothing, so the PR's newest trigger comment fires β€” provided it was posted after the PR's head commit (an older one was already superseded by that push). That's what makes the first concourse plan on a PR fire even when some other PR's version is currently "latest".

    Key properties:

    • A marker per resource, keyed by source (repository, paths, filters, trigger patterns β€” never credentials), so one pipeline handling a comment never stops another from triggering on it. Set comment_note_key to tell apart two resources whose settings are otherwise identical; changing those settings starts a new marker.
    • Concurrency-safe. One comment triggers every pipeline at once, so their gets write the note together. Each waits a short random moment, re-reads after writing, and re-checks a few seconds later, rewriting anything another job's edit dropped; duplicate notes (two created at once) are folded into the oldest. A dropped marker is harmless β€” the resource's next table-less check re-fires the same version Concourse already built, in the same place, so nothing rebuilds; only its row is missing until its next trigger.
    • Forgery-resistant. The marker is signed with a key derived from the resource's credentials, so a task can't forge it, and only comments posted by the resource's own GitHub App (github_app_id) or access_token user are read at all. The note never copies a trigger comment's text β€” it names the pattern that matched and the author without mentioning them, and rows are re-rendered from stored data (markup, HTML, @-mentions and non-plain links stripped) on every write β€” so a hand-edit to its text is never re-posted.
    • delete_previous_comments keeps it. out deletes the resource's earlier comments except the note, and the note is size-bounded (dropping the oldest entries first β€” rows and markers alike, never the writing resource's own) to stay under GitHub's comment limit.
    • get fails if it can't write the note, naming the permission it needs (Pull requests: write) rather than silently leaving the comment looking new to every table-less check. When first rolled out, a PR that already has a trigger comment but no marker may run once.

The very first trigger_comments-eligible check for a PR (or the first check after upgrading to a version of this resource that supports it) only establishes the watermark β€” it won't retroactively fire on comments that already existed before the pipeline started watching for them.

Comment triggers are independent of paths/ignore_paths matching on the latest push. Once a PR has an established comment-trigger watermark, a matching comment fires regardless of whether the PR's most recent commit happens to touch a configured path β€” a comment isn't a push, and there's no reason a manual "please re-plan" request should depend on what the latest commit touched. (An earlier version of this resource got this wrong: once path filtering was scoped to the diff since the last build instead of the PR's full history, a PR whose latest commit didn't touch a matching path could permanently stop being eligible for comment triggers too, since nothing else ever advances a stale cursor forward. Fixed β€” comment-trigger eligibility for the resource's tracked PR no longer depends on the commit-triggering path match.)

Behavior

check

PR List Mode: Returns the current version of every PR matching the filter criteria, on every check. A new version is picked up by Concourse when:

  • A new PR is opened
  • An existing, already-tracked PR gets a new commit
  • A PR is closed/merged
  • A PR's state changes (e.g., from draft to ready)
  • (if trigger_comments is set) a new matching comment is posted β€” see Comment Triggers

Single PR Mode: Returns list of commits to the specified PR. A new version is emitted for each new commit, or (if trigger_comments is set) for a matching comment.

Every version this resource returns carries extra fields you'll see in Concourse's resource-version listing whenever trigger_comments is configured: comment_id and comment_baseline. These are an internal watermark, not a log of every comment ever posted β€” comment_id is just the highest matching-comment ID seen so far for that version's own PR, and comment_baseline marks that the watermark has been established at least once. They show up on ordinary commit-triggered versions too (not just comment-triggered ones) because the resource needs this watermark carried forward on every version to correctly detect the next new comment on that same PR. If you don't set trigger_comments, neither field appears. In PR list mode, you'll additionally see one dedicated version per check with pr: "0" and a comment_watermarks field (a JSON object mapping every tracked PR number to its own watermark) β€” see Comment Triggers for why, and what your task script needs to do about it.

Path Filtering and Diff Scope

paths/ignore_paths are checked against the files changed since the last build for a PR this resource already has a previous commit recorded for β€” not against the PR's entire history. This matters because GitHub's "list files changed in a PR" API always returns the full base-branch-to-HEAD diff, no matter which commit you ask about: without scoping to a specific commit range, a PR that touched a matching path in any commit would keep matching forever, even once its latest push only changed unrelated files, causing redundant/surprising re-triggers.

  • Single PR Mode: fully correct. This mode always has a reliable previous commit (the one it last built) to diff from.
  • PR List Mode: correct for whichever PR currently is the resource's single tracked cursor (see Comment Triggers for the same cursor concept), if it's been pushed to since it was last built. For every other PR β€” one appearing for the first time, an unchanged one, or one the cursor has moved away from β€” there's no previous commit to diff from, so the check falls back to the PR's full base...HEAD diff, same as before this existed. In practice this means: a PR you're actively pushing to gets precise, per-push path filtering once it's been checked at least once; a PR nobody's touched recently is evaluated on its full history, same as always.

Concurrency

PR List Mode only. A single check does one GitHub API call per PR per pass β€” one to test paths/ignore_paths, and (if trigger_comments is set) another to scan that PR's comments. With many open PRs on the repo, running these sequentially can make one check take tens of seconds and eat into the GitHub App's rate limit.

To reduce that, both passes run with up to check_concurrency PRs in flight concurrently β€” 10 by default, configurable up to 50 (see the check_concurrency source field above). This cuts wall-clock time per check roughly by that factor on repos with many matching PRs; it does not reduce the total number of GitHub API calls made, so it doesn't help with rate-limit budget β€” only with how long a single check takes to run. Raising it further mostly just shifts where the bottleneck is, into GitHub's own per-token concurrency/secondary rate limits. Output order is unaffected: results are always assembled back into the same order GitHub returned the PRs in, regardless of which concurrent call finishes first.

Single PR Mode does not need this β€” it tracks exactly one PR, so there's nothing to parallelize.

get (in)

PR List Mode: Fetches PR metadata without cloning the repository.

Parameters:

Parameter Default Description
skip_download false Only fetch metadata, don't create files

Single PR Mode: Clones the repository and integrates the PR.

Parameters:

Parameter Default Description
skip_download false Skip cloning (for status updates only)
integration_tool merge How to integrate: merge, rebase, or checkout
git_depth 0 Shallow clone depth (0 = full clone)
submodules false Recursively clone submodules
list_changed_files false Create list of changed files
fetch_tags false Fetch tags from remote

put (out)

Updates PR status and adds comments. Available in both modes β€” cmd/out always runs the same underlying implementation regardless of number being set, using whatever PR was checked out by the preceding get step.

Parameters:

Parameter Required Description
path Yes Path to the PR resource from get
status No Commit status: success, failure, error, pending
base_context No Status context prefix (default: concourse-ci)
context No Status context suffix (default: status)
target_url No URL to link from the status
description No Status description
description_file No File containing the status description
comment No Comment text to add to PR
comment_file No File containing comment text
delete_previous_comments No Delete this resource's earlier comments on the PR before adding a new one

put acts only on the exact version the preceding get fetched, which get signs (.git/resource/version.sig) with a key derived from the resource's credentials. A checkout whose signature is missing, doesn't verify, or was changed after the get fails the put before it reports anything, and the pr/head_sha files are ignored. put returns that version unchanged, so its own output never looks like a new version that would re-trigger the job. In single PR mode the version must also be for source.number. (The signature covers the version, its PR and commit, and the repository β€” not the build β€” since Concourse reuses a cached get across builds.)

Metadata

The resource provides metadata in multiple formats:

  1. Individual files in .git/resource/:

    • pr - PR number
    • url - PR URL
    • title - PR title
    • author - Author username
    • head_ref - Head branch name
    • head_sha - The commit that was checked out β€” the version's commit, even if the PR has been pushed to since
    • base_ref - Base branch name
    • base_sha - Base branch tip the PR was integrated onto, captured before the PR is checked out (Single PR mode only). Diff $(git merge-base base_sha head_sha)..head_sha for the PR's own changes
    • state - PR state
    • approved_review_count - Number of approvals
  2. JSON files in .git/resource/:

    • version.json - Version object
    • metadata.json - Full metadata array
  3. Changed files (if list_changed_files: true):

    • .git/resource/changed_files - Newline-separated list

Examples

Example 1: Instance Pipelines for Each PR

resources:
  - name: prs
    type: github-pr
    source:
      repository: myorg/myrepo
      access_token: ((github-token))
      base_branch: main
      ignore_drafts: true
      required_review_approvals: 2
      labels: ["ready-for-ci"]

jobs:
  - name: test-pr
    plan:
      - get: prs
        trigger: true
        version: every
      - task: run-tests
        config:
          platform: linux
          image_resource:
            type: registry-image
            source: {repository: golang}
          inputs:
            - name: prs
          run:
            path: sh
            args:
              - -c
              - |
                PR_NUMBER=$(cat prs/.git/resource/pr)
                echo "Testing PR #$PR_NUMBER"
                # Your test commands here

Example 2: Single PR with Status Updates

resources:
  - name: my-pr
    type: github-pr
    source:
      repository: myorg/myrepo
      access_token: ((github-token))
      number: 42

jobs:
  - name: test
    plan:
      - get: my-pr
        trigger: true
      - put: my-pr
        params:
          path: my-pr
          status: pending
          context: tests
      - task: run-tests
        config:
          platform: linux
          image_resource:
            type: registry-image
            source: {repository: golang}
          inputs:
            - name: my-pr
          run:
            path: sh
            args: ["-c", "cd my-pr && go test ./..."]
        on_success:
          put: my-pr
          params:
            path: my-pr
            status: success
            context: tests
        on_failure:
          put: my-pr
          params:
            path: my-pr
            status: failure
            context: tests
            comment: "Tests failed! Please check the build."

Example 3: Path Filtering

resources:
  - name: backend-prs
    type: github-pr
    source:
      repository: myorg/myrepo
      access_token: ((github-token))
      paths:
        - "backend/**"
        - "api/**"
      ignore_paths:
        - "**/*.md"
        - "docs/**"

Example 4: Multiple Integration Tools

resources:
  - name: pr
    type: github-pr
    source:
      repository: myorg/myrepo
      access_token: ((github-token))
      number: 42

jobs:
  - name: test-with-rebase
    plan:
      - get: pr
        trigger: true
        params:
          integration_tool: rebase  # Rebase PR on top of base
          list_changed_files: true

πŸ› οΈ Development

Building Locally

Requirements:

  • Go 1.27 or higher
  • Docker 24.0 or higher
  • Git 2.40 or higher
# Clone repository
git clone https://github.com/ujala-singh/github-pr-concourse-resource.git
cd github-pr-concourse-resource

# Install dependencies
go mod download

# Build binaries
go build -o bin/check ./cmd/check
go build -o bin/in ./cmd/in
go build -o bin/out ./cmd/out

# Build Docker image
docker build -t github-pr-concourse-resource:dev .

# Run locally (for testing)
docker run -i github-pr-concourse-resource:dev /opt/resource/check < check-request.json

Testing

# Run all tests
go test ./...

# Run with verbose output
go test -v ./...

# Run with race detection
go test -race ./...

# Run with coverage
go test -cover ./...

# Generate coverage report
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out

# Run specific test
go test ./models -run TestGetAccessToken

# Run tests in watch mode (requires entr)
find . -name "*.go" | entr -c go test ./...

Code Quality

# Format code
go fmt ./...

# Run linter (requires golangci-lint)
golangci-lint run

# Vet code
go vet ./...

# Static analysis
staticcheck ./...

Project Structure

.
β”œβ”€β”€ cmd/
β”‚   β”œβ”€β”€ check/          # Check for new versions
β”‚   β”œβ”€β”€ in/             # Fetch resource
β”‚   └── out/            # Update resource (status/comments)
β”œβ”€β”€ models/             # Core data models and GitHub client
β”‚   β”œβ”€β”€ github_app.go   # GitHub App authentication
β”‚   └── models.go       # Data structures
β”œβ”€β”€ pr/                 # Single PR mode implementation
β”œβ”€β”€ prlist/             # PR List mode implementation
β”œβ”€β”€ docs/               # Comprehensive documentation
β”‚   β”œβ”€β”€ QUICKSTART.md
β”‚   β”œβ”€β”€ ARCHITECTURE.md
β”‚   β”œβ”€β”€ IMPLEMENTATION.md
β”‚   └── GITHUB_APP_AUTHENTICATION.md
└── Dockerfile          # Multi-stage build configuration

πŸ” Troubleshooting

Common Issues

1. Authentication Failures

Symptom: HTTP 401 or Bad credentials errors

Solutions:

  • Verify your access token has repo scope
  • For GitHub App: Ensure all three parameters are correct
  • Check token hasn't expired
  • Verify repository access permissions
# Debug: Enable verbose logging
source:
  repository: owner/repo
  access_token: ((github-token))
  # Test token with: curl -H "Authorization: token YOUR_TOKEN" https://api.github.com/user

2. Resource Not Triggering

Symptom: PRs created but pipeline doesn't trigger

Solutions:

  • Check filter criteria (labels, base_branch, etc.)
  • Verify webhook configuration (if using webhooks)
  • Check PR meets required_review_approvals
  • Look at draft status if ignore_drafts: true
# Debug check behavior
fly -t your-target check-resource --resource pipeline/pr-resource

3. Git Clone Failures

Symptom: fatal: could not read Username or authentication errors during clone

Solutions:

  • Ensure GitHub App installation has repository access
  • Verify private key format (must be PEM with newlines preserved)
  • Check network connectivity to GitHub
  • For private repos, confirm authentication method has access

4. Merge Conflicts

Symptom: merge conflict during get step

Solutions:

  • Use integration_tool: rebase instead of merge
  • Use integration_tool: checkout to skip integration
  • Address conflicts in the PR before running pipeline

Debug Mode

Enable detailed logging by setting environment variables:

jobs:
  - name: debug-pr
    plan:
      - get: pull-requests
        params:
          skip_download: false
        # Add environment for debugging
        env:
          - name: DEBUG
            value: "true"

Getting Help

πŸ“Š Performance Considerations

  • Shallow cloning: Set git_depth to reduce clone time
  • Path filtering: Use paths and ignore_paths to reduce check frequency
  • PR List Mode: Scales to hundreds of PRs efficiently
  • Multi-architecture: Native ARM64 support for Apple Silicon and ARM servers

πŸ”’ Security Best Practices

  1. Use GitHub Apps instead of personal access tokens
  2. Limit token scopes to minimum required (repo for PAT)
  3. Store secrets in Concourse credential managers (Vault, etc.)
  4. Enable required reviews with required_review_approvals
  5. Filter forks with disable_forks: true for security-sensitive repos
  6. Use signed commits when possible
  7. Regular updates: Keep the resource image updated

🀝 Contributing

We welcome contributions! Please see CONTRIBUTING.md for guidelines.

How to Contribute

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Make your changes
  4. Add/update tests
  5. Ensure tests pass (go test ./...)
  6. Commit your changes (git commit -m 'feat: add amazing feature')
  7. Push to the branch (git push origin feature/amazing-feature)
  8. Open a Pull Request

Development Setup

See the Development section above for setup instructions.

πŸ“œ License

MIT License - see LICENSE file for details.

πŸ™ Acknowledgments

Special thanks to all contributors and the Concourse CI community!

πŸ“š Additional Documentation


Maintained with ❀️ by the community | Report Issues | Request Features

About

Github pull request resource for Concourse, for creating instance-pipelines per PR

Resources

Contributing

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages