knife-azure 1.0.2 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +275 -0
- data/lib/azure/certificate.rb +87 -0
- data/lib/azure/connection.rb +3 -1
- data/lib/azure/deploy.rb +72 -28
- data/lib/azure/host.rb +97 -18
- data/lib/azure/image.rb +21 -15
- data/lib/azure/rest.rb +9 -18
- data/lib/azure/role.rb +165 -48
- data/lib/azure/storageaccount.rb +39 -18
- data/lib/chef/knife/azure_base.rb +69 -9
- data/lib/chef/knife/azure_image_list.rb +14 -13
- data/lib/chef/knife/azure_server_create.rb +271 -108
- data/lib/chef/knife/azure_server_delete.rb +55 -13
- data/lib/chef/knife/azure_server_list.rb +9 -16
- data/lib/knife-azure/version.rb +1 -1
- metadata +119 -207
- data/README.rdoc +0 -252
data/lib/azure/storageaccount.rb
CHANGED
@@ -21,24 +21,45 @@ class Azure
|
|
21
21
|
def initialize(connection)
|
22
22
|
@connection=connection
|
23
23
|
end
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
24
|
+
# force_load should be true when there is something in local cache and we want to reload
|
25
|
+
# first call is always load.
|
26
|
+
def load(force_load = false)
|
27
|
+
if not @azure_storage_accounts || force_load
|
28
|
+
@azure_storage_accounts = begin
|
29
|
+
azure_storage_accounts = Hash.new
|
30
|
+
responseXML = @connection.query_azure('storageservices')
|
31
|
+
servicesXML = responseXML.css('StorageServices StorageService')
|
32
|
+
servicesXML.each do |serviceXML|
|
33
|
+
storage = StorageAccount.new(@connection).parse(serviceXML)
|
34
|
+
azure_storage_accounts[storage.name] = storage
|
35
|
+
end
|
36
|
+
azure_storage_accounts
|
37
|
+
end
|
31
38
|
end
|
32
|
-
|
39
|
+
@azure_storage_accounts
|
33
40
|
end
|
34
|
-
|
35
|
-
|
36
|
-
self.
|
37
|
-
|
38
|
-
|
41
|
+
|
42
|
+
def all
|
43
|
+
self.load.values
|
44
|
+
end
|
45
|
+
|
46
|
+
# first look up local cache if we have already loaded list.
|
47
|
+
def exists?(name)
|
48
|
+
return @azure_storage_accounts.key?(name) if @azure_storage_accounts
|
49
|
+
self.exists_on_cloud?(name)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Look up on cloud and not local cache
|
53
|
+
def exists_on_cloud?(name)
|
54
|
+
ret_val = @connection.query_azure("storageservices/#{name}")
|
55
|
+
if ret_val.nil? || ret_val.css('Error Code').length > 0
|
56
|
+
Chef::Log.warn 'Unable to find storage account:' + ret_val.at_css('Error Code').content + ' : ' + ret_val.at_css('Error Message').content if ret_val
|
57
|
+
false
|
58
|
+
else
|
59
|
+
true
|
39
60
|
end
|
40
|
-
storageExists
|
41
61
|
end
|
62
|
+
|
42
63
|
def create(params)
|
43
64
|
storage = StorageAccount.new(@connection)
|
44
65
|
storage.create(params)
|
@@ -74,11 +95,11 @@ class Azure
|
|
74
95
|
def create(params)
|
75
96
|
builder = Nokogiri::XML::Builder.new do |xml|
|
76
97
|
xml.CreateStorageServiceInput('xmlns'=>'http://schemas.microsoft.com/windowsazure') {
|
77
|
-
xml.ServiceName params[:
|
78
|
-
xml.Label Base64.encode64(params[:
|
79
|
-
xml.Description params[:
|
98
|
+
xml.ServiceName params[:azure_storage_account]
|
99
|
+
xml.Label Base64.encode64(params[:azure_storage_account])
|
100
|
+
xml.Description params[:azure_storage_account_description] || 'Explicitly created storage service'
|
80
101
|
# Location defaults to 'West US'
|
81
|
-
xml.Location params[:
|
102
|
+
xml.Location params[:azure_service_location] || 'West US'
|
82
103
|
}
|
83
104
|
end
|
84
105
|
@connection.query_azure("storageservices", "post", builder.to_xml)
|
@@ -47,27 +47,41 @@ class Chef
|
|
47
47
|
:description => "Your Azure PEM file name",
|
48
48
|
:proc => Proc.new { |key| Chef::Config[:knife][:azure_mgmt_cert] = key }
|
49
49
|
|
50
|
-
option :
|
50
|
+
option :azure_api_host_name,
|
51
51
|
:short => "-H HOSTNAME",
|
52
|
-
:long => "--azure-host-name HOSTNAME",
|
52
|
+
:long => "--azure-api-host-name HOSTNAME",
|
53
53
|
:description => "Your Azure host name",
|
54
|
-
:proc => Proc.new { |key| Chef::Config[:knife][:
|
55
|
-
:default => "management.core.windows.net"
|
54
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:azure_api_host_name] = key }
|
56
55
|
|
57
56
|
option :verify_ssl_cert,
|
58
57
|
:long => "--verify-ssl-cert",
|
59
58
|
:description => "Verify SSL Certificates for communication over HTTPS",
|
60
59
|
:boolean => true,
|
61
60
|
:default => false
|
61
|
+
|
62
|
+
option :azure_publish_settings_file,
|
63
|
+
:long => "--azure-publish-settings-file FILENAME",
|
64
|
+
:description => "Your Azure Publish Settings File",
|
65
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:azure_publish_settings_file] = key }
|
62
66
|
end
|
63
67
|
end
|
64
68
|
|
69
|
+
def is_image_windows?
|
70
|
+
images = connection.images
|
71
|
+
target_image = images.all.select { |i| i.name == locate_config_value(:azure_source_image) }
|
72
|
+
unless target_image[0].nil?
|
73
|
+
return target_image[0].os == 'Windows'
|
74
|
+
else
|
75
|
+
ui.error("Invalid image. Use the command \"knife azure image list\" to verify the image name")
|
76
|
+
exit 1
|
77
|
+
end
|
78
|
+
end
|
65
79
|
def connection
|
66
80
|
@connection ||= begin
|
67
81
|
connection = Azure::Connection.new(
|
68
82
|
:azure_subscription_id => locate_config_value(:azure_subscription_id),
|
69
83
|
:azure_mgmt_cert => locate_config_value(:azure_mgmt_cert),
|
70
|
-
:
|
84
|
+
:azure_api_host_name => locate_config_value(:azure_api_host_name),
|
71
85
|
:verify_ssl_cert => locate_config_value(:verify_ssl_cert)
|
72
86
|
)
|
73
87
|
end
|
@@ -84,21 +98,67 @@ class Chef
|
|
84
98
|
end
|
85
99
|
end
|
86
100
|
|
87
|
-
def validate!(keys=[:azure_subscription_id, :azure_mgmt_cert, :
|
101
|
+
def validate!(keys=[:azure_subscription_id, :azure_mgmt_cert, :azure_api_host_name])
|
88
102
|
errors = []
|
89
|
-
|
103
|
+
if(locate_config_value(:azure_mgmt_cert) != nil)
|
104
|
+
config[:azure_mgmt_cert] = File.read find_file(locate_config_value(:azure_mgmt_cert))
|
105
|
+
end
|
106
|
+
if(locate_config_value(:azure_publish_settings_file) != nil)
|
107
|
+
parse_publish_settings_file(locate_config_value(:azure_publish_settings_file))
|
108
|
+
end
|
90
109
|
keys.each do |k|
|
91
110
|
pretty_key = k.to_s.gsub(/_/, ' ').gsub(/\w+/){ |w| (w =~ /(ssh)|(aws)/i) ? w.upcase : w.capitalize }
|
92
111
|
if locate_config_value(k).nil?
|
93
|
-
errors << "You did not provide a valid '#{pretty_key}' value."
|
112
|
+
errors << "You did not provide a valid '#{pretty_key}' value. Please set knife[:#{k}] in your knife.rb or pass as an option."
|
94
113
|
end
|
95
114
|
end
|
96
|
-
|
97
115
|
if errors.each{|e| ui.error(e)}.any?
|
98
116
|
exit 1
|
99
117
|
end
|
100
118
|
end
|
101
119
|
|
120
|
+
def parse_publish_settings_file(filename)
|
121
|
+
require 'nokogiri'
|
122
|
+
require 'base64'
|
123
|
+
require 'openssl'
|
124
|
+
require 'uri'
|
125
|
+
begin
|
126
|
+
doc = Nokogiri::XML(File.open(find_file(filename)))
|
127
|
+
profile = doc.at_css("PublishProfile")
|
128
|
+
subscription = profile.at_css("Subscription")
|
129
|
+
#check given PublishSettings XML file format.Currently PublishSettings file have two different XML format
|
130
|
+
if profile.attribute("SchemaVersion").nil?
|
131
|
+
management_cert = OpenSSL::PKCS12.new(Base64.decode64(profile.attribute("ManagementCertificate").value))
|
132
|
+
Chef::Config[:knife][:azure_api_host_name] = URI(profile.attribute("Url").value).host
|
133
|
+
elsif profile.attribute("SchemaVersion").value == "2.0"
|
134
|
+
management_cert = OpenSSL::PKCS12.new(Base64.decode64(subscription.attribute("ManagementCertificate").value))
|
135
|
+
Chef::Config[:knife][:azure_api_host_name] = URI(subscription.attribute("ServiceManagementUrl").value).host
|
136
|
+
else
|
137
|
+
ui.error("Publish settings file Schema not supported - " + filename)
|
138
|
+
end
|
139
|
+
Chef::Config[:knife][:azure_mgmt_cert] = management_cert.certificate.to_pem + management_cert.key.to_pem
|
140
|
+
Chef::Config[:knife][:azure_subscription_id] = doc.at_css("Subscription").attribute("Id").value
|
141
|
+
rescue
|
142
|
+
ui.error("Incorrect publish settings file - " + filename)
|
143
|
+
exit 1
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def find_file(name)
|
148
|
+
config_dir = Chef::Knife.chef_config_dir
|
149
|
+
if File.exist? name
|
150
|
+
file = name
|
151
|
+
elsif config_dir && File.exist?(File.join(config_dir, name))
|
152
|
+
file = File.join(config_dir, name)
|
153
|
+
elsif File.exist?(File.join(ENV['HOME'], '.chef', name))
|
154
|
+
file = File.join(ENV['HOME'], '.chef', name)
|
155
|
+
else
|
156
|
+
ui.error('Unable to find file - ' + name)
|
157
|
+
exit 1
|
158
|
+
end
|
159
|
+
file
|
160
|
+
end
|
161
|
+
|
102
162
|
end
|
103
163
|
end
|
104
164
|
end
|
@@ -29,6 +29,12 @@ class Chef
|
|
29
29
|
|
30
30
|
banner "knife azure image list (options)"
|
31
31
|
|
32
|
+
option :show_all_fields,
|
33
|
+
:long => "--full",
|
34
|
+
:default => false,
|
35
|
+
:boolean => true,
|
36
|
+
:description => "Show all the fields of the images"
|
37
|
+
|
32
38
|
def h
|
33
39
|
@highline ||= HighLine.new
|
34
40
|
end
|
@@ -38,23 +44,18 @@ class Chef
|
|
38
44
|
|
39
45
|
validate!
|
40
46
|
|
41
|
-
|
42
|
-
|
43
|
-
ui.color('Category', :bold),
|
44
|
-
ui.color('Label', :bold),
|
45
|
-
ui.color('OS', :bold),
|
46
|
-
]
|
47
|
+
image_labels = !locate_config_value(:show_all_fields) ? ['Name', 'OS'] : ['Name', 'Category', 'Label', 'OS']
|
48
|
+
image_list = image_labels.map {|label| ui.color(label, :bold)}
|
47
49
|
items = connection.images.all
|
50
|
+
|
51
|
+
image_items = image_labels.map {|item| item.downcase }
|
48
52
|
items.each do |image|
|
49
|
-
|
50
|
-
image_list << image.name.to_s
|
51
|
-
image_list << image.category.to_s
|
52
|
-
image_list << image.label.to_s
|
53
|
-
image_list << image.os.to_s
|
54
|
-
end
|
53
|
+
image_items.each {|item| image_list << image.send(item).to_s }
|
55
54
|
end
|
55
|
+
|
56
56
|
puts "\n"
|
57
|
-
puts h.list(image_list, :
|
57
|
+
puts h.list(image_list, :uneven_columns_across, !locate_config_value(:show_all_fields) ? 2 : 4)
|
58
|
+
|
58
59
|
end
|
59
60
|
end
|
60
61
|
end
|
@@ -18,25 +18,40 @@
|
|
18
18
|
# limitations under the License.
|
19
19
|
#
|
20
20
|
|
21
|
-
require
|
22
|
-
|
21
|
+
require 'chef/knife/azure_base'
|
22
|
+
require 'chef/knife/winrm_base'
|
23
23
|
class Chef
|
24
24
|
class Knife
|
25
25
|
class AzureServerCreate < Knife
|
26
26
|
|
27
27
|
include Knife::AzureBase
|
28
|
+
include Knife::WinrmBase
|
28
29
|
|
29
30
|
deps do
|
30
31
|
require 'readline'
|
31
32
|
require 'chef/json_compat'
|
32
33
|
require 'chef/knife/bootstrap'
|
34
|
+
require 'chef/knife/bootstrap_windows_ssh'
|
35
|
+
require 'chef/knife/core/windows_bootstrap_context'
|
33
36
|
Chef::Knife::Bootstrap.load_deps
|
34
37
|
end
|
35
38
|
|
39
|
+
def load_winrm_deps
|
40
|
+
require 'winrm'
|
41
|
+
require 'em-winrm'
|
42
|
+
require 'chef/knife/winrm'
|
43
|
+
require 'chef/knife/bootstrap_windows_winrm'
|
44
|
+
end
|
45
|
+
|
36
46
|
banner "knife azure server create (options)"
|
37
47
|
|
38
48
|
attr_accessor :initial_sleep_delay
|
39
49
|
|
50
|
+
option :bootstrap_protocol,
|
51
|
+
:long => "--bootstrap-protocol protocol",
|
52
|
+
:description => "Protocol to bootstrap windows servers. options: winrm/ssh",
|
53
|
+
:default => "winrm"
|
54
|
+
|
40
55
|
option :chef_node_name,
|
41
56
|
:short => "-N NAME",
|
42
57
|
:long => "--node-name NAME",
|
@@ -52,10 +67,10 @@ class Chef
|
|
52
67
|
:long => "--ssh-password PASSWORD",
|
53
68
|
:description => "The ssh password"
|
54
69
|
|
55
|
-
option :
|
56
|
-
:
|
57
|
-
:
|
58
|
-
:
|
70
|
+
option :ssh_port,
|
71
|
+
:long => "--ssh-port PORT",
|
72
|
+
:description => "The ssh port. Default is 22.",
|
73
|
+
:default => '22'
|
59
74
|
|
60
75
|
option :prerelease,
|
61
76
|
:long => "--prerelease",
|
@@ -86,55 +101,57 @@ class Chef
|
|
86
101
|
:proc => lambda { |o| o.split(/[\s,]+/) },
|
87
102
|
:default => []
|
88
103
|
|
89
|
-
option :
|
90
|
-
:long => "--no-host-key-verify",
|
91
|
-
:description => "
|
104
|
+
option :host_key_verify,
|
105
|
+
:long => "--[no-]host-key-verify",
|
106
|
+
:description => "Verify host key, enabled by default.",
|
92
107
|
:boolean => true,
|
93
|
-
:default =>
|
94
|
-
|
95
|
-
option :hosted_service_name,
|
96
|
-
:short => "-s NAME",
|
97
|
-
:long => "--hosted-service-name NAME",
|
98
|
-
:description => "specifies the name for the hosted service"
|
99
|
-
|
100
|
-
option :hosted_service_description,
|
101
|
-
:short => "-D DESCRIPTION",
|
102
|
-
:long => "--hosted_service_description DESCRIPTION",
|
103
|
-
:description => "Description for the hosted service"
|
108
|
+
:default => true
|
104
109
|
|
105
|
-
option :
|
110
|
+
option :azure_storage_account,
|
106
111
|
:short => "-a NAME",
|
107
|
-
:long => "--storage-account NAME",
|
108
|
-
:description => "
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
:
|
117
|
-
|
118
|
-
|
119
|
-
option :
|
112
|
+
:long => "--azure-storage-account NAME",
|
113
|
+
:description => "Required for advanced server-create option.
|
114
|
+
A name for the storage account that is unique within Windows Azure. Storage account names must be
|
115
|
+
between 3 and 24 characters in length and use numbers and lower-case letters only.
|
116
|
+
This name is the DNS prefix name and can be used to access blobs, queues, and tables in the storage account.
|
117
|
+
For example: http://ServiceName.blob.core.windows.net/mycontainer/"
|
118
|
+
|
119
|
+
option :azure_vm_name,
|
120
|
+
:long => "--azure-vm-name NAME",
|
121
|
+
:description => "Required for advanced server-create option.
|
122
|
+
Specifies the name for the virtual machine. The name must be unique within the deployment."
|
123
|
+
|
124
|
+
option :azure_service_location,
|
120
125
|
:short => "-m LOCATION",
|
121
|
-
:long => "--service-location LOCATION",
|
122
|
-
:description => "
|
123
|
-
|
124
|
-
|
126
|
+
:long => "--azure-service-location LOCATION",
|
127
|
+
:description => "Required. Specifies the geographic location - the name of the data center location that is valid for your subscription.
|
128
|
+
Eg: West US, East US, East Asia, Southeast Asia, North Europe, West Europe"
|
129
|
+
|
130
|
+
option :azure_dns_name,
|
131
|
+
:short => "-d DNS_NAME",
|
132
|
+
:long => "--azure-dns-name DNS_NAME",
|
133
|
+
:description => "Required. The DNS prefix name that can be used to access the cloud service which is unique within Windows Azure.
|
134
|
+
If you want to add new VM to an existing service/deployment, specify an exiting dns-name,
|
135
|
+
along with --azure-connect-to-existing-dns option.
|
136
|
+
Otherwise a new deployment is created. For example, if the DNS of cloud service is MyService you could access the cloud service
|
137
|
+
by calling: http://DNS_NAME.cloudapp.net"
|
138
|
+
|
139
|
+
option :azure_os_disk_name,
|
125
140
|
:short => "-o DISKNAME",
|
126
|
-
:long => "--os-disk-name DISKNAME",
|
127
|
-
:description => "
|
141
|
+
:long => "--azure-os-disk-name DISKNAME",
|
142
|
+
:description => "Optional. Specifies the friendly name of the disk containing the guest OS image in the image repository."
|
128
143
|
|
129
|
-
option :
|
144
|
+
option :azure_source_image,
|
130
145
|
:short => "-I IMAGE",
|
131
|
-
:long => "--source-image IMAGE",
|
132
|
-
:description => "disk image
|
146
|
+
:long => "--azure-source-image IMAGE",
|
147
|
+
:description => "Required. Specifies the name of the disk image to use to create the virtual machine.
|
148
|
+
Do a \"knife azure image list\" to see a list of available images."
|
133
149
|
|
134
|
-
option :
|
150
|
+
option :azure_vm_size,
|
135
151
|
:short => "-z SIZE",
|
136
|
-
:long => "--
|
137
|
-
:description => "
|
152
|
+
:long => "--azure-vm-size SIZE",
|
153
|
+
:description => "Optional. Size of virtual machine (ExtraSmall, Small, Medium, Large, ExtraLarge)",
|
154
|
+
:default => 'Small'
|
138
155
|
|
139
156
|
option :tcp_endpoints,
|
140
157
|
:short => "-t PORT_LIST",
|
@@ -146,6 +163,29 @@ class Chef
|
|
146
163
|
:long => "--udp-endpoints PORT_LIST",
|
147
164
|
:description => "Comma separated list of UDP local and public ports to open i.e. '80:80,433:5000'"
|
148
165
|
|
166
|
+
option :azure_connect_to_existing_dns,
|
167
|
+
:short => "-c",
|
168
|
+
:long => "--azure-connect-to-existing-dns",
|
169
|
+
:boolean => true,
|
170
|
+
:default => false,
|
171
|
+
:description => "Set this flag to add the new VM to an existing deployment/service. Must give the name of the existing
|
172
|
+
DNS correctly in the --dns-name option"
|
173
|
+
option :identity_file,
|
174
|
+
:long => "--identity-file FILENAME",
|
175
|
+
:description => "SSH identity file for authentication, optional. It is the RSA private key path. Specify either ssh-password or identity-file"
|
176
|
+
|
177
|
+
option :identity_file_passphrase,
|
178
|
+
:long => "--identity-file-passphrase PASSWORD",
|
179
|
+
:description => "SSH key passphrase. Optional, specify if passphrase for identity-file exists"
|
180
|
+
|
181
|
+
option :hint,
|
182
|
+
:long => "--hint HINT_NAME[=HINT_FILE]",
|
183
|
+
:description => "Specify Ohai Hint to be set on the bootstrap target. Use multiple --hint options to specify multiple hints.",
|
184
|
+
:proc => Proc.new { |h|
|
185
|
+
Chef::Config[:knife][:hints] ||= {}
|
186
|
+
name, path = h.split("=")
|
187
|
+
Chef::Config[:knife][:hints][name] = path ? JSON.parse(::File.read(path)) : Hash.new
|
188
|
+
}
|
149
189
|
|
150
190
|
def strip_non_ascii(string)
|
151
191
|
string.gsub(/[^0-9a-z ]/i, '')
|
@@ -155,6 +195,28 @@ class Chef
|
|
155
195
|
(0...len).map{65.+(rand(25)).chr}.join
|
156
196
|
end
|
157
197
|
|
198
|
+
def tcp_test_winrm(ip_addr, port)
|
199
|
+
hostname = ip_addr
|
200
|
+
socket = TCPSocket.new(hostname, port)
|
201
|
+
return true
|
202
|
+
rescue SocketError
|
203
|
+
sleep 2
|
204
|
+
false
|
205
|
+
rescue Errno::ETIMEDOUT
|
206
|
+
false
|
207
|
+
rescue Errno::EPERM
|
208
|
+
false
|
209
|
+
rescue Errno::ECONNREFUSED
|
210
|
+
sleep 2
|
211
|
+
false
|
212
|
+
rescue Errno::EHOSTUNREACH
|
213
|
+
sleep 2
|
214
|
+
false
|
215
|
+
rescue Errno::ENETUNREACH
|
216
|
+
sleep 2
|
217
|
+
false
|
218
|
+
end
|
219
|
+
|
158
220
|
def tcp_test_ssh(fqdn, sshport)
|
159
221
|
tcp_socket = TCPSocket.new(fqdn, sshport)
|
160
222
|
readable = IO.select([tcp_socket], nil, nil, 5)
|
@@ -175,7 +237,6 @@ class Chef
|
|
175
237
|
rescue Errno::ECONNREFUSED
|
176
238
|
sleep 2
|
177
239
|
false
|
178
|
-
# This happens on EC2 quite often
|
179
240
|
rescue Errno::EHOSTUNREACH
|
180
241
|
sleep 2
|
181
242
|
false
|
@@ -183,32 +244,6 @@ class Chef
|
|
183
244
|
tcp_socket && tcp_socket.close
|
184
245
|
end
|
185
246
|
|
186
|
-
def parameter_test
|
187
|
-
details = Array.new
|
188
|
-
details << ui.color('name', :bold, :blue)
|
189
|
-
details << ui.color('Chef::Config', :bold, :blue)
|
190
|
-
details << ui.color('config', :bold, :blue)
|
191
|
-
details << ui.color('winner is', :bold, :blue)
|
192
|
-
[
|
193
|
-
:azure_subscription_id,
|
194
|
-
:azure_mgmt_cert,
|
195
|
-
:azure_host_name,
|
196
|
-
:role_name,
|
197
|
-
:host_name,
|
198
|
-
:ssh_user,
|
199
|
-
:ssh_password,
|
200
|
-
:service_location,
|
201
|
-
:source_image,
|
202
|
-
:role_size
|
203
|
-
].each do |key|
|
204
|
-
key = key.to_sym
|
205
|
-
details << key.to_s
|
206
|
-
details << Chef::Config[:knife][key].to_s
|
207
|
-
details << config[key].to_s
|
208
|
-
details << locate_config_value(key)
|
209
|
-
end
|
210
|
-
puts ui.list(details, :columns_across, 4)
|
211
|
-
end
|
212
247
|
def run
|
213
248
|
$stdout.sync = true
|
214
249
|
storage = nil
|
@@ -217,31 +252,55 @@ class Chef
|
|
217
252
|
validate!
|
218
253
|
|
219
254
|
Chef::Log.info("creating...")
|
220
|
-
|
221
|
-
if not locate_config_value(:
|
222
|
-
config[:
|
255
|
+
|
256
|
+
if not locate_config_value(:azure_vm_name)
|
257
|
+
config[:azure_vm_name] = locate_config_value(:azure_dns_name)
|
223
258
|
end
|
224
259
|
|
225
|
-
#If Storage Account is not specified, check if the geographic location has one to re-use
|
226
|
-
if not locate_config_value(:
|
260
|
+
#If Storage Account is not specified, check if the geographic location has one to re-use
|
261
|
+
if not locate_config_value(:azure_storage_account)
|
227
262
|
storage_accts = connection.storageaccounts.all
|
228
|
-
storage = storage_accts.find { |storage_acct| storage_acct.location.to_s == locate_config_value(:
|
263
|
+
storage = storage_accts.find { |storage_acct| storage_acct.location.to_s == locate_config_value(:azure_service_location) }
|
229
264
|
if not storage
|
230
|
-
config[:
|
265
|
+
config[:azure_storage_account] = [strip_non_ascii(locate_config_value(:azure_vm_name)), random_string].join.downcase
|
231
266
|
else
|
232
|
-
config[:
|
267
|
+
config[:azure_storage_account] = storage.name.to_s
|
233
268
|
end
|
234
269
|
end
|
270
|
+
|
235
271
|
server = connection.deploys.create(create_server_def)
|
272
|
+
fqdn = server.publicipaddress
|
236
273
|
|
237
274
|
puts("\n")
|
275
|
+
if is_image_windows?
|
276
|
+
if locate_config_value(:bootstrap_protocol) == 'ssh'
|
277
|
+
port = server.sshport
|
278
|
+
print "\n#{ui.color("Waiting for sshd on #{fqdn}:#{port}", :magenta)}"
|
279
|
+
|
280
|
+
print(".") until tcp_test_ssh(fqdn,port) {
|
281
|
+
sleep @initial_sleep_delay ||= 10
|
282
|
+
puts("done")
|
283
|
+
}
|
284
|
+
|
285
|
+
elsif locate_config_value(:bootstrap_protocol) == 'winrm'
|
286
|
+
port = server.winrmport
|
287
|
+
|
288
|
+
print "\n#{ui.color("Waiting for winrm on #{fqdn}:#{port}", :magenta)}"
|
289
|
+
|
290
|
+
print(".") until tcp_test_winrm(fqdn,port) {
|
291
|
+
sleep @initial_sleep_delay ||= 10
|
292
|
+
puts("done")
|
293
|
+
}
|
238
294
|
|
239
|
-
|
240
|
-
|
295
|
+
end
|
296
|
+
sleep 15
|
297
|
+
bootstrap_for_windows_node(server,fqdn, port).run
|
298
|
+
else
|
299
|
+
unless server && server.publicipaddress && server.sshport
|
300
|
+
Chef::Log.fatal("server not created")
|
241
301
|
exit 1
|
242
302
|
end
|
243
303
|
|
244
|
-
fqdn = server.sshipaddress
|
245
304
|
port = server.sshport
|
246
305
|
|
247
306
|
print "\n#{ui.color("Waiting for sshd on #{fqdn}:#{port}", :magenta)}"
|
@@ -252,10 +311,69 @@ class Chef
|
|
252
311
|
}
|
253
312
|
|
254
313
|
sleep 15
|
314
|
+
bootstrap_for_node(server,fqdn,port).run
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
def load_cloud_attributes_in_hints(server)
|
319
|
+
# Modify global configuration state to ensure hint gets set by knife-bootstrap
|
320
|
+
# Query azure and load necessary attributes.
|
321
|
+
cloud_attributes = {}
|
322
|
+
cloud_attributes["public_ip"] = server.publicipaddress
|
323
|
+
cloud_attributes["vm_name"] = server.name
|
324
|
+
cloud_attributes["public_fqdn"] = server.hostedservicename.to_s + ".cloudapp.net"
|
325
|
+
cloud_attributes["public_ssh_port"] = server.sshport if server.sshport
|
326
|
+
cloud_attributes["public_winrm_port"] = server.winrmport if server.winrmport
|
255
327
|
|
256
|
-
|
328
|
+
Chef::Config[:knife][:hints] ||= {}
|
329
|
+
Chef::Config[:knife][:hints]["azure"] ||= cloud_attributes
|
257
330
|
|
258
|
-
|
331
|
+
end
|
332
|
+
|
333
|
+
def bootstrap_common_params(bootstrap, server)
|
334
|
+
|
335
|
+
bootstrap.config[:run_list] = config[:run_list]
|
336
|
+
bootstrap.config[:prerelease] = config[:prerelease]
|
337
|
+
bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version)
|
338
|
+
bootstrap.config[:distro] = locate_config_value(:distro)
|
339
|
+
bootstrap.config[:template_file] = locate_config_value(:template_file)
|
340
|
+
load_cloud_attributes_in_hints(server)
|
341
|
+
bootstrap
|
342
|
+
end
|
343
|
+
|
344
|
+
|
345
|
+
def bootstrap_for_windows_node(server, fqdn, port)
|
346
|
+
if locate_config_value(:bootstrap_protocol) == 'winrm'
|
347
|
+
|
348
|
+
load_winrm_deps
|
349
|
+
if not Chef::Platform.windows?
|
350
|
+
require 'gssapi'
|
351
|
+
end
|
352
|
+
|
353
|
+
bootstrap = Chef::Knife::BootstrapWindowsWinrm.new
|
354
|
+
|
355
|
+
bootstrap.config[:winrm_user] = locate_config_value(:winrm_user) || 'Administrator'
|
356
|
+
bootstrap.config[:winrm_password] = locate_config_value(:winrm_password)
|
357
|
+
bootstrap.config[:winrm_transport] = locate_config_value(:winrm_transport)
|
358
|
+
|
359
|
+
bootstrap.config[:winrm_port] = port
|
360
|
+
|
361
|
+
elsif locate_config_value(:bootstrap_protocol) == 'ssh'
|
362
|
+
bootstrap = Chef::Knife::BootstrapWindowsSsh.new
|
363
|
+
bootstrap.config[:ssh_user] = locate_config_value(:ssh_user)
|
364
|
+
bootstrap.config[:ssh_password] = locate_config_value(:ssh_password)
|
365
|
+
bootstrap.config[:ssh_port] = port
|
366
|
+
bootstrap.config[:identity_file] = locate_config_value(:identity_file)
|
367
|
+
bootstrap.config[:host_key_verify] = locate_config_value(:host_key_verify)
|
368
|
+
else
|
369
|
+
ui.error("Unsupported Bootstrapping Protocol. Supported : winrm, ssh")
|
370
|
+
exit 1
|
371
|
+
end
|
372
|
+
bootstrap.name_args = [fqdn]
|
373
|
+
bootstrap.config[:chef_node_name] = config[:chef_node_name] || server.name
|
374
|
+
bootstrap.config[:encrypted_data_bag_secret] = config[:encrypted_data_bag_secret]
|
375
|
+
bootstrap.config[:encrypted_data_bag_secret_file] = config[:encrypted_data_bag_secret_file]
|
376
|
+
bootstrap_common_params(bootstrap, server)
|
259
377
|
end
|
260
378
|
|
261
379
|
def bootstrap_for_node(server,fqdn,port)
|
@@ -271,45 +389,90 @@ class Chef
|
|
271
389
|
bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version)
|
272
390
|
bootstrap.config[:distro] = locate_config_value(:distro)
|
273
391
|
bootstrap.config[:use_sudo] = true unless locate_config_value(:ssh_user) == 'root'
|
392
|
+
bootstrap.config[:use_sudo_password] = true if bootstrap.config[:use_sudo]
|
274
393
|
bootstrap.config[:template_file] = config[:template_file]
|
275
394
|
bootstrap.config[:environment] = locate_config_value(:environment)
|
276
395
|
# may be needed for vpc_mode
|
277
|
-
bootstrap.config[:
|
396
|
+
bootstrap.config[:host_key_verify] = config[:host_key_verify]
|
397
|
+
|
398
|
+
# Load cloud attributes.
|
399
|
+
load_cloud_attributes_in_hints(server)
|
400
|
+
|
278
401
|
bootstrap
|
279
402
|
end
|
280
403
|
|
281
404
|
def validate!
|
282
405
|
super([
|
283
|
-
:azure_subscription_id,
|
284
|
-
:azure_mgmt_cert,
|
285
|
-
:
|
286
|
-
:
|
287
|
-
:
|
288
|
-
:
|
289
|
-
:
|
290
|
-
:service_location,
|
291
|
-
:source_image,
|
292
|
-
:role_size
|
406
|
+
:azure_subscription_id,
|
407
|
+
:azure_mgmt_cert,
|
408
|
+
:azure_api_host_name,
|
409
|
+
:azure_dns_name,
|
410
|
+
:azure_service_location,
|
411
|
+
:azure_source_image,
|
412
|
+
:azure_vm_size,
|
293
413
|
])
|
414
|
+
if locate_config_value(:azure_connect_to_existing_dns) && locate_config_value(:azure_vm_name).nil?
|
415
|
+
ui.error("Specify the VM name using --azure-vm-name option, since you are connecting to existing dns")
|
416
|
+
exit 1
|
417
|
+
end
|
294
418
|
end
|
295
419
|
|
296
420
|
def create_server_def
|
297
421
|
server_def = {
|
298
|
-
:
|
299
|
-
:
|
300
|
-
:
|
301
|
-
:
|
302
|
-
:
|
303
|
-
:
|
304
|
-
:
|
305
|
-
:os_disk_name => locate_config_value(:os_disk_name),
|
306
|
-
:source_image => locate_config_value(:source_image),
|
307
|
-
:role_size => locate_config_value(:role_size),
|
422
|
+
:azure_storage_account => locate_config_value(:azure_storage_account),
|
423
|
+
:azure_dns_name => locate_config_value(:azure_dns_name),
|
424
|
+
:azure_vm_name => locate_config_value(:azure_vm_name),
|
425
|
+
:azure_service_location => locate_config_value(:azure_service_location),
|
426
|
+
:azure_os_disk_name => locate_config_value(:azure_os_disk_name),
|
427
|
+
:azure_source_image => locate_config_value(:azure_source_image),
|
428
|
+
:azure_vm_size => locate_config_value(:azure_vm_size),
|
308
429
|
:tcp_endpoints => locate_config_value(:tcp_endpoints),
|
309
|
-
:udp_endpoints => locate_config_value(:udp_endpoints)
|
430
|
+
:udp_endpoints => locate_config_value(:udp_endpoints),
|
431
|
+
:bootstrap_proto => locate_config_value(:bootstrap_protocol),
|
432
|
+
:azure_connect_to_existing_dns => locate_config_value(:azure_connect_to_existing_dns)
|
310
433
|
}
|
434
|
+
# If user is connecting a new VM to an existing dns, then
|
435
|
+
# the VM needs to have a unique public port. Logic below takes care of this.
|
436
|
+
if !is_image_windows? or locate_config_value(:bootstrap_protocol) == 'ssh'
|
437
|
+
port = locate_config_value(:ssh_port) || '22'
|
438
|
+
if locate_config_value(:azure_connect_to_existing_dns) && (port == '22')
|
439
|
+
port = Random.rand(64000) + 1000
|
440
|
+
end
|
441
|
+
else
|
442
|
+
port = locate_config_value(:winrm_port) || '5985'
|
443
|
+
if locate_config_value(:azure_connect_to_existing_dns) && (port == '5985')
|
444
|
+
port = Random.rand(64000) + 1000
|
445
|
+
end
|
446
|
+
end
|
447
|
+
server_def[:port] = port
|
448
|
+
|
449
|
+
if is_image_windows?
|
450
|
+
server_def[:os_type] = 'Windows'
|
451
|
+
if not locate_config_value(:winrm_password) or not locate_config_value(:bootstrap_protocol)
|
452
|
+
ui.error("WinRM Password and Bootstrapping Protocol are compulsory parameters")
|
453
|
+
end
|
454
|
+
server_def[:admin_password] = locate_config_value(:winrm_password)
|
455
|
+
server_def[:bootstrap_proto] = locate_config_value(:bootstrap_protocol)
|
456
|
+
else
|
457
|
+
server_def[:os_type] = 'Linux'
|
458
|
+
server_def[:bootstrap_proto] = 'ssh'
|
459
|
+
if not locate_config_value(:ssh_user)
|
460
|
+
ui.error("SSH User is compulsory parameter")
|
461
|
+
exit 1
|
462
|
+
end
|
463
|
+
unless locate_config_value(:ssh_password) or locate_config_value(:identity_file)
|
464
|
+
ui.error("Specify either SSH Key or SSH Password")
|
465
|
+
exit 1
|
466
|
+
end
|
467
|
+
|
468
|
+
server_def[:ssh_user] = locate_config_value(:ssh_user)
|
469
|
+
server_def[:ssh_password] = locate_config_value(:ssh_password)
|
470
|
+
server_def[:identity_file] = locate_config_value(:identity_file)
|
471
|
+
server_def[:identity_file_passphrase] = locate_config_value(:identity_file_passphrase)
|
472
|
+
end
|
311
473
|
server_def
|
312
474
|
end
|
313
475
|
end
|
314
476
|
end
|
315
477
|
end
|
478
|
+
|