foreman_azure 1.0.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.
data/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # Foreman Azure
2
+
3
+ Integration between [Microsoft Azure](http://azure.com/) and [Foreman](http://theforeman.org/)
4
+
5
+ ## Installation
6
+
7
+ See [How_to_Install_a_Plugin](http://projects.theforeman.org/projects/foreman/wiki/How_to_Install_a_Plugin)
8
+ for how to install Foreman plugins
9
+
10
+ ## Configuration
11
+
12
+ When you create the compute resource, you need to provide your subscription ID, a path to the .pem certificate, and a URL to your Azure API
13
+
14
+ ### Certificate creation
15
+
16
+ The certificate used must be generated by the user. OpenSSL can be used to create the management certificates. Two certificates are needed: a .cer file, which is uploaded to Azure, and a .pem file, which is stored locally.
17
+
18
+ To create the .pem file, execute the following command:
19
+
20
+ openssl req -x509 -nodes -days 365 -newkey rsa:1024 -keyout /etc/foreman/azure.pem -out /etc/foreman/azure.pem
21
+
22
+ To create the .cer file, execute the following command:
23
+
24
+ openssl x509 -inform pem -in /etc/foreman/azure.pem -outform der -out /etc/foreman/azure.cer
25
+
26
+ After creating these files, the .cer file will need to be uploaded to Azure via the "Upload a Management Certificate" action of the "Management Certificates" tab within the "Settings" section of the management portal.
27
+
28
+ ## Pending
29
+
30
+ * foreman-installer support
31
+ * listing in theforeman.org
32
+ * improve documentation
33
+ * packaging
34
+ * move to github.com/theforeman
35
+
36
+ ## Copyright
37
+
38
+ Copyright (c) 2016 Daniel Lobato Garcia
39
+
40
+ This program is free software: you can redistribute it and/or modify
41
+ it under the terms of the GNU General Public License as published by
42
+ the Free Software Foundation, either version 3 of the License, or
43
+ (at your option) any later version.
44
+
45
+ This program is distributed in the hope that it will be useful,
46
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
47
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
48
+ GNU General Public License for more details.
49
+
50
+ You should have received a copy of the GNU General Public License
51
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
52
+
data/Rakefile ADDED
@@ -0,0 +1,47 @@
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 = 'ForemanAzure'
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', __FILE__)
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 => _
42
+ puts 'Rubocop not loaded.'
43
+ end
44
+
45
+ task :default do
46
+ Rake::Task['rubocop'].execute
47
+ end
@@ -0,0 +1,25 @@
1
+ function azure_image_selected() {
2
+ var url = $('#host_compute_attributes_image').attr('data-url');
3
+ var imageId = $('#host_compute_attributes_image').val();
4
+ var azure_locations = $('#azure_locations');
5
+ var locations_spinner = $('#azure_locations_spinner');
6
+ foreman.tools.showSpinner();
7
+ locations_spinner.removeClass('hide');
8
+ $.ajax({
9
+ data: { "image_id": imageId },
10
+ type:'get',
11
+ url: url,
12
+ complete: function(){
13
+ reloadOnAjaxComplete('#host_compute_attributes_image');
14
+ locations_spinner.addClass('hide');
15
+ },
16
+ error: function(request, status, error) {
17
+ },
18
+ success: function(request_locations) {
19
+ azure_locations.empty();
20
+ $.each(request_locations, function() {
21
+ azure_locations.append($("<option />").val(this).text(this));
22
+ });
23
+ }
24
+ });
25
+ }
@@ -0,0 +1,10 @@
1
+ module ForemanAzure
2
+ module Concerns
3
+ module HostsControllerExtensions
4
+ def locations
5
+ azure_resource = Image.find_by_uuid(params[:image_id]).compute_resource
6
+ render :json => azure_resource.image_locations(params[:image_id])
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,67 @@
1
+ module AzureImagesHelper
2
+ def select_azure_image(f, images)
3
+ select_f(
4
+ f,
5
+ :image,
6
+ images,
7
+ :uuid,
8
+ :name,
9
+ { :include_blank => (images.empty? || images.size == 1) ? false : _('Please select an image') },
10
+ :label => ('Image'),
11
+ :disabled => images.empty?,
12
+ :class => 'without_select2',
13
+ :'data-url' => '/azure/locations',
14
+ :onclick => 'azure_image_selected();',
15
+ :onchange => 'azure_image_selected();')
16
+ end
17
+
18
+ def select_azure_region(f)
19
+ selectable_f(
20
+ f,
21
+ :location, [],
22
+ { :include_blank => _('Choose an operating system and image first') },
23
+ :label => _('Azure location'),
24
+ :class => 'without_select2',
25
+ :id => 'azure_locations',
26
+ :help_inline => refresh_button + spinner_indicator)
27
+ end
28
+
29
+ def select_storage_account(f, accounts)
30
+ selectable_f(
31
+ f,
32
+ :storage_account_name, accounts.map(&:name),
33
+ { :include_blank => _('None') },
34
+ :label => _('Storage account'))
35
+ end
36
+
37
+ def select_role_size(f, role_sizes)
38
+ selectable_f(
39
+ f,
40
+ :vm_size, role_sizes,
41
+ {},
42
+ :label => _('Role size'))
43
+ end
44
+
45
+ def select_cloud_service(f, cloud_services)
46
+ selectable_f(
47
+ f,
48
+ :cloud_service_name, cloud_services.map(&:name),
49
+ { :include_blank => _('Defaults to VM name') },
50
+ :label => _('Cloud service name'))
51
+ end
52
+
53
+ private
54
+
55
+ def spinner_indicator
56
+ content_tag(:span,
57
+ content_tag(:div, '',
58
+ :id => 'azure_locations_spinner',
59
+ :class => 'hide spinner spinner-xs'),
60
+ :class => 'help-block').html_safe
61
+ end
62
+
63
+ def refresh_button
64
+ link_to_function(icon_text('refresh', ''),
65
+ 'azure_image_selected();')
66
+ end
67
+ end
@@ -0,0 +1,15 @@
1
+ module FogExtensions
2
+ module Azure
3
+ module Compute
4
+ extend ActiveSupport::Concern
5
+
6
+ def role_sizes
7
+ @vm_svc.list_role_sizes
8
+ end
9
+
10
+ def cloud_services
11
+ ::Azure::CloudServiceManagementService.new.list_cloud_services
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ module FogExtensions
2
+ module Azure
3
+ module Image
4
+ extend ActiveSupport::Concern
5
+
6
+ def id
7
+ name
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module FogExtensions
2
+ module Azure
3
+ module Server
4
+ extend ActiveSupport::Concern
5
+
6
+ def to_s
7
+ "#{vm_name}@#{cloud_service_name}"
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,18 @@
1
+ module FogExtensions
2
+ module Azure
3
+ module Servers
4
+ extend ActiveSupport::Concern
5
+
6
+ # Azure servers.all doesn't take any argument, against the fog
7
+ # standard, so we override the method.
8
+ # https://github.com/fog/fog-azure/pull/25
9
+ included do
10
+ alias_method_chain :all, :patched_arguments
11
+ end
12
+
13
+ def all_with_patched_arguments(_options = {})
14
+ all_without_patched_arguments
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,69 @@
1
+ module ForemanAzure
2
+ class Azure < ComputeResource
3
+ alias_attribute :subscription_id, :user
4
+ alias_attribute :certificate_path, :url
5
+ attr_accessible :subscription_id, :certificate_path
6
+
7
+ before_create :test_connection
8
+
9
+ delegate :storage_accounts, :role_sizes, :cloud_services, :to => :client
10
+
11
+ def to_label
12
+ "#{name} (#{provider_friendly_name})"
13
+ end
14
+
15
+ def capabilities
16
+ [:image]
17
+ end
18
+
19
+ def self.model_name
20
+ ComputeResource.model_name
21
+ end
22
+
23
+ def provider_friendly_name
24
+ 'Azure'
25
+ end
26
+
27
+ def test_connection(options = {})
28
+ client.storage_accounts # Make a simple 'ping' request
29
+ super(options)
30
+ end
31
+
32
+ def available_images
33
+ client.images
34
+ end
35
+
36
+ def image_locations(image_id)
37
+ client.images.get(image_id).locations.split(';')
38
+ end
39
+
40
+ def create_vm(args = {})
41
+ args[:hostname] = args[:name]
42
+ args[:vm_name] = args[:name].split('.').first
43
+ args[:cloud_service_name] ||= args[:vm_name]
44
+ args[:vm_user] = Image.find_by_uuid(args[:image]).username
45
+ args[:private_key_file] = url
46
+ super(args)
47
+ end
48
+
49
+ # Azure does not have an UUID for each vm. It has a unique pair
50
+ # 'cloud_service_name' and 'vm_name'. We will need to override all
51
+ # destroy_vm, start_vm, stop_vm... to accept two parameters.
52
+ #
53
+ # fog-azure should probably build this UUID concept instead,
54
+ # but until then, we can just do our best to match it
55
+
56
+ def find_vm_by_uuid(uuid)
57
+ client.servers.find { |vm| vm.vm_name == uuid }
58
+ end
59
+
60
+ protected
61
+
62
+ def client
63
+ @client ||= Fog::Compute.new(
64
+ :provider => 'Azure',
65
+ :azure_sub_id => subscription_id,
66
+ :azure_pem => certificate_path)
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,6 @@
1
+ # Hack to add javascript via partials
2
+ Deface::Override.new(
3
+ :virtual_path => 'hosts/_form',
4
+ :name => 'add_image_location_js',
5
+ :insert_after => 'erb[loud]:contains("javascript")',
6
+ :text => "<% javascript 'foreman_azure/host_os_azure_selected' %>")
@@ -0,0 +1,7 @@
1
+ <%= text_f f, :subscription_id, :label => _('Subscription ID') %>
2
+ <%= text_f f, :certificate_path, :label => _('Certificate path (.pem)'),
3
+ :help_inline => link_to_function(
4
+ _("Test Connection"), "testConnection(this)",
5
+ :class => "btn btn-default",
6
+ :'data-url' => test_connection_compute_resources_path) +
7
+ hidden_spinner('', :id => 'test_connection_indicator').html_safe %>
File without changes
@@ -0,0 +1,11 @@
1
+ <%= select_azure_region(f) %>
2
+ <%= select_storage_account(f, compute_resource.storage_accounts) %>
3
+ <%= select_role_size(f, compute_resource.role_sizes) %>
4
+ <%= select_cloud_service(f, compute_resource.cloud_services) %>
5
+
6
+ <% arch ||= nil ; os ||= nil
7
+ images = possible_images(compute_resource, arch, os) %>
8
+
9
+ <div id='image_selection'>
10
+ <%= select_azure_image(f, images) %>
11
+ </div>
@@ -0,0 +1,24 @@
1
+ <table class="table table-bordered" data-table='inline'>
2
+ <thead>
3
+ <tr>
4
+ <th><%= _('Name') %></th>
5
+ <th><%= _('Image') %></th>
6
+ <th><%= _('Size') %></th>
7
+ <th><%= _('Location') %></th>
8
+ <th><%= _('State') %></th>
9
+ <th><%= _('Action') %></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 if vm.image.present? %></td>
16
+ <td><%= vm.vm_size %></td>
17
+ <td><%= vm.location %></td>
18
+ <td> <span <%= vm_power_class(vm.ready?) %>> <%= vm_state(vm) %></span> </td>
19
+ <td>
20
+ <%= vm_power_action(vm, authorizer) %>
21
+ </td>
22
+ </tr>
23
+ <% end %>
24
+ </table>
@@ -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
+ <%= image_field(f) %>
6
+ <%= checkbox_f f, :user_data, :help_inline => _("Does this image support user data input (e.g. via cloud-init)?") %>
data/config/routes.rb ADDED
@@ -0,0 +1,5 @@
1
+ Rails.application.routes.draw do
2
+ scope :azure, :path => '/azure' do
3
+ get :locations, :controller =>:hosts
4
+ end
5
+ end
@@ -0,0 +1,4 @@
1
+ require 'foreman_azure/engine'
2
+
3
+ module ForemanAzure
4
+ end
@@ -0,0 +1,52 @@
1
+ require 'deface'
2
+
3
+ module ForemanAzure
4
+ class Engine < ::Rails::Engine
5
+ engine_name 'foreman_azure'
6
+
7
+ initializer 'foreman_azure.register_plugin', :before => :finisher_hook do
8
+ Foreman::Plugin.register :foreman_azure do
9
+ requires_foreman '>= 1.8'
10
+ compute_resource(Azure)
11
+ end
12
+ end
13
+
14
+ initializer 'foreman_azure.register_gettext', after: :load_config_initializers do
15
+ locale_dir = File.join(File.expand_path('../../..', __FILE__), 'locale')
16
+ locale_domain = 'foreman_azure'
17
+ Foreman::Gettext::Support.add_text_domain locale_domain, locale_dir
18
+ end
19
+
20
+ initializer 'foreman_azure.assets.precompile' do |app|
21
+ app.config.assets.precompile += %w(foreman_azure/host_os_azure_selected.js)
22
+ end
23
+
24
+ initializer 'foreman_azure.configure_assets', :group => :assets do
25
+ SETTING[:foreman_azure] =
26
+ { :assets => { :precompile => ['foreman_azure/host_os_azure_selected.js'] } }
27
+ end
28
+
29
+ config.to_prepare do
30
+ require 'fog/azure'
31
+ require 'fog/azure/models/compute/servers'
32
+ require File.expand_path(
33
+ '../../../app/models/concerns/fog_extensions/azure/servers', __FILE__)
34
+ Fog::Compute::Azure::Servers.send(:include, FogExtensions::Azure::Servers)
35
+ require 'fog/azure/models/compute/image'
36
+ require File.expand_path(
37
+ '../../../app/models/concerns/fog_extensions/azure/image', __FILE__)
38
+ Fog::Compute::Azure::Image.send(:include, FogExtensions::Azure::Image)
39
+ require 'fog/azure/models/compute/server'
40
+ require File.expand_path(
41
+ '../../../app/models/concerns/fog_extensions/azure/server', __FILE__)
42
+ Fog::Compute::Azure::Server.send(:include, FogExtensions::Azure::Server)
43
+ require 'fog/azure/compute'
44
+ require File.expand_path(
45
+ '../../../app/models/concerns/fog_extensions/azure/compute', __FILE__)
46
+ Fog::Compute::Azure::Real.send(:include, FogExtensions::Azure::Compute)
47
+
48
+ ::HostsController.send :include,
49
+ ForemanAzure::Concerns::HostsControllerExtensions
50
+ end
51
+ end
52
+ end