wslc-wip 0.17.0 → 0.17.2
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/lib/wip/cli.rb +6 -1
- data/lib/wip/command_runner.rb +6 -2
- data/lib/wip/compose_file.rb +5 -2
- data/lib/wip/config.rb +16 -2
- data/lib/wip/config_loader.rb +3 -2
- data/lib/wip/initializer.rb +1 -17
- data/lib/wip/variable_interpolation.rb +60 -0
- 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: ffab59f2ac2df1c7a566825b0ee97eeff0b515396b42f3b95b1067b361d3aa8c
|
|
4
|
+
data.tar.gz: 9500d0b3842aa86f2aaabdc41b89986cad0c66fa0b765b130c6b37679212b985
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 96991898c1e0b423ad14151ee6b7db24bc38c8ea84b7f5213c69d14d83786288faa5b2d1f71c94382611c0f4c569aa939333e66a3e050095b45a1d50b6ef4bb6
|
|
7
|
+
data.tar.gz: 963d62e619fab36835541421ce7f1642a98e063ba916a0c466311d6138ddce53bf6cae3660dbdadd779272defe81898fa153d9be17a661c11be4c93bff8179e5
|
data/lib/wip/cli.rb
CHANGED
|
@@ -70,6 +70,7 @@ module Wip
|
|
|
70
70
|
extra.shift if extra.first == '--'
|
|
71
71
|
settings = load_config.command('build') || {}
|
|
72
72
|
context = settings['context'] || '.'
|
|
73
|
+
warn "wip: staging build context (#{context})"
|
|
73
74
|
BuildContext.new(context).stage do |staged_context|
|
|
74
75
|
execute(builder.build(settings: settings.merge('context' => staged_context), extra: extra))
|
|
75
76
|
end
|
|
@@ -188,7 +189,11 @@ module Wip
|
|
|
188
189
|
|
|
189
190
|
private
|
|
190
191
|
|
|
191
|
-
def loader
|
|
192
|
+
def loader
|
|
193
|
+
env_file = options[:env_file] && Pathname(options[:env_file]).expand_path
|
|
194
|
+
ConfigLoader.new(path: options[:config], env_file: env_file)
|
|
195
|
+
end
|
|
196
|
+
|
|
192
197
|
def load_config = (@load_config ||= loader.load)
|
|
193
198
|
def resolver = CommandResolver.new
|
|
194
199
|
|
data/lib/wip/command_runner.rb
CHANGED
|
@@ -26,8 +26,7 @@ module Wip
|
|
|
26
26
|
threads.each(&:join)
|
|
27
27
|
status = wait.value
|
|
28
28
|
end
|
|
29
|
-
|
|
30
|
-
@stderr.puts("\n#{hint}") if !status.success? && hint
|
|
29
|
+
report_hint(captured) unless status.success?
|
|
31
30
|
status.exitstatus
|
|
32
31
|
rescue Errno::ENOENT => e
|
|
33
32
|
@stderr.puts e.message
|
|
@@ -39,6 +38,11 @@ module Wip
|
|
|
39
38
|
|
|
40
39
|
private
|
|
41
40
|
|
|
41
|
+
def report_hint(captured)
|
|
42
|
+
hint = @interpreter.interpret(captured.force_encoding(Encoding::UTF_8).scrub)
|
|
43
|
+
@stderr.puts("\n#{hint}") if hint
|
|
44
|
+
end
|
|
45
|
+
|
|
42
46
|
# Piping stdin/stdout/stderr (as `run` does above) closes the child's
|
|
43
47
|
# stdin immediately, which breaks anything that reads from the terminal
|
|
44
48
|
# (a shell, `rails console`, ...). Inherit the real file descriptors
|
data/lib/wip/compose_file.rb
CHANGED
|
@@ -30,8 +30,11 @@ module Wip
|
|
|
30
30
|
BUILD_KEYS = %w[context dockerfile args].freeze
|
|
31
31
|
SUPPORTED_CONDITIONS = %w[service_started].freeze
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
# env interpolates compose.yml's ${VAR} references the way `docker compose` does,
|
|
34
|
+
# so values like `user: ${USER_ID}:${GROUP_ID}` reach wslc already substituted
|
|
35
|
+
# instead of literally (see Config#compose_interpolation_env for what's passed in).
|
|
36
|
+
def self.load(path, env: {})
|
|
37
|
+
raw = VariableInterpolation.tree(YAML.safe_load_file(path, aliases: true), env)
|
|
35
38
|
raise ConfigError, "#{path}: services: must be a mapping" unless raw.is_a?(Hash) && raw['services'].is_a?(Hash)
|
|
36
39
|
|
|
37
40
|
new(raw['services'], path: path)
|
data/lib/wip/config.rb
CHANGED
|
@@ -19,9 +19,10 @@ module Wip
|
|
|
19
19
|
MODES = %w[container compose compose-native].freeze
|
|
20
20
|
attr_reader :path
|
|
21
21
|
|
|
22
|
-
def initialize(raw, path = nil)
|
|
22
|
+
def initialize(raw, path = nil, env_file = nil)
|
|
23
23
|
@raw = stringify(raw)
|
|
24
24
|
@path = path
|
|
25
|
+
@env_file = env_file
|
|
25
26
|
validate!
|
|
26
27
|
end
|
|
27
28
|
|
|
@@ -202,7 +203,20 @@ module Wip
|
|
|
202
203
|
def presence(value) = value.to_s.empty? ? nil : value.to_s
|
|
203
204
|
|
|
204
205
|
def parsed_compose_file
|
|
205
|
-
@parsed_compose_file ||= ComposeFile.load(ComposeBridge.file_path(self))
|
|
206
|
+
@parsed_compose_file ||= ComposeFile.load(ComposeBridge.file_path(self), env: compose_interpolation_env)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Compose interpolates ${VAR} references in compose.yml from the shell environment
|
|
210
|
+
# and a project .env file, shell values winning on conflict — mirror that here so
|
|
211
|
+
# `wip` doesn't need the value duplicated into wip.yml just to resolve a compose.yml
|
|
212
|
+
# reference. Uses the same dotenv file --env-file/DotenvLoader would (explicit
|
|
213
|
+
# override, else .env next to wip.yml), so compose interpolation and the env
|
|
214
|
+
# actually passed to containers never see two different .env files.
|
|
215
|
+
def compose_interpolation_env
|
|
216
|
+
env_file = @env_file || (path && Pathname(path).dirname.join('.env'))
|
|
217
|
+
return ENV.to_h unless env_file
|
|
218
|
+
|
|
219
|
+
DotenvLoader.new(env_file).load.merge(ENV.to_h)
|
|
206
220
|
end
|
|
207
221
|
|
|
208
222
|
def default_compose_network = path && Pathname(path).dirname.basename.to_s
|
data/lib/wip/config_loader.rb
CHANGED
|
@@ -8,9 +8,10 @@ module Wip
|
|
|
8
8
|
class ConfigLoader
|
|
9
9
|
FILENAME = 'wip.yml'
|
|
10
10
|
|
|
11
|
-
def initialize(start_dir: Dir.pwd, path: nil)
|
|
11
|
+
def initialize(start_dir: Dir.pwd, path: nil, env_file: nil)
|
|
12
12
|
@start_dir = Pathname(start_dir).expand_path
|
|
13
13
|
@path = path
|
|
14
|
+
@env_file = env_file
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
def find
|
|
@@ -28,7 +29,7 @@ module Wip
|
|
|
28
29
|
raise ConfigError, "wip.yml was not found (searched from #{@start_dir} to the filesystem root)" unless path&.file?
|
|
29
30
|
|
|
30
31
|
data = YAML.safe_load_file(path, permitted_classes: [], aliases: false) || {}
|
|
31
|
-
Config.new(data, path)
|
|
32
|
+
Config.new(data, path, @env_file)
|
|
32
33
|
rescue Psych::Exception => e
|
|
33
34
|
raise ConfigError, "Could not parse #{path}: #{e.message}"
|
|
34
35
|
end
|
data/lib/wip/initializer.rb
CHANGED
|
@@ -122,23 +122,7 @@ module Wip
|
|
|
122
122
|
# optional; exec (default, mirrors into the running container) | run (always a fresh container)
|
|
123
123
|
mode: exec
|
|
124
124
|
|
|
125
|
-
# image/build
|
|
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
|
|
132
|
-
# image: your/image:tag
|
|
133
|
-
|
|
134
|
-
# alternative to image — let wip build a small dedicated mirror image itself
|
|
135
|
-
# build:
|
|
136
|
-
# dockerfile: |
|
|
137
|
-
# FROM alpine:latest
|
|
138
|
-
# RUN apk add --no-cache rsync
|
|
139
|
-
|
|
140
|
-
# optional (default: wip-sync-<container>:latest)
|
|
141
|
-
# tag: wip-sync-app:latest
|
|
125
|
+
# sync.image/sync.build — required for mode: compose and used by sync.mode: run; see README "Source sync"
|
|
142
126
|
YAML
|
|
143
127
|
end
|
|
144
128
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wip
|
|
4
|
+
# Interpolates ${VAR} references the way `docker compose` does when reading
|
|
5
|
+
# compose.yml: ${VAR}, ${VAR:-default}/${VAR-default}, bare $VAR, and $$ as an
|
|
6
|
+
# escaped literal dollar sign. ${VAR:?err}/${VAR:+alt} aren't recognized by
|
|
7
|
+
# PATTERN at all, so — unlike an unset $VAR/${VAR}, which this resolves to an
|
|
8
|
+
# empty string — they pass through completely unchanged, "${...}" and all.
|
|
9
|
+
module VariableInterpolation
|
|
10
|
+
PATTERN = /\$\$|\$\{([A-Za-z_][A-Za-z0-9_]*)((:-|-)([^}]*))?\}|\$([A-Za-z_][A-Za-z0-9_]*)/
|
|
11
|
+
|
|
12
|
+
def self.call(text, env)
|
|
13
|
+
text.gsub(PATTERN) do |matched|
|
|
14
|
+
next '$' if matched == '$$'
|
|
15
|
+
|
|
16
|
+
name, _, operator, default, bare_name = Regexp.last_match.captures
|
|
17
|
+
resolve(env[name || bare_name], operator, default)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Walks an already-parsed YAML structure and interpolates string values only —
|
|
22
|
+
# real Compose interpolates YAML values, never mapping keys (its docs call this
|
|
23
|
+
# out explicitly), and doing this after parsing means a substituted value can't
|
|
24
|
+
# introduce YAML syntax (e.g. a literal "#" turning into a comment marker).
|
|
25
|
+
#
|
|
26
|
+
# `seen` tracks Hash/Array objects on the current recursion path (by identity,
|
|
27
|
+
# not #==) so a self-referential YAML alias — ComposeFile.load parses with
|
|
28
|
+
# aliases: true — raises instead of recursing until SystemStackError. The same
|
|
29
|
+
# object reached again from a *different* branch (an anchor reused, not a
|
|
30
|
+
# cycle) is fine: it's removed from `seen` once its own subtree finishes.
|
|
31
|
+
def self.tree(value, env, seen = {}.compare_by_identity)
|
|
32
|
+
case value
|
|
33
|
+
when String then call(value, env)
|
|
34
|
+
when Hash, Array then walk_container(value, env, seen)
|
|
35
|
+
else value
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.walk_container(value, env, seen)
|
|
40
|
+
raise ConfigError, 'compose.yml contains a self-referential YAML alias' if seen.key?(value)
|
|
41
|
+
|
|
42
|
+
seen[value] = true
|
|
43
|
+
begin
|
|
44
|
+
value.is_a?(Hash) ? value.transform_values { |v| tree(v, env, seen) } : value.map { |v| tree(v, env, seen) }
|
|
45
|
+
ensure
|
|
46
|
+
seen.delete(value)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
private_class_method :walk_container
|
|
50
|
+
|
|
51
|
+
def self.resolve(value, operator, default)
|
|
52
|
+
case operator
|
|
53
|
+
when ':-' then value.nil? || value.empty? ? default.to_s : value
|
|
54
|
+
when '-' then value.nil? ? default.to_s : value
|
|
55
|
+
else value.to_s
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
private_class_method :resolve
|
|
59
|
+
end
|
|
60
|
+
end
|
data/lib/wip/version.rb
CHANGED
data/lib/wip.rb
CHANGED
|
@@ -13,6 +13,7 @@ require_relative 'wip/command_resolver'
|
|
|
13
13
|
require_relative 'wip/error_interpreter'
|
|
14
14
|
require_relative 'wip/command_builder'
|
|
15
15
|
require_relative 'wip/compose_bridge'
|
|
16
|
+
require_relative 'wip/variable_interpolation'
|
|
16
17
|
require_relative 'wip/compose_file'
|
|
17
18
|
require_relative 'wip/initializer'
|
|
18
19
|
require_relative 'wip/command_display'
|
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.
|
|
4
|
+
version: 0.17.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Wip contributors
|
|
@@ -53,6 +53,7 @@ files:
|
|
|
53
53
|
- lib/wip/initializer.rb
|
|
54
54
|
- lib/wip/resource_monitor.rb
|
|
55
55
|
- lib/wip/sync_settings.rb
|
|
56
|
+
- lib/wip/variable_interpolation.rb
|
|
56
57
|
- lib/wip/version.rb
|
|
57
58
|
homepage: https://wslc-wip.slidict.com/
|
|
58
59
|
licenses:
|