foreman_nutanix 0.0.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.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +22 -0
  3. data/README.md +32 -0
  4. data/Rakefile +49 -0
  5. data/app/assets/javascripts/foreman_nutanix/locale/en/foreman_nutanix.js +55 -0
  6. data/app/controllers/concerns/foreman/controller/parameters/compute_resource_extension.rb +17 -0
  7. data/app/controllers/foreman_nutanix/api/v2/apipie_extensions.rb +16 -0
  8. data/app/controllers/foreman_nutanix/api/v2/compute_resources_extensions.rb +29 -0
  9. data/app/controllers/foreman_nutanix/api/v2/hosts_controller_extensions.rb +50 -0
  10. data/app/lib/foreman_nutanix/nutanix_adapter.rb +105 -0
  11. data/app/lib/nutanix_compute/compute_collection.rb +23 -0
  12. data/app/lib/nutanix_extensions/attached_disk.rb +22 -0
  13. data/app/models/concerns/foreman_nutanix/host_managed_extensions.rb +38 -0
  14. data/app/models/foreman_nutanix/nutanix.rb +471 -0
  15. data/app/models/foreman_nutanix/nutanix_compute.rb +370 -0
  16. data/app/views/compute_resources/form/_nutanix.html.erb +9 -0
  17. data/app/views/compute_resources/show/_nutanix.html.erb +238 -0
  18. data/app/views/compute_resources_vms/form/nutanix/_base.html.erb +72 -0
  19. data/app/views/compute_resources_vms/form/nutanix/_volume.html.erb +1 -0
  20. data/app/views/compute_resources_vms/index/_gce.html.erb +41 -0
  21. data/app/views/compute_resources_vms/index/_nutanix.html.erb +41 -0
  22. data/app/views/compute_resources_vms/show/_gce.html.erb +18 -0
  23. data/app/views/compute_resources_vms/show/_nutanix.html.erb +81 -0
  24. data/config/initializers/zeitwerk.rb +1 -0
  25. data/config/routes.rb +2 -0
  26. data/lib/foreman_nutanix/engine.rb +56 -0
  27. data/lib/foreman_nutanix/version.rb +3 -0
  28. data/lib/foreman_nutanix.rb +4 -0
  29. data/lib/tasks/foreman_nutanix_tasks.rake +31 -0
  30. data/locale/foreman_nutanix.pot +131 -0
  31. data/package.json +41 -0
  32. data/webpack/global_index.js +6 -0
  33. data/webpack/global_test_setup.js +11 -0
  34. data/webpack/index.js +7 -0
  35. data/webpack/legacy.js +16 -0
  36. data/webpack/src/Extends/index.js +15 -0
  37. data/webpack/src/Router/routes.js +5 -0
  38. data/webpack/src/reducers.js +7 -0
  39. data/webpack/test_setup.js +17 -0
  40. metadata +100 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 72f1e26e3c671aaf28a8bac7d0cf5096d806b68f6eb5f0cc0a804f00fc093615
4
+ data.tar.gz: b7208fcee1fbc1862cb51660450768dcded3667515d26497e82feb277f645c3f
5
+ SHA512:
6
+ metadata.gz: 7821fc531b943cf9fd9313275250de30f98147e9e7d3b677d5cd1d1162d309c21a0f8f659e32a642c3f03121d0db3ad82745e31c222487634f401611c8a2e966
7
+ data.tar.gz: 05f0992cc5f62bf0981bfa5b3525f41b880bd8a0553ae14c72e3778769660a926e19b8b15a6920aadd24404a29979364b435d1dc18aada78a8e116603acc41ce
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 NORCE Research
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.
22
+
data/README.md ADDED
@@ -0,0 +1,32 @@
1
+ # Foreman Nutanix Plugin
2
+
3
+ Nutanix compute resource plugin for [Foreman](https://theforeman.org/).
4
+
5
+ ## Installation
6
+
7
+ Add to your Foreman's bundler.d:
8
+
9
+ ```ruby
10
+ # bundler.d/foreman_nutanix.rb
11
+ gem 'foreman_nutanix'
12
+ ```
13
+
14
+ Then run:
15
+
16
+ ```bash
17
+ bundle install
18
+ ```
19
+
20
+ ## Configuration
21
+
22
+ This plugin requires the [nutanix-shim-server](https://pypi.org/project/nutanix-shim-server/) to be running and accessible.
23
+
24
+ Set the shim server address:
25
+
26
+ ```bash
27
+ export NUTANIX_SHIM_SERVER_ADDR=http://localhost:8000
28
+ ```
29
+
30
+ ## License
31
+
32
+ MIT
data/Rakefile ADDED
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'ForemanNutanix'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ APP_RAKEFILE = File.expand_path('test/dummy/Rakefile', __dir__)
24
+
25
+ Bundler::GemHelper.install_tasks
26
+
27
+ require 'rake/testtask'
28
+
29
+ Rake::TestTask.new(:test) do |t|
30
+ t.libs << 'lib'
31
+ t.libs << 'test'
32
+ t.pattern = 'test/**/*_test.rb'
33
+ t.verbose = false
34
+ end
35
+
36
+ task default: :test
37
+
38
+ begin
39
+ require 'rubocop/rake_task'
40
+ RuboCop::RakeTask.new
41
+ rescue StandardError => _e
42
+ puts 'Rubocop not loaded.'
43
+ rescue LoadError
44
+ puts 'Rubocop not loaded.'
45
+ end
46
+
47
+ task :default do
48
+ Rake::Task['rubocop'].execute
49
+ end
@@ -0,0 +1,55 @@
1
+ locales["foreman_nutanix"] = locales["foreman_nutanix"] || {};
2
+ locales["foreman_nutanix"]["en"] = {
3
+ domain: "foreman_nutanix",
4
+ locale_data: {
5
+ foreman_nutanix: {
6
+ "": {
7
+ "Project-Id-Version": "foreman_nutanix 1.0.0",
8
+ "Report-Msgid-Bugs-To": "",
9
+ "PO-Revision-Date": "2025-02-20 10:11+0100",
10
+ "Last-Translator": "FULL NAME <EMAIL@ADDRESS>",
11
+ "Language-Team": "LANGUAGE <LL@li.org>",
12
+ Language: "",
13
+ "MIME-Version": "1.0",
14
+ "Content-Type": "text/plain; charset=UTF-8",
15
+ "Content-Transfer-Encoding": "8bit",
16
+ "Plural-Forms": "nplurals=INTEGER; plural=EXPRESSION;",
17
+ lang: "en",
18
+ domain: "foreman_nutanix",
19
+ plural_forms: "nplurals=INTEGER; plural=EXPRESSION;",
20
+ },
21
+ Actions: [""],
22
+ "Associate Ephemeral External IP": [""],
23
+ "Certificate path, for Nutanix only": [""],
24
+ "Deprecated, email is automatically loaded from the JSON file. For Nutanix only":
25
+ [""],
26
+ "Deprecated, project is automatically loaded from the JSON file. For Nutanix only":
27
+ [""],
28
+ "Does this image support user data input (e.g. via cloud-init)?": [""],
29
+ "nutanix Compute Engine plugin for the Foreman.": [""],
30
+ Image: [""],
31
+ "JSON key": [""],
32
+ "Load Zones": [""],
33
+ "Machine Type": [""],
34
+ "Machine type": [""],
35
+ "Missing an image for operating system!": [""],
36
+ Name: [""],
37
+ Network: [""],
38
+ "Please select an image": [""],
39
+ Properties: [""],
40
+ "Size (GB)": [""],
41
+ State: [""],
42
+ "The email parameter is deprecated, value is automatically loaded from the JSON file":
43
+ [""],
44
+ "The project parameter is deprecated, value is automatically loaded from the JSON file":
45
+ [""],
46
+ "The user that is used to ssh into the instance, normally cloud-user, ec2-user, ubuntu, etc. Note: nutanix engine doesn't support SSH for the root user.":
47
+ [""],
48
+ Type: [""],
49
+ Zone: [""],
50
+ "Zone, for Nutanix only": [""],
51
+ "console is not available at this time because the instance is powered off":
52
+ [""],
53
+ },
54
+ },
55
+ };
@@ -0,0 +1,17 @@
1
+ module Foreman
2
+ module Controller
3
+ module Parameters
4
+ module ComputeResourceExtension
5
+ extend ActiveSupport::Concern
6
+
7
+ class_methods do
8
+ def compute_resource_params_filter
9
+ super.tap do |filter|
10
+ filter.permit :key_path, :zone, :cluster, :host_id
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ module ForemanNutanix
2
+ module Api
3
+ module V2
4
+ module ApipieExtensions
5
+ extend Apipie::DSL::Concern
6
+
7
+ update_api(:create, :update) do
8
+ param :compute_resource, Hash do
9
+ param :cluster, String, desc: N_('Nutanix cluster UUID')
10
+ param :zone, String, desc: N_('Zone/availability zone')
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,29 @@
1
+ module ForemanNutanix
2
+ module Api
3
+ module V2
4
+ module ComputeResourcesExtensions
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ before_action :read_key, only: [:create]
9
+ before_action :deprecated_params, only: [:create]
10
+ end
11
+
12
+ private
13
+
14
+ def read_key
15
+ return unless compute_resource_params['provider'] == 'Nutanix'
16
+ # Handle key file reading if needed
17
+ Rails.logger.info "ForemanNutanix: Reading key for Nutanix provider"
18
+ end
19
+
20
+ def deprecated_params
21
+ return unless compute_resource_params['provider'] == 'Nutanix'
22
+
23
+ # Handle deprecated parameters
24
+ Rails.logger.info "ForemanNutanix: Checking deprecated params for Nutanix provider"
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,50 @@
1
+ module ForemanNutanix
2
+ module Api
3
+ module V2
4
+ module HostsControllerExtensions
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ # Prepend to override the power_status method
9
+ prepend PowerStatusOverride
10
+ end
11
+
12
+ module PowerStatusOverride
13
+ def power_status
14
+ Rails.logger.info '=== NUTANIX: HostsControllerExtensions::power_status called ==='
15
+
16
+ # Check if this host uses a Nutanix compute resource
17
+ if @host&.compute_resource.is_a?(ForemanNutanix::Nutanix)
18
+ Rails.logger.info "=== NUTANIX: Host #{@host.name} uses Nutanix compute resource ==="
19
+
20
+ # Get the VM and its actual power state
21
+ vm = @host.compute_resource.find_vm_by_uuid(@host.uuid)
22
+ if vm
23
+ state = vm.state
24
+ Rails.logger.info "=== NUTANIX: VM state is '#{state}' ==="
25
+
26
+ # Map to Foreman's expected format
27
+ case state
28
+ when 'running'
29
+ render json: { id: @host.id, state: 'on', title: 'On', statusText: 'Powered On' }
30
+ when 'stopped'
31
+ render json: { id: @host.id, state: 'off', title: 'Off', statusText: 'Powered Off' }
32
+ when 'paused'
33
+ render json: { id: @host.id, state: 'paused', title: 'Paused', statusText: 'Paused' }
34
+ else
35
+ render json: { id: @host.id, state: 'na', title: 'N/A', statusText: 'Unknown' }
36
+ end
37
+ else
38
+ Rails.logger.warn "=== NUTANIX: VM not found for host #{@host.name} ==="
39
+ render json: { id: @host.id, state: 'na', title: 'N/A', statusText: 'VM Not Found' }
40
+ end
41
+ else
42
+ # Fall back to default behavior for non-Nutanix hosts
43
+ super
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,105 @@
1
+ module ForemanNutanix
2
+ class NutanixAdapter
3
+ def initialize(cluster)
4
+ Rails.logger.info "=== NUTANIX: NutanixAdapter::initialize cluster=#{cluster} ==="
5
+ @cluster = cluster
6
+ end
7
+
8
+ # Required by Foreman - returns a collection of servers
9
+ def servers(attrs = {})
10
+ Rails.logger.info "=== NUTANIX: NutanixAdapter::servers called with attrs: #{attrs} ==="
11
+ ServersCollection.new(@cluster)
12
+ end
13
+
14
+ # Mock servers collection for Foreman
15
+ class ServersCollection
16
+ def initialize(cluster)
17
+ @cluster = cluster
18
+ end
19
+
20
+ def new(attrs = {})
21
+ Rails.logger.info "=== NUTANIX: ServersCollection::new called with attrs: #{attrs} ==="
22
+ # Return the NutanixCompute model that Foreman expects
23
+ NutanixCompute.new(@cluster, attrs)
24
+ end
25
+
26
+ def get(uuid)
27
+ Rails.logger.info "=== NUTANIX: ServersCollection::get called with uuid: #{uuid} ==="
28
+
29
+ # Extract the actual UUID if it has a prefix
30
+ actual_uuid = uuid.to_s.include?(':') ? uuid.to_s.split(':').last : uuid.to_s
31
+
32
+ # Fetch full VM details from shim server
33
+ base = ENV['NUTANIX_SHIM_SERVER_ADDR'] || 'http://localhost:8000'
34
+ uri = URI("#{base.chomp('/')}/api/v1/vmm/vms/#{actual_uuid}")
35
+ response = Net::HTTP.get_response(uri)
36
+
37
+ if response.is_a?(Net::HTTPSuccess)
38
+ data = JSON.parse(response.body)
39
+
40
+ total_cpus = (data['num_sockets'] || 1) * (data['num_cores_per_socket'] || 1)
41
+ memory_gb = data['memory_size_bytes'] ? (data['memory_size_bytes'].to_f / 1024**3).round : 4
42
+
43
+ vm = NutanixCompute.new(@cluster, {
44
+ identity: data['ext_id'],
45
+ name: data['name'],
46
+ cpus: total_cpus,
47
+ memory: memory_gb,
48
+ power_state: data['power_state'],
49
+ mac_address: data['mac_address'],
50
+ ip_addresses: data['ip_addresses'] || [],
51
+ create_time: data['create_time'],
52
+ })
53
+ vm.instance_variable_set(:@persisted, true)
54
+ vm
55
+ elsif response.code == '404'
56
+ Rails.logger.warn "=== NUTANIX: VM not found (deleted from Nutanix?): #{uuid} ==="
57
+ # Return nil so Foreman knows the VM doesn't exist
58
+ nil
59
+ else
60
+ Rails.logger.error "=== NUTANIX: ServersCollection::get failed: #{response.code} - #{response.body} ==="
61
+ nil
62
+ end
63
+ rescue StandardError => e
64
+ Rails.logger.error "=== NUTANIX: ServersCollection::get error: #{e.message} ==="
65
+ nil
66
+ end
67
+
68
+ def all(opts = {})
69
+ Rails.logger.info "=== NUTANIX: ServersCollection::all called with opts: #{opts} ==="
70
+
71
+ # Fetch VMs from shim server (now includes MAC and IP)
72
+ base = ENV['NUTANIX_SHIM_SERVER_ADDR'] || 'http://localhost:8000'
73
+ uri = URI("#{base.chomp('/')}/api/v1/vmm/list-vms")
74
+ response = Net::HTTP.get_response(uri)
75
+ data = JSON.parse(response.body)
76
+
77
+ # Filter VMs by cluster
78
+ filtered_data = data.select { |vm| vm['cluster_ext_id'] == @cluster }
79
+
80
+ # Convert to NutanixCompute instances
81
+ filtered_data.map do |vm_data|
82
+ total_cpus = (vm_data['num_sockets'] || 1) * (vm_data['num_cores_per_socket'] || 1)
83
+ memory_gb = vm_data['memory_size_bytes'] ? (vm_data['memory_size_bytes'].to_f / 1024**3).round : 4
84
+
85
+ vm = NutanixCompute.new(@cluster, {
86
+ identity: vm_data['ext_id'],
87
+ name: vm_data['name'],
88
+ cpus: total_cpus,
89
+ memory: memory_gb,
90
+ power_state: vm_data['power_state'],
91
+ mac_address: vm_data['mac_address'],
92
+ ip_addresses: vm_data['ip_addresses'] || [],
93
+ create_time: vm_data['create_time'],
94
+ })
95
+ vm.instance_variable_set(:@persisted, true)
96
+ vm
97
+ end
98
+ rescue StandardError => e
99
+ Rails.logger.error "=== NUTANIX: Error fetching VMs: #{e.message} ==="
100
+ []
101
+ end
102
+ end
103
+ end
104
+ end
105
+
@@ -0,0 +1,23 @@
1
+ module NutanixCompute
2
+ class ComputeCollection
3
+ include Enumerable
4
+
5
+ def initialize(client, zone, attrs = {})
6
+ instances = client.instances(zone, **attrs)
7
+ @virtual_machines = instances.map do |vm|
8
+ ForemanNutanix::NutanixCompute.new client: client,
9
+ zone: zone,
10
+ identity: vm.id,
11
+ instance: vm
12
+ end
13
+ end
14
+
15
+ def each(&block)
16
+ @virtual_machines.each(&block)
17
+ end
18
+
19
+ def all(_opts = {})
20
+ @virtual_machines
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,22 @@
1
+ module NutanixExtensions
2
+ module AttachedDisk
3
+ def persisted?
4
+ end
5
+
6
+ def id
7
+ end
8
+
9
+ def _delete
10
+ end
11
+
12
+ def insert_attrs
13
+ attrs = { name: device_name, size_gb: disk_size_gb }
14
+ attrs[:source_image] = source if source.present?
15
+ attrs
16
+ end
17
+
18
+ def size_gb
19
+ disk_size_gb
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,38 @@
1
+ module ForemanNutanix
2
+ module HostManagedExtensions
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ end
7
+
8
+ def ip_addresses
9
+ Rails.logger.info "=== NUTANIX: HOST::ip_addresses called, vm: #{vm.inspect} ==="
10
+ vm&.ip_addresses || ['192.168.1.100', '10.0.0.100']
11
+ end
12
+
13
+ # TODO: Hard-coded should probably be removed
14
+ def vm_ip_address
15
+ Rails.logger.info "=== NUTANIX: HOST::vm_ip_address called, vm: #{vm.inspect} ==="
16
+ vm&.vm_ip_address || '192.168.1.100'
17
+ end
18
+
19
+ # Override provisioning methods to add debugging
20
+ def provision_method
21
+ Rails.logger.info '=== NUTANIX: HOST::provision_method called ==='
22
+ super
23
+ end
24
+
25
+ def build?
26
+ Rails.logger.info "=== NUTANIX: HOST::build? called, build: #{super} ==="
27
+ super
28
+ end
29
+
30
+ # Override vm method to add debugging
31
+ def vm
32
+ Rails.logger.info "=== NUTANIX: HOST::vm called, compute_resource: #{compute_resource.inspect} ==="
33
+ Rails.logger.info "=== NUTANIX: HOST::vm uuid: #{uuid} ==="
34
+ super
35
+ end
36
+ end
37
+ end
38
+