wslc-wip 0.2.0 → 0.3.1
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 +71 -39
- data/lib/wip/cli.rb +36 -10
- data/lib/wip/command_builder.rb +9 -2
- data/lib/wip/command_runner.rb +16 -1
- data/lib/wip/config.rb +3 -0
- 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: d9b186a9b511381675ce6b5c7073292bc3ba8401287807c2f088d722f7cc1d7c
|
|
4
|
+
data.tar.gz: 84a5b9b0ed0c9a3ada3ffe06eb793c12192d36d76b22197c9310776ddbe28c18
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b46b8daa014806f40f361a5a8fb696cfbfa66f7e0652be23de5d328aaa13401248d640126767c39f1d87ca0329e2428e4a3abe7e19fb5d94e139209e43421646
|
|
7
|
+
data.tar.gz: ed2eb9f43b08a29f8ac48d16e2820f7aa088bd8d8eb2e8db5f27c22b145f4e26e082f6b0efda81db9e04d395dc7a90a96a9b211b5d723ebd859fd0605899f28d
|
data/README.md
CHANGED
|
@@ -1,37 +1,49 @@
|
|
|
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
|
|
@@ -45,6 +57,9 @@ defaults:
|
|
|
45
57
|
- "3000:3000"
|
|
46
58
|
volumes:
|
|
47
59
|
- ".:/app"
|
|
60
|
+
up:
|
|
61
|
+
command: server # command `wip up` passes to the image when it creates the container
|
|
62
|
+
# (omit to use the image's default CMD)
|
|
48
63
|
commands:
|
|
49
64
|
rails:
|
|
50
65
|
type: exec
|
|
@@ -72,46 +87,53 @@ commands:
|
|
|
72
87
|
tag: slidict/slidict:development
|
|
73
88
|
```
|
|
74
89
|
|
|
75
|
-
`env`
|
|
90
|
+
`env` values are stringified. `wip config` masks any key matching token, password, secret,
|
|
91
|
+
credential, or auth. Keep real secrets out of the config file and in your runtime environment
|
|
92
|
+
instead.
|
|
76
93
|
|
|
77
|
-
##
|
|
94
|
+
## Commands
|
|
78
95
|
|
|
79
|
-
|
|
|
96
|
+
| Command | Description |
|
|
80
97
|
|---|---|
|
|
81
|
-
| `wip version` | wip
|
|
82
|
-
| `wip doctor` | WSL2
|
|
83
|
-
| `wip config` |
|
|
84
|
-
| `wip build -- --no-cache` | build
|
|
85
|
-
| `wip up [-d]` | `defaults.container`
|
|
86
|
-
| `wip down` | `defaults.container`
|
|
87
|
-
| `wip exec [--no-interactive] COMMAND...` |
|
|
88
|
-
| `wip run [--no-interactive] COMMAND...` | `--rm`
|
|
89
|
-
| `wip shell` |
|
|
90
|
-
| `wip NAME ARGS...` | `commands.NAME
|
|
91
|
-
|
|
92
|
-
TTY
|
|
98
|
+
| `wip version` | wip's version, plus WSLC's if it can be detected |
|
|
99
|
+
| `wip doctor` | Diagnose WSL2, interop, WSLC, config, architecture, and Git |
|
|
100
|
+
| `wip config` | Print the effective configuration (secrets masked) |
|
|
101
|
+
| `wip build -- --no-cache` | Build the image from the `build` definition |
|
|
102
|
+
| `wip up [-d]` | Start `defaults.container` (creating it with `up.command` if missing). `-d` runs it in the background |
|
|
103
|
+
| `wip down` | Stop and remove `defaults.container` |
|
|
104
|
+
| `wip exec [--no-interactive] COMMAND...` | Run a command in the existing container |
|
|
105
|
+
| `wip run [--no-interactive] COMMAND...` | Run a command in a new `--rm` container |
|
|
106
|
+
| `wip shell` | Open the configured shell, falling back to `bash` then `sh` |
|
|
107
|
+
| `wip NAME ARGS...` | Run `commands.NAME`, appending any extra arguments |
|
|
108
|
+
|
|
109
|
+
TTY allocation is decided by combining the command's config, the CLI option, and whether both
|
|
110
|
+
stdin and stdout are real TTYs. Set `WIP_DEBUG=1` to print the `Shellwords`-joined command before
|
|
111
|
+
running it.
|
|
93
112
|
|
|
94
113
|
## doctor
|
|
95
114
|
|
|
96
|
-
`[OK]
|
|
115
|
+
Each check prints as `[OK]`, `[WARN]`, or `[FAIL]`. Warnings alone exit 0; a WSL2, interop, WSLC,
|
|
116
|
+
or config problem that blocks execution exits 1. Git being unreachable from the real build
|
|
117
|
+
environment is only a warning.
|
|
97
118
|
|
|
98
|
-
##
|
|
119
|
+
## Common errors
|
|
99
120
|
|
|
100
|
-
### WSLC
|
|
121
|
+
### WSLC not found
|
|
101
122
|
|
|
102
|
-
|
|
123
|
+
Install or update the WSL container tooling, then run `wip doctor`. `auto` looks for `wslc.exe`,
|
|
124
|
+
`wslc`, then `/mnt/c/Windows/System32/wslc.exe`, in that order.
|
|
103
125
|
|
|
104
|
-
### Docker Hub
|
|
126
|
+
### Docker Hub authentication
|
|
105
127
|
|
|
106
|
-
`pull access denied`
|
|
128
|
+
When `pull access denied` (or similar) is detected, wip suggests how to log in:
|
|
107
129
|
|
|
108
130
|
```bash
|
|
109
131
|
wslc registry login -u <username> docker.io
|
|
110
132
|
```
|
|
111
133
|
|
|
112
|
-
### CPU
|
|
134
|
+
### CPU architecture mismatch
|
|
113
135
|
|
|
114
|
-
|
|
136
|
+
Check the image and publish a multi-arch (amd64/arm64) image:
|
|
115
137
|
|
|
116
138
|
```bash
|
|
117
139
|
docker buildx imagetools inspect <image>
|
|
@@ -121,10 +143,10 @@ docker buildx build \
|
|
|
121
143
|
--push .
|
|
122
144
|
```
|
|
123
145
|
|
|
124
|
-
##
|
|
146
|
+
## Development
|
|
125
147
|
|
|
126
148
|
```bash
|
|
127
|
-
git clone
|
|
149
|
+
git clone https://github.com/slidict/wip.git
|
|
128
150
|
cd wip
|
|
129
151
|
bundle install
|
|
130
152
|
bundle exec rspec
|
|
@@ -132,11 +154,21 @@ bundle exec rubocop
|
|
|
132
154
|
bundle exec rake
|
|
133
155
|
```
|
|
134
156
|
|
|
135
|
-
|
|
157
|
+
The test suite doesn't need WSLC — the resolution, build, and execution layers are all
|
|
158
|
+
swappable. GitHub Actions runs RSpec and RuboCop on Ruby 3.2, 3.3, 3.4, and 4.0.
|
|
159
|
+
|
|
160
|
+
## Contributing
|
|
161
|
+
|
|
162
|
+
Bug reports and pull requests are welcome on [GitHub](https://github.com/slidict/wip). See
|
|
163
|
+
[CONTRIBUTING.md](CONTRIBUTING.md) for commit conventions, versioning policy, and the PR
|
|
164
|
+
checklist.
|
|
136
165
|
|
|
137
|
-
##
|
|
166
|
+
## Not in the initial release
|
|
138
167
|
|
|
139
|
-
Compose
|
|
168
|
+
Compose compatibility, a resident/daemon process, a GUI, PowerShell-specific tuning, direct
|
|
169
|
+
registry API/manifest parsing, self-update, and plugins are all unimplemented. Likely future
|
|
170
|
+
additions: a richer config schema, lifecycle hooks, multi-container support, platform selection,
|
|
171
|
+
and more detailed diagnostics.
|
|
140
172
|
|
|
141
173
|
## License
|
|
142
174
|
|
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.
|
|
@@ -54,8 +56,15 @@ module Wip
|
|
|
54
56
|
desc 'up', 'Start the configured container, creating it if necessary'
|
|
55
57
|
option :detach, type: :boolean, default: false, aliases: '-d'
|
|
56
58
|
def up
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
container = load_config.defaults['container']
|
|
60
|
+
interactive = builder.tty?(!options[:detach])
|
|
61
|
+
if container_exists?
|
|
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
68
|
end
|
|
60
69
|
|
|
61
70
|
desc 'down', 'Stop and remove the configured container'
|
|
@@ -67,24 +76,32 @@ module Wip
|
|
|
67
76
|
desc 'exec COMMAND...', 'Execute a command in the running container'
|
|
68
77
|
option :interactive, type: :boolean, default: true
|
|
69
78
|
def exec(*command)
|
|
70
|
-
|
|
79
|
+
tty = builder.tty?(options[:interactive])
|
|
80
|
+
execute(builder.exec(command, interactive: options[:interactive]), interactive: tty)
|
|
71
81
|
end
|
|
72
82
|
|
|
73
83
|
desc 'run COMMAND...', 'Run a command in a new container'
|
|
74
84
|
option :interactive, type: :boolean, default: true
|
|
75
85
|
def run_command(*command)
|
|
76
|
-
|
|
86
|
+
tty = builder.tty?(options[:interactive])
|
|
87
|
+
execute(builder.run(command, interactive: options[:interactive]), interactive: tty)
|
|
77
88
|
end
|
|
78
89
|
map 'run' => :run_command
|
|
79
90
|
|
|
80
91
|
desc 'shell', 'Open a shell in the configured container'
|
|
81
92
|
def shell_command
|
|
82
93
|
configured = load_config.command('shell')
|
|
83
|
-
|
|
94
|
+
if configured
|
|
95
|
+
tty = builder.tty?(configured.fetch('interactive', false))
|
|
96
|
+
return execute(builder.custom('shell', []), interactive: tty)
|
|
97
|
+
end
|
|
84
98
|
|
|
99
|
+
tty = builder.tty?(true)
|
|
85
100
|
code = execute(builder.exec(['bash'], settings: { 'interactive' => true }, interactive: true),
|
|
86
|
-
exit_on_failure: false)
|
|
87
|
-
|
|
101
|
+
interactive: tty, exit_on_failure: false)
|
|
102
|
+
return if code.zero?
|
|
103
|
+
|
|
104
|
+
execute(builder.exec(['sh'], settings: { 'interactive' => true }, interactive: true), interactive: tty)
|
|
88
105
|
end
|
|
89
106
|
map 'shell' => :shell_command
|
|
90
107
|
|
|
@@ -92,7 +109,8 @@ module Wip
|
|
|
92
109
|
def dispatch(name = nil, *arguments)
|
|
93
110
|
raise ConfigError, 'A command is required' unless name
|
|
94
111
|
|
|
95
|
-
|
|
112
|
+
values = load_config.command(name) || raise(ConfigError, "Unknown command: #{name}")
|
|
113
|
+
execute(builder.custom(name, arguments), interactive: builder.tty?(values.fetch('interactive', false)))
|
|
96
114
|
end
|
|
97
115
|
|
|
98
116
|
private
|
|
@@ -102,10 +120,18 @@ module Wip
|
|
|
102
120
|
def resolver = CommandResolver.new
|
|
103
121
|
def builder = CommandBuilder.new(wslc: resolver.resolve(load_config.wslc_command), config: load_config)
|
|
104
122
|
|
|
105
|
-
def execute(command, exit_on_failure: true)
|
|
106
|
-
code = CommandRunner.new.run(command)
|
|
123
|
+
def execute(command, interactive: false, exit_on_failure: true)
|
|
124
|
+
code = CommandRunner.new.run(command, interactive: interactive)
|
|
107
125
|
exit(code) if exit_on_failure && !code.zero?
|
|
108
126
|
code
|
|
109
127
|
end
|
|
128
|
+
|
|
129
|
+
def container_exists?
|
|
130
|
+
out = StringIO.new
|
|
131
|
+
code = CommandRunner.new(stdout: out, stderr: StringIO.new).run(builder.find)
|
|
132
|
+
code.zero? && !JSON.parse(out.string).empty?
|
|
133
|
+
rescue JSON::ParserError
|
|
134
|
+
false
|
|
135
|
+
end
|
|
110
136
|
end
|
|
111
137
|
end
|
data/lib/wip/command_builder.rb
CHANGED
|
@@ -32,6 +32,8 @@ module Wip
|
|
|
32
32
|
command << '-d' if detach
|
|
33
33
|
command << '-it' if !detach && tty?(true)
|
|
34
34
|
command.concat(options(values)).push(required(values, 'image'))
|
|
35
|
+
command.concat(Shellwords.split(@config.up_command.to_s)) if @config.up_command
|
|
36
|
+
command
|
|
35
37
|
end
|
|
36
38
|
|
|
37
39
|
def start(detach: false)
|
|
@@ -40,6 +42,11 @@ module Wip
|
|
|
40
42
|
command
|
|
41
43
|
end
|
|
42
44
|
|
|
45
|
+
def find
|
|
46
|
+
container = required(@config.defaults, 'container')
|
|
47
|
+
[@wslc, 'list', '--all', '--filter', "name=#{container}", '--format', 'json']
|
|
48
|
+
end
|
|
49
|
+
|
|
43
50
|
def down
|
|
44
51
|
[@wslc, 'stop', required(@config.defaults, 'container')]
|
|
45
52
|
end
|
|
@@ -66,6 +73,8 @@ module Wip
|
|
|
66
73
|
public_send(type, base + arguments, settings: values, interactive: values.fetch('interactive', false))
|
|
67
74
|
end
|
|
68
75
|
|
|
76
|
+
def tty?(requested) = requested && @environment.interactive?
|
|
77
|
+
|
|
69
78
|
private
|
|
70
79
|
|
|
71
80
|
def options(values, include_container: false, include_publish: true)
|
|
@@ -86,7 +95,5 @@ module Wip
|
|
|
86
95
|
|
|
87
96
|
value
|
|
88
97
|
end
|
|
89
|
-
|
|
90
|
-
def tty?(requested) = requested && @environment.interactive?
|
|
91
98
|
end
|
|
92
99
|
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
|
@@ -17,6 +17,7 @@ module Wip
|
|
|
17
17
|
def wslc_command = @raw.dig('wslc', 'command') || 'auto'
|
|
18
18
|
def commands = @raw['commands'] || {}
|
|
19
19
|
def defaults = DEFAULTS.merge(@raw['defaults'] || {})
|
|
20
|
+
def up_command = @raw.dig('up', 'command')
|
|
20
21
|
|
|
21
22
|
def command(name)
|
|
22
23
|
entry = commands[name.to_s]
|
|
@@ -27,6 +28,7 @@ module Wip
|
|
|
27
28
|
|
|
28
29
|
def to_h(redact: true)
|
|
29
30
|
value = { 'version' => 1, 'wslc' => { 'command' => wslc_command }, 'defaults' => defaults,
|
|
31
|
+
'up' => { 'command' => up_command },
|
|
30
32
|
'commands' => commands.transform_values { |entry| defaults.merge('type' => 'exec').merge(entry) } }
|
|
31
33
|
redact ? redact_secrets(value) : value
|
|
32
34
|
end
|
|
@@ -36,6 +38,7 @@ module Wip
|
|
|
36
38
|
def validate!
|
|
37
39
|
raise ConfigError, "Unsupported configuration version: #{@raw['version']}" unless (@raw['version'] || 1) == 1
|
|
38
40
|
raise ConfigError, 'commands must be a mapping' unless commands.is_a?(Hash)
|
|
41
|
+
raise ConfigError, 'up must be a mapping' if @raw.key?('up') && !@raw['up'].is_a?(Hash)
|
|
39
42
|
|
|
40
43
|
commands.each { |name, entry| validate_command!(name, entry) }
|
|
41
44
|
end
|
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.3.1
|
|
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
|