foreman_azure 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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