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.
- 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
|
+
|