vagrant-orbstack-provider 0.1.1
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 +7 -0
- data/CHANGELOG.md +175 -0
- data/LICENSE +21 -0
- data/README.md +628 -0
- data/lib/vagrant-orbstack/action/create.rb +177 -0
- data/lib/vagrant-orbstack/action/destroy.rb +90 -0
- data/lib/vagrant-orbstack/action/halt.rb +64 -0
- data/lib/vagrant-orbstack/action/machine_validation.rb +60 -0
- data/lib/vagrant-orbstack/action/reload.rb +56 -0
- data/lib/vagrant-orbstack/action/ssh_run.rb +70 -0
- data/lib/vagrant-orbstack/action/start.rb +68 -0
- data/lib/vagrant-orbstack/action.rb +21 -0
- data/lib/vagrant-orbstack/config.rb +161 -0
- data/lib/vagrant-orbstack/errors.rb +64 -0
- data/lib/vagrant-orbstack/plugin.rb +45 -0
- data/lib/vagrant-orbstack/provider.rb +460 -0
- data/lib/vagrant-orbstack/util/machine_namer.rb +130 -0
- data/lib/vagrant-orbstack/util/orbstack_cli.rb +283 -0
- data/lib/vagrant-orbstack/util/ssh_readiness_checker.rb +115 -0
- data/lib/vagrant-orbstack/util/state_cache.rb +106 -0
- data/lib/vagrant-orbstack/version.rb +7 -0
- data/lib/vagrant-orbstack-provider.rb +6 -0
- data/lib/vagrant-orbstack.rb +36 -0
- data/locales/en.yml +49 -0
- metadata +71 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'open3'
|
|
4
|
+
require 'timeout'
|
|
5
|
+
require 'json'
|
|
6
|
+
require 'vagrant-orbstack/errors'
|
|
7
|
+
|
|
8
|
+
module VagrantPlugins
|
|
9
|
+
module OrbStack
|
|
10
|
+
module Util
|
|
11
|
+
# Utility class for detecting and interacting with OrbStack CLI
|
|
12
|
+
#
|
|
13
|
+
# This class provides a Ruby interface to the OrbStack command-line tool (`orb`).
|
|
14
|
+
# All methods execute shell commands and return parsed results. The class handles
|
|
15
|
+
# timeouts, error detection, and logging automatically.
|
|
16
|
+
#
|
|
17
|
+
# @example Check if OrbStack is available
|
|
18
|
+
# if OrbStackCLI.available?
|
|
19
|
+
# puts "OrbStack version: #{OrbStackCLI.version}"
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# @example Create and manage a machine
|
|
23
|
+
# OrbStackCLI.create_machine('ubuntu', 'my-dev-vm')
|
|
24
|
+
# OrbStackCLI.start_machine('my-dev-vm')
|
|
25
|
+
# info = OrbStackCLI.machine_info('my-dev-vm')
|
|
26
|
+
# OrbStackCLI.stop_machine('my-dev-vm')
|
|
27
|
+
#
|
|
28
|
+
# @example List all machines
|
|
29
|
+
# machines = OrbStackCLI.list_machines
|
|
30
|
+
# machines.each do |m|
|
|
31
|
+
# puts "#{m[:name]} - #{m[:status]}"
|
|
32
|
+
# end
|
|
33
|
+
class OrbStackCLI
|
|
34
|
+
# Default timeout for non-mutating query operations (list, info).
|
|
35
|
+
# Use this for fast read operations.
|
|
36
|
+
# @return [Integer] Timeout in seconds
|
|
37
|
+
QUERY_TIMEOUT = 30
|
|
38
|
+
|
|
39
|
+
# Default timeout for state-changing operations (start, stop, delete).
|
|
40
|
+
# Use this for operations that modify machine state.
|
|
41
|
+
# @return [Integer] Timeout in seconds
|
|
42
|
+
MUTATE_TIMEOUT = 60
|
|
43
|
+
|
|
44
|
+
# Default timeout for machine creation operations.
|
|
45
|
+
# Longer timeout accounts for distribution image downloads which
|
|
46
|
+
# can take 30-120 seconds on first use.
|
|
47
|
+
# @return [Integer] Timeout in seconds
|
|
48
|
+
CREATE_TIMEOUT = 120
|
|
49
|
+
|
|
50
|
+
@logger = Log4r::Logger.new('vagrant_orbstack::util')
|
|
51
|
+
|
|
52
|
+
class << self
|
|
53
|
+
# Check if orb command is available in PATH
|
|
54
|
+
# @return [Boolean] true if orb command exists, false otherwise
|
|
55
|
+
def available?
|
|
56
|
+
stdout, _stderr, success = execute_command('which orb')
|
|
57
|
+
!stdout.empty? && success
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Get OrbStack version
|
|
61
|
+
# @return [String, nil] version string or nil if not available
|
|
62
|
+
def version
|
|
63
|
+
stdout, _stderr, success = execute_command('orb --version')
|
|
64
|
+
return nil unless success
|
|
65
|
+
|
|
66
|
+
# Parse version from output like "orb version 1.2.3" or just "1.2.3"
|
|
67
|
+
match = stdout.match(/(\d+\.\d+\.\d+)/)
|
|
68
|
+
match ? match[1] : nil
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Check if OrbStack is currently running
|
|
72
|
+
# @return [Boolean] true if running, false otherwise
|
|
73
|
+
def running?
|
|
74
|
+
stdout, _stderr, success = execute_command('orb status')
|
|
75
|
+
success && stdout.match?(/running/i)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# List all OrbStack machines
|
|
79
|
+
#
|
|
80
|
+
# Executes `orb list` and parses the output into an array of machine hashes.
|
|
81
|
+
# Each hash contains the machine name and current status.
|
|
82
|
+
#
|
|
83
|
+
# @return [Array<Hash>] Array of machine hashes, each with:
|
|
84
|
+
# - :name [String] The machine name
|
|
85
|
+
# - :status [String] The machine status (e.g., 'running', 'stopped')
|
|
86
|
+
# @return [Array] Empty array on failure or if no machines exist
|
|
87
|
+
# @raise [CommandTimeoutError] If command times out
|
|
88
|
+
#
|
|
89
|
+
# @example List all machines
|
|
90
|
+
# machines = OrbStackCLI.list_machines
|
|
91
|
+
# # => [{name: 'ubuntu-dev', status: 'running'}, {name: 'debian-test', status: 'stopped'}]
|
|
92
|
+
# rubocop:disable Metrics/MethodLength
|
|
93
|
+
def list_machines
|
|
94
|
+
stdout, _stderr, success = execute_command('orb list', timeout: QUERY_TIMEOUT)
|
|
95
|
+
return [] unless success
|
|
96
|
+
return [] if stdout.empty?
|
|
97
|
+
|
|
98
|
+
# Parse machine list output (format: NAME STATUS DISTRO IP)
|
|
99
|
+
machines = []
|
|
100
|
+
stdout.each_line do |line|
|
|
101
|
+
parts = line.strip.split(/\s+/)
|
|
102
|
+
next if parts.empty?
|
|
103
|
+
|
|
104
|
+
machines << {
|
|
105
|
+
name: parts[0],
|
|
106
|
+
status: parts[1]
|
|
107
|
+
}
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
machines
|
|
111
|
+
end
|
|
112
|
+
# rubocop:enable Metrics/MethodLength
|
|
113
|
+
|
|
114
|
+
# Get detailed information about a specific machine
|
|
115
|
+
#
|
|
116
|
+
# Executes `orb info <name>` and parses the JSON output. Returns nil if
|
|
117
|
+
# the machine doesn't exist or if JSON parsing fails.
|
|
118
|
+
#
|
|
119
|
+
# @param name [String] The machine name
|
|
120
|
+
# @return [Hash, nil] Parsed machine information hash with OrbStack-specific
|
|
121
|
+
# fields, or nil if machine not found or parsing fails
|
|
122
|
+
# @raise [CommandTimeoutError] If command times out
|
|
123
|
+
#
|
|
124
|
+
# @example Get machine info
|
|
125
|
+
# info = OrbStackCLI.machine_info('my-dev-vm')
|
|
126
|
+
# puts info['distro'] if info
|
|
127
|
+
def machine_info(name)
|
|
128
|
+
stdout, _stderr, success = execute_command("orb info --format json #{name}", timeout: QUERY_TIMEOUT)
|
|
129
|
+
return nil unless success
|
|
130
|
+
|
|
131
|
+
JSON.parse(stdout)
|
|
132
|
+
rescue JSON::ParserError => e
|
|
133
|
+
@logger.warn("Failed to parse machine info JSON: #{e.message}")
|
|
134
|
+
nil
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Create a new OrbStack machine
|
|
138
|
+
#
|
|
139
|
+
# Executes `orb create <distro> <name>`. This operation can take 30-120 seconds
|
|
140
|
+
# on first use if the distribution image needs to be downloaded. Subsequent
|
|
141
|
+
# creations of the same distribution are much faster.
|
|
142
|
+
#
|
|
143
|
+
# @param name [String] The machine name
|
|
144
|
+
# @param distribution [String] The distribution to use (e.g., 'ubuntu:noble', 'debian')
|
|
145
|
+
# @param timeout [Integer] Command timeout in seconds (default: CREATE_TIMEOUT)
|
|
146
|
+
# @return [Hash] Machine info hash with :id and :status keys
|
|
147
|
+
# @raise [CommandExecutionError] If creation fails
|
|
148
|
+
# @raise [CommandTimeoutError] If command times out
|
|
149
|
+
#
|
|
150
|
+
# @example Create an Ubuntu machine
|
|
151
|
+
# OrbStackCLI.create_machine('my-dev-vm', distribution: 'ubuntu:noble')
|
|
152
|
+
#
|
|
153
|
+
# @example Create with custom timeout for slow networks
|
|
154
|
+
# OrbStackCLI.create_machine('my-vm', distribution: 'ubuntu', timeout: 180)
|
|
155
|
+
# rubocop:disable Naming/PredicateMethod
|
|
156
|
+
def create_machine(name, distribution:, timeout: CREATE_TIMEOUT)
|
|
157
|
+
_, stderr, success = execute_command("orb create #{distribution} #{name}", timeout: timeout)
|
|
158
|
+
raise_unless_successful!('create', stderr, success)
|
|
159
|
+
{ id: name, status: 'running' }
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Delete an OrbStack machine
|
|
163
|
+
#
|
|
164
|
+
# Executes `orb delete <name>`. This permanently removes the machine and
|
|
165
|
+
# all its data. The operation cannot be undone.
|
|
166
|
+
#
|
|
167
|
+
# @param name [String] The machine name
|
|
168
|
+
# @return [Boolean] true on success
|
|
169
|
+
# @raise [CommandExecutionError] If deletion fails
|
|
170
|
+
# @raise [CommandTimeoutError] If command times out
|
|
171
|
+
#
|
|
172
|
+
# @example Delete a machine
|
|
173
|
+
# OrbStackCLI.delete_machine('my-dev-vm')
|
|
174
|
+
def delete_machine(name)
|
|
175
|
+
_, stderr, success = execute_command("orb delete #{name}", timeout: MUTATE_TIMEOUT)
|
|
176
|
+
raise_unless_successful!('delete', stderr, success)
|
|
177
|
+
true
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Start an OrbStack machine
|
|
181
|
+
#
|
|
182
|
+
# Executes `orb start <name>`. Starts a stopped machine. If the machine
|
|
183
|
+
# is already running, this is a no-op.
|
|
184
|
+
#
|
|
185
|
+
# @param name [String] The machine name
|
|
186
|
+
# @param timeout [Integer] Command timeout in seconds (default: MUTATE_TIMEOUT)
|
|
187
|
+
# @return [Hash] Machine info hash with :id and :status keys
|
|
188
|
+
# @raise [CommandExecutionError] If start fails
|
|
189
|
+
# @raise [CommandTimeoutError] If command times out
|
|
190
|
+
#
|
|
191
|
+
# @example Start a machine
|
|
192
|
+
# OrbStackCLI.start_machine('my-dev-vm')
|
|
193
|
+
def start_machine(name, timeout: MUTATE_TIMEOUT)
|
|
194
|
+
_, stderr, success = execute_command("orb start #{name}", timeout: timeout)
|
|
195
|
+
raise_unless_successful!('start', stderr, success)
|
|
196
|
+
{ id: name, status: 'running' }
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Stop an OrbStack machine
|
|
200
|
+
#
|
|
201
|
+
# Executes `orb stop <name>`. Stops a running machine gracefully. If the
|
|
202
|
+
# machine is already stopped, this is a no-op.
|
|
203
|
+
#
|
|
204
|
+
# @param name [String] The machine name
|
|
205
|
+
# @return [Boolean] true on success
|
|
206
|
+
# @raise [CommandExecutionError] If stop fails
|
|
207
|
+
# @raise [CommandTimeoutError] If command times out
|
|
208
|
+
#
|
|
209
|
+
# @example Stop a machine
|
|
210
|
+
# OrbStackCLI.stop_machine('my-dev-vm')
|
|
211
|
+
def stop_machine(name)
|
|
212
|
+
_, stderr, success = execute_command("orb stop #{name}", timeout: MUTATE_TIMEOUT)
|
|
213
|
+
raise_unless_successful!('stop', stderr, success)
|
|
214
|
+
true
|
|
215
|
+
end
|
|
216
|
+
# rubocop:enable Naming/PredicateMethod
|
|
217
|
+
|
|
218
|
+
private
|
|
219
|
+
|
|
220
|
+
# Raises CommandExecutionError unless the operation succeeded
|
|
221
|
+
#
|
|
222
|
+
# Helper method to enforce error handling for mutating operations.
|
|
223
|
+
# Only raises if the operation failed.
|
|
224
|
+
#
|
|
225
|
+
# @param action [String] The action being performed (e.g., 'create', 'delete')
|
|
226
|
+
# @param stderr [String] Error output from CLI
|
|
227
|
+
# @param success [Boolean] Whether the operation succeeded
|
|
228
|
+
# @return [void]
|
|
229
|
+
# @raise [CommandExecutionError] If success is false
|
|
230
|
+
def raise_unless_successful!(action, stderr, success)
|
|
231
|
+
return if success
|
|
232
|
+
|
|
233
|
+
raise CommandExecutionError, command: "orb #{action}", details: stderr
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# Execute an OrbStack CLI command.
|
|
237
|
+
#
|
|
238
|
+
# Executes a shell command using Open3.capture3 and captures stdout,
|
|
239
|
+
# stderr, and exit status. Supports optional timeout for long-running
|
|
240
|
+
# operations. Logs command execution at DEBUG level on success and
|
|
241
|
+
# ERROR level on failure.
|
|
242
|
+
#
|
|
243
|
+
# On any StandardError (other than timeout), returns empty strings and
|
|
244
|
+
# false success flag rather than propagating the exception.
|
|
245
|
+
#
|
|
246
|
+
# @param command [String] The command to execute
|
|
247
|
+
# @param timeout [Integer] Timeout in seconds (default: QUERY_TIMEOUT)
|
|
248
|
+
# @return [Array<String, String, Boolean>] Tuple of [stdout, stderr, success].
|
|
249
|
+
# All string values are stripped of leading/trailing whitespace.
|
|
250
|
+
# @raise [CommandTimeoutError] If command exceeds timeout
|
|
251
|
+
# @api private
|
|
252
|
+
# rubocop:disable Metrics/MethodLength
|
|
253
|
+
def execute_command(command, timeout: QUERY_TIMEOUT)
|
|
254
|
+
stdout, stderr, status = if timeout
|
|
255
|
+
Timeout.timeout(timeout) do
|
|
256
|
+
Open3.capture3(command)
|
|
257
|
+
end
|
|
258
|
+
else
|
|
259
|
+
Open3.capture3(command)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
success = status.success?
|
|
263
|
+
|
|
264
|
+
if success
|
|
265
|
+
@logger.debug("Command succeeded: #{command}")
|
|
266
|
+
else
|
|
267
|
+
@logger.error("Command failed: #{command}, stderr: #{stderr}")
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
[stdout.strip, stderr.strip, success]
|
|
271
|
+
rescue Timeout::Error
|
|
272
|
+
@logger.error("Command timed out after #{timeout}s: #{command}")
|
|
273
|
+
raise CommandTimeoutError, "Command timed out: #{command}"
|
|
274
|
+
rescue StandardError => e
|
|
275
|
+
@logger.error("Command error: #{e.message}")
|
|
276
|
+
['', '', false]
|
|
277
|
+
end
|
|
278
|
+
# rubocop:enable Metrics/MethodLength
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'orbstack_cli'
|
|
4
|
+
require_relative '../errors'
|
|
5
|
+
|
|
6
|
+
module VagrantPlugins
|
|
7
|
+
module OrbStack
|
|
8
|
+
module Util
|
|
9
|
+
# Utility module for waiting until SSH becomes available on an OrbStack machine
|
|
10
|
+
#
|
|
11
|
+
# This module provides a polling mechanism to check if an OrbStack machine
|
|
12
|
+
# is ready for SSH connections. It repeatedly queries the machine status
|
|
13
|
+
# until the machine reports as 'running', indicating SSH is available.
|
|
14
|
+
#
|
|
15
|
+
# @example Wait for SSH on a newly created machine
|
|
16
|
+
# SSHReadinessChecker.wait_for_ready('my-machine', ui: ui)
|
|
17
|
+
#
|
|
18
|
+
# @example Handle timeout gracefully
|
|
19
|
+
# begin
|
|
20
|
+
# SSHReadinessChecker.wait_for_ready('my-machine', ui: ui)
|
|
21
|
+
# rescue VagrantPlugins::OrbStack::SSHNotReady => e
|
|
22
|
+
# ui.error("SSH not ready: #{e.message}")
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# @api public
|
|
26
|
+
module SSHReadinessChecker
|
|
27
|
+
# Maximum time to wait for SSH to become ready (in seconds)
|
|
28
|
+
# @return [Integer] Timeout in seconds
|
|
29
|
+
MAX_WAIT_TIME = 120
|
|
30
|
+
|
|
31
|
+
# Interval between status polls (in seconds)
|
|
32
|
+
# @return [Integer] Poll interval in seconds
|
|
33
|
+
POLL_INTERVAL = 2
|
|
34
|
+
|
|
35
|
+
# Wait for SSH to become available on a machine.
|
|
36
|
+
#
|
|
37
|
+
# Polls the machine status every POLL_INTERVAL seconds until the machine
|
|
38
|
+
# reports 'running' status, or until MAX_WAIT_TIME is exceeded. Displays
|
|
39
|
+
# progress messages via the provided UI object.
|
|
40
|
+
#
|
|
41
|
+
# @param machine_name [String] The name of the machine to check
|
|
42
|
+
# @param ui [Vagrant::UI::Interface] UI object for displaying progress messages
|
|
43
|
+
# @return [Boolean] true when SSH is ready
|
|
44
|
+
# @raise [SSHNotReady] If machine doesn't become ready within MAX_WAIT_TIME
|
|
45
|
+
# @raise [CommandExecutionError] If OrbStack CLI command fails
|
|
46
|
+
# @raise [CommandTimeoutError] If OrbStack CLI command times out
|
|
47
|
+
# @raise [OrbStackNotInstalled] If OrbStack CLI is not available
|
|
48
|
+
#
|
|
49
|
+
# @example Wait for SSH
|
|
50
|
+
# SSHReadinessChecker.wait_for_ready('ubuntu-dev', ui: machine.ui)
|
|
51
|
+
# # => true
|
|
52
|
+
#
|
|
53
|
+
# @api public
|
|
54
|
+
# rubocop:disable Naming/MethodParameterName
|
|
55
|
+
def self.wait_for_ready(machine_name, ui:)
|
|
56
|
+
# rubocop:enable Naming/MethodParameterName
|
|
57
|
+
ui.info("Waiting for SSH to become available on #{machine_name}...")
|
|
58
|
+
|
|
59
|
+
poll_until_ready(machine_name, ui) ||
|
|
60
|
+
raise_timeout_error(machine_name)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Poll machine status until ready or timeout.
|
|
64
|
+
#
|
|
65
|
+
# @param machine_name [String] The name of the machine to check
|
|
66
|
+
# @param ui [Vagrant::UI::Interface] UI object for progress messages
|
|
67
|
+
# @return [Boolean, nil] true if ready, nil if timeout
|
|
68
|
+
# @api private
|
|
69
|
+
# rubocop:disable Naming/MethodParameterName, Metrics/MethodLength
|
|
70
|
+
def self.poll_until_ready(machine_name, ui)
|
|
71
|
+
# rubocop:enable Naming/MethodParameterName, Metrics/MethodLength
|
|
72
|
+
start_time = Time.now
|
|
73
|
+
elapsed = 0
|
|
74
|
+
|
|
75
|
+
while elapsed < MAX_WAIT_TIME
|
|
76
|
+
if machine_running?(machine_name)
|
|
77
|
+
ui.info('SSH is ready!')
|
|
78
|
+
return true
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
sleep(POLL_INTERVAL)
|
|
82
|
+
elapsed = Time.now - start_time
|
|
83
|
+
ui.info(" Still waiting... (#{elapsed.to_i} seconds elapsed)")
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
nil
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Raise timeout error with machine_name parameter for I18n.
|
|
90
|
+
#
|
|
91
|
+
# @param machine_name [String] The name of the machine
|
|
92
|
+
# @raise [SSHNotReady] Always raises with machine_name parameter
|
|
93
|
+
# @api private
|
|
94
|
+
def self.raise_timeout_error(machine_name)
|
|
95
|
+
raise SSHNotReady, machine_name: machine_name
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Check if machine is in running state.
|
|
99
|
+
#
|
|
100
|
+
# Queries OrbStack CLI for machine status and checks if it's 'running'.
|
|
101
|
+
# Handles nil responses and missing 'status' keys gracefully.
|
|
102
|
+
#
|
|
103
|
+
# @param machine_name [String] The name of the machine to check
|
|
104
|
+
# @return [Boolean] true if machine status is 'running', false otherwise
|
|
105
|
+
# @api private
|
|
106
|
+
def self.machine_running?(machine_name)
|
|
107
|
+
info = OrbStackCLI.machine_info(machine_name)
|
|
108
|
+
info && info.dig('record', 'state') == 'running'
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
private_class_method :poll_until_ready, :raise_timeout_error, :machine_running?
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module VagrantPlugins
|
|
4
|
+
module OrbStack
|
|
5
|
+
module Util
|
|
6
|
+
# TTL-based cache utility for machine state queries.
|
|
7
|
+
#
|
|
8
|
+
# This class provides a simple time-to-live (TTL) cache to reduce redundant
|
|
9
|
+
# CLI calls when querying machine state. State queries are cached with a
|
|
10
|
+
# configurable TTL (default 5 seconds), and automatically expire when the
|
|
11
|
+
# TTL is exceeded.
|
|
12
|
+
#
|
|
13
|
+
# The cache is designed for Vagrant's single-threaded environment and does
|
|
14
|
+
# not implement thread-safety mechanisms.
|
|
15
|
+
#
|
|
16
|
+
# @example Basic usage with default TTL
|
|
17
|
+
# cache = StateCache.new
|
|
18
|
+
# cache.set('machine-id', state)
|
|
19
|
+
# cached_state = cache.get('machine-id') # Returns state
|
|
20
|
+
#
|
|
21
|
+
# @example Custom TTL
|
|
22
|
+
# cache = StateCache.new(ttl: 10) # 10-second cache
|
|
23
|
+
# cache.set('key', 'value')
|
|
24
|
+
# # 11 seconds later...
|
|
25
|
+
# cache.get('key') # Returns nil (expired)
|
|
26
|
+
#
|
|
27
|
+
# @example Cache invalidation
|
|
28
|
+
# cache.invalidate('key') # Remove specific key
|
|
29
|
+
# cache.invalidate_all # Clear entire cache
|
|
30
|
+
class StateCache
|
|
31
|
+
# Default TTL for cached entries (in seconds)
|
|
32
|
+
DEFAULT_TTL = 5
|
|
33
|
+
|
|
34
|
+
# Initialize a new state cache.
|
|
35
|
+
#
|
|
36
|
+
# @param ttl [Integer] Time-to-live for cached entries in seconds (default: 5)
|
|
37
|
+
# @api public
|
|
38
|
+
def initialize(ttl: DEFAULT_TTL)
|
|
39
|
+
@ttl = ttl
|
|
40
|
+
@cache = {}
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Retrieve a cached value.
|
|
44
|
+
#
|
|
45
|
+
# Returns the cached value if it exists and has not expired. Returns nil
|
|
46
|
+
# if the key doesn't exist or if the cached entry has exceeded its TTL.
|
|
47
|
+
#
|
|
48
|
+
# @param key [String] The cache key
|
|
49
|
+
# @return [Object, nil] The cached value if valid, nil if missing or expired
|
|
50
|
+
# @api public
|
|
51
|
+
def get(key)
|
|
52
|
+
entry = @cache[key]
|
|
53
|
+
return nil unless entry
|
|
54
|
+
|
|
55
|
+
# Check if entry has expired
|
|
56
|
+
if Time.now - entry[:timestamp] > @ttl
|
|
57
|
+
# Entry expired, remove it and return nil
|
|
58
|
+
@cache.delete(key)
|
|
59
|
+
return nil
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
entry[:value]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Store a value in the cache.
|
|
66
|
+
#
|
|
67
|
+
# Stores the value with the current timestamp. If the key already exists,
|
|
68
|
+
# its value and timestamp are overwritten.
|
|
69
|
+
#
|
|
70
|
+
# @param key [String] The cache key
|
|
71
|
+
# @param value [Object] The value to cache (can be any object, including nil)
|
|
72
|
+
# @return [void]
|
|
73
|
+
# @api public
|
|
74
|
+
def set(key, value)
|
|
75
|
+
@cache[key] = {
|
|
76
|
+
value: value,
|
|
77
|
+
timestamp: Time.now
|
|
78
|
+
}
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Invalidate a specific cache entry.
|
|
82
|
+
#
|
|
83
|
+
# Removes the specified key from the cache. This is a no-op if the key
|
|
84
|
+
# doesn't exist.
|
|
85
|
+
#
|
|
86
|
+
# @param key [String] The cache key to remove
|
|
87
|
+
# @return [void]
|
|
88
|
+
# @api public
|
|
89
|
+
def invalidate(key)
|
|
90
|
+
@cache.delete(key)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Clear all cache entries.
|
|
94
|
+
#
|
|
95
|
+
# Removes all cached entries. This is useful when you need to ensure
|
|
96
|
+
# fresh data is retrieved on the next query.
|
|
97
|
+
#
|
|
98
|
+
# @return [void]
|
|
99
|
+
# @api public
|
|
100
|
+
def invalidate_all
|
|
101
|
+
@cache.clear
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Vagrant's plugin loader requires a file matching the packaged gem name
|
|
4
|
+
# ("vagrant-orbstack-provider"). The actual implementation lives under
|
|
5
|
+
# vagrant-orbstack/ to keep the require path stable across gem renames.
|
|
6
|
+
require 'vagrant-orbstack'
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'pathname'
|
|
4
|
+
require 'vagrant-orbstack/plugin'
|
|
5
|
+
|
|
6
|
+
module VagrantPlugins
|
|
7
|
+
# OrbStack provider for Vagrant.
|
|
8
|
+
#
|
|
9
|
+
# This module contains the implementation of the OrbStack provider plugin,
|
|
10
|
+
# enabling Vagrant to use OrbStack as a backend for managing Linux
|
|
11
|
+
# development environments on macOS.
|
|
12
|
+
#
|
|
13
|
+
# @see Plugin
|
|
14
|
+
# @see Provider
|
|
15
|
+
# @see Config
|
|
16
|
+
module OrbStack
|
|
17
|
+
lib_path = Pathname.new(File.join(__dir__, 'vagrant-orbstack'))
|
|
18
|
+
autoload :Action, lib_path.join('action')
|
|
19
|
+
autoload :Errors, lib_path.join('errors')
|
|
20
|
+
|
|
21
|
+
# Sentinel value to distinguish "not set" from nil.
|
|
22
|
+
# Used in configuration to differentiate between explicitly set to nil
|
|
23
|
+
# versus never configured (should use default).
|
|
24
|
+
#
|
|
25
|
+
# @api private
|
|
26
|
+
UNSET_VALUE = ::Object.new.freeze
|
|
27
|
+
|
|
28
|
+
# Returns the path to the source of this plugin.
|
|
29
|
+
#
|
|
30
|
+
# @return [Pathname] Root directory of the plugin source
|
|
31
|
+
# @api private
|
|
32
|
+
def self.source_root
|
|
33
|
+
@source_root ||= Pathname.new(File.join(__dir__, '..'))
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
data/locales/en.yml
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
en:
|
|
2
|
+
vagrant_orbstack:
|
|
3
|
+
errors:
|
|
4
|
+
orbstack_not_installed: |-
|
|
5
|
+
OrbStack CLI not found. The 'orb' command is not available in your PATH.
|
|
6
|
+
|
|
7
|
+
To fix this issue:
|
|
8
|
+
1. Install OrbStack from https://orbstack.dev/
|
|
9
|
+
2. Ensure the 'orb' command is in your PATH
|
|
10
|
+
3. Restart your terminal and try again
|
|
11
|
+
|
|
12
|
+
orbstack_not_running: |-
|
|
13
|
+
OrbStack is not running. Please start OrbStack before using this provider.
|
|
14
|
+
|
|
15
|
+
To fix this issue:
|
|
16
|
+
1. Open the OrbStack application
|
|
17
|
+
2. Wait for it to fully start
|
|
18
|
+
3. Try your Vagrant command again
|
|
19
|
+
|
|
20
|
+
command_execution_error: |-
|
|
21
|
+
Failed to execute OrbStack CLI command: %{command}
|
|
22
|
+
|
|
23
|
+
Error details: %{details}
|
|
24
|
+
|
|
25
|
+
Please check that OrbStack is properly installed and running.
|
|
26
|
+
|
|
27
|
+
ssh_not_ready: |-
|
|
28
|
+
SSH is not ready on machine '%{machine_name}'.
|
|
29
|
+
|
|
30
|
+
The machine exists but SSH service is not yet available. This typically
|
|
31
|
+
happens when the machine is still booting or the SSH daemon is starting.
|
|
32
|
+
|
|
33
|
+
Please wait a few moments and try again. If the problem persists, check
|
|
34
|
+
that the machine is fully started and SSH is enabled.
|
|
35
|
+
|
|
36
|
+
ssh_connection_failed: |-
|
|
37
|
+
Failed to establish SSH connection to machine '%{machine_name}'.
|
|
38
|
+
|
|
39
|
+
Reason: %{reason}
|
|
40
|
+
|
|
41
|
+
This error occurs when SSH connection attempts fail after the machine
|
|
42
|
+
appears ready. Common causes include:
|
|
43
|
+
|
|
44
|
+
- Network connectivity issues
|
|
45
|
+
- SSH authentication problems
|
|
46
|
+
- Firewall blocking connections
|
|
47
|
+
- SSH service crashed after initial startup
|
|
48
|
+
|
|
49
|
+
Check the machine's SSH configuration and network settings.
|
metadata
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: vagrant-orbstack-provider
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Spiral House
|
|
8
|
+
- hungthai1401
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: Enables OrbStack as a Vagrant provider for managing Linux development
|
|
14
|
+
environments on macOS
|
|
15
|
+
email:
|
|
16
|
+
- opensource@spiralhouse.io
|
|
17
|
+
executables: []
|
|
18
|
+
extensions: []
|
|
19
|
+
extra_rdoc_files: []
|
|
20
|
+
files:
|
|
21
|
+
- CHANGELOG.md
|
|
22
|
+
- LICENSE
|
|
23
|
+
- README.md
|
|
24
|
+
- lib/vagrant-orbstack-provider.rb
|
|
25
|
+
- lib/vagrant-orbstack.rb
|
|
26
|
+
- lib/vagrant-orbstack/action.rb
|
|
27
|
+
- lib/vagrant-orbstack/action/create.rb
|
|
28
|
+
- lib/vagrant-orbstack/action/destroy.rb
|
|
29
|
+
- lib/vagrant-orbstack/action/halt.rb
|
|
30
|
+
- lib/vagrant-orbstack/action/machine_validation.rb
|
|
31
|
+
- lib/vagrant-orbstack/action/reload.rb
|
|
32
|
+
- lib/vagrant-orbstack/action/ssh_run.rb
|
|
33
|
+
- lib/vagrant-orbstack/action/start.rb
|
|
34
|
+
- lib/vagrant-orbstack/config.rb
|
|
35
|
+
- lib/vagrant-orbstack/errors.rb
|
|
36
|
+
- lib/vagrant-orbstack/plugin.rb
|
|
37
|
+
- lib/vagrant-orbstack/provider.rb
|
|
38
|
+
- lib/vagrant-orbstack/util/machine_namer.rb
|
|
39
|
+
- lib/vagrant-orbstack/util/orbstack_cli.rb
|
|
40
|
+
- lib/vagrant-orbstack/util/ssh_readiness_checker.rb
|
|
41
|
+
- lib/vagrant-orbstack/util/state_cache.rb
|
|
42
|
+
- lib/vagrant-orbstack/version.rb
|
|
43
|
+
- locales/en.yml
|
|
44
|
+
homepage: https://github.com/hungthai1401/vagrant-orbstack-provider
|
|
45
|
+
licenses:
|
|
46
|
+
- MIT
|
|
47
|
+
metadata:
|
|
48
|
+
homepage_uri: https://github.com/hungthai1401/vagrant-orbstack-provider
|
|
49
|
+
source_code_uri: https://github.com/hungthai1401/vagrant-orbstack-provider
|
|
50
|
+
bug_tracker_uri: https://github.com/hungthai1401/vagrant-orbstack-provider/issues
|
|
51
|
+
changelog_uri: https://github.com/hungthai1401/vagrant-orbstack-provider/blob/main/CHANGELOG.md
|
|
52
|
+
documentation_uri: https://github.com/hungthai1401/vagrant-orbstack-provider
|
|
53
|
+
allowed_push_host: https://rubygems.org
|
|
54
|
+
rdoc_options: []
|
|
55
|
+
require_paths:
|
|
56
|
+
- lib
|
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: 3.2.0
|
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
- - ">="
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '0'
|
|
67
|
+
requirements: []
|
|
68
|
+
rubygems_version: 4.0.16
|
|
69
|
+
specification_version: 4
|
|
70
|
+
summary: Vagrant provider for OrbStack
|
|
71
|
+
test_files: []
|