wslc-wip 0.14.2 → 0.16.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 +34 -13
- data/lib/wip/cli.rb +4 -1
- data/lib/wip/command_builder.rb +15 -6
- data/lib/wip/compose_file.rb +46 -25
- data/lib/wip/config.rb +1 -1
- data/lib/wip/initializer.rb +171 -62
- 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: c93fe67017509908d6ea81f4a80555ef193991504fb9f19cba7e598bd231635b
|
|
4
|
+
data.tar.gz: a19a5d66f81bfa19be592a778cba3369301f2f1c7f3c1a24a6a111513a2cceca
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a3a6bd4bae10abee5389f7314dbbf7693882e7559412c9207569a7bed16206aceef5ed781b01ab96b94791dfe2a20bb16944b4fad7d2bfcd3e5fa74667576343
|
|
7
|
+
data.tar.gz: 439b070a2f3f00f9396b595dd939c251d1854cf8ba65ee3d6b93c55c512d90d85090e2be2b77cd2d68a8fc0aae149125b235fbba3fbd4d57a943dad7dc512410
|
data/README.md
CHANGED
|
@@ -181,6 +181,17 @@ sync:
|
|
|
181
181
|
tag: null # optional (default: "wip-sync-<container>:latest")
|
|
182
182
|
```
|
|
183
183
|
|
|
184
|
+
`wip init --template NAME` writes `exclude`'s default list live, picked from that stack's own
|
|
185
|
+
`github/gitignore` template:
|
|
186
|
+
|
|
187
|
+
| `--template` | Stack | Default `exclude` |
|
|
188
|
+
|---|---|---|
|
|
189
|
+
| `rails` | Rails | `.git`, `log/`, `tmp/`, `storage/`, `public/assets/`, `public/packs/`, `.bundle/`, `vendor/bundle/`, `coverage/`, `node_modules/` |
|
|
190
|
+
| `node` | Node.js | `.git`, `node_modules/`, `dist/`, `build/`, `.next/`, `.cache/`, `coverage/` |
|
|
191
|
+
| `rust` | Rust | `.git`, `target/` |
|
|
192
|
+
| `csharp` | C# | `.git`, `bin/`, `obj/`, `.vs/`, `packages/` |
|
|
193
|
+
| (omitted) | — | `.git`, `tmp/`, `node_modules/` |
|
|
194
|
+
|
|
184
195
|
Everything below `sync:` is optional — `sync: {}` alone already works. With it in place:
|
|
185
196
|
|
|
186
197
|
- Any `volumes` entry on the primary container mounting `target` (the usual `.:/app`) is replaced
|
|
@@ -329,8 +340,11 @@ valid compose.yml over sections it doesn't need to look at:
|
|
|
329
340
|
`compose.yml`, not wherever `wip` is invoked from), `command` (shell or exec form), `environment`
|
|
330
341
|
(mapping or `KEY=VALUE` array — a mapping value must not be null; host environment pass-through
|
|
331
342
|
isn't supported), `ports`/`volumes` (short syntax only — `"host:container"` strings, not
|
|
332
|
-
long-syntax mappings), `working_dir`, `depends_on` (ordering only — a `condition:` other
|
|
333
|
-
`service_started` is rejected, since there's no health-check support).
|
|
343
|
+
long-syntax mappings), `working_dir`, `user`, `depends_on` (ordering only — a `condition:` other
|
|
344
|
+
than `service_started` is rejected, since there's no health-check support). `tty`, `stdin_open`,
|
|
345
|
+
and `networks` are accepted but silently ignored: TTY/stdin allocation is already decided per
|
|
346
|
+
invocation (see "TTY allocation" below), not fixed per service, and every service already shares
|
|
347
|
+
the one project network `compose.project` sets up.
|
|
334
348
|
- `wip logs` takes at most one `SERVICE` (defaulting to `compose.service`) — `wslc logs`, like
|
|
335
349
|
`docker logs`, follows a single container, unlike a real compose tool's multi-service view.
|
|
336
350
|
- `sync:` behaves exactly like `mode: container`'s (falls back to the primary service's own image,
|
|
@@ -341,7 +355,7 @@ valid compose.yml over sections it doesn't need to look at:
|
|
|
341
355
|
|
|
342
356
|
| Command | Description |
|
|
343
357
|
|---|---|
|
|
344
|
-
| `wip init [--force]` | Write a starter `wip.yml`: `mode: compose-native` if a `compose.yml`/`docker-compose.yml` is found next to it, `mode: container` otherwise. Refuses to overwrite an existing `wip.yml` unless `--force` |
|
|
358
|
+
| `wip init [--force] [--template NAME]` | Write a starter `wip.yml`: `mode: compose-native` if a `compose.yml`/`docker-compose.yml` is found next to it, `mode: container` otherwise. `--template` picks `sync.exclude`'s default patterns for a stack (`rails`, `node`, `rust`, `csharp`); omitted, it falls back to `.git`/`tmp/`/`node_modules/`. Refuses to overwrite an existing `wip.yml` unless `--force` |
|
|
345
359
|
| `wip version` | wip's version, plus WSLC's if it can be detected |
|
|
346
360
|
| `wip doctor` | Diagnose WSL2, interop, WSLC, config, architecture, and Git |
|
|
347
361
|
| `wip config` | Print the effective configuration (secrets masked) |
|
|
@@ -470,21 +484,28 @@ checklist.
|
|
|
470
484
|
|
|
471
485
|
## Not in the initial release
|
|
472
486
|
|
|
473
|
-
Full Compose compatibility isn't reimplemented in `wip` itself — `mode: compose-native`
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
[Compose mode](#compose-mode)). A resident/daemon
|
|
477
|
-
direct registry API/manifest parsing, self-update, and
|
|
487
|
+
Full Compose compatibility isn't reimplemented in `wip` itself — `mode: compose-native` parses
|
|
488
|
+
`compose.yml` and drives `wslc` directly, but only covers a minimal subset (see
|
|
489
|
+
[Compose mode (native)](#compose-mode-native)); full parity is otherwise available by delegating
|
|
490
|
+
to a third-party compose-for-`wslc` tool (see [Compose mode](#compose-mode)). A resident/daemon
|
|
491
|
+
process, a GUI, PowerShell-specific tuning, direct registry API/manifest parsing, self-update, and
|
|
492
|
+
plugins are all unimplemented.
|
|
478
493
|
|
|
479
494
|
## Roadmap
|
|
480
495
|
|
|
481
496
|
`wip` already covers most of what [`dip`](https://github.com/bibendi/dip) adds on top of Compose —
|
|
482
497
|
named commands (`commands:`), `run`/`exec` hidden behind a single verb, `.env` passthrough, and
|
|
483
|
-
sidecar services via `dependencies:` + `network:`.
|
|
484
|
-
(
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
498
|
+
sidecar services via `dependencies:` + `network:`. Rather than waiting on `wslc`'s own Compose
|
|
499
|
+
support ([microsoft/WSL#40948](https://github.com/microsoft/WSL/issues/40948)) or an external
|
|
500
|
+
compose-for-`wslc` tool staying complete, `mode: compose-native` (see
|
|
501
|
+
[Compose mode (native)](#compose-mode-native)) parses `compose.yml` itself and drives `wslc`
|
|
502
|
+
directly — no external binary in the loop, `wip run` gets a real `--rm` container, and iterating
|
|
503
|
+
on the parser is faster than chasing a third-party tool's own bugs. It's still a deliberately
|
|
504
|
+
minimal subset (`depends_on` ordering but no health checks, single-container `logs`, no named
|
|
505
|
+
volumes/scaling), and `dependencies:` + `network:` remains the escape hatch for sidecars it
|
|
506
|
+
doesn't model. Fuller Compose semantics beyond that subset stay behind delegating to a separate
|
|
507
|
+
compose-for-`wslc` tool in [compose mode](#compose-mode) — which of those actually work is
|
|
508
|
+
entirely up to the external tool you point `compose.command` at; `wip` only forwards
|
|
488
509
|
`-f FILE [-p PROJECT] up|down|exec|logs`, so treat that list as what Compose offers, not as
|
|
489
510
|
something `wip` guarantees. See that section for what compose mode covers
|
|
490
511
|
and its current limitations (`run`, and `commands:` of type `run`/`build`). What's still planned
|
data/lib/wip/cli.rb
CHANGED
|
@@ -42,11 +42,13 @@ module Wip
|
|
|
42
42
|
|
|
43
43
|
desc 'init', 'Create a starter wip.yml (detects an existing compose.yml/docker-compose.yml)'
|
|
44
44
|
option :force, type: :boolean, default: false, desc: 'Overwrite an existing wip.yml'
|
|
45
|
+
option :template, type: :string,
|
|
46
|
+
desc: "sync.exclude preset: #{Initializer::TEMPLATE_LABELS.keys.join(', ')} (default: none)"
|
|
45
47
|
def init
|
|
46
48
|
path = Pathname(options[:config] || ConfigLoader::FILENAME).expand_path
|
|
47
49
|
raise Error, "#{path} already exists (use --force to overwrite)" if path.file? && !options[:force]
|
|
48
50
|
|
|
49
|
-
initializer = Initializer.new(dir: path.dirname)
|
|
51
|
+
initializer = Initializer.new(dir: path.dirname, template: options[:template])
|
|
50
52
|
path.write(initializer.call)
|
|
51
53
|
warn "wip: wrote #{path} (mode: #{initializer.compose? ? 'compose-native' : 'container'})"
|
|
52
54
|
end
|
|
@@ -292,6 +294,7 @@ module Wip
|
|
|
292
294
|
def ensure_compose_images
|
|
293
295
|
load_config.compose_build_specs.each_value do |spec|
|
|
294
296
|
extra = spec['dockerfile'] ? ['-f', spec['dockerfile']] : []
|
|
297
|
+
spec['args']&.each { |key, value| extra.push('--build-arg', "#{key}=#{value}") }
|
|
295
298
|
execute(builder.build(settings: { 'context' => spec['context'], 'tag' => spec['tag'] }, extra: extra))
|
|
296
299
|
end
|
|
297
300
|
end
|
data/lib/wip/command_builder.rb
CHANGED
|
@@ -159,17 +159,26 @@ module Wip
|
|
|
159
159
|
private
|
|
160
160
|
|
|
161
161
|
def options(values, include_container: false, include_publish: true, sync: true)
|
|
162
|
-
result =
|
|
163
|
-
result.push('-w', values['workdir']) unless values['workdir'].to_s.empty?
|
|
162
|
+
result = scalar_options(values)
|
|
164
163
|
merged_env(values).each { |key, value| result.push('-e', "#{key}=#{value}") }
|
|
165
|
-
if include_publish
|
|
166
|
-
Array(values['ports']).each { |port| result.push('-p', port.to_s) }
|
|
167
|
-
volume_specs(values, sync: sync).each { |volume| result.push('-v', volume) }
|
|
168
|
-
end
|
|
164
|
+
result.concat(publish_options(values, sync: sync)) if include_publish
|
|
169
165
|
result << required(values, 'container') if include_container
|
|
170
166
|
result
|
|
171
167
|
end
|
|
172
168
|
|
|
169
|
+
def scalar_options(values)
|
|
170
|
+
result = []
|
|
171
|
+
result.push('-w', values['workdir']) unless values['workdir'].to_s.empty?
|
|
172
|
+
result.push('-u', values['user']) unless values['user'].to_s.empty?
|
|
173
|
+
result
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def publish_options(values, sync:)
|
|
177
|
+
result = Array(values['ports']).flat_map { |port| ['-p', port.to_s] }
|
|
178
|
+
volume_specs(values, sync: sync).each { |volume| result.push('-v', volume) }
|
|
179
|
+
result
|
|
180
|
+
end
|
|
181
|
+
|
|
173
182
|
# With sync configured, a live bind mount of the target (`.:/app`) is
|
|
174
183
|
# swapped for the read-only source mount plus the named volume, so the
|
|
175
184
|
# running app only ever touches the volume.
|
data/lib/wip/compose_file.rb
CHANGED
|
@@ -15,10 +15,19 @@ module Wip
|
|
|
15
15
|
# "Compose mode (native)") — once wslc ships that support, or a
|
|
16
16
|
# compose-for-wslc tool reliably supports `run`.
|
|
17
17
|
class ComposeFile
|
|
18
|
-
Service = Struct.new(:image, :build, :command, :env, :ports, :volumes, :workdir, :
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
Service = Struct.new(:image, :build, :command, :env, :ports, :volumes, :workdir, :user, :depends_on,
|
|
19
|
+
keyword_init: true)
|
|
20
|
+
|
|
21
|
+
SERVICE_KEYS = %w[image build command environment ports volumes working_dir user depends_on].freeze
|
|
22
|
+
# Real Compose keys that read as meaningful here but have nothing to map onto: TTY/stdin
|
|
23
|
+
# allocation is already decided per invocation (see CommandBuilder#tty?), not fixed per
|
|
24
|
+
# service; there's no per-service network to join beyond the one project network `network`
|
|
25
|
+
# (config.rb) already puts every service on; `wslc run`/`exec` has no restart-policy or
|
|
26
|
+
# capability flag to forward `restart:`/`cap_add:` to; and `profiles:` gates which services a
|
|
27
|
+
# real `docker compose up` starts by default, which doesn't apply here since wip never starts
|
|
28
|
+
# "every service" — only `compose.service` and whatever it reaches via `depends_on`.
|
|
29
|
+
IGNORED_SERVICE_KEYS = %w[tty stdin_open networks restart cap_add profiles].freeze
|
|
30
|
+
BUILD_KEYS = %w[context dockerfile args].freeze
|
|
22
31
|
SUPPORTED_CONDITIONS = %w[service_started].freeze
|
|
23
32
|
|
|
24
33
|
def self.load(path)
|
|
@@ -44,10 +53,10 @@ module Wip
|
|
|
44
53
|
# name => {context:, dockerfile:, tag:} for every service with a build:.
|
|
45
54
|
def build_specs
|
|
46
55
|
@order.filter_map do |name|
|
|
47
|
-
|
|
48
|
-
next unless build
|
|
56
|
+
service = @services.fetch(name)
|
|
57
|
+
next unless service.build
|
|
49
58
|
|
|
50
|
-
[name, build.merge('tag' => image_tag(name))]
|
|
59
|
+
[name, service.build.merge('tag' => image_tag(name, service))]
|
|
51
60
|
end.to_h
|
|
52
61
|
end
|
|
53
62
|
|
|
@@ -56,35 +65,41 @@ module Wip
|
|
|
56
65
|
def to_dependencies_hash
|
|
57
66
|
@order.to_h do |name|
|
|
58
67
|
service = @services.fetch(name)
|
|
59
|
-
[name, { 'image' => service.build ? image_tag(name) : service.image, 'command' => service.command,
|
|
68
|
+
[name, { 'image' => service.build ? image_tag(name, service) : service.image, 'command' => service.command,
|
|
60
69
|
'env' => service.env, 'ports' => service.ports, 'volumes' => service.volumes,
|
|
61
|
-
'workdir' => service.workdir }]
|
|
70
|
+
'workdir' => service.workdir, 'user' => service.user }]
|
|
62
71
|
end
|
|
63
72
|
end
|
|
64
73
|
|
|
65
74
|
private
|
|
66
75
|
|
|
67
|
-
|
|
76
|
+
# A service naming both `build:` and `image:` builds via the former and tags the
|
|
77
|
+
# result with the latter (real Compose's own rule for that combination), instead
|
|
78
|
+
# of the auto-generated tag a build:-only service gets.
|
|
79
|
+
def image_tag(name, service) = service.image || "wip-compose-#{name}:latest"
|
|
68
80
|
|
|
69
81
|
def build_service(name, entry)
|
|
70
82
|
raise ConfigError, "#{@path}: services.#{name} must be a mapping" unless entry.is_a?(Hash)
|
|
71
83
|
|
|
72
|
-
unknown = entry.keys.map(&:to_s) - SERVICE_KEYS
|
|
84
|
+
unknown = entry.keys.map(&:to_s) - SERVICE_KEYS - IGNORED_SERVICE_KEYS
|
|
73
85
|
raise ConfigError, "#{@path}: services.#{name} has unsupported key(s): #{unknown.join(', ')}" if unknown.any?
|
|
74
86
|
|
|
75
87
|
image, build = image_or_build(name, entry)
|
|
76
88
|
Service.new(image: image, build: build, command: normalize_command(entry['command']),
|
|
77
|
-
env: normalize_env(name, entry['environment']),
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
89
|
+
env: normalize_env(name, entry['environment']), **normalize_service_lists(name, entry),
|
|
90
|
+
workdir: presence(entry['working_dir']), user: presence(entry['user']),
|
|
91
|
+
depends_on: normalize_depends_on(name, entry['depends_on']))
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def normalize_service_lists(name, entry)
|
|
95
|
+
{ ports: normalize_list(name, entry['ports'], 'ports'),
|
|
96
|
+
volumes: normalize_list(name, entry['volumes'], 'volumes') }
|
|
81
97
|
end
|
|
82
98
|
|
|
83
99
|
def image_or_build(name, entry)
|
|
84
100
|
image = presence(entry['image'])
|
|
85
101
|
build = normalize_build(name, entry['build'])
|
|
86
102
|
raise ConfigError, "#{@path}: services.#{name} must set image or build" unless image || build
|
|
87
|
-
raise ConfigError, "#{@path}: services.#{name} must not set both image and build" if image && build
|
|
88
103
|
|
|
89
104
|
[image, build]
|
|
90
105
|
end
|
|
@@ -118,27 +133,33 @@ module Wip
|
|
|
118
133
|
|
|
119
134
|
context = resolve_context(presence(value['context']) || '.')
|
|
120
135
|
dockerfile = presence(value['dockerfile'])
|
|
121
|
-
{ 'context' => context, 'dockerfile' => dockerfile && Pathname(context).join(dockerfile).to_s
|
|
136
|
+
{ 'context' => context, 'dockerfile' => dockerfile && Pathname(context).join(dockerfile).to_s,
|
|
137
|
+
'args' => normalize_kv(name, value['args'], 'build.args') }.compact
|
|
122
138
|
end
|
|
123
139
|
|
|
124
140
|
# build.context is relative to compose.yml's own directory (Compose's own rule),
|
|
125
141
|
# not wherever `wip` happens to be invoked from.
|
|
126
142
|
def resolve_context(raw) = Pathname(@path).expand_path.dirname.join(raw).to_s
|
|
127
143
|
|
|
128
|
-
def normalize_env(name, value)
|
|
144
|
+
def normalize_env(name, value) = normalize_kv(name, value, 'environment')
|
|
145
|
+
|
|
146
|
+
# Shared by `environment:` and `build.args:` — both accept a mapping or a
|
|
147
|
+
# KEY=VALUE array, and neither supports host environment pass-through (a
|
|
148
|
+
# null mapping value, or a bare KEY with no `=`).
|
|
149
|
+
def normalize_kv(name, value, label)
|
|
129
150
|
return {} unless value
|
|
130
151
|
|
|
131
152
|
case value
|
|
132
|
-
when Hash then
|
|
133
|
-
when Array then
|
|
134
|
-
else raise ConfigError, "#{@path}: services.#{name}
|
|
153
|
+
when Hash then normalize_kv_mapping(name, value, label)
|
|
154
|
+
when Array then normalize_kv_array(name, value, label)
|
|
155
|
+
else raise ConfigError, "#{@path}: services.#{name}.#{label} must be a mapping or an array of KEY=VALUE"
|
|
135
156
|
end
|
|
136
157
|
end
|
|
137
158
|
|
|
138
|
-
def
|
|
159
|
+
def normalize_kv_mapping(name, value, label)
|
|
139
160
|
value.to_h do |key, val|
|
|
140
161
|
if val.nil?
|
|
141
|
-
raise ConfigError, "#{@path}: services.#{name}
|
|
162
|
+
raise ConfigError, "#{@path}: services.#{name}.#{label}.#{key} must have a value " \
|
|
142
163
|
'(host environment pass-through is not supported)'
|
|
143
164
|
end
|
|
144
165
|
|
|
@@ -146,10 +167,10 @@ module Wip
|
|
|
146
167
|
end
|
|
147
168
|
end
|
|
148
169
|
|
|
149
|
-
def
|
|
170
|
+
def normalize_kv_array(name, value, label)
|
|
150
171
|
value.to_h do |item|
|
|
151
172
|
key, val = item.to_s.split('=', 2)
|
|
152
|
-
raise ConfigError, "#{@path}: services.#{name}
|
|
173
|
+
raise ConfigError, "#{@path}: services.#{name}.#{label} entries must be KEY=VALUE" unless val
|
|
153
174
|
|
|
154
175
|
[key, val]
|
|
155
176
|
end
|
data/lib/wip/config.rb
CHANGED
|
@@ -7,7 +7,7 @@ module Wip
|
|
|
7
7
|
class Config
|
|
8
8
|
# Applied to every dependencies: entry, primary container included — there
|
|
9
9
|
# is no separate, differently-shaped bucket for "the one you exec into."
|
|
10
|
-
DEPENDENCY_DEFAULTS = { 'workdir' => nil, 'interactive' => false, 'remove' => true,
|
|
10
|
+
DEPENDENCY_DEFAULTS = { 'workdir' => nil, 'user' => nil, 'interactive' => false, 'remove' => true,
|
|
11
11
|
'env' => {}, 'ports' => [], 'volumes' => [] }.freeze
|
|
12
12
|
SECRET_PATTERN = /token|password|secret|credential|auth/i
|
|
13
13
|
# Which orchestration path `up`/`down`/`sync`/etc. take. Explicit rather than
|
data/lib/wip/initializer.rb
CHANGED
|
@@ -8,72 +8,54 @@ module Wip
|
|
|
8
8
|
# mode: compose-native and mode: container, since that's the one decision
|
|
9
9
|
# `wip init` can't leave to a placeholder.
|
|
10
10
|
class Initializer
|
|
11
|
-
#
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
11
|
+
# --template values wip init accepts, and the label used in the exclude: comment.
|
|
12
|
+
TEMPLATE_LABELS = {
|
|
13
|
+
'rails' => 'Rails',
|
|
14
|
+
'node' => 'Node.js',
|
|
15
|
+
'rust' => 'Rust',
|
|
16
|
+
'csharp' => 'C#'
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
# sync.exclude patterns written live for each --template. They mirror that
|
|
20
|
+
# stack's own github/gitignore template — directories that are either
|
|
21
|
+
# regenerated inside the container or too large/irrelevant to mirror over rsync.
|
|
22
|
+
TEMPLATE_EXCLUDES = {
|
|
23
|
+
'rails' => %w[.git log/ tmp/ storage/ public/assets/ public/packs/ .bundle/ vendor/bundle/
|
|
24
|
+
coverage/ node_modules/],
|
|
25
|
+
'node' => %w[.git node_modules/ dist/ build/ .next/ .cache/ coverage/],
|
|
26
|
+
'rust' => %w[.git target/],
|
|
27
|
+
'csharp' => %w[.git bin/ obj/ .vs/ packages/]
|
|
28
|
+
}.freeze
|
|
29
|
+
|
|
30
|
+
# Used when --template is omitted — the same starting point the README's own
|
|
31
|
+
# example uses.
|
|
32
|
+
FALLBACK_EXCLUDES = %w[.git tmp/ node_modules/].freeze
|
|
33
33
|
|
|
34
34
|
# Appended to both templates after dependencies/compose. commands: itself is left
|
|
35
35
|
# commented since an empty commands: {} is indistinguishable from omitting the key.
|
|
36
36
|
COMMANDS_EXAMPLE = <<~YAML.chomp
|
|
37
|
-
#
|
|
37
|
+
# optional; custom subcommands, e.g. `wip test` — see README
|
|
38
|
+
# commands:
|
|
38
39
|
# test:
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
YAML
|
|
42
|
-
|
|
43
|
-
CONTAINER_TEMPLATE = <<~YAML.freeze
|
|
44
|
-
version: 1
|
|
45
|
-
mode: container # container | compose | compose-native (see README)
|
|
46
|
-
container: app # TODO: rename freely, as long as it matches a key under dependencies: below
|
|
47
|
-
|
|
48
|
-
wslc:
|
|
49
|
-
command: auto # optional; wslc binary/path wip shells out to ("auto" resolves it for you)
|
|
40
|
+
# optional; exec (default, runs in the running container) | run (fresh container) | build
|
|
41
|
+
# type: exec
|
|
50
42
|
|
|
51
|
-
#
|
|
52
|
-
|
|
53
|
-
dependencies:
|
|
54
|
-
app: # this is the container wip creates, execs into, and runs commands in
|
|
55
|
-
image: your/image:tag # TODO: image to run
|
|
56
|
-
workdir: /app # TODO: adjust to match your image, or delete this line
|
|
57
|
-
interactive: false # optional; keep stdin open / allocate a tty
|
|
58
|
-
remove: true # optional; remove the container after each run
|
|
59
|
-
env: {} # optional; environment variables passed to the container, e.g. {FOO: bar}
|
|
60
|
-
ports: [] # optional; published ports, e.g. ["3000:3000"]
|
|
61
|
-
volumes: [] # optional; extra -v specs beyond the sync mounts below
|
|
62
|
-
|
|
63
|
-
#{COMMANDS_EXAMPLE}
|
|
64
|
-
|
|
65
|
-
sync: # optional; mirrors the source into a named volume instead of bind-mounting it live
|
|
66
|
-
#{SYNC_EXTRAS}
|
|
43
|
+
# TODO
|
|
44
|
+
# command: bundle exec rspec
|
|
67
45
|
YAML
|
|
68
46
|
|
|
69
|
-
def initialize(dir: Dir.pwd)
|
|
47
|
+
def initialize(dir: Dir.pwd, template: nil)
|
|
48
|
+
raise Error, "unknown --template #{template.inspect} (valid: #{TEMPLATE_LABELS.keys.join(', ')})" \
|
|
49
|
+
if template && !TEMPLATE_LABELS.key?(template)
|
|
50
|
+
|
|
70
51
|
@dir = dir
|
|
52
|
+
@template = template
|
|
71
53
|
end
|
|
72
54
|
|
|
73
55
|
def compose? = !!compose_file
|
|
74
56
|
|
|
75
57
|
def call
|
|
76
|
-
compose? ? compose_template :
|
|
58
|
+
compose? ? compose_template : container_template
|
|
77
59
|
end
|
|
78
60
|
|
|
79
61
|
private
|
|
@@ -87,29 +69,156 @@ module Wip
|
|
|
87
69
|
# without duplicating it as a live value that would go stale if the directory moves.
|
|
88
70
|
def compose_project_default = Pathname(@dir).basename.to_s
|
|
89
71
|
|
|
72
|
+
def exclude_comment
|
|
73
|
+
if @template
|
|
74
|
+
"# optional; rsync --exclude patterns picked for --template #{@template} (#{TEMPLATE_LABELS[@template]})"
|
|
75
|
+
else
|
|
76
|
+
'# optional; rsync --exclude patterns, e.g. ["node_modules", "*.log"]'
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def exclude_list
|
|
81
|
+
patterns = @template ? TEMPLATE_EXCLUDES.fetch(@template) : FALLBACK_EXCLUDES
|
|
82
|
+
patterns.map { |pattern| " - #{pattern}" }.join("\n")
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Appended to sync: in both templates. Every key here is either a plain constant
|
|
86
|
+
# default (safe to state literally) or, where commented, a value SyncSettings
|
|
87
|
+
# derives from another key (target from workdir, volume from the container/service
|
|
88
|
+
# name) — those stay unset so they keep tracking that key instead of going stale.
|
|
89
|
+
# A blank line separates every key from its neighbor; the gsub only indents
|
|
90
|
+
# non-blank lines so those separators stay actual blank lines, not trailing
|
|
91
|
+
# whitespace.
|
|
92
|
+
def sync_extras
|
|
93
|
+
<<~YAML.chomp.gsub(/^(?=.)/, ' ')
|
|
94
|
+
# optional; host path to mirror (default: wip.yml directory)
|
|
95
|
+
source: .
|
|
96
|
+
|
|
97
|
+
# optional; in-container mount point for the mirror (default: the container's workdir, else /app)
|
|
98
|
+
# target: /app
|
|
99
|
+
|
|
100
|
+
# optional; read-only host mount inside the container
|
|
101
|
+
mount: /host-src
|
|
102
|
+
|
|
103
|
+
# optional; named volume holding the mirrored tree (default: "<container>-src")
|
|
104
|
+
# volume: app-src
|
|
105
|
+
|
|
106
|
+
# optional; rsync --delete so removals on the host are mirrored too
|
|
107
|
+
delete: true
|
|
108
|
+
|
|
109
|
+
#{exclude_comment}
|
|
110
|
+
exclude:
|
|
111
|
+
#{exclude_list}
|
|
112
|
+
|
|
113
|
+
# optional; mirror binary to shell out to
|
|
114
|
+
command: rsync
|
|
115
|
+
|
|
116
|
+
# optional; extra flags appended to the mirror command
|
|
117
|
+
options: []
|
|
118
|
+
|
|
119
|
+
# optional; seconds between mirrors
|
|
120
|
+
interval: 2
|
|
121
|
+
|
|
122
|
+
# optional; exec (default, mirrors into the running container) | run (always a fresh container)
|
|
123
|
+
mode: exec
|
|
124
|
+
|
|
125
|
+
# only needed if sync.mode: run (or under mode: compose); image the mirror container runs
|
|
126
|
+
# image: your/image:tag
|
|
127
|
+
|
|
128
|
+
# alternative to image — let wip build a small dedicated mirror image itself
|
|
129
|
+
# build:
|
|
130
|
+
# dockerfile: |
|
|
131
|
+
# FROM alpine:latest
|
|
132
|
+
# RUN apk add --no-cache rsync
|
|
133
|
+
|
|
134
|
+
# optional (default: wip-sync-<container>:latest)
|
|
135
|
+
# tag: wip-sync-app:latest
|
|
136
|
+
YAML
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def container_template
|
|
140
|
+
<<~YAML
|
|
141
|
+
version: 1
|
|
142
|
+
|
|
143
|
+
# container | compose | compose-native (see README)
|
|
144
|
+
mode: container
|
|
145
|
+
|
|
146
|
+
# TODO: rename freely, as long as it matches a key under dependencies: below
|
|
147
|
+
container: app
|
|
148
|
+
|
|
149
|
+
wslc:
|
|
150
|
+
# optional; wslc binary/path wip shells out to ("auto" resolves it for you)
|
|
151
|
+
command: auto
|
|
152
|
+
|
|
153
|
+
# optional; container network name (mode: container only)
|
|
154
|
+
# network: my-network
|
|
155
|
+
|
|
156
|
+
dependencies:
|
|
157
|
+
# this is the container wip creates, execs into, and runs commands in
|
|
158
|
+
app:
|
|
159
|
+
# TODO: image to run
|
|
160
|
+
image: your/image:tag
|
|
161
|
+
|
|
162
|
+
# TODO: adjust to match your image, or delete this line
|
|
163
|
+
workdir: /app
|
|
164
|
+
|
|
165
|
+
# optional; keep stdin open / allocate a tty
|
|
166
|
+
interactive: false
|
|
167
|
+
|
|
168
|
+
# optional; remove the container after each run
|
|
169
|
+
remove: true
|
|
170
|
+
|
|
171
|
+
# optional; environment variables passed to the container, e.g. {FOO: bar}
|
|
172
|
+
env: {}
|
|
173
|
+
|
|
174
|
+
# optional; published ports, e.g. ["3000:3000"]
|
|
175
|
+
ports: []
|
|
176
|
+
|
|
177
|
+
# optional; extra -v specs beyond the sync mounts below
|
|
178
|
+
volumes: []
|
|
179
|
+
|
|
180
|
+
#{COMMANDS_EXAMPLE}
|
|
181
|
+
|
|
182
|
+
# optional; mirrors the source into a named volume instead of bind-mounting it live
|
|
183
|
+
sync:
|
|
184
|
+
#{sync_extras}
|
|
185
|
+
YAML
|
|
186
|
+
end
|
|
187
|
+
|
|
90
188
|
def compose_template
|
|
91
189
|
<<~YAML
|
|
92
190
|
version: 1
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
191
|
+
|
|
192
|
+
# wip parses #{compose_file} itself and drives wslc directly — no external
|
|
193
|
+
# compose-for-wslc binary needed. Prefer a real compose tool instead? Use
|
|
194
|
+
# mode: compose (see README "Compose mode") and set compose.command.
|
|
195
|
+
mode: compose-native
|
|
96
196
|
|
|
97
197
|
wslc:
|
|
98
|
-
|
|
198
|
+
# optional; wslc binary/path wip shells out to ("auto" resolves it for you)
|
|
199
|
+
command: auto
|
|
99
200
|
|
|
100
201
|
compose:
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
#
|
|
202
|
+
# TODO: which service in #{compose_file} wip run/exec/NAME target
|
|
203
|
+
service: app
|
|
204
|
+
|
|
205
|
+
# optional; override which compose file wip parses (default: auto-detected)
|
|
206
|
+
# file: #{compose_file}
|
|
207
|
+
|
|
208
|
+
# optional; project/network name (default: this directory's name)
|
|
209
|
+
# project: #{compose_project_default}
|
|
210
|
+
|
|
211
|
+
# only used under mode: compose (external compose-for-wslc binary); unused here under compose-native
|
|
212
|
+
# command:
|
|
105
213
|
|
|
106
214
|
# network: is derived from compose.project above (or this directory's name) — setting it
|
|
107
215
|
# directly conflicts with compose: (ConfigError), so it's intentionally left out here.
|
|
108
216
|
|
|
109
217
|
#{COMMANDS_EXAMPLE}
|
|
110
218
|
|
|
111
|
-
|
|
112
|
-
|
|
219
|
+
# optional; mirrors the source into a named volume instead of bind-mounting it live
|
|
220
|
+
sync:
|
|
221
|
+
#{sync_extras}
|
|
113
222
|
YAML
|
|
114
223
|
end
|
|
115
224
|
end
|
data/lib/wip/version.rb
CHANGED