wslc-wip 0.3.1 → 0.4.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 +26 -6
- data/lib/wip/cli.rb +52 -13
- data/lib/wip/command_builder.rb +46 -0
- data/lib/wip/config.rb +30 -2
- data/lib/wip/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c1e6e4afaf5e33e69fa5dd6dc35f4fc32695e9916b0ceeac998ae58334fab3d4
|
|
4
|
+
data.tar.gz: 56e438283cad62937ac37132bb49048a1159897de4d3058c122f14272bca9e68
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 29bfbc9430132bf62ae58813e333f64df00d9228ad98386f3374e3a0de897e55605b977d7208bf7ce9622957e0a3acd1d598e25f25a594eed839fc7007deb37d
|
|
7
|
+
data.tar.gz: 8588e1b97851a59936bb42daf6a8d09002e1c3fdba12dafba0a35bfe8a1c9e430742bee1b8f818a677b83cc793f6b96151be1d729f7465632c3c368d9b518cd8
|
data/README.md
CHANGED
|
@@ -50,6 +50,7 @@ defaults:
|
|
|
50
50
|
workdir: /app
|
|
51
51
|
interactive: false
|
|
52
52
|
remove: true
|
|
53
|
+
network: app-tier # optional; shared with `dependencies` so containers can resolve each other by name
|
|
53
54
|
env:
|
|
54
55
|
RAILS_ENV: development
|
|
55
56
|
PORT: "3000"
|
|
@@ -60,6 +61,15 @@ defaults:
|
|
|
60
61
|
up:
|
|
61
62
|
command: server # command `wip up` passes to the image when it creates the container
|
|
62
63
|
# (omit to use the image's default CMD)
|
|
64
|
+
dependencies:
|
|
65
|
+
redis:
|
|
66
|
+
image: redis:latest
|
|
67
|
+
development.mysql:
|
|
68
|
+
image: mysql:8.0
|
|
69
|
+
command: --default-authentication-plugin=mysql_native_password
|
|
70
|
+
env:
|
|
71
|
+
MYSQL_ROOT_PASSWORD: password
|
|
72
|
+
MYSQL_DATABASE: development
|
|
63
73
|
commands:
|
|
64
74
|
rails:
|
|
65
75
|
type: exec
|
|
@@ -91,6 +101,16 @@ commands:
|
|
|
91
101
|
credential, or auth. Keep real secrets out of the config file and in your runtime environment
|
|
92
102
|
instead.
|
|
93
103
|
|
|
104
|
+
### Dependency containers
|
|
105
|
+
|
|
106
|
+
If your app needs sidecar services (a database, Redis, ...), declare them under `dependencies`
|
|
107
|
+
and set `defaults.network`. `wip up` creates the network first (if it doesn't exist), then brings
|
|
108
|
+
up each dependency by name before the main container — so `bin/rails c` (or anything else run
|
|
109
|
+
inside the main container) can reach `development.mysql`/`redis`/etc. by their dependency name,
|
|
110
|
+
the same way Compose's service names resolve. `wip down` tears the main container and all
|
|
111
|
+
dependencies down (the network itself is left in place). Each dependency entry accepts `image`
|
|
112
|
+
(required), `command`, `env`, `ports`, `volumes`, and `workdir` — the same shape as `defaults`.
|
|
113
|
+
|
|
94
114
|
## Commands
|
|
95
115
|
|
|
96
116
|
| Command | Description |
|
|
@@ -99,8 +119,8 @@ instead.
|
|
|
99
119
|
| `wip doctor` | Diagnose WSL2, interop, WSLC, config, architecture, and Git |
|
|
100
120
|
| `wip config` | Print the effective configuration (secrets masked) |
|
|
101
121
|
| `wip build -- --no-cache` | Build the image from the `build` definition |
|
|
102
|
-
| `wip up [-d]` | Start `defaults.container` (creating
|
|
103
|
-
| `wip down` | Stop and remove `defaults.container` |
|
|
122
|
+
| `wip up [-d]` | Start `defaults.container` and its `dependencies` (creating any that are missing, on `defaults.network` if set). `-d` runs the main container in the background |
|
|
123
|
+
| `wip down` | Stop and remove `defaults.container` and its `dependencies` |
|
|
104
124
|
| `wip exec [--no-interactive] COMMAND...` | Run a command in the existing container |
|
|
105
125
|
| `wip run [--no-interactive] COMMAND...` | Run a command in a new `--rm` container |
|
|
106
126
|
| `wip shell` | Open the configured shell, falling back to `bash` then `sh` |
|
|
@@ -165,10 +185,10 @@ checklist.
|
|
|
165
185
|
|
|
166
186
|
## Not in the initial release
|
|
167
187
|
|
|
168
|
-
Compose compatibility,
|
|
169
|
-
|
|
170
|
-
additions: a richer config schema,
|
|
171
|
-
and more detailed diagnostics.
|
|
188
|
+
Full Compose compatibility (multiple networks, `depends_on` ordering/health checks, profiles), a
|
|
189
|
+
resident/daemon process, a GUI, PowerShell-specific tuning, direct registry API/manifest parsing,
|
|
190
|
+
self-update, and plugins are all unimplemented. Likely future additions: a richer config schema,
|
|
191
|
+
lifecycle hooks, platform selection, and more detailed diagnostics.
|
|
172
192
|
|
|
173
193
|
## License
|
|
174
194
|
|
data/lib/wip/cli.rb
CHANGED
|
@@ -53,24 +53,22 @@ module Wip
|
|
|
53
53
|
execute(builder.build(settings: load_config.command('build') || {}, extra: extra))
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
-
desc 'up', 'Start the configured container, creating
|
|
56
|
+
desc 'up', 'Start the configured container and its dependencies, creating them if necessary'
|
|
57
57
|
option :detach, type: :boolean, default: false, aliases: '-d'
|
|
58
58
|
def up
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
warn "wip: starting existing container '#{container}'"
|
|
63
|
-
execute(builder.start(detach: options[:detach]), interactive: interactive)
|
|
64
|
-
else
|
|
65
|
-
warn "wip: container '#{container}' not found, creating it"
|
|
66
|
-
execute(builder.up(detach: options[:detach]), interactive: interactive)
|
|
67
|
-
end
|
|
59
|
+
ensure_network
|
|
60
|
+
load_config.dependencies.each_key { |name| ensure_dependency(name) }
|
|
61
|
+
ensure_container
|
|
68
62
|
end
|
|
69
63
|
|
|
70
|
-
desc 'down', 'Stop and remove the configured container'
|
|
64
|
+
desc 'down', 'Stop and remove the configured container and its dependencies'
|
|
71
65
|
def down
|
|
72
66
|
execute(builder.down, exit_on_failure: false)
|
|
73
67
|
execute(builder.remove, exit_on_failure: false)
|
|
68
|
+
load_config.dependencies.each_key do |name|
|
|
69
|
+
execute(builder.dependency_down(name), exit_on_failure: false)
|
|
70
|
+
execute(builder.dependency_remove(name), exit_on_failure: false)
|
|
71
|
+
end
|
|
74
72
|
end
|
|
75
73
|
|
|
76
74
|
desc 'exec COMMAND...', 'Execute a command in the running container'
|
|
@@ -126,12 +124,53 @@ module Wip
|
|
|
126
124
|
code
|
|
127
125
|
end
|
|
128
126
|
|
|
129
|
-
def
|
|
127
|
+
def resource_exists?(find_command)
|
|
130
128
|
out = StringIO.new
|
|
131
|
-
code = CommandRunner.new(stdout: out, stderr: StringIO.new).run(
|
|
129
|
+
code = CommandRunner.new(stdout: out, stderr: StringIO.new).run(find_command)
|
|
132
130
|
code.zero? && !JSON.parse(out.string).empty?
|
|
133
131
|
rescue JSON::ParserError
|
|
134
132
|
false
|
|
135
133
|
end
|
|
134
|
+
|
|
135
|
+
def ensure_network
|
|
136
|
+
network = load_config.network
|
|
137
|
+
return unless network
|
|
138
|
+
return if network_exists?(network)
|
|
139
|
+
|
|
140
|
+
warn "wip: creating network '#{network}'"
|
|
141
|
+
execute(builder.network_create, exit_on_failure: false)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def network_exists?(network)
|
|
145
|
+
out = StringIO.new
|
|
146
|
+
code = CommandRunner.new(stdout: out, stderr: StringIO.new).run(builder.network_list)
|
|
147
|
+
return false unless code.zero?
|
|
148
|
+
|
|
149
|
+
JSON.parse(out.string).any? { |entry| entry['Name'] == network }
|
|
150
|
+
rescue JSON::ParserError
|
|
151
|
+
false
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def ensure_dependency(name)
|
|
155
|
+
if resource_exists?(builder.dependency_find(name))
|
|
156
|
+
warn "wip: starting existing dependency '#{name}'"
|
|
157
|
+
execute(builder.dependency_start(name))
|
|
158
|
+
else
|
|
159
|
+
warn "wip: dependency '#{name}' not found, creating it"
|
|
160
|
+
execute(builder.dependency_up(name))
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def ensure_container
|
|
165
|
+
container = load_config.defaults['container']
|
|
166
|
+
interactive = builder.tty?(!options[:detach])
|
|
167
|
+
if resource_exists?(builder.find)
|
|
168
|
+
warn "wip: starting existing container '#{container}'"
|
|
169
|
+
execute(builder.start(detach: options[:detach]), interactive: interactive)
|
|
170
|
+
else
|
|
171
|
+
warn "wip: container '#{container}' not found, creating it"
|
|
172
|
+
execute(builder.up(detach: options[:detach]), interactive: interactive)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
136
175
|
end
|
|
137
176
|
end
|
data/lib/wip/command_builder.rb
CHANGED
|
@@ -29,6 +29,7 @@ module Wip
|
|
|
29
29
|
def up(detach: false)
|
|
30
30
|
values = @config.defaults
|
|
31
31
|
command = [@wslc, 'run', '--name', required(values, 'container')]
|
|
32
|
+
command.push('--network', @config.network) if @config.network
|
|
32
33
|
command << '-d' if detach
|
|
33
34
|
command << '-it' if !detach && tty?(true)
|
|
34
35
|
command.concat(options(values)).push(required(values, 'image'))
|
|
@@ -55,6 +56,40 @@ module Wip
|
|
|
55
56
|
[@wslc, 'remove', '-f', required(@config.defaults, 'container')]
|
|
56
57
|
end
|
|
57
58
|
|
|
59
|
+
def network_create
|
|
60
|
+
[@wslc, 'network', 'create', required_network]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def network_list
|
|
64
|
+
[@wslc, 'network', 'list', '--format', 'json']
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def dependency_up(name, detach: true)
|
|
68
|
+
values = dependency_values(name)
|
|
69
|
+
command = [@wslc, 'run', '--name', name.to_s]
|
|
70
|
+
command.push('--network', @config.network) if @config.network
|
|
71
|
+
command << '-d' if detach
|
|
72
|
+
command.concat(options(values)).push(required(values, 'image'))
|
|
73
|
+
command.concat(Shellwords.split(values['command'].to_s)) unless values['command'].to_s.empty?
|
|
74
|
+
command
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def dependency_start(name)
|
|
78
|
+
[@wslc, 'start', name.to_s]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def dependency_find(name)
|
|
82
|
+
[@wslc, 'list', '--all', '--filter', "name=#{name}", '--format', 'json']
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def dependency_down(name)
|
|
86
|
+
[@wslc, 'stop', name.to_s]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def dependency_remove(name)
|
|
90
|
+
[@wslc, 'remove', '-f', name.to_s]
|
|
91
|
+
end
|
|
92
|
+
|
|
58
93
|
def build(settings:, extra: [])
|
|
59
94
|
values = @config.defaults.merge(settings)
|
|
60
95
|
context = values['context'] || '.'
|
|
@@ -95,5 +130,16 @@ module Wip
|
|
|
95
130
|
|
|
96
131
|
value
|
|
97
132
|
end
|
|
133
|
+
|
|
134
|
+
def required_network
|
|
135
|
+
network = @config.network
|
|
136
|
+
raise ConfigError, 'Configured network must not be empty' if network.to_s.empty?
|
|
137
|
+
|
|
138
|
+
network
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def dependency_values(name)
|
|
142
|
+
@config.dependency(name) || raise(ConfigError, "Unknown dependency: #{name}")
|
|
143
|
+
end
|
|
98
144
|
end
|
|
99
145
|
end
|
data/lib/wip/config.rb
CHANGED
|
@@ -18,6 +18,8 @@ module Wip
|
|
|
18
18
|
def commands = @raw['commands'] || {}
|
|
19
19
|
def defaults = DEFAULTS.merge(@raw['defaults'] || {})
|
|
20
20
|
def up_command = @raw.dig('up', 'command')
|
|
21
|
+
def dependencies = @raw['dependencies'] || {}
|
|
22
|
+
def network = defaults['network']
|
|
21
23
|
|
|
22
24
|
def command(name)
|
|
23
25
|
entry = commands[name.to_s]
|
|
@@ -26,9 +28,16 @@ module Wip
|
|
|
26
28
|
defaults.merge('type' => 'exec').merge(entry)
|
|
27
29
|
end
|
|
28
30
|
|
|
31
|
+
def dependency(name)
|
|
32
|
+
entry = dependencies[name.to_s]
|
|
33
|
+
return unless entry
|
|
34
|
+
|
|
35
|
+
{ 'workdir' => nil, 'env' => {}, 'ports' => [], 'volumes' => [] }.merge(entry)
|
|
36
|
+
end
|
|
37
|
+
|
|
29
38
|
def to_h(redact: true)
|
|
30
39
|
value = { 'version' => 1, 'wslc' => { 'command' => wslc_command }, 'defaults' => defaults,
|
|
31
|
-
'up' => { 'command' => up_command },
|
|
40
|
+
'up' => { 'command' => up_command }, 'dependencies' => dependencies,
|
|
32
41
|
'commands' => commands.transform_values { |entry| defaults.merge('type' => 'exec').merge(entry) } }
|
|
33
42
|
redact ? redact_secrets(value) : value
|
|
34
43
|
end
|
|
@@ -37,12 +46,24 @@ module Wip
|
|
|
37
46
|
|
|
38
47
|
def validate!
|
|
39
48
|
raise ConfigError, "Unsupported configuration version: #{@raw['version']}" unless (@raw['version'] || 1) == 1
|
|
40
|
-
raise ConfigError, 'commands must be a mapping' unless commands.is_a?(Hash)
|
|
41
49
|
raise ConfigError, 'up must be a mapping' if @raw.key?('up') && !@raw['up'].is_a?(Hash)
|
|
42
50
|
|
|
51
|
+
validate_commands!
|
|
52
|
+
validate_dependencies!
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def validate_commands!
|
|
56
|
+
raise ConfigError, 'commands must be a mapping' unless commands.is_a?(Hash)
|
|
57
|
+
|
|
43
58
|
commands.each { |name, entry| validate_command!(name, entry) }
|
|
44
59
|
end
|
|
45
60
|
|
|
61
|
+
def validate_dependencies!
|
|
62
|
+
raise ConfigError, 'dependencies must be a mapping' unless dependencies.is_a?(Hash)
|
|
63
|
+
|
|
64
|
+
dependencies.each { |name, entry| validate_dependency!(name, entry) }
|
|
65
|
+
end
|
|
66
|
+
|
|
46
67
|
def validate_command!(name, entry)
|
|
47
68
|
raise ConfigError, "commands.#{name} must be a mapping" unless entry.is_a?(Hash)
|
|
48
69
|
|
|
@@ -52,6 +73,13 @@ module Wip
|
|
|
52
73
|
entry['env']&.transform_values!(&:to_s)
|
|
53
74
|
end
|
|
54
75
|
|
|
76
|
+
def validate_dependency!(name, entry)
|
|
77
|
+
raise ConfigError, "dependencies.#{name} must be a mapping" unless entry.is_a?(Hash)
|
|
78
|
+
raise ConfigError, "dependencies.#{name} must set image" if entry['image'].to_s.empty?
|
|
79
|
+
|
|
80
|
+
entry['env']&.transform_values!(&:to_s)
|
|
81
|
+
end
|
|
82
|
+
|
|
55
83
|
def stringify(object)
|
|
56
84
|
case object
|
|
57
85
|
when Hash then object.to_h { |key, value| [key.to_s, stringify(value)] }
|
data/lib/wip/version.rb
CHANGED