feat: implement CI pipeline (Jenkinsfile) for 001-ci-pipeline
Implements tasks T001-T016, T018-T022, T024-T026 from specs/001-ci-pipeline/tasks.md (T017/T023 need a real Jenkins instance to verify and are left for manual follow-up). - Add Jenkinsfile: checkout -> install -> environment validation -> typecheck -> lint (+ architecture check) -> format check -> unit -> integration -> E2E -> build -> Docker build -> publish -> deploy, matching the constitution's required stage order. Secrets are always injected from Jenkins credentials at runtime, never read from a repo-committed file. Publish/Deploy are skipped (not failed) on branches with no resolved deploy target. - Fix docker-compose.test.yml: remove fixed container_name on app/postgres/redis, which would have made concurrent CI runs collide (FR-009). Verified locally that two runs under different -p project names no longer share container/volume/network names. - Document the pipeline and local .env setup in README.md. - Mark completed tasks in specs/001-ci-pipeline/tasks.md and record the container_name/compose-down-env-file findings in the spec's requirements checklist notes. Locally verified passing: Dockerfile build, typecheck, lint, architecture check, format check, unit test suite, and the edited docker-compose.test.yml bringing up postgres/redis with isolated per-project container names. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
dd2d803c93
commit
2dffe58496
Vendored
+219
@@ -0,0 +1,219 @@
|
||||
// CI/CD pipeline for supporthub-api.
|
||||
//
|
||||
// Stage order and guarantees are defined by:
|
||||
// - .specify/memory/constitution.md -> "Testing, Observability & CI/CD Gates"
|
||||
// - specs/001-ci-pipeline/contracts/pipeline-stage-contract.md
|
||||
//
|
||||
// Prerequisites (configured on the Jenkins side, never in this repo):
|
||||
// - Agents with Docker and Node.js 20+ available (label: 'docker && node20')
|
||||
// - Credentials (Secret text unless noted) per target environment <env> in [test, prod]:
|
||||
// <env>-postgres-password, <env>-redis-password, <env>-jwt-secret
|
||||
// <env>-aws-access-key-id, <env>-aws-secret-access-key
|
||||
// Plus one Username/Password credential: docker-registry-credentials
|
||||
// - A DOCKER_REGISTRY value (e.g. via a "CI_DOCKER_REGISTRY" global Jenkins env var,
|
||||
// or override the default below) pointing at the org's actual image registry.
|
||||
//
|
||||
// This file intentionally contains no secret values, only credential IDs — see
|
||||
// specs/001-ci-pipeline/research.md "Environment/secrets handling in the pipeline".
|
||||
|
||||
pipeline {
|
||||
agent { label 'docker && node20' }
|
||||
|
||||
options {
|
||||
timestamps()
|
||||
buildDiscarder(logRotator(numToKeepStr: '30'))
|
||||
// Each build gets its own workspace by default; Compose project names below are
|
||||
// additionally scoped by BUILD_NUMBER so concurrent runs never share containers,
|
||||
// networks, or volumes (FR-009 / SC-005).
|
||||
ansiColor('xterm')
|
||||
}
|
||||
|
||||
environment {
|
||||
DOCKER_REGISTRY = "${env.CI_DOCKER_REGISTRY ?: 'registry.example.com/supporthub'}"
|
||||
IMAGE_NAME = 'supporthub-api'
|
||||
COMPOSE_PROJECT = "supporthub-ci-${env.BUILD_NUMBER}"
|
||||
}
|
||||
|
||||
stages {
|
||||
|
||||
stage('Checkout') {
|
||||
steps {
|
||||
checkout scm
|
||||
}
|
||||
}
|
||||
|
||||
stage('Install') {
|
||||
steps {
|
||||
sh 'npm ci'
|
||||
}
|
||||
}
|
||||
|
||||
stage('Environment validation') {
|
||||
steps {
|
||||
script {
|
||||
resolveDeployTarget()
|
||||
writeTargetEnvFile(env.TARGET_ENV == 'none' ? 'test' : env.TARGET_ENV)
|
||||
}
|
||||
// Reuses the existing Zod schema in src/config/env.ts as-is: it throws a
|
||||
// specific, descriptive error on safeParse failure, so this fails fast with
|
||||
// no new validation logic (FR-002).
|
||||
sh "npx tsx --env-file=.env.${env.TARGET_ENV == 'none' ? 'test' : env.TARGET_ENV} -e \"import('./src/config/env.ts').then(() => console.log('Environment OK'))\""
|
||||
}
|
||||
}
|
||||
|
||||
stage('Generate Prisma client') {
|
||||
steps {
|
||||
sh 'npm run prisma:generate'
|
||||
}
|
||||
}
|
||||
|
||||
stage('Typecheck') {
|
||||
steps {
|
||||
sh 'npm run typecheck'
|
||||
}
|
||||
}
|
||||
|
||||
stage('Lint') {
|
||||
steps {
|
||||
sh 'npm run lint'
|
||||
// Enforces Constitution Principle III (module boundaries) server-side —
|
||||
// mirrors .husky/pre-commit so a bypassed/missing local hook can't merge
|
||||
// a boundary violation.
|
||||
sh 'npx tsx scripts/check-architecture.ts'
|
||||
}
|
||||
}
|
||||
|
||||
stage('Format check') {
|
||||
steps {
|
||||
sh 'npm run format:check'
|
||||
}
|
||||
}
|
||||
|
||||
stage('Unit test') {
|
||||
steps {
|
||||
sh 'npm run test:unit'
|
||||
}
|
||||
}
|
||||
|
||||
stage('Integration test') {
|
||||
steps {
|
||||
sh "docker compose -p ${env.COMPOSE_PROJECT} --env-file .env.test -f docker-compose.test.yml up -d --wait postgres redis"
|
||||
sh "docker compose -p ${env.COMPOSE_PROJECT} --env-file .env.test -f docker-compose.test.yml run --rm --build app npm run test:integration"
|
||||
}
|
||||
}
|
||||
|
||||
stage('E2E test') {
|
||||
steps {
|
||||
sh "docker compose -p ${env.COMPOSE_PROJECT} --env-file .env.test -f docker-compose.test.yml run --rm app npm run test:e2e"
|
||||
}
|
||||
}
|
||||
|
||||
stage('Build') {
|
||||
steps {
|
||||
script {
|
||||
def buildTarget = env.TARGET_ENV == 'none' ? 'test' : env.TARGET_ENV
|
||||
sh "npm run build:${buildTarget}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Docker build') {
|
||||
steps {
|
||||
script {
|
||||
def buildTarget = env.TARGET_ENV == 'none' ? 'test' : env.TARGET_ENV
|
||||
env.IMAGE_TAG = "${env.DOCKER_REGISTRY}/${env.IMAGE_NAME}:${buildTarget}-${env.BUILD_NUMBER}"
|
||||
}
|
||||
sh "docker build --build-arg BUILD_COMMAND=\"npm run build:${env.TARGET_ENV == 'none' ? 'test' : env.TARGET_ENV}\" -t ${env.IMAGE_TAG} ."
|
||||
}
|
||||
}
|
||||
|
||||
stage('Publish') {
|
||||
when { expression { env.TARGET_ENV != 'none' } }
|
||||
steps {
|
||||
withCredentials([usernamePassword(credentialsId: 'docker-registry-credentials',
|
||||
usernameVariable: 'REGISTRY_USER',
|
||||
passwordVariable: 'REGISTRY_PASSWORD')]) {
|
||||
sh '''
|
||||
echo "$REGISTRY_PASSWORD" | docker login "$DOCKER_REGISTRY" -u "$REGISTRY_USER" --password-stdin
|
||||
docker push "$IMAGE_TAG"
|
||||
docker logout "$DOCKER_REGISTRY"
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Deploy') {
|
||||
when { expression { env.TARGET_ENV != 'none' } }
|
||||
steps {
|
||||
script {
|
||||
writeTargetEnvFile(env.TARGET_ENV)
|
||||
}
|
||||
sh "docker compose --env-file .env.${env.TARGET_ENV} -f docker-compose.${env.TARGET_ENV == 'prod' ? 'prod' : 'test'}.yml up -d"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
always {
|
||||
// Tear down the ephemeral integration/E2E stack regardless of outcome, and never
|
||||
// leave a generated .env.* file behind in the workspace. --env-file must be passed
|
||||
// to `down` too, or Compose can fail to resolve service config and leave containers
|
||||
// running (confirmed while validating this pipeline locally).
|
||||
sh "docker compose -p ${env.COMPOSE_PROJECT} --env-file .env.test -f docker-compose.test.yml down -v --remove-orphans || true"
|
||||
sh 'rm -f .env.test .env.prod .env.development'
|
||||
cleanWs()
|
||||
}
|
||||
failure {
|
||||
echo "Pipeline failed at stage: ${currentBuild.result}. See the failing stage's log above for the exact command and output — no local reproduction should be necessary (FR-004)."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Resolves env.TARGET_ENV from the branch being built:
|
||||
// main -> prod
|
||||
// develop/test -> test
|
||||
// anything else -> none (validate-only; Publish/Deploy stages are skipped, not failed)
|
||||
void resolveDeployTarget() {
|
||||
def branch = env.BRANCH_NAME ?: ''
|
||||
if (branch == 'main') {
|
||||
env.TARGET_ENV = 'prod'
|
||||
} else if (branch == 'develop' || branch == 'test') {
|
||||
env.TARGET_ENV = 'test'
|
||||
} else {
|
||||
env.TARGET_ENV = 'none'
|
||||
}
|
||||
echo "Resolved deploy target for branch '${branch}': ${env.TARGET_ENV}"
|
||||
}
|
||||
|
||||
// Writes .env.<target> into the workspace from Jenkins credentials — never read from a
|
||||
// file committed to the repository (constitution governance: "secrets never committed").
|
||||
void writeTargetEnvFile(String target) {
|
||||
withCredentials([
|
||||
string(credentialsId: "${target}-postgres-password", variable: 'POSTGRES_PASSWORD'),
|
||||
string(credentialsId: "${target}-redis-password", variable: 'REDIS_PASSWORD'),
|
||||
string(credentialsId: "${target}-jwt-secret", variable: 'JWT_SECRET'),
|
||||
string(credentialsId: "${target}-aws-access-key-id", variable: 'AWS_ACCESS_KEY_ID'),
|
||||
string(credentialsId: "${target}-aws-secret-access-key", variable: 'AWS_SECRET_ACCESS_KEY'),
|
||||
]) {
|
||||
def port = target == 'prod' ? '4503' : (target == 'test' ? '4502' : '4501')
|
||||
def dbName = target == 'prod' ? 'myapp_prod' : (target == 'test' ? 'myapp_test' : 'support_dev')
|
||||
def dbUser = target == 'prod' ? 'myapp_prod' : (target == 'test' ? 'myapp_test' : 'support_user')
|
||||
writeFile file: ".env.${target}", text: """
|
||||
NODE_ENV=${target == 'prod' ? 'production' : target}
|
||||
PORT=${port}
|
||||
BUILD_COMMAND=npm run build:${target}
|
||||
POSTGRES_HOST=postgres
|
||||
POSTGRES_DB=${dbName}
|
||||
POSTGRES_USER=${dbUser}
|
||||
POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
DATABASE_URL=postgresql://${dbUser}:${POSTGRES_PASSWORD}@postgres:5432/${dbName}
|
||||
REDIS_HOST=redis
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=${REDIS_PASSWORD}
|
||||
JWT_SECRET=${JWT_SECRET}
|
||||
AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
|
||||
AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
|
||||
CORS_ORIGINS=${target == 'prod' ? 'https://app.supporthub.com,https://admin.supporthub.com' : 'http://localhost:3000'}
|
||||
""".stripIndent().trim()
|
||||
}
|
||||
}
|
||||
@@ -10,3 +10,21 @@ docker compose --env-file .env.prod -f docker-compose.prod.yml up --build -d
|
||||
# Stop
|
||||
docker compose -f docker-compose.prod.yml down
|
||||
|
||||
# Local environment setup
|
||||
`.env.development`, `.env.test`, and `.env.prod` are gitignored (they hold real credentials) —
|
||||
copy `.env.example` to the one you need and fill in real values before running any command above.
|
||||
|
||||
# CI/CD
|
||||
Every push/PR triggers the Jenkins pipeline defined in `Jenkinsfile`. Stage order:
|
||||
checkout → install → environment validation → typecheck → lint → format check → unit test →
|
||||
integration test → E2E test → build → Docker build → publish → deploy. Publish/deploy only run
|
||||
on branches with a configured deploy target (`main` → prod, `develop`/`test` → test); other
|
||||
branches validate and build only. Pipeline run status and per-stage logs are visible in the
|
||||
Jenkins UI for the relevant job — see `specs/001-ci-pipeline/quickstart.md` for how to validate
|
||||
the pipeline itself, and `specs/001-ci-pipeline/contracts/pipeline-stage-contract.md` for the
|
||||
guarantees each stage makes.
|
||||
|
||||
Required Jenkins credentials (see the header comment in `Jenkinsfile` for exact IDs): per target
|
||||
environment (`test`, `prod`) a Postgres password, Redis password, JWT secret, and AWS access
|
||||
key/secret, plus one shared Docker registry username/password. None of these are ever read from
|
||||
a file in this repository.
|
||||
|
||||
@@ -6,8 +6,6 @@ services:
|
||||
args:
|
||||
BUILD_COMMAND: ${BUILD_COMMAND}
|
||||
|
||||
container_name: support-test
|
||||
|
||||
env_file:
|
||||
- .env.test
|
||||
|
||||
@@ -33,8 +31,6 @@ services:
|
||||
postgres:
|
||||
image: postgres:18-alpine
|
||||
|
||||
container_name: postgres-test
|
||||
|
||||
environment:
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
POSTGRES_USER: ${POSTGRES_USER}
|
||||
@@ -58,8 +54,6 @@ services:
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
|
||||
container_name: redis-test
|
||||
|
||||
command:
|
||||
- redis-server
|
||||
- --requirepass
|
||||
|
||||
@@ -36,3 +36,22 @@
|
||||
Technology & Platform Constraints section already commits to Jenkins per docs/09; that mapping
|
||||
belongs in `/speckit-plan`, not here.
|
||||
- All items pass; no revision iterations were needed.
|
||||
|
||||
## Implementation notes (added during /speckit-implement)
|
||||
|
||||
- `docker-compose.test.yml` had fixed `container_name` values (`support-test`,
|
||||
`postgres-test`, `redis-test`) on all three services — this would have made FR-009/SC-005
|
||||
(concurrent-run isolation) impossible, since Docker container names must be unique per host
|
||||
regardless of Compose project. Removed them so Compose auto-names containers per project
|
||||
(verified locally: two `up` runs under different `-p` project names now produce
|
||||
`<project>-postgres-1` / `<project>-redis-1` etc. with no collision).
|
||||
- `docker compose ... down` needs the same `--env-file` flag as `up`, or it can fail to resolve
|
||||
service config and leave containers running — confirmed by hitting this locally; the
|
||||
`Jenkinsfile`'s `post { always { ... } }` teardown includes it.
|
||||
- The existing `test:unit` npm script (`vitest run` with no path filter) currently runs the
|
||||
entire `tests/**/*.test.ts` glob — including integration/E2E — because `vitest.config.ts`'s
|
||||
`include` isn't scoped per script; only `test:integration`/`test:e2e` narrow by passing an
|
||||
explicit directory. Today's "integration" tests are instantiation-only checks (no real DB/Redis
|
||||
calls yet), so this isn't currently harmful, but it means the `Unit test` stage doesn't
|
||||
actually isolate unit-only coverage. Out of scope to fix here (not part of this feature's
|
||||
requirements) — worth a follow-up once real integration tests exist.
|
||||
|
||||
@@ -36,10 +36,10 @@ Single project — this feature adds one new root-level file, `Jenkinsfile`, plu
|
||||
**Purpose**: Get a buildable pipeline skeleton and confirm the container build this pipeline will
|
||||
drive actually works today, before wiring stage logic into it.
|
||||
|
||||
- [ ] T001 Create `Jenkinsfile` at repo root with declarative pipeline skeleton: `agent`,
|
||||
- [X] T001 Create `Jenkinsfile` at repo root with declarative pipeline skeleton: `agent`,
|
||||
`options { disableConcurrentMultipleBuilds... }`, empty `stages {}` block, and a `post`
|
||||
block placeholder — in `Jenkinsfile`
|
||||
- [ ] T002 [P] Verify the existing multi-stage `Dockerfile` builds cleanly outside CI
|
||||
- [X] T002 [P] Verify the existing multi-stage `Dockerfile` builds cleanly outside CI
|
||||
(`docker build --build-arg BUILD_COMMAND="npm run build:prod" -t supporthub-api-ci .`) so
|
||||
the pipeline's `Docker build` stage has a known-good target — no file changes, verification
|
||||
only
|
||||
@@ -56,13 +56,13 @@ run next.
|
||||
|
||||
**⚠️ CRITICAL**: No user-story stage work can be added until this phase is complete.
|
||||
|
||||
- [ ] T003 Add `Checkout` stage (SCM checkout) to `Jenkinsfile`
|
||||
- [ ] T004 Add `Install` stage (`npm ci`) to `Jenkinsfile` (depends on T003)
|
||||
- [ ] T005 Add `Environment validation` stage to `Jenkinsfile`: materialize `.env.<target>` from
|
||||
- [X] T003 Add `Checkout` stage (SCM checkout) to `Jenkinsfile`
|
||||
- [X] T004 Add `Install` stage (`npm ci`) to `Jenkinsfile` (depends on T003)
|
||||
- [X] T005 Add `Environment validation` stage to `Jenkinsfile`: materialize `.env.<target>` from
|
||||
Jenkins credentials (per research.md's secrets decision — never read the repo's `.env.*`),
|
||||
then invoke the existing Zod schema in `src/config/env.ts` so a missing/malformed variable
|
||||
fails immediately with its existing descriptive error (FR-002) (depends on T004)
|
||||
- [ ] T006 [P] Add a `Generate Prisma client` step (`npm run prisma:generate`) to `Jenkinsfile`,
|
||||
- [X] T006 [P] Add a `Generate Prisma client` step (`npm run prisma:generate`) to `Jenkinsfile`,
|
||||
required before `Typecheck`/`Build` can succeed (depends on T004)
|
||||
|
||||
**Checkpoint**: Checkout → install → env validation → Prisma generate all run and pass on a clean
|
||||
@@ -82,23 +82,23 @@ missing required env var and confirm `Environment validation` fails first (Quick
|
||||
|
||||
### Implementation for User Story 1
|
||||
|
||||
- [ ] T007 [US1] Add `Typecheck` stage (`npm run typecheck`) to `Jenkinsfile` (depends on T006)
|
||||
- [ ] T008 [US1] Add `Lint` stage to `Jenkinsfile`, running both `npm run lint` and
|
||||
- [X] T007 [US1] Add `Typecheck` stage (`npm run typecheck`) to `Jenkinsfile` (depends on T006)
|
||||
- [X] T008 [US1] Add `Lint` stage to `Jenkinsfile`, running both `npm run lint` and
|
||||
`npx tsx scripts/check-architecture.ts` (module-boundary check, matches `.husky/pre-commit`
|
||||
and enforces Constitution Principle III server-side) (depends on T007)
|
||||
- [ ] T009 [US1] Add `Format check` stage (`npm run format:check`) to `Jenkinsfile` (depends on T008)
|
||||
- [ ] T010 [US1] Add `Unit test` stage (`npm run test:unit`) to `Jenkinsfile` (depends on T009)
|
||||
- [ ] T011 [US1] Add a step before `Integration test` that brings up ephemeral `postgres`/`redis`
|
||||
- [X] T009 [US1] Add `Format check` stage (`npm run format:check`) to `Jenkinsfile` (depends on T008)
|
||||
- [X] T010 [US1] Add `Unit test` stage (`npm run test:unit`) to `Jenkinsfile` (depends on T009)
|
||||
- [X] T011 [US1] Add a step before `Integration test` that brings up ephemeral `postgres`/`redis`
|
||||
via `docker-compose.test.yml`, with the Compose project name parameterized by
|
||||
`${BUILD_NUMBER}` for run isolation (FR-009), in `Jenkinsfile` (depends on T010)
|
||||
- [ ] T012 [US1] Add `Integration test` stage (`npm run test:integration`) to `Jenkinsfile`
|
||||
- [X] T012 [US1] Add `Integration test` stage (`npm run test:integration`) to `Jenkinsfile`
|
||||
(depends on T011)
|
||||
- [ ] T013 [US1] Add `E2E test` stage (`npm run test:e2e`) to `Jenkinsfile` (depends on T011)
|
||||
- [ ] T014 [US1] Add `Build` stage (`npm run build:prod`, or the target-specific `build:*` script
|
||||
- [X] T013 [US1] Add `E2E test` stage (`npm run test:e2e`) to `Jenkinsfile` (depends on T011)
|
||||
- [X] T014 [US1] Add `Build` stage (`npm run build:prod`, or the target-specific `build:*` script
|
||||
matching the resolved Deploy Target) to `Jenkinsfile` (depends on T012, T013)
|
||||
- [ ] T015 [US1] Add `Docker build` stage using the root `Dockerfile` (T002's verified command) to
|
||||
- [X] T015 [US1] Add `Docker build` stage using the root `Dockerfile` (T002's verified command) to
|
||||
`Jenkinsfile` (depends on T014)
|
||||
- [ ] T016 [US1] Add a `post` block to `Jenkinsfile` that surfaces which stage failed and its
|
||||
- [X] T016 [US1] Add a `post` block to `Jenkinsfile` that surfaces which stage failed and its
|
||||
captured output on failure (FR-004, SC-002), and tears down the ephemeral
|
||||
`docker-compose.test.yml` stack (`always`) regardless of outcome
|
||||
- [ ] T017 [US1] Manually run Quickstart Scenarios 1, 2, and 5 from
|
||||
@@ -123,18 +123,18 @@ commands (Quickstart Scenario 3); push to a branch with no Deploy Target and con
|
||||
|
||||
### Implementation for User Story 2
|
||||
|
||||
- [ ] T018 [US2] Add branch → Deploy Target resolution logic to `Jenkinsfile` (e.g. `main` → prod,
|
||||
- [X] T018 [US2] Add branch → Deploy Target resolution logic to `Jenkinsfile` (e.g. `main` → prod,
|
||||
a designated test branch → test; everything else → no Deploy Target) (depends on T015)
|
||||
- [ ] T019 [US2] Add `Publish` stage to `Jenkinsfile`: push the `Docker build` image to a
|
||||
- [X] T019 [US2] Add `Publish` stage to `Jenkinsfile`: push the `Docker build` image to a
|
||||
container registry, guarded to run only when a Deploy Target was resolved (T018) (depends
|
||||
on T018)
|
||||
- [ ] T020 [US2] Add a step to `Jenkinsfile` that generates the target's `.env.<target>` from
|
||||
- [X] T020 [US2] Add a step to `Jenkinsfile` that generates the target's `.env.<target>` from
|
||||
Jenkins credentials immediately before deploy (never from the repo copy, per research.md),
|
||||
scoped to the `Deploy` stage's workspace only (depends on T018)
|
||||
- [ ] T021 [US2] Add `Deploy` stage to `Jenkinsfile`: run
|
||||
- [X] T021 [US2] Add `Deploy` stage to `Jenkinsfile`: run
|
||||
`docker compose --env-file <generated> -f docker-compose.<target>.yml up -d` against the
|
||||
resolved Deploy Target, guarded the same way as `Publish` (depends on T019, T020)
|
||||
- [ ] T022 [US2] Confirm (via `Jenkinsfile` `when` conditions) that `Publish`/`Deploy` are marked
|
||||
- [X] T022 [US2] Confirm (via `Jenkinsfile` `when` conditions) that `Publish`/`Deploy` are marked
|
||||
`skipped`, not `failed`, on runs with no resolved Deploy Target (depends on T018)
|
||||
- [ ] T023 [US2] Manually run Quickstart Scenarios 3 and 4 from
|
||||
`specs/001-ci-pipeline/quickstart.md` against a real Jenkins job and confirm both pass,
|
||||
@@ -150,13 +150,13 @@ cleanly after `Docker build`.
|
||||
|
||||
**Purpose**: Documentation and final verification once both stories are implemented.
|
||||
|
||||
- [ ] T024 [P] Add a short "CI/CD" section to `README.md` describing how the pipeline is
|
||||
- [X] T024 [P] Add a short "CI/CD" section to `README.md` describing how the pipeline is
|
||||
triggered, where to view run status, and how to configure required Jenkins credentials
|
||||
(cross-reference `specs/001-ci-pipeline/quickstart.md`)
|
||||
- [ ] T025 [P] Review `Jenkinsfile` line-by-line to confirm no credential value or literal
|
||||
- [X] T025 [P] Review `Jenkinsfile` line-by-line to confirm no credential value or literal
|
||||
environment secret was hardcoded anywhere in the file (SC-004) — should only ever reference
|
||||
Jenkins credential IDs, never raw values
|
||||
- [ ] T026 Update `specs/001-ci-pipeline/checklists/requirements.md` Notes if implementation
|
||||
- [X] T026 Update `specs/001-ci-pipeline/checklists/requirements.md` Notes if implementation
|
||||
surfaced any spec gap not previously captured
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user