Skip to content

Commit b918066

Browse files
committed
feat(serviceGroups): add hooks
Allow before/after start/stop hooks. @moduon MT-14138
1 parent 255a2b1 commit b918066

4 files changed

Lines changed: 234 additions & 0 deletions

File tree

docs/src/getting_started.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,25 @@ This will add two commands to the devshell: `database:start` and
188188
group in the foreground and shows their output. `database:stop` can be executed
189189
in a different shell to stop the processes (or press Ctrl-C in the main shell).
190190

191+
You can also run commands before and after starting/stopping a service group.
192+
For example, to run database migrations before starting and cleanup after stopping:
193+
```toml
194+
[serviceGroups.api]
195+
description = "API server and related services"
196+
beforeStart = """
197+
echo "Running database migrations..."
198+
./manage.py migrate
199+
"""
200+
afterStop = """
201+
echo "Cleaning up temp files..."
202+
rm -rf /tmp/api_*
203+
"""
204+
[serviceGroups.api.services.django]
205+
command = "python manage.py runserver 0.0.0.0:8000"
206+
[serviceGroups.api.services.redis]
207+
command = "redis-server"
208+
```
209+
191210
## Wrapping up
192211

193212
**devshell** is extensible in many different ways. In the next chapters we will

docs/src/modules_schema.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1284,6 +1284,86 @@ attribute set of (submodule)
12841284

12851285
- [modules/services.nix](https://github.com/numtide/devshell/tree/main/modules/services.nix)
12861286

1287+
### `serviceGroups.<name>.beforeStart`
1288+
1289+
Shell command to run before starting the service group.
1290+
1291+
**Type**:
1292+
1293+
```console
1294+
null or string
1295+
```
1296+
1297+
**Default value**:
1298+
1299+
```nix
1300+
null
1301+
```
1302+
1303+
**Declared in**:
1304+
1305+
- [modules/services.nix](https://github.com/numtide/devshell/tree/main/modules/services.nix)
1306+
1307+
### `serviceGroups.<name>.afterStart`
1308+
1309+
Shell command to run after starting the service group.
1310+
1311+
**Type**:
1312+
1313+
```console
1314+
null or string
1315+
```
1316+
1317+
**Default value**:
1318+
1319+
```nix
1320+
null
1321+
```
1322+
1323+
**Declared in**:
1324+
1325+
- [modules/services.nix](https://github.com/numtide/devshell/tree/main/modules/services.nix)
1326+
1327+
### `serviceGroups.<name>.beforeStop`
1328+
1329+
Shell command to run before stopping the service group.
1330+
1331+
**Type**:
1332+
1333+
```console
1334+
null or string
1335+
```
1336+
1337+
**Default value**:
1338+
1339+
```nix
1340+
null
1341+
```
1342+
1343+
**Declared in**:
1344+
1345+
- [modules/services.nix](https://github.com/numtide/devshell/tree/main/modules/services.nix)
1346+
1347+
### `serviceGroups.<name>.afterStop`
1348+
1349+
Shell command to run after stopping the service group.
1350+
1351+
**Type**:
1352+
1353+
```console
1354+
null or string
1355+
```
1356+
1357+
**Default value**:
1358+
1359+
```nix
1360+
null
1361+
```
1362+
1363+
**Declared in**:
1364+
1365+
- [modules/services.nix](https://github.com/numtide/devshell/tree/main/modules/services.nix)
1366+
12871367
### `serviceGroups.<name>.services.<name>.command`
12881368

12891369
Command to execute.

modules/services.nix

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,34 @@ let
4747
Attrset of services that should be run in this group.
4848
'';
4949
};
50+
beforeStart = mkOption {
51+
type = types.nullOr types.str;
52+
default = null;
53+
description = ''
54+
Shell command to run before starting the service group.
55+
'';
56+
};
57+
afterStart = mkOption {
58+
type = types.nullOr types.str;
59+
default = null;
60+
description = ''
61+
Shell command to run after starting the service group.
62+
'';
63+
};
64+
beforeStop = mkOption {
65+
type = types.nullOr types.str;
66+
default = null;
67+
description = ''
68+
Shell command to run before stopping the service group.
69+
'';
70+
};
71+
afterStop = mkOption {
72+
type = types.nullOr types.str;
73+
default = null;
74+
description = ''
75+
Shell command to run after stopping the service group.
76+
'';
77+
};
5078
};
5179
groupToProcfile =
5280
name: g:
@@ -75,13 +103,17 @@ let
75103
exit 1
76104
fi
77105
mkdir -p "$PRJ_DATA_DIR/pids/"
106+
${g.beforeStart}
78107
${pkgs.honcho}/bin/honcho start -f ${procfile} -d "$PRJ_ROOT" &
79108
pid=$!
80109
echo $pid > "$PRJ_DATA_DIR/pids/${gName}.pid"
110+
${g.afterStart}
81111
on_stop() {
112+
${g.beforeStop}
82113
if ps -p $pid > /dev/null; then
83114
kill -TERM $pid
84115
fi
116+
${g.afterStop}
85117
rm "$PRJ_DATA_DIR/pids/${gName}.pid"
86118
wait $pid
87119
}
@@ -97,8 +129,10 @@ let
97129
(pkgs.writeShellScript "${gName}-services-stop" ''
98130
if [ -e "$PRJ_DATA_DIR/pids/${gName}.pid" ]; then
99131
pid=$(${pkgs.coreutils}/bin/cat "$PRJ_DATA_DIR/pids/${gName}.pid")
132+
${g.beforeStop}
100133
kill -TERM $pid
101134
rm "$PRJ_DATA_DIR/pids/${gName}.pid"
135+
${g.afterStop}
102136
fi
103137
'').outPath;
104138
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
{
2+
pkgs,
3+
devshell,
4+
runTest,
5+
}:
6+
let
7+
hookBeforeStart = pkgs.writeShellScript "hook-before-start" ''
8+
echo "BEFORE_START_EXECUTED" > "$PRJ_DATA_DIR/hook_before_start.log"
9+
'';
10+
hookAfterStart = pkgs.writeShellScript "hook-after-start" ''
11+
echo "AFTER_START_EXECUTED" > "$PRJ_DATA_DIR/hook_after_start.log"
12+
'';
13+
hookBeforeStop = pkgs.writeShellScript "hook-before-stop" ''
14+
echo "BEFORE_STOP_EXECUTED" > "$PRJ_DATA_DIR/hook_before_stop.log"
15+
'';
16+
hookAfterStop = pkgs.writeShellScript "hook-after-stop" ''
17+
echo "AFTER_STOP_EXECUTED" > "$PRJ_DATA_DIR/hook_after_stop.log"
18+
'';
19+
dummyService = pkgs.writeShellScript "dummy-service" ''
20+
while true; do sleep 1; done
21+
'';
22+
23+
shell = devshell.mkShell {
24+
devshell.name = "service-groups-hooks-test";
25+
serviceGroups.test = {
26+
description = "Test service group";
27+
beforeStart = "${hookBeforeStart}";
28+
afterStart = "${hookAfterStart}";
29+
beforeStop = "${hookBeforeStop}";
30+
afterStop = "${hookAfterStop}";
31+
services.dummy = { command = "${dummyService}"; };
32+
};
33+
};
34+
in
35+
{
36+
service-groups-hooks-test = runTest "service-groups-hooks" { } ''
37+
# Load the devshell
38+
source ${shell}/env.bash
39+
40+
# Start the service group
41+
test:start &
42+
START_PID=$!
43+
44+
# Give it a moment to start
45+
sleep 2
46+
47+
# Check that beforeStart hook was executed
48+
if [ ! -f "$PRJ_DATA_DIR/hook_before_start.log" ]; then
49+
echo "ERROR: beforeStart hook was not executed"
50+
exit 1
51+
fi
52+
53+
if [ "$(cat "$PRJ_DATA_DIR/hook_before_start.log")" != "BEFORE_START_EXECUTED" ]; then
54+
echo "ERROR: beforeStart hook did not execute correctly"
55+
exit 1
56+
fi
57+
58+
# Check that afterStart hook was executed
59+
if [ ! -f "$PRJ_DATA_DIR/hook_after_start.log" ]; then
60+
echo "ERROR: afterStart hook was not executed"
61+
exit 1
62+
fi
63+
64+
if [ "$(cat "$PRJ_DATA_DIR/hook_after_start.log")" != "AFTER_START_EXECUTED" ]; then
65+
echo "ERROR: afterStart hook did not execute correctly"
66+
exit 1
67+
fi
68+
69+
# Stop the service group
70+
test:stop
71+
72+
# Give it a moment to stop
73+
sleep 2
74+
75+
# Check that beforeStop hook was executed
76+
if [ ! -f "$PRJ_DATA_DIR/hook_before_stop.log" ]; then
77+
echo "ERROR: beforeStop hook was not executed"
78+
exit 1
79+
fi
80+
81+
if [ "$(cat "$PRJ_DATA_DIR/hook_before_stop.log")" != "BEFORE_STOP_EXECUTED" ]; then
82+
echo "ERROR: beforeStop hook did not execute correctly"
83+
exit 1
84+
fi
85+
86+
# Check that afterStop hook was executed
87+
if [ ! -f "$PRJ_DATA_DIR/hook_after_stop.log" ]; then
88+
echo "ERROR: afterStop hook was not executed"
89+
exit 1
90+
fi
91+
92+
if [ "$(cat "$PRJ_DATA_DIR/hook_after_stop.log")" != "AFTER_STOP_EXECUTED" ]; then
93+
echo "ERROR: afterStop hook did not execute correctly"
94+
exit 1
95+
fi
96+
97+
# Clean up
98+
kill $START_PID 2>/dev/null || true
99+
wait $START_PID 2>/dev/null || true
100+
'';
101+
}

0 commit comments

Comments
 (0)