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.
@@ -21,24 +21,45 @@ class Azure
21
21
  def initialize(connection)
22
22
  @connection=connection
23
23
  end
24
- def all
25
- storage_accounts = Array.new
26
- responseXML = @connection.query_azure('storageservices')
27
- servicesXML = responseXML.css('StorageServices StorageService')
28
- servicesXML.each do |serviceXML|
29
- storage_account = StorageAccount.new(@connection)
30
- storage_accounts << storage_account.parse(serviceXML)
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
- storage_accounts
39
+ @azure_storage_accounts
33
40
  end
34
- def exists(name)
35
- storageExists = false
36
- self.all.each do |storage|
37
- next unless storage.name == name
38
- storageExists = true
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[:storage_account]
78
- xml.Label Base64.encode64(params[:storage_account])
79
- xml.Description params[:storage_account_description] || 'Explicitly created storage service'
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[:service_location] || 'West US'
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 :azure_host_name,
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][:azure_host_name] = key },
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
- :azure_host_name => locate_config_value(:azure_host_name),
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, :azure_host_name])
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
- image_list = [
42
- ui.color('Name', :bold),
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
- if image.os == 'Linux'
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, :columns_across, 4)
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 File.expand_path('../azure_base', __FILE__)
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 :identity_file,
56
- :short => "-i IDENTITY_FILE",
57
- :long => "--identity-file IDENTITY_FILE",
58
- :description => "The SSH identity file used for authentication"
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 :no_host_key_verify,
90
- :long => "--no-host-key-verify",
91
- :description => "Disable host key verification",
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 => false
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 :storage_account,
110
+ option :azure_storage_account,
106
111
  :short => "-a NAME",
107
- :long => "--storage-account NAME",
108
- :description => "specifies the name for the hosted service"
109
-
110
- option :role_name,
111
- :short => "-R name",
112
- :long => "--role-name NAME",
113
- :description => "specifies the name for the virtual machine"
114
-
115
- option :host_name,
116
- :long => "--host-name NAME",
117
- :description => "specifies the host name for the virtual machine"
118
-
119
- option :service_location,
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 => "specify the Geographic location for the virtual machine and services"
123
-
124
- option :os_disk_name,
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 => "unique name for specifying os disk (optional)"
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 :source_image,
144
+ option :azure_source_image,
130
145
  :short => "-I IMAGE",
131
- :long => "--source-image IMAGE",
132
- :description => "disk image name to use to create virtual machine"
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 :role_size,
150
+ option :azure_vm_size,
135
151
  :short => "-z SIZE",
136
- :long => "--role-size SIZE",
137
- :description => "size of virtual machine (ExtraSmall, Small, Medium, Large, ExtraLarge)"
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(:hosted_service_name)
222
- config[:hosted_service_name] = [strip_non_ascii(locate_config_value(:role_name)), random_string].join
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(:storage_account)
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(:service_location) }
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[:storage_account] = [strip_non_ascii(locate_config_value(:role_name)), random_string].join.downcase
265
+ config[:azure_storage_account] = [strip_non_ascii(locate_config_value(:azure_vm_name)), random_string].join.downcase
231
266
  else
232
- config[:storage_account] = storage.name.to_s
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
- unless server && server.sshipaddress && server.sshport
240
- Chef::Log.fatal("server not created")
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
- bootstrap_for_node(server,fqdn,port).run
328
+ Chef::Config[:knife][:hints] ||= {}
329
+ Chef::Config[:knife][:hints]["azure"] ||= cloud_attributes
257
330
 
258
- puts "\n"
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[:no_host_key_verify] = config[:no_host_key_verify]
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
- :azure_host_name,
286
- :role_name,
287
- :host_name,
288
- :ssh_user,
289
- :ssh_password,
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
- :hosted_service_name => locate_config_value(:hosted_service_name),
299
- :storage_account => locate_config_value(:storage_account),
300
- :role_name => locate_config_value(:role_name),
301
- :host_name => locate_config_value(:host_name),
302
- :ssh_user => locate_config_value(:ssh_user),
303
- :ssh_password => locate_config_value(:ssh_password),
304
- :service_location => locate_config_value(:service_location),
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
+