splent-feature-autodeploy 0.2.0__tar.gz → 0.3.0__tar.gz
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.
- {splent_feature_autodeploy-0.2.0/src/splent_feature_autodeploy.egg-info → splent_feature_autodeploy-0.3.0}/PKG-INFO +107 -43
- {splent_feature_autodeploy-0.2.0 → splent_feature_autodeploy-0.3.0}/README.md +106 -42
- {splent_feature_autodeploy-0.2.0 → splent_feature_autodeploy-0.3.0}/pyproject.toml +27 -5
- {splent_feature_autodeploy-0.2.0 → splent_feature_autodeploy-0.3.0/src/splent_feature_autodeploy.egg-info}/PKG-INFO +107 -43
- {splent_feature_autodeploy-0.2.0 → splent_feature_autodeploy-0.3.0}/src/splent_io/splent_feature_autodeploy/commands.py +7 -0
- {splent_feature_autodeploy-0.2.0 → splent_feature_autodeploy-0.3.0}/COPYING +0 -0
- {splent_feature_autodeploy-0.2.0 → splent_feature_autodeploy-0.3.0}/COPYING.LESSER +0 -0
- {splent_feature_autodeploy-0.2.0 → splent_feature_autodeploy-0.3.0}/MANIFEST.in +0 -0
- {splent_feature_autodeploy-0.2.0 → splent_feature_autodeploy-0.3.0}/setup.cfg +0 -0
- {splent_feature_autodeploy-0.2.0 → splent_feature_autodeploy-0.3.0}/src/splent_feature_autodeploy.egg-info/SOURCES.txt +0 -0
- {splent_feature_autodeploy-0.2.0 → splent_feature_autodeploy-0.3.0}/src/splent_feature_autodeploy.egg-info/dependency_links.txt +0 -0
- {splent_feature_autodeploy-0.2.0 → splent_feature_autodeploy-0.3.0}/src/splent_feature_autodeploy.egg-info/requires.txt +0 -0
- {splent_feature_autodeploy-0.2.0 → splent_feature_autodeploy-0.3.0}/src/splent_feature_autodeploy.egg-info/top_level.txt +0 -0
- {splent_feature_autodeploy-0.2.0 → splent_feature_autodeploy-0.3.0}/src/splent_io/splent_feature_autodeploy/__init__.py +0 -0
- {splent_feature_autodeploy-0.2.0 → splent_feature_autodeploy-0.3.0}/src/splent_io/splent_feature_autodeploy/config.py +0 -0
- {splent_feature_autodeploy-0.2.0 → splent_feature_autodeploy-0.3.0}/src/splent_io/splent_feature_autodeploy/forms.py +0 -0
- {splent_feature_autodeploy-0.2.0 → splent_feature_autodeploy-0.3.0}/src/splent_io/splent_feature_autodeploy/hooks.py +0 -0
- {splent_feature_autodeploy-0.2.0 → splent_feature_autodeploy-0.3.0}/src/splent_io/splent_feature_autodeploy/presenters.py +0 -0
- {splent_feature_autodeploy-0.2.0 → splent_feature_autodeploy-0.3.0}/src/splent_io/splent_feature_autodeploy/routes.py +0 -0
- {splent_feature_autodeploy-0.2.0 → splent_feature_autodeploy-0.3.0}/src/splent_io/splent_feature_autodeploy/services.py +0 -0
- {splent_feature_autodeploy-0.2.0 → splent_feature_autodeploy-0.3.0}/src/splent_io/splent_feature_autodeploy/templates/autodeploy/index.html +0 -0
- {splent_feature_autodeploy-0.2.0 → splent_feature_autodeploy-0.3.0}/src/splent_io/splent_feature_autodeploy/templates/hooks/sidebar_deployments.html +0 -0
- {splent_feature_autodeploy-0.2.0 → splent_feature_autodeploy-0.3.0}/src/splent_io/splent_feature_autodeploy/translations/es/LC_MESSAGES/messages.mo +0 -0
- {splent_feature_autodeploy-0.2.0 → splent_feature_autodeploy-0.3.0}/src/splent_io/splent_feature_autodeploy/translations/es/LC_MESSAGES/messages.po +0 -0
- {splent_feature_autodeploy-0.2.0 → splent_feature_autodeploy-0.3.0}/src/splent_io/splent_feature_autodeploy/translations/messages.pot +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: splent_feature_autodeploy
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
License-Expression: LGPL-3.0-or-later
|
|
5
5
|
Requires-Python: >=3.13
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -12,12 +12,14 @@ Dynamic: license-file
|
|
|
12
12
|
# splent_feature_autodeploy
|
|
13
13
|
|
|
14
14
|
Release-gated continuous delivery for a SPLENT product. A product that
|
|
15
|
-
selects this feature runs a small agent
|
|
16
|
-
production. The agent follows a registry image
|
|
17
|
-
`git.splent.io/splent-io/<product>:stable`, and when
|
|
18
|
-
new image it redeploys the product
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
selects this feature runs a small agent in a container of its own next to
|
|
16
|
+
its web container in production. The agent follows a registry image
|
|
17
|
+
channel, for example `git.splent.io/splent-io/<product>:stable`, and when
|
|
18
|
+
the channel points to a new image it redeploys the product on that image's
|
|
19
|
+
digest. It backs up first (off the host too, when the product has
|
|
20
|
+
`splent_feature_backup`), migrates once, waits for the new version's
|
|
21
|
+
`/readyz`, and rolls the container back, and the database when the deploy
|
|
22
|
+
changed it, when the new version is not ready.
|
|
21
23
|
|
|
22
24
|
The channel only moves when someone runs `splent product:release`, so nothing
|
|
23
25
|
reaches production that nobody released. The agent never builds anything and
|
|
@@ -29,41 +31,92 @@ Every `AUTODEPLOY_INTERVAL` seconds, and whenever an administrator asks,
|
|
|
29
31
|
the agent goes through these steps.
|
|
30
32
|
|
|
31
33
|
1. Pull the channel image. If its id is the one the web container runs,
|
|
32
|
-
there is nothing to do.
|
|
34
|
+
there is nothing to do. Otherwise the image is named from now on by its
|
|
35
|
+
digest (`<registry>/<org>/<product>@sha256:...`), which no push can
|
|
36
|
+
move: that is what the new container runs and what the history records.
|
|
37
|
+
An image the registry knows no digest of runs by its id, just as fixed.
|
|
33
38
|
2. Skip an image that failed before, until the channel points elsewhere.
|
|
34
39
|
Honour the pause flag and the update window (`AUTODEPLOY_WINDOW`). A
|
|
35
40
|
check somebody asks for (Check now) deploys outside the window too.
|
|
36
|
-
3. Back up
|
|
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
|
-
|
|
41
|
+
3. Back up.
|
|
42
|
+
- When the product has `splent_feature_backup`, run its off-host backup
|
|
43
|
+
in the running web container, as the app's user:
|
|
44
|
+
`splent feature:backup run --tag pre-deploy-<short id of the image
|
|
45
|
+
being replaced>` (`AUTODEPLOY_BACKUP_COMMAND`). It is encrypted to keys
|
|
46
|
+
the server does not hold, and it is the copy that survives the host.
|
|
47
|
+
- Then the agent's own dump, the one a rollback restores:
|
|
48
|
+
`mariadb-dump --single-transaction --routines --triggers` runs as root
|
|
49
|
+
inside the database container, with the credentials that container
|
|
50
|
+
already holds, and the gzipped dump is kept only if `mariadb-dump`
|
|
51
|
+
exited cleanly and the dump ends with its `Dump completed` trailer.
|
|
52
|
+
|
|
53
|
+
No complete backup, no deploy.
|
|
54
|
+
4. Record what the web container runs (`splent version --json`), and a
|
|
55
|
+
fingerprint of the schema: every table's columns and every migration
|
|
56
|
+
revision (`alembic_*`, `splent_migrations`).
|
|
57
|
+
5. Create the new web container on the image's digest with everything the
|
|
58
|
+
old one was configured with, the way Watchtower does it. That is its
|
|
59
|
+
name, its environment (the container's own values on top of the new
|
|
60
|
+
image's), its labels, mounts and binds, port bindings, restart policy,
|
|
61
|
+
healthcheck, and its networks with their aliases. Cmd, Entrypoint and
|
|
62
|
+
WorkingDir follow the new image when the old container only inherited
|
|
63
|
+
them, and are kept when it chose them (the compose file's `command:`).
|
|
64
|
+
The old container is renamed `<name>-previous` and stopped, not removed.
|
|
65
|
+
6. Migrate, once: a one-off container of the new image, `<name>-migrate`,
|
|
66
|
+
with the web container's environment, mounts and networks, runs
|
|
67
|
+
`AUTODEPLOY_MIGRATE_COMMAND` (`splent db:upgrade`) as the app's user
|
|
68
|
+
while neither version serves. It answers to no alias, so nginx never
|
|
69
|
+
finds it, publishes no port and is removed once it exits. The new
|
|
70
|
+
container's entrypoint upgrades again as it starts and finds nothing
|
|
71
|
+
left to do.
|
|
72
|
+
7. Start the new container and wait, up to `AUTODEPLOY_HEALTH_TIMEOUT`
|
|
73
|
+
seconds, for `/readyz` to answer 200 (`AUTODEPLOY_READY_PATH`, asked by
|
|
74
|
+
the container's name on `AUTODEPLOY_WEB_PORT`), while Docker's
|
|
75
|
+
healthcheck does not say unhealthy. `/readyz` is the framework's
|
|
76
|
+
(1.22.0 and later): 200 when the database and the services the product
|
|
77
|
+
declares answer. An image without it answers 404, and the healthcheck
|
|
78
|
+
decides for it.
|
|
79
|
+
8. Once it is ready, remove the previous container and write the history
|
|
80
|
+
entry (from and to image, the digest, product version, the features
|
|
81
|
+
whose version changed, the off-host backup, duration).
|
|
82
|
+
9. If the migration fails, or the new container turns unhealthy, exits,
|
|
83
|
+
restarts or is not ready in time, keep its last 80 log lines (the
|
|
84
|
+
migration's, when that is what failed), remove it, put the database
|
|
85
|
+
back when the rule below says so, put the previous container back under
|
|
86
|
+
its name, wait for it to be healthy, and refuse that image from then on.
|
|
87
|
+
The history entry says `rolled_back`, carries the log lines and says
|
|
88
|
+
whether the database went back and why.
|
|
89
|
+
|
|
90
|
+
### When the database goes back
|
|
91
|
+
|
|
92
|
+
A rollback restores the agent's pre-deploy dump (dropping the database
|
|
93
|
+
first, so tables the new migrations created do not linger) when:
|
|
94
|
+
|
|
95
|
+
- the migration failed, or the agent died during it. MariaDB commits DDL as
|
|
96
|
+
it goes, so a failed migration can leave half of its changes behind
|
|
97
|
+
without recording them;
|
|
98
|
+
- the migration finished, and the schema fingerprint differs from the one
|
|
99
|
+
taken before the deploy. The previous version cannot run on the new
|
|
100
|
+
schema, and a down-migration is not something to trust with production
|
|
101
|
+
data;
|
|
102
|
+
- the fingerprint cannot be read, which counts as changed;
|
|
103
|
+
- `AUTODEPLOY_MIGRATE_COMMAND=off`: the new container migrated as it
|
|
104
|
+
started, and the agent cannot tell a failed migration from anything
|
|
105
|
+
else.
|
|
106
|
+
|
|
107
|
+
It does not when the deploy stopped before anything ran against the
|
|
108
|
+
database (a failed backup, a container that could not be created), and when
|
|
109
|
+
the migration finished without changing the schema: the previous version
|
|
110
|
+
comes back on the same database, and nothing written while the new one
|
|
111
|
+
tried to start is lost. The dump is taken right before the old version
|
|
112
|
+
stops, so a restore loses only what the new version wrote during its
|
|
113
|
+
attempt.
|
|
62
114
|
|
|
63
115
|
Every step is written to `/data/state.json` before it is taken. An agent
|
|
64
116
|
restarted in the middle of a deploy (a reboot, a redeploy of the agent
|
|
65
|
-
itself) finishes it if the new container turns
|
|
66
|
-
otherwise
|
|
117
|
+
itself) finishes it if the new container turns ready, and rolls it back
|
|
118
|
+
otherwise; one restarted during the migration rolls back with the
|
|
119
|
+
database.
|
|
67
120
|
|
|
68
121
|
| State | Meaning |
|
|
69
122
|
|----------------------|--------------------------------------------------------------|
|
|
@@ -137,11 +190,16 @@ with the product's names filled in.
|
|
|
137
190
|
|
|
138
191
|
| Variable | Default | Meaning |
|
|
139
192
|
|------------------------------|-------------------------------------|---------|
|
|
140
|
-
| `AUTODEPLOY_IMAGE` | empty | Image to follow. Empty means `SPLENT_IMAGE` |
|
|
193
|
+
| `AUTODEPLOY_IMAGE` | empty | Image to follow. Empty means `SPLENT_IMAGE`. A channel (`:stable`) or a version tag; `:latest`, or no tag at all, is refused |
|
|
141
194
|
| `AUTODEPLOY_INTERVAL` | `300` | Seconds between checks (at least 30) |
|
|
142
195
|
| `AUTODEPLOY_WINDOW` | empty | UTC span for deploys, like `02:00-05:00` or `22:00-04:00`. Empty means any time |
|
|
143
|
-
| `AUTODEPLOY_HEALTH_TIMEOUT` | `600` | Seconds the new container
|
|
144
|
-
| `
|
|
196
|
+
| `AUTODEPLOY_HEALTH_TIMEOUT` | `600` | Seconds the migrations get, and then the new container to turn ready |
|
|
197
|
+
| `AUTODEPLOY_MIGRATE_COMMAND` | `splent db:upgrade` | What the one-off migration container runs. `off` leaves the migrations to the new container's start |
|
|
198
|
+
| `AUTODEPLOY_READY_PATH` | `/readyz` | The readiness probe. `off` waits for Docker's healthcheck alone |
|
|
199
|
+
| `AUTODEPLOY_WEB_PORT` | `5000` | The port the web container answers on, inside the product network |
|
|
200
|
+
| `AUTODEPLOY_BACKUP_COMMAND` | `auto` | The off-host backup before a deploy. `auto` is `splent_feature_backup`'s when the product has it, `off` none, anything else the command to run in the web container |
|
|
201
|
+
| `AUTODEPLOY_APP_USER` | `splent` | The user the migration and the backup command run as |
|
|
202
|
+
| `AUTODEPLOY_KEEP_BACKUPS` | `5` | Rollback dumps kept, newest first |
|
|
145
203
|
| `AUTODEPLOY_WEB_CONTAINER` | `<product>_web_deploy` | The container that is replaced |
|
|
146
204
|
| `AUTODEPLOY_DB_CONTAINER` | `<product>_db_deploy` | The MariaDB container that is backed up |
|
|
147
205
|
| `AUTODEPLOY_AGENT_CONTAINER` | `<product>_autodeploy` | The agent's container name |
|
|
@@ -202,8 +260,10 @@ since.
|
|
|
202
260
|
|
|
203
261
|
- The agent mounts `/var/run/docker.sock`. Whoever controls that socket is
|
|
204
262
|
root on the host, so the agent is as sensitive as the host itself. It runs
|
|
205
|
-
only in production,
|
|
206
|
-
feature's `docker/` directory, and does
|
|
263
|
+
only in production, in a container of its own, from the image
|
|
264
|
+
`product:deploy` builds out of this feature's `docker/` directory, and does
|
|
265
|
+
only what this README describes. No container of the app mounts the
|
|
266
|
+
socket: not the web container, not the migration job it creates.
|
|
207
267
|
- It publishes no port. Only containers on the product network reach the API,
|
|
208
268
|
and every call needs the shared secret. The secret never appears in the
|
|
209
269
|
status, the history or the logs.
|
|
@@ -221,7 +281,8 @@ One per deploy, in the agent's `autodeploy_data` volume under
|
|
|
221
281
|
so each file says which code its schema belongs to. The newest
|
|
222
282
|
`AUTODEPLOY_KEEP_BACKUPS` are kept. They live on the same host as the
|
|
223
283
|
database, so they protect against a bad release, not against losing the
|
|
224
|
-
server
|
|
284
|
+
server: that is what `splent_feature_backup`'s off-host backup, taken just
|
|
285
|
+
before, is for. Without that feature, copy them elsewhere if you need it.
|
|
225
286
|
|
|
226
287
|
## Restoring a backup by hand
|
|
227
288
|
|
|
@@ -248,7 +309,10 @@ To let the agent try an image it refused, pause it, delete the image id from
|
|
|
248
309
|
feature that ships its own container), or needs a new setting without a
|
|
249
310
|
default in `.env.deploy`, has to go through `splent product:deploy`; pause
|
|
250
311
|
the agent while you do it. Left to the agent such a release fails its
|
|
251
|
-
|
|
312
|
+
readiness check and is rolled back, which is safe but not what you want.
|
|
313
|
+
- It takes no database lock (`GET_LOCK`) around the migration: one agent
|
|
314
|
+
replaces one web container, which is stopped while the job runs, so the
|
|
315
|
+
job is the only migrator. Several replicas would need that lock.
|
|
252
316
|
- It does not update itself. A new version of this feature reaches the
|
|
253
317
|
server with `splent product:deploy`, which rebuilds the agent's image.
|
|
254
318
|
- It replaces one web container, not a set of replicas.
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# splent_feature_autodeploy
|
|
2
2
|
|
|
3
3
|
Release-gated continuous delivery for a SPLENT product. A product that
|
|
4
|
-
selects this feature runs a small agent
|
|
5
|
-
production. The agent follows a registry image
|
|
6
|
-
`git.splent.io/splent-io/<product>:stable`, and when
|
|
7
|
-
new image it redeploys the product
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
selects this feature runs a small agent in a container of its own next to
|
|
5
|
+
its web container in production. The agent follows a registry image
|
|
6
|
+
channel, for example `git.splent.io/splent-io/<product>:stable`, and when
|
|
7
|
+
the channel points to a new image it redeploys the product on that image's
|
|
8
|
+
digest. It backs up first (off the host too, when the product has
|
|
9
|
+
`splent_feature_backup`), migrates once, waits for the new version's
|
|
10
|
+
`/readyz`, and rolls the container back, and the database when the deploy
|
|
11
|
+
changed it, when the new version is not ready.
|
|
10
12
|
|
|
11
13
|
The channel only moves when someone runs `splent product:release`, so nothing
|
|
12
14
|
reaches production that nobody released. The agent never builds anything and
|
|
@@ -18,41 +20,92 @@ Every `AUTODEPLOY_INTERVAL` seconds, and whenever an administrator asks,
|
|
|
18
20
|
the agent goes through these steps.
|
|
19
21
|
|
|
20
22
|
1. Pull the channel image. If its id is the one the web container runs,
|
|
21
|
-
there is nothing to do.
|
|
23
|
+
there is nothing to do. Otherwise the image is named from now on by its
|
|
24
|
+
digest (`<registry>/<org>/<product>@sha256:...`), which no push can
|
|
25
|
+
move: that is what the new container runs and what the history records.
|
|
26
|
+
An image the registry knows no digest of runs by its id, just as fixed.
|
|
22
27
|
2. Skip an image that failed before, until the channel points elsewhere.
|
|
23
28
|
Honour the pause flag and the update window (`AUTODEPLOY_WINDOW`). A
|
|
24
29
|
check somebody asks for (Check now) deploys outside the window too.
|
|
25
|
-
3. Back up
|
|
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
|
-
|
|
30
|
+
3. Back up.
|
|
31
|
+
- When the product has `splent_feature_backup`, run its off-host backup
|
|
32
|
+
in the running web container, as the app's user:
|
|
33
|
+
`splent feature:backup run --tag pre-deploy-<short id of the image
|
|
34
|
+
being replaced>` (`AUTODEPLOY_BACKUP_COMMAND`). It is encrypted to keys
|
|
35
|
+
the server does not hold, and it is the copy that survives the host.
|
|
36
|
+
- Then the agent's own dump, the one a rollback restores:
|
|
37
|
+
`mariadb-dump --single-transaction --routines --triggers` runs as root
|
|
38
|
+
inside the database container, with the credentials that container
|
|
39
|
+
already holds, and the gzipped dump is kept only if `mariadb-dump`
|
|
40
|
+
exited cleanly and the dump ends with its `Dump completed` trailer.
|
|
41
|
+
|
|
42
|
+
No complete backup, no deploy.
|
|
43
|
+
4. Record what the web container runs (`splent version --json`), and a
|
|
44
|
+
fingerprint of the schema: every table's columns and every migration
|
|
45
|
+
revision (`alembic_*`, `splent_migrations`).
|
|
46
|
+
5. Create the new web container on the image's digest with everything the
|
|
47
|
+
old one was configured with, the way Watchtower does it. That is its
|
|
48
|
+
name, its environment (the container's own values on top of the new
|
|
49
|
+
image's), its labels, mounts and binds, port bindings, restart policy,
|
|
50
|
+
healthcheck, and its networks with their aliases. Cmd, Entrypoint and
|
|
51
|
+
WorkingDir follow the new image when the old container only inherited
|
|
52
|
+
them, and are kept when it chose them (the compose file's `command:`).
|
|
53
|
+
The old container is renamed `<name>-previous` and stopped, not removed.
|
|
54
|
+
6. Migrate, once: a one-off container of the new image, `<name>-migrate`,
|
|
55
|
+
with the web container's environment, mounts and networks, runs
|
|
56
|
+
`AUTODEPLOY_MIGRATE_COMMAND` (`splent db:upgrade`) as the app's user
|
|
57
|
+
while neither version serves. It answers to no alias, so nginx never
|
|
58
|
+
finds it, publishes no port and is removed once it exits. The new
|
|
59
|
+
container's entrypoint upgrades again as it starts and finds nothing
|
|
60
|
+
left to do.
|
|
61
|
+
7. Start the new container and wait, up to `AUTODEPLOY_HEALTH_TIMEOUT`
|
|
62
|
+
seconds, for `/readyz` to answer 200 (`AUTODEPLOY_READY_PATH`, asked by
|
|
63
|
+
the container's name on `AUTODEPLOY_WEB_PORT`), while Docker's
|
|
64
|
+
healthcheck does not say unhealthy. `/readyz` is the framework's
|
|
65
|
+
(1.22.0 and later): 200 when the database and the services the product
|
|
66
|
+
declares answer. An image without it answers 404, and the healthcheck
|
|
67
|
+
decides for it.
|
|
68
|
+
8. Once it is ready, remove the previous container and write the history
|
|
69
|
+
entry (from and to image, the digest, product version, the features
|
|
70
|
+
whose version changed, the off-host backup, duration).
|
|
71
|
+
9. If the migration fails, or the new container turns unhealthy, exits,
|
|
72
|
+
restarts or is not ready in time, keep its last 80 log lines (the
|
|
73
|
+
migration's, when that is what failed), remove it, put the database
|
|
74
|
+
back when the rule below says so, put the previous container back under
|
|
75
|
+
its name, wait for it to be healthy, and refuse that image from then on.
|
|
76
|
+
The history entry says `rolled_back`, carries the log lines and says
|
|
77
|
+
whether the database went back and why.
|
|
78
|
+
|
|
79
|
+
### When the database goes back
|
|
80
|
+
|
|
81
|
+
A rollback restores the agent's pre-deploy dump (dropping the database
|
|
82
|
+
first, so tables the new migrations created do not linger) when:
|
|
83
|
+
|
|
84
|
+
- the migration failed, or the agent died during it. MariaDB commits DDL as
|
|
85
|
+
it goes, so a failed migration can leave half of its changes behind
|
|
86
|
+
without recording them;
|
|
87
|
+
- the migration finished, and the schema fingerprint differs from the one
|
|
88
|
+
taken before the deploy. The previous version cannot run on the new
|
|
89
|
+
schema, and a down-migration is not something to trust with production
|
|
90
|
+
data;
|
|
91
|
+
- the fingerprint cannot be read, which counts as changed;
|
|
92
|
+
- `AUTODEPLOY_MIGRATE_COMMAND=off`: the new container migrated as it
|
|
93
|
+
started, and the agent cannot tell a failed migration from anything
|
|
94
|
+
else.
|
|
95
|
+
|
|
96
|
+
It does not when the deploy stopped before anything ran against the
|
|
97
|
+
database (a failed backup, a container that could not be created), and when
|
|
98
|
+
the migration finished without changing the schema: the previous version
|
|
99
|
+
comes back on the same database, and nothing written while the new one
|
|
100
|
+
tried to start is lost. The dump is taken right before the old version
|
|
101
|
+
stops, so a restore loses only what the new version wrote during its
|
|
102
|
+
attempt.
|
|
51
103
|
|
|
52
104
|
Every step is written to `/data/state.json` before it is taken. An agent
|
|
53
105
|
restarted in the middle of a deploy (a reboot, a redeploy of the agent
|
|
54
|
-
itself) finishes it if the new container turns
|
|
55
|
-
otherwise
|
|
106
|
+
itself) finishes it if the new container turns ready, and rolls it back
|
|
107
|
+
otherwise; one restarted during the migration rolls back with the
|
|
108
|
+
database.
|
|
56
109
|
|
|
57
110
|
| State | Meaning |
|
|
58
111
|
|----------------------|--------------------------------------------------------------|
|
|
@@ -126,11 +179,16 @@ with the product's names filled in.
|
|
|
126
179
|
|
|
127
180
|
| Variable | Default | Meaning |
|
|
128
181
|
|------------------------------|-------------------------------------|---------|
|
|
129
|
-
| `AUTODEPLOY_IMAGE` | empty | Image to follow. Empty means `SPLENT_IMAGE` |
|
|
182
|
+
| `AUTODEPLOY_IMAGE` | empty | Image to follow. Empty means `SPLENT_IMAGE`. A channel (`:stable`) or a version tag; `:latest`, or no tag at all, is refused |
|
|
130
183
|
| `AUTODEPLOY_INTERVAL` | `300` | Seconds between checks (at least 30) |
|
|
131
184
|
| `AUTODEPLOY_WINDOW` | empty | UTC span for deploys, like `02:00-05:00` or `22:00-04:00`. Empty means any time |
|
|
132
|
-
| `AUTODEPLOY_HEALTH_TIMEOUT` | `600` | Seconds the new container
|
|
133
|
-
| `
|
|
185
|
+
| `AUTODEPLOY_HEALTH_TIMEOUT` | `600` | Seconds the migrations get, and then the new container to turn ready |
|
|
186
|
+
| `AUTODEPLOY_MIGRATE_COMMAND` | `splent db:upgrade` | What the one-off migration container runs. `off` leaves the migrations to the new container's start |
|
|
187
|
+
| `AUTODEPLOY_READY_PATH` | `/readyz` | The readiness probe. `off` waits for Docker's healthcheck alone |
|
|
188
|
+
| `AUTODEPLOY_WEB_PORT` | `5000` | The port the web container answers on, inside the product network |
|
|
189
|
+
| `AUTODEPLOY_BACKUP_COMMAND` | `auto` | The off-host backup before a deploy. `auto` is `splent_feature_backup`'s when the product has it, `off` none, anything else the command to run in the web container |
|
|
190
|
+
| `AUTODEPLOY_APP_USER` | `splent` | The user the migration and the backup command run as |
|
|
191
|
+
| `AUTODEPLOY_KEEP_BACKUPS` | `5` | Rollback dumps kept, newest first |
|
|
134
192
|
| `AUTODEPLOY_WEB_CONTAINER` | `<product>_web_deploy` | The container that is replaced |
|
|
135
193
|
| `AUTODEPLOY_DB_CONTAINER` | `<product>_db_deploy` | The MariaDB container that is backed up |
|
|
136
194
|
| `AUTODEPLOY_AGENT_CONTAINER` | `<product>_autodeploy` | The agent's container name |
|
|
@@ -191,8 +249,10 @@ since.
|
|
|
191
249
|
|
|
192
250
|
- The agent mounts `/var/run/docker.sock`. Whoever controls that socket is
|
|
193
251
|
root on the host, so the agent is as sensitive as the host itself. It runs
|
|
194
|
-
only in production,
|
|
195
|
-
feature's `docker/` directory, and does
|
|
252
|
+
only in production, in a container of its own, from the image
|
|
253
|
+
`product:deploy` builds out of this feature's `docker/` directory, and does
|
|
254
|
+
only what this README describes. No container of the app mounts the
|
|
255
|
+
socket: not the web container, not the migration job it creates.
|
|
196
256
|
- It publishes no port. Only containers on the product network reach the API,
|
|
197
257
|
and every call needs the shared secret. The secret never appears in the
|
|
198
258
|
status, the history or the logs.
|
|
@@ -210,7 +270,8 @@ One per deploy, in the agent's `autodeploy_data` volume under
|
|
|
210
270
|
so each file says which code its schema belongs to. The newest
|
|
211
271
|
`AUTODEPLOY_KEEP_BACKUPS` are kept. They live on the same host as the
|
|
212
272
|
database, so they protect against a bad release, not against losing the
|
|
213
|
-
server
|
|
273
|
+
server: that is what `splent_feature_backup`'s off-host backup, taken just
|
|
274
|
+
before, is for. Without that feature, copy them elsewhere if you need it.
|
|
214
275
|
|
|
215
276
|
## Restoring a backup by hand
|
|
216
277
|
|
|
@@ -237,7 +298,10 @@ To let the agent try an image it refused, pause it, delete the image id from
|
|
|
237
298
|
feature that ships its own container), or needs a new setting without a
|
|
238
299
|
default in `.env.deploy`, has to go through `splent product:deploy`; pause
|
|
239
300
|
the agent while you do it. Left to the agent such a release fails its
|
|
240
|
-
|
|
301
|
+
readiness check and is rolled back, which is safe but not what you want.
|
|
302
|
+
- It takes no database lock (`GET_LOCK`) around the migration: one agent
|
|
303
|
+
replaces one web container, which is stopped while the job runs, so the
|
|
304
|
+
job is the only migrator. Several replicas would need that lock.
|
|
241
305
|
- It does not update itself. A new version of this feature reaches the
|
|
242
306
|
server with `splent product:deploy`, which rebuilds the agent's image.
|
|
243
307
|
- It replaces one web container, not a set of replicas.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "splent_feature_autodeploy"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
readme = "README.md"
|
|
9
9
|
requires-python = ">=3.13"
|
|
10
10
|
license = "LGPL-3.0-or-later"
|
|
@@ -95,7 +95,9 @@ services = []
|
|
|
95
95
|
# The registry image to follow. Empty means SPLENT_IMAGE, the image the web
|
|
96
96
|
# service runs, which is the usual choice: the server follows the channel it
|
|
97
97
|
# was deployed from, git.splent.io/splent-io/<product>:stable for example,
|
|
98
|
-
# and that tag only moves when someone runs splent product:release.
|
|
98
|
+
# and that tag only moves when someone runs splent product:release. Never
|
|
99
|
+
# :latest, which the agent refuses; the container it creates runs the
|
|
100
|
+
# image's digest, whatever tag it followed.
|
|
99
101
|
AUTODEPLOY_IMAGE = ""
|
|
100
102
|
# Seconds between two looks at the registry. A pull of an image already
|
|
101
103
|
# present costs one manifest request.
|
|
@@ -103,10 +105,30 @@ AUTODEPLOY_INTERVAL = 300
|
|
|
103
105
|
# Daily UTC span in which a deploy may start, like 02:00-05:00. It may wrap
|
|
104
106
|
# past midnight (22:00-04:00). Empty means any time.
|
|
105
107
|
AUTODEPLOY_WINDOW = ""
|
|
106
|
-
# Seconds the new web container
|
|
107
|
-
#
|
|
108
|
+
# Seconds the migrations get, and then the new web container to answer its
|
|
109
|
+
# readiness probe.
|
|
108
110
|
AUTODEPLOY_HEALTH_TIMEOUT = 600
|
|
109
|
-
#
|
|
111
|
+
# What a one-off container of the new image runs, once, between stopping the
|
|
112
|
+
# old version and starting the new one. off leaves the migrations to the new
|
|
113
|
+
# container's start, and then a failed deploy always restores the database.
|
|
114
|
+
AUTODEPLOY_MIGRATE_COMMAND = "splent db:upgrade"
|
|
115
|
+
# The new version is ready when this answers 200, asked by the web
|
|
116
|
+
# container's name on its AUTODEPLOY_WEB_PORT. off waits for Docker's
|
|
117
|
+
# healthcheck alone. An image whose framework has no /readyz (before 1.22.0)
|
|
118
|
+
# answers 404, and the healthcheck decides for it.
|
|
119
|
+
AUTODEPLOY_READY_PATH = "/readyz"
|
|
120
|
+
AUTODEPLOY_WEB_PORT = 5000
|
|
121
|
+
# The off-host backup taken in the web container before anything changes.
|
|
122
|
+
# auto runs splent_feature_backup's (splent feature:backup run --tag
|
|
123
|
+
# pre-deploy-<image>) when the product has that feature; off runs none; any
|
|
124
|
+
# other value is the command to run. It failing stops the deploy.
|
|
125
|
+
AUTODEPLOY_BACKUP_COMMAND = "auto"
|
|
126
|
+
# The user the migrations and the backup command run as: the one the
|
|
127
|
+
# production image runs the app as, so what they write belongs to the app.
|
|
128
|
+
AUTODEPLOY_APP_USER = "splent"
|
|
129
|
+
# Database dumps kept in the agent's volume, one per deploy, newest first.
|
|
130
|
+
# They are the rollback's, on this host; the off-host copy is the backup
|
|
131
|
+
# feature's.
|
|
110
132
|
AUTODEPLOY_KEEP_BACKUPS = 5
|
|
111
133
|
# The web container the agent replaces, as the production compose names it.
|
|
112
134
|
AUTODEPLOY_WEB_CONTAINER = "__PRODUCT___web"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: splent_feature_autodeploy
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
License-Expression: LGPL-3.0-or-later
|
|
5
5
|
Requires-Python: >=3.13
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -12,12 +12,14 @@ Dynamic: license-file
|
|
|
12
12
|
# splent_feature_autodeploy
|
|
13
13
|
|
|
14
14
|
Release-gated continuous delivery for a SPLENT product. A product that
|
|
15
|
-
selects this feature runs a small agent
|
|
16
|
-
production. The agent follows a registry image
|
|
17
|
-
`git.splent.io/splent-io/<product>:stable`, and when
|
|
18
|
-
new image it redeploys the product
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
selects this feature runs a small agent in a container of its own next to
|
|
16
|
+
its web container in production. The agent follows a registry image
|
|
17
|
+
channel, for example `git.splent.io/splent-io/<product>:stable`, and when
|
|
18
|
+
the channel points to a new image it redeploys the product on that image's
|
|
19
|
+
digest. It backs up first (off the host too, when the product has
|
|
20
|
+
`splent_feature_backup`), migrates once, waits for the new version's
|
|
21
|
+
`/readyz`, and rolls the container back, and the database when the deploy
|
|
22
|
+
changed it, when the new version is not ready.
|
|
21
23
|
|
|
22
24
|
The channel only moves when someone runs `splent product:release`, so nothing
|
|
23
25
|
reaches production that nobody released. The agent never builds anything and
|
|
@@ -29,41 +31,92 @@ Every `AUTODEPLOY_INTERVAL` seconds, and whenever an administrator asks,
|
|
|
29
31
|
the agent goes through these steps.
|
|
30
32
|
|
|
31
33
|
1. Pull the channel image. If its id is the one the web container runs,
|
|
32
|
-
there is nothing to do.
|
|
34
|
+
there is nothing to do. Otherwise the image is named from now on by its
|
|
35
|
+
digest (`<registry>/<org>/<product>@sha256:...`), which no push can
|
|
36
|
+
move: that is what the new container runs and what the history records.
|
|
37
|
+
An image the registry knows no digest of runs by its id, just as fixed.
|
|
33
38
|
2. Skip an image that failed before, until the channel points elsewhere.
|
|
34
39
|
Honour the pause flag and the update window (`AUTODEPLOY_WINDOW`). A
|
|
35
40
|
check somebody asks for (Check now) deploys outside the window too.
|
|
36
|
-
3. Back up
|
|
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
|
-
|
|
41
|
+
3. Back up.
|
|
42
|
+
- When the product has `splent_feature_backup`, run its off-host backup
|
|
43
|
+
in the running web container, as the app's user:
|
|
44
|
+
`splent feature:backup run --tag pre-deploy-<short id of the image
|
|
45
|
+
being replaced>` (`AUTODEPLOY_BACKUP_COMMAND`). It is encrypted to keys
|
|
46
|
+
the server does not hold, and it is the copy that survives the host.
|
|
47
|
+
- Then the agent's own dump, the one a rollback restores:
|
|
48
|
+
`mariadb-dump --single-transaction --routines --triggers` runs as root
|
|
49
|
+
inside the database container, with the credentials that container
|
|
50
|
+
already holds, and the gzipped dump is kept only if `mariadb-dump`
|
|
51
|
+
exited cleanly and the dump ends with its `Dump completed` trailer.
|
|
52
|
+
|
|
53
|
+
No complete backup, no deploy.
|
|
54
|
+
4. Record what the web container runs (`splent version --json`), and a
|
|
55
|
+
fingerprint of the schema: every table's columns and every migration
|
|
56
|
+
revision (`alembic_*`, `splent_migrations`).
|
|
57
|
+
5. Create the new web container on the image's digest with everything the
|
|
58
|
+
old one was configured with, the way Watchtower does it. That is its
|
|
59
|
+
name, its environment (the container's own values on top of the new
|
|
60
|
+
image's), its labels, mounts and binds, port bindings, restart policy,
|
|
61
|
+
healthcheck, and its networks with their aliases. Cmd, Entrypoint and
|
|
62
|
+
WorkingDir follow the new image when the old container only inherited
|
|
63
|
+
them, and are kept when it chose them (the compose file's `command:`).
|
|
64
|
+
The old container is renamed `<name>-previous` and stopped, not removed.
|
|
65
|
+
6. Migrate, once: a one-off container of the new image, `<name>-migrate`,
|
|
66
|
+
with the web container's environment, mounts and networks, runs
|
|
67
|
+
`AUTODEPLOY_MIGRATE_COMMAND` (`splent db:upgrade`) as the app's user
|
|
68
|
+
while neither version serves. It answers to no alias, so nginx never
|
|
69
|
+
finds it, publishes no port and is removed once it exits. The new
|
|
70
|
+
container's entrypoint upgrades again as it starts and finds nothing
|
|
71
|
+
left to do.
|
|
72
|
+
7. Start the new container and wait, up to `AUTODEPLOY_HEALTH_TIMEOUT`
|
|
73
|
+
seconds, for `/readyz` to answer 200 (`AUTODEPLOY_READY_PATH`, asked by
|
|
74
|
+
the container's name on `AUTODEPLOY_WEB_PORT`), while Docker's
|
|
75
|
+
healthcheck does not say unhealthy. `/readyz` is the framework's
|
|
76
|
+
(1.22.0 and later): 200 when the database and the services the product
|
|
77
|
+
declares answer. An image without it answers 404, and the healthcheck
|
|
78
|
+
decides for it.
|
|
79
|
+
8. Once it is ready, remove the previous container and write the history
|
|
80
|
+
entry (from and to image, the digest, product version, the features
|
|
81
|
+
whose version changed, the off-host backup, duration).
|
|
82
|
+
9. If the migration fails, or the new container turns unhealthy, exits,
|
|
83
|
+
restarts or is not ready in time, keep its last 80 log lines (the
|
|
84
|
+
migration's, when that is what failed), remove it, put the database
|
|
85
|
+
back when the rule below says so, put the previous container back under
|
|
86
|
+
its name, wait for it to be healthy, and refuse that image from then on.
|
|
87
|
+
The history entry says `rolled_back`, carries the log lines and says
|
|
88
|
+
whether the database went back and why.
|
|
89
|
+
|
|
90
|
+
### When the database goes back
|
|
91
|
+
|
|
92
|
+
A rollback restores the agent's pre-deploy dump (dropping the database
|
|
93
|
+
first, so tables the new migrations created do not linger) when:
|
|
94
|
+
|
|
95
|
+
- the migration failed, or the agent died during it. MariaDB commits DDL as
|
|
96
|
+
it goes, so a failed migration can leave half of its changes behind
|
|
97
|
+
without recording them;
|
|
98
|
+
- the migration finished, and the schema fingerprint differs from the one
|
|
99
|
+
taken before the deploy. The previous version cannot run on the new
|
|
100
|
+
schema, and a down-migration is not something to trust with production
|
|
101
|
+
data;
|
|
102
|
+
- the fingerprint cannot be read, which counts as changed;
|
|
103
|
+
- `AUTODEPLOY_MIGRATE_COMMAND=off`: the new container migrated as it
|
|
104
|
+
started, and the agent cannot tell a failed migration from anything
|
|
105
|
+
else.
|
|
106
|
+
|
|
107
|
+
It does not when the deploy stopped before anything ran against the
|
|
108
|
+
database (a failed backup, a container that could not be created), and when
|
|
109
|
+
the migration finished without changing the schema: the previous version
|
|
110
|
+
comes back on the same database, and nothing written while the new one
|
|
111
|
+
tried to start is lost. The dump is taken right before the old version
|
|
112
|
+
stops, so a restore loses only what the new version wrote during its
|
|
113
|
+
attempt.
|
|
62
114
|
|
|
63
115
|
Every step is written to `/data/state.json` before it is taken. An agent
|
|
64
116
|
restarted in the middle of a deploy (a reboot, a redeploy of the agent
|
|
65
|
-
itself) finishes it if the new container turns
|
|
66
|
-
otherwise
|
|
117
|
+
itself) finishes it if the new container turns ready, and rolls it back
|
|
118
|
+
otherwise; one restarted during the migration rolls back with the
|
|
119
|
+
database.
|
|
67
120
|
|
|
68
121
|
| State | Meaning |
|
|
69
122
|
|----------------------|--------------------------------------------------------------|
|
|
@@ -137,11 +190,16 @@ with the product's names filled in.
|
|
|
137
190
|
|
|
138
191
|
| Variable | Default | Meaning |
|
|
139
192
|
|------------------------------|-------------------------------------|---------|
|
|
140
|
-
| `AUTODEPLOY_IMAGE` | empty | Image to follow. Empty means `SPLENT_IMAGE` |
|
|
193
|
+
| `AUTODEPLOY_IMAGE` | empty | Image to follow. Empty means `SPLENT_IMAGE`. A channel (`:stable`) or a version tag; `:latest`, or no tag at all, is refused |
|
|
141
194
|
| `AUTODEPLOY_INTERVAL` | `300` | Seconds between checks (at least 30) |
|
|
142
195
|
| `AUTODEPLOY_WINDOW` | empty | UTC span for deploys, like `02:00-05:00` or `22:00-04:00`. Empty means any time |
|
|
143
|
-
| `AUTODEPLOY_HEALTH_TIMEOUT` | `600` | Seconds the new container
|
|
144
|
-
| `
|
|
196
|
+
| `AUTODEPLOY_HEALTH_TIMEOUT` | `600` | Seconds the migrations get, and then the new container to turn ready |
|
|
197
|
+
| `AUTODEPLOY_MIGRATE_COMMAND` | `splent db:upgrade` | What the one-off migration container runs. `off` leaves the migrations to the new container's start |
|
|
198
|
+
| `AUTODEPLOY_READY_PATH` | `/readyz` | The readiness probe. `off` waits for Docker's healthcheck alone |
|
|
199
|
+
| `AUTODEPLOY_WEB_PORT` | `5000` | The port the web container answers on, inside the product network |
|
|
200
|
+
| `AUTODEPLOY_BACKUP_COMMAND` | `auto` | The off-host backup before a deploy. `auto` is `splent_feature_backup`'s when the product has it, `off` none, anything else the command to run in the web container |
|
|
201
|
+
| `AUTODEPLOY_APP_USER` | `splent` | The user the migration and the backup command run as |
|
|
202
|
+
| `AUTODEPLOY_KEEP_BACKUPS` | `5` | Rollback dumps kept, newest first |
|
|
145
203
|
| `AUTODEPLOY_WEB_CONTAINER` | `<product>_web_deploy` | The container that is replaced |
|
|
146
204
|
| `AUTODEPLOY_DB_CONTAINER` | `<product>_db_deploy` | The MariaDB container that is backed up |
|
|
147
205
|
| `AUTODEPLOY_AGENT_CONTAINER` | `<product>_autodeploy` | The agent's container name |
|
|
@@ -202,8 +260,10 @@ since.
|
|
|
202
260
|
|
|
203
261
|
- The agent mounts `/var/run/docker.sock`. Whoever controls that socket is
|
|
204
262
|
root on the host, so the agent is as sensitive as the host itself. It runs
|
|
205
|
-
only in production,
|
|
206
|
-
feature's `docker/` directory, and does
|
|
263
|
+
only in production, in a container of its own, from the image
|
|
264
|
+
`product:deploy` builds out of this feature's `docker/` directory, and does
|
|
265
|
+
only what this README describes. No container of the app mounts the
|
|
266
|
+
socket: not the web container, not the migration job it creates.
|
|
207
267
|
- It publishes no port. Only containers on the product network reach the API,
|
|
208
268
|
and every call needs the shared secret. The secret never appears in the
|
|
209
269
|
status, the history or the logs.
|
|
@@ -221,7 +281,8 @@ One per deploy, in the agent's `autodeploy_data` volume under
|
|
|
221
281
|
so each file says which code its schema belongs to. The newest
|
|
222
282
|
`AUTODEPLOY_KEEP_BACKUPS` are kept. They live on the same host as the
|
|
223
283
|
database, so they protect against a bad release, not against losing the
|
|
224
|
-
server
|
|
284
|
+
server: that is what `splent_feature_backup`'s off-host backup, taken just
|
|
285
|
+
before, is for. Without that feature, copy them elsewhere if you need it.
|
|
225
286
|
|
|
226
287
|
## Restoring a backup by hand
|
|
227
288
|
|
|
@@ -248,7 +309,10 @@ To let the agent try an image it refused, pause it, delete the image id from
|
|
|
248
309
|
feature that ships its own container), or needs a new setting without a
|
|
249
310
|
default in `.env.deploy`, has to go through `splent product:deploy`; pause
|
|
250
311
|
the agent while you do it. Left to the agent such a release fails its
|
|
251
|
-
|
|
312
|
+
readiness check and is rolled back, which is safe but not what you want.
|
|
313
|
+
- It takes no database lock (`GET_LOCK`) around the migration: one agent
|
|
314
|
+
replaces one web container, which is stopped while the job runs, so the
|
|
315
|
+
job is the only migrator. Several replicas would need that lock.
|
|
252
316
|
- It does not update itself. A new version of this feature reaches the
|
|
253
317
|
server with `splent product:deploy`, which rebuilds the agent's image.
|
|
254
318
|
- It replaces one web container, not a set of replicas.
|
|
@@ -129,8 +129,15 @@ def history(limit, as_json):
|
|
|
129
129
|
f" {change.get('name')} {change.get('from') or '(new)'} -> "
|
|
130
130
|
f"{change.get('to') or '(removed)'}"
|
|
131
131
|
)
|
|
132
|
+
if entry.get("to_ref"):
|
|
133
|
+
click.echo(f" image {entry['to_ref']}")
|
|
134
|
+
if entry.get("offsite_backup"):
|
|
135
|
+
click.echo(f" off-host backup {entry['offsite_backup']}")
|
|
132
136
|
if entry.get("reason"):
|
|
133
137
|
click.echo(f" reason {entry['reason']}")
|
|
138
|
+
if "database_restored" in entry:
|
|
139
|
+
state = "restored" if entry["database_restored"] else "kept"
|
|
140
|
+
click.echo(f" database {state}: {entry.get('database_decision')}")
|
|
134
141
|
for problem in entry.get("problems") or []:
|
|
135
142
|
click.secho(f" problem {problem}", fg="red")
|
|
136
143
|
click.echo()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|