wslc-wip 0.17.6 → 0.17.7

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: adf27a8bafbb2482bfe25b393e3623a39fd0c8d77f92a7c6e6e60286506c8f17
4
- data.tar.gz: 77d180849706ea0915e82c03a7bfcae6d5ee0c581a87db808e3eceacdb0b8210
3
+ metadata.gz: 3f6c715d39600201d79b4181892f546555b4ccb48fb6046815c2dd27d2e4d203
4
+ data.tar.gz: 4aab099d2fa77a875c8037fb865739d9858d59abda28ae4d3bda83d85b2ac7aa
5
5
  SHA512:
6
- metadata.gz: f894b61d72a792604221a0bc41c5bef018cb2257ee7469d958f17f96fd5de57e52f2a1665b74e51bfbbd3e6cea6ba9f684f42a4ab64747462098c4742b7cd350
7
- data.tar.gz: 68201cce4009583ffee442f06858d930a9d618a399553fbf04523281c8012630e0c8bedc1517012b5f9afca9896612d42829206303943b5548118d0a2d960d17
6
+ metadata.gz: 75beec87e7d2b2d4b9a2e71f72a0d126d4e407840a3c9d4fc06a63af80d5fe7d0b4ae68cfbaaf85b9fe749356f9f455953118110cc2c955db5bb152c9eed5dab
7
+ data.tar.gz: 3ac4feb3c65bef872f8b86027ecff32f9f3891a6f3c630b481167e87fb18e7b17a1fed631a23731a9445275e37feb521cb2a0ecf98adf82d7e0a7df9f515536d
data/README.md CHANGED
@@ -370,7 +370,7 @@ valid compose.yml over sections it doesn't need to look at:
370
370
  | `wip version` | wip's version, plus WSLC's if it can be detected |
371
371
  | `wip doctor` | Diagnose WSL2, interop, WSLC, config, architecture, and Git |
372
372
  | `wip config` | Print the effective configuration (secrets masked) |
373
- | `wip build [--no-cache] [-- OPTIONS]` | Build the image from the `build` definition. By default, wip passes the target tag as `--cache-from` so previous layers can be reused; `--no-cache` disables that cache path. |
373
+ | `wip build [--no-cache] [-- OPTIONS]` | Build the image from the `build` definition. `wslc build` reuses matching local layers by default; `--no-cache` disables that. |
374
374
  | `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` |
375
375
  | `wip down` | Stop and remove the primary container and its sidecar `dependencies:` |
376
376
  | `wip exec [--no-interactive] COMMAND...` | Run a command in the existing container |
@@ -136,14 +136,16 @@ module Wip
136
136
  [@wslc, 'remove', '-f', name.to_s]
137
137
  end
138
138
 
139
+ # `wslc build` reuses matching local layers by default (like `docker build`
140
+ # without `--pull`) — it has no `--cache-from` flag to ask for that explicitly,
141
+ # and passing one is a hard error (unrecognized argument).
139
142
  def build(settings:, extra: [])
140
143
  values = primary_values.merge(settings)
141
144
  context = values['context'] || '.'
142
145
  tag = values['tag'] || values['image']
143
146
  raise ConfigError, 'Build image/tag must not be empty' if tag.to_s.empty?
144
147
 
145
- cache_options = extra.include?('--no-cache') ? [] : ['--cache-from', tag]
146
- [@wslc, 'build', '-t', tag, *cache_options, *extra, context]
148
+ [@wslc, 'build', '-t', tag, *extra, context]
147
149
  end
148
150
 
149
151
  def custom(name, arguments)
@@ -15,20 +15,18 @@ 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, :depends_on,
18
+ Service = Struct.new(:image, :build, :command, :env, :ports, :volumes, :workdir, :user, :depends_on, :profiles,
19
19
  keyword_init: true)
20
20
 
21
- SERVICE_KEYS = %w[image build command environment ports volumes working_dir user depends_on].freeze
21
+ SERVICE_KEYS = %w[image build command environment ports volumes working_dir user depends_on profiles].freeze
22
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
23
+ # allocation is decided per invocation (CommandBuilder#tty?), not per service; every
24
+ # service already shares one project network (config.rb); and `wslc run`/`exec` has no
25
+ # restart-policy or capability flag to forward `restart:`/`cap_add:` to.
26
+ IGNORED_SERVICE_KEYS = %w[tty stdin_open networks restart cap_add].freeze
30
27
  BUILD_KEYS = %w[context dockerfile args].freeze
31
28
  SUPPORTED_CONDITIONS = %w[service_started].freeze
29
+ LIST_HINT = 'only supports short syntax ("host:container"), not long-syntax mappings'
32
30
 
33
31
  # env interpolates compose.yml's ${VAR} references the way `docker compose` does,
34
32
  # so values like `user: ${USER_ID}:${GROUP_ID}` reach wslc already substituted
@@ -53,9 +51,13 @@ module Wip
53
51
 
54
52
  def service_names_in_dependency_order = @order.dup
55
53
 
54
+ # True if `name` is gated behind profiles: (wip has no --profile flag); false for an unknown name.
55
+ def profiled?(name) = @services[name.to_s]&.profiles&.any? || false
56
+
56
57
  # name => {context:, dockerfile:, tag:} for every service with a build:.
58
+ # Profile-gated services are skipped — see #startable_order.
57
59
  def build_specs
58
- @order.filter_map do |name|
60
+ startable_order.filter_map do |name|
59
61
  service = @services.fetch(name)
60
62
  next unless service.build
61
63
 
@@ -66,7 +68,7 @@ module Wip
66
68
  # Shaped like Config::DEPENDENCY_DEFAULTS expects: image/command/env/ports/volumes/workdir,
67
69
  # in dependency order so callers iterating sidecars start them before their dependents.
68
70
  def to_dependencies_hash
69
- @order.to_h do |name|
71
+ startable_order.to_h do |name|
70
72
  service = @services.fetch(name)
71
73
  [name, { 'image' => service.build ? image_tag(name, service) : service.image, 'command' => service.command,
72
74
  'env' => service.env, 'ports' => service.ports, 'volumes' => service.volumes,
@@ -76,6 +78,10 @@ module Wip
76
78
 
77
79
  private
78
80
 
81
+ # A profile-gated service (see #profiled?) is never among the ones wip starts
82
+ # on its own, but still participates in #topological_order/depends_on validation.
83
+ def startable_order = @order.reject { |name| profiled?(name) }
84
+
79
85
  # A service naming both `build:` and `image:` builds via the former and tags the
80
86
  # result with the latter (real Compose's own rule for that combination), instead
81
87
  # of the auto-generated tag a build:-only service gets.
@@ -96,7 +102,8 @@ module Wip
96
102
 
97
103
  def normalize_service_lists(name, entry)
98
104
  { ports: normalize_list(name, entry['ports'], 'ports'),
99
- volumes: normalize_list(name, entry['volumes'], 'volumes') }
105
+ volumes: normalize_list(name, entry['volumes'], 'volumes'),
106
+ profiles: normalize_list(name, entry['profiles'], 'profiles', hint: 'must be an array of strings') }
100
107
  end
101
108
 
102
109
  def image_or_build(name, entry)
@@ -129,10 +136,8 @@ module Wip
129
136
 
130
137
  def normalize_build_mapping(name, value)
131
138
  unknown = value.keys.map(&:to_s) - BUILD_KEYS
132
- if unknown.any?
133
- raise ConfigError,
134
- "#{@path}: services.#{name}.build has unsupported key(s): #{unknown.join(', ')}"
135
- end
139
+ raise ConfigError, "#{@path}: services.#{name}.build has unsupported key(s): #{unknown.join(', ')}" \
140
+ unless unknown.empty?
136
141
 
137
142
  context = resolve_context(presence(value['context']) || '.')
138
143
  dockerfile = presence(value['dockerfile'])
@@ -179,13 +184,12 @@ module Wip
179
184
  end
180
185
  end
181
186
 
182
- def normalize_list(name, value, key)
187
+ def normalize_list(name, value, key, hint: LIST_HINT)
183
188
  return [] unless value
184
189
  raise ConfigError, "#{@path}: services.#{name}.#{key} must be an array" unless value.is_a?(Array)
185
- if value.any?(Hash)
186
- raise ConfigError, "#{@path}: services.#{name}.#{key} only supports short syntax (\"host:container\"), " \
187
- 'not long-syntax mappings'
188
- end
190
+
191
+ nested = value.any? { |item| item.is_a?(Hash) || item.is_a?(Array) }
192
+ raise ConfigError, "#{@path}: services.#{name}.#{key} #{hint}" if nested
189
193
 
190
194
  value.map(&:to_s)
191
195
  end
@@ -209,10 +213,18 @@ module Wip
209
213
  dep.to_s
210
214
  end
211
215
 
216
+ # Also rejects a startable (unprofiled) service depending on a profile-gated one —
217
+ # with no profile activation, real Compose treats that as an invalid model, since
218
+ # the dependency would silently never start.
212
219
  def validate_depends_on!
213
220
  @services.each do |name, service|
214
221
  service.depends_on.each do |dep|
215
222
  raise ConfigError, "#{@path}: services.#{name} depends_on unknown service '#{dep}'" unless @services.key?(dep)
223
+ next if service.profiles.any? || !profiled?(dep)
224
+
225
+ raise ConfigError, "#{@path}: services.#{name} depends_on '#{dep}', gated behind profiles: " \
226
+ "(#{@services.fetch(dep).profiles.join(', ')}) wip never activates " \
227
+ '(no --profile flag)'
216
228
  end
217
229
  end
218
230
  end
data/lib/wip/config.rb CHANGED
@@ -204,6 +204,19 @@ module Wip
204
204
 
205
205
  def parsed_compose_file
206
206
  @parsed_compose_file ||= ComposeFile.load(ComposeBridge.file_path(self), env: compose_interpolation_env)
207
+ .tap { |file| validate_compose_service_startable!(file) }
208
+ end
209
+
210
+ # compose.service is what wip always starts by default (container:) — wip has no
211
+ # --profile flag to activate one, so naming a profile-gated service here would
212
+ # otherwise fail later with a misleading "No dependencies.<name> entry" instead
213
+ # of pointing at the real cause.
214
+ def validate_compose_service_startable!(file)
215
+ return unless file.profiled?(compose_service)
216
+
217
+ raise ConfigError, "compose.service '#{compose_service}' is gated behind profiles: in compose.yml, " \
218
+ 'but wip has no --profile flag to activate one — pick a service with no profiles: ' \
219
+ 'or remove profiles: from it'
207
220
  end
208
221
 
209
222
  # Compose interpolates ${VAR} references in compose.yml from the shell environment
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.17.6'
4
+ VERSION = '0.17.7'
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.17.6
4
+ version: 0.17.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wip contributors