knife-azure 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/Gemfile +22 -0
  2. data/Guardfile +8 -0
  3. data/LICENSE +201 -0
  4. data/Rakefile +51 -0
  5. data/knife-azure.gemspec +106 -0
  6. data/lib/azure/connection.rb +75 -0
  7. data/lib/azure/deploy.rb +114 -0
  8. data/lib/azure/disk.rb +62 -0
  9. data/lib/azure/host.rb +90 -0
  10. data/lib/azure/image.rb +58 -0
  11. data/lib/azure/rest.rb +97 -0
  12. data/lib/azure/role.rb +182 -0
  13. data/lib/azure/utility.rb +29 -0
  14. data/lib/chef/knife/azure_base.rb +102 -0
  15. data/lib/chef/knife/azure_image_list.rb +58 -0
  16. data/lib/chef/knife/azure_server_create.rb +283 -0
  17. data/lib/chef/knife/azure_server_delete.rb +103 -0
  18. data/lib/chef/knife/azure_server_describe.rb +85 -0
  19. data/lib/chef/knife/azure_server_list.rb +70 -0
  20. data/lib/knife-azure/version.rb +7 -0
  21. data/readme.rdoc +210 -0
  22. data/spec/functional/deploys_test.rb +39 -0
  23. data/spec/functional/host_test.rb +22 -0
  24. data/spec/functional/images_list_test.rb +44 -0
  25. data/spec/functional/role_test.rb +16 -0
  26. data/spec/integration/role_lifecycle_test.rb +60 -0
  27. data/spec/spec_helper.rb +41 -0
  28. data/spec/unit/assets/create_deployment.xml +37 -0
  29. data/spec/unit/assets/create_deployment_in_progress.xml +1 -0
  30. data/spec/unit/assets/create_host.xml +7 -0
  31. data/spec/unit/assets/create_role.xml +54 -0
  32. data/spec/unit/assets/list_deployments_for_service000.xml +126 -0
  33. data/spec/unit/assets/list_deployments_for_service001.xml +166 -0
  34. data/spec/unit/assets/list_deployments_for_service002.xml +1 -0
  35. data/spec/unit/assets/list_deployments_for_service003.xml +1 -0
  36. data/spec/unit/assets/list_disks.xml +1 -0
  37. data/spec/unit/assets/list_hosts.xml +1 -0
  38. data/spec/unit/assets/list_images.xml +1 -0
  39. data/spec/unit/assets/post_success.xml +6 -0
  40. data/spec/unit/deploys_list_spec.rb +55 -0
  41. data/spec/unit/disks_spec.rb +44 -0
  42. data/spec/unit/hosts_spec.rb +55 -0
  43. data/spec/unit/images_spec.rb +35 -0
  44. data/spec/unit/query_azure_mock.rb +69 -0
  45. data/spec/unit/roles_create_spec.rb +82 -0
  46. data/spec/unit/roles_list_spec.rb +32 -0
  47. metadata +240 -0
@@ -0,0 +1,283 @@
1
+ #
2
+ # Author:: Barry Davis (barryd@jetstreamsoftware.com)
3
+ # Author:: Adam Jacob (<adam@opscode.com>)
4
+ # Author:: Seth Chisamore (<schisamo@opscode.com>)
5
+ # Copyright:: Copyright (c) 2010-2011 Opscode, Inc.
6
+ # License:: Apache License, Version 2.0
7
+ #
8
+ # Licensed under the Apache License, Version 2.0 (the "License");
9
+ # you may not use this file except in compliance with the License.
10
+ # You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+ #
20
+
21
+ require File.expand_path('../azure_base', __FILE__)
22
+
23
+ class Chef
24
+ class Knife
25
+ class AzureServerCreate < Knife
26
+
27
+ include Knife::AzureBase
28
+
29
+ deps do
30
+ require 'readline'
31
+ require 'chef/json_compat'
32
+ require 'chef/knife/bootstrap'
33
+ Chef::Knife::Bootstrap.load_deps
34
+ end
35
+
36
+ banner "knife azure server create (options)"
37
+
38
+ attr_accessor :initial_sleep_delay
39
+
40
+ option :chef_node_name,
41
+ :short => "-N NAME",
42
+ :long => "--node-name NAME",
43
+ :description => "The Chef node name for your new node"
44
+
45
+ option :ssh_user,
46
+ :short => "-x USERNAME",
47
+ :long => "--ssh-user USERNAME",
48
+ :description => "The ssh username"
49
+
50
+ option :ssh_password,
51
+ :short => "-P PASSWORD",
52
+ :long => "--ssh-password PASSWORD",
53
+ :description => "The ssh password"
54
+
55
+ option :identity_file,
56
+ :short => "-i IDENTITY_FILE",
57
+ :long => "--identity-file IDENTITY_FILE",
58
+ :description => "The SSH identity file used for authentication"
59
+
60
+ option :prerelease,
61
+ :long => "--prerelease",
62
+ :description => "Install the pre-release chef gems"
63
+
64
+ option :bootstrap_version,
65
+ :long => "--bootstrap-version VERSION",
66
+ :description => "The version of Chef to install",
67
+ :proc => Proc.new { |v| Chef::Config[:knife][:bootstrap_version] = v }
68
+
69
+ option :distro,
70
+ :short => "-d DISTRO",
71
+ :long => "--distro DISTRO",
72
+ :description => "Bootstrap a distro using a template",
73
+ :proc => Proc.new { |d| Chef::Config[:knife][:distro] = d },
74
+ :default => "chef-full"
75
+
76
+ option :template_file,
77
+ :long => "--template-file TEMPLATE",
78
+ :description => "Full path to location of template to use",
79
+ :proc => Proc.new { |t| Chef::Config[:knife][:template_file] = t },
80
+ :default => false
81
+
82
+ option :run_list,
83
+ :short => "-r RUN_LIST",
84
+ :long => "--run-list RUN_LIST",
85
+ :description => "Comma separated list of roles/recipes to apply",
86
+ :proc => lambda { |o| o.split(/[\s,]+/) },
87
+ :default => []
88
+
89
+ option :no_host_key_verify,
90
+ :long => "--no-host-key-verify",
91
+ :description => "Disable host key verification",
92
+ :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 :role_name,
101
+ :short => "-R name",
102
+ :long => "--role-name NAME",
103
+ :description => "specifies the name for the virtual machine"
104
+
105
+ option :host_name,
106
+ :short => "-H NAME",
107
+ :long => "--host-name NAME",
108
+ :description => "specifies the host name for the virtual machine"
109
+
110
+ option :media_location_prefix,
111
+ :short => "-m PREFIX",
112
+ :long => "--media-location-prefix PREFIX",
113
+ :description => "user account name (used for constructing os disk media link)"
114
+
115
+ option :os_disk_name,
116
+ :short => "-o DISKNAME",
117
+ :long => "--os-disk-name DISKNAME",
118
+ :description => "unique name for specifying os disk (optional)"
119
+
120
+ option :source_image,
121
+ :short => "-I IMAGE",
122
+ :long => "--source-image IMAGE",
123
+ :description => "disk image name to use to create virtual machine"
124
+
125
+ option :role_size,
126
+ :short => "-z SIZE",
127
+ :long => "--role-size SIZE",
128
+ :description => "size of virtual machine (ExtraSmall, Small, Medium, Large, ExtraLarge)"
129
+
130
+ option :tcp_endpoints,
131
+ :short => "-t PORT_LIST",
132
+ :long => "--tcp-endpoints PORT_LIST",
133
+ :description => "Comma separated list of TCP local and public ports to open i.e. '80:80,433:5000'"
134
+
135
+ option :udp_endpoints,
136
+ :short => "-u PORT_LIST",
137
+ :long => "--udp-endpoints PORT_LIST",
138
+ :description => "Comma separated list of UDP local and public ports to open i.e. '80:80,433:5000'"
139
+
140
+
141
+ def tcp_test_ssh(fqdn, sshport)
142
+ tcp_socket = TCPSocket.new(fqdn, sshport)
143
+ readable = IO.select([tcp_socket], nil, nil, 5)
144
+ if readable
145
+ Chef::Log.debug("sshd accepting connections on #{fqdn}, banner is #{tcp_socket.gets}")
146
+ yield
147
+ true
148
+ else
149
+ false
150
+ end
151
+ rescue SocketError
152
+ sleep 2
153
+ false
154
+ rescue Errno::ETIMEDOUT
155
+ false
156
+ rescue Errno::EPERM
157
+ false
158
+ rescue Errno::ECONNREFUSED
159
+ sleep 2
160
+ false
161
+ # This happens on EC2 quite often
162
+ rescue Errno::EHOSTUNREACH
163
+ sleep 2
164
+ false
165
+ ensure
166
+ tcp_socket && tcp_socket.close
167
+ end
168
+
169
+ def parameter_test
170
+ details = Array.new
171
+ details << ui.color('name', :bold, :blue)
172
+ details << ui.color('Chef::Config', :bold, :blue)
173
+ details << ui.color('config', :bold, :blue)
174
+ details << ui.color('winner is', :bold, :blue)
175
+ [
176
+ :azure_subscription_id,
177
+ :azure_pem_file,
178
+ :azure_host_name,
179
+ :hosted_service_name,
180
+ :role_name,
181
+ :host_name,
182
+ :ssh_user,
183
+ :ssh_password,
184
+ :media_location_prefix,
185
+ :source_image,
186
+ :role_size
187
+ ].each do |key|
188
+ key = key.to_sym
189
+ details << key.to_s
190
+ details << Chef::Config[:knife][key].to_s
191
+ details << config[key].to_s
192
+ details << locate_config_value(key)
193
+ end
194
+ puts ui.list(details, :columns_across, 4)
195
+ end
196
+ def run
197
+ $stdout.sync = true
198
+
199
+ Chef::Log.info("validating...")
200
+ validate!
201
+
202
+ Chef::Log.info("creating...")
203
+ server = connection.deploys.create(create_server_def)
204
+
205
+ puts("\n")
206
+
207
+ unless server && server.sshipaddress && server.sshport
208
+ Chef::Log.fatal("server not created")
209
+ exit 1
210
+ end
211
+
212
+ fqdn = server.sshipaddress
213
+ port = server.sshport
214
+
215
+ print "\n#{ui.color("Waiting for sshd on #{fqdn}:#{port}", :magenta)}"
216
+
217
+ print(".") until tcp_test_ssh(fqdn,port) {
218
+ sleep @initial_sleep_delay ||= 10
219
+ puts("done")
220
+ }
221
+
222
+ sleep 15
223
+
224
+ bootstrap_for_node(server,fqdn,port).run
225
+
226
+ puts "\n"
227
+ end
228
+
229
+ def bootstrap_for_node(server,fqdn,port)
230
+ bootstrap = Chef::Knife::Bootstrap.new
231
+ bootstrap.name_args = [fqdn]
232
+ bootstrap.config[:run_list] = config[:run_list]
233
+ bootstrap.config[:ssh_user] = locate_config_value(:ssh_user)
234
+ bootstrap.config[:ssh_password] = locate_config_value(:ssh_password)
235
+ bootstrap.config[:ssh_port] = port
236
+ bootstrap.config[:identity_file] = locate_config_value(:identity_file)
237
+ bootstrap.config[:chef_node_name] = locate_config_value(:chef_node_name) || server.name
238
+ bootstrap.config[:prerelease] = locate_config_value(:prerelease)
239
+ bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version)
240
+ bootstrap.config[:distro] = locate_config_value(:distro)
241
+ bootstrap.config[:use_sudo] = true unless locate_config_value(:ssh_user) == 'root'
242
+ bootstrap.config[:template_file] = config[:template_file]
243
+ bootstrap.config[:environment] = locate_config_value(:environment)
244
+ # may be needed for vpc_mode
245
+ bootstrap.config[:no_host_key_verify] = config[:no_host_key_verify]
246
+ bootstrap
247
+ end
248
+
249
+ def validate!
250
+ super([
251
+ :azure_subscription_id,
252
+ :azure_pem_file,
253
+ :azure_host_name,
254
+ :hosted_service_name,
255
+ :role_name,
256
+ :host_name,
257
+ :ssh_user,
258
+ :ssh_password,
259
+ :media_location_prefix,
260
+ :source_image,
261
+ :role_size
262
+ ])
263
+ end
264
+
265
+ def create_server_def
266
+ server_def = {
267
+ :hosted_service_name => locate_config_value(:hosted_service_name),
268
+ :role_name => locate_config_value(:role_name),
269
+ :host_name => locate_config_value(:host_name),
270
+ :ssh_user => locate_config_value(:ssh_user),
271
+ :ssh_password => locate_config_value(:ssh_password),
272
+ :media_location_prefix => locate_config_value(:media_location_prefix),
273
+ :os_disk_name => locate_config_value(:os_disk_name),
274
+ :source_image => locate_config_value(:source_image),
275
+ :role_size => locate_config_value(:role_size),
276
+ :tcp_endpoints => locate_config_value(:tcp_endpoints),
277
+ :udp_endpoints => locate_config_value(:udp_endpoints)
278
+ }
279
+ server_def
280
+ end
281
+ end
282
+ end
283
+ end
@@ -0,0 +1,103 @@
1
+ #
2
+ # Author:: Barry Davis (barryd@jetstreamsoftware.com)
3
+ # Author:: Adam Jacob (<adam@opscode.com>)
4
+ # Author:: Seth Chisamore (<schisamo@opscode.com>)
5
+ # Copyright:: Copyright (c) 2009-2011 Opscode, Inc.
6
+ # License:: Apache License, Version 2.0
7
+ #
8
+ # Licensed under the Apache License, Version 2.0 (the "License");
9
+ # you may not use this file except in compliance with the License.
10
+ # You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+ #
20
+
21
+ require File.expand_path('../azure_base', __FILE__)
22
+
23
+ # These two are needed for the '--purge' deletion case
24
+ require 'chef/node'
25
+ require 'chef/api_client'
26
+
27
+ class Chef
28
+ class Knife
29
+ class AzureServerDelete < Knife
30
+
31
+ include Knife::AzureBase
32
+
33
+ banner "knife azure server delete SERVER [SERVER] (options)"
34
+
35
+ option :purge,
36
+ :short => "-P",
37
+ :long => "--purge",
38
+ :boolean => true,
39
+ :default => false,
40
+ :description => "Destroy corresponding node and client on the Chef Server, in addition to destroying the EC2 node itself. Assumes node and client have the same name as the server (if not, add the '--node-name' option)."
41
+
42
+ option :chef_node_name,
43
+ :short => "-N NAME",
44
+ :long => "--node-name NAME",
45
+ :description => "The name of the node and client to delete, if it differs from the server name. Only has meaning when used with the '--purge' option."
46
+
47
+ # Extracted from Chef::Knife.delete_object, because it has a
48
+ # confirmation step built in... By specifying the '--purge'
49
+ # flag (and also explicitly confirming the server destruction!)
50
+ # the user is already making their intent known. It is not
51
+ # necessary to make them confirm two more times.
52
+ def destroy_item(klass, name, type_name)
53
+ begin
54
+ object = klass.load(name)
55
+ object.destroy
56
+ ui.warn("Deleted #{type_name} #{name}")
57
+ rescue Net::HTTPServerException
58
+ ui.warn("Could not find a #{type_name} named #{name} to delete!")
59
+ end
60
+ end
61
+
62
+ def run
63
+
64
+ validate!
65
+
66
+ @name_args.each do |name|
67
+
68
+ begin
69
+ server = connection.roles.find(name)
70
+
71
+ puts "\n"
72
+ msg_pair('Service', server.hostedservicename)
73
+ msg_pair('Deployment', server.deployname)
74
+ msg_pair('Role', server.name)
75
+ msg_pair('Size', server.size)
76
+ msg_pair('SSH Ip Address', server.sshipaddress)
77
+ msg_pair('SSH Port', server.sshport)
78
+
79
+ puts "\n"
80
+ confirm("Do you really want to delete this server")
81
+
82
+ connection.roles.delete(name)
83
+
84
+ puts "\n"
85
+ ui.warn("Deleted server #{server.name}")
86
+
87
+ if config[:purge]
88
+ thing_to_delete = config[:chef_node_name] || name
89
+ destroy_item(Chef::Node, thing_to_delete, "node")
90
+ destroy_item(Chef::ApiClient, thing_to_delete, "client")
91
+ else
92
+ ui.warn("Corresponding node and client for the #{name} server were not deleted and remain registered with the Chef Server")
93
+ end
94
+
95
+ rescue NoMethodError
96
+ ui.error("Could not locate server '#{name}'. Please verify it was provisioned.")
97
+ end
98
+ end
99
+ end
100
+
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,85 @@
1
+ #
2
+ # Author:: Barry Davis (barryd@jetstreamsoftware.com)
3
+ # Author:: Seth Chisamore (<schisamo@opscode.com>)
4
+ # Author:: Adam Jacob (<adam@opscode.com>)
5
+ # Copyright:: Copyright (c) 2010-2011 Opscode, Inc.
6
+ # License:: Apache License, Version 2.0
7
+ #
8
+ # Licensed under the Apache License, Version 2.0 (the "License");
9
+ # you may not use this file except in compliance with the License.
10
+ # You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+ #
20
+
21
+ require File.expand_path('../azure_base', __FILE__)
22
+
23
+ class Chef
24
+ class Knife
25
+ class AzureServerDescribe < Knife
26
+
27
+ include Knife::AzureBase
28
+
29
+ banner "knife azure server describe ROLE [ROLE]"
30
+
31
+ def run
32
+ $stdout.sync = true
33
+
34
+ validate!
35
+
36
+ @name_args.each do |name|
37
+ role = connection.roles.find name
38
+ puts ''
39
+ if (role)
40
+ details = Array.new
41
+ details << ui.color('Role name', :bold, :blue)
42
+ details << role.name
43
+ details << ui.color('Status', :bold, :blue)
44
+ details << role.status
45
+ details << ui.color('Size', :bold, :blue)
46
+ details << role.size
47
+ details << ui.color('Hosted service name', :bold, :blue)
48
+ details << role.hostedservicename
49
+ details << ui.color('Deployment name', :bold, :blue)
50
+ details << role.deployname
51
+ details << ui.color('Host name', :bold, :blue)
52
+ details << role.hostname
53
+ details << ui.color('SSH', :bold, :blue)
54
+ details << role.sshipaddress + ':' + role.sshport
55
+ puts ui.list(details, :columns_across, 2)
56
+ if role.tcpports.length > 0 || role.udpports.length > 0
57
+ details.clear
58
+ details << ui.color('Ports open', :bold, :blue)
59
+ details << ui.color('Local port', :bold, :blue)
60
+ details << ui.color('IP', :bold, :blue)
61
+ details << ui.color('Public port', :bold, :blue)
62
+ if role.tcpports.length > 0
63
+ role.tcpports.each do |port|
64
+ details << 'tcp'
65
+ details << port['LocalPort']
66
+ details << port['Vip']
67
+ details << port['PublicPort']
68
+ end
69
+ end
70
+ if role.udpports.length > 0
71
+ role.udpports.each do |port|
72
+ details << 'udp'
73
+ details << port['LocalPort']
74
+ details << port['Vip']
75
+ details << port['PublicPort']
76
+ end
77
+ end
78
+ puts ui.list(details, :columns_across, 4)
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,70 @@
1
+ #
2
+ # Author:: Barry Davis (barryd@jetstreamsoftware.com)
3
+ # Author:: Seth Chisamore (<schisamo@opscode.com>)
4
+ # Author:: Adam Jacob (<adam@opscode.com>)
5
+ # Copyright:: Copyright (c) 2010-2011 Opscode, Inc.
6
+ # License:: Apache License, Version 2.0
7
+ #
8
+ # Licensed under the Apache License, Version 2.0 (the "License");
9
+ # you may not use this file except in compliance with the License.
10
+ # You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+ #
20
+
21
+ require File.expand_path('../azure_base', __FILE__)
22
+
23
+ class Chef
24
+ class Knife
25
+ class AzureServerList < Knife
26
+
27
+ include Knife::AzureBase
28
+
29
+ banner "knife azure server list (options)"
30
+
31
+ def run
32
+ $stdout.sync = true
33
+
34
+ validate!
35
+
36
+ server_list = [
37
+ ui.color('Status', :bold),
38
+ ui.color('Service', :bold),
39
+ ui.color('Deployment', :bold),
40
+ ui.color('Role', :bold),
41
+ ui.color('Host', :bold),
42
+ ui.color('SSH IP', :bold),
43
+ ui.color('SSH Port', :bold)
44
+ ]
45
+ items = connection.roles.all
46
+ items.each do |server|
47
+ server_list << begin
48
+ state = server.status.to_s.downcase
49
+ case state
50
+ when 'shutting-down','terminated','stopping','stopped'
51
+ ui.color(state, :red)
52
+ when 'pending'
53
+ ui.color(state, :yellow)
54
+ else
55
+ ui.color('ready', :green)
56
+ end
57
+ end
58
+ server_list << server.hostedservicename.to_s
59
+ server_list << server.deployname.to_s
60
+ server_list << server.name.to_s
61
+ server_list << server.hostname.to_s
62
+ server_list << server.sshipaddress.to_s
63
+ server_list << server.sshport.to_s
64
+ end
65
+ puts ''
66
+ puts ui.list(server_list, :columns_across, 7)
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,7 @@
1
+ module Knife
2
+ module Azure
3
+ VERSION = "1.0.0"
4
+ MAJOR, MINOR, TINY = VERSION.split('.')
5
+ end
6
+ end
7
+