wslc-wip 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +96 -5
- data/lib/wip/cli.rb +56 -14
- data/lib/wip/command_resolver.rb +17 -10
- data/lib/wip/compose_bridge.rb +70 -0
- data/lib/wip/config.rb +17 -1
- data/lib/wip/doctor.rb +38 -7
- data/lib/wip/version.rb +1 -1
- data/lib/wip.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c125a123c76462cbb6b78fe0ead5ea400b8fd0f7a8c825421985547190603d02
|
|
4
|
+
data.tar.gz: 0762dbbf6480a84d9ef58d9481d1398672b5d2ef7e8d644d39be655558704022
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b91868415f3069167ddd479616d9bd2937d3684af2e7c6228789b198cd7af952e9d2705fe363877ef05a6831a7f5a8e9a203e073623bb0c4b46102f2771429c5
|
|
7
|
+
data.tar.gz: 88b8d3fdec972776b0bdefedd5753ebcb4a025cb9bf54122ad7eb723e663f4cac64dff5a4ec201bd45b5959b9f44ba6801e8441419ff72f388631eb7eee80f67
|
data/README.md
CHANGED
|
@@ -129,6 +129,47 @@ the same way Compose's service names resolve. `wip down` tears the main containe
|
|
|
129
129
|
dependencies down (the network itself is left in place). Each dependency entry accepts `image`
|
|
130
130
|
(required), `command`, `env`, `ports`, `volumes`, and `workdir` — the same shape as `defaults`.
|
|
131
131
|
|
|
132
|
+
### Compose mode
|
|
133
|
+
|
|
134
|
+
If your project already has a real `compose.yml`, don't duplicate it in `dependencies:` — point
|
|
135
|
+
`wip` at it instead:
|
|
136
|
+
|
|
137
|
+
```yaml
|
|
138
|
+
version: 1
|
|
139
|
+
compose:
|
|
140
|
+
service: app # required: which compose service wip run/exec/NAME target
|
|
141
|
+
command: wslc-compose # required: the compose-for-wslc binary/path you have installed
|
|
142
|
+
file: compose.yml # optional; auto-detected next to wip.yml otherwise
|
|
143
|
+
project: myapp # optional; omitted lets the compose tool pick its own default
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`compose:` is mutually exclusive with `dependencies:`/`defaults.network` — pick one orchestration
|
|
147
|
+
path per project. In compose mode, `wip` becomes a thin bridge to an external compose-for-`wslc`
|
|
148
|
+
CLI rather than reimplementing Compose itself.
|
|
149
|
+
|
|
150
|
+
`wslc` itself has no native Compose support yet (tracked upstream in
|
|
151
|
+
[microsoft/WSL#40948](https://github.com/microsoft/WSL/issues/40948)), and until it does,
|
|
152
|
+
independent third-party tools fill the gap — for example
|
|
153
|
+
[bacarndiaye/wslc-compose](https://github.com/bacarndiaye/wslc-compose) (Python) and
|
|
154
|
+
[inuyume/wslc-compose](https://github.com/inuyume/wslc-compose) (Go), among others. `wslc` is new
|
|
155
|
+
and still evolving, so expect more of these to show up (and existing ones to change) over time.
|
|
156
|
+
`wip` deliberately doesn't pick a winner or default to any of them (unlike `wslc.command`, which
|
|
157
|
+
defaults to `auto` and searches for `wslc.exe`/`wslc`): `compose.command` is required and treats
|
|
158
|
+
every implementation equally — set it to whichever binary name or absolute path you've installed.
|
|
159
|
+
Whichever one you use needs to understand `-f FILE [-p PROJECT] up|down|exec|logs`, the subset of
|
|
160
|
+
the Compose CLI vocabulary `wip` drives. `wip doctor` reports whether the configured command is
|
|
161
|
+
found, its version, and which compose file `wip` resolved.
|
|
162
|
+
|
|
163
|
+
- `wip up`/`wip down` delegate straight to `<compose command> up -d`/`down`.
|
|
164
|
+
- `wip exec`/`wip NAME` (custom `commands:`) run inside `compose.service`.
|
|
165
|
+
- `wip shell` also goes through the bridge: unless `commands.shell` is defined in `wip.yml`, it
|
|
166
|
+
`exec`s `bash` against `compose.service`, falling back to `sh`.
|
|
167
|
+
- `wip logs [-f] [SERVICE...]` is only available in compose mode.
|
|
168
|
+
- `wip run` has no ephemeral-container equivalent in this exec-only vocabulary, so it falls back
|
|
169
|
+
to `exec` against the already-running `compose.service` (wip warns when this happens).
|
|
170
|
+
- `commands:` entries with `type: run`/`type: build` aren't supported in compose mode — use your
|
|
171
|
+
compose tool's own `build`/`up --build` directly; compose owns builds for its own services.
|
|
172
|
+
|
|
132
173
|
## Commands
|
|
133
174
|
|
|
134
175
|
| Command | Description |
|
|
@@ -140,8 +181,9 @@ dependencies down (the network itself is left in place). Each dependency entry a
|
|
|
140
181
|
| `wip up [-d]` | Start `defaults.container` and its `dependencies` (creating any that are missing, on `defaults.network` if set). `-d` runs the main container in the background |
|
|
141
182
|
| `wip down` | Stop and remove `defaults.container` and its `dependencies` |
|
|
142
183
|
| `wip exec [--no-interactive] COMMAND...` | Run a command in the existing container |
|
|
143
|
-
| `wip run [--no-interactive] COMMAND...` | Run a command in a new `--rm` container |
|
|
184
|
+
| `wip run [--no-interactive] COMMAND...` | Run a command in a new `--rm` container (compose mode: `exec`s into `compose.service` instead) |
|
|
144
185
|
| `wip shell` | Open the configured shell, falling back to `bash` then `sh` |
|
|
186
|
+
| `wip logs [-f] [SERVICE...]` | Follow compose service logs (compose mode only) |
|
|
145
187
|
| `wip NAME ARGS...` | Run `commands.NAME`, appending any extra arguments |
|
|
146
188
|
|
|
147
189
|
TTY allocation is decided by combining the command's config, the CLI option, and whether both
|
|
@@ -209,6 +251,23 @@ When `pull access denied` (or similar) is detected, wip suggests how to log in:
|
|
|
209
251
|
wslc registry login -u <username> docker.io
|
|
210
252
|
```
|
|
211
253
|
|
|
254
|
+
### Slow boot when the app directory is bind-mounted
|
|
255
|
+
|
|
256
|
+
wslc containers run in their own VM, so a bind-mounted host directory (`.:/app`) is always shared
|
|
257
|
+
in over virtiofs, even when the host path is already on WSL's native filesystem. Frameworks that
|
|
258
|
+
scan large directory trees at startup (Ruby's Zeitwerk autoloader, for example) issue many small
|
|
259
|
+
per-file stat/open calls, and each one is a round trip through that layer — CPU on the Windows
|
|
260
|
+
side can look busy while almost no data is actually transferred, and the process can appear hung
|
|
261
|
+
for minutes with barely any resource usage to show for it.
|
|
262
|
+
|
|
263
|
+
If a debug log shows a boot-time command "stuck" with low CPU/mem/IO in `resource_monitor`'s
|
|
264
|
+
output, this is worth checking before assuming the app itself is broken. The workaround is to stop
|
|
265
|
+
bind-mounting the source live and instead mirror it into a named volume: mount the host path
|
|
266
|
+
read-only (`.:/host-src:ro`), add a named volume for `/app`, and have the container's entrypoint
|
|
267
|
+
`rsync` from the read-only mount into the volume before boot, then keep syncing in the background
|
|
268
|
+
(e.g. every couple of seconds) so edits still show up with a short delay. The app then only ever
|
|
269
|
+
touches fast native storage once it's running.
|
|
270
|
+
|
|
212
271
|
### CPU architecture mismatch
|
|
213
272
|
|
|
214
273
|
Check the image and publish a multi-arch (amd64/arm64) image:
|
|
@@ -243,10 +302,42 @@ checklist.
|
|
|
243
302
|
|
|
244
303
|
## Not in the initial release
|
|
245
304
|
|
|
246
|
-
Full Compose compatibility
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
305
|
+
Full Compose compatibility isn't reimplemented in `wip` itself, but is available by delegating to
|
|
306
|
+
a third-party compose-for-`wslc` tool — see [Compose mode](#compose-mode). A resident/daemon
|
|
307
|
+
process, a GUI, PowerShell-specific tuning, direct registry API/manifest parsing, self-update, and
|
|
308
|
+
plugins are all unimplemented.
|
|
309
|
+
|
|
310
|
+
## Roadmap
|
|
311
|
+
|
|
312
|
+
`wip` already covers most of what [`dip`](https://github.com/bibendi/dip) adds on top of Compose —
|
|
313
|
+
named commands (`commands:`), `run`/`exec` hidden behind a single verb, `.env` passthrough, and
|
|
314
|
+
sidecar services via `dependencies:` + `defaults.network`. Fuller Compose semantics
|
|
315
|
+
(`depends_on` ordering/health checks, log aggregation, named volumes, profiles, scaling) are now
|
|
316
|
+
handled by delegating to a separate compose-for-`wslc` tool in [compose mode](#compose-mode)
|
|
317
|
+
rather than being reimplemented in `wip` itself. Which of those actually work is entirely up to
|
|
318
|
+
the external tool you point `compose.command` at — `wip` only forwards
|
|
319
|
+
`-f FILE [-p PROJECT] up|down|exec|logs`, so treat that list as what Compose offers, not as
|
|
320
|
+
something `wip` guarantees. See that section for what compose mode covers
|
|
321
|
+
and its current limitations (`run`, and `commands:` of type `run`/`build`). What's still planned
|
|
322
|
+
for `wip`, roughly in priority order:
|
|
323
|
+
|
|
324
|
+
1. **`wip provision`** — a dip-style one-shot bootstrap hook (build → up deps → install deps →
|
|
325
|
+
create/migrate/seed DB) so a new contributor can go from `git clone` to a working environment
|
|
326
|
+
in two commands (`wip provision && wip up`).
|
|
327
|
+
2. **Config file merging** — `--config` currently accepts one file; support layering
|
|
328
|
+
(`wip.yml` + `wip.override.yml`, or a `WIP_CONFIG` list) for dev/CI/debug variants without
|
|
329
|
+
duplicating the whole file, plus a `wip config --resolved` view of the merged result.
|
|
330
|
+
3. **Bind-mount boot time (`rails c`, `bundle`, ...)** — commands like `wip rails c` still start
|
|
331
|
+
noticeably slower than the equivalent under `docker compose`, mostly from WSL2 bind-mounted
|
|
332
|
+
(`.:/app`-style) volumes doing many small reads for gems/`node_modules` (use `--debug` to
|
|
333
|
+
confirm it's disk I/O and not `wip`'s own overhead). This isn't really `wip`'s responsibility —
|
|
334
|
+
the fix has to come from either the mount layer or the project's own volume layout. We're also
|
|
335
|
+
hoping for improvements on the `wslc` side itself (faster bind-mount/cache behavior); `wip`
|
|
336
|
+
will pick those up for free as soon as they land.
|
|
337
|
+
|
|
338
|
+
Each of these should stay additive to the existing `wip.yml` shape — no breaking changes to
|
|
339
|
+
`defaults`, `commands`, `dependencies`, or `compose` are planned. A resident daemon and a GUI
|
|
340
|
+
remain out of scope; see "Not in the initial release" above.
|
|
250
341
|
|
|
251
342
|
## License
|
|
252
343
|
|
data/lib/wip/cli.rb
CHANGED
|
@@ -4,6 +4,7 @@ require 'thor'
|
|
|
4
4
|
require 'yaml'
|
|
5
5
|
require 'json'
|
|
6
6
|
require 'stringio'
|
|
7
|
+
require 'shellwords'
|
|
7
8
|
|
|
8
9
|
module Wip
|
|
9
10
|
# Thor-based command-line interface for wip.
|
|
@@ -63,6 +64,10 @@ module Wip
|
|
|
63
64
|
desc 'up', 'Start the configured container and its dependencies, creating them if necessary'
|
|
64
65
|
option :detach, type: :boolean, default: false, aliases: '-d'
|
|
65
66
|
def up
|
|
67
|
+
if load_config.compose?
|
|
68
|
+
return execute(compose_bridge.up(detach: options[:detach]), interactive: tty?(!options[:detach]))
|
|
69
|
+
end
|
|
70
|
+
|
|
66
71
|
ensure_network
|
|
67
72
|
load_config.dependencies.each_key { |name| ensure_dependency(name) }
|
|
68
73
|
ensure_container
|
|
@@ -70,6 +75,8 @@ module Wip
|
|
|
70
75
|
|
|
71
76
|
desc 'down', 'Stop and remove the configured container and its dependencies'
|
|
72
77
|
def down
|
|
78
|
+
return execute(compose_bridge.down, exit_on_failure: false) if load_config.compose?
|
|
79
|
+
|
|
73
80
|
execute(builder.down, exit_on_failure: false)
|
|
74
81
|
execute(builder.remove, exit_on_failure: false)
|
|
75
82
|
load_config.dependencies.each_key do |name|
|
|
@@ -81,41 +88,51 @@ module Wip
|
|
|
81
88
|
desc 'exec COMMAND...', 'Execute a command in the running container'
|
|
82
89
|
option :interactive, type: :boolean, default: true
|
|
83
90
|
def exec(*command)
|
|
84
|
-
|
|
85
|
-
execute(builder.exec(command, interactive: options[:interactive]), interactive: tty)
|
|
91
|
+
execute(exec_target(command, interactive: options[:interactive]), interactive: tty?(options[:interactive]))
|
|
86
92
|
end
|
|
87
93
|
|
|
88
94
|
desc 'run COMMAND...', 'Run a command in a new container'
|
|
89
95
|
option :interactive, type: :boolean, default: true
|
|
90
96
|
def run_command(*command)
|
|
91
|
-
|
|
92
|
-
|
|
97
|
+
if load_config.compose?
|
|
98
|
+
warn "wip: compose mode has no ephemeral 'run'; executing in the running " \
|
|
99
|
+
"'#{load_config.compose_service}' service instead"
|
|
100
|
+
return execute(exec_target(command, interactive: options[:interactive]),
|
|
101
|
+
interactive: tty?(options[:interactive]))
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
execute(builder.run(command, interactive: options[:interactive]), interactive: tty?(options[:interactive]))
|
|
93
105
|
end
|
|
94
106
|
map 'run' => :run_command
|
|
95
107
|
|
|
96
108
|
desc 'shell', 'Open a shell in the configured container'
|
|
97
109
|
def shell_command
|
|
98
110
|
configured = load_config.command('shell')
|
|
99
|
-
if configured
|
|
100
|
-
tty = builder.tty?(configured.fetch('interactive', false))
|
|
101
|
-
return execute(builder.custom('shell', []), interactive: tty)
|
|
102
|
-
end
|
|
111
|
+
return dispatch('shell') if configured
|
|
103
112
|
|
|
104
|
-
|
|
105
|
-
code = execute(builder.exec(['bash'], settings: { 'interactive' => true }, interactive: true),
|
|
106
|
-
interactive: tty, exit_on_failure: false)
|
|
113
|
+
code = execute(exec_target(['bash'], interactive: true), interactive: tty?(true), exit_on_failure: false)
|
|
107
114
|
return if code.zero?
|
|
108
115
|
|
|
109
|
-
execute(
|
|
116
|
+
execute(exec_target(['sh'], interactive: true), interactive: tty?(true))
|
|
110
117
|
end
|
|
111
118
|
map 'shell' => :shell_command
|
|
112
119
|
|
|
120
|
+
desc 'logs [SERVICE...]', 'Follow logs from compose services (compose mode only)'
|
|
121
|
+
option :follow, type: :boolean, default: true, aliases: '-f'
|
|
122
|
+
def logs(*services)
|
|
123
|
+
raise ConfigError, '`wip logs` is only available in compose mode' unless load_config.compose?
|
|
124
|
+
|
|
125
|
+
execute(compose_bridge.logs(services: services, follow: options[:follow]), interactive: true)
|
|
126
|
+
end
|
|
127
|
+
|
|
113
128
|
desc 'dispatch COMMAND [ARGS...]', 'Run a command defined in wip.yml'
|
|
114
129
|
def dispatch(name = nil, *arguments)
|
|
115
130
|
raise ConfigError, 'A command is required' unless name
|
|
116
131
|
|
|
117
132
|
values = load_config.command(name) || raise(ConfigError, "Unknown command: #{name}")
|
|
118
|
-
|
|
133
|
+
return dispatch_compose(name, values, arguments) if load_config.compose?
|
|
134
|
+
|
|
135
|
+
execute(builder.custom(name, arguments), interactive: tty?(values.fetch('interactive', false)))
|
|
119
136
|
end
|
|
120
137
|
|
|
121
138
|
private
|
|
@@ -134,6 +151,31 @@ module Wip
|
|
|
134
151
|
CommandBuilder.new(wslc: resolver.resolve(load_config.wslc_command), config: load_config, dotenv: dotenv)
|
|
135
152
|
end
|
|
136
153
|
|
|
154
|
+
def compose_bridge = @compose_bridge ||= ComposeBridge.for(load_config)
|
|
155
|
+
|
|
156
|
+
def tty?(requested) = requested && Environment.new.interactive?
|
|
157
|
+
|
|
158
|
+
def exec_target(arguments, interactive:)
|
|
159
|
+
if load_config.compose?
|
|
160
|
+
return compose_bridge.exec(load_config.compose_service, arguments,
|
|
161
|
+
interactive: interactive)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
builder.exec(arguments, interactive: interactive)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def dispatch_compose(name, values, arguments)
|
|
168
|
+
type = values['type'] || 'exec'
|
|
169
|
+
unless type == 'exec'
|
|
170
|
+
raise ConfigError, "commands.#{name}: type '#{type}' is not supported in compose mode " \
|
|
171
|
+
'(use `wslc-compose build`/`up --build` directly)'
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
command = Shellwords.split(values['command'].to_s) + arguments
|
|
175
|
+
interactive = values.fetch('interactive', false)
|
|
176
|
+
execute(exec_target(command, interactive: interactive), interactive: tty?(interactive))
|
|
177
|
+
end
|
|
178
|
+
|
|
137
179
|
def execute(command, interactive: false, exit_on_failure: true)
|
|
138
180
|
runner = CommandRunner.new(debug: debug?)
|
|
139
181
|
code = reporter.step("running: #{CommandDisplay.for_debug(command)}", live: !interactive) do
|
|
@@ -188,7 +230,7 @@ module Wip
|
|
|
188
230
|
|
|
189
231
|
def ensure_container
|
|
190
232
|
container = load_config.defaults['container']
|
|
191
|
-
interactive =
|
|
233
|
+
interactive = tty?(!options[:detach])
|
|
192
234
|
if resource_exists?(builder.find)
|
|
193
235
|
warn "wip: starting existing container '#{container}'"
|
|
194
236
|
execute(builder.start(detach: options[:detach]), interactive: interactive)
|
data/lib/wip/command_resolver.rb
CHANGED
|
@@ -1,20 +1,29 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Wip
|
|
4
|
-
# Locates
|
|
4
|
+
# Locates an executable on the current system, trying a list of candidates.
|
|
5
5
|
class CommandResolver
|
|
6
6
|
CANDIDATES = ['wslc.exe', 'wslc', '/mnt/c/Windows/System32/wslc.exe'].freeze
|
|
7
|
+
DEFAULT_INSTALL_HINT = <<~HINT.chomp
|
|
8
|
+
Install or update the WSL container tooling, then run:
|
|
7
9
|
|
|
8
|
-
|
|
10
|
+
wip doctor
|
|
11
|
+
HINT
|
|
12
|
+
|
|
13
|
+
def initialize(path: ENV.fetch('PATH', ''), executable: nil, candidates: CANDIDATES, label: 'WSLC',
|
|
14
|
+
install_hint: DEFAULT_INSTALL_HINT)
|
|
9
15
|
@path = path
|
|
10
16
|
@executable = executable || method(:executable?)
|
|
17
|
+
@candidates = candidates
|
|
18
|
+
@label = label
|
|
19
|
+
@install_hint = install_hint
|
|
11
20
|
end
|
|
12
21
|
|
|
13
22
|
def resolve(configured = 'auto')
|
|
14
23
|
return configured if configured != 'auto' && @executable.call(configured)
|
|
15
|
-
return raise_not_found if configured != 'auto'
|
|
24
|
+
return raise_not_found([configured]) if configured != 'auto'
|
|
16
25
|
|
|
17
|
-
|
|
26
|
+
@candidates.find { |candidate| @executable.call(candidate) } || raise_not_found(@candidates)
|
|
18
27
|
end
|
|
19
28
|
|
|
20
29
|
private
|
|
@@ -25,16 +34,14 @@ module Wip
|
|
|
25
34
|
@path.split(File::PATH_SEPARATOR).any? { |directory| File.executable?(File.join(directory, command)) }
|
|
26
35
|
end
|
|
27
36
|
|
|
28
|
-
def raise_not_found
|
|
37
|
+
def raise_not_found(attempted)
|
|
29
38
|
raise CommandNotFoundError, <<~MESSAGE
|
|
30
|
-
|
|
39
|
+
#{@label} was not found.
|
|
31
40
|
|
|
32
41
|
Checked:
|
|
33
|
-
#{
|
|
34
|
-
|
|
35
|
-
Install or update the WSL container tooling, then run:
|
|
42
|
+
#{attempted.join("\n ")}
|
|
36
43
|
|
|
37
|
-
|
|
44
|
+
#{@install_hint}
|
|
38
45
|
MESSAGE
|
|
39
46
|
end
|
|
40
47
|
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wip
|
|
4
|
+
# Builds argument arrays for wslc-compose invocations, delegating orchestration
|
|
5
|
+
# to a real compose.yml instead of wip's own dependencies:/network handling.
|
|
6
|
+
class ComposeBridge
|
|
7
|
+
FILENAMES = %w[compose.yml compose.yaml docker-compose.yml docker-compose.yaml].freeze
|
|
8
|
+
# No default candidates: wip doesn't favor any one compose-for-wslc implementation.
|
|
9
|
+
# compose.command must name the one you've installed.
|
|
10
|
+
INSTALL_HINT = <<~HINT.chomp
|
|
11
|
+
wip doesn't bundle or pin a compose-for-wslc implementation — install one and set
|
|
12
|
+
compose.command in wip.yml to its binary name or path, e.g.:
|
|
13
|
+
|
|
14
|
+
https://github.com/bacarndiaye/wslc-compose
|
|
15
|
+
https://github.com/inuyume/wslc-compose
|
|
16
|
+
HINT
|
|
17
|
+
|
|
18
|
+
def self.for(config, resolver: CommandResolver.new(candidates: [], label: 'compose command',
|
|
19
|
+
install_hint: INSTALL_HINT))
|
|
20
|
+
new(compose_command: resolver.resolve(config.compose_command), file: file_path(config),
|
|
21
|
+
project: config.compose_project)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.file_path(config)
|
|
25
|
+
base = Pathname(config.path).dirname
|
|
26
|
+
configured = config.compose_file
|
|
27
|
+
# Relative compose.file is resolved against wip.yml, not the current directory,
|
|
28
|
+
# so `wip` behaves the same from any subdirectory (matching auto-detection below).
|
|
29
|
+
return base.join(configured).expand_path if configured
|
|
30
|
+
|
|
31
|
+
FILENAMES.map { |name| base.join(name) }.find(&:file?) ||
|
|
32
|
+
raise(ConfigError, "compose mode: no compose file found next to #{config.path} " \
|
|
33
|
+
"(looked for #{FILENAMES.join(', ')})")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def initialize(compose_command:, file:, project: nil)
|
|
37
|
+
@compose_command = compose_command
|
|
38
|
+
@file = file
|
|
39
|
+
@project = project
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def up(detach: true)
|
|
43
|
+
command = base.push('up')
|
|
44
|
+
command << '-d' if detach
|
|
45
|
+
command
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def down = base.push('down')
|
|
49
|
+
|
|
50
|
+
def exec(service, arguments, interactive: true)
|
|
51
|
+
command = base.push('exec')
|
|
52
|
+
command << '-T' unless interactive
|
|
53
|
+
command.push(service.to_s).concat(arguments)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def logs(services: [], follow: true)
|
|
57
|
+
command = base.push('logs')
|
|
58
|
+
command << '-f' if follow
|
|
59
|
+
command.concat(services.map(&:to_s))
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def base
|
|
65
|
+
command = [@compose_command, '-f', @file.to_s]
|
|
66
|
+
command.push('-p', @project) if @project
|
|
67
|
+
command
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
data/lib/wip/config.rb
CHANGED
|
@@ -20,6 +20,12 @@ module Wip
|
|
|
20
20
|
def up_command = @raw.dig('up', 'command')
|
|
21
21
|
def dependencies = @raw['dependencies'] || {}
|
|
22
22
|
def network = defaults['network']
|
|
23
|
+
def compose = @raw['compose']
|
|
24
|
+
def compose? = !!compose
|
|
25
|
+
def compose_service = compose && compose['service']
|
|
26
|
+
def compose_file = compose && compose['file']
|
|
27
|
+
def compose_project = compose && compose['project']
|
|
28
|
+
def compose_command = compose && compose['command']
|
|
23
29
|
|
|
24
30
|
def command(name)
|
|
25
31
|
entry = commands[name.to_s]
|
|
@@ -37,7 +43,7 @@ module Wip
|
|
|
37
43
|
|
|
38
44
|
def to_h(redact: true)
|
|
39
45
|
value = { 'version' => 1, 'wslc' => { 'command' => wslc_command }, 'defaults' => defaults,
|
|
40
|
-
'up' => { 'command' => up_command }, 'dependencies' => dependencies,
|
|
46
|
+
'up' => { 'command' => up_command }, 'dependencies' => dependencies, 'compose' => compose,
|
|
41
47
|
'commands' => commands.transform_values { |entry| defaults.merge('type' => 'exec').merge(entry) } }
|
|
42
48
|
redact ? redact_secrets(value) : value
|
|
43
49
|
end
|
|
@@ -50,6 +56,7 @@ module Wip
|
|
|
50
56
|
|
|
51
57
|
validate_commands!
|
|
52
58
|
validate_dependencies!
|
|
59
|
+
validate_compose!
|
|
53
60
|
end
|
|
54
61
|
|
|
55
62
|
def validate_commands!
|
|
@@ -64,6 +71,15 @@ module Wip
|
|
|
64
71
|
dependencies.each { |name, entry| validate_dependency!(name, entry) }
|
|
65
72
|
end
|
|
66
73
|
|
|
74
|
+
def validate_compose!
|
|
75
|
+
return unless @raw.key?('compose')
|
|
76
|
+
raise ConfigError, 'compose must be a mapping' unless compose.is_a?(Hash)
|
|
77
|
+
raise ConfigError, 'compose.service must not be empty' if compose_service.to_s.empty?
|
|
78
|
+
raise ConfigError, 'compose.command must not be empty' if compose_command.to_s.empty?
|
|
79
|
+
raise ConfigError, 'compose is mutually exclusive with dependencies' if dependencies.any?
|
|
80
|
+
raise ConfigError, 'compose is mutually exclusive with defaults.network' if network
|
|
81
|
+
end
|
|
82
|
+
|
|
67
83
|
def validate_command!(name, entry)
|
|
68
84
|
raise ConfigError, "commands.#{name} must be a mapping" unless entry.is_a?(Hash)
|
|
69
85
|
|
data/lib/wip/doctor.rb
CHANGED
|
@@ -7,10 +7,13 @@ module Wip
|
|
|
7
7
|
class Doctor
|
|
8
8
|
Result = Data.define(:level, :message)
|
|
9
9
|
|
|
10
|
-
def initialize(loader:, resolver: CommandResolver.new, environment: Environment.new
|
|
10
|
+
def initialize(loader:, resolver: CommandResolver.new, environment: Environment.new,
|
|
11
|
+
compose_resolver: CommandResolver.new(candidates: [], label: 'compose command',
|
|
12
|
+
install_hint: ComposeBridge::INSTALL_HINT))
|
|
11
13
|
@loader = loader
|
|
12
14
|
@resolver = resolver
|
|
13
15
|
@environment = environment
|
|
16
|
+
@compose_resolver = compose_resolver
|
|
14
17
|
end
|
|
15
18
|
|
|
16
19
|
def call
|
|
@@ -19,8 +22,8 @@ module Wip
|
|
|
19
22
|
results << interop_result unless @environment.windows?
|
|
20
23
|
results << Result.new(:ok, "Architecture: #{@environment.architecture}")
|
|
21
24
|
config = load_config(results)
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
check_wslc(config, results) if config
|
|
26
|
+
check_compose(config, results) if config&.compose?
|
|
24
27
|
results << result(command_available?('git') ? :ok : :warn, 'Git is available',
|
|
25
28
|
'Git is not available to the WSLC build environment')
|
|
26
29
|
results
|
|
@@ -41,7 +44,7 @@ module Wip
|
|
|
41
44
|
|
|
42
45
|
def load_config(results)
|
|
43
46
|
config = @loader.load
|
|
44
|
-
invalid = %w[container image].select { |key| config.defaults[key].to_s.empty? }
|
|
47
|
+
invalid = config.compose? ? [] : %w[container image].select { |key| config.defaults[key].to_s.empty? }
|
|
45
48
|
results << Result.new(invalid.empty? ? :ok : :fail,
|
|
46
49
|
invalid.empty? ? 'Loaded wip.yml' : "Empty defaults: #{invalid.join(', ')}")
|
|
47
50
|
config
|
|
@@ -50,6 +53,11 @@ module Wip
|
|
|
50
53
|
nil
|
|
51
54
|
end
|
|
52
55
|
|
|
56
|
+
def check_wslc(config, results)
|
|
57
|
+
command = resolve(config, results)
|
|
58
|
+
check_version(command, results) if command
|
|
59
|
+
end
|
|
60
|
+
|
|
53
61
|
def resolve(config, results)
|
|
54
62
|
command = @resolver.resolve(config.wslc_command)
|
|
55
63
|
results << Result.new(:ok, "Found #{command}")
|
|
@@ -59,12 +67,35 @@ module Wip
|
|
|
59
67
|
nil
|
|
60
68
|
end
|
|
61
69
|
|
|
62
|
-
def check_version(command, results)
|
|
70
|
+
def check_version(command, results, label: 'WSLC')
|
|
63
71
|
_output, status = Open3.capture2e(command, 'version')
|
|
64
72
|
results << Result.new(status.success? ? :ok : :fail,
|
|
65
|
-
status.success? ?
|
|
73
|
+
status.success? ? "#{label} is available" : "#{label} version failed")
|
|
66
74
|
rescue Errno::ENOENT
|
|
67
|
-
results << Result.new(:fail,
|
|
75
|
+
results << Result.new(:fail, "#{label} version failed")
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def check_compose(config, results)
|
|
79
|
+
command = resolve_compose(config, results)
|
|
80
|
+
check_version(command, results, label: 'compose command') if command
|
|
81
|
+
check_compose_file(config, results)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def resolve_compose(config, results)
|
|
85
|
+
command = @compose_resolver.resolve(config.compose_command)
|
|
86
|
+
results << Result.new(:ok, "Found #{command}")
|
|
87
|
+
command
|
|
88
|
+
rescue CommandNotFoundError => e
|
|
89
|
+
results << Result.new(:fail, e.message)
|
|
90
|
+
nil
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def check_compose_file(config, results)
|
|
94
|
+
path = ComposeBridge.file_path(config)
|
|
95
|
+
results << result(path.file? ? :ok : :fail, "Found compose file #{path}",
|
|
96
|
+
"Compose file not found: #{path}")
|
|
97
|
+
rescue ConfigError => e
|
|
98
|
+
results << Result.new(:fail, e.message)
|
|
68
99
|
end
|
|
69
100
|
|
|
70
101
|
def result(condition, ok_message, fail_message)
|
data/lib/wip/version.rb
CHANGED
data/lib/wip.rb
CHANGED
|
@@ -11,6 +11,7 @@ require_relative 'wip/build_context'
|
|
|
11
11
|
require_relative 'wip/command_resolver'
|
|
12
12
|
require_relative 'wip/error_interpreter'
|
|
13
13
|
require_relative 'wip/command_builder'
|
|
14
|
+
require_relative 'wip/compose_bridge'
|
|
14
15
|
require_relative 'wip/command_display'
|
|
15
16
|
require_relative 'wip/command_runner'
|
|
16
17
|
require_relative 'wip/resource_monitor'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: wslc-wip
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Wip contributors
|
|
@@ -39,6 +39,7 @@ files:
|
|
|
39
39
|
- lib/wip/command_display.rb
|
|
40
40
|
- lib/wip/command_resolver.rb
|
|
41
41
|
- lib/wip/command_runner.rb
|
|
42
|
+
- lib/wip/compose_bridge.rb
|
|
42
43
|
- lib/wip/config.rb
|
|
43
44
|
- lib/wip/config_loader.rb
|
|
44
45
|
- lib/wip/debug_reporter.rb
|