@starci/hfs 4.12.0 → 4.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +43 -0
- package/README.md +8 -9
- package/cache/app-families.d.mts +30 -0
- package/cache/app-families.mjs +94 -0
- package/cache/content-index.d.mts +13 -0
- package/cache/content-index.mjs +37 -0
- package/cache/db-family.d.mts +13 -0
- package/cache/db-family.mjs +37 -0
- package/cache/fake-session.d.mts +42 -0
- package/cache/fake-session.mjs +57 -0
- package/cache/fingerprint.d.mts +28 -0
- package/cache/fingerprint.mjs +120 -0
- package/cache/format-family.d.mts +13 -0
- package/cache/format-family.mjs +49 -0
- package/cache/git.d.mts +11 -0
- package/cache/git.mjs +13 -0
- package/cache/hash.d.mts +15 -0
- package/cache/hash.mjs +58 -0
- package/cache/hfs-family.d.mts +35 -0
- package/cache/hfs-family.mjs +164 -0
- package/cache/import-graph.d.mts +75 -0
- package/cache/import-graph.mjs +204 -0
- package/cache/jsonl.d.mts +31 -0
- package/cache/jsonl.mjs +115 -0
- package/cache/lint-family.d.mts +162 -0
- package/cache/lint-family.mjs +433 -0
- package/cache/order.d.mts +1 -0
- package/cache/order.mjs +2 -0
- package/cache/progress.d.mts +19 -0
- package/cache/progress.mjs +68 -0
- package/cache/quality-baselines.d.mts +109 -0
- package/cache/quality-baselines.mjs +235 -0
- package/cache/records.d.mts +33 -0
- package/cache/records.mjs +54 -0
- package/cache/report.d.mts +19 -0
- package/cache/report.mjs +32 -0
- package/cache/run-log.d.mts +17 -0
- package/cache/run-log.mjs +37 -0
- package/cache/run.d.mts +33 -0
- package/cache/run.mjs +65 -0
- package/cache/session.d.mts +29 -0
- package/cache/session.mjs +197 -0
- package/cache/side-key.d.mts +10 -0
- package/cache/side-key.mjs +47 -0
- package/cache/test-family.d.mts +130 -0
- package/cache/test-family.mjs +258 -0
- package/cache/trust.d.mts +53 -0
- package/cache/trust.mjs +157 -0
- package/cache/types-family.d.mts +74 -0
- package/cache/types-family.mjs +154 -0
- package/cache/types.d.mts +99 -0
- package/cache/types.mjs +8 -0
- package/cache/wiring-family.d.mts +14 -0
- package/cache/wiring-family.mjs +43 -0
- package/coverage/cli.d.mts +4 -2
- package/coverage/cli.mjs +44 -5
- package/db/migrate.d.mts +16 -0
- package/db/migrate.mjs +21 -4
- package/db/postgres.d.mts +23 -0
- package/db/postgres.mjs +32 -1
- package/lint/run.d.mts +47 -1
- package/lint/run.mjs +157 -5
- package/package.json +2 -1
- package/release/gate.d.mts +2 -0
- package/release/gate.mjs +4 -2
- package/release/records.d.mts +15 -0
- package/release/records.mjs +25 -0
- package/release/urgent.mjs +23 -4
- package/runtime/knowledge/grammars/core/playbook.yaml +12 -18
- package/runtime/knowledge/hfs/canon-pins.yaml +8 -8
- package/runtime/knowledge/hfs/ci-standard.yaml +29 -6
- package/runtime/knowledge/hfs/rules.yaml +97 -7
- package/runtime/knowledge/hfs/slots.yaml +19 -7
- package/runtime/modules/kernel/failure-codes.yaml +46 -4
- package/runtime/modules/schemas/application-stacks.schema.yaml +26 -0
- package/runtime/scripts/hfs/architecture/hfs.d.mts +6 -6
- package/runtime/scripts/hfs/check.mjs +1 -1
- package/runtime/scripts/hfs/gate-threshold.mjs +2 -1
- package/runtime/scripts/hfs/rules/boot-proof.d.mts +5 -0
- package/runtime/scripts/hfs/rules/boot-proof.mjs +15 -1
- package/runtime/scripts/hfs/rules/ci-custody.mjs +3 -1
- package/runtime/scripts/hfs/rules/ci-standard.mjs +32 -3
- package/runtime/scripts/hfs/rules/database-migrations.d.mts +14 -0
- package/runtime/scripts/hfs/rules/database-migrations.mjs +55 -0
- package/runtime/scripts/hfs/rules/database.mjs +2 -1
- package/runtime/scripts/hfs/rules/deploy-workflow.mjs +53 -6
- package/runtime/scripts/hfs/rules/release-provenance.mjs +32 -4
- package/runtime/scripts/hfs/rules/stacks.mjs +5 -1
- package/runtime/scripts/hfs/rules/supply-chain.mjs +4 -1
- package/runtime/scripts/hfs/rules/unused-symbol-translators.mjs +98 -7
- package/runtime/scripts/hfs/rules/unused-symbol.d.mts +9 -2
- package/runtime/scripts/hfs/rules/unused-symbol.mjs +15 -2
- package/runtime/scripts/hfs/runtime-rules/git-triggers.mjs +21 -5
- package/runtime/scripts/hfs/stack-deploy-config.d.mts +6 -0
- package/runtime/scripts/hfs/stack-deploy-config.mjs +32 -1
- package/runtime/scripts/hfs/stacks-gate.mjs +2 -0
- package/runtime/scripts/hfs/tree-findings.mjs +3 -1
- package/scaffold/add-inbox-lite.mjs +2 -4
- package/scaffold/add-queue-lite.mjs +2 -3
- package/scaffold/add-table.mjs +2 -4
- package/scaffold/migration-stamp.d.mts +11 -0
- package/scaffold/migration-stamp.mjs +32 -0
- package/src/helpers/hfs-arch-fixture.d.mts +1 -1
- package/src/main.d.mts +4 -1
- package/src/main.mjs +189 -74
- package/src/stack-records-double.d.mts +31 -0
- package/stack/cli.d.mts +3 -1
- package/stack/cli.mjs +17 -11
- package/stack/config.d.mts +6 -0
- package/stack/converge.d.mts +2 -0
- package/stack/converge.mjs +1 -1
- package/stack/db-phases.d.mts +185 -0
- package/stack/db-phases.mjs +346 -0
- package/stack/deploy.d.mts +18 -4
- package/stack/deploy.mjs +235 -70
- package/stack/journal.d.mts +127 -0
- package/stack/journal.mjs +182 -0
- package/stack/manager-backup.d.mts +54 -0
- package/stack/manager-backup.mjs +136 -0
- package/stack/release-store.d.mts +44 -0
- package/stack/release-store.mjs +89 -6
- package/stack/remote.d.mts +2 -0
- package/stack/remote.mjs +31 -11
- package/stack/server-key.d.mts +2 -0
- package/stack/server-key.mjs +1 -1
- package/stack/status.d.mts +49 -0
- package/stack/status.mjs +114 -0
- package/stack/urgent-floor.d.mts +10 -61
- package/stack/urgent-floor.mjs +28 -123
- package/stack/verify.mjs +26 -0
- package/sync/ci-commands.mjs +10 -5
- package/sync/format-worker.d.mts +1 -0
- package/sync/format-worker.mjs +17 -0
- package/sync/format.d.mts +21 -3
- package/sync/format.mjs +99 -7
- package/sync/index.d.mts +3 -6
- package/sync/index.mjs +12 -12
- package/sync/stack-images.d.mts +25 -10
- package/sync/stack-images.mjs +215 -54
- package/templates/app/ci-workflows/github/workflows/ci.yml +114 -7
- package/templates/app/ci-workflows/github/workflows/images.yml +21 -65
- package/templates/app/ci-workflows/github/workflows/stack-deploy.yml +130 -37
- package/templates/app/ci-workflows-lite/github/workflows/ci.yml +114 -7
- package/templates/app/ci-workflows-lite/github/workflows/images.yml +21 -65
- package/templates/app/ci-workflows-lite/github/workflows/stack-deploy.yml +130 -37
- package/templates/app/gitignore +1 -0
- package/templates/app/gitignore-lite +1 -0
- package/templates/app/package-scripts/db.json +1 -1
- package/templates/app/skeleton/.gitattributes +2 -0
- package/templates/app/skeleton-lite/.gitattributes +2 -0
- package/templates/be/skeleton/src/modules/platform/config/env-source.config.ts +2 -1
- package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +0 -63
|
@@ -1,33 +1,70 @@
|
|
|
1
1
|
{{header}}
|
|
2
|
-
# Deploys the app's Docker Swarm stack. This file only assembles arguments: the logic lives in the verbs connect,
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
2
|
+
# Deploys the app's Docker Swarm stack. This file only assembles arguments: the logic lives in the verbs connect, deploy, rollback and disconnect
|
|
3
|
+
# of "starci app stack". It has two callers:
|
|
4
|
+
# ci.yml calls it (workflow_call) once per environment with deploy.auto, after the verdict job, with the digests of the images that run
|
|
5
|
+
# pushed and the CI run that proves the commit. A green release tag deploys by itself.
|
|
6
|
+
# by hand Actions > deploy stack > Run workflow, from a release tag v* whose version is the package.json version.
|
|
7
|
+
# operation deploy deploys the release of the tag: the immutable digests of its images (the run's, or the registry's sha-<sha> tags when
|
|
8
|
+
# dispatched), the CI evidence of the commit, then render, store, pre-pull, migrate (expand), converge, post-deploy, verify, accept
|
|
9
|
+
# and the contract migrations; a failure redeploys the previous accepted release
|
|
10
|
+
# operation redeploy the same, although the accepted release already runs this commit
|
|
7
11
|
# operation rollback redeploys a stored release; "release" is 16 lowercase hex characters, empty means the previous accepted release
|
|
8
|
-
# The environment
|
|
9
|
-
# vars
|
|
12
|
+
# The environment names a GitHub environment whose deployment refs are restricted to the tags v* and that holds:
|
|
13
|
+
# vars STARCI_DEPLOY_PAUSED (true stops the automatic deploy of that environment without a code change; a manual run still works), and a
|
|
14
|
+
# variable named in deploy.passEnv when the stack interpolates one
|
|
10
15
|
# secrets SOPS_AGE_KEY (that environment's server identity, stored there by starci app secret server-key --env <env> --backup github --execute;
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
16
|
+
# the jobs are bound to the environment, so they read it and never a repository secret of that name)
|
|
17
|
+
# The transport is sealed: the CI deploy key, the pinned host keys and the host are the member vps-ci under .starcistacks/<env>/secrets, opened with
|
|
18
|
+
# the server identity (a key of its own, never the owner's, authorised on the manager with a forced command). Nothing else is a secret: the images
|
|
19
|
+
# are read with the workflow token. Inputs reach scripts only through env. The log of a public repository is public: no step prints a host or a path.
|
|
15
20
|
name: deploy stack
|
|
16
21
|
|
|
17
22
|
on:
|
|
23
|
+
workflow_call:
|
|
24
|
+
inputs:
|
|
25
|
+
environment:
|
|
26
|
+
description: GitHub environment holding the server identity and the sealed custody under .starcistacks
|
|
27
|
+
required: true
|
|
28
|
+
type: string
|
|
29
|
+
operation:
|
|
30
|
+
description: deploy, redeploy or rollback
|
|
31
|
+
required: false
|
|
32
|
+
type: string
|
|
33
|
+
default: deploy
|
|
34
|
+
release:
|
|
35
|
+
description: "Rollback only: release id (16 lowercase hex) to deploy again; empty means the previous accepted release"
|
|
36
|
+
required: false
|
|
37
|
+
type: string
|
|
38
|
+
default: ""
|
|
39
|
+
images:
|
|
40
|
+
description: "JSON object, service -> repository@sha256:digest of the images the calling run pushed; empty reads the registry's sha-<sha> tags"
|
|
41
|
+
required: false
|
|
42
|
+
type: string
|
|
43
|
+
default: ""
|
|
44
|
+
ci_run:
|
|
45
|
+
description: Id of the CI run that proves the commit; empty finds the green CI run of the commit
|
|
46
|
+
required: false
|
|
47
|
+
type: string
|
|
48
|
+
default: ""
|
|
49
|
+
auto:
|
|
50
|
+
description: The automatic deploy of a green release tag (it stops when STARCI_DEPLOY_PAUSED is true)
|
|
51
|
+
required: false
|
|
52
|
+
type: boolean
|
|
53
|
+
default: false
|
|
18
54
|
workflow_dispatch:
|
|
19
55
|
inputs:
|
|
20
56
|
environment:
|
|
21
|
-
description:
|
|
57
|
+
description: GitHub environment holding the server identity and the sealed custody under .starcistacks
|
|
22
58
|
required: true
|
|
23
59
|
type: environment
|
|
24
60
|
operation:
|
|
25
|
-
description: Deploy
|
|
61
|
+
description: Deploy the release of this tag, deploy it again, or roll back to a stored release
|
|
26
62
|
required: true
|
|
27
63
|
type: choice
|
|
28
64
|
default: deploy
|
|
29
65
|
options:
|
|
30
66
|
- deploy
|
|
67
|
+
- redeploy
|
|
31
68
|
- rollback
|
|
32
69
|
release:
|
|
33
70
|
description: "Rollback only: release id (16 lowercase hex) to deploy again; empty means the previous accepted release"
|
|
@@ -38,69 +75,125 @@ on:
|
|
|
38
75
|
permissions:
|
|
39
76
|
contents: read
|
|
40
77
|
|
|
41
|
-
concurrency:
|
|
42
|
-
group: stack-${{ inputs.environment }}
|
|
43
|
-
cancel-in-progress: false
|
|
44
|
-
|
|
45
78
|
jobs:
|
|
46
|
-
|
|
47
|
-
if: ${{ inputs.operation
|
|
79
|
+
deploy:
|
|
80
|
+
if: ${{ inputs.operation != 'rollback' }}
|
|
48
81
|
runs-on: ubuntu-latest
|
|
49
82
|
environment: ${{ inputs.environment }}
|
|
50
83
|
timeout-minutes: 60
|
|
51
84
|
permissions:
|
|
52
85
|
contents: read
|
|
53
|
-
packages:
|
|
86
|
+
packages: read
|
|
54
87
|
actions: read
|
|
88
|
+
concurrency:
|
|
89
|
+
group: stack-${{ inputs.environment }}
|
|
90
|
+
cancel-in-progress: false
|
|
55
91
|
env:
|
|
56
92
|
STACK_ENVIRONMENT: ${{ inputs.environment }}
|
|
57
93
|
steps:{{stackGuard}}
|
|
58
94
|
- name: require release tag
|
|
59
95
|
shell: bash
|
|
60
96
|
run: '[[ "$GITHUB_REF" == refs/tags/v* ]] || exit 1'
|
|
97
|
+
- name: deploy pause
|
|
98
|
+
id: pause
|
|
99
|
+
shell: bash
|
|
100
|
+
env:
|
|
101
|
+
AUTO: ${{ inputs.auto }}
|
|
102
|
+
PAUSED: ${{ vars.STARCI_DEPLOY_PAUSED }}
|
|
103
|
+
run: |
|
|
104
|
+
if [[ "$AUTO" == true && "$PAUSED" == true ]]; then
|
|
105
|
+
echo "paused=true" >> "$GITHUB_OUTPUT"
|
|
106
|
+
echo "::notice::STARCI_DEPLOY_PAUSED is true for this environment: this release was not deployed"
|
|
107
|
+
echo "### Deploy paused" >> "$GITHUB_STEP_SUMMARY"
|
|
108
|
+
echo "STARCI_DEPLOY_PAUSED is true for this environment, so the automatic deploy was skipped. Run this workflow by hand to deploy the release." >> "$GITHUB_STEP_SUMMARY"
|
|
109
|
+
else
|
|
110
|
+
echo "paused=false" >> "$GITHUB_OUTPUT"
|
|
111
|
+
fi
|
|
61
112
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
|
113
|
+
if: ${{ steps.pause.outputs.paused != 'true' }}
|
|
62
114
|
- name: release tag matches package version
|
|
115
|
+
if: ${{ steps.pause.outputs.paused != 'true' }}
|
|
63
116
|
run: node -e "const version=require('./package.json').version;if(process.env.GITHUB_REF_NAME!=='v'+version)process.exit(1)"
|
|
64
117
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
118
|
+
if: ${{ steps.pause.outputs.paused != 'true' }}
|
|
65
119
|
with:
|
|
66
120
|
node-version: {{nodeMajor}}
|
|
67
121
|
cache: npm
|
|
68
122
|
- run: npm ci --ignore-scripts
|
|
123
|
+
if: ${{ steps.pause.outputs.paused != 'true' }}
|
|
69
124
|
- name: install SOPS
|
|
125
|
+
if: ${{ steps.pause.outputs.paused != 'true' }}
|
|
70
126
|
uses: mdgreenwald/mozilla-sops-action@bb2d96a13ef438ad122e5827a57ebd75d6ff118d # v2.1.0
|
|
71
127
|
with:
|
|
72
128
|
version: v3.13.2
|
|
73
|
-
- name: image repositories
|
|
74
|
-
id: names
|
|
75
|
-
shell: bash
|
|
76
|
-
run: |
|
|
77
|
-
owner="${GITHUB_REPOSITORY_OWNER,,}"{{stackNames}}
|
|
78
129
|
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
|
130
|
+
if: ${{ steps.pause.outputs.paused != 'true' }}
|
|
79
131
|
- uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
|
|
132
|
+
if: ${{ steps.pause.outputs.paused != 'true' }}
|
|
80
133
|
with:
|
|
81
134
|
registry: ghcr.io
|
|
82
135
|
username: ${{ github.actor }}
|
|
83
|
-
password: ${{ github.token }}
|
|
136
|
+
password: ${{ github.token }}
|
|
137
|
+
- name: image digests
|
|
138
|
+
id: images
|
|
139
|
+
if: ${{ steps.pause.outputs.paused != 'true' }}
|
|
140
|
+
shell: bash
|
|
141
|
+
env:
|
|
142
|
+
IMAGES: ${{ inputs.images }}
|
|
143
|
+
run: |
|
|
144
|
+
set -euo pipefail
|
|
145
|
+
owner="${GITHUB_REPOSITORY_OWNER,,}"
|
|
146
|
+
[[ -n "$IMAGES" ]] || IMAGES='{}'
|
|
147
|
+
while read -r service app; do
|
|
148
|
+
ref=$(jq -r --arg service "$service" '.[$service] // empty' <<<"$IMAGES")
|
|
149
|
+
if [[ -z "$ref" ]]; then
|
|
150
|
+
repo="ghcr.io/${owner}/{{project}}-${app}"
|
|
151
|
+
digest=$(docker buildx imagetools inspect "$repo:sha-$GITHUB_SHA" --format '{{json .Manifest}}' | jq -r .digest)
|
|
152
|
+
ref="${repo}@${digest}"
|
|
153
|
+
fi
|
|
154
|
+
[[ "$ref" =~ ^ghcr\.io/[a-z0-9._/-]+@sha256:[0-9a-f]{64}$ ]] || { echo "::error::no immutable image for ${service}"; exit 1; }
|
|
155
|
+
echo "${service}=${ref}" >> "$GITHUB_OUTPUT"
|
|
156
|
+
done <<'LIST'
|
|
157
|
+
{{stackImageList}}
|
|
158
|
+
LIST
|
|
159
|
+
- name: CI run of the commit
|
|
160
|
+
id: ci
|
|
161
|
+
if: ${{ steps.pause.outputs.paused != 'true' }}
|
|
162
|
+
shell: bash
|
|
163
|
+
env:
|
|
164
|
+
GH_TOKEN: ${{ github.token }}
|
|
165
|
+
CI_RUN: ${{ inputs.ci_run }}
|
|
166
|
+
run: |
|
|
167
|
+
set -euo pipefail
|
|
168
|
+
if [[ -z "$CI_RUN" ]]; then
|
|
169
|
+
CI_RUN=$(gh api "repos/${GITHUB_REPOSITORY}/actions/workflows/ci.yml/runs?head_sha=${GITHUB_SHA}&event=push&status=success&per_page=100" --jq '[.workflow_runs[].id] | first // empty')
|
|
170
|
+
fi
|
|
171
|
+
[[ "$CI_RUN" =~ ^[1-9][0-9]*$ ]] || { echo "::error::no green CI run of this commit: a release deploys only a commit the CI proved"; exit 1; }
|
|
172
|
+
echo "run=$CI_RUN" >> "$GITHUB_OUTPUT"
|
|
84
173
|
- name: connect to the swarm manager
|
|
174
|
+
if: ${{ steps.pause.outputs.paused != 'true' }}
|
|
85
175
|
env:
|
|
86
176
|
XDG_DATA_HOME: ${{ runner.temp }}/starci-data
|
|
87
|
-
|
|
88
|
-
VPS_KNOWN_HOSTS: ${{ secrets.VPS_KNOWN_HOSTS }}
|
|
89
|
-
VPS_HOST: ${{ vars.VPS_HOST }}
|
|
90
|
-
VPS_USER: ${{ vars.VPS_USER }}
|
|
177
|
+
SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }}
|
|
91
178
|
run: npx --no-install starci app stack connect --env "$STACK_ENVIRONMENT" --execute
|
|
92
179
|
- name: deploy the release
|
|
180
|
+
if: ${{ steps.pause.outputs.paused != 'true' }}
|
|
181
|
+
shell: bash
|
|
93
182
|
env:
|
|
94
183
|
XDG_DATA_HOME: ${{ runner.temp }}/starci-data
|
|
95
184
|
GH_TOKEN: ${{ github.token }}
|
|
96
|
-
SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }}
|
|
185
|
+
SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }}
|
|
186
|
+
OPERATION: ${{ inputs.operation }}
|
|
187
|
+
CI_RUN: ${{ steps.ci.outputs.run }}{{stackImageEnv}}{{stackPassEnv}}
|
|
97
188
|
run: |
|
|
189
|
+
again=()
|
|
190
|
+
[[ "$OPERATION" != redeploy ]] || again=(--redeploy)
|
|
98
191
|
npx --no-install starci app stack deploy --env "$STACK_ENVIRONMENT" \{{stackImageArgs}}
|
|
99
|
-
--sha "$GITHUB_SHA" --execute
|
|
192
|
+
--sha "$GITHUB_SHA" --ci-run "$CI_RUN" "${again[@]}" --execute
|
|
100
193
|
- name: disconnect from the swarm manager
|
|
101
194
|
env:
|
|
102
195
|
XDG_DATA_HOME: ${{ runner.temp }}/starci-data
|
|
103
|
-
if: ${{ always() }}
|
|
196
|
+
if: ${{ always() && steps.pause.outputs.paused != 'true' }}
|
|
104
197
|
run: npx --no-install starci app stack disconnect --env "$STACK_ENVIRONMENT" --execute
|
|
105
198
|
|
|
106
199
|
rollback:
|
|
@@ -111,6 +204,9 @@ jobs:
|
|
|
111
204
|
permissions:
|
|
112
205
|
contents: read
|
|
113
206
|
packages: read
|
|
207
|
+
concurrency:
|
|
208
|
+
group: stack-${{ inputs.environment }}
|
|
209
|
+
cancel-in-progress: false
|
|
114
210
|
env:
|
|
115
211
|
STACK_ENVIRONMENT: ${{ inputs.environment }}
|
|
116
212
|
steps:
|
|
@@ -137,10 +233,7 @@ jobs:
|
|
|
137
233
|
- name: connect to the swarm manager
|
|
138
234
|
env:
|
|
139
235
|
XDG_DATA_HOME: ${{ runner.temp }}/starci-data
|
|
140
|
-
|
|
141
|
-
VPS_KNOWN_HOSTS: ${{ secrets.VPS_KNOWN_HOSTS }}
|
|
142
|
-
VPS_HOST: ${{ vars.VPS_HOST }}
|
|
143
|
-
VPS_USER: ${{ vars.VPS_USER }}
|
|
236
|
+
SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }}
|
|
144
237
|
run: npx --no-install starci app stack connect --env "$STACK_ENVIRONMENT" --execute
|
|
145
238
|
- name: redeploy the stored release
|
|
146
239
|
env:
|
|
@@ -1,25 +1,85 @@
|
|
|
1
1
|
{{header}}
|
|
2
2
|
name: ci
|
|
3
3
|
|
|
4
|
-
# CI judges main, release candidates and release tags.
|
|
4
|
+
# CI judges pull requests, main, release candidates and release tags, and a green release tag deploys.
|
|
5
|
+
# pull_request the checks and the image builds of a change to main, with no secret and no environment (a Dependabot or fork run has none anyway);
|
|
6
|
+
# and no run of this workflow executes a pull request's code with a token that can write
|
|
7
|
+
# main judged only when no release tag points at the pushed commit (the release push sends main and its tag together; the tag run is the release run)
|
|
8
|
+
# candidate release/candidate-<version>: the full run of a release the tag run then reuses
|
|
9
|
+
# tag v* the release run: plan, checks and images in parallel, the verdict, then the deploy of every environment with deploy.auto
|
|
5
10
|
on:
|
|
6
11
|
push:
|
|
7
12
|
branches: ["main", "release/candidate-*"]
|
|
8
13
|
tags: ["v*"]
|
|
14
|
+
pull_request:
|
|
15
|
+
branches: ["main"]
|
|
9
16
|
workflow_dispatch:
|
|
10
17
|
|
|
11
18
|
permissions:
|
|
12
19
|
contents: read
|
|
13
20
|
|
|
14
|
-
#
|
|
21
|
+
# The committed check cache (.starcistacks/cache, contexts/project/check-cache.md): a run judges only what differs from the cache at its trusted base
|
|
22
|
+
# (the first parent of a push to main). A release candidate and a release tag judge in full and never read the cache.
|
|
23
|
+
env:
|
|
24
|
+
STARCI_APP_CHECK_CACHE: ${{ (startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/heads/release/candidate-')) && 'full' || '' }}
|
|
25
|
+
|
|
26
|
+
# One run per ref. A pull request run is replaced by the next push to it; every other run, a release tag above all, is never cancelled.
|
|
15
27
|
concurrency:
|
|
16
28
|
group: ci-${{ github.ref }}
|
|
17
|
-
cancel-in-progress:
|
|
29
|
+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
18
30
|
|
|
19
|
-
# The job shape of the StarCi CI standard (knowledge/hfs/ci-standard.yaml): fast, lint per side, the unit run in shards, one coverage merge, build,
|
|
20
|
-
# and
|
|
31
|
+
# The job shape of the StarCi CI standard (knowledge/hfs/ci-standard.yaml): plan, fast, lint per side, the unit run in shards, one coverage merge, build, images,
|
|
32
|
+
# sonar, the verdict and the deploy jobs. Timeouts and the shard count come from ci.jobs of .starcistacks/application-stacks.yaml (defaults in the standard).
|
|
33
|
+
# A job the plan leaves out is never a green one: the verdict job requires every job it needs to be green or, where the plan left it out on purpose, not run.
|
|
21
34
|
jobs:
|
|
35
|
+
plan:
|
|
36
|
+
timeout-minutes: 10
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
permissions:
|
|
39
|
+
contents: read
|
|
40
|
+
actions: read
|
|
41
|
+
outputs:
|
|
42
|
+
checks: ${{ steps.plan.outputs.checks }}
|
|
43
|
+
images: ${{ steps.plan.outputs.images }}
|
|
44
|
+
push: ${{ steps.plan.outputs.push }}
|
|
45
|
+
full: ${{ steps.plan.outputs.full }}
|
|
46
|
+
release: ${{ steps.plan.outputs.release }}
|
|
47
|
+
ci_run: ${{ steps.plan.outputs.ci_run }}
|
|
48
|
+
steps:
|
|
49
|
+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
|
50
|
+
with:
|
|
51
|
+
fetch-depth: 0
|
|
52
|
+
- name: plan
|
|
53
|
+
id: plan
|
|
54
|
+
shell: bash
|
|
55
|
+
env:
|
|
56
|
+
GH_TOKEN: ${{ github.token }}
|
|
57
|
+
EVENT: ${{ github.event_name }}
|
|
58
|
+
REF_TYPE: ${{ github.ref_type }}
|
|
59
|
+
run: |
|
|
60
|
+
set -euo pipefail
|
|
61
|
+
checks=true images=true push=false full=false release=false ci_run="$GITHUB_RUN_ID" note="checks and image builds"
|
|
62
|
+
if [[ "$EVENT" == push ]]; then
|
|
63
|
+
full=true push=true
|
|
64
|
+
if [[ "$REF_TYPE" == tag ]]; then
|
|
65
|
+
release=true
|
|
66
|
+
[[ "$GITHUB_REF_NAME" == "v$(node -p "require('./package.json').version")" ]] || { echo "::error::the release tag does not match the package version"; exit 1; }
|
|
67
|
+
reused=$(gh api "repos/${GITHUB_REPOSITORY}/actions/workflows/ci.yml/runs?head_sha=${GITHUB_SHA}&event=push&status=success&per_page=100" --jq '[.workflow_runs[] | select(.head_branch | startswith("release/candidate-"))] | first | .id // empty')
|
|
68
|
+
if [[ -n "$reused" ]]; then checks=false ci_run="$reused" note="release run: the green candidate run $reused judged this commit; images are built and pushed here"; else note="release run: full checks and images"; fi
|
|
69
|
+
elif [[ "$GITHUB_REF" == refs/heads/release/candidate-* ]]; then
|
|
70
|
+
note="candidate run: full checks; images are built and pushed under sha-<sha>"
|
|
71
|
+
elif [[ -n "$(git tag --points-at "$GITHUB_SHA" --list 'v*')" ]]; then
|
|
72
|
+
checks=false images=false push=false full=false note="deferred: the release tag run of this commit judges it"
|
|
73
|
+
else
|
|
74
|
+
images=false push=false full=false note="main without a release tag: checks only"
|
|
75
|
+
fi
|
|
76
|
+
fi
|
|
77
|
+
{ echo "checks=$checks"; echo "images=$images"; echo "push=$push"; echo "full=$full"; echo "release=$release"; echo "ci_run=$ci_run"; } >> "$GITHUB_OUTPUT"
|
|
78
|
+
echo "plan: $note" >> "$GITHUB_STEP_SUMMARY"
|
|
79
|
+
|
|
22
80
|
fast:
|
|
81
|
+
needs: plan
|
|
82
|
+
if: ${{ needs.plan.outputs.checks == 'true' }}
|
|
23
83
|
timeout-minutes: {{timeoutFast}}
|
|
24
84
|
runs-on: ubuntu-latest
|
|
25
85
|
steps:
|
|
@@ -27,13 +87,15 @@ jobs:
|
|
|
27
87
|
- name: production dependency audit
|
|
28
88
|
run: {{auditCommand}}
|
|
29
89
|
- name: format
|
|
30
|
-
run:
|
|
90
|
+
run: npx --no-install starci app check --format
|
|
31
91
|
- name: typecheck
|
|
32
92
|
run: npm run typecheck
|
|
33
93
|
env:
|
|
34
94
|
NODE_OPTIONS: {{nodeOptions}}
|
|
35
95
|
|
|
36
96
|
lint:
|
|
97
|
+
needs: plan
|
|
98
|
+
if: ${{ needs.plan.outputs.checks == 'true' }}
|
|
37
99
|
timeout-minutes: {{timeoutLint}}
|
|
38
100
|
runs-on: ubuntu-latest
|
|
39
101
|
strategy:
|
|
@@ -46,6 +108,8 @@ jobs:
|
|
|
46
108
|
{{ciLintSteps}}
|
|
47
109
|
|
|
48
110
|
test:
|
|
111
|
+
needs: plan
|
|
112
|
+
if: ${{ needs.plan.outputs.checks == 'true' }}
|
|
49
113
|
timeout-minutes: {{timeoutTest}}
|
|
50
114
|
runs-on: ubuntu-latest
|
|
51
115
|
strategy:
|
|
@@ -69,7 +133,7 @@ jobs:
|
|
|
69
133
|
path: {{shardCoverageReport}}
|
|
70
134
|
|
|
71
135
|
coverage:
|
|
72
|
-
needs: test
|
|
136
|
+
needs: [plan, test]
|
|
73
137
|
timeout-minutes: {{timeoutCoverage}}
|
|
74
138
|
runs-on: ubuntu-latest
|
|
75
139
|
# Codecov OIDC authentication requires a job-scoped identity token.
|
|
@@ -96,6 +160,8 @@ jobs:
|
|
|
96
160
|
{{codecovSteps}}
|
|
97
161
|
|
|
98
162
|
build:
|
|
163
|
+
needs: plan
|
|
164
|
+
if: ${{ needs.plan.outputs.checks == 'true' }}
|
|
99
165
|
timeout-minutes: {{timeoutBuild}}
|
|
100
166
|
runs-on: ubuntu-latest
|
|
101
167
|
steps:
|
|
@@ -110,3 +176,44 @@ jobs:
|
|
|
110
176
|
NODE_OPTIONS: {{nodeOptions}}
|
|
111
177
|
|
|
112
178
|
{{sonarJob}}
|
|
179
|
+
images:
|
|
180
|
+
needs: plan
|
|
181
|
+
if: ${{ needs.plan.outputs.images == 'true' }}
|
|
182
|
+
permissions:
|
|
183
|
+
contents: read
|
|
184
|
+
packages: write
|
|
185
|
+
uses: ./.github/workflows/images.yml
|
|
186
|
+
with:
|
|
187
|
+
push: ${{ needs.plan.outputs.push == 'true' }}
|
|
188
|
+
release: ${{ needs.plan.outputs.release == 'true' }}
|
|
189
|
+
|
|
190
|
+
verdict:
|
|
191
|
+
needs: [plan, fast, lint, test, coverage, build, images{{verdictSonarNeed}}]
|
|
192
|
+
if: ${{ always() }}
|
|
193
|
+
timeout-minutes: 5
|
|
194
|
+
runs-on: ubuntu-latest
|
|
195
|
+
permissions: {}
|
|
196
|
+
steps:
|
|
197
|
+
- name: verdict
|
|
198
|
+
shell: bash
|
|
199
|
+
env:
|
|
200
|
+
NEEDS: ${{ toJSON(needs) }}
|
|
201
|
+
CHECKS: ${{ needs.plan.outputs.checks }}
|
|
202
|
+
IMAGES: ${{ needs.plan.outputs.images }}
|
|
203
|
+
FULL: ${{ needs.plan.outputs.full }}
|
|
204
|
+
run: |
|
|
205
|
+
set -euo pipefail
|
|
206
|
+
fail=0
|
|
207
|
+
expect() {
|
|
208
|
+
got=$(jq -r --arg job "$1" '.[$job].result' <<<"$NEEDS")
|
|
209
|
+
case "$2" in
|
|
210
|
+
success) [[ "$got" == success ]] || { echo "::error::job $1 is $got, expected success"; fail=1; } ;;
|
|
211
|
+
idle) [[ "$got" != success && "$got" != failure && "$got" != cancelled ]] || { echo "::error::job $1 ran ($got) where the plan left it out"; fail=1; } ;;
|
|
212
|
+
esac
|
|
213
|
+
}
|
|
214
|
+
expect plan success
|
|
215
|
+
if [[ "$CHECKS" == true ]]; then want=success; else want=idle; fi
|
|
216
|
+
for job in fast lint test coverage build; do expect "$job" "$want"; done
|
|
217
|
+
if [[ "$IMAGES" == true ]]; then expect images success; else expect images idle; fi{{verdictSonarCheck}}
|
|
218
|
+
exit "$fail"
|
|
219
|
+
{{ciDeployJobs}}
|
|
@@ -1,74 +1,30 @@
|
|
|
1
1
|
{{header}}
|
|
2
|
-
# Builds
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
2
|
+
# Builds every image the app declares, once per commit, and pushes the deployed ones to GHCR. ci.yml calls it next to the checks, so the images
|
|
3
|
+
# are built in parallel with them and a Dockerfile that no longer builds fails the same run. It has no trigger of its own.
|
|
4
|
+
# tags sha-<sha> on every push; the release tag v<version> is added on a release run by retagging that digest (no rebuild), so the
|
|
5
|
+
# image the checks ran beside is the image that ships. No mutable environment tag is ever written or deployed.
|
|
6
|
+
# evidence an SBOM and provenance (mode=max) are attached to the pushed digest; a build that is not pushed only proves the Dockerfile builds.
|
|
7
|
+
# values build arguments are public values (the browser receives them); a secret never enters an image build.
|
|
8
|
+
# sealed an image whose public build values live in a sealed member is built by the job bound to the release environment, on a release
|
|
9
|
+
# tag run only (that environment is restricted to v* tags); every other image builds without an environment or a secret.
|
|
10
|
+
# The workflow is read by anyone when the repository is public: it runs on GitHub-hosted runners only, and nothing it prints names a host.
|
|
6
11
|
name: images
|
|
7
12
|
|
|
8
13
|
on:
|
|
9
|
-
|
|
10
|
-
|
|
14
|
+
workflow_call:
|
|
15
|
+
inputs:
|
|
16
|
+
push:
|
|
17
|
+
description: Push the deployed images (a push run); a pull request or a manual run only builds them
|
|
18
|
+
required: true
|
|
19
|
+
type: boolean
|
|
20
|
+
release:
|
|
21
|
+
description: A release tag run - also tag the pushed images v<version> and build the images that open sealed build values
|
|
22
|
+
required: true
|
|
23
|
+
type: boolean
|
|
24
|
+
{{imageWorkflowOutputs}}
|
|
11
25
|
|
|
12
26
|
permissions:
|
|
13
27
|
contents: read
|
|
14
28
|
|
|
15
|
-
# One run per ref.
|
|
16
|
-
concurrency:
|
|
17
|
-
group: images-${{ github.ref }}
|
|
18
|
-
cancel-in-progress: false
|
|
19
|
-
|
|
20
29
|
jobs:
|
|
21
|
-
|
|
22
|
-
timeout-minutes: 30
|
|
23
|
-
runs-on: ubuntu-latest{{imageJobEnvironment}}
|
|
24
|
-
env:
|
|
25
|
-
STACK_ENVIRONMENT: {{imageEnvironment}}
|
|
26
|
-
strategy:
|
|
27
|
-
fail-fast: false
|
|
28
|
-
matrix:
|
|
29
|
-
include:
|
|
30
|
-
{{imageMatrix}}
|
|
31
|
-
steps:{{imageEnvironmentGuard}}
|
|
32
|
-
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
|
33
|
-
- name: release tag matches package version
|
|
34
|
-
run: node -e "const version=require('./package.json').version;if(process.env.GITHUB_REF_NAME!=='v'+version)process.exit(1)"
|
|
35
|
-
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
36
|
-
if: {{imageBuildArgsIf}}
|
|
37
|
-
with:
|
|
38
|
-
node-version: {{nodeMajor}}
|
|
39
|
-
cache: npm
|
|
40
|
-
- run: npm ci --ignore-scripts
|
|
41
|
-
if: {{imageBuildArgsIf}}
|
|
42
|
-
- name: install SOPS
|
|
43
|
-
if: {{imageBuildArgsIf}}
|
|
44
|
-
uses: mdgreenwald/mozilla-sops-action@bb2d96a13ef438ad122e5827a57ebd75d6ff118d # v2.1.0
|
|
45
|
-
with:
|
|
46
|
-
version: v3.13.2
|
|
47
|
-
- name: public build values
|
|
48
|
-
id: args
|
|
49
|
-
if: {{imageBuildArgsIf}}
|
|
50
|
-
env:
|
|
51
|
-
SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }}
|
|
52
|
-
IMAGE_APP: ${{ matrix.name }}
|
|
53
|
-
run: npx --no-install starci app stack build-args --env "$STACK_ENVIRONMENT" --app "$IMAGE_APP"
|
|
54
|
-
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
|
55
|
-
- uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
|
|
56
|
-
with:
|
|
57
|
-
context: .
|
|
58
|
-
file: ${{ matrix.file }}
|
|
59
|
-
build-args: ${{ steps.args.outputs.args }}
|
|
60
|
-
push: false
|
|
61
|
-
tags: {{imageProject}}/${{ matrix.name }}:${{ github.ref_name }}
|
|
62
|
-
# OCI keeps the SBOM and provenance attestations without publishing to a registry.
|
|
63
|
-
outputs: type=oci,dest=${{ runner.temp }}/${{ matrix.name }}.oci.tar
|
|
64
|
-
sbom: true
|
|
65
|
-
provenance: mode=max
|
|
66
|
-
cache-from: type=gha,scope=${{ matrix.name }}
|
|
67
|
-
cache-to: type=gha,scope=${{ matrix.name }},mode=max
|
|
68
|
-
- name: retain image with SBOM and provenance
|
|
69
|
-
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
|
70
|
-
with:
|
|
71
|
-
name: image-${{ matrix.name }}-${{ github.sha }}-${{ github.run_attempt }}
|
|
72
|
-
path: ${{ runner.temp }}/${{ matrix.name }}.oci.tar
|
|
73
|
-
if-no-files-found: error
|
|
74
|
-
compression-level: 0
|
|
30
|
+
{{imageJobs}}
|