Class: Wip::CLI
- Inherits:
-
Thor
- Object
- Thor
- Wip::CLI
- Defined in:
- lib/wip/cli.rb
Overview
Thor-based command-line interface for wip.
Constant Summary collapse
- GLOBAL_SWITCHES =
Thor only recognizes class_options (--config, --debug, ...) once they appear after the command name (
wip up --config foo). Pull a leading run of them off the front of ARGV and reinsert it right after the command name sowip --config foo upworks too. { '--config' => :value, '--env-file' => :value, '--debug' => :flag, '--debug-log' => :value }.freeze
- AUTO_RESTART_POLICIES =
Real Compose values that trigger a restart when a container's exited;
no(the default) and anything unrecognized both mean "leave it alone." Exact match only — a typo'd value like "always-invalid" must stay inert, not accidentally match via a loose prefix check. %w[always unless-stopped].freeze
- ON_FAILURE_POLICY =
optional
:MAX_RETRIESsuffix, e.g. "on-failure:3" /\Aon-failure(?::\d+)?\z/- WSLC_CONTAINER_STATE_EXITED =
WSLC's
WslcContainerState(confirmed via microsoft/WSL's own docs and ContainerModel.h — ContainerInformation#State has no custom JSON enum serializer, so nlohmann::json emits its raw ordinal): 0 invalid, 1 created, 2 running, 3 exited, 4 deleted. Unlike Docker, there's no separate "dead" state — onlyexitedis a live, restartable exit;deletedmeans the container itself is gone (needswip upto recreate it, notstart). 3
Class Method Summary collapse
-
.dispatch(meth, given_args, given_opts, config) ⇒ Object
Thor only falls back to the default task when no command name is given at all; an unrecognized first argument (e.g. a custom wip.yml command name) would otherwise raise "Could not find command".
- .exit_on_failure? ⇒ Boolean
- .reorder_global_options(args) ⇒ Object
- .start(given_args = ARGV, config = {}) ⇒ Object
Instance Method Summary collapse
- #build(*extra) ⇒ Object
- #config ⇒ Object
- #dispatch(name = nil, *arguments) ⇒ Object
- #doctor ⇒ Object
- #down ⇒ Object
- #exec(*command) ⇒ Object
- #init ⇒ Object
- #logs(*services) ⇒ Object
- #run_command(*command) ⇒ Object
- #shell_command ⇒ Object
- #stop ⇒ Object
- #sync ⇒ Object
- #up ⇒ Object
- #version ⇒ Object
Class Method Details
.dispatch(meth, given_args, given_opts, config) ⇒ Object
Thor only falls back to the default task when no command name is given at
all; an unrecognized first argument (e.g. a custom wip.yml command name)
would otherwise raise "Could not find command". Route those to dispatch.
60 61 62 63 64 65 66 67 |
# File 'lib/wip/cli.rb', line 60 def self.dispatch(meth, given_args, given_opts, config) name = given_args.first if meth.nil? && name && !name.to_s.start_with?('-') && find_command_possibilities(name).empty? return super('dispatch', given_args, given_opts, config) end super end |
.exit_on_failure? ⇒ Boolean
55 |
# File 'lib/wip/cli.rb', line 55 def self.exit_on_failure? = true |
.reorder_global_options(args) ⇒ Object
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/wip/cli.rb', line 34 def self.(args) remaining = args.dup extracted = [] until remaining.empty? name, _, inline_value = remaining.first.to_s.partition('=') kind = GLOBAL_SWITCHES[name] break unless kind switch = remaining.shift extracted << switch extracted << remaining.shift if kind == :value && inline_value.empty? && !remaining.empty? end command_index = remaining.index { |arg| !arg.start_with?('-') } return args if command_index.nil? remaining.insert(command_index + 1, *extracted) remaining end |
.start(given_args = ARGV, config = {}) ⇒ Object
30 31 32 |
# File 'lib/wip/cli.rb', line 30 def self.start(given_args = ARGV, config = {}) super((given_args), config) end |
Instance Method Details
#build(*extra) ⇒ Object
106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/wip/cli.rb', line 106 def build(*extra) extra = (extra) settings = load_config.command('build') || {} context = Pathname(load_config.path).dirname.join(settings['context'] || '.').to_s warn "wip: staging build context (#{context})" progress = StagingProgress.new build_context = BuildContext.new(context, shadow_root: settings['shadow_context']) build_context.stage(on_progress: progress.method(:tick)) do |staged_context| progress.finish run_staged_build(build_context, staged_context, settings, extra) end ensure progress&.finish end |
#config ⇒ Object
100 101 102 |
# File 'lib/wip/cli.rb', line 100 def config puts YAML.dump(load_config.to_h) end |
#dispatch(name = nil, *arguments) ⇒ Object
252 253 254 255 256 257 258 259 |
# File 'lib/wip/cli.rb', line 252 def dispatch(name = nil, *arguments) return help if name.nil? values = load_config.command(name) || raise(ConfigError, "Unknown command: #{name}") return dispatch_compose(name, values, arguments) if load_config.compose? execute(builder.custom(name, arguments), interactive: tty?(values.fetch('interactive', false))) end |
#doctor ⇒ Object
93 94 95 96 97 |
# File 'lib/wip/cli.rb', line 93 def doctor results = Doctor.new(loader: loader).call results.each { |item| puts "[#{item.level.to_s.upcase}] #{item.}" } exit(1) if results.any? { |item| item.level == :fail } end |
#down ⇒ Object
189 190 191 192 193 194 195 196 |
# File 'lib/wip/cli.rb', line 189 def down return execute(compose_bridge.down, exit_on_failure: false) if load_config.compose? execute(builder.remove, exit_on_failure: false) sidecar_names.each do |name| execute(builder.dependency_remove(name), exit_on_failure: false) end end |
#exec(*command) ⇒ Object
200 201 202 |
# File 'lib/wip/cli.rb', line 200 def exec(*command) execute(exec_target(command, interactive: [:interactive]), interactive: tty?([:interactive])) end |
#init ⇒ Object
83 84 85 86 87 88 89 90 |
# File 'lib/wip/cli.rb', line 83 def init path = Pathname([:config] || ConfigLoader::FILENAME). raise Error, "#{path} already exists (use --force to overwrite)" if path.file? && ![:force] initializer = Initializer.new(dir: path.dirname, template: [:template]) path.write(initializer.call) warn "wip: wrote #{path} (mode: #{initializer.compose? ? 'compose-native' : 'container'})" end |
#logs(*services) ⇒ Object
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 |
# File 'lib/wip/cli.rb', line 232 def logs(*services) unless load_config.compose? || load_config.compose_native? raise ConfigError, '`wip logs` is only available in compose mode' end if load_config.compose? return execute(compose_bridge.logs(services: services, follow: [:follow]), interactive: true) end # wslc has no compose-style multi-service log aggregation (see CommandBuilder#logs) — # exactly one SERVICE is required, defaulting to compose.service when none is given. if services.size > 1 raise ConfigError, '`wip logs` under mode: compose-native takes at most one SERVICE ' \ '(wslc logs, unlike a real compose tool, only follows one container at a time)' end execute(builder.logs(services.first || load_config.compose_service, follow: [:follow]), interactive: true) end |
#run_command(*command) ⇒ Object
206 207 208 209 210 211 212 213 214 215 |
# File 'lib/wip/cli.rb', line 206 def run_command(*command) if load_config.compose? warn "wip: compose mode has no ephemeral 'run'; executing in the running " \ "'#{load_config.compose_service}' service instead" return execute(exec_target(command, interactive: [:interactive]), interactive: tty?([:interactive])) end execute(builder.run(command, interactive: [:interactive]), interactive: tty?([:interactive])) end |
#shell_command ⇒ Object
219 220 221 222 223 224 225 226 227 |
# File 'lib/wip/cli.rb', line 219 def shell_command configured = load_config.command('shell') return dispatch('shell') if configured code = execute(exec_target(['bash'], interactive: true), interactive: tty?(true), exit_on_failure: false) return if code.zero? execute(exec_target(['sh'], interactive: true), interactive: tty?(true)) end |
#stop ⇒ Object
179 180 181 182 183 184 185 186 |
# File 'lib/wip/cli.rb', line 179 def stop return execute(compose_bridge.stop, exit_on_failure: false) if load_config.compose? execute(builder.stop, exit_on_failure: false) sidecar_names.each do |name| execute(builder.dependency_stop(name), exit_on_failure: false) end end |
#sync ⇒ Object
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
# File 'lib/wip/cli.rb', line 161 def sync settings = sync_settings! warn_shadowed_command('sync') ensure_sync_image(settings) return run_sync unless [:watch] interval = watch_interval(settings) warn "wip: syncing #{settings.source} -> #{settings.volume}:#{settings.target} " \ "every #{interval}s (Ctrl-C to stop)" loop do run_sync(exit_on_failure: false) sleep interval end rescue Interrupt warn "\nwip: sync stopped" end |
#up ⇒ Object
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
# File 'lib/wip/cli.rb', line 142 def up return up_via_compose_bridge if load_config.compose? # Validated up front, before any startup side effect (image build, network/dependency/ # container creation) — otherwise a bad --interval would only surface as a ConfigError # after already bringing everything up. interval = restart_interval if [:watch] ensure_compose_images ensure_network sidecar_names.each { |name| ensure_dependency(name) } sync_before_boot if [:sync] ensure_container watch_restarts(interval) if [:watch] end |