wslc-wip 1.1.2 → 1.1.3
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.
- checksums.yaml +4 -4
- data/README.md +59 -10
- data/lib/wip/cli.rb +127 -8
- data/lib/wip/compose_file.rb +24 -7
- data/lib/wip/config.rb +11 -1
- data/lib/wip/initializer.rb +4 -0
- data/lib/wip/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 753cbc1f34c7c0c598edd7b4e4bc821953b97e12c66cec448096e05a0debd909
|
|
4
|
+
data.tar.gz: c9cc51e4f53a4f8240be292487315ee4cdec019febc006d232d09560457e1a20
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c45401f388f42dc378066a1a3e14b39b622f13087f8627439f59ce8fa384fa2fb7f162bee8d6ee1f11581e32d5ece6d782bcb2d1c7bfd06f64973944d30ee1c5
|
|
7
|
+
data.tar.gz: beb47f232295309e11aa6df585b829165fa125024283da3e29f8ee222ea05eaee530df0ee9f759bb2de8b29b11dfb789d883691c8750e43e6e5655b44ebc4122
|
data/README.md
CHANGED
|
@@ -159,9 +159,12 @@ The two are aliases for the same feature, not separate ones: pick whichever name
|
|
|
159
159
|
|
|
160
160
|
`dependencies:` holds every container uniformly — the primary one `container:` points at and any
|
|
161
161
|
sidecar services (a database, Redis, ...) alongside it. Each entry accepts `image` (required),
|
|
162
|
-
`command`, `env`, `ports`, `volumes`, and `
|
|
163
|
-
for "the one you exec into." `container:` has no default; once `dependencies:` has any entries,
|
|
164
|
-
wip needs to be told explicitly which one is primary rather than guessing a name.
|
|
162
|
+
`command`, `env`, `ports`, `volumes`, `workdir`, and `restart`; there's no separate, differently-shaped
|
|
163
|
+
block for "the one you exec into." `container:` has no default; once `dependencies:` has any entries,
|
|
164
|
+
wip needs to be told explicitly which one is primary rather than guessing a name. `restart` is stored
|
|
165
|
+
but inert on its own — see
|
|
166
|
+
[Restarting exited dependencies](#restarting-exited-dependencies-wip-up---watch) for what actually
|
|
167
|
+
acts on it (`wip up --watch`).
|
|
165
168
|
|
|
166
169
|
What sets the primary entry apart is operational, not structural: `wip up` brings up every other
|
|
167
170
|
entry by name first (creating `network:` beforehand if it doesn't exist and set), then boots or
|
|
@@ -172,6 +175,46 @@ in place). Only the primary container is a target for `exec`/`run`/`build`/`inte
|
|
|
172
175
|
are only ever started and stopped, matching Compose's own service-vs-you-exec-into-one-of-them
|
|
173
176
|
split.
|
|
174
177
|
|
|
178
|
+
#### Restarting exited dependencies (`wip up --watch`)
|
|
179
|
+
|
|
180
|
+
Real Compose auto-restarts a container tagged `restart: always`/`unless-stopped`/`on-failure` when
|
|
181
|
+
it exits. `wslc` has no such policy, and no push-based "container exited" notification for `wip` to
|
|
182
|
+
hook into, so the closest approximation is polling: `wip up --watch` brings everything up the same
|
|
183
|
+
way `wip up -d` does, then keeps checking (`--interval SECONDS`, default 5) whether any
|
|
184
|
+
dependency — the primary container included — has exited, restarting the ones whose `restart:`
|
|
185
|
+
allows it.
|
|
186
|
+
|
|
187
|
+
```console
|
|
188
|
+
$ wip up --watch
|
|
189
|
+
wip: watching app, mysql for exited restart: containers every 5s (running detached; Ctrl-C to stop)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
- `restart:` accepts the same values Compose does — `no` (the default), `always`, `unless-stopped`,
|
|
193
|
+
`on-failure`, optionally with a `:MAX_RETRIES` suffix. `wip up --watch` treats the three
|
|
194
|
+
restarting values identically: it restarts on any exited container regardless of exit code,
|
|
195
|
+
unlike real `on-failure`, which skips a clean (zero) exit — reading an exit code needs a heavier
|
|
196
|
+
call this polling loop doesn't make.
|
|
197
|
+
- This is a foreground loop, not a background daemon or service — the project intentionally has
|
|
198
|
+
neither (see [Roadmap](#roadmap)). Keep the terminal it's running in open, the same as
|
|
199
|
+
`wip sync --watch`; Ctrl-C (or closing the terminal) stops the supervision.
|
|
200
|
+
- `--watch` implies `-d`: it can't attach a TTY to the primary container and poll in a loop on the
|
|
201
|
+
same thread, so the primary container always runs detached under `--watch`, whether or not you
|
|
202
|
+
also passed `-d`.
|
|
203
|
+
- Not available under `mode: compose` — wip never parses a service list in that mode, so there's
|
|
204
|
+
nothing for it to poll; use whatever restart support your external compose-for-`wslc` tool offers.
|
|
205
|
+
- It's status-based, not event-based: each tick checks whether a dependency is currently exited, not
|
|
206
|
+
whether it *just* exited. It can't tell "crashed on its own" apart from "you ran `wip stop`/
|
|
207
|
+
`wip down` in another terminal" — Ctrl-C the `--watch` loop first, or it may race and restart
|
|
208
|
+
what you just stopped.
|
|
209
|
+
- Re-running `wip up -d --watch` against an already-running stack is safe: an already-running
|
|
210
|
+
container's `start` is a no-op, the same as it is for plain `wip up`.
|
|
211
|
+
- The exit-detection reads `wslc list --all --format json`'s `State` field as a raw integer, per
|
|
212
|
+
`WslcContainerState` ([wsl.dev](https://wsl.dev/api-reference/c/enumerations/wslccontainerstate/)):
|
|
213
|
+
`0` invalid, `1` created, `2` running, `3` exited, `4` deleted. Unlike Docker, there's no separate
|
|
214
|
+
`dead` state — a `deleted` container is gone and needs `wip up` (not `--watch`) to recreate it.
|
|
215
|
+
If `--watch` never restarts anything you believe really exited, run `wip up --watch --debug` and
|
|
216
|
+
check the logged `list` entry against this enum.
|
|
217
|
+
|
|
175
218
|
### Compose mode
|
|
176
219
|
|
|
177
220
|
If your project already has a real `compose.yml`, don't duplicate it in `dependencies:` — point
|
|
@@ -250,11 +293,13 @@ valid compose.yml over sections it doesn't need to look at:
|
|
|
250
293
|
does), `command` (shell or exec form), `environment`
|
|
251
294
|
(mapping or `KEY=VALUE` array — a mapping value must not be null; host environment pass-through
|
|
252
295
|
isn't supported), `ports`/`volumes` (short syntax only — `"host:container"` strings, not
|
|
253
|
-
long-syntax mappings), `working_dir`, `user`, `
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
296
|
+
long-syntax mappings), `working_dir`, `user`, `restart` (stored as-is; `no` is the default —
|
|
297
|
+
`wip up --watch` is what actually acts on it, see
|
|
298
|
+
[Restarting exited dependencies](#restarting-exited-dependencies-wip-up---watch)), `depends_on`
|
|
299
|
+
(ordering only — a `condition:` other than `service_started` is rejected, since there's no
|
|
300
|
+
health-check support). `tty`, `stdin_open`, and `networks` are accepted but silently ignored:
|
|
301
|
+
TTY/stdin allocation is already decided per invocation (see "TTY allocation" below), not fixed
|
|
302
|
+
per service, and every service already shares the one project network `compose.project` sets up.
|
|
258
303
|
- `wip logs` takes at most one `SERVICE` (defaulting to `compose.service`) — `wslc logs`, like
|
|
259
304
|
`docker logs`, follows a single container, unlike a real compose tool's multi-service view.
|
|
260
305
|
- `sync:` behaves exactly like `mode: container`'s (falls back to the primary service's own image,
|
|
@@ -422,7 +467,7 @@ to that tag directly — `sync.build`'s tag wins if both are set, so don't confi
|
|
|
422
467
|
| `wip doctor` | Diagnose WSL2, interop, WSLC, config, architecture, and Git |
|
|
423
468
|
| `wip config` | Print the effective configuration (secrets masked) |
|
|
424
469
|
| `wip build [--no-cache] [-- OPTIONS]` | Build the image from the `build` definition. `wslc build` reuses matching local layers by default; `--no-cache` disables that. |
|
|
425
|
-
| `wip up [-d] [--no-sync] [--no-cache]` | Start the primary `dependencies:` entry (`container:` names which one) and its sidecars (creating any that are missing, on `network:` if set). `-d` runs the main container in the background; with `sync:` configured, the source is mirrored into the volume first unless `--no-sync` |
|
|
470
|
+
| `wip up [-d] [--no-sync] [--no-cache] [--watch] [--interval N]` | Start the primary `dependencies:` entry (`container:` names which one) and its sidecars (creating any that are missing, on `network:` if set). `-d` runs the main container in the background; with `sync:` configured, the source is mirrored into the volume first unless `--no-sync`. `--watch` polls every `N` seconds (default 5, implies `-d`) and restarts any exited dependency whose `restart:` allows it (not available under mode: compose) |
|
|
426
471
|
| `wip stop` | Stop the primary container and its sidecar `dependencies:` without removing them |
|
|
427
472
|
| `wip down` | Stop and remove the primary container and its sidecar `dependencies:` |
|
|
428
473
|
| `wip exec [--no-interactive] COMMAND...` | Run a command in the existing container |
|
|
@@ -635,7 +680,11 @@ and its current limitations (`run`, and `interaction:` of type `run`/`build`).
|
|
|
635
680
|
|
|
636
681
|
Beyond Compose parity, a resident/daemon process, a GUI, PowerShell-specific tuning, direct
|
|
637
682
|
registry API/manifest parsing, self-update, and plugins are all unimplemented and not currently
|
|
638
|
-
planned.
|
|
683
|
+
planned. (`wip up --watch`'s restart-policy poll loop isn't an exception to this — it's a
|
|
684
|
+
foreground, opt-in loop you keep a terminal open for, the same shape as `wip sync --watch`, not a
|
|
685
|
+
background service; see
|
|
686
|
+
[Restarting exited dependencies](#restarting-exited-dependencies-wip-up---watch).) What's still
|
|
687
|
+
planned for `wip`, roughly in priority order:
|
|
639
688
|
|
|
640
689
|
1. **`wip provision`** — a dip-style one-shot bootstrap hook (build → up deps → install deps →
|
|
641
690
|
create/migrate/seed DB) so a new contributor can go from `git clone` to a working environment
|
data/lib/wip/cli.rb
CHANGED
|
@@ -16,6 +16,42 @@ module Wip
|
|
|
16
16
|
class_option :debug_log, type: :string, desc: 'Where --debug snapshots go: a file path, or "-" for inline'
|
|
17
17
|
default_task :dispatch
|
|
18
18
|
|
|
19
|
+
# Thor only recognizes class_options (--config, --debug, ...) once they
|
|
20
|
+
# appear after the command name (`wip up --config foo`). Pull a leading
|
|
21
|
+
# run of them off the front of ARGV and reinsert it right after the
|
|
22
|
+
# command name so `wip --config foo up` works too.
|
|
23
|
+
GLOBAL_SWITCHES = {
|
|
24
|
+
'--config' => :value,
|
|
25
|
+
'--env-file' => :value,
|
|
26
|
+
'--debug' => :flag,
|
|
27
|
+
'--debug-log' => :value
|
|
28
|
+
}.freeze
|
|
29
|
+
|
|
30
|
+
def self.start(given_args = ARGV, config = {})
|
|
31
|
+
super(reorder_global_options(given_args), config)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.reorder_global_options(args)
|
|
35
|
+
remaining = args.dup
|
|
36
|
+
extracted = []
|
|
37
|
+
|
|
38
|
+
until remaining.empty?
|
|
39
|
+
name, _, inline_value = remaining.first.to_s.partition('=')
|
|
40
|
+
kind = GLOBAL_SWITCHES[name]
|
|
41
|
+
break unless kind
|
|
42
|
+
|
|
43
|
+
switch = remaining.shift
|
|
44
|
+
extracted << switch
|
|
45
|
+
extracted << remaining.shift if kind == :value && inline_value.empty? && !remaining.empty?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
command_index = remaining.index { |arg| !arg.start_with?('-') }
|
|
49
|
+
return args if command_index.nil?
|
|
50
|
+
|
|
51
|
+
remaining.insert(command_index + 1, *extracted)
|
|
52
|
+
remaining
|
|
53
|
+
end
|
|
54
|
+
|
|
19
55
|
def self.exit_on_failure? = true
|
|
20
56
|
|
|
21
57
|
# Thor only falls back to the default task when no command name is given at
|
|
@@ -82,21 +118,41 @@ module Wip
|
|
|
82
118
|
progress&.finish
|
|
83
119
|
end
|
|
84
120
|
|
|
121
|
+
# Real Compose values that trigger a restart when a container's exited; `no` (the default)
|
|
122
|
+
# and anything unrecognized both mean "leave it alone." Exact match only — a typo'd value
|
|
123
|
+
# like "always-invalid" must stay inert, not accidentally match via a loose prefix check.
|
|
124
|
+
AUTO_RESTART_POLICIES = %w[always unless-stopped].freeze
|
|
125
|
+
ON_FAILURE_POLICY = /\Aon-failure(?::\d+)?\z/ # optional `:MAX_RETRIES` suffix, e.g. "on-failure:3"
|
|
126
|
+
|
|
127
|
+
# WSLC's `WslcContainerState` (confirmed via microsoft/WSL's own docs and
|
|
128
|
+
# ContainerModel.h — ContainerInformation#State has no custom JSON enum serializer, so
|
|
129
|
+
# nlohmann::json emits its raw ordinal): 0 invalid, 1 created, 2 running, 3 exited, 4
|
|
130
|
+
# deleted. Unlike Docker, there's no separate "dead" state — only `exited` is a live,
|
|
131
|
+
# restartable exit; `deleted` means the container itself is gone (needs `wip up` to
|
|
132
|
+
# recreate it, not `start`).
|
|
133
|
+
WSLC_CONTAINER_STATE_EXITED = 3
|
|
134
|
+
|
|
85
135
|
desc 'up', 'Start the configured container and its dependencies, creating them if necessary'
|
|
86
136
|
option :detach, type: :boolean, default: false, aliases: '-d'
|
|
87
137
|
option :sync, type: :boolean, default: true, desc: 'Mirror the source into the sync volume first (--no-sync skips)'
|
|
88
138
|
option :no_cache, type: :boolean, default: false, desc: 'Build compose-native images without cached layers'
|
|
139
|
+
option :watch, type: :boolean, default: false, aliases: '-w',
|
|
140
|
+
desc: 'Poll dependencies and restart any exited one whose restart: allows it (implies -d)'
|
|
141
|
+
option :interval, type: :numeric, default: 5, desc: 'Seconds between --watch polls (default: 5)'
|
|
89
142
|
def up
|
|
90
|
-
if load_config.compose?
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
143
|
+
return up_via_compose_bridge if load_config.compose?
|
|
144
|
+
|
|
145
|
+
# Validated up front, before any startup side effect (image build, network/dependency/
|
|
146
|
+
# container creation) — otherwise a bad --interval would only surface as a ConfigError
|
|
147
|
+
# after already bringing everything up.
|
|
148
|
+
interval = restart_interval if options[:watch]
|
|
94
149
|
|
|
95
150
|
ensure_compose_images
|
|
96
151
|
ensure_network
|
|
97
152
|
sidecar_names.each { |name| ensure_dependency(name) }
|
|
98
153
|
sync_before_boot if options[:sync]
|
|
99
154
|
ensure_container
|
|
155
|
+
watch_restarts(interval) if options[:watch]
|
|
100
156
|
end
|
|
101
157
|
|
|
102
158
|
desc 'sync', 'Mirror the source tree into the sync volume'
|
|
@@ -194,7 +250,7 @@ module Wip
|
|
|
194
250
|
|
|
195
251
|
desc 'dispatch COMMAND [ARGS...]', 'Run a command defined in wip.yml'
|
|
196
252
|
def dispatch(name = nil, *arguments)
|
|
197
|
-
|
|
253
|
+
return help if name.nil?
|
|
198
254
|
|
|
199
255
|
values = load_config.command(name) || raise(ConfigError, "Unknown command: #{name}")
|
|
200
256
|
return dispatch_compose(name, values, arguments) if load_config.compose?
|
|
@@ -407,16 +463,79 @@ module Wip
|
|
|
407
463
|
warn "wip: run `wip sync --watch` in another terminal to keep #{settings.target} up to date"
|
|
408
464
|
end
|
|
409
465
|
|
|
466
|
+
def up_via_compose_bridge
|
|
467
|
+
if options[:watch]
|
|
468
|
+
raise ConfigError, '`wip up --watch` is not supported under mode: compose (wip never parses a ' \
|
|
469
|
+
'compose.yml service list in that mode, so there is nothing to poll)'
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
sync_before_boot if options[:sync]
|
|
473
|
+
execute(compose_bridge.up(detach: options[:detach]), interactive: tty?(!options[:detach]))
|
|
474
|
+
end
|
|
475
|
+
|
|
410
476
|
def ensure_container
|
|
411
477
|
container = load_config.container
|
|
412
|
-
interactive = tty?(!
|
|
478
|
+
interactive = tty?(!detach?)
|
|
413
479
|
if resource_exists?(builder.find)
|
|
414
480
|
warn "wip: starting existing container '#{container}'"
|
|
415
|
-
execute(builder.start(detach:
|
|
481
|
+
execute(builder.start(detach: detach?), interactive: interactive)
|
|
416
482
|
else
|
|
417
483
|
warn "wip: container '#{container}' not found, creating it"
|
|
418
|
-
execute(builder.up(detach:
|
|
484
|
+
execute(builder.up(detach: detach?), interactive: interactive)
|
|
485
|
+
end
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
# --watch polls in a loop after boot, which can't share this one thread with an attached
|
|
489
|
+
# (`-it`) primary container — force the same effective behavior `-d` gives ensure_container.
|
|
490
|
+
def detach? = options[:detach] || options[:watch]
|
|
491
|
+
|
|
492
|
+
# Approximates Docker Compose's `restart:` policy via a foreground poll loop — not a
|
|
493
|
+
# background daemon/service (see README "Roadmap"); the same opt-in, keep-a-terminal-open
|
|
494
|
+
# shape as `wip sync --watch`.
|
|
495
|
+
def watch_restarts(interval)
|
|
496
|
+
names = load_config.dependencies.keys
|
|
497
|
+
warn "wip: watching #{names.join(', ')} for exited restart: containers every #{interval}s " \
|
|
498
|
+
'(running detached; Ctrl-C to stop)'
|
|
499
|
+
loop do
|
|
500
|
+
names.each { |name| restart_if_exited(name) }
|
|
501
|
+
sleep interval
|
|
419
502
|
end
|
|
503
|
+
rescue Interrupt
|
|
504
|
+
warn "\nwip: watch stopped"
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
def restart_interval
|
|
508
|
+
raise ConfigError, '--interval must be a positive number' unless options[:interval].positive?
|
|
509
|
+
|
|
510
|
+
options[:interval]
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
# Status-based, not transition-based: each tick checks current state, not whether it *just*
|
|
514
|
+
# exited. Can't distinguish "crashed on its own" from "you ran `wip stop`/`wip down` in
|
|
515
|
+
# another terminal" — Ctrl-C this loop first if you're about to do either (see README).
|
|
516
|
+
def restart_if_exited(name)
|
|
517
|
+
policy = load_config.dependency(name)['restart']
|
|
518
|
+
return unless auto_restart?(policy)
|
|
519
|
+
return unless container_status(name) == WSLC_CONTAINER_STATE_EXITED
|
|
520
|
+
|
|
521
|
+
warn "wip: '#{name}' has exited, restarting it (restart: #{policy})"
|
|
522
|
+
execute(builder.dependency_start(name), exit_on_failure: false)
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
def auto_restart?(policy) = AUTO_RESTART_POLICIES.include?(policy) || ON_FAILURE_POLICY.match?(policy.to_s)
|
|
526
|
+
|
|
527
|
+
# Isolated to this one method: if a future wslc release changes this shape, fixing it here
|
|
528
|
+
# is a one-line change. Logs the raw entry under --debug so that's immediately visible
|
|
529
|
+
# instead of silently no-op'ing forever.
|
|
530
|
+
def container_status(name)
|
|
531
|
+
code, output = probe(builder.dependency_find(name))
|
|
532
|
+
return nil unless code.zero?
|
|
533
|
+
|
|
534
|
+
entry = JSON.parse(output).first
|
|
535
|
+
warn "wip: [debug] '#{name}': #{entry.inspect}" if debug?
|
|
536
|
+
entry&.fetch('State', nil)
|
|
537
|
+
rescue JSON::ParserError
|
|
538
|
+
nil
|
|
420
539
|
end
|
|
421
540
|
end
|
|
422
541
|
end
|
data/lib/wip/compose_file.rb
CHANGED
|
@@ -15,15 +15,17 @@ module Wip
|
|
|
15
15
|
# "Compose mode (native)") — once wslc ships that support, or a
|
|
16
16
|
# compose-for-wslc tool reliably supports `run`.
|
|
17
17
|
class ComposeFile
|
|
18
|
-
Service = Struct.new(:image, :build, :command, :env, :ports, :volumes, :workdir, :user, :
|
|
19
|
-
keyword_init: true)
|
|
18
|
+
Service = Struct.new(:image, :build, :command, :env, :ports, :volumes, :workdir, :user, :restart, :depends_on,
|
|
19
|
+
:profiles, keyword_init: true)
|
|
20
20
|
|
|
21
|
-
SERVICE_KEYS = %w[image build command environment ports volumes working_dir user depends_on
|
|
21
|
+
SERVICE_KEYS = %w[image build command environment ports volumes working_dir user restart depends_on
|
|
22
|
+
profiles].freeze
|
|
22
23
|
# Real Compose keys that read as meaningful here but have nothing to map onto: TTY/stdin
|
|
23
24
|
# allocation is decided per invocation (CommandBuilder#tty?), not per service; every
|
|
24
25
|
# service already shares one project network (config.rb); and `wslc run`/`exec` has no
|
|
25
|
-
#
|
|
26
|
-
|
|
26
|
+
# capability flag to forward `cap_add:` to. (`restart:` used to be here too, silently
|
|
27
|
+
# ignored — it's parsed below now and approximated by `wip up --watch`, see cli.rb.)
|
|
28
|
+
IGNORED_SERVICE_KEYS = %w[tty stdin_open networks cap_add].freeze
|
|
27
29
|
BUILD_KEYS = %w[context dockerfile args shadow_context].freeze
|
|
28
30
|
SUPPORTED_CONDITIONS = %w[service_started].freeze
|
|
29
31
|
LIST_HINT = 'only supports short syntax ("host:container"), not long-syntax mappings'
|
|
@@ -65,14 +67,14 @@ module Wip
|
|
|
65
67
|
end.to_h
|
|
66
68
|
end
|
|
67
69
|
|
|
68
|
-
# Shaped like Config::DEPENDENCY_DEFAULTS expects: image/command/env/ports/volumes/workdir,
|
|
70
|
+
# Shaped like Config::DEPENDENCY_DEFAULTS expects: image/command/env/ports/volumes/workdir/restart,
|
|
69
71
|
# in dependency order so callers iterating sidecars start them before their dependents.
|
|
70
72
|
def to_dependencies_hash
|
|
71
73
|
startable_order.to_h do |name|
|
|
72
74
|
service = @services.fetch(name)
|
|
73
75
|
[name, { 'image' => service.build ? image_tag(name, service) : service.image, 'command' => service.command,
|
|
74
76
|
'env' => service.env, 'ports' => service.ports, 'volumes' => service.volumes,
|
|
75
|
-
'workdir' => service.workdir, 'user' => service.user }]
|
|
77
|
+
'workdir' => service.workdir, 'user' => service.user, 'restart' => service.restart }]
|
|
76
78
|
end
|
|
77
79
|
end
|
|
78
80
|
|
|
@@ -97,6 +99,7 @@ module Wip
|
|
|
97
99
|
Service.new(image: image, build: build, command: normalize_command(entry['command']),
|
|
98
100
|
env: normalize_env(name, entry['environment']), **normalize_service_lists(name, entry),
|
|
99
101
|
workdir: presence(entry['working_dir']), user: presence(entry['user']),
|
|
102
|
+
restart: normalize_restart(entry['restart']),
|
|
100
103
|
depends_on: normalize_depends_on(name, entry['depends_on']))
|
|
101
104
|
end
|
|
102
105
|
|
|
@@ -250,6 +253,20 @@ module Wip
|
|
|
250
253
|
order << name
|
|
251
254
|
end
|
|
252
255
|
|
|
256
|
+
# Compose's own default (`no`) applies whether restart: is absent or explicitly falsy —
|
|
257
|
+
# including the very common unquoted `restart: no`, which YAML resolves to the boolean
|
|
258
|
+
# `false`, not the string "no" (confirmed against this repo's own Psych: `YAML.safe_load
|
|
259
|
+
# ("restart: no")` => {"restart"=>false}). Every other value is accepted as-is, even ones
|
|
260
|
+
# outside always/unless-stopped/on-failure[:N]: this parser's job is to read what's in
|
|
261
|
+
# compose.yml, not police it — `wip up --watch` (cli.rb) decides which values it acts on.
|
|
262
|
+
# Rejecting a real, valid Compose value here would break projects that already work today
|
|
263
|
+
# (compose.yml predates wip, unlike wip.yml itself).
|
|
264
|
+
def normalize_restart(value)
|
|
265
|
+
return 'no' if value == false
|
|
266
|
+
|
|
267
|
+
presence(value) || 'no'
|
|
268
|
+
end
|
|
269
|
+
|
|
253
270
|
def presence(value) = value.to_s.empty? ? nil : value.to_s
|
|
254
271
|
end
|
|
255
272
|
end
|
data/lib/wip/config.rb
CHANGED
|
@@ -8,7 +8,7 @@ module Wip
|
|
|
8
8
|
# Applied to every dependencies: entry, primary container included — there
|
|
9
9
|
# is no separate, differently-shaped bucket for "the one you exec into."
|
|
10
10
|
DEPENDENCY_DEFAULTS = { 'workdir' => nil, 'user' => nil, 'interactive' => false, 'remove' => true,
|
|
11
|
-
'env' => {}, 'ports' => [], 'volumes' => [] }.freeze
|
|
11
|
+
'env' => {}, 'ports' => [], 'volumes' => [], 'restart' => 'no' }.freeze
|
|
12
12
|
SECRET_PATTERN = /token|password|secret|credential|auth/i
|
|
13
13
|
# Which orchestration path `up`/`down`/`sync`/etc. take. Explicit rather than
|
|
14
14
|
# inferred from a `compose:` block's presence, so a config reader doesn't have
|
|
@@ -209,6 +209,16 @@ module Wip
|
|
|
209
209
|
raise ConfigError, "dependencies.#{name} must set image" if entry['image'].to_s.empty?
|
|
210
210
|
|
|
211
211
|
entry['env']&.transform_values!(&:to_s)
|
|
212
|
+
normalize_dependency_restart!(entry)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Mirrors ComposeFile#normalize_restart: an unquoted `restart: no` parses as the boolean
|
|
216
|
+
# false rather than the string "no", and an explicit nil/"" should default the same way an
|
|
217
|
+
# absent key does (DEPENDENCY_DEFAULTS only fills in a genuinely *missing* key).
|
|
218
|
+
def normalize_dependency_restart!(entry)
|
|
219
|
+
return unless entry.key?('restart')
|
|
220
|
+
|
|
221
|
+
entry['restart'] = 'no' if entry['restart'] == false || entry['restart'].to_s.empty?
|
|
212
222
|
end
|
|
213
223
|
|
|
214
224
|
def stringify(object)
|
data/lib/wip/initializer.rb
CHANGED
|
@@ -159,6 +159,10 @@ module Wip
|
|
|
159
159
|
# optional; remove the container after each run
|
|
160
160
|
remove: true
|
|
161
161
|
|
|
162
|
+
# optional; restart policy `wip up --watch` polls for — always/unless-stopped/on-failure
|
|
163
|
+
# restart an exited container; "no" (the default) never does — see README
|
|
164
|
+
restart: "no"
|
|
165
|
+
|
|
162
166
|
# optional; environment variables passed to the container, e.g. {FOO: bar}
|
|
163
167
|
env: {}
|
|
164
168
|
|
data/lib/wip/version.rb
CHANGED