wslc-wip 0.3.0 → 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 +90 -40
- data/lib/wip/cli.rb +77 -12
- data/lib/wip/command_builder.rb +52 -1
- data/lib/wip/command_runner.rb +16 -1
- data/lib/wip/config.rb +30 -2
- data/lib/wip/version.rb +1 -1
- metadata +5 -2
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
|
@@ -1,43 +1,56 @@
|
|
|
1
1
|
# wip
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/slidict/wip/actions/workflows/test.yml)
|
|
4
|
+
[](https://rubygems.org/gems/wslc-wip)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](wslc-wip.gemspec)
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
`wip` is a Ruby-built OSS CLI wrapper that brings a [`dip`](https://github.com/bibendi/dip)-like
|
|
9
|
+
workflow to Microsoft WSLC. It collects a project's container, image, environment variables, and
|
|
10
|
+
commands into a single `wip.yml`, and forwards them to `wslc.exe` / `wslc` as safe argument arrays
|
|
11
|
+
(no shell interpolation).
|
|
6
12
|
|
|
7
|
-
|
|
13
|
+

|
|
8
14
|
|
|
9
|
-
|
|
15
|
+
> **Status:** early release. Expect to track WSLC's own interface as it evolves.
|
|
16
|
+
|
|
17
|
+
## Requirements & installation
|
|
18
|
+
|
|
19
|
+
Ruby 3.2+, WSL2, and Microsoft WSLC.
|
|
10
20
|
|
|
11
21
|
```bash
|
|
12
|
-
gem install wip
|
|
22
|
+
gem install wslc-wip
|
|
13
23
|
```
|
|
14
24
|
|
|
15
|
-
|
|
25
|
+
From source: `bundle install && bundle exec exe/wip version`.
|
|
16
26
|
|
|
17
|
-
##
|
|
27
|
+
## Quick start
|
|
18
28
|
|
|
19
29
|
```bash
|
|
20
|
-
gem install wip
|
|
30
|
+
gem install wslc-wip
|
|
21
31
|
cd my-project
|
|
22
32
|
wip doctor
|
|
23
33
|
wip build
|
|
34
|
+
wip up -d
|
|
24
35
|
wip rails console
|
|
25
36
|
```
|
|
26
37
|
|
|
27
|
-
##
|
|
38
|
+
## Full configuration example
|
|
28
39
|
|
|
29
|
-
|
|
40
|
+
Put a `wip.yml` in your project root. Running from a subdirectory walks up to find it, or pass
|
|
41
|
+
`--config PATH` to point at one explicitly.
|
|
30
42
|
|
|
31
43
|
```yaml
|
|
32
44
|
version: 1
|
|
33
45
|
wslc:
|
|
34
|
-
command: auto # wslc.exe
|
|
46
|
+
command: auto # tries wslc.exe, wslc, then System32; an absolute path also works
|
|
35
47
|
defaults:
|
|
36
48
|
container: app
|
|
37
49
|
image: slidict/slidict:development
|
|
38
50
|
workdir: /app
|
|
39
51
|
interactive: false
|
|
40
52
|
remove: true
|
|
53
|
+
network: app-tier # optional; shared with `dependencies` so containers can resolve each other by name
|
|
41
54
|
env:
|
|
42
55
|
RAILS_ENV: development
|
|
43
56
|
PORT: "3000"
|
|
@@ -46,7 +59,17 @@ defaults:
|
|
|
46
59
|
volumes:
|
|
47
60
|
- ".:/app"
|
|
48
61
|
up:
|
|
49
|
-
command: server # `wip up`
|
|
62
|
+
command: server # command `wip up` passes to the image when it creates the container
|
|
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
|
|
50
73
|
commands:
|
|
51
74
|
rails:
|
|
52
75
|
type: exec
|
|
@@ -74,46 +97,63 @@ commands:
|
|
|
74
97
|
tag: slidict/slidict:development
|
|
75
98
|
```
|
|
76
99
|
|
|
77
|
-
`env`
|
|
100
|
+
`env` values are stringified. `wip config` masks any key matching token, password, secret,
|
|
101
|
+
credential, or auth. Keep real secrets out of the config file and in your runtime environment
|
|
102
|
+
instead.
|
|
103
|
+
|
|
104
|
+
### Dependency containers
|
|
78
105
|
|
|
79
|
-
|
|
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`.
|
|
80
113
|
|
|
81
|
-
|
|
114
|
+
## Commands
|
|
115
|
+
|
|
116
|
+
| Command | Description |
|
|
82
117
|
|---|---|
|
|
83
|
-
| `wip version` | wip
|
|
84
|
-
| `wip doctor` | WSL2
|
|
85
|
-
| `wip config` |
|
|
86
|
-
| `wip build -- --no-cache` | build
|
|
87
|
-
| `wip up [-d]` | `defaults.container`
|
|
88
|
-
| `wip down` | `defaults.container`
|
|
89
|
-
| `wip exec [--no-interactive] COMMAND...` |
|
|
90
|
-
| `wip run [--no-interactive] COMMAND...` | `--rm`
|
|
91
|
-
| `wip shell` |
|
|
92
|
-
| `wip NAME ARGS...` | `commands.NAME
|
|
93
|
-
|
|
94
|
-
TTY
|
|
118
|
+
| `wip version` | wip's version, plus WSLC's if it can be detected |
|
|
119
|
+
| `wip doctor` | Diagnose WSL2, interop, WSLC, config, architecture, and Git |
|
|
120
|
+
| `wip config` | Print the effective configuration (secrets masked) |
|
|
121
|
+
| `wip build -- --no-cache` | Build the image from the `build` definition |
|
|
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` |
|
|
124
|
+
| `wip exec [--no-interactive] COMMAND...` | Run a command in the existing container |
|
|
125
|
+
| `wip run [--no-interactive] COMMAND...` | Run a command in a new `--rm` container |
|
|
126
|
+
| `wip shell` | Open the configured shell, falling back to `bash` then `sh` |
|
|
127
|
+
| `wip NAME ARGS...` | Run `commands.NAME`, appending any extra arguments |
|
|
128
|
+
|
|
129
|
+
TTY allocation is decided by combining the command's config, the CLI option, and whether both
|
|
130
|
+
stdin and stdout are real TTYs. Set `WIP_DEBUG=1` to print the `Shellwords`-joined command before
|
|
131
|
+
running it.
|
|
95
132
|
|
|
96
133
|
## doctor
|
|
97
134
|
|
|
98
|
-
`[OK]
|
|
135
|
+
Each check prints as `[OK]`, `[WARN]`, or `[FAIL]`. Warnings alone exit 0; a WSL2, interop, WSLC,
|
|
136
|
+
or config problem that blocks execution exits 1. Git being unreachable from the real build
|
|
137
|
+
environment is only a warning.
|
|
99
138
|
|
|
100
|
-
##
|
|
139
|
+
## Common errors
|
|
101
140
|
|
|
102
|
-
### WSLC
|
|
141
|
+
### WSLC not found
|
|
103
142
|
|
|
104
|
-
|
|
143
|
+
Install or update the WSL container tooling, then run `wip doctor`. `auto` looks for `wslc.exe`,
|
|
144
|
+
`wslc`, then `/mnt/c/Windows/System32/wslc.exe`, in that order.
|
|
105
145
|
|
|
106
|
-
### Docker Hub
|
|
146
|
+
### Docker Hub authentication
|
|
107
147
|
|
|
108
|
-
`pull access denied`
|
|
148
|
+
When `pull access denied` (or similar) is detected, wip suggests how to log in:
|
|
109
149
|
|
|
110
150
|
```bash
|
|
111
151
|
wslc registry login -u <username> docker.io
|
|
112
152
|
```
|
|
113
153
|
|
|
114
|
-
### CPU
|
|
154
|
+
### CPU architecture mismatch
|
|
115
155
|
|
|
116
|
-
|
|
156
|
+
Check the image and publish a multi-arch (amd64/arm64) image:
|
|
117
157
|
|
|
118
158
|
```bash
|
|
119
159
|
docker buildx imagetools inspect <image>
|
|
@@ -123,10 +163,10 @@ docker buildx build \
|
|
|
123
163
|
--push .
|
|
124
164
|
```
|
|
125
165
|
|
|
126
|
-
##
|
|
166
|
+
## Development
|
|
127
167
|
|
|
128
168
|
```bash
|
|
129
|
-
git clone
|
|
169
|
+
git clone https://github.com/slidict/wip.git
|
|
130
170
|
cd wip
|
|
131
171
|
bundle install
|
|
132
172
|
bundle exec rspec
|
|
@@ -134,11 +174,21 @@ bundle exec rubocop
|
|
|
134
174
|
bundle exec rake
|
|
135
175
|
```
|
|
136
176
|
|
|
137
|
-
|
|
177
|
+
The test suite doesn't need WSLC — the resolution, build, and execution layers are all
|
|
178
|
+
swappable. GitHub Actions runs RSpec and RuboCop on Ruby 3.2, 3.3, 3.4, and 4.0.
|
|
179
|
+
|
|
180
|
+
## Contributing
|
|
181
|
+
|
|
182
|
+
Bug reports and pull requests are welcome on [GitHub](https://github.com/slidict/wip). See
|
|
183
|
+
[CONTRIBUTING.md](CONTRIBUTING.md) for commit conventions, versioning policy, and the PR
|
|
184
|
+
checklist.
|
|
138
185
|
|
|
139
|
-
##
|
|
186
|
+
## Not in the initial release
|
|
140
187
|
|
|
141
|
-
Compose
|
|
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.
|
|
142
192
|
|
|
143
193
|
## License
|
|
144
194
|
|
data/lib/wip/cli.rb
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
require 'thor'
|
|
4
4
|
require 'yaml'
|
|
5
|
+
require 'json'
|
|
6
|
+
require 'stringio'
|
|
5
7
|
|
|
6
8
|
module Wip
|
|
7
9
|
# Thor-based command-line interface for wip.
|
|
@@ -51,40 +53,53 @@ module Wip
|
|
|
51
53
|
execute(builder.build(settings: load_config.command('build') || {}, extra: extra))
|
|
52
54
|
end
|
|
53
55
|
|
|
54
|
-
desc 'up', 'Start the configured container, creating
|
|
56
|
+
desc 'up', 'Start the configured container and its dependencies, creating them if necessary'
|
|
55
57
|
option :detach, type: :boolean, default: false, aliases: '-d'
|
|
56
58
|
def up
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
ensure_network
|
|
60
|
+
load_config.dependencies.each_key { |name| ensure_dependency(name) }
|
|
61
|
+
ensure_container
|
|
59
62
|
end
|
|
60
63
|
|
|
61
|
-
desc 'down', 'Stop and remove the configured container'
|
|
64
|
+
desc 'down', 'Stop and remove the configured container and its dependencies'
|
|
62
65
|
def down
|
|
63
66
|
execute(builder.down, exit_on_failure: false)
|
|
64
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
|
|
65
72
|
end
|
|
66
73
|
|
|
67
74
|
desc 'exec COMMAND...', 'Execute a command in the running container'
|
|
68
75
|
option :interactive, type: :boolean, default: true
|
|
69
76
|
def exec(*command)
|
|
70
|
-
|
|
77
|
+
tty = builder.tty?(options[:interactive])
|
|
78
|
+
execute(builder.exec(command, interactive: options[:interactive]), interactive: tty)
|
|
71
79
|
end
|
|
72
80
|
|
|
73
81
|
desc 'run COMMAND...', 'Run a command in a new container'
|
|
74
82
|
option :interactive, type: :boolean, default: true
|
|
75
83
|
def run_command(*command)
|
|
76
|
-
|
|
84
|
+
tty = builder.tty?(options[:interactive])
|
|
85
|
+
execute(builder.run(command, interactive: options[:interactive]), interactive: tty)
|
|
77
86
|
end
|
|
78
87
|
map 'run' => :run_command
|
|
79
88
|
|
|
80
89
|
desc 'shell', 'Open a shell in the configured container'
|
|
81
90
|
def shell_command
|
|
82
91
|
configured = load_config.command('shell')
|
|
83
|
-
|
|
92
|
+
if configured
|
|
93
|
+
tty = builder.tty?(configured.fetch('interactive', false))
|
|
94
|
+
return execute(builder.custom('shell', []), interactive: tty)
|
|
95
|
+
end
|
|
84
96
|
|
|
97
|
+
tty = builder.tty?(true)
|
|
85
98
|
code = execute(builder.exec(['bash'], settings: { 'interactive' => true }, interactive: true),
|
|
86
|
-
exit_on_failure: false)
|
|
87
|
-
|
|
99
|
+
interactive: tty, exit_on_failure: false)
|
|
100
|
+
return if code.zero?
|
|
101
|
+
|
|
102
|
+
execute(builder.exec(['sh'], settings: { 'interactive' => true }, interactive: true), interactive: tty)
|
|
88
103
|
end
|
|
89
104
|
map 'shell' => :shell_command
|
|
90
105
|
|
|
@@ -92,7 +107,8 @@ module Wip
|
|
|
92
107
|
def dispatch(name = nil, *arguments)
|
|
93
108
|
raise ConfigError, 'A command is required' unless name
|
|
94
109
|
|
|
95
|
-
|
|
110
|
+
values = load_config.command(name) || raise(ConfigError, "Unknown command: #{name}")
|
|
111
|
+
execute(builder.custom(name, arguments), interactive: builder.tty?(values.fetch('interactive', false)))
|
|
96
112
|
end
|
|
97
113
|
|
|
98
114
|
private
|
|
@@ -102,10 +118,59 @@ module Wip
|
|
|
102
118
|
def resolver = CommandResolver.new
|
|
103
119
|
def builder = CommandBuilder.new(wslc: resolver.resolve(load_config.wslc_command), config: load_config)
|
|
104
120
|
|
|
105
|
-
def execute(command, exit_on_failure: true)
|
|
106
|
-
code = CommandRunner.new.run(command)
|
|
121
|
+
def execute(command, interactive: false, exit_on_failure: true)
|
|
122
|
+
code = CommandRunner.new.run(command, interactive: interactive)
|
|
107
123
|
exit(code) if exit_on_failure && !code.zero?
|
|
108
124
|
code
|
|
109
125
|
end
|
|
126
|
+
|
|
127
|
+
def resource_exists?(find_command)
|
|
128
|
+
out = StringIO.new
|
|
129
|
+
code = CommandRunner.new(stdout: out, stderr: StringIO.new).run(find_command)
|
|
130
|
+
code.zero? && !JSON.parse(out.string).empty?
|
|
131
|
+
rescue JSON::ParserError
|
|
132
|
+
false
|
|
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
|
|
110
175
|
end
|
|
111
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'))
|
|
@@ -42,6 +43,11 @@ module Wip
|
|
|
42
43
|
command
|
|
43
44
|
end
|
|
44
45
|
|
|
46
|
+
def find
|
|
47
|
+
container = required(@config.defaults, 'container')
|
|
48
|
+
[@wslc, 'list', '--all', '--filter', "name=#{container}", '--format', 'json']
|
|
49
|
+
end
|
|
50
|
+
|
|
45
51
|
def down
|
|
46
52
|
[@wslc, 'stop', required(@config.defaults, 'container')]
|
|
47
53
|
end
|
|
@@ -50,6 +56,40 @@ module Wip
|
|
|
50
56
|
[@wslc, 'remove', '-f', required(@config.defaults, 'container')]
|
|
51
57
|
end
|
|
52
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
|
+
|
|
53
93
|
def build(settings:, extra: [])
|
|
54
94
|
values = @config.defaults.merge(settings)
|
|
55
95
|
context = values['context'] || '.'
|
|
@@ -68,6 +108,8 @@ module Wip
|
|
|
68
108
|
public_send(type, base + arguments, settings: values, interactive: values.fetch('interactive', false))
|
|
69
109
|
end
|
|
70
110
|
|
|
111
|
+
def tty?(requested) = requested && @environment.interactive?
|
|
112
|
+
|
|
71
113
|
private
|
|
72
114
|
|
|
73
115
|
def options(values, include_container: false, include_publish: true)
|
|
@@ -89,6 +131,15 @@ module Wip
|
|
|
89
131
|
value
|
|
90
132
|
end
|
|
91
133
|
|
|
92
|
-
def
|
|
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
|
|
93
144
|
end
|
|
94
145
|
end
|
data/lib/wip/command_runner.rb
CHANGED
|
@@ -13,8 +13,10 @@ module Wip
|
|
|
13
13
|
@interpreter = interpreter
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
def run(command, env: {})
|
|
16
|
+
def run(command, env: {}, interactive: false)
|
|
17
17
|
@stderr.puts "+ #{Shellwords.join(command)}" if ENV['WIP_DEBUG']
|
|
18
|
+
return run_attached(command, env) if interactive
|
|
19
|
+
|
|
18
20
|
captured = +''
|
|
19
21
|
status = nil
|
|
20
22
|
Open3.popen3(env, *command) do |input, output, error, wait|
|
|
@@ -33,6 +35,19 @@ module Wip
|
|
|
33
35
|
|
|
34
36
|
private
|
|
35
37
|
|
|
38
|
+
# Piping stdin/stdout/stderr (as `run` does above) closes the child's
|
|
39
|
+
# stdin immediately, which breaks anything that reads from the terminal
|
|
40
|
+
# (a shell, `rails console`, ...). Inherit the real file descriptors
|
|
41
|
+
# instead so the child gets a genuine TTY.
|
|
42
|
+
def run_attached(command, env)
|
|
43
|
+
pid = Process.spawn(env, *command)
|
|
44
|
+
_, status = Process.wait2(pid)
|
|
45
|
+
status.exitstatus
|
|
46
|
+
rescue Errno::ENOENT => e
|
|
47
|
+
@stderr.puts e.message
|
|
48
|
+
127
|
|
49
|
+
end
|
|
50
|
+
|
|
36
51
|
def pump(source, destination, captured)
|
|
37
52
|
Thread.new do
|
|
38
53
|
source.each(4096) do |chunk|
|
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
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.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Wip contributors
|
|
@@ -44,11 +44,14 @@ files:
|
|
|
44
44
|
- lib/wip/error_interpreter.rb
|
|
45
45
|
- lib/wip/errors.rb
|
|
46
46
|
- lib/wip/version.rb
|
|
47
|
-
homepage: https://github.com/
|
|
47
|
+
homepage: https://github.com/slidict/wip
|
|
48
48
|
licenses:
|
|
49
49
|
- MIT
|
|
50
50
|
metadata:
|
|
51
51
|
rubygems_mfa_required: 'true'
|
|
52
|
+
source_code_uri: https://github.com/slidict/wip
|
|
53
|
+
changelog_uri: https://github.com/slidict/wip/releases
|
|
54
|
+
bug_tracker_uri: https://github.com/slidict/wip/issues
|
|
52
55
|
rdoc_options: []
|
|
53
56
|
require_paths:
|
|
54
57
|
- lib
|