vagrant-eryph 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9decd72160b533c3c04d1c06ab3e7d05e3c28b4415940c05af982ce66cbb2f8b
4
+ data.tar.gz: 914506e8dc13c6665743360f03c4b8ebcedd180bf73e0f2daed82b290c8d27f6
5
+ SHA512:
6
+ metadata.gz: 02354e91939f344f23cef10df4d263fcb0612339288483e16579c160a3e4e9e11652ef26c13b5ff28dec8a39192cb185085294fb123539d5af52efa7954679c2
7
+ data.tar.gz: 17e138e16fd7b7eb09e750e41a98de186a6c8a39519c18c53b20e6bfef9283c5ebe8519a3a0be0be2eb8817af29dde47d287d0a764b6a1b611988a910023976e
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 dbosoft
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,79 @@
1
+ # Vagrant Eryph Provider
2
+
3
+ A Vagrant provider plugin for [Eryph](https://www.eryph.io/) that allows you to manage catlets using Eryph's compute API.
4
+
5
+ ## Features
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
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ gem install vagrant-eryph
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ ```ruby
23
+ Vagrant.configure("2") do |config|
24
+ 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
28
+ end
29
+ end
30
+ ```
31
+
32
+ ## Configuration
33
+
34
+ ### Basic Settings
35
+ - `project` - Eryph project name
36
+ - `parent` - Parent gene for the catlet
37
+ - `auto_create_project` - Auto-create project if it doesn't exist (default: true)
38
+
39
+ ### Auto-configuration
40
+ - `auto_config` - Enable/disable automatic Vagrant user setup (default: true)
41
+ - `enable_winrm` - Enable WinRM for Windows catlets (default: true)
42
+ - `vagrant_password` - Password for Windows vagrant user (default: "vagrant")
43
+ - `ssh_key_injection` - SSH key injection method (:direct or :variable)
44
+
45
+ ### Resources
46
+ - `cpus` - Number of CPUs
47
+ - `memory` - Memory in MB
48
+ - `drives` - Custom drives configuration
49
+ - `networks` - Custom networks configuration
50
+
51
+ ### Cloud-init
52
+ - `fodder` - Custom cloud-init configuration (merged with auto-generated config)
53
+
54
+ ## Project Management
55
+
56
+ The plugin includes commands to manage Eryph projects:
57
+
58
+ ```bash
59
+ # List projects
60
+ vagrant eryph project list
61
+
62
+ # Create project
63
+ vagrant eryph project create my-project --description "My development project"
64
+
65
+ # Manage project networks
66
+ vagrant eryph network get my-project
67
+ vagrant eryph network set my-project --file networks.yml
68
+ ```
69
+
70
+ ## Development
71
+
72
+ ```bash
73
+ bundle install
74
+ bundle exec vagrant --help
75
+ ```
76
+
77
+ ## License
78
+
79
+ MIT
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../helpers/eryph_client'
4
+
5
+ module VagrantPlugins
6
+ module Eryph
7
+ module Actions
8
+ class ConnectEryph
9
+ def initialize(app, _env)
10
+ @app = app
11
+ end
12
+
13
+ def call(env)
14
+ # Initialize Eryph client and store it in the environment
15
+ env[:eryph_client] = Helpers::EryphClient.new(env[:machine])
16
+
17
+ # Test the connection by calling client (which tests connectivity)
18
+ env[:eryph_client].client
19
+
20
+ # Ensure the project exists (create if needed and auto_create_project is enabled)
21
+ config = env[:machine].provider_config
22
+ env[:eryph_client].ensure_project_exists(config.project) if config.project
23
+
24
+ @app.call(env)
25
+ 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
29
+ 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
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'json'
5
+
6
+ module VagrantPlugins
7
+ module Eryph
8
+ module Actions
9
+ class CreateCatlet
10
+ def initialize(app, _env)
11
+ @app = app
12
+ end
13
+
14
+ def call(env)
15
+ env[:machine].provider_config
16
+ ui = env[:ui]
17
+ client = env[:eryph_client]
18
+
19
+ # Build catlet creation request as hash (like AWS provider)
20
+ catlet_config = build_catlet_config(env)
21
+
22
+ ui.info('Creating catlet with configuration:')
23
+ ui.info(" Name: #{catlet_config[:name]}")
24
+ ui.info(" Parent Gene: #{catlet_config[:parent]}")
25
+ ui.info(" Project: #{catlet_config[:project]}")
26
+
27
+ # Create and start the catlet (client handles the full lifecycle)
28
+ operation_result = client.create_catlet(catlet_config)
29
+
30
+ # Store the catlet ID from the operation result
31
+ # The client handles creation and starting, now extract the catlet
32
+ raise 'Catlet creation failed' unless operation_result&.completed?
33
+
34
+ catlet = operation_result.catlet
35
+ raise 'Failed to get catlet from operation result' unless catlet
36
+
37
+ env[:machine].id = catlet.id
38
+ ui.info("Catlet provisioned successfully with ID: #{catlet.id}")
39
+
40
+ # Clear cached catlet status to force refresh on next lookup
41
+ Provider.instance_variable_set(:@eryph_catlets, nil)
42
+
43
+ @app.call(env)
44
+ rescue StandardError => e
45
+ ui.error("Failed to create catlet: #{e.message}")
46
+ raise e
47
+ end
48
+
49
+ private
50
+
51
+ def build_catlet_config(env)
52
+ config = env[:machine].provider_config
53
+ fodder = env[:catlet_fodder] || []
54
+
55
+ # Use the new effective_catlet_configuration method
56
+ catlet_config = config.effective_catlet_configuration(env[:machine])
57
+
58
+ # Add fodder configuration if present
59
+ if fodder.any?
60
+ catlet_config[:fodder] = fodder.map do |item|
61
+ if item[:content]
62
+ # Regular fodder with content - serialize the content
63
+ item.merge(content: serialize_fodder_content(item[:content]))
64
+ else
65
+ # Gene fodder or other - copy as-is
66
+ item
67
+ end
68
+ end
69
+ end
70
+
71
+ catlet_config
72
+ end
73
+
74
+ def serialize_fodder_content(content)
75
+ case content
76
+ when Hash
77
+ # Convert hash to YAML for cloud-config (remove YAML document separator)
78
+ content.to_yaml.sub(/^---\n/, '')
79
+ when String
80
+ content
81
+ else
82
+ content.to_s
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VagrantPlugins
4
+ module Eryph
5
+ module Actions
6
+ class DestroyCatlet
7
+ def initialize(app, _env)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ return @app.call(env) unless env[:machine].id
13
+
14
+ ui = env[:ui]
15
+ client = env[:eryph_client]
16
+
17
+ ui.info('Destroying catlet...')
18
+ client.destroy_catlet(env[:machine].id)
19
+
20
+ # Clear the machine ID since it no longer exists
21
+ env[:machine].id = nil
22
+
23
+ ui.info('Catlet destroyed successfully')
24
+
25
+ @app.call(env)
26
+ rescue StandardError => e
27
+ ui.error("Failed to destroy catlet: #{e.message}")
28
+ raise e
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VagrantPlugins
4
+ module Eryph
5
+ module Actions
6
+ class IsCreated
7
+ def initialize(app, _env)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ # Check if the machine has been created by looking for a machine ID
13
+ # and verifying the catlet exists in Eryph
14
+ env[:result] = env[:machine].id && catlet_exists?(env)
15
+ @app.call(env)
16
+ end
17
+
18
+ private
19
+
20
+ def catlet_exists?(env)
21
+ return false unless env[:machine].id
22
+
23
+ begin
24
+ catlet = Provider.eryph_catlet(env[:machine])
25
+ catlet.respond_to?(:status) && catlet.status != 'not_created'
26
+ rescue StandardError => e
27
+ env[:ui].warn("Error checking catlet existence: #{e.message}")
28
+ false
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VagrantPlugins
4
+ module Eryph
5
+ module Actions
6
+ class IsStopped
7
+ def initialize(app, _env)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ # Check if the catlet is in a stopped state
13
+ catlet = Provider.eryph_catlet(env[:machine])
14
+ env[:result] = catlet && catlet.status&.downcase == 'stopped'
15
+ @app.call(env)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VagrantPlugins
4
+ module Eryph
5
+ module Actions
6
+ class MessageAlreadyCreated
7
+ def initialize(app, _env)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ env[:ui].info('Catlet is already created and running.')
13
+ @app.call(env)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VagrantPlugins
4
+ module Eryph
5
+ module Actions
6
+ class MessageNotCreated
7
+ def initialize(app, _env)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ env[:ui].error('Catlet has not been created. Run `vagrant up` first.')
13
+ @app.call(env)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VagrantPlugins
4
+ module Eryph
5
+ module Actions
6
+ class MessageWillNotDestroy
7
+ def initialize(app, _env)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ env[:ui].info('Catlet will not be destroyed, since the confirmation was declined.')
13
+ @app.call(env)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../helpers/cloud_init'
4
+
5
+ module VagrantPlugins
6
+ module Eryph
7
+ module Actions
8
+ class PrepareCloudInit
9
+ def initialize(app, _env)
10
+ @app = app
11
+ end
12
+
13
+ def call(env)
14
+ env[:machine].provider_config
15
+ ui = env[:ui]
16
+
17
+ # Initialize cloud-init helper
18
+ cloud_init = Helpers::CloudInit.new(env[:machine])
19
+
20
+ ui.info('Preparing cloud-init configuration...')
21
+
22
+ # Generate complete fodder configuration (auto + user)
23
+ fodder = cloud_init.generate_complete_fodder
24
+
25
+ if fodder.any?
26
+ ui.info("Generated #{fodder.length} cloud-init fodder entries:")
27
+
28
+ # Log fodder details with content
29
+ fodder.each do |item|
30
+ if item[:source]
31
+ # Gene fodder - show name if available, otherwise source
32
+ display_name = item[:name] || item[:source]
33
+ ui.detail(" - #{display_name} (gene)")
34
+ else
35
+ # Regular fodder
36
+ ui.detail(" - #{item[:name]} (#{item[:type]})")
37
+ end
38
+ end
39
+ end
40
+
41
+ # Store fodder in environment for use by create action
42
+ env[:catlet_fodder] = fodder
43
+
44
+ @app.call(env)
45
+ rescue StandardError => e
46
+ ui.error("Failed to prepare cloud-init configuration: #{e.message}")
47
+ raise e
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VagrantPlugins
4
+ module Eryph
5
+ module Actions
6
+ class ReadSSHInfo
7
+ def initialize(app, _env)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ # Delegate to the provider's ssh_info method for consistency
13
+ provider = env[:machine].provider
14
+ env[:machine_ssh_info] = provider.ssh_info
15
+ @app.call(env)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VagrantPlugins
4
+ module Eryph
5
+ module Actions
6
+ class ReadState
7
+ def initialize(app, _env)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ env[:machine_state_id] = read_state(env)
13
+ @app.call(env)
14
+ end
15
+
16
+ private
17
+
18
+ def read_state(env)
19
+ return :not_created unless env[:machine].id
20
+
21
+ begin
22
+ catlet = Provider.eryph_catlet(env[:machine], refresh: true)
23
+
24
+ if catlet.respond_to?(:status) && catlet.status
25
+ map_catlet_state_to_vagrant(catlet.status)
26
+ else
27
+ :not_created
28
+ end
29
+ rescue StandardError => e
30
+ env[:ui].warn("Error reading catlet state: #{e.message}")
31
+ :unknown
32
+ end
33
+ end
34
+
35
+ def map_catlet_state_to_vagrant(eryph_status)
36
+ case eryph_status.downcase
37
+ when 'running'
38
+ :running
39
+ when 'stopped'
40
+ :stopped
41
+ when 'pending'
42
+ :unknown
43
+ when 'error'
44
+ :error
45
+ else
46
+ :unknown
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VagrantPlugins
4
+ module Eryph
5
+ module Actions
6
+ class StartCatlet
7
+ def initialize(app, _env)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ return @app.call(env) unless env[:machine].id
13
+
14
+ ui = env[:ui]
15
+ client = env[:eryph_client]
16
+
17
+ # Check current state
18
+ catlet = Provider.eryph_catlet(env[:machine])
19
+
20
+ if catlet.status&.downcase == 'running'
21
+ ui.info('Catlet is already running')
22
+ return @app.call(env)
23
+ end
24
+
25
+ ui.info('Starting catlet...')
26
+ client.start_catlet(env[:machine].id)
27
+ ui.info('Catlet started successfully')
28
+
29
+ # Clear cached catlet status to force refresh on next lookup
30
+ Provider.instance_variable_set(:@eryph_catlets, nil)
31
+
32
+ @app.call(env)
33
+ rescue StandardError => e
34
+ ui.error("Failed to start catlet: #{e.message}")
35
+ raise e
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VagrantPlugins
4
+ module Eryph
5
+ module Actions
6
+ class StopCatlet
7
+ def initialize(app, _env)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ return @app.call(env) unless env[:machine].id
13
+
14
+ ui = env[:ui]
15
+ client = env[:eryph_client]
16
+
17
+ # Check current state
18
+ catlet = Provider.eryph_catlet(env[:machine])
19
+
20
+ if catlet.status&.downcase == 'stopped'
21
+ ui.info('Catlet is already stopped')
22
+ return @app.call(env)
23
+ end
24
+
25
+ ui.info('Stopping catlet...')
26
+ client.stop_catlet(env[:machine].id, 'graceful')
27
+ ui.info('Catlet stopped successfully')
28
+
29
+ # Clear cached catlet status to force refresh on next lookup
30
+ Provider.instance_variable_set(:@eryph_catlets, nil)
31
+
32
+ @app.call(env)
33
+ rescue StandardError => e
34
+ ui.error("Failed to stop catlet: #{e.message}")
35
+ raise e
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end