foreman_azure_rm 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +619 -0
  3. data/README.md +23 -0
  4. data/Rakefile +47 -0
  5. data/app/assets/javascripts/foreman_azure_rm/azure_rm_location_callbacks.js +72 -0
  6. data/app/assets/javascripts/foreman_azure_rm/azure_rm_size_from_location.js +39 -0
  7. data/app/assets/javascripts/foreman_azure_rm/azure_rm_subnet_from_vnet.js +39 -0
  8. data/app/controllers/foreman_azure_rm/concerns/hosts_controller_extensions.rb +33 -0
  9. data/app/models/concerns/fog_extensions/azurerm/compute.rb +246 -0
  10. data/app/models/concerns/fog_extensions/azurerm/data_disk.rb +24 -0
  11. data/app/models/concerns/fog_extensions/azurerm/managed_disk.rb +10 -0
  12. data/app/models/concerns/fog_extensions/azurerm/managed_disks.rb +15 -0
  13. data/app/models/concerns/fog_extensions/azurerm/network_interface.rb +19 -0
  14. data/app/models/concerns/fog_extensions/azurerm/network_interfaces.rb +16 -0
  15. data/app/models/concerns/fog_extensions/azurerm/server.rb +123 -0
  16. data/app/models/concerns/fog_extensions/azurerm/servers.rb +23 -0
  17. data/app/models/foreman_azure_rm/azure_rm.rb +310 -0
  18. data/app/overrides/add_location_size_js.rb +20 -0
  19. data/app/views/compute_resources/form/_azurerm.html.erb +9 -0
  20. data/app/views/compute_resources/show/_azurerm.html.erb +0 -0
  21. data/app/views/compute_resources_vms/form/azurerm/_base.html.erb +207 -0
  22. data/app/views/compute_resources_vms/form/azurerm/_network.html.erb +23 -0
  23. data/app/views/compute_resources_vms/form/azurerm/_volume.html.erb +22 -0
  24. data/app/views/compute_resources_vms/index/_azurerm.html.erb +29 -0
  25. data/app/views/compute_resources_vms/show/_azurerm.html.erb +14 -0
  26. data/app/views/images/form/_azurerm.html.erb +6 -0
  27. data/config/routes.rb +8 -0
  28. data/lib/foreman_azure_rm.rb +5 -0
  29. data/lib/foreman_azure_rm/engine.rb +92 -0
  30. data/lib/foreman_azure_rm/version.rb +3 -0
  31. data/locale/gemspec.rb +2 -0
  32. metadata +103 -0
@@ -0,0 +1,23 @@
1
+ # Foreman Azure
2
+
3
+ ## Description
4
+ Plugin to add [Microsoft Azure Resource Manager](http://azure.com/) as a compute resource for [The Foreman](http://theforeman.org/)
5
+
6
+ ## Features
7
+ * Managed disks support
8
+ * Support for most typical IaaS operations
9
+ * VM creation
10
+ * Multiple NICs
11
+ * Multiple data disks, premium or not
12
+ * Static or dynamic addresses on a per NIC basis
13
+ * Limited extension support
14
+ * Microsoft's custom script extension
15
+ * Puppet Lab's Puppet agent extension for Windows
16
+
17
+ ## Planned Features
18
+ * Improved extension support
19
+
20
+ ## Known Limitations
21
+ * Most Azure marketplace images (likely all of them) disallow direct root login, which means SSH provisioning
22
+ with The Foreman has limited functionality. A workaround is to provide a dummy user data template and do all
23
+ post-provisioning with the custom script extension
@@ -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 = 'ForemanAzureRM'
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,72 @@
1
+ /**
2
+ * Created by tgregory on 3/13/17.
3
+ */
4
+ function azure_rm_storage_accts_from_location() {
5
+ var imageId = $('#host_compute_attributes_custom_data').val();
6
+ var storage_accts = $('#azure_rm_storage_acct');
7
+ var location = $('#azure_rm_location').val();
8
+ if (typeof tfm == 'undefined') { // earlier than 1.13
9
+ foreman.tools.showSpinner();
10
+ } else {
11
+ tfm.tools.showSpinner();
12
+ }
13
+ $.ajax({
14
+ data: { "image_id": imageId, "location": location },
15
+ type: "get",
16
+ url: "/azure_rm/storage_accts",
17
+ complete: function() {
18
+ reloadOnAjaxComplete('#azure_rm_storage_acct');
19
+ if (typeof tfm == 'undefined') { // earlier than 1.13
20
+ foreman.tools.hideSpinner();
21
+ } else {
22
+ tfm.tools.hideSpinner();
23
+ }
24
+ },
25
+ error: function(request, status, error) {
26
+ console.log(status);
27
+ console.log(request);
28
+ console.log(error);
29
+ },
30
+ success: function(request_accts) {
31
+ storage_accts.empty();
32
+ $.each(request_accts, function() {
33
+ storage_accts.append($("<option />").val(this).text(this));
34
+ });
35
+ }
36
+ });
37
+ }
38
+
39
+ function azure_rm_vnets_from_location() {
40
+ var imageId = $('#host_compute_attributes_custom_data').val();
41
+ var vnets = $('#azure_rm_vnet');
42
+ var location = $('#azure_rm_location').val();
43
+ if (typeof tfm == 'undefined') { // earlier than 1.13
44
+ foreman.tools.showSpinner();
45
+ } else {
46
+ tfm.tools.showSpinner();
47
+ }
48
+ $.ajax({
49
+ data: { "image_id": imageId, "location": location },
50
+ type: "get",
51
+ url: "/azure_rm/vnets",
52
+ complete: function() {
53
+ reloadOnAjaxComplete('#azure_rm_storage_acct');
54
+ if (typeof tfm == 'undefined') { // earlier than 1.13
55
+ foreman.tools.hideSpinner();
56
+ } else {
57
+ tfm.tools.hideSpinner();
58
+ }
59
+ },
60
+ error: function(request, status, error) {
61
+ console.log(status);
62
+ console.log(request);
63
+ console.log(error);
64
+ },
65
+ success: function(request_vnets) {
66
+ vnets.empty();
67
+ $.each(request_vnets, function() {
68
+ vnets.append($("<option />").val(this.id).text(this.name));
69
+ });
70
+ }
71
+ });
72
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Created by tgregory on 3/9/17.
3
+ */
4
+ function azure_rm_get_size_from_location() {
5
+ var location = $('#azure_rm_location').val();
6
+ var size_spinner = $('#azure_rm_size_spinner');
7
+ var sizes = $('#azure_rm_size');
8
+ var imageId = $('#host_compute_attributes_custom_data').val();
9
+ if (typeof tfm == 'undefined') { // earlier than 1.13
10
+ foreman.tools.showSpinner();
11
+ } else {
12
+ tfm.tools.showSpinner();
13
+ }
14
+ size_spinner.removeClass('hide');
15
+ $.ajax({
16
+ data: {"location_string": location, "image_id": imageId},
17
+ type: 'get',
18
+ url: '/azure_rm/sizes',
19
+ complete: function() {
20
+ reloadOnAjaxComplete('#azure_rm_size');
21
+ size_spinner.addClass('hide');
22
+ if (typeof tfm == 'undefined') { // earlier than 1.13
23
+ foreman.tools.hideSpinner();
24
+ } else {
25
+ tfm.tools.hideSpinner();
26
+ }
27
+ },
28
+ error: function(request, status, error) {
29
+ console.log(request);
30
+ console.log(error);
31
+ },
32
+ success: function(request_sizes) {
33
+ sizes.empty();
34
+ $.each(request_sizes, function() {
35
+ sizes.append($("<option />").val(this).text(this));
36
+ });
37
+ }
38
+ });
39
+ }
@@ -0,0 +1,39 @@
1
+ //TODO figure out how to refresh modal
2
+
3
+ /**
4
+ * Created by tgregory on 3/10/17.
5
+ */
6
+ function azure_rm_subnet_from_vnet() {
7
+ var vnet = $('#azure_rm_vnet').val();
8
+ var imageId = $('#host_compute_attributes_custom_data').val();
9
+ var subnets = $('#azure_rm_subnet');
10
+ if (typeof tfm == 'undefined') { // earlier than 1.13
11
+ foreman.tools.showSpinner();
12
+ } else {
13
+ tfm.tools.showSpinner();
14
+ }
15
+ $.ajax({
16
+ data: { "image_id": imageId, "vnet": vnet },
17
+ type: "get",
18
+ url: "/azure_rm/subnets",
19
+ complete: function() {
20
+ reloadOnAjaxComplete('#azure_rm_subnet');
21
+ if (typeof tfm == 'undefined') { // earlier than 1.13
22
+ foreman.tools.hideSpinner();
23
+ } else {
24
+ tfm.tools.hideSpinner();
25
+ }
26
+ },
27
+ error: function(request, status, error) {
28
+ console.log(status);
29
+ console.log(request);
30
+ console.log(error);
31
+ },
32
+ success: function(request_subnets) {
33
+ subnets.empty();
34
+ $.each(request_subnets, function() {
35
+ subnets.append($("<option />").val(this.id).text(this.name));
36
+ });
37
+ }
38
+ });
39
+ }
@@ -0,0 +1,33 @@
1
+ module ForemanAzureRM
2
+ module Concerns
3
+ module HostsControllerExtensions
4
+
5
+ def sizes
6
+ if (azure_rm_resource = Image.unscoped.find_by_uuid(params[:image_id])).present?
7
+ resource = azure_rm_resource.compute_resource
8
+ render :json => resource.vm_sizes(params[:location_string])
9
+ else
10
+ no_sizes = _('The location you selected has no sizes associated with it')
11
+ render :json => "[\"#{no_sizes}\"]"
12
+ end
13
+ end
14
+
15
+ def subnets
16
+ azure_rm_image = Image.unscoped.find_by_uuid(params[:image_id])
17
+ if azure_rm_image.present?
18
+ azure_rm_resource = azure_rm_image.compute_resource
19
+ subnets = azure_rm_resource.subnets(params[:location])
20
+ if subnets.present?
21
+ render :json => azure_rm_resource.subnets(params[:location])
22
+ else
23
+ no_subnets = _('The selected location has no subnets')
24
+ render :json => "[\"#{no_subnets}\"]"
25
+ end
26
+ else
27
+ no_compute = _('The selected image has no associated compute resource')
28
+ render :json => "[\"#{no_compute}\"]"
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,246 @@
1
+ module FogExtensions
2
+ module AzureRM
3
+ module Compute
4
+ extend ActiveSupport::Concern
5
+
6
+ def initialize(options)
7
+ begin
8
+ require 'azure_mgmt_compute'
9
+ require 'azure_mgmt_storage'
10
+ require 'azure_mgmt_network'
11
+ require 'azure/storage'
12
+ rescue LoadError => e
13
+ retry if require('rubygems')
14
+ raise e.message
15
+ end
16
+
17
+ options[:environment] = 'AzureCloud' if options[:environment].nil?
18
+
19
+ telemetry = "fog-azure-rm/#{Fog::AzureRM::VERSION}"
20
+ credentials = Fog::Credentials::AzureRM.get_credentials(options[:tenant_id], options[:client_id], options[:client_secret], options[:environment])
21
+ @compute_mgmt_client = ::Azure::ARM::Compute::ComputeManagementClient.new(credentials, resource_manager_endpoint_url(options[:environment]))
22
+ @compute_mgmt_client.subscription_id = options[:subscription_id]
23
+ @compute_mgmt_client.add_user_agent_information(telemetry)
24
+ @storage_mgmt_client = ::Azure::ARM::Storage::StorageManagementClient.new(credentials, resource_manager_endpoint_url(options[:environment]))
25
+ @storage_mgmt_client.subscription_id = options[:subscription_id]
26
+ @storage_mgmt_client.add_user_agent_information(telemetry)
27
+ # noinspection RubyArgCount
28
+ @storage_service = Fog::Storage::AzureRM.new(tenant_id: options[:tenant_id], client_id: options[:client_id], client_secret: options[:client_secret], subscription_id: options[:subscription_id], environment: options[:environment])
29
+ @network_client = ::Azure::ARM::Network::NetworkManagementClient.new(credentials, resource_manager_endpoint_url(options[:environment]))
30
+ @network_client.subscription_id = options[:subscription_id]
31
+ @network_client.add_user_agent_information(telemetry)
32
+ end
33
+
34
+ def list_available_sizes(location)
35
+ sizes = []
36
+ @compute_mgmt_client.virtual_machine_sizes.list(location).value().each do |vmsize|
37
+ sizes << vmsize.name
38
+ end
39
+ sizes
40
+ end
41
+
42
+ def list_all_vms
43
+ @compute_mgmt_client.virtual_machines.list_all
44
+ end
45
+
46
+ def get_vm_nic(nic_rg, nic_name)
47
+ @network_client.network_interfaces.get(nic_rg, nic_name)
48
+ end
49
+
50
+ def get_public_ip(ip_rg, ip_name)
51
+ @network_client.public_ipaddresses.get(ip_rg, ip_name)
52
+ end
53
+
54
+ def define_managed_storage_profile(vm_name, vhd_path, publisher, offer, sku, version,
55
+ os_disk_caching, platform, os_disk_size, premium_os_disk,
56
+ data_disks = nil)
57
+ storage_profile = Azure::ARM::Compute::Models::StorageProfile.new
58
+ os_disk = Azure::ARM::Compute::Models::OSDisk.new
59
+ managed_disk_params = Azure::ARM::Compute::Models::ManagedDiskParameters.new
60
+
61
+ # Create OS disk
62
+ os_disk.name = "#{vm_name}-osdisk"
63
+ os_disk.os_type = if platform == 'Windows'
64
+ Azure::ARM::Compute::Models::OperatingSystemTypes::Windows
65
+ else
66
+ Azure::ARM::Compute::Models::OperatingSystemTypes::Linux
67
+ end
68
+ os_disk.create_option = Azure::ARM::Compute::Models::DiskCreateOptionTypes::FromImage
69
+ os_disk.caching = if os_disk_caching.present?
70
+ case os_disk_caching
71
+ when 'None'
72
+ Azure::ARM::Compute::Models::CachingTypes::None
73
+ when 'ReadOnly'
74
+ Azure::ARM::Compute::Models::CachingTypes::ReadOnly
75
+ when 'ReadWrite'
76
+ Azure::ARM::Compute::Models::CachingTypes::ReadWrite
77
+ else
78
+ # ARM best practices stipulate RW caching on the OS disk
79
+ Azure::ARM::Compute::Models::CachingTypes::ReadWrite
80
+ end
81
+ end
82
+ os_disk.disk_size_gb = os_disk_size
83
+ managed_disk_params.storage_account_type = if premium_os_disk == 'true'
84
+ Azure::ARM::Compute::Models::StorageAccountTypes::PremiumLRS
85
+ else
86
+ Azure::ARM::Compute::Models::StorageAccountTypes::StandardLRS
87
+ end
88
+ os_disk.managed_disk = managed_disk_params
89
+ storage_profile.os_disk = os_disk
90
+
91
+ # Create data disks
92
+ unless data_disks.nil?
93
+ disks = []
94
+ disk_count = 0
95
+ data_disks.each do |_, attrs|
96
+ managed_data_disk = Azure::ARM::Compute::Models::ManagedDiskParameters.new
97
+ managed_data_disk.storage_account_type = if attrs[:account_type] == 'true'
98
+ Azure::ARM::Compute::Models::StorageAccountTypes::PremiumLRS
99
+ else
100
+ Azure::ARM::Compute::Models::StorageAccountTypes::StandardLRS
101
+ end
102
+ disk = Azure::ARM::Compute::Models::DataDisk.new
103
+ disk.name = "#{vm_name}-disk#{disk_count}"
104
+ disk.caching = if attrs[:data_disk_caching].present?
105
+ case attrs[:data_disk_caching]
106
+ when 'None'
107
+ Azure::ARM::Compute::Models::CachingTypes::None
108
+ when 'ReadOnly'
109
+ Azure::ARM::Compute::Models::CachingTypes::ReadOnly
110
+ when 'ReadWrite'
111
+ Azure::ARM::Compute::Models::CachingTypes::ReadWrite
112
+ else
113
+ # ARM best practices stipulate no caching on data disks by default
114
+ Azure::ARM::Compute::Models::CachingTypes::None
115
+ end
116
+ end
117
+ disk.disk_size_gb = attrs[:disk_size_gb]
118
+ disk.create_option = Azure::ARM::Compute::Models::DiskCreateOption::Empty
119
+ disk.lun = disk_count + 1
120
+ disk.managed_disk = managed_data_disk
121
+ disk_count += 1
122
+ disks << disk
123
+ end
124
+ storage_profile.data_disks = disks
125
+ end
126
+
127
+ if vhd_path.nil?
128
+ # We are using a marketplace image
129
+ storage_profile.image_reference = image_reference(publisher, offer,
130
+ sku, version)
131
+ else
132
+ # We are using a custom managed image
133
+ image_ref = Azure::ARM::Compute::Models::ImageReference.new
134
+ image_ref.id = vhd_path
135
+ storage_profile.image_reference = image_ref
136
+ end
137
+ storage_profile
138
+ end
139
+
140
+ def create_vm_extension(vm)
141
+ if vm[:script_command].present? && vm[:script_uris].present?
142
+ extension = Azure::ARM::Compute::Models::VirtualMachineExtension.new
143
+ extension.publisher = 'Microsoft.Azure.Extensions'
144
+ extension.virtual_machine_extension_type = 'CustomScript'
145
+ extension.type_handler_version = '2.0'
146
+ extension.auto_upgrade_minor_version = true
147
+ extension.location = vm['location'].gsub(/\s+/, '').downcase
148
+ extension.settings = {
149
+ 'commandToExecute' => vm[:script_command],
150
+ 'fileUris' => vm[:script_uris].split(',')
151
+ }
152
+ @compute_mgmt_client.virtual_machine_extensions.create_or_update(vm['resource_group'],
153
+ vm['name'],
154
+ 'ForemanCustomScript',
155
+ extension)
156
+ end
157
+ if vm[:platform] == 'Windows'
158
+ if vm[:puppet_master].present?
159
+ extension = Azure::ARM::Compute::Models::VirtualMachineExtension.new
160
+ extension.publisher = 'PuppetLabs'
161
+ extension.virtual_machine_extension_type = 'PuppetEnterpriseAgent'
162
+ extension.type_handler_version = '3.8'
163
+ extension.auto_upgrade_minor_version = true
164
+ extension.location = vm['location'].gsub(/\s+/, '').downcase
165
+ extension.settings = {
166
+ 'puppet_master_service' => vm[:puppet_master]
167
+ }
168
+ @compute_mgmt_client.virtual_machine_extensions.create_or_update(vm['resource_group'],
169
+ vm['name'],
170
+ 'InstallPuppet',
171
+ extension)
172
+ end
173
+ end
174
+ end
175
+
176
+ def create_managed_virtual_machine(vm_hash, async = false)
177
+ msg = "Creating Virtual Machine #{vm_hash[:name]} in Resource Group #{vm_hash[:resource_group]}."
178
+ Fog::Logger.debug msg
179
+ virtual_machine = Azure::ARM::Compute::Models::VirtualMachine.new
180
+
181
+ unless vm_hash[:availability_set_id].nil?
182
+ sub_resource = MsRestAzure::SubResource.new
183
+ sub_resource.id = vm_hash[:availability_set_id]
184
+ virtual_machine.availability_set = sub_resource
185
+ end
186
+
187
+ # If image UUID begins with / it is a custom managed image
188
+ # Otherwise it is a marketplace URN
189
+ unless vm_hash[:vhd_path].start_with?('/')
190
+ urn = vm_hash[:vhd_path].split(':')
191
+ vm_hash[:publisher] = urn[0]
192
+ vm_hash[:offer] = urn[1]
193
+ vm_hash[:sku] = urn[2]
194
+ vm_hash[:version] = urn[3]
195
+ vm_hash[:vhd_path] = nil
196
+ end
197
+
198
+ string_data = vm_hash[:custom_data]
199
+ string_data = WHITE_SPACE if string_data.nil?
200
+ encoded_data = Base64.strict_encode64(string_data)
201
+ virtual_machine.hardware_profile = define_hardware_profile(vm_hash[:vm_size])
202
+ virtual_machine.storage_profile = define_managed_storage_profile(vm_hash[:name],
203
+ vm_hash[:vhd_path],
204
+ vm_hash[:publisher],
205
+ vm_hash[:offer],
206
+ vm_hash[:sku],
207
+ vm_hash[:version],
208
+ vm_hash[:os_disk_caching],
209
+ vm_hash[:platform],
210
+ vm_hash[:os_disk_size],
211
+ vm_hash[:premium_os_disk],
212
+ vm_hash[:data_disks])
213
+ virtual_machine.os_profile = if vm_hash[:platform].casecmp(WINDOWS).zero?
214
+ define_windows_os_profile(vm_hash[:name],
215
+ vm_hash[:username],
216
+ vm_hash[:password],
217
+ vm_hash[:provision_vm_agent],
218
+ vm_hash[:enable_automatic_updates],
219
+ encoded_data)
220
+ else
221
+ define_linux_os_profile(vm_hash[:name],
222
+ vm_hash[:username],
223
+ vm_hash[:password],
224
+ vm_hash[:disable_password_authentication],
225
+ vm_hash[:ssh_key_path],
226
+ vm_hash[:ssh_key_data],
227
+ encoded_data)
228
+ end
229
+ virtual_machine.network_profile = define_network_profile(vm_hash[:network_interface_card_ids])
230
+ virtual_machine.location = vm_hash[:location]
231
+
232
+ begin
233
+ response = if async
234
+ @compute_mgmt_client.virtual_machines.create_or_update_async(vm_hash[:resource_group], vm_hash[:name], virtual_machine)
235
+ else
236
+ @compute_mgmt_client.virtual_machines.create_or_update(vm_hash[:resource_group], vm_hash[:name], virtual_machine)
237
+ end
238
+ rescue MsRestAzure::AzureOperationError => e
239
+ raise_azure_exception(e, msg)
240
+ end
241
+ Fog::Logger.debug "Virtual Machine #{vm_hash[:name]} Created Successfully." unless async
242
+ response
243
+ end
244
+ end
245
+ end
246
+ end