chef-provisioning-azure 0.2.1 → 0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
[![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/
|
1
|
+
[![Gitter](https://badges.gitter.im/Join Chat.svg)](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
|