mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 14:16:43 +00:00
@@ -260,7 +260,7 @@ jobs:
|
|||||||
id: build
|
id: build
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: docker
|
||||||
file: docker/Dockerfile.integrated
|
file: docker/Dockerfile.integrated
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
push: true
|
push: true
|
||||||
@@ -332,6 +332,17 @@ jobs:
|
|||||||
- name: Pull image
|
- name: Pull image
|
||||||
run: docker pull "${{ steps.image.outputs.ref }}"
|
run: docker pull "${{ steps.image.outputs.ref }}"
|
||||||
|
|
||||||
|
- name: Verify anonymous pull access
|
||||||
|
run: |
|
||||||
|
CLEAN_DOCKER_CONFIG="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "${CLEAN_DOCKER_CONFIG}"' EXIT
|
||||||
|
if ! DOCKER_CONFIG="${CLEAN_DOCKER_CONFIG}" docker pull "${{ steps.image.outputs.ref }}"; then
|
||||||
|
echo "[X] ghcr.io/kaitranntt/ccs is not anonymously pullable." >&2
|
||||||
|
echo "[i] Make the GHCR package public, then rerun the Docker publish workflow." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "[OK] Anonymous pull succeeded for ${{ steps.image.outputs.ref }}"
|
||||||
|
|
||||||
- name: Assert image size budget (amd64)
|
- name: Assert image size budget (amd64)
|
||||||
run: |
|
run: |
|
||||||
chmod +x tests/docker/image-size.sh
|
chmod +x tests/docker/image-size.sh
|
||||||
@@ -471,3 +482,24 @@ jobs:
|
|||||||
"${IMAGE_REF}"
|
"${IMAGE_REF}"
|
||||||
|
|
||||||
echo "[OK] Promoted: :latest :${MINOR} :${MAJOR} → ${IMAGE_REF}"
|
echo "[OK] Promoted: :latest :${MINOR} :${MAJOR} → ${IMAGE_REF}"
|
||||||
|
|
||||||
|
- name: Verify promoted tags are anonymously pullable
|
||||||
|
env:
|
||||||
|
VERSION: ${{ needs.publish-integrated.outputs.version }}
|
||||||
|
run: |
|
||||||
|
OWNER_LOWER=$(echo "${GITHUB_REPOSITORY_OWNER}" | tr '[:upper:]' '[:lower:]')
|
||||||
|
IMAGE="ghcr.io/${OWNER_LOWER}/ccs"
|
||||||
|
MINOR="${VERSION%.*}"
|
||||||
|
MAJOR="${VERSION%%.*}"
|
||||||
|
CLEAN_DOCKER_CONFIG="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "${CLEAN_DOCKER_CONFIG}"' EXIT
|
||||||
|
|
||||||
|
for TAG in latest "${MINOR}" "${MAJOR}"; do
|
||||||
|
REF="${IMAGE}:${TAG}"
|
||||||
|
if ! DOCKER_CONFIG="${CLEAN_DOCKER_CONFIG}" docker pull "${REF}"; then
|
||||||
|
echo "[X] ${REF} is not anonymously pullable." >&2
|
||||||
|
echo "[i] Confirm the GHCR package is public, then rerun promotion." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "[OK] Anonymous pull succeeded for ${REF}"
|
||||||
|
done
|
||||||
|
|||||||
@@ -97,9 +97,11 @@ function evaluateDashboardSunset({ targetTag, baselineVersion, releaseWindow, st
|
|||||||
const versions = parseStableTags(stableTags);
|
const versions = parseStableTags(stableTags);
|
||||||
const hasBaseline = versions.some((version) => compareVersions(version, baseline) === 0);
|
const hasBaseline = versions.some((version) => compareVersions(version, baseline) === 0);
|
||||||
if (compareVersions(target, baseline) > 0 && !hasBaseline) {
|
if (compareVersions(target, baseline) > 0 && !hasBaseline) {
|
||||||
throw new Error(
|
return {
|
||||||
`Cannot count dashboard sunset releases: baseline tag ${baseline.raw} is missing from git tags`,
|
publish: false,
|
||||||
);
|
elapsed: releaseWindow,
|
||||||
|
reason: `legacy dashboard sunset baseline ${baseline.raw} is missing from git tags; skipping deprecated image publish`,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!versions.some((version) => compareVersions(version, target) === 0)) {
|
if (!versions.some((version) => compareVersions(version, target) === 0)) {
|
||||||
|
|||||||
@@ -106,8 +106,9 @@ assert_case "skips at the larger configured window boundary" 0 false 3 \
|
|||||||
assert_failure "rejects prerelease target tags" \
|
assert_failure "rejects prerelease target tags" \
|
||||||
--target "v7.80.0-rc.1" --baseline "7.80.0" --window "2"
|
--target "v7.80.0-rc.1" --baseline "7.80.0" --window "2"
|
||||||
|
|
||||||
assert_failure "fails loudly when baseline tag is unavailable after baseline" \
|
assert_case "skips deprecated publish when baseline tag is unavailable after baseline" 0 false 2 \
|
||||||
--target "v7.81.2" --baseline "7.81.0" --window "2"
|
"v7.81.2" "7.81.0" "2" \
|
||||||
|
$'v7.80.0'
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Dashboard sunset guard tests complete: ${PASS} passed, ${FAIL} failed."
|
echo "Dashboard sunset guard tests complete: ${PASS} passed, ${FAIL} failed."
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { describe, expect, test } from 'bun:test';
|
||||||
|
import { existsSync, readFileSync } from 'node:fs';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
|
||||||
|
const repoRoot = join(import.meta.dir, '../../..');
|
||||||
|
|
||||||
|
describe('docker release workflow context', () => {
|
||||||
|
test('builds the integrated Dockerfile with the context its COPY paths expect', () => {
|
||||||
|
const workflow = readFileSync(join(repoRoot, '.github/workflows/docker-release.yml'), 'utf8');
|
||||||
|
const dockerfile = readFileSync(join(repoRoot, 'docker/Dockerfile.integrated'), 'utf8');
|
||||||
|
|
||||||
|
expect(workflow).toMatch(
|
||||||
|
/Build and push integrated image[\s\S]*context: docker[\s\S]*file: docker\/Dockerfile\.integrated/,
|
||||||
|
);
|
||||||
|
expect(dockerfile).toContain('COPY supervisord.conf /etc/supervisord.conf');
|
||||||
|
expect(dockerfile).toContain('COPY entrypoint-integrated.sh /entrypoint-integrated.sh');
|
||||||
|
expect(existsSync(join(repoRoot, 'docker/supervisord.conf'))).toBe(true);
|
||||||
|
expect(existsSync(join(repoRoot, 'docker/entrypoint-integrated.sh'))).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('keeps integrated smoke tests independent from legacy dashboard publish', () => {
|
||||||
|
const workflow = readFileSync(join(repoRoot, '.github/workflows/docker-release.yml'), 'utf8');
|
||||||
|
|
||||||
|
expect(workflow).not.toMatch(/publish-integrated:[\s\S]*?needs:\s*\[?publish-dashboard/);
|
||||||
|
expect(workflow).toMatch(/smoke-test:[\s\S]*?needs:\s*\[publish-integrated\]/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('verifies immutable and promoted tags without registry credentials', () => {
|
||||||
|
const workflow = readFileSync(join(repoRoot, '.github/workflows/docker-release.yml'), 'utf8');
|
||||||
|
|
||||||
|
expect(workflow).toMatch(
|
||||||
|
/Verify anonymous pull access[\s\S]*DOCKER_CONFIG="\$\{CLEAN_DOCKER_CONFIG\}" docker pull/
|
||||||
|
);
|
||||||
|
expect(workflow).toMatch(
|
||||||
|
/Verify promoted tags are anonymously pullable[\s\S]*DOCKER_CONFIG="\$\{CLEAN_DOCKER_CONFIG\}" docker pull/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -45,14 +45,18 @@ describe('docker dashboard sunset guard', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('fails loudly if the baseline tag is missing after the baseline', () => {
|
test('skips deprecated publish if the baseline tag is missing after the baseline', () => {
|
||||||
expect(() =>
|
const result = evaluateDashboardSunset({
|
||||||
evaluateDashboardSunset({
|
targetTag: 'v7.81.2',
|
||||||
targetTag: 'v7.81.2',
|
baselineVersion: '7.81.0',
|
||||||
baselineVersion: '7.81.0',
|
releaseWindow: 2,
|
||||||
releaseWindow: 2,
|
stableTags: ['v7.80.0'],
|
||||||
stableTags: ['v7.80.0'],
|
});
|
||||||
}),
|
|
||||||
).toThrow('baseline tag v7.81.0 is missing');
|
expect(result).toMatchObject({
|
||||||
|
elapsed: 2,
|
||||||
|
publish: false,
|
||||||
|
});
|
||||||
|
expect(result.reason).toContain('baseline v7.81.0 is missing');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user