foreman_ovm 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.
data/README.md ADDED
@@ -0,0 +1,91 @@
1
+ # Foreman Oracle VM Plugin
2
+
3
+ [![Code Climate](https://codeclimate.com/github/theforeman/foreman-digitalocean/badges/gpa.svg)](https://codeclimate.com/github/theforeman/foreman-digitalocean)
4
+ [![Gem Version](https://badge.fury.io/rb/foreman_digitalocean.svg)](http://badge.fury.io/rb/foreman_digitalocean)
5
+ [![Dependency Status](https://gemnasium.com/theforeman/foreman-digitalocean.svg)](https://gemnasium.com/theforeman/foreman-digitalocean)
6
+
7
+ ```foreman-ovm``` enables provisioning and managing of [Oracle VM](https://www.oracle.com/virtualization/vm-server-for-x86/index.html) instances in [Foreman](http://github.com/theforeman/foreman), all of that under the GPL v3+ license.
8
+
9
+ * Website: [TheForeman.org](http://theforeman.org)
10
+ * ServerFault tag: [Foreman](http://serverfault.com/questions/tagged/foreman)
11
+ * Issues: [foreman-ovm Github Issues](http://www.github.com/mattwilmott/foreman-ovm/issues)
12
+ * Wiki: [Foreman wiki](http://projects.theforeman.org/projects/foreman/wiki/About)
13
+ * Community and support: #theforeman for general support, #theforeman-dev for development chat in [Freenode](irc.freenode.net)
14
+ * Mailing lists:
15
+ * [foreman-users](https://groups.google.com/forum/?fromgroups#!forum/foreman-users)
16
+ * [foreman-dev](https://groups.google.com/forum/?fromgroups#!forum/foreman-dev)
17
+
18
+
19
+ ## WARNING
20
+
21
+ This gem is likely broken until version 0.1.x. It is currently undergoing heavy development
22
+
23
+ ## Installation
24
+
25
+ Please see the Foreman manual for appropriate instructions:
26
+
27
+ * [Foreman: How to Install a Plugin](http://theforeman.org/manuals/latest/index.html#6.1InstallaPlugin)
28
+
29
+ ### Red Hat, CentOS, Fedora, Scientific Linux (rpm)
30
+
31
+ Set up the repo as explained in the link above, then run
32
+
33
+ # yum install ruby193-rubygem-foreman_ovm
34
+
35
+ ### Debian, Ubuntu (deb)
36
+
37
+ Set up the repo as explained in the link above, then run
38
+
39
+ # apt-get install ruby-foreman-ovm
40
+
41
+ ### Bundle (gem)
42
+
43
+ Add the following to bundler.d/Gemfile.local.rb in your Foreman installation directory (/usr/share/foreman by default)
44
+
45
+ $ gem 'foreman_ovm'
46
+
47
+ Then run `bundle install` from the same directory
48
+
49
+ -------------------
50
+
51
+ To verify that the installation was successful, go to Foreman, top bar **Administer > About** and check 'foreman_ovm' shows up in the **System Status** menu under the Plugins tab.
52
+
53
+ ## Compatibility
54
+
55
+
56
+ | Foreman Version | Plugin Version |
57
+ | --------------- | --------------:|
58
+ | >= 1.13.0 | ~> 0.0.1 |
59
+
60
+ ## Configuration
61
+
62
+ Go to **Infrastructure > Compute Resources** and click on "New Compute Resource".
63
+
64
+ Choose the **OVM provider**, and fill in all the fields. You need a service account with read and write access. It will be encrypted in the database.
65
+
66
+ That's it. You're now ready to create and manage droplets in your new OVM compute resource.
67
+
68
+ You should see something like this in the Compute Resource page:
69
+
70
+ ![](http://i.imgur.com/cyFYOWg.png)
71
+ ![](http://i.imgur.com/CTedBU1.png)
72
+
73
+ ## How to contribute?
74
+
75
+ Generally, follow the [Foreman guidelines](http://theforeman.org/contribute.html). For code-related contributions, fork this project and send a pull request with all changes. Some things to keep in mind:
76
+ * [Follow the rules](http://theforeman.org/contribute.html#SubmitPatches) about commit message style and create a Redmine issue. Doing this right will help reviewers to get your contribution merged faster.
77
+ * [Rubocop](https://github.com/bbatsov/rubocop) will analyze your code, you can run it locally with `rake rubocop`.
78
+ * All of our pull requests run the full test suite in our [Jenkins CI system](http://ci.theforeman.org/). Please include tests in your pull requests for any additions or changes in functionality
79
+
80
+
81
+ ### Latest code
82
+
83
+ You can get the nightly branch of the plugin by specifying your Gemfile in this way:
84
+
85
+ gem 'foreman_ovm', :git => "https://github.com/mattwilmott/foreman-ovm.git"
86
+
87
+ # License
88
+
89
+ It is licensed as GPLv3 since it is a [Foreman](http://theforeman.org) plugin.
90
+
91
+ See LICENSE for more details.
@@ -0,0 +1,33 @@
1
+ module OvmImagesHelper
2
+ def ovm_image_field(f)
3
+ images = @compute_resource.available_images
4
+ images.each { |image| image.name = image.id if image.name.nil? }
5
+ select_f f, :uuid, images.to_a.sort_by(&:full_name),
6
+ :id, :full_name, {}, :label => _('Image')
7
+ end
8
+
9
+ def select_image(f, compute_resource)
10
+ images = possible_images(compute_resource, nil, nil)
11
+
12
+ select_f(f,
13
+ :image_id,
14
+ images,
15
+ :id,
16
+ :slug,
17
+ { :include_blank => (images.empty? || images.size == 1) ? false : _('Please select an image') },
18
+ { :label => ('Image'), :disabled => images.empty? } )
19
+ end
20
+
21
+ # def select_region(f, compute_resource)
22
+ # regions = compute_resource.regions
23
+ # f.object.region = compute_resource.region
24
+ # select_f(f,
25
+ # :region,
26
+ # regions,
27
+ # :slug,
28
+ # :slug,
29
+ # {},
30
+ # :label => ('Region'),
31
+ # :disabled => compute_resource.images.empty?)
32
+ # end
33
+ end
@@ -0,0 +1,34 @@
1
+ module FogExtensions
2
+ module Ovm
3
+ module Image
4
+ extend ActiveSupport::Concern
5
+
6
+ attr_accessor :os_version, :uuid
7
+
8
+ # Override attribute :name
9
+ included do
10
+ define_method :name, instance_method(:full_name)
11
+ define_method :name=, instance_method(:full_name=)
12
+ end
13
+
14
+ def full_name= value
15
+ self.os_version = value
16
+ end
17
+
18
+ def full_name
19
+ requires :distribution, :os_version
20
+ "#{distribution} #{os_version}"
21
+ end
22
+
23
+ # Attempt guessing arch based on the name from ovm
24
+ def arch
25
+ requires :os_version
26
+ if os_version.end_with?("x64")
27
+ "x86_64"
28
+ elsif os_version.end_with?("x32")
29
+ "i386"
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,55 @@
1
+ module FogExtensions
2
+ module Ovm
3
+ module Server
4
+ extend ActiveSupport::Concern
5
+
6
+ attr_accessor :image_id
7
+
8
+ def identity_to_s
9
+ identity.to_s
10
+ end
11
+
12
+ def vm_description
13
+ flavor.try(:name)
14
+ end
15
+
16
+ def flavor
17
+ requires :flavor_id
18
+ @flavor ||= service.flavors.get(flavor_id.to_i)
19
+ end
20
+
21
+ def flavor_name
22
+ requires :flavor
23
+ @flavor_name ||= @flavor.try(:name)
24
+ end
25
+
26
+ def image
27
+ requires :image_id
28
+ @image ||= service.images.get(image_id.to_i)
29
+ end
30
+
31
+ def image_name
32
+ @image_name ||= @image.try(:name)
33
+ end
34
+
35
+ # def region
36
+ # requires :region_id
37
+ # @region ||= service.regions.get(region_id.to_i)
38
+ # end
39
+
40
+ # def region_name
41
+ # requires :region
42
+ # @region_name ||= @region.try(:name)
43
+ # end
44
+
45
+ def ip_addresses
46
+ [public_ip_address, private_ip_address].flatten.select(&:present?)
47
+ end
48
+
49
+ def state
50
+ requires :status
51
+ @state ||= status
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,16 @@
1
+ module ForemanOvm
2
+ module Concerns
3
+ module HostManagedExtensions
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ # Rails 4 does not provide dynamic finders for delegated methods and
8
+ # the SSH orchestrate compute method uses them.
9
+ def self.find_by_ip(ip)
10
+ nic = Nic::Base.find_by_ip(ip)
11
+ nic.host if nic.present?
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,159 @@
1
+ module ForemanOvm
2
+ class Ovm < ComputeResource
3
+ #alias_attribute :api_key, :password
4
+ #alias_attribute :region, :url
5
+
6
+ has_one :key_pair, :foreign_key => :compute_resource_id, :dependent => :destroy
7
+ delegate :flavors, :to => :client
8
+
9
+ validates :username, :presence => true
10
+ validates :password, :presence => true
11
+ before_create :test_connection
12
+
13
+ #after_create :setup_key_pair
14
+ #after_destroy :destroy_key_pair
15
+
16
+ def to_label
17
+ "#{name} (#{provider_friendly_name})"
18
+ end
19
+
20
+ def provided_attributes
21
+ super.merge(:uuid => :identity_to_s, :ip => :public_ip_address)
22
+ end
23
+
24
+ def self.model_name
25
+ ComputeResource.model_name
26
+ end
27
+
28
+ def capabilities
29
+ [:image]
30
+ end
31
+
32
+ def find_vm_by_uuid(uuid)
33
+ client.servers.get(uuid)
34
+ rescue Fog::Compute::Ovm::Error
35
+ raise(ActiveRecord::RecordNotFound)
36
+ end
37
+
38
+ def create_vm(args = {})
39
+ args["ssh_keys"] = [ssh_key] if ssh_key
40
+ args['image'] = args['image_id']
41
+ super(args)
42
+ rescue Fog::Errors::Error => e
43
+ logger.error "Unhandled Ovm error: #{e.class}:#{e.message}\n " + e.backtrace.join("\n ")
44
+ raise e
45
+ end
46
+
47
+ def available_images
48
+ images = []
49
+ collection = client.images
50
+ begin
51
+ images += collection.to_a
52
+ end until !collection.next_page
53
+ images
54
+ end
55
+
56
+ def regions
57
+ return [] if api_key.blank?
58
+ client.regions
59
+ end
60
+
61
+ def test_connection(options = {})
62
+ super
63
+ errors[:password].empty? && regions.count
64
+ rescue Excon::Errors::Unauthorized => e
65
+ errors[:base] << e.response.body
66
+ rescue Fog::Errors::Error => e
67
+ errors[:base] << e.message
68
+ end
69
+
70
+ def destroy_vm(uuid)
71
+ vm = find_vm_by_uuid(uuid)
72
+ vm.delete if vm.present?
73
+ true
74
+ end
75
+
76
+ # not supporting update at the moment
77
+ def update_required?(*)
78
+ false
79
+ end
80
+
81
+ def self.provider_friendly_name
82
+ "OVM"
83
+ end
84
+
85
+ def associated_host(vm)
86
+ associate_by("ip", [vm.public_ip_address])
87
+ end
88
+
89
+ def user_data_supported?
90
+ true
91
+ end
92
+
93
+ def default_region_name
94
+ @default_region_name ||= client.regions[region.to_i].try(:name)
95
+ rescue Excon::Errors::Unauthorized => e
96
+ errors[:base] << e.response.body
97
+ end
98
+
99
+ private
100
+
101
+ def client
102
+ @client ||= Fog::Compute.new(
103
+ :provider => "Ovm",
104
+ :version => 'V1',
105
+ :username => username,
106
+ :password => password
107
+ )
108
+ end
109
+
110
+ def vm_instance_defaults
111
+ super.merge(
112
+ :size => client.flavors.first.slug
113
+ )
114
+ end
115
+
116
+ /*
117
+ # Creates a new key pair for each new Ovm compute resource
118
+ # After creating the key, it uploads it to Ovm
119
+ def setup_key_pair
120
+ public_key, private_key = generate_key
121
+ key_name = "foreman-#{id}#{Foreman.uuid}"
122
+ client.create_ssh_key key_name, public_key
123
+ KeyPair.create! :name => key_name, :compute_resource_id => id, :secret => private_key
124
+ rescue => e
125
+ logger.warn "failed to generate key pair"
126
+ logger.error e.message
127
+ logger.error e.backtrace.join("\n")
128
+ destroy_key_pair
129
+ raise
130
+ end
131
+
132
+ def destroy_key_pair
133
+ return unless key_pair
134
+ logger.info "removing DigitalOcean key #{key_pair.name}"
135
+ client.destroy_ssh_key(ssh_key.id) if ssh_key
136
+ key_pair.destroy
137
+ true
138
+ rescue => e
139
+ logger.warn "failed to delete key pair from DigitalOcean, you might need to cleanup manually : #{e}"
140
+ end
141
+
142
+ def ssh_key
143
+ @ssh_key ||= begin
144
+ key = client.list_ssh_keys.data[:body]["ssh_keys"].find { |i| i["name"] == key_pair.name }
145
+ key['id'] if key.present?
146
+ end
147
+ end
148
+
149
+ def generate_key
150
+ key = OpenSSL::PKey::RSA.new 2048
151
+ type = key.ssh_type
152
+ data = [key.to_blob].pack('m0')
153
+
154
+ openssh_format_public_key = "#{type} #{data}"
155
+ [openssh_format_public_key, key.to_pem]
156
+ end
157
+ */
158
+ end
159
+ end
@@ -0,0 +1 @@
1
+ attributes :user
@@ -0,0 +1 @@
1
+ attributes :user
@@ -0,0 +1,12 @@
1
+ <%= password_f f, :password, :label => _("Password"), :unset => unset_password? %>
2
+ <% regions = f.object.regions rescue [] %>
3
+
4
+ <div id='region_selection'>
5
+ <%= selectable_f(f, :region, regions.map(&:slug), {},
6
+ :label => _('Default Region'), :disabled => regions.empty?,
7
+ :help_inline => link_to_function(
8
+ regions.empty? ? _("Load Regions") : _("Test Connection"), "testConnection(this)",
9
+ :class => "btn + #{regions.empty? ? "btn-default" : "btn-success"}",
10
+ :'data-url' => test_connection_compute_resources_path) +
11
+ hidden_spinner('', :id => 'test_connection_indicator').html_safe) %>
12
+ </div>
@@ -0,0 +1,4 @@
1
+ <tr>
2
+ <td><%= _("Default Region") %></td>
3
+ <td><%= @compute_resource.default_region_name %></td>
4
+ </tr>
@@ -0,0 +1,7 @@
1
+ <%= select_f f, :size, compute_resource.flavors, :slug, :slug, {}, {:label => _('Flavor')} %>
2
+ <div id='image_selection'>
3
+ <%= select_image(f, compute_resource) %>
4
+ </div>
5
+ <div id='region_selection'>
6
+ <%= select_region(f, compute_resource) %>
7
+ </div>
@@ -0,0 +1,26 @@
1
+ <table class="table table-bordered" data-table='inline'>
2
+ <thead>
3
+ <tr>
4
+ <th><%= _('Name') %></th>
5
+ <th><%= _('Image') %></th>
6
+ <th><%= _('Type') %></th>
7
+ <th><%= _('Region') %></th>
8
+ <th><%= _('State') %></th>
9
+ <th></th>
10
+ </tr>
11
+ </thead>
12
+ <% @vms.each do |vm| %>
13
+ <tr>
14
+ <td><%= link_to_if_authorized vm.name, hash_for_compute_resource_vm_path(:compute_resource_id => @compute_resource, :id => vm.identity).merge(:auth_object => @compute_resource, :authorizer => authorizer) %></td>
15
+ <td><%= vm.image['slug'] if vm.image.present? %></td>
16
+ <td><%= vm.size['slug'] %></td>
17
+ <td><%= vm.region['slug'] %></td>
18
+ <td> <span <%= vm_power_class(vm.ready?) %>> <%= vm_state(vm) %></span> </td>
19
+ <td>
20
+ <%= action_buttons(
21
+ vm_power_action(vm, authorizer),
22
+ display_delete_if_authorized(hash_for_compute_resource_vm_path(:compute_resource_id => @compute_resource, :id => vm.id).merge(:auth_object => @compute_resource, :authorizer => authorizer))) %>
23
+ </td>
24
+ </tr>
25
+ <% end %>
26
+ </table>
@@ -0,0 +1,13 @@
1
+ <% title @vm.name %>
2
+ <div class='col-md-12'>
3
+ <table class="table table-bordered table-striped">
4
+ <tr><th colspan="2">Properties</th></tr>
5
+ <%= prop :public_ip_address %>
6
+ <%= prop :private_ip_address %>
7
+ <%= prop :state %>
8
+ <%= prop :created_at, 'Created' %>
9
+ <%= prop :image_name, 'Image' if @vm.image.present? %>
10
+ <%= prop :flavor_name, "Type (flavor)" %>
11
+ <%= prop :region_name, "Region" %>
12
+ </table>
13
+ </div>
@@ -0,0 +1,6 @@
1
+ <%= text_f f,
2
+ :username,
3
+ :value => @image.username || "root",
4
+ :help_inline => _("The user that is used to ssh into the instance, normally cloud-user, ec2-user, ubuntu, root etc") %>
5
+ <%= ovm_image_field(f) %>
6
+ <%= checkbox_f f, :user_data, :help_inline => _("Does this image support user data input (e.g. via cloud-init)?") %>
@@ -0,0 +1,49 @@
1
+ require 'fast_gettext'
2
+ require 'gettext_i18n_rails'
3
+
4
+ module ForemanOvm
5
+ class Engine < ::Rails::Engine
6
+ engine_name 'foreman_ovm'
7
+
8
+ config.autoload_paths += Dir["#{config.root}/app/models/concerns"]
9
+
10
+ initializer 'foreman_ovm.register_gettext', :after => :load_config_initializers do
11
+ locale_dir = File.join(File.expand_path('../../..', __FILE__), 'locale')
12
+ locale_domain = 'foreman_ovm'
13
+
14
+ Foreman::Gettext::Support.add_text_domain locale_domain, locale_dir
15
+ end
16
+
17
+ initializer 'foreman_ovm.register_plugin', :before => :finisher_hook do
18
+ Foreman::Plugin.register :foreman_ovm do
19
+ requires_foreman '>= 1.13'
20
+ compute_resource ForemanOvm::Ovm
21
+ parameter_filter ComputeResource, :username, :password, :uri
22
+ end
23
+ end
24
+
25
+ rake_tasks do
26
+ load "#{ForemanOvm::Engine.root}/lib/foreman_ovm/tasks/test.rake"
27
+ end
28
+
29
+ config.to_prepare do
30
+ require 'xlab-si/fog-oracle'
31
+ #require 'fog/digitalocean/compute_v2'
32
+ #require 'fog/digitalocean/models/compute_v2/image'
33
+ #require 'fog/digitalocean/models/compute_v2/server'
34
+ require File.expand_path(
35
+ '../../../app/models/concerns/fog_extensions/ovm/server',
36
+ __FILE__)
37
+ require File.expand_path(
38
+ '../../../app/models/concerns/fog_extensions/ovm/image',
39
+ __FILE__)
40
+
41
+ Fog::Compute::Ovm::Image.send :include,
42
+ FogExtensions::Ovm::Image
43
+ Fog::Compute::Ovm::Server.send :include,
44
+ FogExtensions::Ovm::Server
45
+ ::Host::Managed.send :include,
46
+ ForemanOvm::Concerns::HostManagedExtensions
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,45 @@
1
+ require File.expand_path("../engine", File.dirname(__FILE__))
2
+ namespace :test do
3
+ desc "Run the plugin unit test suite."
4
+ task :ovm => ['db:test:prepare'] do
5
+ test_task = Rake::TestTask.new('ovm_test_task') do |t|
6
+ t.libs << ["test", "#{ForemanOvm::Engine.root}/test"]
7
+ t.test_files = [
8
+ "#{ForemanOvm::Engine.root}/test/**/*_test.rb"
9
+ ]
10
+ t.verbose = true
11
+ t.warning = false
12
+ end
13
+
14
+ Rake::Task[test_task.name].invoke
15
+ end
16
+ end
17
+
18
+ namespace :ovm do
19
+ task :rubocop do
20
+ begin
21
+ require 'rubocop/rake_task'
22
+ RuboCop::RakeTask.new(:rubocop_ovm) do |task|
23
+ task.patterns = ["#{ForemanOvm::Engine.root}/app/**/*.rb",
24
+ "#{ForemanOvm::Engine.root}/lib/**/*.rb",
25
+ "#{ForemanOvm::Engine.root}/test/**/*.rb"]
26
+ end
27
+ rescue
28
+ puts "Rubocop not loaded."
29
+ end
30
+
31
+ Rake::Task['rubocop_ovm'].invoke
32
+ end
33
+ end
34
+
35
+ Rake::Task[:test].enhance do
36
+ Rake::Task['test:ovm'].invoke
37
+ end
38
+
39
+ load 'tasks/jenkins.rake'
40
+ if Rake::Task.task_defined?(:'jenkins:unit')
41
+ Rake::Task["jenkins:unit"].enhance do
42
+ Rake::Task['test:ovm'].invoke
43
+ Rake::Task['ovm:rubocop'].invoke
44
+ end
45
+ end
@@ -0,0 +1,3 @@
1
+ module ForemanOvm
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,3 @@
1
+ require 'foreman_ovm/engine'
2
+ module ForemanOvm
3
+ end
data/locale/Makefile ADDED
@@ -0,0 +1,6 @@
1
+ #
2
+ # Makefile for PO merging and MO generation. For more info see
3
+ # locale/README in the Foreman Core.
4
+ #
5
+ include ../.foreman_app/locale/Makefile
6
+ DOMAIN = ovm
@@ -0,0 +1,14 @@
1
+ FactoryGirl.define do
2
+ factory :container_resource, :class => ComputeResource do
3
+ sequence(:name) { |n| "compute_resource#{n}" }
4
+
5
+ trait :ovm do
6
+ provider 'Ovm'
7
+ username 'test'
8
+ password 'test'
9
+ region 'everywhere'
10
+ end
11
+
12
+ factory :ovm_cr, :class => ForemanOvm::Ovm, :traits => [:ovm]
13
+ end
14
+ end
@@ -0,0 +1,6 @@
1
+ # This calls the main test_helper in Foreman core
2
+ require 'test_helper'
3
+
4
+ # Add plugin to FactoryGirl's paths
5
+ FactoryGirl.definition_file_paths << File.join(File.dirname(__FILE__), 'factories')
6
+ FactoryGirl.reload
@@ -0,0 +1,18 @@
1
+ require 'test_plugin_helper'
2
+
3
+ module ForemanOvm
4
+ class OvmTest < ActiveSupport::TestCase
5
+ should validate_presence_of(:username)
6
+ should validate_presence_of(:password)
7
+ #should have_one(:key_pair)
8
+
9
+ setup { Fog.mock! }
10
+ teardown { Fog.unmock! }
11
+
12
+ test 'ssh key pair gets created after its saved' do
13
+ ovm = FactoryGirl.build(:ovm_cr)
14
+ #ovm.expects(:setup_key_pair)
15
+ ovm.save
16
+ end
17
+ end
18
+ end