chef-provisioning-azure 0.2.1 → 0.3
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 +4 -4
- data/README.md +83 -14
- data/lib/chef/provisioning/azure_driver/driver.rb +93 -50
- data/lib/chef/provisioning/azure_driver/subscriptions.rb +222 -0
- data/lib/chef/provisioning/azure_driver/version.rb +1 -1
- metadata +29 -28
- data/lib/chef/provisioning/azure_driver/credentials.rb +0 -73
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ea6f8fc59f775e2ca0dbca8498954dfdad8dab5a
|
4
|
+
data.tar.gz: 8d3d5954e16f150c15396d05b20c04e82b541224
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6e4facf681246035a6aa86fcf36307601610435efe26d4fea7c3dfb2927832cef1b58952d548b14cff719b2d10873ab681522b295e64ad1c023b3ba75fd49a09
|
7
|
+
data.tar.gz: 02636ab1ff97415cade4db45f5dbc8700e137b25de192e97bd0f53c57b76f6e9535454a666052c88f9a2f8381617e782899772e61c55ae2bc7a6e125555b40e4
|
data/README.md
CHANGED
@@ -1,39 +1,108 @@
|
|
1
|
-
[](https://gitter.im/
|
1
|
+
[](https://gitter.im/chef/chef-provisioning?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
2
2
|
|
3
3
|
# chef-provisioning-azure
|
4
4
|
|
5
|
-
|
6
|
-
|
5
|
+
This is an implementation of an Microsoft Azure driver for [chef-provisioning](/chef/chef-provisioning) that relies on [azure-sdk-for-ruby](https://github.com/stuartpreston/stuartpreston-azure-sdk-for-ruby) and the Azure Service Management API.
|
7
6
|
|
8
7
|
## What does it do?
|
9
8
|
|
10
9
|
It can provision and converge a host on Azure with a recipe like the following:
|
11
10
|
|
11
|
+
### Linux
|
12
|
+
|
12
13
|
```ruby
|
13
14
|
require 'chef/provisioning/azure_driver'
|
14
15
|
with_driver 'azure'
|
15
16
|
|
16
17
|
machine_options = {
|
17
18
|
:bootstrap_options => {
|
18
|
-
:cloud_service_name => 'chefprovisioning',
|
19
|
-
:storage_account_name => 'chefprovisioning',
|
20
|
-
|
21
|
-
:location => 'West US'
|
19
|
+
:cloud_service_name => 'chefprovisioning', #required
|
20
|
+
:storage_account_name => 'chefprovisioning', #required
|
21
|
+
:vm_size => "Standard_D1", #required
|
22
|
+
:location => 'West US', #required
|
23
|
+
:tcp_endpoints => '80:80' #optional
|
22
24
|
},
|
23
|
-
|
25
|
+
:image_id => 'b39f27a8b8c64d52b05eac6a62ebad85__Ubuntu-14_04_1-LTS-amd64-server-20140927-en-us-30GB', #required
|
24
26
|
# Until SSH keys are supported (soon)
|
25
|
-
:password => "chefm3t4l\\m/"
|
27
|
+
:password => "chefm3t4l\\m/" #required
|
26
28
|
}
|
27
29
|
|
28
30
|
machine 'toad' do
|
29
31
|
machine_options machine_options
|
30
32
|
end
|
31
33
|
```
|
32
|
-
|
33
|
-
That's it. No images, nothing else. Do not expect much just yet. Currently you have to specify the
|
34
|
-
password you want the initial user to have in your recipe. No, this will not be for very long.
|
35
34
|
|
36
|
-
###
|
35
|
+
### Windows
|
36
|
+
|
37
|
+
The following example creates a Windows Server 2012 R2 VM from the public OS image gallery, then the uses the public WinRM/s port to bootstrap the server.
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
require 'chef/provisioning/azure_driver'
|
41
|
+
with_driver 'azure'
|
42
|
+
|
43
|
+
machine_options = {
|
44
|
+
:bootstrap_options => {
|
45
|
+
:vm_user => 'localadmin', #required if Windows
|
46
|
+
:cloud_service_name => 'chefprovisioning', #required
|
47
|
+
:storage_account_name => 'chefprovisioning', #required
|
48
|
+
:vm_size => 'Standard_D1', #optional
|
49
|
+
:location => 'West US', #optional
|
50
|
+
:tcp_endpoints => '3389:3389', #optional
|
51
|
+
:winrm_transport => { #optional
|
52
|
+
'https' => { #required (valid values: 'http', 'https')
|
53
|
+
:disable_sspi => false, #optional, (default: false)
|
54
|
+
:basic_auth_only => false, #optional, (default: false)
|
55
|
+
:no_ssl_peer_verification => true #optional, (default: false)
|
56
|
+
}
|
57
|
+
}
|
58
|
+
},
|
59
|
+
:password => 'P2ssw0rd', #required
|
60
|
+
:image_id => 'a699494373c04fc0bc8f2bb1389d6106__Windows-Server-2012-R2-201502.01-en.us-127GB.vhd' #required
|
61
|
+
}
|
62
|
+
|
63
|
+
machine 'toad' do
|
64
|
+
machine_options machine_options
|
65
|
+
end
|
66
|
+
```
|
67
|
+
|
68
|
+
## Supported Features
|
69
|
+
* Automatic creation and teardown of Cloud Services
|
70
|
+
* Public (OS) images and captured User (VM) images
|
71
|
+
* Up to date (March 2015) VM sizes including 'D', 'DS', 'G', A10/A11 sizes.
|
72
|
+
* Custom TCP/UDP endpoints per VM role
|
73
|
+
* Linux VMs, SSH external bootstrap via cloud service endpoint
|
74
|
+
* Windows VMs, WinRM bootstrap via cloud service endpoint
|
75
|
+
|
76
|
+
## Currently untested/Known issues
|
77
|
+
* Load-balanced sets
|
78
|
+
* Availability sets/Fault domains
|
79
|
+
* Cloud Service autoscaling
|
80
|
+
* Endpoint monitoring
|
81
|
+
* Additional disk volumes
|
82
|
+
* Affinity groups
|
83
|
+
* Direct server return IP addresses
|
84
|
+
* Reserved/Static IP addresses
|
85
|
+
* Virtual network allocation
|
86
|
+
* Bootstrap via internal (private) addresses
|
87
|
+
* Non-IaaS Azure services (e.g CDN/TrafficManager, Service Bus, Azure SQL Database, Media Services, Redis Cache)
|
88
|
+
|
89
|
+
Currently you have to specify the password you want the initial user to have in your recipe. No, this will not be for very long.
|
90
|
+
|
91
|
+
## Getting started
|
92
|
+
|
93
|
+
The gem is installed into Chef's default Ruby via RubyGems:
|
94
|
+
|
95
|
+
```
|
96
|
+
chef gem install chef-provisioning-azure
|
97
|
+
```
|
98
|
+
|
99
|
+
### Setting your credentials (v0.3 and above)
|
100
|
+
|
101
|
+
* If you have previously connected to your Azure subscription using the [azure-cli](http://azure.microsoft.com/en-us/documentation/articles/virtual-machines-command-line-tools/) tools and imported your publishsettings, **you do not need to do anything else** the driver will read your profile information and certificates from ~/.azure/azureProfile.json
|
102
|
+
* Alternatively, we support any of the methods listed in [configuration](docs/configuration.md) to set the driver up with access to your subscription
|
103
|
+
* Note that the use of ~/.azure/config to configure the driver is **no longer supported**.
|
104
|
+
|
105
|
+
### Setting your credentials (v0.2.1 and below)
|
37
106
|
|
38
107
|
Put the right values in ~/.azure/config so that it looks like the following:
|
39
108
|
|
@@ -45,7 +114,7 @@ subscription_id = "YOUR_SUBSCRIPTION_ID"
|
|
45
114
|
|
46
115
|
If you need to generate a certificate for Azure on OSX / Linux you can do it with the following:
|
47
116
|
|
48
|
-
```shell
|
117
|
+
```shell
|
49
118
|
openssl req \
|
50
119
|
-x509 -nodes -days 365 \
|
51
120
|
-newkey rsa:1024 -keyout azure.pem -out azure.pem
|
@@ -2,14 +2,16 @@ require 'chef/mixin/shell_out'
|
|
2
2
|
require 'chef/provisioning/driver'
|
3
3
|
require 'chef/provisioning/convergence_strategy/install_cached'
|
4
4
|
require 'chef/provisioning/convergence_strategy/install_sh'
|
5
|
+
require 'chef/provisioning/convergence_strategy/install_msi'
|
5
6
|
require 'chef/provisioning/convergence_strategy/no_converge'
|
6
7
|
require 'chef/provisioning/transport/ssh'
|
8
|
+
require 'chef/provisioning/transport/winrm'
|
7
9
|
require 'chef/provisioning/machine/windows_machine'
|
8
10
|
require 'chef/provisioning/machine/unix_machine'
|
9
11
|
require 'chef/provisioning/machine_spec'
|
10
12
|
|
11
13
|
require 'chef/provisioning/azure_driver/version'
|
12
|
-
require 'chef/provisioning/azure_driver/
|
14
|
+
require 'chef/provisioning/azure_driver/subscriptions'
|
13
15
|
|
14
16
|
require 'yaml'
|
15
17
|
require 'azure'
|
@@ -24,31 +26,46 @@ module AzureDriver
|
|
24
26
|
# Construct an AzureDriver object from a URL - used to parse existing URL
|
25
27
|
# data to hydrate a driver object.
|
26
28
|
# URL scheme:
|
27
|
-
# azure:
|
29
|
+
# azure:subscription_id
|
28
30
|
# @return [AzureDriver] A chef-provisioning Azure driver object for the given URL
|
29
31
|
def self.from_url(driver_url, config)
|
30
32
|
Driver.new(driver_url, config)
|
31
33
|
end
|
32
34
|
|
35
|
+
def self.canonicalize_url(driver_url, config)
|
36
|
+
scheme, account_id = driver_url.split(':', 2)
|
37
|
+
if account_id.nil? || account_id.empty?
|
38
|
+
subscription = Subscriptions.default_subscription(config)
|
39
|
+
if !subscription
|
40
|
+
raise "Driver #{driver_url} did not specify a subscription ID, and no default subscription was found. Have you downloaded the Azure CLI and used `azure account download` and `azure account import` to set up Azure? Alternately, you can set azure_subscriptions to [ { subscription_id: '...', management_credentials: ... }] in your Chef configuration."
|
41
|
+
end
|
42
|
+
config = Cheffish::MergedConfig.new({ azure_subscriptions: subscription }, config)
|
43
|
+
end
|
44
|
+
if subscription
|
45
|
+
[ "#{scheme}:#{subscription[:subscription_id]}", config ]
|
46
|
+
else
|
47
|
+
[ driver_url, config]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
33
51
|
def initialize(driver_url, config)
|
34
52
|
super
|
35
|
-
|
36
|
-
|
37
|
-
|
53
|
+
scheme, subscription_id = driver_url.split(':', 2)
|
54
|
+
@subscription = Subscriptions.get_subscription(config, subscription_id)
|
55
|
+
if !subscription
|
56
|
+
raise "Driver #{driver_url} has a subscription ID, but the system has no credentials configured for it! If you have access to this subscription, you can use `azure account download` and `azure account import` in the Azure CLI to get the credentials, or set azure_subscriptions to [ { subscription_id: '...', management_credentials: ... }] in your Chef configuration."
|
57
|
+
end
|
58
|
+
|
59
|
+
# TODO make this instantiable so we can have multiple drivers ......
|
38
60
|
Azure.configure do |azure|
|
39
61
|
# Configure these 3 properties to use Storage
|
40
|
-
azure.management_certificate =
|
41
|
-
azure.subscription_id =
|
42
|
-
azure.management_endpoint =
|
62
|
+
azure.management_certificate = subscription[:management_certificate]
|
63
|
+
azure.subscription_id = subscription[:subscription_id]
|
64
|
+
azure.management_endpoint = subscription[:management_endpoint]
|
43
65
|
end
|
44
66
|
end
|
45
67
|
|
46
|
-
|
47
|
-
# @return [String, Hash] A 2-element array of [URL, config]
|
48
|
-
def self.canonicalize_url(driver_url, config)
|
49
|
-
url = driver_url.split(':')[0]
|
50
|
-
["azure:#{url}", config]
|
51
|
-
end
|
68
|
+
attr_reader :subscription
|
52
69
|
|
53
70
|
# -- Machine methods --
|
54
71
|
|
@@ -83,39 +100,33 @@ module AzureDriver
|
|
83
100
|
image_id = machine_options[:image_id] || default_image_for_location(location)
|
84
101
|
|
85
102
|
Chef::Log.debug "Azure bootstrap options: #{bootstrap_options.inspect}"
|
86
|
-
|
87
|
-
# If the cloud service exists already, need to add a role to it - otherwise create virtual machine (including cloud service)
|
88
|
-
cloud_service = azure_cloud_service_service.get_cloud_service(bootstrap_options[:cloud_service_name])
|
89
103
|
|
90
|
-
|
91
|
-
action_handler.report_progress "Cloud Service #{bootstrap_options[:cloud_service_name]} already exists, adding role."
|
92
|
-
params = {
|
104
|
+
params = {
|
93
105
|
vm_name: machine_spec.name,
|
94
|
-
vm_user: default_ssh_username,
|
106
|
+
vm_user: bootstrap_options[:vm_user] || default_ssh_username,
|
95
107
|
image: image_id,
|
96
108
|
# This is only until SSH keys are added
|
97
109
|
password: machine_options[:password],
|
110
|
+
location: location,
|
98
111
|
cloud_service_name: bootstrap_options[:cloud_service_name]
|
99
|
-
|
112
|
+
}
|
100
113
|
|
114
|
+
# If the cloud service exists already, need to add a role to it - otherwise create virtual machine (including cloud service)
|
115
|
+
cloud_service = azure_cloud_service_service.get_cloud_service(bootstrap_options[:cloud_service_name])
|
116
|
+
existing_deployment = azure_vm_service.list_virtual_machines(bootstrap_options[:cloud_service_name]).any?
|
117
|
+
|
118
|
+
if cloud_service and existing_deployment
|
119
|
+
action_handler.report_progress "Cloud Service #{bootstrap_options[:cloud_service_name]} already exists, adding role."
|
101
120
|
action_handler.report_progress "Creating #{machine_spec.name} with image #{image_id} in #{bootstrap_options[:cloud_service_name]}..."
|
102
121
|
vm = azure_vm_service.add_role(params, bootstrap_options)
|
103
122
|
else
|
104
|
-
params = {
|
105
|
-
vm_name: machine_spec.name,
|
106
|
-
vm_user: default_ssh_username,
|
107
|
-
image: image_id,
|
108
|
-
# This is only until SSH keys are added
|
109
|
-
password: machine_options[:password],
|
110
|
-
location: location
|
111
|
-
}
|
112
|
-
|
113
123
|
action_handler.report_progress "Creating #{machine_spec.name} with image #{image_id} in #{location}..."
|
114
124
|
vm = azure_vm_service.create_virtual_machine(params, bootstrap_options)
|
115
125
|
end
|
116
126
|
|
117
127
|
machine_spec.location['vm_name'] = vm.vm_name
|
118
|
-
|
128
|
+
machine_spec.location['is_windows'] = (true if vm.os_type == 'Windows') || false
|
129
|
+
action_handler.report_progress "Created #{vm.vm_name} in #{location}..."
|
119
130
|
end
|
120
131
|
|
121
132
|
# (see Chef::Provisioning::Driver#ready_machine)
|
@@ -196,23 +207,11 @@ module AzureDriver
|
|
196
207
|
end
|
197
208
|
|
198
209
|
def transport_for(machine_spec, machine_options, vm)
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
# Grab the list of possible credentials
|
205
|
-
@azure_credentials ||= if driver_options[:azure_credentials]
|
206
|
-
driver_options[:azure_credentials]
|
207
|
-
else
|
208
|
-
credentials = Credentials.new
|
209
|
-
if driver_options[:azure_config_file]
|
210
|
-
credentials.load_ini(driver_options.delete(:azure_config_file))
|
211
|
-
else
|
212
|
-
credentials.load_default
|
213
|
-
end
|
214
|
-
credentials
|
215
|
-
end
|
210
|
+
if machine_spec.location['is_windows']
|
211
|
+
create_winrm_transport(machine_spec, machine_options, vm)
|
212
|
+
else
|
213
|
+
create_ssh_transport(machine_spec, machine_options, vm)
|
214
|
+
end
|
216
215
|
end
|
217
216
|
|
218
217
|
def default_image_for_location(location)
|
@@ -234,7 +233,7 @@ module AzureDriver
|
|
234
233
|
remote_host = tcp_endpoint[:vip]
|
235
234
|
|
236
235
|
# TODO: not this... replace with SSH key ASAP, only for getting this thing going...
|
237
|
-
ssh_options = {
|
236
|
+
ssh_options = {
|
238
237
|
password: machine_options[:password],
|
239
238
|
port: tcp_endpoint[:public_port] # use public port from Cloud Service endpoint
|
240
239
|
}
|
@@ -250,6 +249,50 @@ module AzureDriver
|
|
250
249
|
Chef::Provisioning::Transport::SSH.new(remote_host, username, ssh_options, options, config)
|
251
250
|
end
|
252
251
|
|
252
|
+
def create_winrm_transport(machine_spec, machine_options, instance)
|
253
|
+
winrm_transport_options = machine_options[:bootstrap_options][:winrm_transport]
|
254
|
+
shared_winrm_options = {
|
255
|
+
:user => machine_options[:vm_user] || 'localadmin',
|
256
|
+
:pass => machine_options[:password] # TODO: Replace with encryption
|
257
|
+
}
|
258
|
+
|
259
|
+
if(winrm_transport_options['https'])
|
260
|
+
tcp_endpoint = instance.tcp_endpoints.select { |tcp| tcp[:name] == 'PowerShell' }.first
|
261
|
+
remote_host = tcp_endpoint[:vip]
|
262
|
+
port = tcp_endpoint[:public_port] || default_winrm_https_port
|
263
|
+
endpoint = "https://#{remote_host}:#{port}/wsman"
|
264
|
+
type = :ssl
|
265
|
+
winrm_options = {
|
266
|
+
:disable_sspi => winrm_transport_options['https'][:disable_sspi] || false, # default to Negotiate
|
267
|
+
:basic_auth_only => winrm_transport_options['https'][:basic_auth_only] || false, # disallow Basic auth by default
|
268
|
+
:no_ssl_peer_verification => winrm_transport_options['https'][:no_ssl_peer_verification] || false #disallow MITM potential by default
|
269
|
+
}
|
270
|
+
end
|
271
|
+
|
272
|
+
if(winrm_transport_options['http'])
|
273
|
+
tcp_endpoint = instance.tcp_endpoints.select { |tcp| tcp[:name] == 'WinRm-Http' }.first
|
274
|
+
remote_host = tcp_endpoint[:vip]
|
275
|
+
port = tcp_endpoint[:public_port] || default_winrm_http_port
|
276
|
+
endpoint = "http://#{remote_host}:#{port}/wsman"
|
277
|
+
type = :plaintext
|
278
|
+
winrm_options = {
|
279
|
+
:disable_sspi => winrm_transport_options['http']['disable_sspi'] || false, # default to Negotiate
|
280
|
+
:basic_auth_only => winrm_transport_options['http']['basic_auth_only'] || false # disallow Basic auth by default
|
281
|
+
}
|
282
|
+
end
|
283
|
+
|
284
|
+
merged_winrm_options = winrm_options.merge(shared_winrm_options)
|
285
|
+
Chef::Provisioning::Transport::WinRM.new("#{endpoint}", type, merged_winrm_options, {})
|
286
|
+
end
|
287
|
+
|
288
|
+
def default_winrm_http_port
|
289
|
+
5985
|
290
|
+
end
|
291
|
+
|
292
|
+
def default_winrm_https_port
|
293
|
+
5986
|
294
|
+
end
|
295
|
+
|
253
296
|
def convergence_strategy_for(machine_spec, machine_options)
|
254
297
|
convergence_options = machine_options[:convergence_options]
|
255
298
|
# Defaults
|
@@ -0,0 +1,222 @@
|
|
1
|
+
require 'inifile'
|
2
|
+
require 'json'
|
3
|
+
require 'nokogiri'
|
4
|
+
|
5
|
+
class Chef
|
6
|
+
module Provisioning
|
7
|
+
module AzureDriver
|
8
|
+
module Subscriptions
|
9
|
+
|
10
|
+
#
|
11
|
+
# Get the subscription with the given subscription_id or subscription_name
|
12
|
+
#
|
13
|
+
# Returns `nil` if nothing found.
|
14
|
+
#
|
15
|
+
def self.get_subscription(config, subscription_id_or_name)
|
16
|
+
subscription_count = 0
|
17
|
+
subscription = all_subscriptions(config).select do |s|
|
18
|
+
subscription_count += 1
|
19
|
+
s[:subscription_id] == subscription_id_or_name ||
|
20
|
+
s[:subscription_name] == subscription_id_or_name
|
21
|
+
end.first
|
22
|
+
if !subscription
|
23
|
+
Chef::Log.info("Subscription #{subscription_id_or_name} not found out of #{subscription_count} subscriptions read.")
|
24
|
+
end
|
25
|
+
subscription
|
26
|
+
end
|
27
|
+
|
28
|
+
#
|
29
|
+
# Get the default subscription for the given config.
|
30
|
+
#
|
31
|
+
# The default subscription is either the first .azureProfile subscription with isDefault: true
|
32
|
+
#
|
33
|
+
def self.default_subscription(config)
|
34
|
+
first_subscription = nil
|
35
|
+
all_subscriptions(config).each do |subscription|
|
36
|
+
if subscription[:is_default]
|
37
|
+
Chef::Log.info("Picked default subscription: #{subscription[:subscription_name]} (#{subscription[:subscription_id]}) from #{subscription[:source]}")
|
38
|
+
return subscription
|
39
|
+
end
|
40
|
+
|
41
|
+
first_subscription ||= subscription;
|
42
|
+
end
|
43
|
+
if first_subscription
|
44
|
+
Chef::Log.info("Picked first subscription found as default: #{first_subscription[:subscription_name]} (#{first_subscription[:subscription_id]}) from #{first_subscription[:source]}")
|
45
|
+
else
|
46
|
+
Chef::Log.info("No subscriptions found.")
|
47
|
+
end
|
48
|
+
first_subscription
|
49
|
+
end
|
50
|
+
|
51
|
+
#
|
52
|
+
# We search for subscription in the order specified by Chef::Config.azure_subscriptions,
|
53
|
+
# which includes:
|
54
|
+
#
|
55
|
+
# Key | Description
|
56
|
+
# ------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------
|
57
|
+
# `subscription_id` | The GUID of the subscription.
|
58
|
+
# `subscription_name` | The name of the subscription.
|
59
|
+
# `management_certificate` | The path to the actual credentials, a proc, or an IO object (optional; if this is not set, the keychain will be searched).
|
60
|
+
# `management_endpoint` | The management endpoint URL (optional; if not set, the default Azure endpoint will be used).
|
61
|
+
# `is_default` | If `true`, this should be considered the default subscription.
|
62
|
+
# `publishsettings` | The path/glob to one or more `.publishsettings` formatted files, an IO object, or a hash with one or more { type: <path|io> } keys, where type=:pem, :pdx or :cert.
|
63
|
+
# `azure_profile` | The path/glob to one or more `azureProfile.json` formatted files, an IO object, or a Hash representing the parsed data.
|
64
|
+
# `allow_missing_file` | If `true`, provisioning will skip the publishsettings or azure_profile if it does not exist; otherwise it will raise an error on missing file. Defaults to `false`.
|
65
|
+
#
|
66
|
+
def self.all_subscriptions(config)
|
67
|
+
Enumerator.new do |y|
|
68
|
+
subscriptions = config[:azure_subscriptions]
|
69
|
+
subscriptions ||= default_subscriptions(config)
|
70
|
+
subscriptions = [ subscriptions ].flatten
|
71
|
+
|
72
|
+
subscriptions.each do |subscription_spec|
|
73
|
+
allow_missing_file = subscription_spec[:allow_missing_file]
|
74
|
+
if subscription_spec[:subscription_id]
|
75
|
+
subscription = {
|
76
|
+
management_endpoint: default_management_endpoint(config),
|
77
|
+
source: "Chef configuration"
|
78
|
+
}
|
79
|
+
y.yield subscription.merge(subscription_spec)
|
80
|
+
end
|
81
|
+
if subscription_spec[:publishsettings]
|
82
|
+
load_publishsettings_subscriptions(config, subscription_spec[:publishsettings], allow_missing_file) do |subscription|
|
83
|
+
y.yield subscription.merge(subscription_spec)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
if subscription_spec[:azure_profile]
|
87
|
+
load_azure_profile_subscriptions(config, subscription_spec[:azure_profile], allow_missing_file) do |subscription|
|
88
|
+
y.yield subscription.merge(subscription_spec)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.load_publishsettings_subscriptions(config, data, allow_missing_file, filename=nil, &block)
|
96
|
+
case data
|
97
|
+
when String
|
98
|
+
found = false
|
99
|
+
Dir.glob(data) do |filename|
|
100
|
+
found = true
|
101
|
+
Chef::Log.info("Reading publishsettings file #{filename}")
|
102
|
+
File.open(filename) do |f|
|
103
|
+
load_publishsettings_subscriptions(config, f, allow_missing_file, filename, &block)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
if !found
|
107
|
+
if allow_missing_file
|
108
|
+
Chef::Log.info("Skipping missing publishsettings file #{data}.")
|
109
|
+
else
|
110
|
+
raise "Missing publishsettings file #{data}!"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
when IO
|
115
|
+
xml = Nokogiri::XML(data)
|
116
|
+
|
117
|
+
# Parse publishsettings content
|
118
|
+
xml.xpath("//PublishData/PublishProfile/Subscription").each do |subscription|
|
119
|
+
Chef::Log.debug("- Read subscription #{subscription['Name']} (#{subscription['Id']})")
|
120
|
+
result = {
|
121
|
+
subscription_id: subscription['Id'],
|
122
|
+
subscription_name: subscription['Name'],
|
123
|
+
management_endpoint: subscription['ServiceManagementUrl'] || default_management_endpoint(config),
|
124
|
+
source: "publishsettings #{filename ? "file #{filename}" : " IO object"}"
|
125
|
+
}
|
126
|
+
result[:publishsettings] = filename if filename
|
127
|
+
if subscription['ManagementCertificate']
|
128
|
+
result[:management_certificate] = {
|
129
|
+
type: :pdx,
|
130
|
+
data: subscription['ManagementCertificate']
|
131
|
+
}
|
132
|
+
end
|
133
|
+
yield result
|
134
|
+
end
|
135
|
+
else
|
136
|
+
raise "Unexpected value #{data.inspect} for publishsettings!"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def self.load_azure_profile_subscriptions(config, data, allow_missing_file, filename=nil, &block)
|
141
|
+
case data
|
142
|
+
when String
|
143
|
+
found = false
|
144
|
+
Dir.glob(data) do |filename|
|
145
|
+
found = true
|
146
|
+
Chef::Log.info("Reading azure profile file #{filename}")
|
147
|
+
File.open(filename) do |f|
|
148
|
+
load_azure_profile_subscriptions(config, f, allow_missing_file, filename, &block)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
if !found
|
152
|
+
if allow_missing_file
|
153
|
+
Chef::Log.info("Skipping missing azure profile file #{data}.")
|
154
|
+
else
|
155
|
+
raise "Missing azure profile file #{data}!"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
when IO
|
160
|
+
profile = JSON.parse(data.read, create_additions: false)
|
161
|
+
if profile["subscriptions"]
|
162
|
+
profile["subscriptions"].each do |subscription|
|
163
|
+
Chef::Log.debug("- Read#{subscription['isDefault'] ? " default" : ""} subscription #{subscription['name']} (#{subscription['id']})")
|
164
|
+
|
165
|
+
result = {
|
166
|
+
subscription_id: subscription['id'],
|
167
|
+
subscription_name: subscription['name'],
|
168
|
+
management_endpoint: subscription['managementEndpointUrl'] || default_management_endpoint(config),
|
169
|
+
source: "azure profile #{filename ? "file #{filename}" : " IO object"}"
|
170
|
+
}
|
171
|
+
subscription[:azure_profile] = filename
|
172
|
+
if subscription['isDefault']
|
173
|
+
result[:is_default] = true
|
174
|
+
end
|
175
|
+
if subscription['managementCertificate'] && subscription['managementCertificate']['key']
|
176
|
+
# Concatenate the key and cert to one .pem so the SDK will be OK with it
|
177
|
+
result[:management_certificate] = {
|
178
|
+
type: :pem,
|
179
|
+
data: "#{subscription['managementCertificate']['key']}#{subscription['managementCertificate']['cert']}",
|
180
|
+
}
|
181
|
+
end
|
182
|
+
yield result
|
183
|
+
end
|
184
|
+
else
|
185
|
+
Chef::Log.warn("Azure profile #{filename ? "file #{filename}" : data} has no subscriptions")
|
186
|
+
end
|
187
|
+
|
188
|
+
else
|
189
|
+
raise "Unexpected value #{data.inspect} for azure_profile!"
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def self.default_subscriptions(config)
|
194
|
+
default_azure_profile = self.default_azure_profile(config)
|
195
|
+
azure_publish_settings_file = Chef::Config.knife[:azure_publish_settings_file] if Chef::Config.knife
|
196
|
+
Chef::Log.info("No Chef::Config[:driver_options][:subscriptions] found, reading environment variables AZURE_SUBSCRIPTION_ID, AZURE_MANAGEMENT_CERTIFICATE, and AZURE_MANAGEMENT_ENDPOINT,#{azure_publish_settings_file ? " then #{azure_publish_settings_file}," : ""} and then reading #{default_azure_profile}")
|
197
|
+
result = []
|
198
|
+
result << {
|
199
|
+
subscription_id: ENV["AZURE_SUBSCRIPTION_ID"],
|
200
|
+
management_certificate: ENV["AZURE_MANAGEMENT_CERTIFICATE"],
|
201
|
+
management_endpoint: ENV["AZURE_MANAGEMENT_ENDPOINT"],
|
202
|
+
source: "environment variables"
|
203
|
+
}
|
204
|
+
result << { publishsettings: azure_publish_settings_file } if azure_publish_settings_file
|
205
|
+
result << {
|
206
|
+
azure_profile: default_azure_profile,
|
207
|
+
allow_missing_file: true
|
208
|
+
}
|
209
|
+
result
|
210
|
+
end
|
211
|
+
|
212
|
+
def self.default_management_endpoint(config)
|
213
|
+
'https://management.core.windows.net'
|
214
|
+
end
|
215
|
+
|
216
|
+
def self.default_azure_profile(config)
|
217
|
+
File.join(config[:home_dir] || File.expand_path("~"), ".azure", "azureProfile.json")
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
metadata
CHANGED
@@ -1,100 +1,101 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chef-provisioning-azure
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: '0.3'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Ewart
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-02
|
11
|
+
date: 2015-04-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: chef
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - '>='
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - '>='
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: chef-provisioning
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - ~>
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '0.9'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - ~>
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0.9'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name: azure
|
42
|
+
name: stuartpreston-azure-sdk-for-ruby
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- -
|
45
|
+
- - ~>
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
47
|
+
version: 0.6.9
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- -
|
52
|
+
- - ~>
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
54
|
+
version: 0.6.9
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: rspec
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- -
|
59
|
+
- - '>='
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '0'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- -
|
66
|
+
- - '>='
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: rake
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- -
|
73
|
+
- - '>='
|
74
74
|
- !ruby/object:Gem::Version
|
75
75
|
version: '0'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- -
|
80
|
+
- - '>='
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: yard
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
|
-
- -
|
87
|
+
- - '>='
|
88
88
|
- !ruby/object:Gem::Version
|
89
89
|
version: '0'
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
|
-
- -
|
94
|
+
- - '>='
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
|
-
description:
|
97
|
+
description: This is a driver that works with chef-provisioning thatallows Chef Provisioning
|
98
|
+
to manage objects in Microsoft Azure.
|
98
99
|
email: jewart@getchef.com
|
99
100
|
executables: []
|
100
101
|
extensions: []
|
@@ -102,20 +103,21 @@ extra_rdoc_files:
|
|
102
103
|
- README.md
|
103
104
|
- LICENSE
|
104
105
|
files:
|
106
|
+
- Rakefile
|
105
107
|
- LICENSE
|
106
108
|
- README.md
|
107
|
-
- Rakefile
|
108
|
-
- lib/chef/provisioning/azure_driver.rb
|
109
109
|
- lib/chef/provisioning/azure_driver/bootstrap_options.rb
|
110
110
|
- lib/chef/provisioning/azure_driver/constants.rb
|
111
|
-
- lib/chef/provisioning/azure_driver/credentials.rb
|
112
111
|
- lib/chef/provisioning/azure_driver/driver.rb
|
113
112
|
- lib/chef/provisioning/azure_driver/machine_options.rb
|
114
113
|
- lib/chef/provisioning/azure_driver/resources.rb
|
114
|
+
- lib/chef/provisioning/azure_driver/subscriptions.rb
|
115
115
|
- lib/chef/provisioning/azure_driver/version.rb
|
116
|
+
- lib/chef/provisioning/azure_driver.rb
|
116
117
|
- lib/chef/provisioning/driver_init/azure.rb
|
117
|
-
homepage: https://github.com/
|
118
|
-
licenses:
|
118
|
+
homepage: https://github.com/chef/chef-provisioning-azure
|
119
|
+
licenses:
|
120
|
+
- Apache-2.0
|
119
121
|
metadata: {}
|
120
122
|
post_install_message:
|
121
123
|
rdoc_options: []
|
@@ -123,19 +125,18 @@ require_paths:
|
|
123
125
|
- lib
|
124
126
|
required_ruby_version: !ruby/object:Gem::Requirement
|
125
127
|
requirements:
|
126
|
-
- -
|
128
|
+
- - '>='
|
127
129
|
- !ruby/object:Gem::Version
|
128
130
|
version: '0'
|
129
131
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
130
132
|
requirements:
|
131
|
-
- -
|
133
|
+
- - '>='
|
132
134
|
- !ruby/object:Gem::Version
|
133
135
|
version: '0'
|
134
136
|
requirements: []
|
135
137
|
rubyforge_project:
|
136
|
-
rubygems_version: 2.
|
138
|
+
rubygems_version: 2.0.14
|
137
139
|
signing_key:
|
138
140
|
specification_version: 4
|
139
|
-
summary: Provisioner for creating Azure
|
141
|
+
summary: Provisioner for creating Microsoft Azure resources using Chef Provisioning.
|
140
142
|
test_files: []
|
141
|
-
has_rdoc:
|
@@ -1,73 +0,0 @@
|
|
1
|
-
require 'inifile'
|
2
|
-
|
3
|
-
class Chef
|
4
|
-
module Provisioning
|
5
|
-
module AzureDriver
|
6
|
-
# Reads in a credentials file from a config file and presents them
|
7
|
-
class Credentials
|
8
|
-
CONFIG_PATH = "#{ENV['HOME']}/.azure/config"
|
9
|
-
|
10
|
-
def initialize
|
11
|
-
@credentials = {}
|
12
|
-
load_default
|
13
|
-
end
|
14
|
-
|
15
|
-
include Enumerable
|
16
|
-
|
17
|
-
def default
|
18
|
-
if @credentials.size == 0
|
19
|
-
fail "No credentials loaded! Do you have a #{CONFIG_PATH}?"
|
20
|
-
end
|
21
|
-
default_key = ENV['AZURE_DEFAULT_PROFILE'] || 'default'
|
22
|
-
@credentials[default_key] || @credentials.first[1]
|
23
|
-
end
|
24
|
-
|
25
|
-
def keys
|
26
|
-
@credentials.keys
|
27
|
-
end
|
28
|
-
|
29
|
-
def [](name)
|
30
|
-
@credentials[name]
|
31
|
-
end
|
32
|
-
|
33
|
-
def each(&block)
|
34
|
-
@credentials.each(&block)
|
35
|
-
end
|
36
|
-
|
37
|
-
def load_ini(credentials_ini_file)
|
38
|
-
inifile = IniFile.load(File.expand_path(credentials_ini_file))
|
39
|
-
if inifile
|
40
|
-
inifile.each_section do |section|
|
41
|
-
if section =~ /^\s*profile\s+(.+)$/ || section =~ /^\s*(default)\s*/
|
42
|
-
profile_name = $1.strip
|
43
|
-
profile = inifile[section].inject({}) do |result, pair|
|
44
|
-
result[pair[0].to_sym] = pair[1]
|
45
|
-
result
|
46
|
-
end
|
47
|
-
profile[:name] = profile_name
|
48
|
-
@credentials[profile_name] = profile
|
49
|
-
end
|
50
|
-
end
|
51
|
-
else
|
52
|
-
# Get it to throw an error
|
53
|
-
File.open(File.expand_path(credentials_ini_file)) do
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
def load_default
|
59
|
-
config_file = ENV['AZURE_CONFIG_FILE'] || File.expand_path(CONFIG_PATH)
|
60
|
-
load_ini(config_file) if File.file?(config_file)
|
61
|
-
end
|
62
|
-
|
63
|
-
def self.method_missing(name, *args, &block)
|
64
|
-
singleton.send(name, *args, &block)
|
65
|
-
end
|
66
|
-
|
67
|
-
def self.singleton
|
68
|
-
@azure_credentials ||= Credentials.new
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|