wslc-wip 0.7.3 → 0.8.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 +13 -4
- data/lib/wip/cli.rb +4 -5
- data/lib/wip/command_builder.rb +7 -9
- data/lib/wip/config.rb +20 -6
- data/lib/wip/sync_settings.rb +27 -6
- 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: afc914ac16e4359e48e682f5dab523827ad782251365abee7abd60b6eee89044
|
|
4
|
+
data.tar.gz: dcc97ec1bace3262bc1c419162085de6a3f5e24f0dde8babb2532ef9bdd6446c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 526f9730c15eb6beabda31c3df3ee95cac2f8058c6da2dddf27100aadb48276bb68d66a3ede2b7383f49ecb6cd6fd45604b39ec1130058718cfbf439e7629f5a
|
|
7
|
+
data.tar.gz: 87a5f6c40e948fabdc79eeaf3e81cb73fac3cdb0fdd306189a72a437f459de22deeb57fbd92167411c157485d66dd82f40734f72316f46cb65fe6aa74b111511
|
data/README.md
CHANGED
|
@@ -44,6 +44,7 @@ Put a `wip.yml` in your project root. Running from a subdirectory walks up to fi
|
|
|
44
44
|
|
|
45
45
|
```yaml
|
|
46
46
|
version: 1
|
|
47
|
+
mode: container # or compose; picks the orchestration path `up`/`down`/`sync`/etc. take (default: container)
|
|
47
48
|
wslc:
|
|
48
49
|
command: auto # tries wslc.exe, wslc, then System32; an absolute path also works
|
|
49
50
|
defaults:
|
|
@@ -156,6 +157,8 @@ sync:
|
|
|
156
157
|
command: rsync # binary that does the mirroring (default: rsync)
|
|
157
158
|
options: [] # extra flags appended to the rsync invocation
|
|
158
159
|
interval: 2 # seconds between syncs for `wip sync --watch` (default: 2)
|
|
160
|
+
mode: exec # exec (mirror inside the running container) or run (a throwaway one);
|
|
161
|
+
# default: exec for `mode: container`, run for `mode: compose`
|
|
159
162
|
```
|
|
160
163
|
|
|
161
164
|
Everything below `sync:` is optional — `sync: {}` alone already works. With it in place:
|
|
@@ -166,8 +169,10 @@ Everything below `sync:` is optional — `sync: {}` alone already works. With it
|
|
|
166
169
|
`dependencies:` keep mounting whatever they declare.
|
|
167
170
|
- `wip up` mirrors the source into the volume before the container boots; `wip up --no-sync`
|
|
168
171
|
skips that step.
|
|
169
|
-
- `wip sync` mirrors on demand
|
|
170
|
-
|
|
172
|
+
- `wip sync` mirrors on demand: `sync.mode: exec` (the default under `mode: container`) execs
|
|
173
|
+
rsync inside the already-running container; `sync.mode: run` always uses a throwaway container
|
|
174
|
+
with the same mounts instead. Which one runs is fixed by config, not guessed at from whether a
|
|
175
|
+
container happens to be up.
|
|
171
176
|
- `wip sync --watch [--interval N]` keeps re-syncing until Ctrl-C, so host edits reach the
|
|
172
177
|
container with a short delay. Run it in a second terminal alongside `wip up -d`.
|
|
173
178
|
- `wip doctor` reports the resolved source, volume, and target, and fails if the source is missing.
|
|
@@ -181,8 +186,11 @@ image already has. And the mirror is one-way (host → volume): anything the app
|
|
|
181
186
|
`target` is removed by the next `--delete` pass unless you `exclude` it, give it its own volume,
|
|
182
187
|
or set `delete: false`.
|
|
183
188
|
|
|
184
|
-
`sync:`
|
|
185
|
-
|
|
189
|
+
`sync:` works alongside `mode: compose` too. Compose still owns the volume layout — a compose
|
|
190
|
+
service must mount the same named volume `sync.volume` names — but wip's mirror runs as its own
|
|
191
|
+
throwaway container (`sync.mode` defaults to `run` and can't be set to `exec` under `mode:
|
|
192
|
+
compose`, since only a container wip itself booted is guaranteed to have the read-only source
|
|
193
|
+
mount attached).
|
|
186
194
|
|
|
187
195
|
### Compose mode
|
|
188
196
|
|
|
@@ -191,6 +199,7 @@ If your project already has a real `compose.yml`, don't duplicate it in `depende
|
|
|
191
199
|
|
|
192
200
|
```yaml
|
|
193
201
|
version: 1
|
|
202
|
+
mode: compose # required to enable compose mode; a compose: block with no mode: compose is an error
|
|
194
203
|
compose:
|
|
195
204
|
service: app # required: which compose service wip run/exec/NAME target
|
|
196
205
|
command: wslc-compose # required: the compose-for-wslc binary/path you have installed
|
data/lib/wip/cli.rb
CHANGED
|
@@ -271,15 +271,14 @@ module Wip
|
|
|
271
271
|
"run it with `wip dispatch #{name}`"
|
|
272
272
|
end
|
|
273
273
|
|
|
274
|
-
#
|
|
275
|
-
#
|
|
274
|
+
# sync.mode picks the path explicitly (default: exec for mode: container,
|
|
275
|
+
# run for mode: compose) rather than probing whether a container happens
|
|
276
|
+
# to be running under a matching name.
|
|
276
277
|
def run_sync(exit_on_failure: true)
|
|
277
|
-
command =
|
|
278
|
+
command = sync_settings!.exec? ? builder.sync_exec : builder.sync_run
|
|
278
279
|
execute(command, exit_on_failure: exit_on_failure)
|
|
279
280
|
end
|
|
280
281
|
|
|
281
|
-
def container_running? = resource_exists?(builder.find_running)
|
|
282
|
-
|
|
283
282
|
def sync_before_boot
|
|
284
283
|
settings = load_config.sync
|
|
285
284
|
return unless settings
|
data/lib/wip/command_builder.rb
CHANGED
|
@@ -49,13 +49,9 @@ module Wip
|
|
|
49
49
|
[@wslc, 'list', '--all', '--filter', "name=#{container}", '--format', 'json']
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
# Mirrors into the volume from a throwaway container, for when the app
|
|
58
|
-
# container isn't running yet (or at all) — e.g. just before `up` boots it.
|
|
52
|
+
# Mirrors into the volume from a throwaway container. Used for sync.mode:
|
|
53
|
+
# run (compose's default, and mode: container's fallback for when the app
|
|
54
|
+
# container isn't running yet — e.g. just before `up` boots it).
|
|
59
55
|
def sync_run
|
|
60
56
|
sync = required_sync
|
|
61
57
|
command = [@wslc, 'run', '--rm']
|
|
@@ -63,8 +59,10 @@ module Wip
|
|
|
63
59
|
command.push(required(@config.defaults, 'image')).concat(sync.mirror_command)
|
|
64
60
|
end
|
|
65
61
|
|
|
66
|
-
# Mirrors from inside the running container
|
|
67
|
-
#
|
|
62
|
+
# Mirrors from inside the already-running container. Only valid for
|
|
63
|
+
# sync.mode: exec (mode: container's default), since only a container wip
|
|
64
|
+
# itself booted is guaranteed to have both the read-only source mount and
|
|
65
|
+
# the volume attached.
|
|
68
66
|
def sync_exec
|
|
69
67
|
[@wslc, 'exec', required(@config.defaults, 'container'), *required_sync.mirror_command]
|
|
70
68
|
end
|
data/lib/wip/config.rb
CHANGED
|
@@ -6,6 +6,10 @@ module Wip
|
|
|
6
6
|
DEFAULTS = { 'container' => 'app', 'workdir' => '/app', 'interactive' => false,
|
|
7
7
|
'remove' => true, 'env' => {}, 'ports' => [], 'volumes' => [] }.freeze
|
|
8
8
|
SECRET_PATTERN = /token|password|secret|credential|auth/i
|
|
9
|
+
# Which orchestration path `up`/`down`/`sync`/etc. take. Explicit rather than
|
|
10
|
+
# inferred from a `compose:` block's presence, so a config reader doesn't have
|
|
11
|
+
# to know that rule to predict which mode wip runs in.
|
|
12
|
+
MODES = %w[container compose].freeze
|
|
9
13
|
attr_reader :path
|
|
10
14
|
|
|
11
15
|
def initialize(raw, path = nil)
|
|
@@ -20,8 +24,9 @@ module Wip
|
|
|
20
24
|
def up_command = @raw.dig('up', 'command')
|
|
21
25
|
def dependencies = @raw['dependencies'] || {}
|
|
22
26
|
def network = defaults['network']
|
|
27
|
+
def mode = @raw['mode'] || 'container'
|
|
23
28
|
def compose = @raw['compose']
|
|
24
|
-
def compose? =
|
|
29
|
+
def compose? = mode == 'compose'
|
|
25
30
|
def compose_service = compose && compose['service']
|
|
26
31
|
def compose_file = compose && compose['file']
|
|
27
32
|
def compose_project = compose && compose['project']
|
|
@@ -50,8 +55,8 @@ module Wip
|
|
|
50
55
|
|
|
51
56
|
def to_h(redact: true)
|
|
52
57
|
value = { 'version' => 1, 'wslc' => { 'command' => wslc_command }, 'defaults' => defaults,
|
|
53
|
-
'up' => { 'command' => up_command }, '
|
|
54
|
-
'sync' => sync&.to_h,
|
|
58
|
+
'up' => { 'command' => up_command }, 'mode' => mode, 'dependencies' => dependencies,
|
|
59
|
+
'compose' => compose, 'sync' => sync&.to_h,
|
|
55
60
|
'commands' => commands.transform_values { |entry| defaults.merge('type' => 'exec').merge(entry) } }
|
|
56
61
|
redact ? redact_secrets(value) : value
|
|
57
62
|
end
|
|
@@ -62,6 +67,7 @@ module Wip
|
|
|
62
67
|
raise ConfigError, "Unsupported configuration version: #{@raw['version']}" unless (@raw['version'] || 1) == 1
|
|
63
68
|
raise ConfigError, 'up must be a mapping' if @raw.key?('up') && !@raw['up'].is_a?(Hash)
|
|
64
69
|
|
|
70
|
+
validate_mode!
|
|
65
71
|
validate_commands!
|
|
66
72
|
validate_dependencies!
|
|
67
73
|
validate_compose!
|
|
@@ -70,12 +76,20 @@ module Wip
|
|
|
70
76
|
|
|
71
77
|
def build_sync
|
|
72
78
|
SyncSettings.new(@raw['sync'], base: path && File.dirname(path.to_s),
|
|
73
|
-
workdir: defaults['workdir'], container: defaults['container']
|
|
79
|
+
workdir: defaults['workdir'], container: defaults['container'],
|
|
80
|
+
compose: compose?)
|
|
74
81
|
end
|
|
75
82
|
|
|
83
|
+
def validate_mode!
|
|
84
|
+
raise ConfigError, "mode must be one of #{MODES.join(', ')}" unless MODES.include?(mode)
|
|
85
|
+
raise ConfigError, 'mode: compose requires a compose: block' if compose? && !@raw.key?('compose')
|
|
86
|
+
raise ConfigError, 'a compose: block requires mode: compose' if @raw.key?('compose') && !compose?
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Forces SyncSettings to build (and so run its own validation, including
|
|
90
|
+
# sync.mode vs. mode) at load time instead of on first access.
|
|
76
91
|
def validate_sync!
|
|
77
|
-
|
|
78
|
-
raise ConfigError, 'sync is mutually exclusive with compose' if compose?
|
|
92
|
+
sync if @raw.key?('sync')
|
|
79
93
|
end
|
|
80
94
|
|
|
81
95
|
def validate_commands!
|
data/lib/wip/sync_settings.rb
CHANGED
|
@@ -16,24 +16,36 @@ module Wip
|
|
|
16
16
|
DEFAULT_TARGET = '/app'
|
|
17
17
|
DEFAULT_BINARY = 'rsync'
|
|
18
18
|
DEFAULT_INTERVAL = 2
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
|
|
19
|
+
# Minimal set for a fast local-to-local mirror: -r walks the tree, -l
|
|
20
|
+
# keeps symlinks as symlinks, -t preserves mtimes so re-syncs can quick-
|
|
21
|
+
# check (size+mtime) instead of re-transferring unchanged files, and
|
|
22
|
+
# --whole-file skips the delta-transfer checksum pass that only pays off
|
|
23
|
+
# over a slow network. Owner/group/perm preservation (-o -g -p, part of
|
|
24
|
+
# -a) is left out since both sides are the same user; add them back via
|
|
25
|
+
# sync.options if a project needs them.
|
|
26
|
+
BASE_OPTIONS = %w[-r -l -t --whole-file].freeze
|
|
22
27
|
# Trailing mount options wslc/docker accept after the container path.
|
|
23
28
|
VOLUME_MODES = %w[ro rw z Z cached delegated consistent].freeze
|
|
29
|
+
# exec mirrors inside the already-running, wip-managed container (fast,
|
|
30
|
+
# but only correct when wip itself created that container with the sync
|
|
31
|
+
# mounts attached). run always mirrors from a disposable container, since
|
|
32
|
+
# compose owns its own services' mounts and never guarantees that shape.
|
|
33
|
+
SYNC_MODES = %w[exec run].freeze
|
|
24
34
|
|
|
25
|
-
attr_reader :target, :mount, :volume, :exclude, :binary, :extra_options, :interval
|
|
35
|
+
attr_reader :target, :mount, :volume, :exclude, :binary, :extra_options, :interval, :mode
|
|
26
36
|
|
|
27
|
-
def initialize(raw, base: nil, workdir: nil, container: nil)
|
|
37
|
+
def initialize(raw, base: nil, workdir: nil, container: nil, compose: false)
|
|
28
38
|
raise ConfigError, 'sync must be a mapping' unless raw.is_a?(Hash)
|
|
29
39
|
|
|
30
40
|
@base = base
|
|
31
41
|
assign_paths(raw, workdir: workdir, container: container)
|
|
32
42
|
assign_mirror(raw)
|
|
43
|
+
assign_mode(raw, compose: compose)
|
|
33
44
|
validate!
|
|
34
45
|
end
|
|
35
46
|
|
|
36
47
|
def delete? = !!@delete
|
|
48
|
+
def exec? = mode == 'exec'
|
|
37
49
|
|
|
38
50
|
# Expanded against the wip.yml directory so the mirror covers the same tree
|
|
39
51
|
# no matter which subdirectory wip was invoked from.
|
|
@@ -64,7 +76,7 @@ module Wip
|
|
|
64
76
|
def to_h
|
|
65
77
|
{ 'source' => source, 'target' => target, 'mount' => mount, 'volume' => volume,
|
|
66
78
|
'delete' => delete?, 'exclude' => exclude, 'command' => binary, 'options' => extra_options,
|
|
67
|
-
'interval' => interval }
|
|
79
|
+
'interval' => interval, 'mode' => mode }
|
|
68
80
|
end
|
|
69
81
|
|
|
70
82
|
private
|
|
@@ -84,6 +96,15 @@ module Wip
|
|
|
84
96
|
@interval = raw.key?('interval') ? raw['interval'] : DEFAULT_INTERVAL
|
|
85
97
|
end
|
|
86
98
|
|
|
99
|
+
def assign_mode(raw, compose:)
|
|
100
|
+
@mode = presence(raw['mode']) || (compose ? 'run' : 'exec')
|
|
101
|
+
raise ConfigError, "sync.mode must be one of #{SYNC_MODES.join(', ')}" unless SYNC_MODES.include?(@mode)
|
|
102
|
+
return unless compose && exec?
|
|
103
|
+
|
|
104
|
+
raise ConfigError, 'sync.mode: exec needs mode: container (compose owns its services’ mounts, ' \
|
|
105
|
+
'so it can’t guarantee the running container has the sync mounts attached)'
|
|
106
|
+
end
|
|
107
|
+
|
|
87
108
|
def validate!
|
|
88
109
|
raise ConfigError, 'sync.target must be an absolute path' unless @target.start_with?('/')
|
|
89
110
|
raise ConfigError, 'sync.mount must be an absolute path' unless @mount.start_with?('/')
|
data/lib/wip/version.rb
CHANGED