wslc-wip 0.15.0 → 0.17.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 +33 -12
- data/lib/wip/cli.rb +1 -0
- data/lib/wip/command_builder.rb +15 -6
- data/lib/wip/compose_file.rb +46 -25
- data/lib/wip/config.rb +1 -1
- data/lib/wip/doctor.rb +6 -0
- data/lib/wip/initializer.rb +7 -1
- 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: 0b6d4bd2dc0f6ceb899efeaebe7378c6b043915b2d640bf4efff7501f5ca4852
|
|
4
|
+
data.tar.gz: e5d6c0ed4b89bacaa374482bc7e815a5b04883b4d817809b6dbb0a91c9801ae3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: aed82d2db66e8036c55986181ce73fc032a1785c3ac3a314aadcc2ef7a8b91f6280dc31cbc209aa97dd25ad5eed536013954ca593b61ed966792004209ebcb2f
|
|
7
|
+
data.tar.gz: 94ee0961eddd267ca8fc8e8f1e198dbd4793801b15c2512539ff55c6cb5223c5ae7e331807030f7eca8d5264b38527db6f4e993ae61fb14d4ef6bdbf26eaf1ad
|
data/README.md
CHANGED
|
@@ -153,6 +153,17 @@ bind-mounted](#slow-boot-when-the-app-directory-is-bind-mounted) for why. A `syn
|
|
|
153
153
|
that problem to `wip`: the source is mounted read-only, the app runs off a named volume, and wip
|
|
154
154
|
mirrors one into the other with `rsync`.
|
|
155
155
|
|
|
156
|
+
`wip up`'s pre-boot mirror always uses a throwaway container (the primary one isn't running yet),
|
|
157
|
+
so `sync.image`/`sync.build` apply there regardless of `sync.mode` — falling back to the primary
|
|
158
|
+
container's own image if neither is set under `mode: container`/`compose-native` (both have a
|
|
159
|
+
`dependencies:` entry to borrow it from); `mode: compose` has no such entry, so one of
|
|
160
|
+
`sync.image`/`sync.build` is required there. Where `sync.mode` actually matters is every mirror
|
|
161
|
+
*after* that: under `sync.mode: exec` (the default for `mode: container`/`compose-native`), `wip
|
|
162
|
+
sync`/`wip sync --watch` run `rsync` inside the already-running primary container instead, so
|
|
163
|
+
*that* image needs `rsync` installed — `sync.image`/`sync.build` are ignored for these. Under
|
|
164
|
+
`sync.mode: run`, every mirror (pre-boot included) uses a throwaway container, so `sync.image`/
|
|
165
|
+
`sync.build` (or the primary image fallback, where available) need `rsync` throughout.
|
|
166
|
+
|
|
156
167
|
```yaml
|
|
157
168
|
sync:
|
|
158
169
|
source: . # host path, relative to wip.yml (default: the wip.yml directory)
|
|
@@ -340,8 +351,11 @@ valid compose.yml over sections it doesn't need to look at:
|
|
|
340
351
|
`compose.yml`, not wherever `wip` is invoked from), `command` (shell or exec form), `environment`
|
|
341
352
|
(mapping or `KEY=VALUE` array — a mapping value must not be null; host environment pass-through
|
|
342
353
|
isn't supported), `ports`/`volumes` (short syntax only — `"host:container"` strings, not
|
|
343
|
-
long-syntax mappings), `working_dir`, `depends_on` (ordering only — a `condition:` other
|
|
344
|
-
`service_started` is rejected, since there's no health-check support).
|
|
354
|
+
long-syntax mappings), `working_dir`, `user`, `depends_on` (ordering only — a `condition:` other
|
|
355
|
+
than `service_started` is rejected, since there's no health-check support). `tty`, `stdin_open`,
|
|
356
|
+
and `networks` are accepted but silently ignored: TTY/stdin allocation is already decided per
|
|
357
|
+
invocation (see "TTY allocation" below), not fixed per service, and every service already shares
|
|
358
|
+
the one project network `compose.project` sets up.
|
|
345
359
|
- `wip logs` takes at most one `SERVICE` (defaulting to `compose.service`) — `wslc logs`, like
|
|
346
360
|
`docker logs`, follows a single container, unlike a real compose tool's multi-service view.
|
|
347
361
|
- `sync:` behaves exactly like `mode: container`'s (falls back to the primary service's own image,
|
|
@@ -481,21 +495,28 @@ checklist.
|
|
|
481
495
|
|
|
482
496
|
## Not in the initial release
|
|
483
497
|
|
|
484
|
-
Full Compose compatibility isn't reimplemented in `wip` itself — `mode: compose-native`
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
[Compose mode](#compose-mode)). A resident/daemon
|
|
488
|
-
direct registry API/manifest parsing, self-update, and
|
|
498
|
+
Full Compose compatibility isn't reimplemented in `wip` itself — `mode: compose-native` parses
|
|
499
|
+
`compose.yml` and drives `wslc` directly, but only covers a minimal subset (see
|
|
500
|
+
[Compose mode (native)](#compose-mode-native)); full parity is otherwise available by delegating
|
|
501
|
+
to a third-party compose-for-`wslc` tool (see [Compose mode](#compose-mode)). A resident/daemon
|
|
502
|
+
process, a GUI, PowerShell-specific tuning, direct registry API/manifest parsing, self-update, and
|
|
503
|
+
plugins are all unimplemented.
|
|
489
504
|
|
|
490
505
|
## Roadmap
|
|
491
506
|
|
|
492
507
|
`wip` already covers most of what [`dip`](https://github.com/bibendi/dip) adds on top of Compose —
|
|
493
508
|
named commands (`commands:`), `run`/`exec` hidden behind a single verb, `.env` passthrough, and
|
|
494
|
-
sidecar services via `dependencies:` + `network:`.
|
|
495
|
-
(
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
509
|
+
sidecar services via `dependencies:` + `network:`. Rather than waiting on `wslc`'s own Compose
|
|
510
|
+
support ([microsoft/WSL#40948](https://github.com/microsoft/WSL/issues/40948)) or an external
|
|
511
|
+
compose-for-`wslc` tool staying complete, `mode: compose-native` (see
|
|
512
|
+
[Compose mode (native)](#compose-mode-native)) parses `compose.yml` itself and drives `wslc`
|
|
513
|
+
directly — no external binary in the loop, `wip run` gets a real `--rm` container, and iterating
|
|
514
|
+
on the parser is faster than chasing a third-party tool's own bugs. It's still a deliberately
|
|
515
|
+
minimal subset (`depends_on` ordering but no health checks, single-container `logs`, no named
|
|
516
|
+
volumes/scaling), and `dependencies:` + `network:` remains the escape hatch for sidecars it
|
|
517
|
+
doesn't model. Fuller Compose semantics beyond that subset stay behind delegating to a separate
|
|
518
|
+
compose-for-`wslc` tool in [compose mode](#compose-mode) — which of those actually work is
|
|
519
|
+
entirely up to the external tool you point `compose.command` at; `wip` only forwards
|
|
499
520
|
`-f FILE [-p PROJECT] up|down|exec|logs`, so treat that list as what Compose offers, not as
|
|
500
521
|
something `wip` guarantees. See that section for what compose mode covers
|
|
501
522
|
and its current limitations (`run`, and `commands:` of type `run`/`build`). What's still planned
|
data/lib/wip/cli.rb
CHANGED
|
@@ -294,6 +294,7 @@ module Wip
|
|
|
294
294
|
def ensure_compose_images
|
|
295
295
|
load_config.compose_build_specs.each_value do |spec|
|
|
296
296
|
extra = spec['dockerfile'] ? ['-f', spec['dockerfile']] : []
|
|
297
|
+
spec['args']&.each { |key, value| extra.push('--build-arg', "#{key}=#{value}") }
|
|
297
298
|
execute(builder.build(settings: { 'context' => spec['context'], 'tag' => spec['tag'] }, extra: extra))
|
|
298
299
|
end
|
|
299
300
|
end
|
data/lib/wip/command_builder.rb
CHANGED
|
@@ -159,17 +159,26 @@ module Wip
|
|
|
159
159
|
private
|
|
160
160
|
|
|
161
161
|
def options(values, include_container: false, include_publish: true, sync: true)
|
|
162
|
-
result =
|
|
163
|
-
result.push('-w', values['workdir']) unless values['workdir'].to_s.empty?
|
|
162
|
+
result = scalar_options(values)
|
|
164
163
|
merged_env(values).each { |key, value| result.push('-e', "#{key}=#{value}") }
|
|
165
|
-
if include_publish
|
|
166
|
-
Array(values['ports']).each { |port| result.push('-p', port.to_s) }
|
|
167
|
-
volume_specs(values, sync: sync).each { |volume| result.push('-v', volume) }
|
|
168
|
-
end
|
|
164
|
+
result.concat(publish_options(values, sync: sync)) if include_publish
|
|
169
165
|
result << required(values, 'container') if include_container
|
|
170
166
|
result
|
|
171
167
|
end
|
|
172
168
|
|
|
169
|
+
def scalar_options(values)
|
|
170
|
+
result = []
|
|
171
|
+
result.push('-w', values['workdir']) unless values['workdir'].to_s.empty?
|
|
172
|
+
result.push('-u', values['user']) unless values['user'].to_s.empty?
|
|
173
|
+
result
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def publish_options(values, sync:)
|
|
177
|
+
result = Array(values['ports']).flat_map { |port| ['-p', port.to_s] }
|
|
178
|
+
volume_specs(values, sync: sync).each { |volume| result.push('-v', volume) }
|
|
179
|
+
result
|
|
180
|
+
end
|
|
181
|
+
|
|
173
182
|
# With sync configured, a live bind mount of the target (`.:/app`) is
|
|
174
183
|
# swapped for the read-only source mount plus the named volume, so the
|
|
175
184
|
# running app only ever touches the volume.
|
data/lib/wip/compose_file.rb
CHANGED
|
@@ -15,10 +15,19 @@ 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, :
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
Service = Struct.new(:image, :build, :command, :env, :ports, :volumes, :workdir, :user, :depends_on,
|
|
19
|
+
keyword_init: true)
|
|
20
|
+
|
|
21
|
+
SERVICE_KEYS = %w[image build command environment ports volumes working_dir user depends_on].freeze
|
|
22
|
+
# Real Compose keys that read as meaningful here but have nothing to map onto: TTY/stdin
|
|
23
|
+
# allocation is already decided per invocation (see CommandBuilder#tty?), not fixed per
|
|
24
|
+
# service; there's no per-service network to join beyond the one project network `network`
|
|
25
|
+
# (config.rb) already puts every service on; `wslc run`/`exec` has no restart-policy or
|
|
26
|
+
# capability flag to forward `restart:`/`cap_add:` to; and `profiles:` gates which services a
|
|
27
|
+
# real `docker compose up` starts by default, which doesn't apply here since wip never starts
|
|
28
|
+
# "every service" — only `compose.service` and whatever it reaches via `depends_on`.
|
|
29
|
+
IGNORED_SERVICE_KEYS = %w[tty stdin_open networks restart cap_add profiles].freeze
|
|
30
|
+
BUILD_KEYS = %w[context dockerfile args].freeze
|
|
22
31
|
SUPPORTED_CONDITIONS = %w[service_started].freeze
|
|
23
32
|
|
|
24
33
|
def self.load(path)
|
|
@@ -44,10 +53,10 @@ module Wip
|
|
|
44
53
|
# name => {context:, dockerfile:, tag:} for every service with a build:.
|
|
45
54
|
def build_specs
|
|
46
55
|
@order.filter_map do |name|
|
|
47
|
-
|
|
48
|
-
next unless build
|
|
56
|
+
service = @services.fetch(name)
|
|
57
|
+
next unless service.build
|
|
49
58
|
|
|
50
|
-
[name, build.merge('tag' => image_tag(name))]
|
|
59
|
+
[name, service.build.merge('tag' => image_tag(name, service))]
|
|
51
60
|
end.to_h
|
|
52
61
|
end
|
|
53
62
|
|
|
@@ -56,35 +65,41 @@ module Wip
|
|
|
56
65
|
def to_dependencies_hash
|
|
57
66
|
@order.to_h do |name|
|
|
58
67
|
service = @services.fetch(name)
|
|
59
|
-
[name, { 'image' => service.build ? image_tag(name) : service.image, 'command' => service.command,
|
|
68
|
+
[name, { 'image' => service.build ? image_tag(name, service) : service.image, 'command' => service.command,
|
|
60
69
|
'env' => service.env, 'ports' => service.ports, 'volumes' => service.volumes,
|
|
61
|
-
'workdir' => service.workdir }]
|
|
70
|
+
'workdir' => service.workdir, 'user' => service.user }]
|
|
62
71
|
end
|
|
63
72
|
end
|
|
64
73
|
|
|
65
74
|
private
|
|
66
75
|
|
|
67
|
-
|
|
76
|
+
# A service naming both `build:` and `image:` builds via the former and tags the
|
|
77
|
+
# result with the latter (real Compose's own rule for that combination), instead
|
|
78
|
+
# of the auto-generated tag a build:-only service gets.
|
|
79
|
+
def image_tag(name, service) = service.image || "wip-compose-#{name}:latest"
|
|
68
80
|
|
|
69
81
|
def build_service(name, entry)
|
|
70
82
|
raise ConfigError, "#{@path}: services.#{name} must be a mapping" unless entry.is_a?(Hash)
|
|
71
83
|
|
|
72
|
-
unknown = entry.keys.map(&:to_s) - SERVICE_KEYS
|
|
84
|
+
unknown = entry.keys.map(&:to_s) - SERVICE_KEYS - IGNORED_SERVICE_KEYS
|
|
73
85
|
raise ConfigError, "#{@path}: services.#{name} has unsupported key(s): #{unknown.join(', ')}" if unknown.any?
|
|
74
86
|
|
|
75
87
|
image, build = image_or_build(name, entry)
|
|
76
88
|
Service.new(image: image, build: build, command: normalize_command(entry['command']),
|
|
77
|
-
env: normalize_env(name, entry['environment']),
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
89
|
+
env: normalize_env(name, entry['environment']), **normalize_service_lists(name, entry),
|
|
90
|
+
workdir: presence(entry['working_dir']), user: presence(entry['user']),
|
|
91
|
+
depends_on: normalize_depends_on(name, entry['depends_on']))
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def normalize_service_lists(name, entry)
|
|
95
|
+
{ ports: normalize_list(name, entry['ports'], 'ports'),
|
|
96
|
+
volumes: normalize_list(name, entry['volumes'], 'volumes') }
|
|
81
97
|
end
|
|
82
98
|
|
|
83
99
|
def image_or_build(name, entry)
|
|
84
100
|
image = presence(entry['image'])
|
|
85
101
|
build = normalize_build(name, entry['build'])
|
|
86
102
|
raise ConfigError, "#{@path}: services.#{name} must set image or build" unless image || build
|
|
87
|
-
raise ConfigError, "#{@path}: services.#{name} must not set both image and build" if image && build
|
|
88
103
|
|
|
89
104
|
[image, build]
|
|
90
105
|
end
|
|
@@ -118,27 +133,33 @@ module Wip
|
|
|
118
133
|
|
|
119
134
|
context = resolve_context(presence(value['context']) || '.')
|
|
120
135
|
dockerfile = presence(value['dockerfile'])
|
|
121
|
-
{ 'context' => context, 'dockerfile' => dockerfile && Pathname(context).join(dockerfile).to_s
|
|
136
|
+
{ 'context' => context, 'dockerfile' => dockerfile && Pathname(context).join(dockerfile).to_s,
|
|
137
|
+
'args' => normalize_kv(name, value['args'], 'build.args') }.compact
|
|
122
138
|
end
|
|
123
139
|
|
|
124
140
|
# build.context is relative to compose.yml's own directory (Compose's own rule),
|
|
125
141
|
# not wherever `wip` happens to be invoked from.
|
|
126
142
|
def resolve_context(raw) = Pathname(@path).expand_path.dirname.join(raw).to_s
|
|
127
143
|
|
|
128
|
-
def normalize_env(name, value)
|
|
144
|
+
def normalize_env(name, value) = normalize_kv(name, value, 'environment')
|
|
145
|
+
|
|
146
|
+
# Shared by `environment:` and `build.args:` — both accept a mapping or a
|
|
147
|
+
# KEY=VALUE array, and neither supports host environment pass-through (a
|
|
148
|
+
# null mapping value, or a bare KEY with no `=`).
|
|
149
|
+
def normalize_kv(name, value, label)
|
|
129
150
|
return {} unless value
|
|
130
151
|
|
|
131
152
|
case value
|
|
132
|
-
when Hash then
|
|
133
|
-
when Array then
|
|
134
|
-
else raise ConfigError, "#{@path}: services.#{name}
|
|
153
|
+
when Hash then normalize_kv_mapping(name, value, label)
|
|
154
|
+
when Array then normalize_kv_array(name, value, label)
|
|
155
|
+
else raise ConfigError, "#{@path}: services.#{name}.#{label} must be a mapping or an array of KEY=VALUE"
|
|
135
156
|
end
|
|
136
157
|
end
|
|
137
158
|
|
|
138
|
-
def
|
|
159
|
+
def normalize_kv_mapping(name, value, label)
|
|
139
160
|
value.to_h do |key, val|
|
|
140
161
|
if val.nil?
|
|
141
|
-
raise ConfigError, "#{@path}: services.#{name}
|
|
162
|
+
raise ConfigError, "#{@path}: services.#{name}.#{label}.#{key} must have a value " \
|
|
142
163
|
'(host environment pass-through is not supported)'
|
|
143
164
|
end
|
|
144
165
|
|
|
@@ -146,10 +167,10 @@ module Wip
|
|
|
146
167
|
end
|
|
147
168
|
end
|
|
148
169
|
|
|
149
|
-
def
|
|
170
|
+
def normalize_kv_array(name, value, label)
|
|
150
171
|
value.to_h do |item|
|
|
151
172
|
key, val = item.to_s.split('=', 2)
|
|
152
|
-
raise ConfigError, "#{@path}: services.#{name}
|
|
173
|
+
raise ConfigError, "#{@path}: services.#{name}.#{label} entries must be KEY=VALUE" unless val
|
|
153
174
|
|
|
154
175
|
[key, val]
|
|
155
176
|
end
|
data/lib/wip/config.rb
CHANGED
|
@@ -7,7 +7,7 @@ module Wip
|
|
|
7
7
|
class Config
|
|
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
|
-
DEPENDENCY_DEFAULTS = { 'workdir' => nil, 'interactive' => false, 'remove' => true,
|
|
10
|
+
DEPENDENCY_DEFAULTS = { 'workdir' => nil, 'user' => nil, 'interactive' => false, 'remove' => true,
|
|
11
11
|
'env' => {}, 'ports' => [], 'volumes' => [] }.freeze
|
|
12
12
|
SECRET_PATTERN = /token|password|secret|credential|auth/i
|
|
13
13
|
# Which orchestration path `up`/`down`/`sync`/etc. take. Explicit rather than
|
data/lib/wip/doctor.rb
CHANGED
|
@@ -138,6 +138,12 @@ module Wip
|
|
|
138
138
|
results << result(File.directory?(sync.source) ? :ok : :fail,
|
|
139
139
|
"Sync source #{sync.source} mirrors into volume #{sync.volume} at #{sync.target}",
|
|
140
140
|
"Sync source not found: #{sync.source}")
|
|
141
|
+
return unless sync.exec? && (sync.image || sync.build)
|
|
142
|
+
|
|
143
|
+
results << Result.new(:warn, 'sync.image/sync.build only cover `wip up`’s one-time pre-boot mirror ' \
|
|
144
|
+
'(the primary container isn’t running yet, so that step always uses a ' \
|
|
145
|
+
'throwaway container) — sync.mode: exec’s `wip sync`/`wip sync --watch` run ' \
|
|
146
|
+
'rsync inside the primary container instead, so its image needs rsync too')
|
|
141
147
|
end
|
|
142
148
|
|
|
143
149
|
def result(condition, ok_message, fail_message)
|
data/lib/wip/initializer.rb
CHANGED
|
@@ -122,7 +122,13 @@ module Wip
|
|
|
122
122
|
# optional; exec (default, mirrors into the running container) | run (always a fresh container)
|
|
123
123
|
mode: exec
|
|
124
124
|
|
|
125
|
-
#
|
|
125
|
+
# image/build below always cover `wip up`'s pre-boot mirror (a throwaway container, since
|
|
126
|
+
# the primary one isn't running yet — falls back to the primary image if neither is set).
|
|
127
|
+
# With mode: exec above (the default here), `wip sync`/`wip sync --watch` afterward run
|
|
128
|
+
# rsync inside the already-running primary container instead, so its own image needs
|
|
129
|
+
# rsync installed too — image/build below aren't read for those.
|
|
130
|
+
|
|
131
|
+
# image the pre-boot (and, under sync.mode: run, every) mirror container runs
|
|
126
132
|
# image: your/image:tag
|
|
127
133
|
|
|
128
134
|
# alternative to image — let wip build a small dedicated mirror image itself
|
data/lib/wip/version.rb
CHANGED