foreman_azure 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +619 -0
- data/README.md +52 -0
- data/Rakefile +47 -0
- data/app/assets/javascripts/foreman_azure/host_os_azure_selected.js +25 -0
- data/app/controllers/foreman_azure/concerns/hosts_controller_extensions.rb +10 -0
- data/app/helpers/azure_images_helper.rb +67 -0
- data/app/models/concerns/fog_extensions/azure/compute.rb +15 -0
- data/app/models/concerns/fog_extensions/azure/image.rb +11 -0
- data/app/models/concerns/fog_extensions/azure/server.rb +11 -0
- data/app/models/concerns/fog_extensions/azure/servers.rb +18 -0
- data/app/models/foreman_azure/azure.rb +69 -0
- data/app/overrides/add_image_location_js.rb +6 -0
- data/app/views/compute_resources/form/_azure.html.erb +7 -0
- data/app/views/compute_resources/show/_azure.html.erb +0 -0
- data/app/views/compute_resources_vms/form/azure/_base.html.erb +11 -0
- data/app/views/compute_resources_vms/index/_azure.html.erb +24 -0
- data/app/views/images/form/_azure.html.erb +6 -0
- data/config/routes.rb +5 -0
- data/lib/foreman_azure.rb +4 -0
- data/lib/foreman_azure/engine.rb +52 -0
- data/lib/foreman_azure/version.rb +3 -0
- data/lib/tasks/foreman_azure_tasks.rake +49 -0
- data/locale/Makefile +60 -0
- data/locale/en/foreman_azure.po +19 -0
- data/locale/foreman_azure.pot +19 -0
- data/locale/gemspec.rb +2 -0
- metadata +111 -0
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,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,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,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,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
|