knife-azure 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+