wslc-wip 0.4.1 → 0.4.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/README.md +28 -2
- data/lib/wip/cli.rb +19 -9
- data/lib/wip/command_display.rb +19 -0
- data/lib/wip/command_runner.rb +4 -3
- data/lib/wip/debug_reporter.rb +27 -0
- data/lib/wip/resource_monitor.rb +54 -0
- data/lib/wip/version.rb +1 -1
- data/lib/wip.rb +3 -0
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: da7bb5fa632921c307adeeea727e792d38e688204393dfd3ee2c7759e40791b4
|
|
4
|
+
data.tar.gz: 2e2a277a4ab2dcf0ce540778b6263a142553430452905924f67158dc13ffed54
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f71156a19e193d0402f2fa4dc501aca0f32b27d63807c20b7a1a8dcddf1932f091af8ec12a0310aac36084a6c28034d843100abbf4adc3ac9c081d4e9304c18c
|
|
7
|
+
data.tar.gz: 538f842fd08441c9ec1603fc147d7c5ce7a5c6f2f2ce22edd0673b12709da19a6c23d3e7f1231a73d69d675337f09a17bc312aa07792308df414f1130e11b2a5
|
data/README.md
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
[](wslc-wip.gemspec)
|
|
7
7
|
|
|
8
|
+
Homepage: https://wslc-wip.slidict.com/
|
|
9
|
+
|
|
8
10
|
`wip` is a Ruby-built OSS CLI wrapper that brings a [`dip`](https://github.com/bibendi/dip)-like
|
|
9
11
|
workflow to Microsoft WSLC. It collects a project's container, image, environment variables, and
|
|
10
12
|
commands into a single `wip.yml`, and forwards them to `wslc.exe` / `wslc` as safe argument arrays
|
|
@@ -127,8 +129,32 @@ dependencies down (the network itself is left in place). Each dependency entry a
|
|
|
127
129
|
| `wip NAME ARGS...` | Run `commands.NAME`, appending any extra arguments |
|
|
128
130
|
|
|
129
131
|
TTY allocation is decided by combining the command's config, the CLI option, and whether both
|
|
130
|
-
stdin and stdout are real TTYs.
|
|
131
|
-
|
|
132
|
+
stdin and stdout are real TTYs.
|
|
133
|
+
|
|
134
|
+
Pass `--debug` (or set `WIP_DEBUG=1`) to see where time is going: wip prints each step it takes —
|
|
135
|
+
checking for an existing network/container/dependency, and running the resolved `wslc`/`docker`
|
|
136
|
+
command — along with how long that step took, e.g.:
|
|
137
|
+
|
|
138
|
+
```console
|
|
139
|
+
$ wip rails c --debug
|
|
140
|
+
wip: [debug] running: wslc.exe exec -it -w /app app bin/rails c
|
|
141
|
+
+ wslc.exe exec -it -w /app app bin/rails c
|
|
142
|
+
...
|
|
143
|
+
wip: [debug] done in 4.32s: running: wslc.exe exec -it -w /app app bin/rails c
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
For long-running interactive commands (like `rails c`), the "done" line only prints after you
|
|
147
|
+
exit, but the timestamp of the `+ ...` line tells you when wip finished its own setup and handed
|
|
148
|
+
off to `wslc`/`docker` — useful for telling wip-side overhead apart from time spent booting inside
|
|
149
|
+
the container.
|
|
150
|
+
|
|
151
|
+
While a step is still running, wip also prints a host resource snapshot (load average, memory
|
|
152
|
+
used, and the top CPU-consuming processes) every 5 seconds, so a hang is visible even before the
|
|
153
|
+
command has produced any output of its own:
|
|
154
|
+
|
|
155
|
+
```console
|
|
156
|
+
wip: [debug] still running (load 3.42 2.10 1.05 | mem 6.1G/15.6G | top: wslc.exe(8842) cpu 61.0%/mem 3.2%, ...): running: wslc.exe exec -it -w /app app bin/rails c
|
|
157
|
+
```
|
|
132
158
|
|
|
133
159
|
## doctor
|
|
134
160
|
|
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 :debug, type: :boolean, default: false, desc: 'Print progress and timing for each step'
|
|
12
13
|
default_task :dispatch
|
|
13
14
|
|
|
14
15
|
def self.exit_on_failure? = true
|
|
@@ -119,19 +120,31 @@ module Wip
|
|
|
119
120
|
def builder = CommandBuilder.new(wslc: resolver.resolve(load_config.wslc_command), config: load_config)
|
|
120
121
|
|
|
121
122
|
def execute(command, interactive: false, exit_on_failure: true)
|
|
122
|
-
|
|
123
|
+
runner = CommandRunner.new(debug: debug?)
|
|
124
|
+
code = reporter.step("running: #{CommandDisplay.for_debug(command)}") do
|
|
125
|
+
runner.run(command, interactive: interactive)
|
|
126
|
+
end
|
|
123
127
|
exit(code) if exit_on_failure && !code.zero?
|
|
124
128
|
code
|
|
125
129
|
end
|
|
126
130
|
|
|
127
131
|
def resource_exists?(find_command)
|
|
128
|
-
|
|
129
|
-
code
|
|
130
|
-
code.zero? && !JSON.parse(out.string).empty?
|
|
132
|
+
code, output = probe(find_command)
|
|
133
|
+
code.zero? && !JSON.parse(output).empty?
|
|
131
134
|
rescue JSON::ParserError
|
|
132
135
|
false
|
|
133
136
|
end
|
|
134
137
|
|
|
138
|
+
def probe(command)
|
|
139
|
+
out = StringIO.new
|
|
140
|
+
runner = CommandRunner.new(stdout: out, stderr: StringIO.new, debug: debug?)
|
|
141
|
+
code = reporter.step("checking: #{CommandDisplay.for_debug(command)}") { runner.run(command) }
|
|
142
|
+
[code, out.string]
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def debug? = options[:debug] || !ENV['WIP_DEBUG'].to_s.empty?
|
|
146
|
+
def reporter = @reporter ||= DebugReporter.new(enabled: debug?)
|
|
147
|
+
|
|
135
148
|
def ensure_network
|
|
136
149
|
network = load_config.network
|
|
137
150
|
return unless network
|
|
@@ -142,11 +155,8 @@ module Wip
|
|
|
142
155
|
end
|
|
143
156
|
|
|
144
157
|
def network_exists?(network)
|
|
145
|
-
|
|
146
|
-
code
|
|
147
|
-
return false unless code.zero?
|
|
148
|
-
|
|
149
|
-
JSON.parse(out.string).any? { |entry| entry['Name'] == network }
|
|
158
|
+
code, output = probe(builder.network_list)
|
|
159
|
+
code.zero? && JSON.parse(output).any? { |entry| entry['Name'] == network }
|
|
150
160
|
rescue JSON::ParserError
|
|
151
161
|
false
|
|
152
162
|
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'shellwords'
|
|
4
|
+
|
|
5
|
+
module Wip
|
|
6
|
+
# Renders a command array for debug output, masking `-e KEY=value` env values
|
|
7
|
+
# so secrets from wip.yml never reach logs.
|
|
8
|
+
module CommandDisplay
|
|
9
|
+
def self.for_debug(command)
|
|
10
|
+
masked = command.each_cons(2).with_index.each_with_object(command.dup) do |((flag, pair), index), result|
|
|
11
|
+
next unless flag == '-e'
|
|
12
|
+
|
|
13
|
+
key, = pair.split('=', 2)
|
|
14
|
+
result[index + 1] = "#{key}=***"
|
|
15
|
+
end
|
|
16
|
+
Shellwords.join(masked)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/wip/command_runner.rb
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'open3'
|
|
4
|
-
require 'shellwords'
|
|
5
4
|
|
|
6
5
|
module Wip
|
|
7
6
|
# Executes a built command, pumping its I/O and returning the exit status.
|
|
8
7
|
class CommandRunner
|
|
9
|
-
def initialize(stdin: $stdin, stdout: $stdout, stderr: $stderr, interpreter: ErrorInterpreter.new
|
|
8
|
+
def initialize(stdin: $stdin, stdout: $stdout, stderr: $stderr, interpreter: ErrorInterpreter.new,
|
|
9
|
+
debug: !ENV['WIP_DEBUG'].to_s.empty?)
|
|
10
10
|
@stdin = stdin
|
|
11
11
|
@stdout = stdout
|
|
12
12
|
@stderr = stderr
|
|
13
13
|
@interpreter = interpreter
|
|
14
|
+
@debug = debug
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
def run(command, env: {}, interactive: false)
|
|
17
|
-
@stderr.puts "+ #{
|
|
18
|
+
@stderr.puts "+ #{CommandDisplay.for_debug(command)}" if @debug
|
|
18
19
|
return run_attached(command, env) if interactive
|
|
19
20
|
|
|
20
21
|
captured = +''
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wip
|
|
4
|
+
# Prints each step wip takes and how long it took, when debug mode is enabled.
|
|
5
|
+
class DebugReporter
|
|
6
|
+
def initialize(enabled:, out: $stderr)
|
|
7
|
+
@enabled = enabled
|
|
8
|
+
@out = out
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def step(label)
|
|
12
|
+
return yield unless @enabled
|
|
13
|
+
|
|
14
|
+
started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
15
|
+
@out.puts "wip: [debug] #{label}"
|
|
16
|
+
monitor = ResourceMonitor.new(out: @out)
|
|
17
|
+
monitor.start(label)
|
|
18
|
+
begin
|
|
19
|
+
yield
|
|
20
|
+
ensure
|
|
21
|
+
monitor.stop
|
|
22
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started
|
|
23
|
+
@out.puts format('wip: [debug] done in %<elapsed>.2fs: %<label>s', elapsed: elapsed, label: label)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wip
|
|
4
|
+
# Periodically prints host CPU/memory/process info while a debug step is still
|
|
5
|
+
# running, so a hung or slow step is visible even before it produces output.
|
|
6
|
+
class ResourceMonitor
|
|
7
|
+
def initialize(interval: 5, out: $stderr)
|
|
8
|
+
@interval = interval
|
|
9
|
+
@out = out
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def start(label)
|
|
13
|
+
@thread = Thread.new do
|
|
14
|
+
loop do
|
|
15
|
+
sleep @interval
|
|
16
|
+
@out.puts "wip: [debug] still running (#{load_average} | #{memory} | top: #{top_processes}): #{label}"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def stop
|
|
22
|
+
@thread&.kill
|
|
23
|
+
@thread&.join
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def load_average
|
|
29
|
+
"load #{File.read('/proc/loadavg').split[0..2].join(' ')}"
|
|
30
|
+
rescue Errno::ENOENT, IOError
|
|
31
|
+
'load n/a'
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def memory
|
|
35
|
+
fields = File.read('/proc/meminfo').lines.to_h { |line| line.split(':', 2).then { |k, v| [k, v.to_i] } }
|
|
36
|
+
total_gb = fields.fetch('MemTotal', 0) / 1_048_576.0
|
|
37
|
+
used_gb = total_gb - (fields.fetch('MemAvailable', 0) / 1_048_576.0)
|
|
38
|
+
format('mem %<used>.1fG/%<total>.1fG', used: used_gb, total: total_gb)
|
|
39
|
+
rescue Errno::ENOENT, IOError
|
|
40
|
+
'mem n/a'
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def top_processes
|
|
44
|
+
`ps -eo pid,pcpu,pmem,comm --sort=-pcpu 2>/dev/null`.lines.drop(1).first(3).filter_map do |line|
|
|
45
|
+
pid, cpu, mem, comm = line.split(nil, 4)
|
|
46
|
+
next unless comm
|
|
47
|
+
|
|
48
|
+
"#{comm.strip}(#{pid}) cpu #{cpu}%/mem #{mem}%"
|
|
49
|
+
end.join(', ')
|
|
50
|
+
rescue StandardError
|
|
51
|
+
'n/a'
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
data/lib/wip/version.rb
CHANGED
data/lib/wip.rb
CHANGED
|
@@ -8,6 +8,9 @@ require_relative 'wip/environment'
|
|
|
8
8
|
require_relative 'wip/command_resolver'
|
|
9
9
|
require_relative 'wip/error_interpreter'
|
|
10
10
|
require_relative 'wip/command_builder'
|
|
11
|
+
require_relative 'wip/command_display'
|
|
11
12
|
require_relative 'wip/command_runner'
|
|
13
|
+
require_relative 'wip/resource_monitor'
|
|
14
|
+
require_relative 'wip/debug_reporter'
|
|
12
15
|
require_relative 'wip/doctor'
|
|
13
16
|
require_relative 'wip/cli'
|
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.
|
|
4
|
+
version: 0.4.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Wip contributors
|
|
@@ -35,20 +35,24 @@ files:
|
|
|
35
35
|
- lib/wip.rb
|
|
36
36
|
- lib/wip/cli.rb
|
|
37
37
|
- lib/wip/command_builder.rb
|
|
38
|
+
- lib/wip/command_display.rb
|
|
38
39
|
- lib/wip/command_resolver.rb
|
|
39
40
|
- lib/wip/command_runner.rb
|
|
40
41
|
- lib/wip/config.rb
|
|
41
42
|
- lib/wip/config_loader.rb
|
|
43
|
+
- lib/wip/debug_reporter.rb
|
|
42
44
|
- lib/wip/doctor.rb
|
|
43
45
|
- lib/wip/environment.rb
|
|
44
46
|
- lib/wip/error_interpreter.rb
|
|
45
47
|
- lib/wip/errors.rb
|
|
48
|
+
- lib/wip/resource_monitor.rb
|
|
46
49
|
- lib/wip/version.rb
|
|
47
|
-
homepage: https://
|
|
50
|
+
homepage: https://wslc-wip.slidict.com/
|
|
48
51
|
licenses:
|
|
49
52
|
- MIT
|
|
50
53
|
metadata:
|
|
51
54
|
rubygems_mfa_required: 'true'
|
|
55
|
+
homepage_uri: https://wslc-wip.slidict.com/
|
|
52
56
|
source_code_uri: https://github.com/slidict/wip
|
|
53
57
|
changelog_uri: https://github.com/slidict/wip/releases
|
|
54
58
|
bug_tracker_uri: https://github.com/slidict/wip/issues
|