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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b8248f18abf8e946e333de3e2e9b70e72b9b0f8806a3216c97e62bfe83ad53bb
4
- data.tar.gz: 19119fe6787aad4b17cc6ed9e1f1048d664cbcd119414bd4bc4b86e0c26dfb5d
3
+ metadata.gz: c93fe67017509908d6ea81f4a80555ef193991504fb9f19cba7e598bd231635b
4
+ data.tar.gz: a19a5d66f81bfa19be592a778cba3369301f2f1c7f3c1a24a6a111513a2cceca
5
5
  SHA512:
6
- metadata.gz: 91535a4b496bc4cc2e55cc44d7feadbf8709811df0ab660c7d7fe8e6f20feccac18a497804b13bd83a6d2775396e3061c51f64040864e3d6635eeaf22edf9b8e
7
- data.tar.gz: 1151d286900c2c11812c607f123d966862ea41b1ac5ee01a74d957f0626667c616fdabf13f618fdad4a62a1b4c8a26f1c68bf837030f1e8f4508a8f2d0e733b1
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 than
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` only
485
- covers a minimal subset (see [Compose mode (native)](#compose-mode-native)), and full parity is
486
- otherwise available by delegating to a third-party compose-for-`wslc` tool (see
487
- [Compose mode](#compose-mode)). A resident/daemon process, a GUI, PowerShell-specific tuning,
488
- direct registry API/manifest parsing, self-update, and plugins are all unimplemented.
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:`. Fuller Compose semantics
495
- (`depends_on` ordering/health checks, log aggregation, named volumes, profiles, scaling) are now
496
- handled by delegating to a separate compose-for-`wslc` tool in [compose mode](#compose-mode)
497
- rather than being reimplemented in `wip` itself. Which of those actually work is entirely up to
498
- the external tool you point `compose.command` at `wip` only forwards
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
@@ -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.
@@ -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, :depends_on, keyword_init: true)
19
-
20
- SERVICE_KEYS = %w[image build command environment ports volumes working_dir depends_on].freeze
21
- BUILD_KEYS = %w[context dockerfile].freeze
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
- build = @services.fetch(name).build
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
- def image_tag(name) = "wip-compose-#{name}:latest"
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
- ports: normalize_list(name, entry['ports'], 'ports'),
79
- volumes: normalize_list(name, entry['volumes'], 'volumes'),
80
- workdir: presence(entry['working_dir']), depends_on: normalize_depends_on(name, entry['depends_on']))
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 }.compact
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 normalize_env_mapping(name, value)
133
- when Array then normalize_env_array(name, value)
134
- else raise ConfigError, "#{@path}: services.#{name}.environment must be a mapping or an array of KEY=VALUE"
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 normalize_env_mapping(name, value)
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}.environment.#{key} must have a value " \
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 normalize_env_array(name, value)
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}.environment entries must be KEY=VALUE" unless val
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Wip
4
- VERSION = '0.15.0'
4
+ VERSION = '0.16.0'
5
5
  end
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.15.0
4
+ version: 0.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wip contributors