gh-promotion-app
π How it works | π Contributing
How it works
The gh-promotion-app is a service that automates the promotion of GitHub branch across environments. It is designed to
operate as a GitHub App and respond to the webhook events to which its App is subscribed.
It currently supports the following event types:
| Event Type |
Description |
push |
Change is pushed to a given branch |
pull_request |
Pull request is opened, reopened or closed |
pull_request_review |
Pull request review is approved |
check_suite |
Check suite is completed |
deployment_status |
Deployment status is marked as success |
status |
When the status of a Git commit changes to success |
workflow_run |
Workflow run conclusion is completed and status is success |
[!TIP]
Check the full docs locally with pkgsite by running the following command:
go install golang.org/x/pkgsite/cmd/pkgsite@latest \
&& pkgsite
Overview
The gh-promotion-app can be run in three modes:
- lambda-http: The application is deployed as an AWS Lambda function and is triggered by an API Gateway endpoint.
go run main.go lambda http # or go run main.go --mode=lambda-http
Input: HTTP request containing the GitHub webhook payload w/ Headers.
- lambda-event: The application is deployed as an AWS Lambda function and is triggered an EventBridge rule.
go run main.go lambda event # or go run main.go --mode=lambda-event
Input: EventBridge / CloudWatch event which encapsulates the GitHub webhook payload in the detail field.
[!NOTE]
The incoming event expected values are as follows:
{
"detail": <original_webhook_payload>,
"detail-type": <webhook_payload_type>, (e.g. pull, pull_request, check_suite, etc.),
"source": <your_custom_event_source>,
...
}
- service: The application is deployed as a standalone service and listens for incoming HTTP requests.
go run main.go service # or go run main.go --mode=service
Input: HTTP request containing the GitHub webhook payload w/ Headers.
Feedback
[!NOTE]
Only one feedback mechanism should be enabled at a time to avoid conflicts.
Check Run (default)
GitHub offers a more detailed check-run feedback mechanism. The initial format is equivalent to the commit status,
however when clicked, it provides a more detailed view of the promotion process.
Example...

Commit Status
Commits that are part of a promotion are marked with a status check. The format is as follows:
{source}β{target} - {status} @ {timestamp}
Example...

Rollback
The application supports rolling back the last promotion stage (e.g. production) to a previous commit by pushing to a dedicated rollback branch. When enabled, pushing to rollback-<lastStage> (e.g. rollback-production) triggers the following:
- The rollback branch commit is validated against the target stage β it must point to an older commit in the linear history (i.e. it must be behind the current target).
- The target stage branch is force-updated to the rollback branch commit (with automatic retry on transient GitHub API failures).
- Any stages listed in
cascadeStages that belong to the promotion path are also force-updated to the same commit, preventing the rolled-back code from being re-promoted automatically.
| Promotion path |
cascadeStages: ["canary"] |
rollback-production affects |
| main β staging β canary β production |
yes |
production + canary |
| main β canary β production |
yes |
production + canary |
| main β staging β production |
yes (canary not in path) |
production only |
| main β production |
yes (canary not in path) |
production only |
The rollback feature is disabled by default. Enable it via configuration:
promotion:
rollback:
enabled: true
prefix: "rollback-" # default
cascadeStages: ["canary"] # additional stages to roll back alongside the last stage
[!IMPORTANT]
The rollback branch must never be ahead of the target stage in git history. The application actively validates this constraint and rejects invalid rollback attempts.
Configuration
The application can be configured using environment variables, a YAML configuration file and/or command-line arguments.
The location of the configuration file is specified using the -c, --config flag, it defaults to config.yaml.
Sample configuration file
config.sample.yaml
---
global:
mode: <string> # (defaults to "lambda")
logging:
verbosity: <int> # 0:WarnLevel 1:InfoLevel 2:DebugLevel (defaults to 0)
callerTrace: <bool> # (defaults to false)
s3:
upload:
enabled: <bool> # (defaults to false)
bucketName: <string>
promotion:
defaultStages: <[]string> # (defaults to ["main", "stating", "canary", "production"])
dynamicPromotion:
enabled: <bool> # (defaults to true)
key: <string> # (defaults to "gitops-promotion-path")
push:
createTargetRef: <bool> # (defaults to true)
createPullRequestInDraftModeKey: <string> # (defaults to "gitops-promotion-draft-pr")
rollback:
enabled: <bool> # (defaults to false)
prefix: <string> # (defaults to "rollback-")
cascadeStages: <[]string> # additional stages to roll back alongside the last stage (defaults to [])
feedback:
commitStatus:
enabled: <bool> # (defaults to true)
context: <string> # (defaults to "{source}β{target}")
checkRun:
enabled: <bool> # (defaults to true)
name: <string> # (defaults to "{source}β{target}")
github:
authMode: <string> # (defaults to "ssm")
ssmKey: <string>
webhookSecret: <string>
service:
path: <string> # (defaults to "/")
addr: <string>
port: <string> # (defaults to "8080")
timeout: <duration> # (defaults to "5s")
lambda:
payloadType: <string> # (defaults to "api-gateway-v2")
For all available configuration options, see the included sample configuration
file config.sample.yaml
and refer to the Usage section.
[!NOTE]
When the github.authMode key is set to token, the GITHUB_TOKEN environment variable must be set with a valid
GitHub token.
If the promotion.feedback.checkRun.enabled key is set to true, the fetched token value must be spawned from a
GitHub app installation.
Authentication modes
GitHub interactions are handled by:
Token (GITHUB_TOKEN)
Personal access token w/ permissions compatible with the selected configuration.
[!NOTE]
PATs are not allowed to create new check-run(s). You will need to use a GitHub app installation instead.
GitHub App
At this time, the application requires that the GitHub app installation secrets are stored in AWS SSM.
The supported format is as follows:
{
"app_id": "<int64>",
"private_key": "<string>",
"webhook_secret": "<string>"
}
The above credentials are then used to authenticate to GitHub on behalf of the app.
Fetched secrets are cached in-memory per-GitHub app installation ID as to avoid unnecessary requests.
AWS interactions are handled by the aws-sdk-go-v2 SDK.
[!NOTE]
The GitHub app permissions must be compatible with the selected configuration.
Usage
Usage:
[flags]
[command]
Available Commands:
lambda
service
-c, --config string path to the configuration file (default "config.yaml")
--create-missing-target-branches [CREATE_MISSING_TARGET_BRANCHES] Create missing target branches (default true)
--feedback-check-run [FEEDBACK_CHECK_RUN] Enable check-run feedback (default true)
--feedback-check-run-name string [FEEDBACK_CHECK_RUN_NAME] The name to use when creating the check run. Supported placeholders: {source}, {target} (default "{source}β{target}")
--feedback-commit-status [FEEDBACK_COMMIT_STATUS] Enable commit status feedback (default true)
--feedback-commit-status-context string [FEEDBACK_COMMIT_STATUS_CONTEXT] The context key to use when pushing the commit status to the repository. Supported placeholders: {source}, {target} (default "{source}β{target}")
--github-app-ssm-arn string [GITHUB_APP_SSM_ARN] The SSM parameter key to use when fetching GitHub App credentials
-A, --github-auth-mode string [GITHUB_AUTH_MODE] Authentication credentials provider. Supported values are 'token' and 'ssm'. (default "ssm")
--github-webhook-secret string [GITHUB_WEBHOOK_SECRET] The secret to use when validating incoming GitHub webhook payloads. If not specified, no validation is performed
-h, --help help for this command
--mode string [MODE] The application runtime mode. Possible values are 'lambda-event', 'lambda-http' and 'service' (default "lambda")
--promotion-default-stages strings [PROMOTION_DEFAULT_STAGES] The default promotion stages (default [sdasd])
--promotion-dynamic [PROMOTION_DYNAMIC] Enable dynamic promotion (default true)
--promotion-dynamic-custom-property-key string [DYNAMIC_PROMOTION_KEY] The key to use when fetching the dynamic promoter configuration (default "gitops-promotion-path")
--promotion-report-s3-upload [PROMOTION_REPORT_S3_UPLOAD] Enable S3 upload of promotion reports
--promotion-report-s3-upload-bucket string [PROMOTION_REPORT_S3_BUCKET] The S3 bucket to use when uploading promotion reports
-v, --verbose count [VERBOSE] Increase logger verbosity (default WarnLevel)
-V, --caller-trace [CALLER_TRACE] Enable caller trace in logs
Contributing
- Feel free to open an issue describing the problem you are facing or the feature you want to see implemented.
- If you want to contribute, fork the repository and submit a pull request.