wslc-wip 0.17.1 → 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/compose_file.rb +5 -2
- data/lib/wip/config.rb +16 -2
- data/lib/wip/config_loader.rb +3 -2
- 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/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
|
|
@@ -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:
|