wslc-wip 0.11.0 → 0.13.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 +42 -12
- data/lib/wip/cli.rb +15 -0
- data/lib/wip/command_builder.rb +34 -13
- data/lib/wip/config.rb +12 -2
- data/lib/wip/initializer.rb +12 -6
- data/lib/wip/sync_settings.rb +29 -9
- 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: 00dbc091bf03ff619a8d2096e87a7c9fea05194829a5378448b0547d1b3133a9
|
|
4
|
+
data.tar.gz: 4a44f1a28d3dc37a4f58f4b171e5034c714c83fe123b4870ac30862fbe0a10d4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f9d78416a789d166c2db523d2b4f6e32e54e3e099f6f6cb369b5d7e1c46b8bc95c3a2088cb211add014a9fc59313985bdbef9f573de70cd86b41f0b936df4cbd
|
|
7
|
+
data.tar.gz: 7131cba0e0935a53bb7966ddd5ce2254374d78d08093b7ef0ccd987848184a2be834938bee169aaad78cfaeb77b9317af94df65c9127fe05559a8f926c51fef2
|
data/README.md
CHANGED
|
@@ -50,7 +50,8 @@ mode: container # default. This example is container mode end-to-end — see "Co
|
|
|
50
50
|
# compose: block instead; the two don't mix within one wip.yml.
|
|
51
51
|
wslc:
|
|
52
52
|
command: auto # tries wslc.exe, wslc, then System32; an absolute path also works
|
|
53
|
-
container: app #
|
|
53
|
+
container: app # required once dependencies: has entries; which one `up`/`exec`/`run`/`build`/`commands:`
|
|
54
|
+
# target. No default — a project must say which entry is the primary one explicitly.
|
|
54
55
|
network: app-tier # optional; shared by every dependencies: entry so containers can resolve each other by name
|
|
55
56
|
dependencies:
|
|
56
57
|
app: # container: points here — the one container wip execs into and runs commands in
|
|
@@ -129,10 +130,11 @@ directly with no copying.
|
|
|
129
130
|
|
|
130
131
|
### Dependency containers
|
|
131
132
|
|
|
132
|
-
`dependencies:` holds every container uniformly — the primary one `container:` points at
|
|
133
|
-
|
|
134
|
-
`
|
|
135
|
-
|
|
133
|
+
`dependencies:` holds every container uniformly — the primary one `container:` points at and any
|
|
134
|
+
sidecar services (a database, Redis, ...) alongside it. Each entry accepts `image` (required),
|
|
135
|
+
`command`, `env`, `ports`, `volumes`, and `workdir`; there's no separate, differently-shaped block
|
|
136
|
+
for "the one you exec into." `container:` has no default; once `dependencies:` has any entries,
|
|
137
|
+
wip needs to be told explicitly which one is primary rather than guessing a name.
|
|
136
138
|
|
|
137
139
|
What sets the primary entry apart is operational, not structural: `wip up` brings up every other
|
|
138
140
|
entry by name first (creating `network:` beforehand if it doesn't exist and set), then boots or
|
|
@@ -167,9 +169,16 @@ sync:
|
|
|
167
169
|
interval: 2 # seconds between syncs for `wip sync --watch` (default: 2)
|
|
168
170
|
mode: exec # exec (mirror inside the running container) or run (a throwaway one);
|
|
169
171
|
# default: exec for `mode: container`, run for `mode: compose`
|
|
170
|
-
image: null # image for the mirror container;
|
|
171
|
-
#
|
|
172
|
-
#
|
|
172
|
+
image: null # image for the mirror container; unused under mode: container unless set
|
|
173
|
+
# (falls back to the primary container's own image). Under mode: compose,
|
|
174
|
+
# one of image or build is required (there's no dependencies: entry to fall
|
|
175
|
+
# back to)
|
|
176
|
+
build: # optional; has wip build the mirror image itself instead of requiring one to
|
|
177
|
+
# already exist. build.tag wins over image if both are set.
|
|
178
|
+
dockerfile: |
|
|
179
|
+
FROM alpine:latest
|
|
180
|
+
RUN apk add --no-cache rsync
|
|
181
|
+
tag: null # optional (default: "wip-sync-<container>:latest")
|
|
173
182
|
```
|
|
174
183
|
|
|
175
184
|
Everything below `sync:` is optional — `sync: {}` alone already works. With it in place:
|
|
@@ -187,6 +196,8 @@ Everything below `sync:` is optional — `sync: {}` alone already works. With it
|
|
|
187
196
|
- `wip sync --watch [--interval N]` keeps re-syncing until Ctrl-C, so host edits reach the
|
|
188
197
|
container with a short delay. Run it in a second terminal alongside `wip up -d`.
|
|
189
198
|
- `wip doctor` reports the resolved source, volume, and target, and fails if the source is missing.
|
|
199
|
+
- With `sync.build` configured, `wip build`s that image once per `wip up`/`wip sync` invocation
|
|
200
|
+
(including once before a `--watch` loop starts, not on every tick) before mirroring with it.
|
|
190
201
|
|
|
191
202
|
Like every built-in command, `wip sync` takes precedence over a `commands:` entry of the same
|
|
192
203
|
name; wip says so and points at `wip dispatch sync`, which still runs yours.
|
|
@@ -215,14 +226,33 @@ or set `delete: false`.
|
|
|
215
226
|
the compose service directly.
|
|
216
227
|
- `sync.mode` defaults to `run` and can't be set to `exec` (only a container wip itself booted is
|
|
217
228
|
guaranteed to have the read-only source mount attached, which compose services never do), and
|
|
218
|
-
`sync.image` becomes required, since that disposable container needs an image
|
|
219
|
-
under `mode: container` it borrows the primary `dependencies:` entry's image,
|
|
220
|
-
has no such entry to borrow from.
|
|
229
|
+
`sync.image` or `sync.build` becomes required, since that disposable container needs an image
|
|
230
|
+
from somewhere — under `mode: container` it borrows the primary `dependencies:` entry's image,
|
|
231
|
+
but compose mode has no such entry to borrow from.
|
|
221
232
|
|
|
222
233
|
`wip up`'s pre-boot mirror (and the `--no-sync` flag that skips it) works the same way under
|
|
223
234
|
`mode: compose` as it does otherwise: the source is mirrored into the volume before
|
|
224
235
|
`compose up` starts the service that mounts it.
|
|
225
236
|
|
|
237
|
+
Since `sync.mode: run` boots a fresh container on every mirror, reusing your app's full image here
|
|
238
|
+
just adds startup overhead for something that only ever runs `rsync`. A dedicated, minimal image is
|
|
239
|
+
worth it — `wip` doesn't publish or default to one itself (same reasoning as `compose.command`:
|
|
240
|
+
picking a specific third-party image for you isn't its call to make), but `sync.build` covers it
|
|
241
|
+
without needing to manage a separate image yourself:
|
|
242
|
+
|
|
243
|
+
```yaml
|
|
244
|
+
sync:
|
|
245
|
+
build:
|
|
246
|
+
dockerfile: |
|
|
247
|
+
FROM alpine:latest
|
|
248
|
+
RUN apk add --no-cache rsync
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
wip builds this once per `wip up`/`wip sync` invocation (not on every `--watch` tick — see above)
|
|
252
|
+
and uses the result, tagged `wip-sync-<container>:latest` by default (`build.tag` overrides it).
|
|
253
|
+
Prefer managing the image yourself instead? Build and tag it however you like, then set `sync.image`
|
|
254
|
+
to that tag directly — `sync.build`'s tag wins if both are set, so don't configure both at once.
|
|
255
|
+
|
|
226
256
|
### Compose mode
|
|
227
257
|
|
|
228
258
|
If your project already has a real `compose.yml`, don't duplicate it in `dependencies:` — point
|
|
@@ -274,7 +304,7 @@ found, its version, and which compose file `wip` resolved.
|
|
|
274
304
|
| `wip doctor` | Diagnose WSL2, interop, WSLC, config, architecture, and Git |
|
|
275
305
|
| `wip config` | Print the effective configuration (secrets masked) |
|
|
276
306
|
| `wip build -- --no-cache` | Build the image from the `build` definition |
|
|
277
|
-
| `wip up [-d] [--no-sync]` | Start the primary `dependencies:` entry (`container
|
|
307
|
+
| `wip up [-d] [--no-sync]` | Start the primary `dependencies:` entry (`container:` names which one) and its sidecars (creating any that are missing, on `network:` if set). `-d` runs the main container in the background; with `sync:` configured, the source is mirrored into the volume first unless `--no-sync` |
|
|
278
308
|
| `wip down` | Stop and remove the primary container and its sidecar `dependencies:` |
|
|
279
309
|
| `wip exec [--no-interactive] COMMAND...` | Run a command in the existing container |
|
|
280
310
|
| `wip run [--no-interactive] COMMAND...` | Run a command in a new `--rm` container (compose mode: `exec`s into `compose.service` instead) |
|
data/lib/wip/cli.rb
CHANGED
|
@@ -5,6 +5,7 @@ require 'yaml'
|
|
|
5
5
|
require 'json'
|
|
6
6
|
require 'stringio'
|
|
7
7
|
require 'shellwords'
|
|
8
|
+
require 'tmpdir'
|
|
8
9
|
|
|
9
10
|
module Wip
|
|
10
11
|
# Thor-based command-line interface for wip.
|
|
@@ -93,6 +94,7 @@ module Wip
|
|
|
93
94
|
def sync
|
|
94
95
|
settings = sync_settings!
|
|
95
96
|
warn_shadowed_command('sync')
|
|
97
|
+
ensure_sync_image(settings)
|
|
96
98
|
return run_sync unless options[:watch]
|
|
97
99
|
|
|
98
100
|
interval = watch_interval(settings)
|
|
@@ -270,6 +272,18 @@ module Wip
|
|
|
270
272
|
load_config.sync || raise(ConfigError, '`wip sync` needs a sync: block in wip.yml')
|
|
271
273
|
end
|
|
272
274
|
|
|
275
|
+
# Builds sync.build's image once per invocation (not per --watch tick, which
|
|
276
|
+
# would pay Docker build-cache-lookup overhead on every mirror for no reason).
|
|
277
|
+
# A no-op unless sync.build is configured.
|
|
278
|
+
def ensure_sync_image(settings)
|
|
279
|
+
return unless settings.build
|
|
280
|
+
|
|
281
|
+
Dir.mktmpdir('wip-sync-build-') do |dir|
|
|
282
|
+
File.write(File.join(dir, 'Dockerfile'), settings.build['dockerfile'])
|
|
283
|
+
execute(builder.sync_build(dir))
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
273
287
|
# `sync.interval` is validated when the config loads; --interval isn't, and
|
|
274
288
|
# a negative one would only surface as an ArgumentError from `sleep`.
|
|
275
289
|
def watch_interval(settings)
|
|
@@ -300,6 +314,7 @@ module Wip
|
|
|
300
314
|
settings = load_config.sync
|
|
301
315
|
return unless settings
|
|
302
316
|
|
|
317
|
+
ensure_sync_image(settings)
|
|
303
318
|
warn "wip: syncing #{settings.source} -> #{settings.volume}:#{settings.target}"
|
|
304
319
|
execute(builder.sync_run)
|
|
305
320
|
warn "wip: run `wip sync --watch` in another terminal to keep #{settings.target} up to date"
|
data/lib/wip/command_builder.rb
CHANGED
|
@@ -16,7 +16,7 @@ module Wip
|
|
|
16
16
|
# dependencies: entries don't carry their own name (it's the hash key), so the
|
|
17
17
|
# exec target defaults to @config.container; a commands: entry can still
|
|
18
18
|
# redirect it by setting its own `container:`.
|
|
19
|
-
values = primary_values.merge('container' =>
|
|
19
|
+
values = primary_values.merge('container' => required_container).merge(settings)
|
|
20
20
|
command = [@wslc, 'exec']
|
|
21
21
|
command << '-it' if tty?(interactive)
|
|
22
22
|
command.concat(options(values, include_container: true, include_publish: false)).concat(arguments)
|
|
@@ -32,7 +32,7 @@ module Wip
|
|
|
32
32
|
|
|
33
33
|
def up(detach: false)
|
|
34
34
|
values = primary_values
|
|
35
|
-
command = [@wslc, 'run', '--name',
|
|
35
|
+
command = [@wslc, 'run', '--name', required_container]
|
|
36
36
|
command.push('--network', @config.network) if @config.network
|
|
37
37
|
command << '-d' if detach
|
|
38
38
|
command << '-it' if !detach && tty?(true)
|
|
@@ -42,25 +42,38 @@ module Wip
|
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
def start(detach: false)
|
|
45
|
-
command = [@wslc, 'start',
|
|
45
|
+
command = [@wslc, 'start', required_container]
|
|
46
46
|
command.push('-a', '-i') unless detach
|
|
47
47
|
command
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
def find
|
|
51
|
-
[@wslc, 'list', '--all', '--filter', "name=#{
|
|
51
|
+
[@wslc, 'list', '--all', '--filter', "name=#{required_container}", '--format', 'json']
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
# Mirrors into the volume from a throwaway container. Used for sync.mode:
|
|
55
55
|
# run (compose's default, and mode: container's fallback for when the app
|
|
56
|
-
# container isn't running yet — e.g. just before `up` boots it).
|
|
57
|
-
#
|
|
58
|
-
#
|
|
56
|
+
# container isn't running yet — e.g. just before `up` boots it). The image
|
|
57
|
+
# comes from sync.build's tag (once built via sync_build), else sync.image,
|
|
58
|
+
# else the primary dependencies: entry; compose mode requires one of the
|
|
59
|
+
# first two, since it has no dependencies: entry to fall back to.
|
|
59
60
|
def sync_run
|
|
60
61
|
sync = required_sync
|
|
61
62
|
command = [@wslc, 'run', '--rm']
|
|
62
63
|
sync.volume_specs.each { |spec| command.push('-v', spec) }
|
|
63
|
-
|
|
64
|
+
image = sync.build&.fetch('tag') || sync.image || required(primary_values, 'image')
|
|
65
|
+
command.push(image).concat(sync.mirror_command)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Builds sync.build's image from a Dockerfile staged in `context` (a caller-
|
|
69
|
+
# managed directory, since the build only reads it once `wslc build` runs).
|
|
70
|
+
# Doesn't touch dependencies: at all, so it works the same under compose
|
|
71
|
+
# mode as it does under container mode.
|
|
72
|
+
def sync_build(context)
|
|
73
|
+
sync = required_sync
|
|
74
|
+
raise ConfigError, 'No sync.build configured in wip.yml' unless sync.build
|
|
75
|
+
|
|
76
|
+
[@wslc, 'build', '-t', sync.build['tag'], context]
|
|
64
77
|
end
|
|
65
78
|
|
|
66
79
|
# Mirrors from inside the already-running container. Only valid for
|
|
@@ -68,15 +81,15 @@ module Wip
|
|
|
68
81
|
# itself booted is guaranteed to have both the read-only source mount and
|
|
69
82
|
# the volume attached.
|
|
70
83
|
def sync_exec
|
|
71
|
-
[@wslc, 'exec',
|
|
84
|
+
[@wslc, 'exec', required_container, *required_sync.mirror_command]
|
|
72
85
|
end
|
|
73
86
|
|
|
74
87
|
def down
|
|
75
|
-
[@wslc, 'stop',
|
|
88
|
+
[@wslc, 'stop', required_container]
|
|
76
89
|
end
|
|
77
90
|
|
|
78
91
|
def remove
|
|
79
|
-
[@wslc, 'remove', '-f',
|
|
92
|
+
[@wslc, 'remove', '-f', required_container]
|
|
80
93
|
end
|
|
81
94
|
|
|
82
95
|
def network_create
|
|
@@ -183,9 +196,17 @@ module Wip
|
|
|
183
196
|
@config.dependency(name) || raise(ConfigError, "Unknown dependency: #{name}")
|
|
184
197
|
end
|
|
185
198
|
|
|
199
|
+
def required_container
|
|
200
|
+
container = @config.container
|
|
201
|
+
raise ConfigError, 'container: must be set in wip.yml' if container.to_s.empty?
|
|
202
|
+
|
|
203
|
+
container
|
|
204
|
+
end
|
|
205
|
+
|
|
186
206
|
def primary_values
|
|
187
|
-
|
|
188
|
-
|
|
207
|
+
container = required_container
|
|
208
|
+
@config.dependency(container) || raise(ConfigError, "No dependencies.#{container} entry " \
|
|
209
|
+
'(check container: in wip.yml)')
|
|
189
210
|
end
|
|
190
211
|
end
|
|
191
212
|
end
|
data/lib/wip/config.rb
CHANGED
|
@@ -25,8 +25,11 @@ module Wip
|
|
|
25
25
|
def dependencies = @raw['dependencies'] || {}
|
|
26
26
|
# Which dependencies: entry `up`/`down`/`exec`/`run`/`build`/`commands:` target
|
|
27
27
|
# by default — the one container wip itself considers "the app." Everything
|
|
28
|
-
# else in dependencies: is a sidecar wip only starts and stops.
|
|
29
|
-
|
|
28
|
+
# else in dependencies: is a sidecar wip only starts and stops. No default:
|
|
29
|
+
# guessing a name here (the old default was "app") either matches by luck or
|
|
30
|
+
# fails in a way that doesn't point at the real problem (a differently-named
|
|
31
|
+
# entry), so a project with any dependencies: must say which one explicitly.
|
|
32
|
+
def container = presence(@raw['container'])
|
|
30
33
|
def network = @raw['network']
|
|
31
34
|
def mode = @raw['mode'] || 'container'
|
|
32
35
|
def compose = @raw['compose']
|
|
@@ -107,6 +110,13 @@ module Wip
|
|
|
107
110
|
|
|
108
111
|
def validate_dependencies!
|
|
109
112
|
raise ConfigError, 'dependencies must be a mapping' unless dependencies.is_a?(Hash)
|
|
113
|
+
# Under compose mode, populated dependencies: is already an error on its own
|
|
114
|
+
# (validate_compose!, below) — this only needs to fire for the mode: container
|
|
115
|
+
# case, where dependencies: having entries but no container: is otherwise valid.
|
|
116
|
+
if dependencies.any? && !container && !compose?
|
|
117
|
+
raise ConfigError,
|
|
118
|
+
'container: must be set when dependencies: has entries'
|
|
119
|
+
end
|
|
110
120
|
|
|
111
121
|
dependencies.each { |name, entry| validate_dependency!(name, entry) }
|
|
112
122
|
end
|
data/lib/wip/initializer.rb
CHANGED
|
@@ -11,9 +11,10 @@ module Wip
|
|
|
11
11
|
CONTAINER_TEMPLATE = <<~YAML
|
|
12
12
|
version: 1
|
|
13
13
|
mode: container
|
|
14
|
+
container: app # TODO: rename freely, as long as it matches a key under dependencies: below
|
|
14
15
|
|
|
15
16
|
dependencies:
|
|
16
|
-
app: #
|
|
17
|
+
app: # this is the container wip creates, execs into, and runs commands in
|
|
17
18
|
image: your/image:tag # TODO: image to run
|
|
18
19
|
workdir: /app # TODO: adjust to match your image, or delete this line
|
|
19
20
|
|
|
@@ -40,6 +41,8 @@ module Wip
|
|
|
40
41
|
<<~YAML
|
|
41
42
|
version: 1
|
|
42
43
|
mode: compose
|
|
44
|
+
container: app # TODO: rename freely; only used to name sync's volume ("<container>-src"),
|
|
45
|
+
# since compose.yml — not dependencies: — owns this service's own definition
|
|
43
46
|
|
|
44
47
|
compose:
|
|
45
48
|
service: app # TODO: which service in #{compose_file} wip run/exec/NAME target
|
|
@@ -49,9 +52,9 @@ module Wip
|
|
|
49
52
|
YAML
|
|
50
53
|
end
|
|
51
54
|
|
|
52
|
-
# sync: needs sync.image (mode: compose has no dependencies: entry to borrow
|
|
53
|
-
# named volume the compose service mounts, matching sync.volume ("app-src" by
|
|
54
|
-
# against the detected compose file so the hint doesn't repeat what's
|
|
55
|
+
# sync: needs sync.image or sync.build (mode: compose has no dependencies: entry to borrow an
|
|
56
|
+
# image from) and a named volume the compose service mounts, matching sync.volume ("app-src" by
|
|
57
|
+
# default). Checked against the detected compose file so the hint doesn't repeat what's set up.
|
|
55
58
|
def sync_hint
|
|
56
59
|
return sync_hint_configured if compose_volume_mounted?
|
|
57
60
|
|
|
@@ -61,14 +64,17 @@ module Wip
|
|
|
61
64
|
def sync_hint_configured
|
|
62
65
|
<<~COMMENT.chomp
|
|
63
66
|
# #{compose_file} already mounts a volume matching sync.volume's default (app-src).
|
|
64
|
-
# sync: # add sync.image too (required under mode: compose) — see README
|
|
67
|
+
# sync: # add sync.build or sync.image too (one's required under mode: compose) — see README
|
|
65
68
|
COMMENT
|
|
66
69
|
end
|
|
67
70
|
|
|
68
71
|
def sync_hint_todo
|
|
69
72
|
<<~COMMENT.chomp
|
|
70
73
|
# sync: # optional; mirrors the source into a named volume instead of bind-mounting it live
|
|
71
|
-
#
|
|
74
|
+
# build: # required under mode: compose (no dependencies: entry to borrow an image from)
|
|
75
|
+
# dockerfile: |
|
|
76
|
+
# FROM alpine:latest
|
|
77
|
+
# RUN apk add --no-cache rsync
|
|
72
78
|
# # the service above must also mount a volume named "app-src" (sync.volume's default) at the
|
|
73
79
|
# # path your app expects — add to #{compose_file}:
|
|
74
80
|
# # volumes:
|
data/lib/wip/sync_settings.rb
CHANGED
|
@@ -32,7 +32,7 @@ module Wip
|
|
|
32
32
|
# compose owns its own services' mounts and never guarantees that shape.
|
|
33
33
|
SYNC_MODES = %w[exec run].freeze
|
|
34
34
|
|
|
35
|
-
attr_reader :target, :mount, :volume, :exclude, :binary, :extra_options, :interval, :mode, :image
|
|
35
|
+
attr_reader :target, :mount, :volume, :exclude, :binary, :extra_options, :interval, :mode, :image, :build
|
|
36
36
|
|
|
37
37
|
def initialize(raw, base: nil, workdir: nil, container: nil, compose: false)
|
|
38
38
|
raise ConfigError, 'sync must be a mapping' unless raw.is_a?(Hash)
|
|
@@ -40,6 +40,7 @@ module Wip
|
|
|
40
40
|
@base = base
|
|
41
41
|
assign_paths(raw, workdir: workdir, container: container)
|
|
42
42
|
assign_mirror(raw)
|
|
43
|
+
assign_build(raw)
|
|
43
44
|
assign_mode(raw, compose: compose)
|
|
44
45
|
validate!
|
|
45
46
|
end
|
|
@@ -76,7 +77,7 @@ module Wip
|
|
|
76
77
|
def to_h
|
|
77
78
|
{ 'source' => source, 'target' => target, 'mount' => mount, 'volume' => volume,
|
|
78
79
|
'delete' => delete?, 'exclude' => exclude, 'command' => binary, 'options' => extra_options,
|
|
79
|
-
'interval' => interval, 'mode' => mode, 'image' => image }
|
|
80
|
+
'interval' => interval, 'mode' => mode, 'image' => image, 'build' => build }
|
|
80
81
|
end
|
|
81
82
|
|
|
82
83
|
private
|
|
@@ -85,7 +86,8 @@ module Wip
|
|
|
85
86
|
@raw_source = presence(raw['source']) || '.'
|
|
86
87
|
@target = presence(raw['target']) || presence(workdir) || DEFAULT_TARGET
|
|
87
88
|
@mount = presence(raw['mount']) || DEFAULT_MOUNT
|
|
88
|
-
@
|
|
89
|
+
@container_name = presence(container) || 'wip'
|
|
90
|
+
@volume = presence(raw['volume']) || "#{@container_name}-src"
|
|
89
91
|
end
|
|
90
92
|
|
|
91
93
|
def assign_mirror(raw)
|
|
@@ -96,12 +98,29 @@ module Wip
|
|
|
96
98
|
@interval = raw.key?('interval') ? raw['interval'] : DEFAULT_INTERVAL
|
|
97
99
|
end
|
|
98
100
|
|
|
101
|
+
# sync.build lets wip build a small, dedicated mirror image itself (e.g. alpine
|
|
102
|
+
# + rsync) instead of requiring one to already exist — handy since sync.mode:
|
|
103
|
+
# run boots a fresh container per mirror and reusing the app's full image just
|
|
104
|
+
# adds startup overhead for something that only ever runs rsync.
|
|
105
|
+
def assign_build(raw)
|
|
106
|
+
build = raw['build']
|
|
107
|
+
@build = nil
|
|
108
|
+
return unless build
|
|
109
|
+
|
|
110
|
+
raise ConfigError, 'sync.build must be a mapping' unless build.is_a?(Hash)
|
|
111
|
+
|
|
112
|
+
dockerfile = presence(build['dockerfile'])
|
|
113
|
+
raise ConfigError, 'sync.build.dockerfile must not be empty' unless dockerfile
|
|
114
|
+
|
|
115
|
+
@build = { 'dockerfile' => dockerfile, 'tag' => presence(build['tag']) || "wip-sync-#{@container_name}:latest" }
|
|
116
|
+
end
|
|
117
|
+
|
|
99
118
|
def assign_mode(raw, compose:)
|
|
100
119
|
@mode = presence(raw['mode']) || (compose ? 'run' : 'exec')
|
|
101
120
|
@image = presence(raw['image'])
|
|
102
121
|
raise ConfigError, "sync.mode must be one of #{SYNC_MODES.join(', ')}" unless SYNC_MODES.include?(@mode)
|
|
103
122
|
|
|
104
|
-
|
|
123
|
+
validate_image_source!(compose)
|
|
105
124
|
return unless compose && exec?
|
|
106
125
|
|
|
107
126
|
raise ConfigError, 'sync.mode: exec needs mode: container (compose owns its services’ mounts, ' \
|
|
@@ -109,12 +128,13 @@ module Wip
|
|
|
109
128
|
end
|
|
110
129
|
|
|
111
130
|
# compose mode has no dependencies: entry to fall back to for the mirror
|
|
112
|
-
# container's image, so
|
|
113
|
-
|
|
114
|
-
|
|
131
|
+
# container's image, so it can't be left implicit there — sync.image or
|
|
132
|
+
# sync.build must name one explicitly.
|
|
133
|
+
def validate_image_source!(compose)
|
|
134
|
+
return if !compose || @image || @build
|
|
115
135
|
|
|
116
|
-
raise ConfigError, 'sync.image is required under mode: compose (there’s no
|
|
117
|
-
'to borrow the mirror container’s image from)'
|
|
136
|
+
raise ConfigError, 'sync.image or sync.build is required under mode: compose (there’s no ' \
|
|
137
|
+
'dependencies: entry to borrow the mirror container’s image from)'
|
|
118
138
|
end
|
|
119
139
|
|
|
120
140
|
def validate!
|
data/lib/wip/version.rb
CHANGED