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.
- 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
|