Skip to content

Commit 2ec910d

Browse files
authored
Merge branch 'main-enterprise' into bug/issue-842
2 parents 3c8839d + c718e9a commit 2ec910d

30 files changed

Lines changed: 500 additions & 268 deletions

.github/workflows/create-pre-release.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,10 @@ jobs:
7171
- name: Run Functional Tests
7272
id: functionaltest
7373
run: |
74-
docker run --env APP_ID=${{ secrets.APP_ID }} --env PRIVATE_KEY=${{ secrets.PRIVATE_KEY }} --env WEBHOOK_SECRET=${{ secrets.WEBHOOK_SECRET }} -d -p 3000:3000 ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:main-enterprise
74+
CONTAINER_ID=$(docker run --env APP_ID=${{ secrets.APP_ID }} --env PRIVATE_KEY=${{ secrets.PRIVATE_KEY }} --env WEBHOOK_SECRET=${{ secrets.WEBHOOK_SECRET }} --env NODE_ENV=development -d -p 3000:3000 ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:main-enterprise)
7575
sleep 10
76-
curl http://localhost:3000
76+
docker logs $CONTAINER_ID || true
77+
curl --fail --retry 5 --retry-delay 3 --retry-connrefused http://localhost:3000
7778
- run: echo "${{ github.ref }}"
7879
- name: Tag a final release
7980
id: prerelease

Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
FROM node:22-alpine
22
WORKDIR /opt/safe-settings
33
ENV NODE_ENV production
4+
ENV HOST=0.0.0.0
45
## Set the Labels
56
LABEL version="1.0" \
67
description="Probot app which is a modified version of Settings Probot GitHub App" \

docs/deploy.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ This will start the container in the background and detached.
8888
- `docker exec -it safe-settings /bin/sh`
8989
- You will now be inside the running **Docker** container and can perform any troubleshooting needed
9090

91+
### Troubleshooting Docker Build and Runtime Issues
92+
For detailed guidance on debugging Docker image builds, runtime failures, and comparing local vs. GHCR images, see [docker-debugging.md](docker-debugging.md).
93+
9194
## Deploy the app to AWS Lambda
9295

9396
### Production-Ready Template

docs/docker-debugging.md

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
2+
# Docker Build and Test Debugging Runbook
3+
4+
This document summarizes the Docker experiments done for this repo and converts them into a repeatable workflow for debugging local image builds and GHCR images.
5+
6+
## Goals
7+
8+
- Build and test the app image from local source.
9+
- Compare behavior with the published image from GHCR.
10+
- Quickly diagnose common failures (container startup, shell access, port exposure, env setup).
11+
12+
## What We Learned From The Experiments
13+
14+
- The image is Alpine-based, so use /bin/sh, not /bin/bash.
15+
- Use docker rm (or docker container rm) to remove containers. Commands like docker delete, docker destroy, or docker remove do not exist.
16+
- To pass host binding correctly, use HOST=0.0.0.0 (equal sign), not HOST:0.0.0.0.
17+
- Port mapping is required for host access: -p 3000:3000.
18+
- Supplying .env values is required for realistic startup testing.
19+
- Testing both local and GHCR images with the same runtime flags makes behavior comparisons easier.
20+
21+
## Prerequisites
22+
23+
- Docker is installed and running.
24+
- A valid .env file exists at repo root.
25+
- You are in repo root.
26+
27+
## 1) Build And Test Local Image
28+
29+
Build the local image:
30+
31+
```bash
32+
docker build -t safe-settings:local .
33+
```
34+
35+
Run container in foreground with explicit runtime env and port mapping:
36+
37+
```bash
38+
docker run --name safe-settings-local \
39+
--env-file ./.env \
40+
--env NODE_ENV=development \
41+
--env HOST=0.0.0.0 \
42+
-p 3000:3000 \
43+
-it safe-settings:local
44+
```
45+
46+
If startup fails, inspect logs:
47+
48+
```bash
49+
docker logs safe-settings-local
50+
```
51+
52+
Shell into running container for investigation:
53+
54+
```bash
55+
docker exec -it safe-settings-local /bin/sh
56+
```
57+
58+
Clean up:
59+
60+
```bash
61+
docker rm -f safe-settings-local
62+
```
63+
64+
## 2) Pull And Test GHCR Image
65+
66+
Pull published image:
67+
68+
```bash
69+
docker pull ghcr.io/github/safe-settings:2.1.19
70+
```
71+
72+
Run with the same env and port flags used for local testing:
73+
74+
```bash
75+
docker run --name safe-settings-ghcr \
76+
--env-file ./.env \
77+
--env NODE_ENV=development \
78+
--env HOST=0.0.0.0 \
79+
-p 3000:3000 \
80+
-it ghcr.io/github/safe-settings:2.1.19
81+
```
82+
83+
Inspect logs:
84+
85+
```bash
86+
docker logs safe-settings-ghcr
87+
```
88+
89+
Debug inside container:
90+
91+
```bash
92+
docker exec -it safe-settings-ghcr /bin/sh
93+
```
94+
95+
Clean up:
96+
97+
```bash
98+
docker rm -f safe-settings-ghcr
99+
```
100+
101+
## 3) Fast Differential Debug (Local vs GHCR)
102+
103+
Use this when one image works and the other does not.
104+
105+
1. Run both images with identical flags (env, HOST, port mapping).
106+
2. Compare startup logs side-by-side.
107+
3. Compare environment inside each container:
108+
109+
```bash
110+
docker exec -it safe-settings-local /bin/sh -c 'env | sort'
111+
docker exec -it safe-settings-ghcr /bin/sh -c 'env | sort'
112+
```
113+
114+
4. Confirm app process is listening on expected port inside container:
115+
116+
```bash
117+
docker exec -it safe-settings-local /bin/sh -c 'netstat -lntp 2>/dev/null || ss -lntp'
118+
docker exec -it safe-settings-ghcr /bin/sh -c 'netstat -lntp 2>/dev/null || ss -lntp'
119+
```
120+
121+
5. Validate host reachability:
122+
123+
```bash
124+
curl -i http://localhost:3000/
125+
```
126+
127+
## 4) Common Failure Patterns And Fixes
128+
129+
Symptom: container exits immediately.
130+
Likely causes:
131+
- Missing required variables in .env.
132+
- Invalid app credentials.
133+
Checks:
134+
- docker logs <container-name>
135+
- Confirm .env has required app settings.
136+
137+
Symptom: cannot connect from host to localhost:3000.
138+
Likely causes:
139+
- Missing -p 3000:3000.
140+
- App not binding to all interfaces.
141+
Checks:
142+
- Ensure HOST=0.0.0.0 is set.
143+
- Ensure port mapping is present.
144+
145+
Symptom: cannot shell into container with bash.
146+
Likely cause:
147+
- Alpine image does not include bash.
148+
Fix:
149+
- Use /bin/sh.
150+
151+
Symptom: name conflict when re-running tests.
152+
Likely cause:
153+
- Old container still exists.
154+
Fix:
155+
- docker rm -f <container-name>
156+
157+
## 5) Minimal Known-Good Commands
158+
159+
Local:
160+
161+
```bash
162+
docker build -t safe-settings:local . && \
163+
docker run --rm --name safe-settings-local \
164+
--env-file ./.env \
165+
--env NODE_ENV=development \
166+
--env HOST=0.0.0.0 \
167+
-p 3000:3000 \
168+
-it safe-settings:local
169+
```
170+
171+
GHCR:
172+
173+
```bash
174+
docker pull ghcr.io/github/safe-settings:2.1.19 && \
175+
docker run --rm --name safe-settings-ghcr \
176+
--env-file ./.env \
177+
--env NODE_ENV=development \
178+
--env HOST=0.0.0.0 \
179+
-p 3000:3000 \
180+
-it ghcr.io/github/safe-settings:2.1.19
181+
```

index.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ module.exports = (robot, { getRouter }, Settings = require('./lib/settings')) =>
199199
async function createCheckRun (context, pull_request, head_sha, head_branch) {
200200
const { payload } = context
201201
// robot.log.debug(`Check suite was requested! for ${context.repo()} ${pull_request.number} ${head_sha} ${head_branch}`)
202-
const res = await context.octokit.checks.create({
202+
const res = await context.octokit.rest.checks.create({
203203
owner: payload.repository.owner.login,
204204
repo: payload.repository.name,
205205
name: 'Safe-setting validator',
@@ -211,13 +211,13 @@ module.exports = (robot, { getRouter }, Settings = require('./lib/settings')) =>
211211
async function info () {
212212
const github = await robot.auth()
213213
const installations = await github.paginate(
214-
github.apps.listInstallations.endpoint.merge({ per_page: 100 })
214+
github.rest.apps.listInstallations.endpoint.merge({ per_page: 100 })
215215
)
216216
robot.log.debug(`installations: ${JSON.stringify(installations)}`)
217217
if (installations.length > 0) {
218218
const installation = installations[0]
219219
const github = await robot.auth(installation.id)
220-
const app = await github.apps.getAuthenticated()
220+
const app = await github.rest.apps.getAuthenticated()
221221
appSlug = app.data.slug
222222
robot.log.debug(`Validated the app is configured properly = \n${JSON.stringify(app.data, null, 2)}`)
223223
}
@@ -228,7 +228,7 @@ module.exports = (robot, { getRouter }, Settings = require('./lib/settings')) =>
228228
const github = await robot.auth()
229229

230230
const installations = await github.paginate(
231-
github.apps.listInstallations.endpoint.merge({ per_page: 100 })
231+
github.rest.apps.listInstallations.endpoint.merge({ per_page: 100 })
232232
)
233233

234234
if (installations.length > 0) {
@@ -577,11 +577,11 @@ module.exports = (robot, { getRouter }, Settings = require('./lib/settings')) =>
577577
output: { title: 'Starting NOP', summary: 'initiating...' }
578578
}
579579
robot.log.debug(`Updating check run ${JSON.stringify(params)}`)
580-
await context.octokit.checks.update(params)
580+
await context.octokit.rest.checks.update(params)
581581

582582
params = Object.assign(context.repo(), { pull_number: pull_request.number })
583583

584-
const changes = await context.octokit.pulls.listFiles(params)
584+
const changes = await context.octokit.rest.pulls.listFiles(params)
585585
const files = changes.data.map(f => { return f.filename })
586586

587587
const settingsModified = files.includes(Settings.FILE_PATH)
@@ -609,7 +609,7 @@ module.exports = (robot, { getRouter }, Settings = require('./lib/settings')) =>
609609
output: { title: 'No Safe-settings changes detected', summary: 'No changes detected' }
610610
}
611611
robot.log.debug(`Completing check run ${JSON.stringify(params)}`)
612-
await context.octokit.checks.update(params)
612+
await context.octokit.rest.checks.update(params)
613613
})
614614

615615
robot.on('repository.created', async context => {

lib/configManager.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ module.exports = class ConfigManager {
1919
try {
2020
const repo = { owner: this.context.repo().owner, repo: env.ADMIN_REPO }
2121
const params = Object.assign(repo, { path: filePath, ref: this.ref })
22-
const response = await this.context.octokit.repos.getContent(params).catch(e => {
22+
const response = await this.context.octokit.rest.repos.getContent(params).catch(e => {
2323
this.log.error(`Error getting settings ${e}`)
2424
})
2525

lib/plugins/archive.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ module.exports = class Archive {
1111

1212
async getRepo () {
1313
try {
14-
const { data } = await this.github.repos.get({
14+
const { data } = await this.github.rest.repos.get({
1515
owner: this.repo.owner,
1616
repo: this.repo.repo
1717
})
@@ -32,13 +32,13 @@ module.exports = class Archive {
3232
return new NopCommand(
3333
this.constructor.name,
3434
this.repo,
35-
this.github.repos.update.endpoint(this.settings),
35+
this.github.rest.repos.update.endpoint(this.settings),
3636
change,
3737
'INFO'
3838
)
3939
}
4040

41-
const { data } = await this.github.repos.update({
41+
const { data } = await this.github.rest.repos.update({
4242
owner: this.repo.owner,
4343
repo: this.repo.repo,
4444
archived

lib/plugins/autolinks.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ module.exports = class Autolinks extends Diffable {
88
// }
99

1010
async find () {
11-
const { data } = await this.github.repos.listAutolinks(this.repo)
11+
const { data } = await this.github.rest.repos.listAutolinks(this.repo)
1212
return data
1313
}
1414

@@ -43,13 +43,13 @@ module.exports = class Autolinks extends Diffable {
4343
return new NopCommand(
4444
this.constructor.name,
4545
this.repo,
46-
this.github.repos.createAutolink.endpoint(attrs),
46+
this.github.rest.repos.createAutolink.endpoint(attrs),
4747
'Add autolink'
4848
)
4949
}
5050

5151
try {
52-
return this.github.repos.createAutolink(attrs)
52+
return this.github.rest.repos.createAutolink(attrs)
5353
} catch (e) {
5454
if (e?.response?.data?.errors?.[0]?.code === 'already_exists') {
5555
this.log.debug(`Did not update ${key_prefix}, as it already exists`)
@@ -68,10 +68,10 @@ module.exports = class Autolinks extends Diffable {
6868
return new NopCommand(
6969
this.constructor.name,
7070
this.repo,
71-
this.github.repos.deleteAutolink.endpoint(attrs),
71+
this.github.rest.repos.deleteAutolink.endpoint(attrs),
7272
'Remove autolink'
7373
)
7474
}
75-
return this.github.repos.deleteAutolink(attrs)
75+
return this.github.rest.repos.deleteAutolink(attrs)
7676
}
7777
}

0 commit comments

Comments
 (0)