Starts and runs Gerrit Code Review server(s) in Docker containers with automated pull-replication plugin configuration for mirroring production Gerrit instances in CI/CD workflows.
- 🚀 Multi-instance support - Start multiple Gerrit servers from a single JSON configuration
- 🔄 Automatic replication - Built-in pull-replication plugin for syncing from production servers
- 🔐 Flexible authentication - Support for SSH keys, HTTP basic auth, and bearer tokens
- 🐳 Docker layer caching - Optimized performance with intelligent caching
- 💾 Persistent containers - Containers remain available throughout workflow duration
- 🧹 Automatic cleanup - Graceful shutdown and cleanup on workflow completion
- 🏥 Health checking - Automated service availability verification
- 📊 Comprehensive outputs - Full instance metadata and access URLs
- 🔗 Gerrit to Platform - Optional
gerrit_to_platformintegration for dispatching GitHub Actions workflows from Gerrit events
steps:
- name: "Start Gerrit mirror"
id: gerrit
uses: lfreleng-actions/gerrit-server-action@main
with:
gerrit_setup: |
[{
"project": "ONAP",
"slug": "onap",
"gerrit": "gerrit.onap.org"
}]
ssh_private_key: ${{ secrets.GERRIT_SSH_KEY }}
sync_on_startup: true
- name: "Use Gerrit instance"
run: |
echo "Gerrit URL: ${{ steps.gerrit.outputs.gerrit_urls }}"
# Access Gerrit at http://localhost:8080Use this action in CI/CD pipelines that need to:
- Test against production data - Mirror a production Gerrit server for integration testing
- Develop offline - Work with Gerrit repositories without affecting production
- Verify changes - Test plugin configurations, migrations, or upgrades
- CI/CD automation - Automate workflows that require Gerrit repository access
- Multi-site testing - Test against multiple Gerrit instances simultaneously
| Name | Required | Default | Description |
|---|---|---|---|
| gerrit_setup | True | JSON array defining Gerrit instances |
| Name | Required | Default | Description |
|---|---|---|---|
| ssh_private_key | False | SSH private key for authentication (required if auth_type=ssh) |
|
| ssh_known_hosts | False | auto | SSH known_hosts entries (auto-generated if not provided) |
| ssh_auth_keys | False | SSH public keys to add for container access (one key per line) | |
| ssh_auth_username | False | Gerrit username to create for SSH access (uses admin if omitted) | |
| http_username | False | HTTP basic auth username (required if auth_type=http_basic) |
|
| http_password | False | HTTP basic auth password (required if auth_type=http_basic) |
|
| bearer_token | False | Bearer token (required if auth_type=bearer_token) |
| Name | Required | Default | Description |
|---|---|---|---|
| gerrit_version | False | 3.13.1-ubuntu24 |
Gerrit Docker image version tag |
| plugin_version | False | stable-3.13 |
Pull-replication plugin version/branch |
| base_http_port | False | 8080 |
Starting HTTP port (increments for multi-instance) |
| base_ssh_port | False | 29418 |
Starting SSH port (increments for multi-instance) |
| auth_type | False | ssh |
Authentication method: ssh, http_basic, or bearer_token |
| remote_ssh_user | False | gerrit |
SSH username for remote Gerrit servers (can override per-instance) |
| remote_ssh_port | False | 29418 |
SSH port for remote Gerrit servers (can override per-instance) |
| Name | Required | Default | Description |
|---|---|---|---|
| sync_on_startup | False | true |
Trigger replication after startup |
| fetch_every | False | 60s |
Interval for pull-replication polling (e.g., 60s, 5m, 0s to disable) |
| replication_timeout | False | 600 |
Timeout for initial replication sync (seconds) |
| replication_wait_timeout | False | 600 |
Max time to wait for replication to match expected repo count (seconds) |
| require_replication_success | False | true |
Fail workflow if replication verification fails |
| sync_refs | False | (all) | Comma-separated refs to sync (e.g., +refs/heads/*:refs/heads/*) |
| replication_threads | False | 4 |
Number of replication threads per instance |
| max_projects | False | 800 |
Maximum projects to fetch when no filter specified (increase for large instances) |
| Name | Required | Default | Description |
|---|---|---|---|
| exit | False | true |
Stop containers when job completes |
| check_service | False | true |
Verify service availability after startup |
| enable_cache | False | true |
Enable Docker layer and dependency caching |
| cache_key_suffix | False | Extra suffix for cache keys (for cache isolation) |
| Name | Required | Default | Description |
|---|---|---|---|
| debug | False | false |
Enable debugging output |
| use_api_path | False | false |
Use source server's URL path (e.g., /r, /infra) for local container |
| additional_plugins | False | Comma-separated list of extra plugin URLs to install | |
| gerrit_init_args | False | Extra arguments for gerrit.war init |
|
| skip_plugin_install | False | false |
Skip pull-replication plugin installation (testing) |
These inputs configure Gerrit with public tunnel URLs for remote access.
| Name | Required | Default | Description |
|---|---|---|---|
| tunnel_host | False | External tunnel hostname (e.g., bore.pub, Tailscale IP). Used for canonicalWebUrl and sshd.advertisedAddress |
|
| tunnel_ports | False | JSON mapping slugs to tunnel ports: {"slug": {"http": 12345, "ssh": 54321}} |
Note: When using tunnels, start the tunnel before invoking this action. Some tunnel tools (like bore) don't require the local port to be listening—they connect on-demand when traffic arrives. Other tunnels (like Tailscale) provide a stable IP that can be used directly with the local ports.
These inputs configure gerrit_to_platform inside running Gerrit containers, enabling them to dispatch GitHub Actions workflows in response to Gerrit events (patchset uploads, comment-added, change merges).
| Name | Required | Default | Description |
|---|---|---|---|
| g2p_enable | False | false |
Enable G2P integration |
| g2p_github_token | False | GitHub PAT for workflow dispatch (see G2P Token Scopes) | |
| g2p_github_owner | False | Target GitHub organisation or user (required when g2p_enable is true) |
|
| g2p_remote_name_style | False | dash |
How Gerrit project names map to GitHub repos: dash, underscore, slash |
| g2p_remote_url | False | Override remote URL pattern (auto-generated from g2p_github_owner) |
|
| g2p_remote_auth_group | False | GitHub Replication |
Gerrit authGroup for the replication remote (must contain "github") |
| g2p_comment_mappings | False | {"recheck":"verify","reverify":"verify","remerge":"merge"} |
JSON mapping of comment keywords to workflow filters |
| g2p_hooks | False | patchset-created,comment-added,change-merged |
Comma-separated Gerrit hooks to enable |
| g2p_validation_mode | False | warn |
Behaviour when GitHub checks fail: error, warn, or skip |
| g2p_validate_workflows | False | true |
Check that the target org has matching Gerrit workflows |
| g2p_validate_repos | False | Comma-separated repos to verify exist in the target org | |
| g2p_ssh_private_key | False | SSH private key for GitHub push-based replication | |
| g2p_github_known_hosts | False | SSH known_hosts entries for github.com (auto-fetched if omitted) | |
| g2p_org_setup | False | verify |
Org-level audit/provisioning mode: provision, verify, or skip (see G2P Org Setup) |
| g2p_org_token_map | False | Base64-encoded JSON mapping orgs to admin PATs (see G2P Org Setup) |
| Name | Description | Example |
|---|---|---|
| container_ids | JSON array of running container IDs | ["abc123", "def456"] |
| container_ips | JSON array of container IP addresses | ["172.17.0.2", "172.17.0.3"] |
| instances | JSON object mapping slug to instance details | See Instances Output |
| gerrit_urls | Comma-separated list of Gerrit HTTP URLs | http://172.17.0.2:8080,http://172.17.0.3:8080 |
| api_paths | JSON object mapping slug to API path details | {"onap": {"api_path": "/r", "api_url": "..."}} |
| ssh_host_keys | JSON object mapping slug to SSH host keys | {"onap": {"ssh_host_ed25519_key": "..."}} |
| g2p_enabled | Whether G2P integration ran | true / false |
| g2p_config_path | Path to generated INI inside container | /var/gerrit/.config/.../gerrit_to_platform.ini |
| g2p_hooks_enabled | JSON array of hooks that got wrapper scripts | ["patchset-created","comment-added"] |
| g2p_github_owner | Configured GitHub owner | modeseven-gerrit-onap |
| g2p_remote_name_style | Configured repository name style | dash |
| g2p_validation_results | JSON array of GitHub check results | [{"check_name":"token_valid","passed":true}] |
| g2p_token_provided | Whether a GitHub token was supplied | true / false |
| g2p_ssh_public_key | Public key for downstream deploy-key setup | ssh-ed25519 AAAA... |
| g2p_org_audit_results | JSON array of org-level audit check results | [{"check_name":"org_secrets","passed":true}] |
| g2p_org_provisioned | Whether any org items got auto-provisioned | true / false |
{
"onap": {
"cid": "abc123def456",
"ip": "172.17.0.2",
"http_port": 8080,
"ssh_port": 29418,
"url": "http://172.17.0.2:8080",
"gerrit_host": "gerrit.onap.org",
"project": "ONAP"
}
}The gerrit_setup input accepts a JSON array of instance configurations:
[
{
"slug": "onap",
"gerrit": "gerrit.onap.org",
"project": "",
"api_path": "/r"
},
{
"slug": "opendaylight",
"gerrit": "git.opendaylight.org",
"project": "regex:releng/.*",
"api_path": "/gerrit",
"ssh_user": "replication-bot",
"ssh_port": "29418"
}
]slug(required) - Unique identifier for this instance (used in container naming and credential lookup)gerrit(required) - Hostname of the source Gerrit server to replicate fromproject(optional) - Project filter for replication:- Empty string (
"") or omitted: Replicate all projects from the server - Literal name:
"releng/lftools"- single project - Comma-separated:
"releng/lftools,ci-management"- multiple projects - Regex pattern:
"regex:releng/.*"or"regex:^infra/.*"- pattern matching (must useregex:prefix to avoid misclassifying literal names containing special characters like.or[)
- Empty string (
api_path(optional) - API path prefix if Gerrit is not at the root (e.g.,/infra,/r,/gerrit). Auto-detected if not provided.ssh_user(optional) - SSH username for the remote Gerrit server. Overrides globalremote_ssh_user. This is the account on the remote server that has yourssh_private_key's public key registered.ssh_port(optional) - SSH port for the remote Gerrit server. Overrides globalremote_ssh_port. Typically29418for Gerrit.
When the project field is empty or omitted, the action will:
- Query the remote Gerrit server's
/projects/API endpoint - Fetch the list of all available projects
- Pre-create empty bare repositories for each project locally
- Configure pull-replication to sync all projects on startup
This enables full server mirroring without needing to specify individual project names.
steps:
- name: "Start Gerrit mirror"
uses: lfreleng-actions/gerrit-server-action@main
with:
gerrit_setup: |
[{
"project": "ONAP",
"slug": "onap",
"gerrit": "gerrit.onap.org"
}]
ssh_private_key: ${{ secrets.GERRIT_SSH_KEY }}
sync_on_startup: truesteps:
- name: "Start multiple Gerrit mirrors"
id: gerrit
uses: lfreleng-actions/gerrit-server-action@main
with:
gerrit_setup: ${{ vars.GERRIT_SETUP }}
ssh_private_key: ${{ secrets.GERRIT_SSH_KEY }}
base_http_port: 9000
base_ssh_port: 30000
- name: "Access instances"
run: |
echo "All URLs: ${{ steps.gerrit.outputs.gerrit_urls }}"
echo "Instance data:"
echo '${{ steps.gerrit.outputs.instances }}' | jq '.'steps:
- name: "Start Gerrit with HTTP auth"
uses: lfreleng-actions/gerrit-server-action@main
with:
gerrit_setup: ${{ vars.GERRIT_SETUP }}
auth_type: http_basic
http_username: ${{ vars.GERRIT_USERNAME }}
http_password: ${{ secrets.GERRIT_PASSWORD }}steps:
- name: "Start Gerrit (persistent)"
id: gerrit
uses: lfreleng-actions/gerrit-server-action@main
with:
gerrit_setup: ${{ vars.GERRIT_SETUP }}
ssh_private_key: ${{ secrets.GERRIT_SSH_KEY }}
exit: false # Keep containers running
- name: "Run tests"
run: |
# Gerrit is available at http://localhost:8080
curl http://localhost:8080/config/server/version
- name: "More work with Gerrit"
run: |
# Container is still available
git clone http://localhost:8080/my-project
- name: "Manual cleanup"
if: always()
run: |
# Cleanup containers manually
docker kill ${{ fromJson(steps.gerrit.outputs.container_ids)[0] }}steps:
- name: "Start specific Gerrit version"
uses: lfreleng-actions/gerrit-server-action@main
with:
gerrit_setup: ${{ vars.GERRIT_SETUP }}
ssh_private_key: ${{ secrets.GERRIT_SSH_KEY }}
gerrit_version: "3.9.4-ubuntu24"
plugin_version: "stable-3.9"steps:
- name: "Selective replication"
uses: lfreleng-actions/gerrit-server-action@main
with:
gerrit_setup: ${{ vars.GERRIT_SETUP }}
ssh_private_key: ${{ secrets.GERRIT_SSH_KEY }}
sync_refs: "+refs/heads/*:refs/heads/*,+refs/tags/*:refs/tags/*"
replication_threads: 8
sync_on_startup: truesteps:
- name: "Start Gerrit with extra plugins"
uses: lfreleng-actions/gerrit-server-action@main
with:
gerrit_setup: ${{ vars.GERRIT_SETUP }}
ssh_private_key: ${{ secrets.GERRIT_SSH_KEY }}
additional_plugins: |
https://example.com/plugins/my-plugin.jar,
https://example.com/plugins/another-plugin.jarThis example shows how to expose Gerrit publicly using an external tunnel. The example uses bore, but other tunnel methods like Tailscale can also be used. Bore tunnels can be started before Gerrit since they connect on-demand.
steps:
# Calculate the local ports that Gerrit will use
- name: "Calculate local ports"
id: ports
run: |
# These match gerrit-action defaults (BASE_PORT + index)
echo "http_port=8080" >> "$GITHUB_OUTPUT"
echo "ssh_port=29418" >> "$GITHUB_OUTPUT"
# Install bore tunnel client
- name: "Install bore"
run: |
BORE_VERSION="0.5.2"
curl -sSL "https://github.com/ekzhang/bore/releases/download/v${BORE_VERSION}/bore-v${BORE_VERSION}-x86_64-unknown-linux-musl.tar.gz" | tar xz
sudo mv bore /usr/local/bin/
# Start tunnels BEFORE Gerrit (bore connects on-demand)
- name: "Start bore tunnels"
id: tunnels
env:
LOCAL_HTTP: ${{ steps.ports.outputs.http_port }}
LOCAL_SSH: ${{ steps.ports.outputs.ssh_port }}
SERVER_SLUG: my-gerrit # Must match slug in gerrit_setup
run: |
# Start tunnels - local ports don't need to be listening yet!
bore local "$LOCAL_HTTP" --to bore.pub > bore-http.log 2>&1 &
bore local "$LOCAL_SSH" --to bore.pub > bore-ssh.log 2>&1 &
# Wait for tunnels to establish
sleep 10
# Extract assigned public ports from logs
HTTP_PORT=$(grep -oP 'listening at bore\.pub:\K\d+' bore-http.log || true)
SSH_PORT=$(grep -oP 'listening at bore\.pub:\K\d+' bore-ssh.log || true)
# Validate extracted ports before using them
if [ -z "$HTTP_PORT" ] || [ -z "$SSH_PORT" ] || \
! [[ "$HTTP_PORT" =~ ^[0-9]+$ ]] || ! [[ "$SSH_PORT" =~ ^[0-9]+$ ]]; then
echo "Error: Failed to extract valid bore tunnel ports from logs." >&2
echo "HTTP_PORT='$HTTP_PORT', SSH_PORT='$SSH_PORT'" >&2
echo "Check bore-http.log and bore-ssh.log for connection issues." >&2
exit 1
fi
# Build tunnel_ports JSON for gerrit-action
TUNNEL_PORTS=$(jq -n \
--arg slug "$SERVER_SLUG" \
--argjson http "$HTTP_PORT" \
--argjson ssh "$SSH_PORT" \
'{($slug): {http: $http, ssh: $ssh}}')
echo "tunnel_ports=$TUNNEL_PORTS" >> "$GITHUB_OUTPUT"
echo "Tunnels ready: bore.pub:$HTTP_PORT (HTTP), bore.pub:$SSH_PORT (SSH)"
# Start Gerrit with tunnel URLs configured from the start
- name: "Start Gerrit mirror"
id: gerrit
uses: lfreleng-actions/gerrit-server-action@main
with:
gerrit_setup: |
[{
"project": "my-project",
"slug": "my-gerrit",
"gerrit": "gerrit.example.org"
}]
ssh_private_key: ${{ secrets.GERRIT_SSH_KEY }}
sync_on_startup: true
# Pass tunnel configuration - Gerrit uses public URLs from the start
tunnel_host: bore.pub
tunnel_ports: ${{ steps.tunnels.outputs.tunnel_ports }}
# Gerrit is now accessible at the public bore.pub URLs!Real-world example: See the tunnel workflow in
test-deploy-gerritfor a complete working implementation with multiple tunnel options.
You can store the Gerrit setup configuration as a repository variable:
Variable Name: GERRIT_SETUP
Variable Value:
[
{
"project": "ONAP",
"slug": "onap",
"gerrit": "gerrit.onap.org"
},
{
"project": "OpenDaylight",
"slug": "opendaylight",
"gerrit": "git.opendaylight.org"
}
]In Workflow:
- uses: lfreleng-actions/gerrit-server-action@main
with:
gerrit_setup: ${{ vars.GERRIT_SETUP }}
ssh_private_key: ${{ secrets.GERRIT_SSH_KEY }}Generate an SSH key pair and add the public key to your Gerrit account:
ssh-keygen -t rsa -b 4096 -f gerrit_key -N ""Add gerrit_key.pub to your Gerrit account's SSH keys, then add
the private key as a GitHub secret:
with:
auth_type: ssh
ssh_private_key: ${{ secrets.GERRIT_SSH_KEY }}with:
auth_type: http_basic
http_username: ${{ vars.GERRIT_USERNAME }}
http_password: ${{ secrets.GERRIT_PASSWORD }}with:
auth_type: bearer_token
bearer_token: ${{ secrets.GERRIT_BEARER_TOKEN }}This action includes intelligent caching for optimal performance:
- Caches Gerrit container images for faster follow-up runs
- Pre-pulls images to reduce startup time
- Smart cache keys based on OS, version, and optional suffix
- Caches downloaded plugins to avoid repeated downloads
- Shares cache across workflows in the same repository
- First run: Downloads and caches all necessary components
- Follow-up runs: 50-80% faster startup using cached layers
- Reduced bandwidth: Fewer external downloads
- Lower costs: Reduced GitHub Actions minutes
with:
enable_cache: falseUse different cache keys for different purposes:
with:
cache_key_suffix: '-production' # Separate cache for production testsOnce started, Gerrit instances are accessible via:
# Using localhost (from host)
curl http://localhost:8080/config/server/version
# Using container IP (from other containers)
curl http://172.17.0.2:8080/config/server/version# Port is configurable (default: 29418)
ssh -p 29418 admin@localhost gerrit version# Clone a repository
git clone http://localhost:8080/my-project
# With authentication; git prompts for the Gerrit HTTP password
git clone http://admin@localhost:8080/my-projectCheck replication status via container logs:
- name: "Check replication"
run: |
docker logs ${{ fromJson(steps.gerrit.outputs.container_ids)[0] }} | grep replicationCheck replicated repositories:
- name: "List replicated repositories"
run: |
docker exec ${{ fromJson(steps.gerrit.outputs.container_ids)[0] }} \
find /var/gerrit/git -name '*.git' -type dCheck container logs:
docker logs <container_id>Common issues:
- Port conflicts (change
base_http_portorbase_ssh_port) - Invalid Gerrit version tag
- Insufficient memory
- Check plugin installation:
docker exec <container_id> ls -la /var/gerrit/plugins/- Verify replication config:
docker exec <container_id> cat /var/gerrit/etc/replication.config-
Check authentication:
- Verify SSH keys are valid
- Ensure credentials have proper permissions
- Check network connectivity to source Gerrit
-
Check replication logs:
docker logs -f <container_id> | grep -i replicationIncrease timeout if Gerrit takes longer to initialize:
with:
replication_timeout: 900 # 15 minutesOr disable health checks for debugging:
with:
check_service: falseIf a Gerrit change is uploaded, reviewed, and merged but no
GitHub Actions workflows run in the target organisation, the
container is most likely missing the Gerrit hooks plugin.
G2P relies on Gerrit invoking the POSIX-shell wrapper scripts
this action installs under /var/gerrit/hooks/
(patchset-created, comment-added, change-merged); each
wrapper exec()s the matching gerrit_to_platform console
script and tees its stdout / stderr to
/var/gerrit/logs/g2p-hooks.log. Without hooks.jar loaded
as a plugin Gerrit never invokes the wrappers and those
log entries never appear.
Check whether the plugin is present in the running container:
docker exec <container_id> ls -la /var/gerrit/plugins/hooks.jarIf the file is missing, the site was initialised without
gerrit init --install-all-plugins. This action always passes
--batch --install-all-plugins to gerrit init and the
gerrit_init_args input only appends to that argv — those
two flags cannot be suppressed by user input. The most likely
remaining cause is an older cached image that pre-dates the
--install-all-plugins change; re-run the deploy workflow
with caching disabled (enable_cache: false) to force a fresh
build and repopulate the bundled plugins.
The most direct signal for whether hooks are firing in real time is the dedicated G2P hook log written by the wrappers this action installs. Tail it while uploading a patchset:
docker exec <container_id> tail -F /var/gerrit/logs/g2p-hooks.logEach invocation produces a structured [g2p-hook][<hook>][pid=…]
header followed by [out] / [err] prefixed lines from the
underlying gerrit_to_platform console script and a final
end pid=… rc=… summary. No entries for the hook you expected
means the Gerrit hooks plugin did not invoke the wrapper at
all — typically because the plugin was not loaded (see the
hooks.jar check above) or the event type is not enabled in
g2p_hooks. Entries present but ending with non-zero rc=
means the wrapper fired and the underlying console script
failed; the [err] lines for the same pid= carry the cause.
For lower-level diagnostics (plugin loader output, JGit errors, SSH session activity) the standard Gerrit logs are still useful:
docker exec <container_id> tail -f /var/gerrit/logs/error_log \
/var/gerrit/logs/sshd_logVerify SSH key format:
ssh-keygen -l -f gerrit_keyTest SSH connection manually:
ssh -i gerrit_key -p 29418 git@gerrit.example.orgThe gerrit_to_platform
package enables a Gerrit instance to dispatch GitHub Actions workflows
in response to Gerrit events. When g2p_enable is set to true, this
action configures the deployed Gerrit container(s) with the files and
wrapper scripts that gerrit_to_platform needs:
gerrit_to_platform.ini— application config with the GitHub token and comment-keyword-to-workflow-filter mappingsreplication.configremote — a detection-only remote that tellsgerrit_to_platformwhich platform, organisation, and repository naming convention to use- Gerrit hook wrapper scripts —
patchset-created,comment-added, andchange-mergedinstalled as POSIX-shell wrappers in/var/gerrit/hooks/that exec() the matching g2p console scripts and tee every invocation (with stdout / stderr) to/var/gerrit/logs/g2p-hooks.log - SSH configuration — keypair and
known_hostsfor github.com
The g2p_github_token input requires a GitHub Personal Access Token.
Either a classic or fine-grained token can be used depending
on your access requirements.
Use a classic token when the Gerrit instance dispatches workflows across multiple organisations (fine-grained tokens are scoped to a single owner).
| Scope | Why it is needed |
|---|---|
workflow |
Trigger workflow dispatch events — the core function of gerrit_to_platform (POST /repos/{owner}/{repo}/actions/workflows/{id}/dispatches). Selecting this scope automatically grants repo (full control of private repositories), which is also needed for listing workflows and repos across organisations. |
read:org |
Read organisation and team membership — required for GET /orgs/{owner} during validation checks. |
Note: The
workflowscope automatically selectsrepo(full control of private repositories) in the GitHub UI. If the target organisation contains only public repositories, you can deselectrepoand select only thepublic_reposub-scope instead.
Use a fine-grained token when dispatching workflows within a single organisation. Set the resource owner to the target organisation and grant these permissions:
- Actions — Read and write (workflow dispatch)
- Contents — Read-only (list workflows and repositories)
- Metadata — Read-only (granted automatically)
- Organization administration — Read-only (validation checks)
- name: "Start Gerrit with G2P"
uses: lfreleng-actions/gerrit-action@main
with:
gerrit_setup: ${{ vars.GERRIT_SETUP }}
ssh_private_key: ${{ secrets.GERRIT_SSH_KEY }}
exit: false
# G2P integration
g2p_enable: true
g2p_github_token: ${{ secrets.G2P_GITHUB_TOKEN }}
g2p_github_owner: my-gerrit-org
g2p_remote_name_style: dash
g2p_validation_mode: warnThe g2p_org_setup input controls whether the action audits — and
optionally creates — the org-level GitHub Actions secrets and
variables that downstream Gerrit-dispatched workflows need to run:
| Mode | Behaviour |
|---|---|
provision |
Audit the target org and auto-create absent required secrets/vars |
verify |
Audit and report only (default); make no changes |
skip |
Skip org-level audits entirely |
Required org-level items checked (and provisioned in provision mode):
- Secret:
GERRIT_SSH_PRIVKEY - Variables:
GERRIT_SERVER,GERRIT_SSH_USER,GERRIT_KNOWN_HOSTS,GERRIT_URL
An optional GERRIT_SSH_PRIVKEY_G2G secret is recorded for
visibility but never blocks the run and never appears as a
warning. It is the SSH private key used by gerrit-to-gerrit
(G2G) replication — when one Gerrit instance pushes changes to
another Gerrit instance rather than to GitHub. Most LF
deployments only mirror Gerrit → GitHub via the G2P flow this
action configures, so the secret is irrelevant and its absence is
expected. Orgs that do run G2G replication should populate the
secret out of band; the audit reports it as found once present.
This action does not configure or provision G2G replication
itself.
In provision mode, every required secret and variable is
always overwritten with the current run's values, even when
the initial audit reports them as already present. This is
deliberate: each fresh Gerrit container build produces a new
ephemeral SSH key, and tunnel host/port assignments may change
between runs. Without overwriting, stale GERRIT_SSH_PRIVKEY /
GERRIT_SERVER / GERRIT_KNOWN_HOSTS / GERRIT_URL values on
the GitHub side would silently diverge from the live Gerrit
instance — workflows would dispatch successfully and then fail
at SSH-push or HTTP-API time. The provisioner uses POST for
absent items and PATCH for existing ones to keep variable
history clean.
If you do not want overwrite-on-every-run semantics, set
g2p_org_setup to verify (audit only) or skip.
Provisioning org-level secrets requires a token with elevated scope
beyond the default g2p_github_token. Provide it via
g2p_org_token_map (recommended) or fall back to g2p_github_token:
- Classic PAT —
admin:orgscope. - Fine-grained PAT —
Organization secrets: Read & writeandOrganization variables: Read & writeon the target org.
The input takes a Base64-encoded JSON array of org/token pairs so a single secret can carry credentials for multiple target orgs.
Pre-encoding JSON (single org):
[
{"github_org": "modeseven-gerrit-onap",
"token": "<github-pat>"}
]Pre-encoding JSON (multiple orgs):
[
{"github_org": "modeseven-gerrit-onap", "token": "<github-pat-onap>"},
{"github_org": "modeseven-gerrit-other", "token": "<github-pat-other>"}
]Encode it (whitespace tolerated; single-line or wrapped both work):
JSON='[{"github_org":"modeseven-gerrit-onap","token":"<github-pat>"}]'
printf '%s' "$JSON" | base64Example resulting value (single line):
W3siZ2l0aHViX29yZyI6Im1vZGVzZXZlbi1nZXJyaXQtb25hcCIsInRva2VuIjoiPGdpdGh1Yi1wYXQ+In1d
Store the encoded string as a repo or org secret (for example
G2P_ORG_TOKENS) and pass it through to the action:
- name: "Start Gerrit with G2P (provision mode)"
uses: lfreleng-actions/gerrit-action@main
with:
gerrit_setup: ${{ vars.GERRIT_SETUP }}
g2p_enable: true
g2p_github_owner: modeseven-gerrit-onap
g2p_github_token: ${{ secrets.G2P_GITHUB_TOKEN }}
g2p_org_setup: provision
g2p_org_token_map: ${{ secrets.G2P_ORG_TOKENS }}When g2p_org_token_map has no entry for the resolved
g2p_github_owner, the action falls back to g2p_github_token and
emits a warning. See docs/GITHUB-ORG-VERIFY-CONFIG.md for the full
design and error-handling matrix.
| Mode | Behaviour |
|---|---|
error |
Fail the step if any GitHub-side check fails |
warn |
Log warnings but continue (default) |
skip |
Skip all GitHub-side checks entirely |
gerrit_to_platform reads the Gerrit replication.config to discover
which platform hosts the target repositories. The action appends a
detection-only remote named github-g2p whose authGroup must contain
the string github (case-insensitive) for detection to succeed. The
remoteNameStyle setting (dash, underscore, or slash) controls
how Gerrit project names (e.g. foo/bar) are converted to GitHub
repository names (e.g. foo-bar).
You can mount custom configuration by modifying the instance before container start. This requires workflow customization beyond the basic action usage.
By default, Gerrit uses H2 database. For production use, consider:
- Mounting external database configuration
- Using PostgreSQL or MySQL backends
- Persisting data volumes between runs
Containers use Docker's default bridge network. For custom networking:
- Use Docker compose for complex setups
- Configure custom networks in workflow
- Use service discovery for multi-container scenarios
- Secrets Management: Always use GitHub Secrets for sensitive data
- SSH Keys: Use dedicated keys with minimal permissions
- Network Isolation: Consider using private runners for sensitive data
- Authentication: Prefer SSH over HTTP basic auth
- Cleanup: Ensure containers stop to avoid data leakage
- G2P Tokens: Use a dedicated PAT with the minimum scopes listed in G2P Token Scopes; never commit tokens to source control
- Max of 10 instances per workflow (port exhaustion)
- Replication time depends on repository size
- SSH authentication requires pre-configured keys on source server
- Container persistence limited to workflow duration
- H2 database by default (not suitable for heavy production use)
This action uses:
- Official Gerrit Docker image:
gerritcodereview/gerrit - Pull-replication plugin from GerritForge
- Docker BuildKit for layer caching
- Bash scripts for orchestration
See the main repository for contribution guidelines.
Apache-2.0
For issues and questions, please use the GitHub issue tracker.
