forked from DSpace/dspace-angular
-
Notifications
You must be signed in to change notification settings - Fork 2
371 lines (333 loc) · 16.6 KB
/
Copy pathdeploy.yml
File metadata and controls
371 lines (333 loc) · 16.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
# DSpace Docker deploy on dataquest servers
name: Deploy DSpace
on:
workflow_call:
inputs:
DEV_MACHINE:
required: false
type: string
default: 'dev-6'
INSTANCE:
required: false
type: string
default: '8603'
IMPORT:
required: false
default: false
type: boolean
ERASE_DB:
required: false
default: false
type: boolean
DUMP_PATH:
required: false
type: string
default: '/opt/dspace-envs/8603/dump/dspace_dev5_dump_26.07.16.sql'
ASSETSTORE_PATH:
required: false
type: string
default: '/opt/dspace-envs/8603/assetstore'
workflow_dispatch:
inputs:
INSTANCE:
required: true
default: '8603'
type: choice
options:
- '8603'
IMPORT:
required: true
default: true
type: boolean
ERASE_DB:
required: false
default: false
type: boolean
DEV_MACHINE:
required: false
type: string
default: 'dev-6'
DUMP_PATH:
description: 'Absolute path of the SQL dump ON THE DEV MACHINE (used only when IMPORT is checked)'
required: false
type: string
default: '/opt/dspace-envs/8603/dump/dspace_dev5_dump_26.07.16.sql'
ASSETSTORE_PATH:
description: 'Absolute path of the assetstore directory ON THE DEV MACHINE'
required: false
type: string
default: '/opt/dspace-envs/8603/assetstore'
PROBE_ONLY:
description: 'Only report the state of the target machine; deploy and import nothing'
required: false
default: false
type: boolean
jobs:
# Read-only report of the machine we are about to touch. Runs first so that a
# misconfigured target is visible in the log even when the deploy then fails.
probe:
runs-on: dspace-${{inputs.DEV_MACHINE}}-dep-1
timeout-minutes: 10
env:
INSTANCE: ${{inputs.INSTANCE}}
CONFIG_PATH: /opt/dspace-envs/${{inputs.INSTANCE}}
ENVFILE: /opt/dspace-envs/${{inputs.INSTANCE}}/.env
steps:
- uses: actions/checkout@v6
- name: config dir + dump
run: |
echo "===== $CONFIG_PATH"
ls -la "$CONFIG_PATH/" || echo "MISSING: $CONFIG_PATH"
echo "===== $CONFIG_PATH/dump"
ls -la "$CONFIG_PATH/dump/" || echo "MISSING: $CONFIG_PATH/dump"
echo "===== dump referenced by DUMP_PATH input"
ls -la "${{ inputs.DUMP_PATH }}" || echo "MISSING dump: ${{ inputs.DUMP_PATH }}"
echo "===== port-ish keys from .env (values shown; these are ports, not secrets)"
grep -E '^(PG_PORT|SOLR_PORT|DSPACE_PORT|DSPACE_FE_PORT|DSPACE_REST_PORT|DSPACE_DOCKER_REST_PORT|DSPACE_VER|DSPACE_HOST)=' "$ENVFILE" || echo "no port keys found"
- name: who owns the host ports
run: |
echo "===== all dspace-ish containers and their published ports"
docker ps -a --format '{{.Names}}\t{{.Status}}\t{{.Ports}}' | grep -Ei 'dspace|postgres|solr' || echo "none"
echo "===== anything publishing host port 5432"
docker ps --format '{{.Names}}\t{{.Ports}}' | grep -E '(^|[^0-9])5432->' || echo "nothing publishes 5432"
echo "===== host listeners on 5432"
(ss -lntp 2>/dev/null || netstat -lntp 2>/dev/null) | grep -E ':5432' || echo "no listener on 5432 (or no permission to see it)"
- name: resolved compose ports for this instance
run: |
echo "===== resolved ports for this instance (this is what deploy uses)"
docker compose --env-file "$ENVFILE" -p dspace-$INSTANCE \
-f docker/docker-compose.yml -f docker/docker-compose-rest.yml \
-f "$CONFIG_PATH/docker-compose-rest.yml" -f "$CONFIG_PATH/docker-compose.yml" \
config 2>&1 | grep -nE '^ [a-z-]+:|container_name:|published:|^\s+ports:' | head -40 \
|| echo "compose config failed"
# The overlay must REPLACE the fixed ports the vanilla files publish, not add to
# them: compose merges `ports` lists across -f files. If a fixed port survives the
# merge above, the overlay is missing `ports: !override` and this instance will
# fight whichever one already owns it on the host.
echo "===== vanilla files alone, for comparison (the fixed ports the overlay must replace)"
docker compose --env-file "$ENVFILE" -p dspace-$INSTANCE \
-f docker/docker-compose.yml -f docker/docker-compose-rest.yml \
config 2>&1 | grep -nE '^ [a-z-]+:|container_name:|published:|^\s+ports:' | head -30 \
|| echo "compose config failed"
echo "===== verdict"
if docker compose --env-file "$ENVFILE" -p dspace-$INSTANCE \
-f docker/docker-compose.yml -f docker/docker-compose-rest.yml \
-f "$CONFIG_PATH/docker-compose-rest.yml" -f "$CONFIG_PATH/docker-compose.yml" \
config 2>/dev/null | grep -qE 'published: "(5432|8080|8983|4000|9876)"'; then
echo " WARNING: overlay still inherits vanilla fixed ports."
echo " Add 'ports: !override' to $CONFIG_PATH/docker-compose.yml and docker-compose-rest.yml,"
echo " otherwise the deploy will fail with 'port is already allocated'."
else
echo " OK: only instance-scoped ports are published"
fi
- name: postgres inside the instance db container
run: |
docker exec dspacedb$INSTANCE sh -c 'echo "PGPORT=$PGPORT"; pg_isready -U dspace; psql -U dspace -d postgres -tAc "SHOW port;" 2>/dev/null' || echo "could not probe dspacedb$INSTANCE"
# Does the instance actually hold the imported data and serve it? A green import
# job only proves the commands exited 0.
- name: data + service health
run: |
# DSpace keeps Flyway's history in "schema_version", not Flyway's default
# "flyway_schema_history" -- see DatabaseUtils.FLYWAY_TABLE.
echo "===== last applied migrations (schema_version)"
docker exec dspacedb$INSTANCE psql -U dspace -d dspace -tAc \
"select version, script, success from schema_version order by installed_rank desc limit 5;" \
2>&1 || echo "could not read schema_version"
# What `database repair` did to the imported CLARIN history. repair only rewrites
# this bookkeeping table -- it never touches user tables or their rows.
echo "===== CLARIN 7.6 entries / anything repair marked deleted"
docker exec dspacedb$INSTANCE psql -U dspace -d dspace -tAc \
"select installed_rank||' | '||version||' | type='||type||' | success='||success||' | '||script from schema_version where version like '7.6%' or type='DELETE' order by installed_rank desc limit 12;" \
2>&1 || echo "could not read schema_version"
echo "===== CLARIN-specific tables still present after repair"
docker exec dspacedb$INSTANCE psql -U dspace -d dspace -tAc \
"select 'public tables total='||count(*) from information_schema.tables where table_schema='public';" 2>&1 || true
docker exec dspacedb$INSTANCE psql -U dspace -d dspace -tAc \
"select string_agg(table_name, ', ') from information_schema.tables where table_schema='public' and (table_name like '%clarin%' or table_name like '%license%');" 2>&1 || true
echo "===== row counts"
docker exec dspacedb$INSTANCE psql -U dspace -d dspace -tAc \
"select 'item='||(select count(*) from item) || ' bitstream='||(select count(*) from bitstream) || ' collection='||(select count(*) from collection) || ' community='||(select count(*) from community) || ' eperson='||(select count(*) from eperson);" \
2>&1 || echo "could not count rows"
echo "===== dspace version"
docker exec dspace$INSTANCE /bin/bash -c "cd /dspace/bin && ./dspace version" 2>&1 | head -5 || echo "dspace version failed"
echo "===== REST API on the instance"
curl -s -o /dev/null -w " repository/server/api -> HTTP %{http_code}\n" "http://localhost:${DSPACE_DOCKER_REST_PORT:-6603}/repository/server/api" || true
curl -s "http://localhost:${DSPACE_DOCKER_REST_PORT:-6603}/repository/server/api/core/items?size=1" 2>/dev/null | head -c 300 || true
echo ""
deploy-8603:
if: (inputs.INSTANCE == '*' || inputs.INSTANCE == '8603') && !inputs.PROBE_ONLY
needs: probe
runs-on: dspace-${{inputs.DEV_MACHINE}}-dep-1
timeout-minutes: 60
env:
INSTANCE: '8603'
CONFIG_PATH: /opt/dspace-envs/${{inputs.INSTANCE}}
ENVFILE: /opt/dspace-envs/${{inputs.INSTANCE}}/.env
steps:
- uses: actions/checkout@v6
- uses: ./.github/actions/erase-db
if: inputs.ERASE_DB
with:
INSTANCE: ${{ env.INSTANCE }}
NAME: dspace-${{ env.INSTANCE }}
- name: Set start.sh as executable
run: chmod +x build-scripts/run/start.sh
- name: deploy to ${{inputs.DEV_MACHINE}}
working-directory: build-scripts/run/
env:
ADMIN_PASSWORD: ${{ secrets.DSPACE_ADMIN_PASSWORD }}
USER_PASSWORD: ${{ secrets.DSPACE_USER_PASSWORD }}
INSTANCE_OVERLAY: ${{ env.CONFIG_PATH }}
run: |
./start.sh dspace-$INSTANCE
cd ../..
docker compose --env-file $ENVFILE -p dspace-$INSTANCE -f docker/docker-compose.yml -f docker/docker-compose-rest.yml -f $CONFIG_PATH/docker-compose-rest.yml -f $CONFIG_PATH/docker-compose.yml up -d --no-build --remove-orphans
import-8603:
runs-on: dspace-${{inputs.DEV_MACHINE}}-dep-1
if: inputs.IMPORT
timeout-minutes: 900
needs: deploy-8603
env:
INSTANCE: ${{inputs.INSTANCE}}
CONFIG_PATH: /opt/dspace-envs/${{inputs.INSTANCE}}
DUMP_PATH: ${{inputs.DUMP_PATH}}
ASSETSTORE_PATH: ${{inputs.ASSETSTORE_PATH}}
ADMIN_PASSWORD: ${{ secrets.DSPACE_ADMIN_PASSWORD }}
steps:
- uses: actions/checkout@v6
- name: vanilla import
run: |
export DNAME=dspace${INSTANCE}
export DDBNAME=dspacedb${INSTANCE}
if [ ! -f "$DUMP_PATH" ]; then
echo "::error::Dump not found on ${{inputs.DEV_MACHINE}}: $DUMP_PATH"
exit 1
fi
echo "Importing dump: $DUMP_PATH ($(du -h "$DUMP_PATH" | cut -f1))"
# Stop the app so it cannot hold connections to the database we are about to drop.
docker stop $DNAME || true
echo "====="
docker cp "$DUMP_PATH" $DDBNAME:/tmp/dump.sql
# Recreate the database from scratch and load the vanilla dump.
docker exec $DDBNAME /bin/bash -c "
dropdb -U dspace dspace || true &&
createdb -U dspace --owner=dspace --encoding=UNICODE dspace &&
psql -U dspace -d dspace -f /tmp/dump.sql
"
echo "====="
docker exec $DDBNAME /bin/bash -c "rm -f /tmp/dump.sql" || true
- name: run database migration
run: |
export DNAME=dspace${INSTANCE}
export ENVFILE=$CONFIG_PATH/.env
export OVERLAY=$CONFIG_PATH
# ---------------------------------------------------------------
# Why a one-off `docker compose run` instead of `docker exec $DNAME migrate`:
# The dspace entrypoint auto-runs `dspace database migrate` (WITHOUT `ignored`)
# and then starts the REST webapp. On a freshly imported dump the
# "Ignored" XMLWorkflow migrations are NOT applied, so the webapp boots against
# an un-migrated DB, crashes, and (with the restart policy) the container
# restarts. That restart SIGKILLs any `docker exec` running inside it -> the
# migration dies with exit 137. So we stop the app and run the migration as a
# one-off compose container (overridden entrypoint = migrate only, no webapp,
# no restart policy), using the same compose files/env as the deploy step.
# ---------------------------------------------------------------
echo "Stopping app container so its restart loop can't kill the migration..."
docker stop $DNAME || true
# `database repair` before `migrate`: the dump comes from a CLARIN 7.6 instance,
# so its flyway_schema_history lists CLARIN-specific migrations that do not exist
# in this vanilla v9 tree. Flyway validation then refuses to migrate at all:
# "Detected applied migration not resolved locally: 7.6.2026.06.01."
# repair marks those unresolvable entries as deleted, which is exactly what we want
# when migrating an old CLARIN dump onto vanilla v9 -- their DDL is already in the
# dump, we just do not carry the migration scripts any more.
echo "Running one-off DB repair + migration via docker compose..."
docker compose --env-file $ENVFILE -p dspace-${INSTANCE} \
-f docker/docker-compose.yml -f docker/docker-compose-rest.yml \
\
-f $OVERLAY/docker-compose-rest.yml -f $OVERLAY/docker-compose.yml \
run --rm --no-deps --entrypoint /bin/bash dspace \
-c "while (!</dev/tcp/dspacedb/5432) >/dev/null 2>&1; do sleep 1; done; cd /dspace/bin && ./dspace database repair && ./dspace database migrate ignored"
echo "Migration done. Starting app container (entrypoint migrate is now a no-op)..."
docker start $DNAME
echo "Waiting for DSpace to be ready..."
sleep 10
- name: restart dspace container
run: |
export DNAME=dspace${INSTANCE}
echo "Restarting DSpace container..."
docker restart $DNAME
sleep 60
- name: copy assetstore
run: |
export DNAME=dspace${INSTANCE}
# No `chown -R dspace:dspace` (unlike v7): the v9 runtime image has no `dspace`
# user, the webapp runs as root, and that chown fails with "invalid user".
echo "Preparing assetstore"
if [ -z "$ASSETSTORE_PATH" ]; then
echo "Assetstore path is empty. Not copying assetstore."
else
docker cp "$ASSETSTORE_PATH" $DNAME:/dspace/
fi
- name: create administrator
run: |
export DNAME=dspace${INSTANCE}
echo "Creating DSpace administrator..."
docker exec $DNAME /bin/bash -c "cd /dspace/bin && ./dspace create-administrator -e dspace.admin.dev@dataquest.sk -f admin -l user -p \"${ADMIN_PASSWORD}\" -c en"
- name: rebuild discovery index and run oai import
run: |
export DNAME=dspace${INSTANCE}
echo "Rebuilding discovery index..."
docker exec $DNAME /bin/bash -c "/dspace/bin/dspace index-discovery -b"
echo "Running OAI import..."
docker exec $DNAME /bin/bash -c "/dspace/bin/dspace oai import -c"
playwright-after-deploy8603:
needs: deploy-8603
if: '!inputs.IMPORT'
uses: ./.github/workflows/playwright-tests.yml
secrets: inherit
rest-tests-after-deploy8603:
runs-on: ubuntu-latest
needs: playwright-after-deploy8603
if: '!inputs.IMPORT'
timeout-minutes: 120
env:
GH_TOKEN: ${{ secrets.DEPLOY_DEV5_GH_ACTION_DISPATCH }}
REPO: dataquest-dev/dspace-rest-test
WORKFLOW: run_unittests.yml
steps:
- name: run rest-tests
run: |
OUTPUT=$(gh workflow run "$WORKFLOW" \
--repo "$REPO" \
--ref master \
-f CUSTOMER="${{ github.ref_name }}" \
-f URL="http://dev-6.pc:8603/repository/server/api" 2>&1)
RUN_ID=$(echo "$OUTPUT" | grep -oE '/runs/[0-9]+' | grep -oE '[0-9]+')
echo "Watching run: https://github.com/$REPO/actions/runs/$RUN_ID"
gh run watch "$RUN_ID" --repo "$REPO" --interval 30 --exit-status
playwright-after-import8603:
needs: import-8603
if: inputs.IMPORT
uses: ./.github/workflows/playwright-tests.yml
secrets: inherit
rest-tests-after-import8603:
runs-on: ubuntu-latest
needs: playwright-after-import8603
if: inputs.IMPORT
timeout-minutes: 120
env:
GH_TOKEN: ${{ secrets.DEPLOY_DEV5_GH_ACTION_DISPATCH }}
REPO: dataquest-dev/dspace-rest-test
WORKFLOW: run_unittests.yml
steps:
- name: run rest-tests
run: |
OUTPUT=$(gh workflow run "$WORKFLOW" \
--repo "$REPO" \
--ref master \
-f CUSTOMER="${{ github.ref_name }}" \
-f URL="http://dev-6.pc:8603/repository/server/api" 2>&1)
RUN_ID=$(echo "$OUTPUT" | grep -oE '/runs/[0-9]+' | grep -oE '[0-9]+')
echo "Watching run: https://github.com/$REPO/actions/runs/$RUN_ID"
gh run watch "$RUN_ID" --repo "$REPO" --interval 30 --exit-status