ci: publish and deploy on merge to development instead of main - #58
Conversation
The Docker Image Publish workflow now triggers on push to `development`
rather than `main`. Tag (`v*`) behaviour is unchanged.
Effect:
merge -> development : builds, pushes gateway:<sha> + gateway:dev-latest,
bumps the digest in gitops, triggers the dev cluster deploy
tag v* : builds, pushes gateway:<tag> + gateway:latest,
triggers the Ansible VM deploy
merge -> main : no longer publishes or deploys
`:latest` remains tag-driven and is untouched by this change.
Also renames the deploy-dev step labels and token id from "(main)" to
"(development)" so they match what actually triggers them. The `git push
origin main` on line 118 is the gitops repo's default branch and is
deliberately left alone.
📝 WalkthroughWalkthroughThe Docker publishing workflow now runs on pushes to Estimated code review effort: 1 (Trivial) | ~5 minutes Suggested reviewers: 🚥 Pre-merge checks | ✅ 7 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (7 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/docker-publish.yml:
- Line 11: Restrict the Docker Hub credential currently exposed through the
docker job’s DOCKER_TOKEN environment to the docker/login-action step only. Pass
secrets.OPT_DOCKER_ACCESS_TOKEN directly to that action or define a step-scoped
environment variable, and remove the job-level secret scope while preserving the
existing login behavior.
- Line 11: Add job-level timeout-minutes values to both the docker and
deploy-dev jobs in the workflow, choosing bounds appropriate for their build and
deployment durations. Ensure each publishing job has a finite timeout while
preserving the existing job steps and behavior.
- Line 11: Add workflow-level permissions in the Docker publish workflow,
setting GITHUB_TOKEN access to contents: read. Preserve write access only
through the existing narrowly scoped GitHub App tokens used by the docker and
deploy-dev jobs.
- Line 11: Add a workflow-level concurrency declaration in the Docker publishing
workflow using one fixed shared group for all runs, rather than keying it by
github.ref, and set cancel-in-progress to false so publishing runs queue without
cancellation.
- Around line 176-183: Update both token-generation steps identified by
infra-dispatch-token and infra-dispatch-token-dev to explicitly set
permission-contents to write in their action inputs. Keep the existing
repository and authentication configuration unchanged.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository: getoptimum/coderabbit/.coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: ca1af303-64d3-46af-988b-63edbd4470e8
📒 Files selected for processing (1)
.github/workflows/docker-publish.yml
| push: | ||
| branches: | ||
| - main | ||
| - development |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Limit the Docker Hub token to the login step.
DOCKER_TOKEN is job-scoped at Line 20, so every action and shell step in the docker job receives the credential. The new development trigger increases the code paths that receive this secret.
Pass secrets.OPT_DOCKER_ACCESS_TOKEN only to docker/login-action, or define it as a step-scoped environment variable.
As per path instructions, workflows must avoid secret exposure and use least-privilege secret scope.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/docker-publish.yml at line 11, Restrict the Docker Hub
credential currently exposed through the docker job’s DOCKER_TOKEN environment
to the docker/login-action step only. Pass secrets.OPT_DOCKER_ACCESS_TOKEN
directly to that action or define a step-scoped environment variable, and remove
the job-level secret scope while preserving the existing login behavior.
Source: Path instructions
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Add bounded timeouts to both publishing jobs.
The docker and deploy-dev jobs have no job-level timeout-minutes. A multi-platform build or gh api dispatch can remain active indefinitely when a runner or external service hangs.
Add bounded values appropriate to the build and deployment durations.
As per path instructions, workflows must define job timeouts.
Also applies to: 176-192
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/docker-publish.yml at line 11, Add job-level
timeout-minutes values to both the docker and deploy-dev jobs in the workflow,
choosing bounds appropriate for their build and deployment durations. Ensure
each publishing job has a finite timeout while preserving the existing job steps
and behavior.
Source: Path instructions
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
file=".github/workflows/docker-publish.yml"
if ! rg -n -C 4 '^\s*permissions:' "$file"; then
echo "No explicit permissions block found" >&2
exit 1
fiRepository: getoptimum/optimum-gateway
Length of output: 507
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
file=".github/workflows/docker-publish.yml"
printf '%s\n' '--- workflow header and triggers ---'
sed -n '1,45p' "$file"
printf '%s\n' '--- permissions, concurrency, timeouts, and jobs ---'
rg -n -C 3 '^[[:space:]]*(permissions|concurrency|timeout-minutes):|^[[:space:]]{2}[A-Za-z0-9_-]+:' "$file"
printf '%s\n' '--- complete job definitions around permissions and credentials ---'
sed -n '40,230p' "$file"Repository: getoptimum/optimum-gateway
Length of output: 9464
Add workflow-level least-privilege permissions. docker and deploy-dev have no job-level permissions and currently inherit repository defaults for GITHUB_TOKEN, which may allow writes. Add permissions: contents: read at workflow scope. Keep write access in the narrowly scoped GitHub App tokens.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/docker-publish.yml at line 11, Add workflow-level
permissions in the Docker publish workflow, setting GITHUB_TOKEN access to
contents: read. Preserve write access only through the existing narrowly scoped
GitHub App tokens used by the docker and deploy-dev jobs.
Source: Path instructions
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
file=".github/workflows/docker-publish.yml"
if ! rg -n '^\s*concurrency:' "$file"; then
echo "No workflow concurrency group found" >&2
exit 1
fiRepository: getoptimum/optimum-gateway
Length of output: 200
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
file=".github/workflows/docker-publish.yml"
printf '%s\n' '--- workflow structure ---'
ast-grep outline "$file" --lang yaml 2>/dev/null || true
printf '%s\n' '--- relevant declarations ---'
rg -n -C 3 '^(name:|on:| [A-Za-z0-9_-]+:|permissions:|concurrency:|jobs:)|timeout-minutes|uses:|docker/login-action|docker/build-push-action|deploy-dev|GitHub App|permission-|DOCKER_TOKEN|git push|repository_dispatch|workflow_dispatch' "$file" || true
printf '%s\n' '--- numbered workflow sections ---'
sed -n '1,240p' "$file"Repository: getoptimum/optimum-gateway
Length of output: 12763
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
import re
path = Path(".github/workflows/docker-publish.yml")
text = path.read_text()
top_level = [
line for line in text.splitlines()
if line and not line.startswith((" ", "\t")) and re.match(r"^[A-Za-z0-9_-]+:", line)
]
print("top_level_keys:", [line.split(":", 1)[0] for line in top_level])
print("workflow_concurrency:", bool(re.search(r"(?m)^concurrency\s*:", text)))
print("docker_push_gitops_main:", bool(re.search(r"(?m)^\s*git push origin main\s*$", text)))
print("development_trigger:", bool(re.search(r"(?m)^\s*-\s*development\s*$", text)))
print("deploy_dev_dispatches:", "client_payload[digest]" in text and "dev-latest" in text)
print("docker_timeout:", bool(re.search(r"(?ms)^ docker:\n.*?^\s+timeout-minutes\s*:", text)))
print("deploy_dev_timeout:", bool(re.search(r"(?ms)^ deploy-dev:\n.*?^\s+timeout-minutes\s*:", text)))
PYRepository: getoptimum/optimum-gateway
Length of output: 339
Add a shared workflow-level concurrency group
The workflow has no concurrency declaration. Concurrent runs can race on git push origin main and dispatch deployment digests out of order. Use one group for all publishing runs, not a group keyed by github.ref, because both branch and tag runs update the same GitOps branch. Set cancel-in-progress: false.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/docker-publish.yml at line 11, Add a workflow-level
concurrency declaration in the Docker publishing workflow using one fixed shared
group for all runs, rather than keying it by github.ref, and set
cancel-in-progress to false so publishing runs queue without cancellation.
Source: Path instructions
| - name: Generate token for infrastructure dispatch (development) | ||
| id: infra-dispatch-token-dev | ||
| uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2 | ||
| with: | ||
| app-id: ${{ secrets.GH_APP_ID }} | ||
| private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} | ||
| owner: getoptimum | ||
| repositories: infrastructure |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
curl -fsSL \
"https://raw.githubusercontent.com/actions/create-github-app-token/29824e69f54612133e76f7eaac726eef6c875baf/action.yml" |
rg -n 'permission-contents'Repository: getoptimum/optimum-gateway
Length of output: 190
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- workflow structure and token references ---'
rg -n -C 5 \
'create-github-app-token|infra-dispatch-token|repository_dispatch|DOCKER_TOKEN|permissions:|concurrency:|timeout-minutes|uses:' \
.github/workflows/docker-publish.yml
printf '%s\n' '--- pinned action metadata ---'
curl -fsSL \
'https://raw.githubusercontent.com/actions/create-github-app-token/29824e69f54612133e76f7eaac726eef6c875baf/action.yml' |
sed -n '1,180p'Repository: getoptimum/optimum-gateway
Length of output: 16818
🌐 Web query:
site:github.com/actions/create-github-app-token README permissions inputs omitted all permissions installation token repository dispatch Contents write
💡 Result:
When using the actions/create-github-app-token GitHub Action, you can explicitly define permissions for the generated installation access token by using the permission-{permission-name} input format [1][2]. By default, if no permission-* inputs are provided, the generated token inherits all permissions granted to the GitHub App installation [3][2]. However, it is a recommended security practice to explicitly list only the permissions required for your specific workflow use case [1][2]. Regarding your specific scenario: 1. Repository Dispatch: Creating a repository_dispatch event requires the contents: write permission [4][5]. If you are manually configuring permissions and find your workflow failing with a "Resource not accessible by integration" error, ensure that permission-contents: write is explicitly included in your action configuration [4][5]. 2. Omitted Permissions: If you previously relied on the default behavior (inheriting all permissions) and are now experiencing "Resource not accessible by integration" errors after adding permission-* inputs, it is likely that you have omitted a required permission [6]. Because the action now strictly enforces the permissions you define, the token will lack any permissions not explicitly specified in your input list [7][6]. To troubleshoot or resolve missing permission issues: - Verify that the GitHub App installation actually possesses the required permissions (permissions must be granted to the installation, not just the app) [2]. - If you are unsure which permissions are required for a specific API endpoint, consult the GitHub REST API documentation for that endpoint [5]. - If you wish to revert to default behavior, you can remove all permission-* inputs, causing the token to inherit all installation permissions again [6]. Alternatively, you can explicitly add all necessary permissions one by one until the error resolves [6].
Citations:
- 1: https://github.com/actions/create-github-app-token/blob/main/README.md
- 2: https://github.com/actions/create-github-app-token/blob/5c1e00d1af39c515564da2ae21cc9dedf0793346/README.md
- 3: https://github.com/actions/create-github-app-token
- 4: How to fix Resource not accessible by integration? actions/create-github-app-token#112
- 5: Resource not accessible by integration actions/create-github-app-token#258
- 6: Changes in permission-* handling since version 2.0.4 actions/create-github-app-token#248
- 7: Using
permission-*option withactions/create-github-app-tokendoes not restrict token permissions actions/create-github-app-token#237
🌐 Web query:
site:docs.github.com REST repositories create a repository dispatch event GitHub App installation access token Contents write
💡 Result:
To trigger a repository dispatch event using a GitHub App, you must make a POST request to the GitHub REST API endpoint: POST /repos/{owner}/{repo}/dispatches [1][2]. To perform this action using a GitHub App installation access token, the app must have at least read-level access to the "Contents" repository permission [3]. This permission is required to subscribe to and trigger the repository_dispatch event [3][4]. When making the request, you must provide: 1. event_type: A custom string (100 characters or fewer) used as the event name [1][2]. 2. client_payload: An optional JSON object containing data that your GitHub Actions workflow or GitHub App can use [1][2]. Authentication is performed by including the GitHub App installation access token in the Authorization header of your request: Authorization: Bearer YOUR_INSTALLATION_ACCESS_TOKEN [5].
Citations:
- 1: https://docs.github.com/en/rest/repos/repos?api=
- 2: https://docs.github.com/en/enterprise-cloud@latest/rest/repos/repos?apiVersion=2022-11-28
- 3: https://docs.github.com/en/webhooks/webhook-events-and-payloads?actionType=create
- 4: https://docs.github.com/en/webhooks/webhook-events-and-payloads?actionType=created
- 5: https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-an-installation-access-token-for-a-github-app
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- pinned action README permission behavior ---'
curl -fsSL \
'https://raw.githubusercontent.com/actions/create-github-app-token/29824e69f54612133e76f7eaac726eef6c875baf/README.md' |
rg -n -C 8 'permission-|all permissions|permissions' | head -160
printf '%s\n' '--- official repository dispatch documentation ---'
curl -fsSL \
'https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28' |
python3 - <<'PY'
import sys
from html.parser import HTMLParser
class Text(HTMLParser):
def __init__(self):
super().__init__()
self.parts = []
def handle_data(self, data):
value = " ".join(data.split())
if value:
self.parts.append(value)
p = Text()
p.feed(sys.stdin.read())
text = " ".join(p.parts)
needle = "Create a repository dispatch event"
pos = text.find(needle)
if pos < 0:
print("dispatch section not found")
else:
print(text[pos:pos + 5000])
PYRepository: getoptimum/optimum-gateway
Length of output: 4191
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
doc=/tmp/github-repos-rest.html
curl -fsSL \
'https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28' \
-o "$doc"
printf '%s\n' '--- repository dispatch section in official REST docs ---'
rg -in -C 12 \
'create a repository dispatch event|repository_dispatch|Contents.*(read|write)|fine-grained' \
"$doc" | head -240
printf '%s\n' '--- official REST API description ---'
api=/tmp/github-rest-api.yaml
curl -fsSL \
'https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.yaml' \
-o "$api"
rg -n -C 16 \
'/repos/\{owner\}/\{repo\}/dispatches|repository dispatch|contents.*write|contents.*read' \
"$api" | head -240Repository: getoptimum/optimum-gateway
Length of output: 50383
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
python3 - <<'PY'
from html.parser import HTMLParser
from html import unescape
path = "/tmp/github-repos-rest.html"
class Parser(HTMLParser):
def __init__(self):
super().__init__()
self.out = []
def handle_data(self, data):
text = " ".join(data.split())
if text:
self.out.append(text)
parser = Parser()
with open(path, encoding="utf-8") as f:
parser.feed(f.read())
text = " ".join(parser.out)
start = text.find("Create a repository dispatch event")
if start < 0:
raise SystemExit("dispatch section not found")
# Stop before the next endpoint heading.
end = text.find("Get the hash algorithm for a repository", start)
if end < 0:
end = start + 7000
print(text[start:end][:9000])
PYRepository: getoptimum/optimum-gateway
Length of output: 200
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
python3 - <<'PY'
from html import unescape
from html.parser import HTMLParser
path = "/tmp/github-repos-rest.html"
raw = open(path, encoding="utf-8").read()
needle = 'id="create-a-repository-dispatch-event"'
positions = []
pos = 0
while True:
pos = raw.find(needle, pos)
if pos < 0:
break
positions.append(pos)
pos += len(needle)
print("heading occurrences:", len(positions), positions)
for i, pos in enumerate(positions):
print(f"\n--- occurrence {i + 1} at {pos} ---")
fragment = raw[max(0, pos - 500):pos + 12000]
parser = HTMLParser()
parts = []
parser.handle_data = lambda data: parts.append(" ".join(data.split()))
parser.feed(fragment)
text = " ".join(x for x in parts if x)
print(text[:9000])
PYRepository: getoptimum/optimum-gateway
Length of output: 3600
Restrict both infrastructure App tokens.
Set permission-contents: write on infra-dispatch-token and infra-dispatch-token-dev. Both tokens call repos/getoptimum/infrastructure/dispatches. Without explicit permission-* inputs, the action inherits all permissions granted to the installation.
🧰 Tools
🪛 zizmor (1.29.0)
[error] 178-178: dangerous use of GitHub App tokens (github-app): app token inherits blanket installation permissions
(github-app)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/docker-publish.yml around lines 176 - 183, Update both
token-generation steps identified by infra-dispatch-token and
infra-dispatch-token-dev to explicitly set permission-contents to write in their
action inputs. Keep the existing repository and authentication configuration
unchanged.
Sources: Path instructions, Linters/SAST tools
Moves the Docker Image Publish trigger from
maintodevelopment. Tag (v*) behaviour is unchanged.Change
on: push: branches: - - main + - development tags: - v*Resulting behaviour
developmentgateway:<sha>+gateway:dev-latestv*gateway:<tag>+gateway:latestmain:lateststays tag-driven and is not affected.Notes
developmentbranch has been created frommain.deploy-devstep labels and token id from(main)to(development)so they describe what actually triggers them.git push origin mainin the gitops step is intentionally unchanged — that is the gitops repo's default branch, not this one.Sequencing
This must merge to
mainfor the change to take effect on both branches: forpushevents GitHub uses the workflow file from the pushed ref, so untilmaincarries this, pushes tomainwould still publish. After merge, bringdevelopmentup to date withmain.Summary by CodeRabbit
developmentbranch.