wslc-wip 0.15.0 → 0.16.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 +22 -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/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: c93fe67017509908d6ea81f4a80555ef193991504fb9f19cba7e598bd231635b
|
|
4
|
+
data.tar.gz: a19a5d66f81bfa19be592a778cba3369301f2f1c7f3c1a24a6a111513a2cceca
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a3a6bd4bae10abee5389f7314dbbf7693882e7559412c9207569a7bed16206aceef5ed781b01ab96b94791dfe2a20bb16944b4fad7d2bfcd3e5fa74667576343
|
|
7
|
+
data.tar.gz: 439b070a2f3f00f9396b595dd939c251d1854cf8ba65ee3d6b93c55c512d90d85090e2be2b77cd2d68a8fc0aae149125b235fbba3fbd4d57a943dad7dc512410
|
data/README.md
CHANGED
|
@@ -340,8 +340,11 @@ valid compose.yml over sections it doesn't need to look at:
|
|
|
340
340
|
`compose.yml`, not wherever `wip` is invoked from), `command` (shell or exec form), `environment`
|
|
341
341
|
(mapping or `KEY=VALUE` array — a mapping value must not be null; host environment pass-through
|
|
342
342
|
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).
|
|
343
|
+
long-syntax mappings), `working_dir`, `user`, `depends_on` (ordering only — a `condition:` other
|
|
344
|
+
than `service_started` is rejected, since there's no health-check support). `tty`, `stdin_open`,
|
|
345
|
+
and `networks` are accepted but silently ignored: TTY/stdin allocation is already decided per
|
|
346
|
+
invocation (see "TTY allocation" below), not fixed per service, and every service already shares
|
|
347
|
+
the one project network `compose.project` sets up.
|
|
345
348
|
- `wip logs` takes at most one `SERVICE` (defaulting to `compose.service`) — `wslc logs`, like
|
|
346
349
|
`docker logs`, follows a single container, unlike a real compose tool's multi-service view.
|
|
347
350
|
- `sync:` behaves exactly like `mode: container`'s (falls back to the primary service's own image,
|
|
@@ -481,21 +484,28 @@ checklist.
|
|
|
481
484
|
|
|
482
485
|
## Not in the initial release
|
|
483
486
|
|
|
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
|
|
487
|
+
Full Compose compatibility isn't reimplemented in `wip` itself — `mode: compose-native` parses
|
|
488
|
+
`compose.yml` and drives `wslc` directly, but only covers a minimal subset (see
|
|
489
|
+
[Compose mode (native)](#compose-mode-native)); full parity is otherwise available by delegating
|
|
490
|
+
to a third-party compose-for-`wslc` tool (see [Compose mode](#compose-mode)). A resident/daemon
|
|
491
|
+
process, a GUI, PowerShell-specific tuning, direct registry API/manifest parsing, self-update, and
|
|
492
|
+
plugins are all unimplemented.
|
|
489
493
|
|
|
490
494
|
## Roadmap
|
|
491
495
|
|
|
492
496
|
`wip` already covers most of what [`dip`](https://github.com/bibendi/dip) adds on top of Compose —
|
|
493
497
|
named commands (`commands:`), `run`/`exec` hidden behind a single verb, `.env` passthrough, and
|
|
494
|
-
sidecar services via `dependencies:` + `network:`.
|
|
495
|
-
(
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
498
|
+
sidecar services via `dependencies:` + `network:`. Rather than waiting on `wslc`'s own Compose
|
|
499
|
+
support ([microsoft/WSL#40948](https://github.com/microsoft/WSL/issues/40948)) or an external
|
|
500
|
+
compose-for-`wslc` tool staying complete, `mode: compose-native` (see
|
|
501
|
+
[Compose mode (native)](#compose-mode-native)) parses `compose.yml` itself and drives `wslc`
|
|
502
|
+
directly — no external binary in the loop, `wip run` gets a real `--rm` container, and iterating
|
|
503
|
+
on the parser is faster than chasing a third-party tool's own bugs. It's still a deliberately
|
|
504
|
+
minimal subset (`depends_on` ordering but no health checks, single-container `logs`, no named
|
|
505
|
+
volumes/scaling), and `dependencies:` + `network:` remains the escape hatch for sidecars it
|
|
506
|
+
doesn't model. Fuller Compose semantics beyond that subset stay behind delegating to a separate
|
|
507
|
+
compose-for-`wslc` tool in [compose mode](#compose-mode) — which of those actually work is
|
|
508
|
+
entirely up to the external tool you point `compose.command` at; `wip` only forwards
|
|
499
509
|
`-f FILE [-p PROJECT] up|down|exec|logs`, so treat that list as what Compose offers, not as
|
|
500
510
|
something `wip` guarantees. See that section for what compose mode covers
|
|
501
511
|
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/version.rb
CHANGED