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,24 @@
1
+ module FogExtensions
2
+ module AzureRM
3
+ module DataDisk
4
+ extend ActiveSupport::Concern
5
+
6
+ def self.prepended base
7
+ base.instance_eval do
8
+ def parse(disk)
9
+ hash = {}
10
+ hash['name'] = disk.name
11
+ hash['disk_size_gb'] = disk.disk_size_gb
12
+ hash['lun'] = disk.lun
13
+ unless disk.vhd.nil?
14
+ hash['vhd_uri'] = disk.vhd.uri
15
+ end
16
+ hash['caching'] = disk.caching unless disk.caching.nil?
17
+ hash['create_option'] = disk.create_option
18
+ hash
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,10 @@
1
+ module FogExtensions
2
+ module AzureRM
3
+ module ManagedDisk
4
+ extend ActiveSupport::Concern
5
+
6
+ attr_accessor :data_disk_caching
7
+
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,15 @@
1
+ module FogExtensions
2
+ module AzureRM
3
+ module ManagedDisks
4
+ extend ActiveSupport::Concern
5
+
6
+ def new(attr = {})
7
+ if resource_group
8
+ super({:resource_group => resource_group}.merge(attr))
9
+ else
10
+ super
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,19 @@
1
+ module FogExtensions
2
+ module AzureRM
3
+ module NetworkInterface
4
+ extend ActiveSupport::Concern
5
+
6
+ attr_accessor :network
7
+ attr_accessor :bridge
8
+
9
+ def new(attr = {})
10
+ if resource_group
11
+ super({:resource_group => resource_group}.merge(attr))
12
+ else
13
+ super
14
+ end
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,16 @@
1
+ module FogExtensions
2
+ module AzureRM
3
+ module NetworkInterfaces
4
+ extend ActiveSupport::Concern
5
+
6
+ def new(attr = {})
7
+ if resource_group
8
+ super({:resource_group => resource_group}.merge(attr))
9
+ else
10
+ super
11
+ end
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,123 @@
1
+ module FogExtensions
2
+ module AzureRM
3
+ module Server
4
+ extend ActiveSupport::Concern
5
+
6
+ attr_accessor :os_disk_size
7
+ attr_accessor :premium_os_disk
8
+ attr_accessor :data_disk_caching
9
+ attr_accessor :os_disk_caching
10
+ attr_accessor :image_id
11
+ attr_accessor :puppet_master
12
+ attr_accessor :script_command
13
+ attr_accessor :script_uris
14
+
15
+ def ready?
16
+ vm_status == 'running'
17
+ end
18
+
19
+ def state
20
+ vm_status
21
+ end
22
+
23
+ def self.prepended base
24
+ base.instance_eval do
25
+ def parse(vm)
26
+ hash = {}
27
+ hash['id'] = vm.id
28
+ hash['name'] = vm.name
29
+ hash['location'] = vm.location
30
+ hash['resource_group'] = get_resource_group_from_id(vm.id)
31
+ hash['vm_size'] = vm.hardware_profile.vm_size unless vm.hardware_profile.vm_size.nil?
32
+ unless vm.storage_profile.nil?
33
+ hash['os_disk_name'] = vm.storage_profile.os_disk.name
34
+ hash['os_disk_caching'] = vm.storage_profile.os_disk.caching
35
+ unless vm.storage_profile.os_disk.vhd.nil?
36
+ hash['os_disk_vhd_uri'] = vm.storage_profile.os_disk.vhd.uri
37
+ hash['storage_account_name'] = hash['os_disk_vhd_uri'].split('/')[2].split('.')[0]
38
+ end
39
+ unless vm.storage_profile.image_reference.nil?
40
+ unless vm.storage_profile.image_reference.publisher.nil?
41
+ hash['publisher'] = vm.storage_profile.image_reference.publisher
42
+ hash['offer'] = vm.storage_profile.image_reference.offer
43
+ hash['sku'] = vm.storage_profile.image_reference.sku
44
+ hash['version'] = vm.storage_profile.image_reference.version
45
+ end
46
+ end
47
+ end
48
+ hash['username'] = vm.os_profile.admin_username
49
+ hash['custom_data'] = vm.os_profile.custom_data
50
+ hash['data_disks'] = []
51
+
52
+ unless vm.storage_profile.data_disks.nil?
53
+ vm.storage_profile.data_disks.each do |disk|
54
+ data_disk = Fog::Storage::AzureRM::DataDisk.new
55
+ hash['data_disks'] << data_disk.merge_attributes(Fog::Storage::AzureRM::DataDisk.parse(disk))
56
+ end
57
+ end
58
+
59
+ hash['disable_password_authentication'] = false
60
+ hash['disable_password_authentication'] = vm.os_profile.linux_configuration.disable_password_authentication unless vm.os_profile.linux_configuration.nil?
61
+ if vm.os_profile.windows_configuration
62
+ hash['provision_vm_agent'] = vm.os_profile.windows_configuration.provision_vmagent
63
+ hash['enable_automatic_updates'] = vm.os_profile.windows_configuration.enable_automatic_updates
64
+ end
65
+ hash['network_interface_card_ids'] = vm.network_profile.network_interfaces.map(&:id)
66
+ hash['availability_set_id'] = vm.availability_set.id unless vm.availability_set.nil?
67
+
68
+ hash
69
+ end
70
+ end
71
+ end
72
+
73
+ def interfaces_attributes=(attrs); end
74
+
75
+ def provisioning_ip_address
76
+ interfaces.each do |nic|
77
+ nic.ip_configurations.each do |configuration|
78
+ next unless configuration.primary
79
+ if configuration.public_ipaddress.present?
80
+ ip_id = configuration.public_ipaddress.id
81
+ ip_rg = ip_id.split('/')[4]
82
+ ip_name = ip_id.split('/')[-1]
83
+ public_ip = service.get_public_ip(ip_rg, ip_name)
84
+ return public_ip.ip_address
85
+ else
86
+ return configuration.private_ipaddress
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ def interfaces
93
+ interfaces = []
94
+ unless attributes[:network_interface_card_ids].nil?
95
+ attributes[:network_interface_card_ids].each do |nic_id|
96
+ nic_rg = nic_id.split('/')[4]
97
+ nic_name = nic_id.split('/')[-1]
98
+ interfaces << service.get_vm_nic(nic_rg, nic_name)
99
+ end
100
+ end
101
+ interfaces
102
+ end
103
+
104
+ def volumes_attributes=(attrs); end
105
+
106
+ def volumes
107
+ volumes = []
108
+ unless attributes[:data_disks].nil?
109
+ attributes[:data_disks].each do |disk|
110
+ volumes << disk
111
+ end
112
+ end
113
+ volumes
114
+ end
115
+
116
+ def stop
117
+ power_off
118
+ deallocate
119
+ end
120
+
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,23 @@
1
+ module FogExtensions
2
+ module AzureRM
3
+ module Servers
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ alias_method_chain :all, :patched_arguments
8
+ alias_method_chain :get, :patched_arguments
9
+ end
10
+
11
+ def all_with_patched_arguments(_options = {})
12
+ all_without_patched_arguments
13
+ end
14
+
15
+ def get_with_patched_arguments(uuid)
16
+ raw_vm = service.list_all_vms.find { |vm| vm.name == uuid }
17
+ virtual_machine_fog = Fog::Compute::AzureRM::Server.new(service: service)
18
+ vm_hash = Fog::Compute::AzureRM::Server.parse(raw_vm)
19
+ virtual_machine_fog.merge_attributes(vm_hash)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,310 @@
1
+ module ForemanAzureRM
2
+ class AzureRM < ComputeResource
3
+ alias_attribute :sub_id, :user
4
+ alias_attribute :secret_key, :password
5
+ alias_attribute :app_ident, :url
6
+ alias_attribute :tenant, :uuid
7
+
8
+ before_create :test_connection
9
+
10
+ class VMContainer
11
+ attr_accessor :virtualmachines
12
+
13
+ def initialize
14
+ @virtualmachines = []
15
+ end
16
+
17
+ def all(_options = {})
18
+ @virtualmachines
19
+ end
20
+ end
21
+
22
+ def to_label
23
+ "#{name} (#{provider_friendly_name})"
24
+ end
25
+
26
+ def self.model_name
27
+ ComputeResource.model_name
28
+ end
29
+
30
+ def provider_friendly_name
31
+ 'Azure Resource Manager'
32
+ end
33
+
34
+ def capabilities
35
+ [:image]
36
+ end
37
+
38
+ def locations
39
+ [
40
+ 'Central US',
41
+ 'South Central US',
42
+ 'North Central US',
43
+ 'West Central US',
44
+ 'East US',
45
+ 'East US 2',
46
+ 'West US',
47
+ 'West US 2'
48
+ ]
49
+ end
50
+
51
+ def resource_groups
52
+ rgs = rg_client.list_resource_groups
53
+ rg_names = []
54
+ rgs.each do |rg|
55
+ rg_names << rg.name
56
+ end
57
+ rg_names
58
+ end
59
+
60
+ def storage_accts(location = nil)
61
+ stripped_location = location.gsub(/\s+/, '').downcase
62
+ acct_names = []
63
+ if location.nil?
64
+ storage_client.list_storage_accounts.each do |acct|
65
+ acct_names << acct.name
66
+ end
67
+ else
68
+ (storage_client.list_storage_accounts.select { |acct| acct.location == stripped_location }).each do |acct|
69
+ acct_names << acct.name
70
+ end
71
+ end
72
+ acct_names
73
+ end
74
+
75
+ def provided_attributes
76
+ super.merge({ :ip => :provisioning_ip_address })
77
+ end
78
+
79
+ def host_interfaces_attrs(host)
80
+ host.interfaces.select(&:physical?).each.with_index.reduce({}) do |hash, (nic, index)|
81
+ hash.merge(index.to_s => nic.compute_attributes.merge(ip: nic.ip, ip6: nic.ip6))
82
+ end
83
+ end
84
+
85
+ def virtual_networks(location = nil)
86
+ if location.nil?
87
+ azure_network_service.virtual_networks
88
+ else
89
+ stripped_location = location.gsub(/\s+/, '').downcase
90
+ azure_network_service.virtual_networks.select { |vnet| vnet.location == stripped_location }
91
+ end
92
+ end
93
+
94
+ def subnets(location)
95
+ vnets = virtual_networks(location)
96
+ subnets = []
97
+ vnets.each do |vnet|
98
+ subnets.concat(azure_network_service.subnets(resource_group: vnet.resource_group,
99
+ virtual_network_name: vnet.name).all)
100
+ end
101
+ subnets
102
+ end
103
+
104
+ def test_connection(options = {})
105
+ rg_client.resource_groups.each do |rg|
106
+ puts "#{rg.name}"
107
+ end
108
+ super(options)
109
+ end
110
+
111
+ def networks
112
+ subnets = []
113
+ virtual_networks.each do |vnet|
114
+ subnets << subnets(vnet.id)
115
+ end
116
+ subnets
117
+ end
118
+
119
+ def new_interface(attr = {})
120
+ azure_network_service.network_interfaces.new(attr)
121
+ end
122
+
123
+ def new_volume(attr = {})
124
+ client.managed_disks.new(attr)
125
+ end
126
+
127
+ def vms
128
+ container = VMContainer.new
129
+ rg_client.resource_groups.each do |rg|
130
+ client.servers(resource_group: rg.name).each do |vm|
131
+ container.virtualmachines << vm
132
+ end
133
+ end
134
+ container
135
+ end
136
+
137
+ def vm_sizes(location)
138
+ client.list_available_sizes(location)
139
+ end
140
+
141
+ def find_vm_by_uuid(uuid)
142
+ # TODO: Find a better way to handle this than loading and sorting through
143
+ # all VMs, which also requires that names be globally unique, instead of
144
+ # unique within a resource group
145
+ vm = vms.all.find { |vm| vm.name == uuid }
146
+ raise ActiveRecord::RecordNotFound unless vm.present?
147
+ vm
148
+ end
149
+
150
+ def create_nics(args = {})
151
+ nics = []
152
+ formatted_location = args[:location].gsub(/\s+/, '').downcase
153
+ args[:interfaces_attributes].each do |nic, attrs|
154
+ attrs[:pubip_alloc] = attrs[:bridge]
155
+ attrs[:privip_alloc] = (attrs[:name] == 'false') ? false : true
156
+ pip_alloc = case attrs[:pubip_alloc]
157
+ when 'Static'
158
+ Fog::ARM::Network::Models::IPAllocationMethod::Static
159
+ when 'Dynamic'
160
+ Fog::ARM::Network::Models::IPAllocationMethod::Dynamic
161
+ when 'None'
162
+ nil
163
+ end
164
+ priv_ip_alloc = if attrs[:priv_ip_alloc]
165
+ Fog::ARM::Network::Models::IPAllocationMethod::Static
166
+ else
167
+ Fog::ARM::Network::Models::IPAllocationMethod::Dynamic
168
+ end
169
+ if pip_alloc.present?
170
+ pip = azure_network_service.public_ips.create(
171
+ name: "#{args[:vm_name]}-pip#{nic}",
172
+ resource_group: args[:resource_group],
173
+ location: formatted_location,
174
+ public_ip_allocation_method: pip_alloc
175
+ )
176
+ end
177
+ new_nic = azure_network_service.network_interfaces.create(
178
+ name: "#{args[:vm_name]}-nic#{nic}",
179
+ resource_group: args[:resource_group],
180
+ location: formatted_location,
181
+ subnet_id: attrs[:network],
182
+ public_ip_address_id: pip.present? ? pip.id : nil,
183
+ ip_configuration_name: 'ForemanIPConfiguration',
184
+ private_ip_allocation_method: priv_ip_alloc
185
+ )
186
+ nics << new_nic
187
+ end
188
+ nics
189
+ end
190
+
191
+ # Preferred behavior is to utilize Fog but Fog Azure RM
192
+ # does not currently support creating managed VMs
193
+ def create_vm(args = {})
194
+ args[:vm_name] = args[:name].split('.')[0]
195
+ nics = create_nics(args)
196
+ if args[:ssh_key_data].present?
197
+ disable_password_auth = true
198
+ ssh_key_path = "/home/#{args[:username]}/.ssh/authorized_keys"
199
+ else
200
+ disable_password_auth = false
201
+ ssh_key_path = nil
202
+ end
203
+ vm = client.create_managed_virtual_machine(
204
+ name: args[:vm_name],
205
+ location: args[:location],
206
+ resource_group: args[:resource_group],
207
+ vm_size: args[:vm_size],
208
+ username: args[:username],
209
+ password: args[:password],
210
+ ssh_key_data: args[:ssh_key_data],
211
+ ssh_key_path: ssh_key_path,
212
+ disable_password_authentication: disable_password_auth,
213
+ network_interface_card_ids: nics.map(&:id),
214
+ platform: args[:platform],
215
+ vhd_path: args[:image_id],
216
+ os_disk_caching: args[:os_disk_caching],
217
+ data_disks: args[:volumes_attributes],
218
+ os_disk_size: args[:os_disk_size],
219
+ premium_os_disk: args[:premium_os_disk],
220
+ )
221
+ vm_hash = Fog::Compute::AzureRM::Server.parse(vm)
222
+ vm_hash[:password] = args[:password]
223
+ vm_hash[:platform] = args[:platform]
224
+ vm_hash[:puppet_master] = args[:puppet_master]
225
+ vm_hash[:script_command] = args[:script_command]
226
+ vm_hash[:script_uris] = args[:script_uris]
227
+ client.create_vm_extension(vm_hash)
228
+ client.servers.new vm_hash
229
+ # fog-azure-rm raises all ARM errors as RuntimeError
230
+ rescue Fog::Errors::Error, RuntimeError => e
231
+ Foreman::Logging.exception('Unhandled Azure RM error', e)
232
+ destroy_vm vm.id if vm
233
+ raise e
234
+ end
235
+
236
+ def destroy_vm(uuid)
237
+ vm = find_vm_by_uuid(uuid)
238
+ raw_model = client.get_virtual_machine(vm.resource_group, vm.name)
239
+ os_disk_name = raw_model.storage_profile.os_disk.name
240
+ data_disks = raw_model.storage_profile.data_disks
241
+ nic_ids = vm.network_interface_card_ids
242
+ # In ARM things must be deleted in order
243
+ vm.destroy
244
+ nic_ids.each do |id|
245
+ nic = azure_network_service.network_interfaces.get(id.split('/')[4],
246
+ id.split('/')[-1])
247
+ ip_id = nic.public_ip_address_id
248
+ nic.destroy
249
+ if ip_id.present?
250
+ azure_network_service.public_ips.get(ip_id.split('/')[4],
251
+ ip_id.split('/')[-1]).destroy
252
+ end
253
+ end
254
+ client.managed_disks.get(vm.resource_group, os_disk_name).destroy
255
+ data_disks.each do |disk|
256
+ client.managed_disks.get(vm.resource_group, disk.name).destroy
257
+ end
258
+
259
+ rescue ActiveRecord::RecordNotFound
260
+ # If the VM does not exist, we don't really care.
261
+ true
262
+ end
263
+
264
+ protected
265
+
266
+ def client
267
+ @client ||= Fog::Compute.new(
268
+ :provider => 'AzureRM',
269
+ :tenant_id => tenant,
270
+ :client_id => app_ident,
271
+ :client_secret => secret_key,
272
+ :subscription_id => sub_id,
273
+ :environment => 'AzureCloud'
274
+ )
275
+ end
276
+
277
+ def rg_client
278
+ # noinspection RubyArgCount
279
+ @rg_client ||= Fog::Resources::AzureRM.new(
280
+ tenant_id: tenant,
281
+ client_id: app_ident,
282
+ client_secret: secret_key,
283
+ subscription_id: sub_id,
284
+ :environment => 'AzureCloud'
285
+ )
286
+ end
287
+
288
+ def storage_client
289
+ @storage_client ||= Fog::Storage.new(
290
+ :provider => 'AzureRM',
291
+ :tenant_id => tenant,
292
+ :client_id => app_ident,
293
+ :client_secret => secret_key,
294
+ :subscription_id => sub_id,
295
+ :environment => 'AzureCloud'
296
+ )
297
+ end
298
+
299
+ def azure_network_service
300
+ # noinspection RubyArgCount
301
+ @azure_network_service ||= Fog::Network::AzureRM.new(
302
+ :tenant_id => tenant,
303
+ :client_id => app_ident,
304
+ :client_secret => secret_key,
305
+ :subscription_id => sub_id,
306
+ :environment => 'AzureCloud'
307
+ )
308
+ end
309
+ end
310
+ end