vagrant-eryph 0.1.0 → 0.1.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9decd72160b533c3c04d1c06ab3e7d05e3c28b4415940c05af982ce66cbb2f8b
4
- data.tar.gz: 914506e8dc13c6665743360f03c4b8ebcedd180bf73e0f2daed82b290c8d27f6
3
+ metadata.gz: 3d8510d9544d0730e1569249a684e99e925219f3e8c3076f9b3e1bb8535291ec
4
+ data.tar.gz: 2eb2c0b65b421828fd05c7341dee2c03f2c98bb9a9148992e09712b74288b22f
5
5
  SHA512:
6
- metadata.gz: 02354e91939f344f23cef10df4d263fcb0612339288483e16579c160a3e4e9e11652ef26c13b5ff28dec8a39192cb185085294fb123539d5af52efa7954679c2
7
- data.tar.gz: 17e138e16fd7b7eb09e750e41a98de186a6c8a39519c18c53b20e6bfef9283c5ebe8519a3a0be0be2eb8817af29dde47d287d0a764b6a1b611988a910023976e
6
+ metadata.gz: 29dac22ab524da0a07f269fd999ea9caaab10d0e6148b6550365ee24fccc512e07dc09398149c7788dd7a4f883208e89555f9bff59cc5225637f6ee4571eb97c
7
+ data.tar.gz: f359b334f4ec51030b57a875f1ee18c773928676f91bd6768aeb72accfbdf6685c7a8e6542342ad67791c915fc58c49a0edd563528791db6b9328ddc9ec48fa3
data/CHANGELOG.md ADDED
@@ -0,0 +1,48 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.2] - 2025-09-18
11
+
12
+ ### Fixed
13
+ - Reload operation failures due to missing action methods (#6)
14
+ - E2E test cleanup issues leaving behind test catlets
15
+ - Improved test catlet cleanup with comprehensive after-suite cleanup
16
+
17
+
18
+ ## [0.1.1] - 2025-09-17
19
+
20
+ ### Fixed
21
+ - New created project is not detected correctly (#3)
22
+ - Operation logging now shows log messages instead of only some tasks (#4)
23
+ - Error handling improvements for project creation
24
+ - Build pipeline fixes
25
+ - README formatting and link corrections
26
+
27
+ ### Added
28
+ - Standalone project commands
29
+ - Unit tests for Eryph client functionality
30
+ - Enhanced operation progress logging
31
+ - Better error messages and localization
32
+
33
+ ### Changed
34
+ - Improved project creation workflow
35
+ - Enhanced error handling with better user feedback
36
+ - Updated README with clearer documentation and examples
37
+ - Simplified project creation commands
38
+
39
+ ## [0.1.0] - Initial Release
40
+
41
+ ### Added
42
+ - Initial Vagrant provider for Eryph
43
+ - Support for Linux and Windows catlets
44
+ - Automatic SSH key generation and injection
45
+ - Cloud-init (fodder) configuration support
46
+ - Project-based resource organization
47
+ - Cross-platform compatibility (Linux/Windows hosts)
48
+ - Auto-configuration for common scenarios
data/README.md CHANGED
@@ -1,34 +1,153 @@
1
- # Vagrant Eryph Provider
1
+ # vagrant eryph Provider
2
2
 
3
- A Vagrant provider plugin for [Eryph](https://www.eryph.io/) that allows you to manage catlets using Eryph's compute API.
3
+ This is a vagrant provider plugin for [eryph](https://www.eryph.io) that allows you to manage virtual machines as catlets, which are VMs built from specification files (catlets are pure declarative VM configurations).
4
4
 
5
- ## Features
5
+ Eryph brings cloud-native features to local/on-premises development environments, such as storage management, virtual networks, and secure remote access. It is built on top of Hyper-V, but hides most of it.
6
6
 
7
- - Full catlet lifecycle management (create, start, stop, destroy)
8
- - Automatic Vagrant user setup via cloud-init
9
- - Cross-platform support (Linux and Windows catlets)
10
- - SSH and WinRM communication support
11
- - Local-scoped credential discovery
12
- - Configurable cloud-init fodder merging
7
+ ## Why choose the eryph provider?
8
+
9
+ - **Standard VMs work instantly** - Use any gene/template from eryph's genepool (https://genepool.eryph.io), Vagrant configuration is automatically injected via cloud-init.
10
+ - **Project isolation with SDN** - Full software-defined networking with isolated projects, virtual networks, and proper routing
11
+ - **API-based remote access** - Develop from anywhere: WSL, Linux, macOS clients can all connect to Windows eryph hosts
12
+ - **Non-admin access** - Unlike Hyper-V provider, you don't need local admin rights
13
+
14
+ **What Cloud-Native Features You Get:**
15
+ - **Storage management** - Automatic creation/removal of disks and VM files
16
+ - **Software-Defined Networking** - Create complex network topologies per project, not just basic VM networking
17
+ - **Multi-platform development** - Windows eryph hosts serve WSL/Linux/macOS developers via REST API
18
+ - **Team collaboration** - Share infrastructure with project-based access control and resource limits
19
+ - **Hyper-V compatibility** - Familiar vagrant settings (cpus, memory, etc.) just work
20
+ - **Template inheritance** - Use eryph genes system to merge configuration instead of building it into each vagrantfile.
21
+
22
+ ## Requirements
23
+
24
+ - **Vagrant** 2.0 or later
25
+ - **Ruby** >= 3.1.0
26
+ - **eryph** - either:
27
+ - [eryph-zero](https://www.eryph.io/downloads/eryph-zero) >= 0.4.1 installed locally or remotely
28
+ - For client management: [PowerShell module](https://www.powershellgallery.com/packages/Eryph.ClientRuntime.Configuration) (works on Windows/Linux/macOS)
29
+
30
+ See the [eryph documentation](https://www.eryph.io/docs) for installation and setup instructions.
13
31
 
14
32
  ## Installation
15
33
 
34
+ Install the plugin using Vagrant's plugin system:
35
+
36
+ ```bash
37
+ vagrant plugin install vagrant-eryph
38
+ ```
39
+
40
+ Or install from a local gem file:
41
+
16
42
  ```bash
17
- gem install vagrant-eryph
43
+ vagrant plugin install ./vagrant-eryph-*.gem
18
44
  ```
19
45
 
20
46
  ## Usage
21
47
 
48
+ ### Basic Linux Example
49
+
22
50
  ```ruby
23
51
  Vagrant.configure("2") do |config|
24
52
  config.vm.provider :eryph do |eryph|
25
- eryph.project = "my-project"
26
- eryph.parent = "dbosoft/ubuntu-22.04/latest"
27
- eryph.auto_config = true # Enable automatic Vagrant user setup
53
+ eryph.parent = "dbosoft/ubuntu-22.04"
28
54
  end
29
55
  end
30
56
  ```
31
57
 
58
+ Run with:
59
+ ```bash
60
+ vagrant up --provider=eryph
61
+ ```
62
+
63
+ ### Windows Example
64
+
65
+ ```ruby
66
+ Vagrant.configure("2") do |config|
67
+ config.vm.provider :eryph do |eryph|
68
+ eryph.parent = "dbosoft/winsrv2022-standard"
69
+ eryph.enable_winrm = true
70
+ eryph.vagrant_password = "SecureP@ss123"
71
+ eryph.cpus = 4
72
+ eryph.memory = 4096
73
+ end
74
+
75
+ # Configure Windows communication
76
+ # eryph requires https by default
77
+ config.vm.communicator = "winrm"
78
+ config.winrm.username = "vagrant"
79
+ config.winrm.password = "SecureP@ss123"
80
+ config.winrm.port = 5986
81
+ config.winrm.transport = :ssl
82
+ config.winrm.ssl_peer_verification = false
83
+ config.winrm.basic_auth_only = true
84
+ config.vm.guest = :windows
85
+ end
86
+ ```
87
+
88
+ Run with:
89
+ ```bash
90
+ vagrant up --provider=eryph
91
+ ```
92
+
93
+ ### Advanced Configuration Example
94
+
95
+ Combining individual helpers with direct configuration and complex setups. The individual property helpers are intentionally compatible with most used Hyper-V provider settings for easy migration:
96
+
97
+ ```ruby
98
+ Vagrant.configure("2") do |config|
99
+ config.vm.provider :eryph do |eryph|
100
+ eryph.project = "development"
101
+
102
+ # Option 1: Use individual property helpers (mapped to catlet hash)
103
+ eryph.parent = "dbosoft/ubuntu-22.04"
104
+ eryph.cpus = 4
105
+ eryph.memory = 4096
106
+ eryph.maxmemory = 8192 # Enables dynamic memory
107
+ eryph.enable_virtualization_extensions = true
108
+
109
+ # Option 2: Or use direct catlet hash for complex scenarios
110
+ # eryph.catlet = {
111
+ # parent: "dbosoft/ubuntu-22.04",
112
+ # cpu: { count: 4 },
113
+ # memory: { startup: 4096, maximum: 8192 },
114
+ # capabilities: [
115
+ # { name: "nested_virtualization" },
116
+ # { name: "dynamic_memory" }
117
+ # ]
118
+ # }
119
+
120
+ # Add additional drives using helper method
121
+ eryph.add_drive("data", size: 100, type: :vhd)
122
+ eryph.add_drive("logs", size: 50, type: :vhd)
123
+
124
+ # Add gene-based fodder for common setup tasks
125
+ eryph.add_fodder_gene("dbosoft", "development-tools",
126
+ fodder_name: "dev-setup")
127
+
128
+ # Add custom cloud-config
129
+ eryph.cloud_config("app-config") do |config|
130
+ config["packages"] = ["nginx", "nodejs", "git"]
131
+ config["runcmd"] = ["systemctl enable nginx"]
132
+ end
133
+
134
+ # Set variables for use in fodder
135
+ eryph.set_variable("environment", "development")
136
+ eryph.set_variable("app_version", "latest")
137
+ end
138
+ end
139
+ ```
140
+
141
+ ### Setting Default Provider
142
+
143
+ To avoid specifying `--provider=eryph` every time:
144
+
145
+ ```bash
146
+ export VAGRANT_DEFAULT_PROVIDER=eryph
147
+ # or on Windows:
148
+ set VAGRANT_DEFAULT_PROVIDER=eryph
149
+ ```
150
+
32
151
  ## Configuration
33
152
 
34
153
  ### Basic Settings
@@ -42,38 +161,206 @@ end
42
161
  - `vagrant_password` - Password for Windows vagrant user (default: "vagrant")
43
162
  - `ssh_key_injection` - SSH key injection method (:direct or :variable)
44
163
 
45
- ### Resources
46
- - `cpus` - Number of CPUs
47
- - `memory` - Memory in MB
48
- - `drives` - Custom drives configuration
49
- - `networks` - Custom networks configuration
164
+ ### Catlet Configuration
165
+
166
+ **Individual property helpers** (automatically mapped to catlet hash):
167
+ - `cpus` - Number of CPUs (maps to `catlet[:cpu][:count]`)
168
+ - `memory` - Memory in MB (maps to `catlet[:memory][:startup]`)
169
+ - `maxmemory` - Maximum memory in MB (enables dynamic memory capability)
170
+ - `parent` - Parent gene (maps to `catlet[:parent]`)
171
+ - `vmname` - VM name (maps to `catlet[:name]`)
172
+ - `hostname` - Network hostname (maps to `catlet[:hostname]`)
173
+ - `enable_virtualization_extensions` - Enable nested virtualization
174
+ - `enable_secure_boot` - Enable secure boot capability
175
+
176
+ **Direct configuration:**
177
+ - `catlet` - Direct catlet configuration hash (alternative to individual settings)
178
+
179
+ **Helper methods for complex configurations:**
180
+ ```ruby
181
+ # Add drives with type mapping
182
+ add_drive("data", size: 100, type: :vhd)
183
+ add_drive("shared", type: :shared_vhd, source: "my-shared-disk")
184
+
185
+ # Add gene-based fodder
186
+ add_fodder_gene("dbosoft", "base-setup", fodder_name: "setup", variables: [...])
187
+
188
+ # Add cloud-config fodder
189
+ cloud_config("my-setup") do |config|
190
+ config["packages"] = ["nginx", "git"]
191
+ end
50
192
 
51
- ### Cloud-init
52
- - `fodder` - Custom cloud-init configuration (merged with auto-generated config)
193
+ # Add shell script fodder
194
+ shell_script("post-install", "#!/bin/bash\necho 'Done'")
195
+
196
+ # Manage capabilities
197
+ enable_capability("nested_virtualization")
198
+ disable_capability("secure_boot")
199
+
200
+ # Set variables for fodder
201
+ set_variable("app_name", "myapp")
202
+ ```
203
+
204
+ ### Automatic Cloud-Init Integration
205
+
206
+ The plugin automatically generates cloud-init configuration:
207
+ - **Linux**: Creates vagrant user with sudo access, SSH keys, and password authentication
208
+ - **Windows**: Creates vagrant user in Administrators group with password and SSH keys
209
+ - **Merging**: Auto-generated fodder is intelligently merged with user-provided fodder
210
+ - **Compatibility**: Converts Vagrant cloud-init configs to eryph fodder format
211
+
212
+ **Fodder vs Cloud-Init relationship:**
213
+ - eryph uses "fodder" (supports cloud-init, shell scripts, PowerShell, etc.)
214
+ - Plugin auto-generates vagrant user setup as cloud-config fodder
215
+ - User fodder configurations are merged with auto-generated ones
216
+ - Duplicates are automatically handled using source/name keys
217
+
218
+ For detailed catlet configuration options and specification format, see the [eryph Specification Files documentation](https://www.eryph.io/docs/refs/specification-files).
219
+
220
+ ### Client Configuration
221
+ - `configuration_name` - eryph client configuration name (default: automatic discovery)
222
+ - `client_id` - Specific client ID for authentication
223
+ - `ssl_verify` - Enable/disable SSL certificate verification (default: false for localhost)
224
+ - `ssl_ca_file` - Path to custom CA certificate file
225
+
226
+ ## Client Management
227
+
228
+ The plugin uses eryph's client configuration system for authentication and connection management. Client configurations are managed through PowerShell commands and store connection details, credentials, and endpoints.
229
+
230
+ ### Configuration Discovery
231
+
232
+ Eryph uses two main configuration types:
233
+ 1. **"zero" configuration** - For local eryph-zero instances (automatic endpoint discovery)
234
+ 2. **"default" configuration** - For remote eryph hosts (manually configured)
235
+
236
+ The plugin automatically discovers configurations in this order:
237
+ 1. **Named configuration** (if `configuration_name` is specified)
238
+ 2. **"default" configuration** (for remote hosts)
239
+ 3. **"zero" configuration** (for local eryph-zero)
240
+ 4. **System client** (requires admin privileges)
241
+
242
+ ### Managing Client Configurations
243
+
244
+ Use PowerShell commands to manage client configurations (works on Windows, Linux, and macOS):
245
+
246
+ ```powershell
247
+ # Install the configuration module
248
+ Install-Module Eryph.ClientRuntime.Configuration
249
+
250
+ # List available configurations
251
+ Get-EryphClientConfiguration
252
+
253
+ # Create a client with compute permissions and add to default configuration
254
+ New-EryphClient my-client `
255
+ -AllowedScopes compute:write `
256
+ -AddToConfiguration -AsDefault
257
+
258
+ # For remote hosts, create default configuration manually
259
+ New-EryphClientConfiguration -Name "default" -Endpoint "https://eryph.company.com"
260
+
261
+ # Remove a configuration
262
+ Remove-EryphClientConfiguration -Name "old-config"
263
+ ```
264
+
265
+ ### Vagrantfile Examples
266
+
267
+ ```ruby
268
+ # Use automatic discovery (recommended for local development)
269
+ config.vm.provider :eryph do |eryph|
270
+ eryph.parent = "dbosoft/ubuntu-22.04"
271
+ # No client config needed - uses automatic discovery
272
+ end
273
+
274
+ # Use specific named configuration
275
+ config.vm.provider :eryph do |eryph|
276
+ eryph.parent = "dbosoft/ubuntu-22.04"
277
+ eryph.configuration_name = "default"
278
+ end
279
+
280
+ # Use specific client ID with custom SSL settings
281
+ config.vm.provider :eryph do |eryph|
282
+ eryph.parent = "dbosoft/ubuntu-22.04"
283
+ eryph.configuration_name = "default"
284
+ eryph.client_id = "my-automation-client"
285
+ eryph.ssl_verify = true
286
+ eryph.ssl_ca_file = "/path/to/ca.crt"
287
+ end
288
+ ```
289
+
290
+ ### Troubleshooting Authentication
291
+
292
+ If you encounter authentication issues:
293
+
294
+ 1. **Check available configurations**: `Get-EryphClientConfiguration`
295
+ 2. **Check credentials**: `Get-EryphClientCredential -Name "your-config"`
296
+ 3. **Verify permissions**: Ensure the client has appropriate compute scopes
297
+ 4. **Check logs**: Use `vagrant up --debug` for detailed connection information
298
+
299
+ For detailed client configuration and authentication setup, see the [eryph PowerShell documentation](https://www.eryph.io/docs/using-powershell#client-configuration).
53
300
 
54
301
  ## Project Management
55
302
 
56
- The plugin includes commands to manage Eryph projects:
303
+ The plugin includes commands to manage eryph projects:
57
304
 
58
305
  ```bash
59
306
  # List projects
60
307
  vagrant eryph project list
61
308
 
62
309
  # Create project
63
- vagrant eryph project create my-project --description "My development project"
310
+ vagrant eryph project create my-project
64
311
 
65
312
  # Manage project networks
66
313
  vagrant eryph network get my-project
67
314
  vagrant eryph network set my-project --file networks.yml
68
315
  ```
69
316
 
317
+
70
318
  ## Development
71
319
 
320
+ ### Setup
321
+
72
322
  ```bash
73
323
  bundle install
74
- bundle exec vagrant --help
324
+ ```
325
+
326
+ ### Building and Testing
327
+
328
+ ```bash
329
+ # Build gem
330
+ rake build
331
+
332
+ # Install locally for testing
333
+ rake install
334
+
335
+ # Reinstall after changes
336
+ rake reinstall
337
+
338
+ # Run unit tests (fast)
339
+ rake unit
340
+
341
+ # Run E2E tests (requires eryph)
342
+ rake e2e
343
+
344
+ # Run all tests
345
+ rake spec
346
+
347
+ # Get detailed test information
348
+ rake test_info
349
+ ```
350
+
351
+ ### Manual Installation
352
+
353
+ ```bash
354
+ # Build gem manually
355
+ gem build vagrant-eryph.gemspec
356
+
357
+ # Install locally
358
+ vagrant plugin install ./vagrant-eryph-*.gem
359
+
360
+ # Uninstall
361
+ vagrant plugin uninstall vagrant-eryph
75
362
  ```
76
363
 
77
364
  ## License
78
365
 
79
- MIT
366
+ MIT
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../helpers/eryph_client'
4
+ require_relative '../errors'
4
5
 
5
6
  module VagrantPlugins
6
7
  module Eryph
@@ -23,16 +24,9 @@ module VagrantPlugins
23
24
 
24
25
  @app.call(env)
25
26
  rescue ::Eryph::ClientRuntime::CredentialsNotFoundError => e
26
- env[:ui].error("Eryph credentials not found: #{e.message}")
27
- env[:ui].error('Please set up your Eryph configuration or check your .eryph directory')
28
- raise Vagrant::Errors::VagrantError, e.message
27
+ raise Errors::CredentialsError, e.message
29
28
  rescue ::Eryph::ClientRuntime::TokenRequestError => e
30
- env[:ui].error("Eryph authentication failed: #{e.message}")
31
- env[:ui].error('Check your client credentials and network connectivity')
32
- raise Vagrant::Errors::VagrantError, e.message
33
- rescue StandardError => e
34
- env[:ui].error("Failed to connect to Eryph: #{e.message}")
35
- raise Vagrant::Errors::VagrantError, e.message
29
+ raise Errors::APIConnectionError, e.message
36
30
  end
37
31
  end
38
32
  end
@@ -41,9 +41,6 @@ module VagrantPlugins
41
41
  Provider.instance_variable_set(:@eryph_catlets, nil)
42
42
 
43
43
  @app.call(env)
44
- rescue StandardError => e
45
- ui.error("Failed to create catlet: #{e.message}")
46
- raise e
47
44
  end
48
45
 
49
46
  private
@@ -23,9 +23,6 @@ module VagrantPlugins
23
23
  ui.info('Catlet destroyed successfully')
24
24
 
25
25
  @app.call(env)
26
- rescue StandardError => e
27
- ui.error("Failed to destroy catlet: #{e.message}")
28
- raise e
29
26
  end
30
27
  end
31
28
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VagrantPlugins
4
+ module Eryph
5
+ module Actions
6
+ class IsState
7
+ def initialize(app, env, state)
8
+ @app = app
9
+ @state = state.to_s.downcase
10
+ end
11
+
12
+ def call(env)
13
+ # Check if the catlet is in the specified state
14
+ catlet = Provider.eryph_catlet(env[:machine])
15
+ env[:result] = catlet && catlet.status&.downcase == @state
16
+ @app.call(env)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -42,9 +42,6 @@ module VagrantPlugins
42
42
  env[:catlet_fodder] = fodder
43
43
 
44
44
  @app.call(env)
45
- rescue StandardError => e
46
- ui.error("Failed to prepare cloud-init configuration: #{e.message}")
47
- raise e
48
45
  end
49
46
  end
50
47
  end
@@ -30,9 +30,6 @@ module VagrantPlugins
30
30
  Provider.instance_variable_set(:@eryph_catlets, nil)
31
31
 
32
32
  @app.call(env)
33
- rescue StandardError => e
34
- ui.error("Failed to start catlet: #{e.message}")
35
- raise e
36
33
  end
37
34
  end
38
35
  end
@@ -30,9 +30,6 @@ module VagrantPlugins
30
30
  Provider.instance_variable_set(:@eryph_catlets, nil)
31
31
 
32
32
  @app.call(env)
33
- rescue StandardError => e
34
- ui.error("Failed to stop catlet: #{e.message}")
35
- raise e
36
33
  end
37
34
  end
38
35
  end
@@ -178,11 +178,12 @@ module VagrantPlugins
178
178
  end
179
179
 
180
180
  b2.use action_halt
181
- b2.use Call, WaitForState, :stopped, 120 do |env2, b3|
181
+ b2.use Call, IsStopped do |env2, b3|
182
182
  if env2[:result]
183
- b3.use action_up
183
+ b3.use action_start
184
184
  else
185
- # TODO: we couldn't reach :stopped, what now?
185
+ # Catlet not stopped, continue anyway
186
+ b3.use action_start
186
187
  end
187
188
  end
188
189
  end
@@ -193,6 +194,7 @@ module VagrantPlugins
193
194
  action_root = Pathname.new(File.expand_path('actions', __dir__))
194
195
  autoload :ConnectEryph, action_root.join('connect_eryph')
195
196
  autoload :IsCreated, action_root.join('is_created')
197
+ autoload :IsState, action_root.join('is_state')
196
198
  autoload :IsStopped, action_root.join('is_stopped')
197
199
  autoload :MessageAlreadyCreated, action_root.join('message_already_created')
198
200
  autoload :MessageNotCreated, action_root.join('message_not_created')
@@ -204,7 +206,6 @@ module VagrantPlugins
204
206
  autoload :PrepareCloudInit, action_root.join('prepare_cloud_init')
205
207
  autoload :ReadSSHInfo, action_root.join('read_ssh_info')
206
208
  autoload :ReadState, action_root.join('read_state')
207
- autoload :WaitForOperation, action_root.join('wait_for_operation')
208
209
  end
209
210
  end
210
211
  end
@@ -3,6 +3,9 @@
3
3
  require 'optparse'
4
4
  require 'yaml'
5
5
  require 'json'
6
+ require_relative 'config'
7
+ require_relative 'helpers/eryph_client'
8
+ require_relative 'errors'
6
9
 
7
10
  module VagrantPlugins
8
11
  module Eryph
@@ -54,6 +57,18 @@ module VagrantPlugins
54
57
  def initialize(argv, env)
55
58
  super
56
59
  @options = {}
60
+
61
+ # Parse global options from full argv first
62
+ global_parser = OptionParser.new do |o|
63
+ o.on('--configuration-name NAME', String) { |name| @options[:configuration_name] = name }
64
+ o.on('--client-id ID', String) { |id| @options[:client_id] = id }
65
+ o.on('--[no-]ssl-verify') { |verify| @options[:ssl_verify] = verify }
66
+ o.on('--ssl-ca-file FILE', String) { |file| @options[:ssl_ca_file] = file }
67
+ end
68
+
69
+ # Parse and remove global options, leaving subcommand and its args
70
+ remaining_args = global_parser.parse(argv.dup)
71
+
57
72
  @parser = OptionParser.new do |o|
58
73
  o.banner = 'Usage: vagrant eryph project <subcommand> [options]'
59
74
  o.separator ''
@@ -63,19 +78,14 @@ module VagrantPlugins
63
78
  o.separator ' remove Remove a project'
64
79
  o.separator ''
65
80
  o.separator 'Global Options:'
66
-
67
- o.on('--configuration-name NAME', String, 'Eryph configuration name (default: auto-detect)') do |name|
68
- @options[:configuration_name] = name
69
- end
70
-
71
- o.on('--client-id ID', String, 'Eryph client ID') do |id|
72
- @options[:client_id] = id
73
- end
74
-
81
+ o.separator ' --configuration-name NAME Eryph configuration name (default: auto-detect)'
82
+ o.separator ' --client-id ID Eryph client ID'
83
+ o.separator ' --[no-]ssl-verify Enable/disable SSL certificate verification'
84
+ o.separator ' --ssl-ca-file FILE Path to custom CA certificate file'
75
85
  o.separator ''
76
86
  end
77
87
 
78
- @main_args, @sub_command, @sub_args = split_main_and_subcommand(argv)
88
+ @main_args, @sub_command, @sub_args = split_main_and_subcommand(remaining_args)
79
89
  end
80
90
 
81
91
  def execute
@@ -136,15 +146,10 @@ module VagrantPlugins
136
146
  project_name = argv[0]
137
147
  client = get_eryph_client
138
148
 
139
- begin
140
- @env.ui.info("Creating project: #{project_name}")
141
- project = client.create_project(project_name)
142
- @env.ui.info("Project '#{project.name}' created successfully (ID: #{project.id})")
143
- 0
144
- rescue StandardError => e
145
- @env.ui.error("Failed to create project: #{e.message}")
146
- 1
147
- end
149
+ @env.ui.info("Creating project: #{project_name}")
150
+ project = client.create_project(project_name)
151
+ @env.ui.info("Project '#{project.name}' created successfully (ID: #{project.id})")
152
+ 0
148
153
  end
149
154
 
150
155
  def execute_remove
@@ -177,59 +182,29 @@ module VagrantPlugins
177
182
  project_name = argv[0]
178
183
  client = get_eryph_client
179
184
 
180
- begin
181
- project = client.get_project(project_name)
182
- unless project
183
- @env.ui.error("Project '#{project_name}' not found")
184
- return 1
185
- end
186
-
187
- unless @options[:force]
188
- response = @env.ui.ask("Project '#{project.name}' (ID: #{project.id}) and all catlets will be deleted! Continue? (y/N)")
189
- return 0 unless response.downcase.start_with?('y')
190
- end
185
+ project = client.get_project(project_name)
186
+ unless project
187
+ raise Errors::ProjectNotFoundError, { project_name: project_name }
188
+ end
191
189
 
192
- @env.ui.info("Removing project: #{project.name}")
193
- delete_project(client, project.id)
194
- @env.ui.info("Project '#{project.name}' removed successfully")
195
- 0
196
- rescue StandardError => e
197
- @env.ui.error("Failed to remove project: #{e.message}")
198
- 1
190
+ unless @options[:force]
191
+ response = @env.ui.ask("Project '#{project.name}' (ID: #{project.id}) and all catlets will be deleted! Continue? (y/N)")
192
+ return 0 unless response.downcase.start_with?('y')
199
193
  end
194
+
195
+ @env.ui.info("Removing project: #{project.name}")
196
+ delete_project(client, project.id)
197
+ @env.ui.info("Project '#{project.name}' removed successfully")
198
+ 0
200
199
  end
201
200
 
202
201
  def get_eryph_client
203
- # Try to get client from existing machines
204
- @env.machine_names.each do |name|
205
- machine = @env.machine(name, :eryph)
206
- if machine.provider_config.is_a?(VagrantPlugins::Eryph::Config)
207
- client = Helpers::EryphClient.new(machine)
208
-
209
- # Override client configuration if command line options are provided
210
- if @options[:configuration_name] || @options[:client_id]
211
- override_client_config(client)
212
- end
213
-
214
- return client
215
- end
216
- end
217
-
218
- # If no machines found, create a temporary config with command line options
202
+ # Always use standalone client for project management commands
219
203
  create_standalone_client
220
204
  end
221
205
 
222
206
  private
223
207
 
224
- def override_client_config(client)
225
- # Update the client's configuration with command line options
226
- config = client.instance_variable_get(:@config)
227
- config.configuration_name = @options[:configuration_name] if @options[:configuration_name]
228
- config.client_id = @options[:client_id] if @options[:client_id]
229
-
230
- # Force recreation of the client with new config
231
- client.instance_variable_set(:@client, nil)
232
- end
233
208
 
234
209
  def create_standalone_client
235
210
  # Create a minimal machine-like object for standalone client
@@ -238,6 +213,8 @@ module VagrantPlugins
238
213
  config = VagrantPlugins::Eryph::Config.new
239
214
  config.configuration_name = @options[:configuration_name] if @options[:configuration_name]
240
215
  config.client_id = @options[:client_id] if @options[:client_id]
216
+ config.ssl_verify = @options[:ssl_verify] unless @options[:ssl_verify].nil?
217
+ config.ssl_ca_file = @options[:ssl_ca_file] if @options[:ssl_ca_file]
241
218
  config.finalize!
242
219
 
243
220
  # Create a fake machine with just the provider config we need
@@ -248,7 +225,7 @@ module VagrantPlugins
248
225
 
249
226
  Helpers::EryphClient.new(fake_machine)
250
227
  rescue StandardError => e
251
- raise "Failed to create Eryph client: #{e.message}. Please configure at least one machine with the Eryph provider or ensure your Eryph configuration is set up."
228
+ raise "Failed to create Eryph client: #{e.message}. Please ensure eryph is running and your client configuration is set up correctly."
252
229
  end
253
230
 
254
231
  def delete_project(client, project_id)
@@ -271,6 +248,18 @@ module VagrantPlugins
271
248
  def initialize(argv, env)
272
249
  super
273
250
  @options = {}
251
+
252
+ # Parse global options from full argv first
253
+ global_parser = OptionParser.new do |o|
254
+ o.on('--configuration-name NAME', String) { |name| @options[:configuration_name] = name }
255
+ o.on('--client-id ID', String) { |id| @options[:client_id] = id }
256
+ o.on('--[no-]ssl-verify') { |verify| @options[:ssl_verify] = verify }
257
+ o.on('--ssl-ca-file FILE', String) { |file| @options[:ssl_ca_file] = file }
258
+ end
259
+
260
+ # Parse and remove global options, leaving subcommand and its args
261
+ remaining_args = global_parser.parse(argv.dup)
262
+
274
263
  @parser = OptionParser.new do |o|
275
264
  o.banner = 'Usage: vagrant eryph network <subcommand> [options]'
276
265
  o.separator ''
@@ -279,19 +268,14 @@ module VagrantPlugins
279
268
  o.separator ' set Set project network configuration from YAML'
280
269
  o.separator ''
281
270
  o.separator 'Global Options:'
282
-
283
- o.on('--configuration-name NAME', String, 'Eryph configuration name (default: auto-detect)') do |name|
284
- @options[:configuration_name] = name
285
- end
286
-
287
- o.on('--client-id ID', String, 'Eryph client ID') do |id|
288
- @options[:client_id] = id
289
- end
290
-
271
+ o.separator ' --configuration-name NAME Eryph configuration name (default: auto-detect)'
272
+ o.separator ' --client-id ID Eryph client ID'
273
+ o.separator ' --[no-]ssl-verify Enable/disable SSL certificate verification'
274
+ o.separator ' --ssl-ca-file FILE Path to custom CA certificate file'
291
275
  o.separator ''
292
276
  end
293
277
 
294
- @main_args, @sub_command, @sub_args = split_main_and_subcommand(argv)
278
+ @main_args, @sub_command, @sub_args = split_main_and_subcommand(remaining_args)
295
279
  end
296
280
 
297
281
  def execute
@@ -475,36 +459,12 @@ module VagrantPlugins
475
459
  end
476
460
 
477
461
  def get_eryph_client
478
- # Try to get client from existing machines
479
- @env.machine_names.each do |name|
480
- machine = @env.machine(name, :eryph)
481
- if machine.provider_config.is_a?(VagrantPlugins::Eryph::Config)
482
- client = Helpers::EryphClient.new(machine)
483
-
484
- # Override client configuration if command line options are provided
485
- if @options[:configuration_name] || @options[:client_id]
486
- override_client_config(client)
487
- end
488
-
489
- return client
490
- end
491
- end
492
-
493
- # If no machines found, create a temporary config with command line options
462
+ # Always use standalone client for project management commands
494
463
  create_standalone_client
495
464
  end
496
465
 
497
466
  private
498
467
 
499
- def override_client_config(client)
500
- # Update the client's configuration with command line options
501
- config = client.instance_variable_get(:@config)
502
- config.configuration_name = @options[:configuration_name] if @options[:configuration_name]
503
- config.client_id = @options[:client_id] if @options[:client_id]
504
-
505
- # Force recreation of the client with new config
506
- client.instance_variable_set(:@client, nil)
507
- end
508
468
 
509
469
  def create_standalone_client
510
470
  # Create a minimal machine-like object for standalone client
@@ -513,6 +473,8 @@ module VagrantPlugins
513
473
  config = VagrantPlugins::Eryph::Config.new
514
474
  config.configuration_name = @options[:configuration_name] if @options[:configuration_name]
515
475
  config.client_id = @options[:client_id] if @options[:client_id]
476
+ config.ssl_verify = @options[:ssl_verify] unless @options[:ssl_verify].nil?
477
+ config.ssl_ca_file = @options[:ssl_ca_file] if @options[:ssl_ca_file]
516
478
  config.finalize!
517
479
 
518
480
  # Create a fake machine with just the provider config we need
@@ -523,7 +485,7 @@ module VagrantPlugins
523
485
 
524
486
  Helpers::EryphClient.new(fake_machine)
525
487
  rescue StandardError => e
526
- raise "Failed to create Eryph client: #{e.message}. Please configure at least one machine with the Eryph provider or ensure your Eryph configuration is set up."
488
+ raise "Failed to create Eryph client: #{e.message}. Please ensure eryph is running and your client configuration is set up correctly."
527
489
  end
528
490
 
529
491
  def parse_network_config(config_string)
@@ -26,6 +26,22 @@ module VagrantPlugins
26
26
  end
27
27
  end
28
28
 
29
+ class OperationFailedError < EryphError
30
+ error_key(:operation_failed)
31
+ end
32
+
33
+ class ProjectNotFoundError < EryphError
34
+ error_key(:project_not_found)
35
+ end
36
+
37
+ class ProjectCreationError < EryphError
38
+ error_key(:project_creation_failed)
39
+ end
40
+
41
+ class CatletCreationError < EryphError
42
+ error_key(:catlet_creation_failed)
43
+ end
44
+
29
45
  class APIConnectionError < EryphError
30
46
  error_key(:api_connection_failed)
31
47
  end
@@ -38,10 +54,6 @@ module VagrantPlugins
38
54
  error_key(:catlet_not_found)
39
55
  end
40
56
 
41
- class OperationFailedError < EryphError
42
- error_key(:operation_failed)
43
- end
44
-
45
57
  class ConfigurationError < EryphError
46
58
  error_key(:configuration_error)
47
59
  end
@@ -91,7 +91,10 @@ module VagrantPlugins
91
91
 
92
92
  else
93
93
  error_msg = result.status_message || 'Operation failed'
94
- raise "Operation ID: #{operation.id} - Catlet creation failed: #{error_msg}"
94
+ raise Errors::CatletCreationError, {
95
+ catlet_name: catlet_config_hash[:name],
96
+ message: error_msg
97
+ }
95
98
  end
96
99
  end
97
100
 
@@ -174,15 +177,25 @@ module VagrantPlugins
174
177
  result = wait_for_operation(operation.id)
175
178
 
176
179
  if result.completed?
177
- # Use OperationResult's project accessor
178
- project = result.project
180
+ # Fetch the operation again with projects expanded
181
+ final_operation = client([SCOPES[:PROJECTS_READ]]).operations.operations_get(
182
+ result.id,
183
+ expand: 'projects'
184
+ )
185
+ final_result = ::Eryph::Compute::OperationResult.new(final_operation, client([SCOPES[:PROJECTS_READ]]))
186
+
187
+ # Now get the project from the properly expanded operation
188
+ project = final_result.project
179
189
  raise "Operation ID: #{operation.id} - Project creation completed but project not found" unless project
180
190
 
181
191
  @logger.info("Operation ID: #{operation.id} - created project with ID: #{project.id}")
182
192
  project # Return the project, not the result
183
193
  else
184
194
  error_msg = result.status_message || 'Operation failed'
185
- raise "Operation ID: #{operation.id} - Project creation failed: #{error_msg}"
195
+ raise Errors::ProjectCreationError, {
196
+ project_name: project_name,
197
+ message: error_msg
198
+ }
186
199
  end
187
200
  end
188
201
 
@@ -193,7 +206,7 @@ module VagrantPlugins
193
206
  return project if project
194
207
 
195
208
  unless @config.auto_create_project
196
- raise "Project '#{project_name}' not found and auto_create_project is disabled"
209
+ raise Errors::ProjectNotFoundError, { project_name: project_name }
197
210
  end
198
211
 
199
212
  @ui.info("Project '#{project_name}' not found, creating automatically...")
@@ -295,10 +308,9 @@ module VagrantPlugins
295
308
  end
296
309
 
297
310
  def wait_for_operation(operation_id, timeout = 600)
298
- start_time = Time.now
299
- current_tasks = {}
300
-
301
311
  @logger.info("Waiting for operation #{operation_id}...")
312
+ primary_task_name = nil
313
+ operation_started = false
302
314
 
303
315
  result = client([SCOPES[:CATLETS_READ]]).wait_for_operation(operation_id, timeout: timeout) do |event_type, data|
304
316
  case event_type
@@ -308,47 +320,41 @@ module VagrantPlugins
308
320
  resource_id = data.resource_id || data.id || 'unknown'
309
321
  @logger.debug("Attached #{resource_type} '#{resource_id}' to operation")
310
322
 
311
- when :task_new, :task_update
312
- # Track current tasks by ID
313
- if data.respond_to?(:id) && data.id
314
- current_tasks[data.id] = {
315
- name: data.display_name || data.name,
316
- progress: data.respond_to?(:progress) ? data.progress : nil
317
- }
318
-
319
- @logger.debug("Task update #{current_tasks[data.id].inspect}")
320
- end
321
-
322
- when :status
323
- # Report current task with progress if available
324
- elapsed = Time.now - start_time
323
+ when :task_new
324
+ # Check if this is the primary task (ParentTaskId == operation_id)
325
+ if data.respond_to?(:parent_task_id) && data.parent_task_id == operation_id
326
+ primary_task_name = data.respond_to?(:display_name) && data.display_name ? data.display_name :
327
+ (data.respond_to?(:name) && data.name ? data.name : 'Operation')
325
328
 
326
- # Find active tasks with progress
327
- active_task = current_tasks.values.find do |task|
328
- task[:progress]&.positive? && task[:progress] < 100
329
+ unless operation_started
330
+ @ui.info("Waiting for operation #{primary_task_name} (#{operation_id})")
331
+ operation_started = true
332
+ end
329
333
  end
330
334
 
331
- if active_task
332
- @ui.info("Working... - #{active_task[:name]} #{active_task[:progress]}% - #{elapsed.round}s total elapsed")
335
+ when :log_entry
336
+ # Display log messages like .NET client verbose output
337
+ if data.respond_to?(:message) && data.message
338
+ @ui.info(data.message)
333
339
  end
334
340
  end
335
341
  end
336
342
 
337
343
  # Show final result
338
344
  if result.completed?
345
+ @ui.info("Operation ID: #{operation_id} - Operation completed successfully")
339
346
  @logger.info("Operation #{operation_id} completed successfully")
340
347
  elsif result.failed?
341
348
  error_msg = result.status_message || 'Operation failed'
342
- @ui.error("Operation failed: #{error_msg}")
343
- raise "Operation #{operation_id} failed: #{error_msg}"
349
+ raise Errors::OperationFailedError, {
350
+ operation_id: operation_id,
351
+ message: error_msg
352
+ }
344
353
  else
345
354
  @ui.warn("Operation finished with status: #{result.status}")
346
355
  end
347
356
 
348
357
  result
349
- rescue StandardError => e
350
- @ui.error("Error waiting for operation: #{e.message}")
351
- raise e
352
358
  end
353
359
 
354
360
  private
@@ -1,5 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ begin
4
+ require 'vagrant'
5
+ rescue LoadError
6
+ raise 'The Vagrant Eryph plugin must be run within Vagrant.'
7
+ end
8
+
9
+ # This is a sanity check to make sure no one is attempting to install
10
+ # this into an early Vagrant version.
11
+ if Vagrant::VERSION < '2.0.0'
12
+ raise 'The Vagrant Eryph plugin is only compatible with Vagrant 2.0+'
13
+ end
14
+
3
15
  module VagrantPlugins
4
16
  module Eryph
5
17
  class Plugin < Vagrant.plugin('2')
@@ -23,6 +35,17 @@ module VagrantPlugins
23
35
  require_relative 'command'
24
36
  Command
25
37
  end
38
+
39
+ def self.setup_i18n
40
+ # Get the gem root directory (two levels up from lib/vagrant-eryph/)
41
+ gem_root = File.expand_path('../../..', __FILE__)
42
+ locale_file = File.join(gem_root, 'locales', 'en.yml')
43
+ I18n.load_path << locale_file if File.exist?(locale_file)
44
+ I18n.reload!
45
+ end
26
46
  end
27
47
  end
28
48
  end
49
+
50
+ # Setup i18n
51
+ VagrantPlugins::Eryph::Plugin.setup_i18n
@@ -2,6 +2,6 @@
2
2
 
3
3
  module VagrantPlugins
4
4
  module Eryph
5
- VERSION = '0.1.0'
5
+ VERSION = '0.1.2'
6
6
  end
7
7
  end
data/locales/en.yml ADDED
@@ -0,0 +1,18 @@
1
+ en:
2
+ vagrant_eryph:
3
+ errors:
4
+ api_connection_failed: |-
5
+ Failed to connect to eryph. Please check your network connection
6
+ and Eryph service status.
7
+ credentials_not_found: |-
8
+ Eryph credentials not found. Please configure your credentials using
9
+ the Eryph CLI or set up configuration files in your .eryph directory.
10
+ catlet_not_found: |-
11
+ The catlet could not be found in Eryph. It may have been deleted
12
+ outside of Vagrant.
13
+ operation_failed: "Operation %{operation_id} failed: %{message}"
14
+ project_not_found: "Project '%{project_name}' not found"
15
+ project_creation_failed: "Failed to create project '%{project_name}': %{message}"
16
+ catlet_creation_failed: "Failed to create catlet '%{catlet_name}': %{message}"
17
+ configuration_error: |-
18
+ Invalid configuration. Please check your Vagrantfile settings.
@@ -6,17 +6,17 @@ Gem::Specification.new do |spec|
6
6
  spec.authors = ['dbosoft and eryph contributors']
7
7
  spec.email = ['package-maintainers@eryph.io']
8
8
 
9
- spec.summary = 'Vagrant provider for Eryph'
9
+ spec.summary = 'Vagrant provider for eryph'
10
10
  spec.description = 'A Vagrant provider plugin that allows you to manage catlets using Eryph\'s compute API'
11
11
  spec.homepage = 'https://github.com/eryph-org/vagrant-eryph'
12
12
  spec.license = 'MIT'
13
13
 
14
14
  spec.required_ruby_version = '>= 3.1.0'
15
15
 
16
- spec.files = Dir.glob('lib/**/*') + %w[README.md LICENSE vagrant-eryph.gemspec]
16
+ spec.files = Dir.glob('lib/**/*') + Dir.glob('locales/**/*') + %w[README.md CHANGELOG.md LICENSE vagrant-eryph.gemspec]
17
17
  spec.require_paths = ['lib']
18
18
 
19
- spec.add_dependency 'eryph-compute', '~> 0.1.1'
19
+ spec.add_dependency 'eryph-compute', '~> 0.1'
20
20
  spec.add_dependency 'eryph-clientruntime', '~> 0.1'
21
21
  spec.add_dependency 'log4r', '~> 1.1'
22
22
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vagrant-eryph
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - dbosoft and eryph contributors
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: 0.1.1
18
+ version: '0.1'
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: 0.1.1
25
+ version: '0.1'
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: eryph-clientruntime
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -115,6 +115,7 @@ executables: []
115
115
  extensions: []
116
116
  extra_rdoc_files: []
117
117
  files:
118
+ - CHANGELOG.md
118
119
  - LICENSE
119
120
  - README.md
120
121
  - lib/vagrant-eryph.rb
@@ -123,6 +124,7 @@ files:
123
124
  - lib/vagrant-eryph/actions/create_catlet.rb
124
125
  - lib/vagrant-eryph/actions/destroy_catlet.rb
125
126
  - lib/vagrant-eryph/actions/is_created.rb
127
+ - lib/vagrant-eryph/actions/is_state.rb
126
128
  - lib/vagrant-eryph/actions/is_stopped.rb
127
129
  - lib/vagrant-eryph/actions/message_already_created.rb
128
130
  - lib/vagrant-eryph/actions/message_not_created.rb
@@ -140,6 +142,7 @@ files:
140
142
  - lib/vagrant-eryph/plugin.rb
141
143
  - lib/vagrant-eryph/provider.rb
142
144
  - lib/vagrant-eryph/version.rb
145
+ - locales/en.yml
143
146
  - vagrant-eryph.gemspec
144
147
  homepage: https://github.com/eryph-org/vagrant-eryph
145
148
  licenses:
@@ -159,7 +162,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
159
162
  - !ruby/object:Gem::Version
160
163
  version: '0'
161
164
  requirements: []
162
- rubygems_version: 3.7.2
165
+ rubygems_version: 3.6.7
163
166
  specification_version: 4
164
- summary: Vagrant provider for Eryph
167
+ summary: Vagrant provider for eryph
165
168
  test_files: []