wslc-wip 0.4.3 → 0.5.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 +16 -0
- data/lib/wip/build_context.rb +48 -0
- data/lib/wip/cli.rb +16 -2
- data/lib/wip/command_builder.rb +6 -2
- data/lib/wip/docker_ignore.rb +57 -0
- data/lib/wip/dotenv_loader.rb +39 -0
- data/lib/wip/version.rb +1 -1
- data/lib/wip.rb +3 -0
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '0845dfb67f17323b8a99e6f2f92ef06c17bc03fd1316680cf5f43f2640d73da3'
|
|
4
|
+
data.tar.gz: 6eea9bb520aed0fff5b401b71a20dccdade6b50d9a71403037e59e4e5b83ab2e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1b522d206ba3070244f51bda9d4e144dfe95df63ab01bb92b35d0fe0e84a29035c997d7b061c2ec23286c8bd9f586c69e276b7304abd1c8ad81fe808293599b5
|
|
7
|
+
data.tar.gz: d7fc237820edfcc734f55775e027133c5f9877c24b16f5b1ca6aa98304b35fdea5825a179ff7f64b4d22fe2634d52d11761777284b68a86e76faa56432d33230
|
data/README.md
CHANGED
|
@@ -103,6 +103,22 @@ commands:
|
|
|
103
103
|
credential, or auth. Keep real secrets out of the config file and in your runtime environment
|
|
104
104
|
instead.
|
|
105
105
|
|
|
106
|
+
### .env
|
|
107
|
+
|
|
108
|
+
Like `docker compose`, `wip` automatically loads a `.env` file next to `wip.yml` (one `KEY=VALUE`
|
|
109
|
+
per line; `#` comments, blank lines, `export` prefixes, and quoted values are all supported) and
|
|
110
|
+
passes its keys through as container environment variables on `build`, `up`, `run`, `exec`, and
|
|
111
|
+
custom commands. `.env` only fills in keys that aren't already set by `defaults.env` or a
|
|
112
|
+
command/dependency's own `env` — those always win on conflict. Pass `--env-file PATH` to load a
|
|
113
|
+
different file instead.
|
|
114
|
+
|
|
115
|
+
### .dockerignore
|
|
116
|
+
|
|
117
|
+
`wip build` reads `.dockerignore` from the build context and stages a filtered copy of the
|
|
118
|
+
context (skipping anything it matches) before handing it to `wslc build`, since `wslc` sends the
|
|
119
|
+
context as-is otherwise. If there's no `.dockerignore`, the original context directory is used
|
|
120
|
+
directly with no copying.
|
|
121
|
+
|
|
106
122
|
### Dependency containers
|
|
107
123
|
|
|
108
124
|
If your app needs sidecar services (a database, Redis, ...), declare them under `dependencies`
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'pathname'
|
|
5
|
+
require 'tmpdir'
|
|
6
|
+
|
|
7
|
+
module Wip
|
|
8
|
+
# Stages a build context into a scratch directory with anything matched by
|
|
9
|
+
# .dockerignore left out, since wslc build (unlike `docker build`) sends the
|
|
10
|
+
# context as-is instead of filtering it itself.
|
|
11
|
+
class BuildContext
|
|
12
|
+
def initialize(context, ignore: nil)
|
|
13
|
+
@root = Pathname(context).expand_path
|
|
14
|
+
@ignore = ignore || DockerIgnore.load(@root.join('.dockerignore'))
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def stage
|
|
18
|
+
return yield @root.to_s if @ignore.empty?
|
|
19
|
+
|
|
20
|
+
Dir.mktmpdir('wip-build-context-') do |dir|
|
|
21
|
+
copy_included_files(Pathname(dir))
|
|
22
|
+
yield dir
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def copy_included_files(destination)
|
|
29
|
+
each_included_file do |relative_path|
|
|
30
|
+
target = destination.join(relative_path)
|
|
31
|
+
FileUtils.mkdir_p(target.dirname)
|
|
32
|
+
FileUtils.cp(@root.join(relative_path), target)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def each_included_file
|
|
37
|
+
Dir.glob('**/*', File::FNM_DOTMATCH, base: @root.to_s).each do |entry|
|
|
38
|
+
next if %w[. ..].include?(entry)
|
|
39
|
+
|
|
40
|
+
path = @root.join(entry)
|
|
41
|
+
next unless path.file?
|
|
42
|
+
next if @ignore.ignored?(entry)
|
|
43
|
+
|
|
44
|
+
yield entry
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
data/lib/wip/cli.rb
CHANGED
|
@@ -9,6 +9,7 @@ module Wip
|
|
|
9
9
|
# Thor-based command-line interface for wip.
|
|
10
10
|
class CLI < Thor
|
|
11
11
|
class_option :config, type: :string, desc: 'Path to wip.yml'
|
|
12
|
+
class_option :env_file, type: :string, desc: 'Path to a dotenv file (default: .env next to wip.yml)'
|
|
12
13
|
class_option :debug, type: :boolean, default: false, desc: 'Print progress and timing for each step'
|
|
13
14
|
class_option :debug_log, type: :string, desc: 'Where --debug snapshots go: a file path, or "-" for inline'
|
|
14
15
|
default_task :dispatch
|
|
@@ -52,7 +53,11 @@ module Wip
|
|
|
52
53
|
desc 'build [OPTIONS]', 'Build the configured image'
|
|
53
54
|
def build(*extra)
|
|
54
55
|
extra.shift if extra.first == '--'
|
|
55
|
-
|
|
56
|
+
settings = load_config.command('build') || {}
|
|
57
|
+
context = settings['context'] || load_config.defaults['context'] || '.'
|
|
58
|
+
BuildContext.new(context).stage do |staged_context|
|
|
59
|
+
execute(builder.build(settings: settings.merge('context' => staged_context), extra: extra))
|
|
60
|
+
end
|
|
56
61
|
end
|
|
57
62
|
|
|
58
63
|
desc 'up', 'Start the configured container and its dependencies, creating them if necessary'
|
|
@@ -118,7 +123,16 @@ module Wip
|
|
|
118
123
|
def loader = ConfigLoader.new(path: options[:config])
|
|
119
124
|
def load_config = (@load_config ||= loader.load)
|
|
120
125
|
def resolver = CommandResolver.new
|
|
121
|
-
|
|
126
|
+
|
|
127
|
+
def dotenv_path
|
|
128
|
+
options[:env_file] ? Pathname(options[:env_file]).expand_path : Pathname(load_config.path).dirname.join('.env')
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def dotenv = @dotenv ||= DotenvLoader.new(dotenv_path).load
|
|
132
|
+
|
|
133
|
+
def builder
|
|
134
|
+
CommandBuilder.new(wslc: resolver.resolve(load_config.wslc_command), config: load_config, dotenv: dotenv)
|
|
135
|
+
end
|
|
122
136
|
|
|
123
137
|
def execute(command, interactive: false, exit_on_failure: true)
|
|
124
138
|
runner = CommandRunner.new(debug: debug?)
|
data/lib/wip/command_builder.rb
CHANGED
|
@@ -5,10 +5,11 @@ require 'shellwords'
|
|
|
5
5
|
module Wip
|
|
6
6
|
# Builds the argument arrays for wslc build/exec/run/custom invocations.
|
|
7
7
|
class CommandBuilder
|
|
8
|
-
def initialize(wslc:, config:, environment: Environment.new)
|
|
8
|
+
def initialize(wslc:, config:, environment: Environment.new, dotenv: {})
|
|
9
9
|
@wslc = wslc
|
|
10
10
|
@config = config
|
|
11
11
|
@environment = environment
|
|
12
|
+
@dotenv = dotenv
|
|
12
13
|
end
|
|
13
14
|
|
|
14
15
|
def exec(arguments, settings: {}, interactive: true)
|
|
@@ -115,7 +116,7 @@ module Wip
|
|
|
115
116
|
def options(values, include_container: false, include_publish: true)
|
|
116
117
|
result = []
|
|
117
118
|
result.push('-w', values['workdir']) unless values['workdir'].to_s.empty?
|
|
118
|
-
values
|
|
119
|
+
merged_env(values).each { |key, value| result.push('-e', "#{key}=#{value}") }
|
|
119
120
|
if include_publish
|
|
120
121
|
Array(values['ports']).each { |port| result.push('-p', port.to_s) }
|
|
121
122
|
Array(values['volumes']).each { |volume| result.push('-v', volume.to_s) }
|
|
@@ -124,6 +125,9 @@ module Wip
|
|
|
124
125
|
result
|
|
125
126
|
end
|
|
126
127
|
|
|
128
|
+
# .env supplies defaults; env set in wip.yml (defaults or per-command) wins on conflict.
|
|
129
|
+
def merged_env(values) = @dotenv.merge(values.fetch('env', {}))
|
|
130
|
+
|
|
127
131
|
def required(values, key)
|
|
128
132
|
value = values[key]
|
|
129
133
|
raise ConfigError, "Configured #{key} must not be empty" if value.to_s.empty?
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'pathname'
|
|
4
|
+
|
|
5
|
+
module Wip
|
|
6
|
+
# Parses a .dockerignore file and decides whether a build-context-relative
|
|
7
|
+
# path should be excluded, following the same pattern rules as the Docker
|
|
8
|
+
# CLI (gitignore-like globs, later rules override earlier ones, `!` negates).
|
|
9
|
+
class DockerIgnore
|
|
10
|
+
Rule = Struct.new(:pattern, :negate)
|
|
11
|
+
|
|
12
|
+
FNMATCH_FLAGS = File::FNM_PATHNAME | File::FNM_DOTMATCH | File::FNM_EXTGLOB
|
|
13
|
+
|
|
14
|
+
def self.load(path)
|
|
15
|
+
path = Pathname(path)
|
|
16
|
+
return new([]) unless path.file?
|
|
17
|
+
|
|
18
|
+
new(path.readlines)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def initialize(lines)
|
|
22
|
+
@rules = lines.filter_map { |line| parse(line) }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def empty? = @rules.empty?
|
|
26
|
+
|
|
27
|
+
def ignored?(relative_path)
|
|
28
|
+
@rules.reduce(false) do |ignored, rule|
|
|
29
|
+
matches?(rule.pattern, relative_path) ? !rule.negate : ignored
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def parse(line)
|
|
36
|
+
line = line.strip
|
|
37
|
+
return nil if line.empty? || line.start_with?('#')
|
|
38
|
+
|
|
39
|
+
negate = line.start_with?('!')
|
|
40
|
+
pattern = negate ? line[1..] : line
|
|
41
|
+
pattern = pattern.delete_suffix('/')
|
|
42
|
+
anchored = pattern.start_with?('/')
|
|
43
|
+
pattern = pattern.delete_prefix('/')
|
|
44
|
+
pattern = "**/#{pattern}" if !anchored && !pattern.include?('/')
|
|
45
|
+
Rule.new(pattern, negate)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# A match on a directory component also excludes everything under it,
|
|
49
|
+
# so every prefix of the path (not just the full path) is tested.
|
|
50
|
+
def matches?(pattern, relative_path)
|
|
51
|
+
components = relative_path.split('/')
|
|
52
|
+
(1..components.size).any? do |i|
|
|
53
|
+
File.fnmatch(pattern, components[0...i].join('/'), FNMATCH_FLAGS)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'pathname'
|
|
4
|
+
|
|
5
|
+
module Wip
|
|
6
|
+
# Parses a .env file the way `docker compose` does, so values don't have to
|
|
7
|
+
# be duplicated into wip.yml just to reach the container as -e flags.
|
|
8
|
+
class DotenvLoader
|
|
9
|
+
LINE_PATTERN = /\A(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=(.*)\z/
|
|
10
|
+
|
|
11
|
+
def initialize(path)
|
|
12
|
+
@path = Pathname(path)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def load
|
|
16
|
+
return {} unless @path.file?
|
|
17
|
+
|
|
18
|
+
@path.readlines.each_with_object({}) do |line, env|
|
|
19
|
+
line = line.strip
|
|
20
|
+
next if line.empty? || line.start_with?('#')
|
|
21
|
+
|
|
22
|
+
match = LINE_PATTERN.match(line)
|
|
23
|
+
next unless match
|
|
24
|
+
|
|
25
|
+
env[match[1]] = unquote(match[2])
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def unquote(value)
|
|
32
|
+
value = value.strip
|
|
33
|
+
return value[1..-2] if value.start_with?('"') && value.end_with?('"') && value.length >= 2
|
|
34
|
+
return value[1..-2] if value.start_with?("'") && value.end_with?("'") && value.length >= 2
|
|
35
|
+
|
|
36
|
+
value.split(/\s+#/, 2).first.to_s.strip
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
data/lib/wip/version.rb
CHANGED
data/lib/wip.rb
CHANGED
|
@@ -5,6 +5,9 @@ require_relative 'wip/errors'
|
|
|
5
5
|
require_relative 'wip/config'
|
|
6
6
|
require_relative 'wip/config_loader'
|
|
7
7
|
require_relative 'wip/environment'
|
|
8
|
+
require_relative 'wip/dotenv_loader'
|
|
9
|
+
require_relative 'wip/docker_ignore'
|
|
10
|
+
require_relative 'wip/build_context'
|
|
8
11
|
require_relative 'wip/command_resolver'
|
|
9
12
|
require_relative 'wip/error_interpreter'
|
|
10
13
|
require_relative 'wip/command_builder'
|
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.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Wip contributors
|
|
@@ -33,6 +33,7 @@ files:
|
|
|
33
33
|
- README.md
|
|
34
34
|
- exe/wip
|
|
35
35
|
- lib/wip.rb
|
|
36
|
+
- lib/wip/build_context.rb
|
|
36
37
|
- lib/wip/cli.rb
|
|
37
38
|
- lib/wip/command_builder.rb
|
|
38
39
|
- lib/wip/command_display.rb
|
|
@@ -41,7 +42,9 @@ files:
|
|
|
41
42
|
- lib/wip/config.rb
|
|
42
43
|
- lib/wip/config_loader.rb
|
|
43
44
|
- lib/wip/debug_reporter.rb
|
|
45
|
+
- lib/wip/docker_ignore.rb
|
|
44
46
|
- lib/wip/doctor.rb
|
|
47
|
+
- lib/wip/dotenv_loader.rb
|
|
45
48
|
- lib/wip/environment.rb
|
|
46
49
|
- lib/wip/error_interpreter.rb
|
|
47
50
|
- lib/wip/errors.rb
|