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