waz-cmd 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/LICENSE +31 -0
- data/README.md +28 -0
- data/Rakefile +2 -0
- data/bin/waz +492 -0
- data/lib/waz-cmd/templates/get_keys.erb +2 -0
- data/lib/waz-cmd/templates/list_affinity_groups.erb +3 -0
- data/lib/waz-cmd/templates/list_applications.erb +3 -0
- data/lib/waz-cmd/templates/list_locations.erb +3 -0
- data/lib/waz-cmd/templates/show_affinity_group.erb +3 -0
- data/lib/waz-cmd/templates/show_application.erb +11 -0
- data/lib/waz-cmd/templates/show_configuration.erb +6 -0
- data/lib/waz-cmd/templates/show_deployment.erb +33 -0
- data/lib/waz-cmd/templates/show_history.erb +21 -0
- data/lib/waz-cmd/templates/show_storage.erb +8 -0
- data/lib/waz-cmd/version.rb +5 -0
- data/lib/waz-cmd.rb +105 -0
- data/waz-cmd.gemspec +27 -0
- metadata +143 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
Microsoft Public License (Ms-PL)
|
2
|
+
|
3
|
+
This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software.
|
4
|
+
|
5
|
+
1. Definitions
|
6
|
+
|
7
|
+
The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law.
|
8
|
+
|
9
|
+
A "contribution" is the original software, or any additions or changes to the software.
|
10
|
+
|
11
|
+
A "contributor" is any person that distributes its contribution under this license.
|
12
|
+
|
13
|
+
"Licensed patents" are a contributor's patent claims that read directly on its contribution.
|
14
|
+
|
15
|
+
2. Grant of Rights
|
16
|
+
|
17
|
+
(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
|
18
|
+
|
19
|
+
(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software.
|
20
|
+
|
21
|
+
3. Conditions and Limitations
|
22
|
+
|
23
|
+
(A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
|
24
|
+
|
25
|
+
(B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically.
|
26
|
+
|
27
|
+
(C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software.
|
28
|
+
|
29
|
+
(D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license.
|
30
|
+
|
31
|
+
(E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement.
|
data/README.md
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
Waz-Cmd
|
2
|
+
=======
|
3
|
+
|
4
|
+
Installation
|
5
|
+
------------
|
6
|
+
|
7
|
+
To install, just `gem install waz-cmd`
|
8
|
+
|
9
|
+
Basic usage
|
10
|
+
-----------
|
11
|
+
|
12
|
+
c:\>waz generate certificates
|
13
|
+
Writing certificate to 'c:\users\smarx/.waz/cert.pem'
|
14
|
+
Writing certificate in .cer form to 'c:\users\smarx/.waz/cert.cer'
|
15
|
+
Writing key to 'c:\users\smarx/.waz/key.pem'
|
16
|
+
|
17
|
+
To use the new certificate, upload 'c:\users\smarx/.waz/cert.cer' as a management certificate in the Windows Azure portal (https://windows.azure.com)
|
18
|
+
|
19
|
+
c:\>waz set subscriptionId XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
|
20
|
+
|
21
|
+
c:\>waz deploy blobedit staging c:\repositories\smarxrole\packages\ExtraSmall.cspkg c:\repositories\smarxrole\packages\ServiceConfiguration.blobedit.cscfg
|
22
|
+
Waiting for operation to complete...
|
23
|
+
Operation succeeded (200)
|
24
|
+
|
25
|
+
Documentation
|
26
|
+
-------------
|
27
|
+
|
28
|
+
Just run `waz help` for full documentation.
|
data/Rakefile
ADDED
data/bin/waz
ADDED
@@ -0,0 +1,492 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'waz-cmd'
|
3
|
+
require 'commander/import'
|
4
|
+
require 'waz-blobs'
|
5
|
+
require 'uri'
|
6
|
+
require 'uuidtools'
|
7
|
+
require 'fileutils'
|
8
|
+
|
9
|
+
include Waz::Cmd
|
10
|
+
|
11
|
+
program :name, 'waz'
|
12
|
+
program :version, '1.0'
|
13
|
+
program :description, 'Windows Azure Command-line Tool'
|
14
|
+
program :help, 'Author', 'Steve Marx (http://blog.smarx.com, Steve.Marx@microsoft.com, @smarx)'
|
15
|
+
|
16
|
+
global_option '--pem FILE', 'Name of the .pem file containing the management certificate, deafault $HOME/.waz/cert.pem'
|
17
|
+
global_option '--key FILE', 'Name of the .pem file containing the private key, default $HOME/.waz/key.pem'
|
18
|
+
global_option '--subscriptionId STRING', 'Subscription ID (a GUID) to operate on'
|
19
|
+
global_option '--expand', 'Expand details where possible (like individual role instances). Default is false'
|
20
|
+
|
21
|
+
begin
|
22
|
+
$config = YAML::load(File.read("#{ENV['HOME']}/.wazrc"))
|
23
|
+
rescue
|
24
|
+
$config = {}
|
25
|
+
end
|
26
|
+
|
27
|
+
def template(name)
|
28
|
+
File.join(File.dirname(__FILE__), '../lib/waz-cmd/templates', name)
|
29
|
+
end
|
30
|
+
|
31
|
+
command 'set' do |c|
|
32
|
+
c.syntax = 'waz set <variable> <value>'
|
33
|
+
c.description = 'Set a default value for a variable (like "subscriptionId") in $HOME/.wazrc'
|
34
|
+
c.action do |args, options|
|
35
|
+
$config[args[0]] = args[1]
|
36
|
+
File.open("#{ENV['HOME']}/.wazrc", 'w') do |f|
|
37
|
+
f.write($config.to_yaml)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
command 'get' do |c|
|
43
|
+
c.syntax = 'waz get <variable>'
|
44
|
+
c.description = 'Get the default value for a variable (like "subscriptionId") in $HOME/.wazrc'
|
45
|
+
c.action do |args, options|
|
46
|
+
puts $config[args[0]]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
command 'get-all' do |c|
|
51
|
+
c.syntax = 'waz get-all'
|
52
|
+
c.description = 'Get the default values for all variables (like "subscriptionId") in $HOME/.wazrc'
|
53
|
+
c.action do |args, options|
|
54
|
+
$config.each do |k,v|
|
55
|
+
puts "#{k} = #{v}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
command 'generate certificate' do |c|
|
61
|
+
c.syntax = 'waz generate certificate [options]'
|
62
|
+
c.description = 'Generate a public/private key pair to use with the Windows Azure Service Management API'
|
63
|
+
c.option '--cer FILE', 'Name of the .cer file to write the public certificate, default $HOME/.waz/cert.cer'
|
64
|
+
c.action do |args, options|
|
65
|
+
set_defaults options
|
66
|
+
rsa = OpenSSL::PKey::RSA.new 1024
|
67
|
+
cert = OpenSSL::X509::Certificate.new
|
68
|
+
cert.version = 3
|
69
|
+
cert.serial = 0
|
70
|
+
name = OpenSSL::X509::Name.new([['CN', 'waz']])
|
71
|
+
cert.subject = name
|
72
|
+
cert.issuer = name
|
73
|
+
cert.not_before = 0
|
74
|
+
cert.not_after = 2147483647 #LONG_MAX
|
75
|
+
cert.public_key = rsa.public_key
|
76
|
+
|
77
|
+
ef = OpenSSL::X509::ExtensionFactory.new(nil,cert)
|
78
|
+
ef.issuer_certificate = cert
|
79
|
+
cert.extensions = [
|
80
|
+
ef.create_extension("basicConstraints","CA:FALSE"),
|
81
|
+
ef.create_extension("keyUsage", "keyEncipherment"),
|
82
|
+
ef.create_extension("subjectKeyIdentifier", "hash"),
|
83
|
+
ef.create_extension("extendedKeyUsage", "serverAuth"),
|
84
|
+
]
|
85
|
+
aki = ef.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always")
|
86
|
+
cert.add_extension(aki)
|
87
|
+
cert.sign(rsa, OpenSSL::Digest::SHA1.new)
|
88
|
+
|
89
|
+
puts "Writing certificate to '#{options.pem}'"
|
90
|
+
FileUtils.makedirs(File.dirname(options.pem))
|
91
|
+
File.open(options.pem, "w") {|f| f.puts cert}
|
92
|
+
puts "Writing certificate in .cer form to '#{options.cer}'"
|
93
|
+
FileUtils.makedirs(File.dirname(options.cer))
|
94
|
+
File.open(options.cer, "wb") {|f| f.write cert.to_der}
|
95
|
+
puts "Writing key to '#{options.key}'"
|
96
|
+
FileUtils.makedirs(File.dirname(options.key))
|
97
|
+
File.open(options.key, "w") {|f| f.puts rsa}
|
98
|
+
puts
|
99
|
+
puts "To use the new certificate, upload '#{options.cer}' as a management certificate in the Windows Azure portal (https://windows.azure.com)"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
command 'list applications' do |c|
|
104
|
+
c.syntax = 'waz list applications [options]'
|
105
|
+
c.description = 'List all Windows Azure applications under the given subscription'
|
106
|
+
c.action do |args, options|
|
107
|
+
set_defaults options
|
108
|
+
raise 'Subscription ID is required' unless options.subscriptionId
|
109
|
+
apps = arrayize(make_call("services/hostedservices", :get, options)['HostedServices']['HostedService'])
|
110
|
+
puts Tilt.new(template('list_applications.erb'), 1, :trim => '%').render(nil, :apps => apps)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
command 'list storage' do |c|
|
115
|
+
c.syntax = 'waz list storage'
|
116
|
+
c.description = 'List all Windows Azure storage accounts under the given subscription'
|
117
|
+
c.action do |args, options|
|
118
|
+
set_defaults options
|
119
|
+
raise 'Subscription ID is required' unless options.subscriptionId
|
120
|
+
apps = arrayize(make_call("services/storageservices", :get, options)['StorageServices']['StorageService'])
|
121
|
+
puts Tilt.new(template('list_applications.erb'), 1, :trim => '%').render(nil, :apps => apps)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
command 'show application' do |c|
|
126
|
+
c.syntax = 'waz show application <name> [options]'
|
127
|
+
c.description = 'Show the details of an application'
|
128
|
+
c.action do |args, options|
|
129
|
+
set_defaults options
|
130
|
+
raise 'Subscription ID is required' unless options.subscriptionId
|
131
|
+
raise 'Application name is required' unless args.first
|
132
|
+
app = make_call("services/hostedservices/#{args.first}?embed-detail=true", :get, options)['HostedService']
|
133
|
+
puts Tilt.new(template('show_application.erb'), 1, :trim => '%').render(nil, :app => app, :expand => options.expand)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
command 'show storage' do |c|
|
138
|
+
c.syntax = 'waz show storage <name> [options]'
|
139
|
+
c.description = 'Show the details of a storage account'
|
140
|
+
c.action do |args, options|
|
141
|
+
set_defaults options
|
142
|
+
raise 'Subscription ID is required' unless options.subscriptionId
|
143
|
+
raise 'Storage account name is required' unless args.first
|
144
|
+
account = make_call("services/storageservices/#{args.first}", :get, options)['StorageService']
|
145
|
+
puts Tilt.new(template('show_storage.erb'), 1, :trim => '%').render(nil, :account => account)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
command 'delete storage' do |c|
|
150
|
+
c.syntax = 'was delete storage <name> [options]'
|
151
|
+
c.description = 'Delete a storage account'
|
152
|
+
c.action do |args, options|
|
153
|
+
set_defaults options
|
154
|
+
raise 'Subscription ID is required' unless options.subscriptionId
|
155
|
+
raise 'Storage account name is required' unless args.first
|
156
|
+
make_call("services/storageservices/#{args.first}", :delete, options) do |response|
|
157
|
+
wait_for_completion options, response.headers[:x_ms_request_id]
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
command 'get keys' do |c|
|
163
|
+
c.syntax = 'waz get keys <name> [options]'
|
164
|
+
c.description = 'Get the keys for a storage account'
|
165
|
+
c.action do |args, options|
|
166
|
+
set_defaults options
|
167
|
+
raise 'Subscription ID is required' unless options.subscriptionId
|
168
|
+
raise 'Storage account name is required' unless args.first
|
169
|
+
keys = make_call("services/storageservices/#{args.first}/keys", :get, options)['StorageService']['StorageServiceKeys']
|
170
|
+
puts Tilt.new(template('get_keys.erb'), 1, :trim => '%').render(nil, :keys => keys)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
command 'connection string' do |c|
|
175
|
+
c.syntax = 'waz get connection string <name> [options]'
|
176
|
+
c.description = 'Get a connection string for a storage account'
|
177
|
+
c.option '--http', 'Use HTTP in the connection string instead of (the default) HTTPS'
|
178
|
+
c.action do |args, options|
|
179
|
+
set_defaults options
|
180
|
+
raise 'Subscription ID is required' unless options.subscriptionId
|
181
|
+
raise 'Storage account name is required' unless args.first
|
182
|
+
keys = make_call("services/storageservices/#{args.first}/keys", :get, options)['StorageService']['StorageServiceKeys']
|
183
|
+
puts "DefaultEndpointsProtocol=#{options.http ? 'http' : 'https'};AccountName=#{args.first};AccountKey=#{keys['Primary']}"
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
alias_command 'cs', 'connection string'
|
188
|
+
|
189
|
+
command 'create storage' do |c|
|
190
|
+
c.syntax = 'waz create storage <name> <location|affinity group> [options]'
|
191
|
+
c.description = 'Create a storage account'
|
192
|
+
c.action do |args, options|
|
193
|
+
set_defaults options
|
194
|
+
raise 'Subscription ID is required' unless options.subscriptionId
|
195
|
+
raise 'Storage account name is required' unless args.first
|
196
|
+
raise 'Location or affinity group is required' unless args[1]
|
197
|
+
make_call("services/storageservices", :post, options,
|
198
|
+
Nokogiri::XML::Builder.new(:encoding => 'utf-8') { |xml|
|
199
|
+
xml.CreateStorageServiceInput('xmlns' => 'http://schemas.microsoft.com/windowsazure') {
|
200
|
+
xml.ServiceName args[0]
|
201
|
+
xml.Description args[0]
|
202
|
+
xml.Label Base64.encode64(args[0]).rstrip
|
203
|
+
if args[1].length > 20
|
204
|
+
xml.AffinityGroup args[1]
|
205
|
+
else
|
206
|
+
xml.Location args[1]
|
207
|
+
end
|
208
|
+
}
|
209
|
+
}.to_xml) do |response|
|
210
|
+
wait_for_completion options, response.headers[:x_ms_request_id]
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
command 'create application' do |c|
|
216
|
+
c.syntax = 'waz create application <name> <location|affinity group> [options]'
|
217
|
+
c.description = 'Create a storage account'
|
218
|
+
c.action do |args, options|
|
219
|
+
set_defaults options
|
220
|
+
raise 'Subscription ID is required' unless options.subscriptionId
|
221
|
+
raise 'Application name is required' unless args.first
|
222
|
+
raise 'Location or affinity group is required' unless args[1]
|
223
|
+
make_call("services/hostedservices", :post, options,
|
224
|
+
Nokogiri::XML::Builder.new(:encoding => 'utf-8') { |xml|
|
225
|
+
xml.CreateHostedService(:xmlns => 'http://schemas.microsoft.com/windowsazure') {
|
226
|
+
xml.ServiceName args[0]
|
227
|
+
xml.Label Base64.encode64(args[0]).rstrip
|
228
|
+
if args[1].length > 20
|
229
|
+
xml.AffinityGroup args[1]
|
230
|
+
else
|
231
|
+
xml.Location args[1]
|
232
|
+
end
|
233
|
+
}
|
234
|
+
}.to_xml) do |response|
|
235
|
+
wait_for_completion options, response.headers[:x_ms_request_id]
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
command 'delete application' do |c|
|
241
|
+
c.syntax = 'waz delete application <name> [options]'
|
242
|
+
c.description = 'Delete an application'
|
243
|
+
c.action do |args, options|
|
244
|
+
set_defaults options
|
245
|
+
raise 'Subscription ID is required' unless options.subscriptionId
|
246
|
+
raise 'Application name is required' unless args.first
|
247
|
+
make_call("services/hostedservices/#{args.first}", :delete, options) do |response|
|
248
|
+
wait_for_completion options, response.headers[:x_ms_request_id]
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
command 'list locations' do |c|
|
254
|
+
c.syntax = 'waz list locations [options]'
|
255
|
+
c.description = 'List data center locations'
|
256
|
+
c.action do |args, options|
|
257
|
+
set_defaults options
|
258
|
+
raise 'Subscription ID is required' unless options.subscriptionId
|
259
|
+
locations = arrayize(make_call("locations", :get, options)['Locations']['Location'])
|
260
|
+
puts Tilt.new(template('list_locations.erb'), 1, :trim => '%').render(nil, :locations => locations)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
command 'list affinity groups' do |c|
|
265
|
+
c.syntax = 'waz list affinity groups [options]'
|
266
|
+
c.description = 'List affinity groups'
|
267
|
+
c.action do |args, options|
|
268
|
+
set_defaults options
|
269
|
+
raise 'Subscription ID is required' unless options.subscriptionId
|
270
|
+
groups = arrayize(make_call("affinitygroups", :get, options)['AffinityGroups']['AffinityGroup'])
|
271
|
+
puts Tilt.new(template('list_affinity_groups.erb'), 1, :trim => '%').render(nil, :groups => groups)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
command 'create affinity group' do |c|
|
276
|
+
c.syntax = 'waz create affinity group <name> <location> [options]'
|
277
|
+
c.description = 'Create an affinity group'
|
278
|
+
c.action do |args, options|
|
279
|
+
set_defaults options
|
280
|
+
raise 'Subscription ID is required' unless options.subscriptionId
|
281
|
+
raise 'Affinity group name is required' unless args.first
|
282
|
+
raise 'Location is required' unless args[1]
|
283
|
+
make_call("affinitygroups", :post, options,
|
284
|
+
Nokogiri::XML::Builder.new(:encoding => 'utf-8') { |xml|
|
285
|
+
xml.CreateAffinityGroup(:xmlns => 'http://schemas.microsoft.com/windowsazure') {
|
286
|
+
xml.Name args[0]
|
287
|
+
xml.Label Base64.encode64(args[0]).rstrip
|
288
|
+
xml.Location args[1]
|
289
|
+
}
|
290
|
+
}.to_xml) do |response|
|
291
|
+
wait_for_completion options, response.headers[:x_ms_request_id]
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
command 'show affinity group' do |c|
|
297
|
+
c.syntax = 'waz show affinity group <name> [options]'
|
298
|
+
c.description = 'Show the details of an affinity group'
|
299
|
+
c.action do |args, options|
|
300
|
+
set_defaults options
|
301
|
+
raise 'Subscription ID is required' unless options.subscriptionId
|
302
|
+
raise 'Affinity group name is required' unless args.first
|
303
|
+
group = make_call("affinitygroups/#{args[0]}", :get, options)['AffinityGroup']
|
304
|
+
puts Tilt.new(template('show_affinity_group.erb'), 1, :trim => '%').render(nil, :group => group)
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
command 'delete affinity group' do |c|
|
309
|
+
c.syntax = 'waz delete affinity group <name> [options]'
|
310
|
+
c.description = 'Delete an affinity group'
|
311
|
+
c.action do |args, options|
|
312
|
+
set_defaults options
|
313
|
+
raise 'Subscription ID is required' unless options.subscriptionId
|
314
|
+
raise 'Affinity group name is required' unless args.first
|
315
|
+
make_call("affinitygroups/#{args[0]}", :delete, options) do |response|
|
316
|
+
wait_for_completion options, response.headers[:x_ms_request_id]
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
command 'show deployment' do |c|
|
322
|
+
c.syntax = 'waz show deployment <application> <staging|production> [options]'
|
323
|
+
c.description = 'Show the details of a deployment'
|
324
|
+
c.action do |args, options|
|
325
|
+
set_defaults options
|
326
|
+
raise 'Subscription ID is required' unless options.subscriptionId
|
327
|
+
raise 'Application name is required' unless args.first
|
328
|
+
raise 'Deployment slot is required' unless args[1]
|
329
|
+
deployment = make_call("services/hostedservices/#{args[0]}/deploymentslots/#{args[1]}", :get, options)['Deployment']
|
330
|
+
puts Tilt.new(template('show_deployment.erb'), 1, :trim => '%').render(nil, :expand => options.expand, :deployment => deployment)
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
command 'show history' do |c|
|
335
|
+
c.syntax = 'waz show history <application> <deployment-name> [options]'
|
336
|
+
c.description = 'Show the history of operations on a deployment. Note that deployment name is the GUID of the deployment (find it with something like "waz show deployment <application> production")'
|
337
|
+
c.option '--startTime STRING', 'Earliest time to retrieve history, in a form like YYYY-MM-DD or YYYY-MM-DDTHH:MM:SSZ (defaults to yesterday)'
|
338
|
+
c.option '--endTime STRING', 'Latest time to retrieve history, in a form like YYYY-MM-DD or YYYY-MM-DDTHH:MM:SSZ (defaults to infinity)'
|
339
|
+
c.action do |args, options|
|
340
|
+
set_defaults options
|
341
|
+
options.default :startTime => (Time.now-24*60*60).strftime("%Y-%m-%d"), :endTime => Time.at(2147483647).strftime("%Y-%m-%d") #LONG_MAX
|
342
|
+
raise 'Subscription ID is required' unless options.subscriptionId
|
343
|
+
raise 'Application name is required' unless args.first
|
344
|
+
raise 'Deployment name is required' unless args[1]
|
345
|
+
history = make_call("operations?StartTime=#{options.startTime}&EndTime=#{options.endTime}&ObjectIdFilter=/#{options.subscriptionId}/services/hostedservices/#{args[0]}/deployments/#{args[1]}", :get, options)['SubscriptionOperationCollection']['SubscriptionOperations']
|
346
|
+
puts Tilt.new(template('show_history.erb'), 1, :trim => '%').render(nil, :history => history)
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
restar_instance 'boot'
|
351
|
+
restar_instance 'image'
|
352
|
+
|
353
|
+
command 'swap' do |c|
|
354
|
+
c.syntax = 'waz swap <application> [options]'
|
355
|
+
c.description = 'Perform a VIP swap between staging and production of the specified application'
|
356
|
+
c.action do |args, options|
|
357
|
+
set_defaults options
|
358
|
+
raise 'Subscription ID is required' unless options.subscriptionId
|
359
|
+
raise 'Application name is required' unless args[0]
|
360
|
+
deployments = arrayize(make_call("services/hostedservices/#{args[0]}?embed-detail=true", :get, options)['HostedService']['Deployments']['Deployment'])
|
361
|
+
prodidx = deployments.index{|d| d['DeploymentSlot'] == 'Production'}
|
362
|
+
stageidx = deployments.index{|d| d['DeploymentSlot'] == 'Staging'}
|
363
|
+
raise 'No staging deployment to swap.' unless stageidx
|
364
|
+
production = prodidx ? deployments[prodidx]['Name'] : nil
|
365
|
+
staging = stageidx ? deployments[stageidx]['Name'] : nil
|
366
|
+
make_call("services/hostedservices/#{args[0]}", :post, options,
|
367
|
+
Nokogiri::XML::Builder.new(:encoding => 'utf-8') { |xml|
|
368
|
+
xml.Swap('xmlns' => 'http://schemas.microsoft.com/windowsazure') {
|
369
|
+
if production
|
370
|
+
xml.Production production
|
371
|
+
end
|
372
|
+
xml.SourceDeployment staging
|
373
|
+
}
|
374
|
+
}.to_xml) do |response|
|
375
|
+
wait_for_completion options, response.headers[:x_ms_request_id]
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
startstop('start', 'Running')
|
381
|
+
startstop('stop', 'Suspended')
|
382
|
+
|
383
|
+
command 'delete deployment' do |c|
|
384
|
+
c.syntax = 'waz delete deployment <application> <production|staging> [options]'
|
385
|
+
c.description = 'Delete a deployment'
|
386
|
+
c.action do |args, options|
|
387
|
+
set_defaults options
|
388
|
+
raise 'Subscription ID is required' unless options.subscriptionId
|
389
|
+
raise 'Application name is required' unless args[0]
|
390
|
+
raise 'Deployment slot is required' unless args[1]
|
391
|
+
|
392
|
+
make_call("services/hostedservices/#{args[0]}/deploymentslots/#{args[1]}", :delete, options) do |response|
|
393
|
+
wait_for_completion options, response.headers[:x_ms_request_id]
|
394
|
+
end
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
command 'show configuration' do |c|
|
399
|
+
c.syntax = 'waz show configuration <application> <production|staging> [options]'
|
400
|
+
c.description = 'Show deployment configuration'
|
401
|
+
c.action do |args, options|
|
402
|
+
set_defaults options
|
403
|
+
raise 'Subscription ID is required' unless options.subscriptionId
|
404
|
+
raise 'Application name is required' unless args[0]
|
405
|
+
raise 'Deployment slot is required' unless args[1]
|
406
|
+
|
407
|
+
roles = arrayize(Crack::XML.parse(Iconv.conv('utf-16', 'utf-8', Base64.decode64(make_call("services/hostedservices/#{args[0]}/deploymentslots/#{args[1]}", :get, options)['Deployment']['Configuration'])))['ServiceConfiguration']['Role'])
|
408
|
+
|
409
|
+
puts Tilt.new(template('show_configuration.erb'), 1, :trim => '%').render(nil, :roles => roles)
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
command 'configure' do |c|
|
414
|
+
c.syntax = 'waz configure <application> <production|staging> <role> <setting-name> <setting-value> [options]'
|
415
|
+
c.description = 'Change a configuration setting for a role of a deployment'
|
416
|
+
c.action do |args, options|
|
417
|
+
set_defaults options
|
418
|
+
raise 'Subscription ID is required' unless options.subscriptionId
|
419
|
+
raise 'Application name is required' unless args[0]
|
420
|
+
raise 'Deployment slot is required' unless args[1]
|
421
|
+
raise 'Role name is required' unless args[2]
|
422
|
+
raise 'Setting name is required' unless args[3]
|
423
|
+
raise 'Setting value is required' unless args[4]
|
424
|
+
|
425
|
+
doc = REXML::Document.new(Iconv.conv('utf-16', 'utf-8', Base64.decode64(make_call("services/hostedservices/#{args[0]}/deploymentslots/#{args[1]}", :get, options)['Deployment']['Configuration'])))
|
426
|
+
role = REXML::XPath.first(doc, "//Role[@name='#{args[2]}']")
|
427
|
+
raise "Couldn't find role '#{args[2]}'" if role.nil?
|
428
|
+
setting = REXML::XPath.first(role, "//Setting[@name='#{args[3]}']")
|
429
|
+
raise "Couldn't find setting '#{args[3]}'" if setting.nil?
|
430
|
+
setting.attributes['value'] = args[4]
|
431
|
+
|
432
|
+
make_call("services/hostedservices/#{args[0]}/deploymentslots/#{args[1]}/?comp=config", :post, options,
|
433
|
+
Nokogiri::XML::Builder.new(:encoding => 'utf-8') { |xml|
|
434
|
+
xml.ChangeConfiguration('xmlns' => 'http://schemas.microsoft.com/windowsazure') {
|
435
|
+
xml.Configuration Base64.encode64(Iconv.conv('utf-8', 'utf-16', doc.to_s)).rstrip
|
436
|
+
}
|
437
|
+
}.to_xml) do |response|
|
438
|
+
wait_for_completion options, response.headers[:x_ms_request_id]
|
439
|
+
end
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
command 'deploy' do |c|
|
444
|
+
c.syntax = 'waz deploy <application> <production|staging> <path/to/.cspkg> <path/to/.cscfg> [options]'
|
445
|
+
c.description = 'Deploy to an existing Windows Azure application'
|
446
|
+
c.option '--deploymentAccount STRING', 'Name of the storage account to deploy through (temporarily hold the .cspkg), defaults to the first storage account in the subscription'
|
447
|
+
c.action do |args, options|
|
448
|
+
set_defaults options
|
449
|
+
raise 'Subscription ID is required' unless options.subscriptionId
|
450
|
+
raise 'Application name is required' unless args[0]
|
451
|
+
raise 'Deployment slot is required' unless args[1]
|
452
|
+
raise 'Package (.cspkg) is required' unless args[2]
|
453
|
+
raise 'Configuration file (.cscfg) is required' unless args[3]
|
454
|
+
|
455
|
+
if options.account.nil?
|
456
|
+
accounts = arrayize(make_call("services/storageservices", :get, options)['StorageServices']['StorageService'])
|
457
|
+
raise 'Must have at least one storage account' unless accounts.length > 0
|
458
|
+
options.account = accounts[0]['ServiceName']
|
459
|
+
end
|
460
|
+
|
461
|
+
key = make_call("services/storageservices/#{options.account}/keys", :get, options)['StorageService']['StorageServiceKeys']['Primary']
|
462
|
+
|
463
|
+
WAZ::Storage::Base.establish_connection!(:account_name => options.account, :access_key => key)
|
464
|
+
begin
|
465
|
+
container = WAZ::Blobs::Container.create('deployments')
|
466
|
+
rescue
|
467
|
+
container = WAZ::Blobs::Container.find('deployments')
|
468
|
+
end
|
469
|
+
|
470
|
+
name = File.basename(args[2]).gsub(' ', '') + Time.new.utc.strftime('%Y-%m-%d%H:%M:%S')
|
471
|
+
blob = nil
|
472
|
+
open(args[2]) do |f|
|
473
|
+
blob = container.upload(URI::escape(name), f, 'application/octet-stream')
|
474
|
+
end
|
475
|
+
|
476
|
+
make_call("services/hostedservices/#{args[0]}/deploymentslots/#{args[1]}", :post, options,
|
477
|
+
Nokogiri::XML::Builder.new(:encoding => 'utf-8') { |xml|
|
478
|
+
xml.CreateDeployment('xmlns' => 'http://schemas.microsoft.com/windowsazure') {
|
479
|
+
xml.Name UUIDTools::UUID.random_create.to_s.gsub '-', ''
|
480
|
+
xml.PackageUrl URI::unescape blob.url
|
481
|
+
xml.Label Base64.encode64 name
|
482
|
+
xml.Configuration Base64.encode64 File.read args[3]
|
483
|
+
xml.StartDeployment 'true'
|
484
|
+
xml.TreatWarningsAsError 'false'
|
485
|
+
}
|
486
|
+
}.to_xml) do |response|
|
487
|
+
wait_for_completion options, response.headers[:x_ms_request_id]
|
488
|
+
end
|
489
|
+
|
490
|
+
blob.destroy!
|
491
|
+
end
|
492
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<%= app['ServiceName'] %>
|
2
|
+
% props = app['HostedServiceProperties']
|
3
|
+
% unless app['Deployments']['Deployment'].nil?
|
4
|
+
% deployments = app['Deployments']['Deployment']
|
5
|
+
% deployments = [deployments] unless deployments.kind_of? Array
|
6
|
+
% deployments.each do |deployment|
|
7
|
+
<%= Tilt.new(template('show_deployment.erb'), 1, :trim => '%').render(nil, :deployment => deployment, :expand => expand).gsub(/^/, ' ') %>
|
8
|
+
% end
|
9
|
+
% else
|
10
|
+
Nothing deployed.
|
11
|
+
% end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
<%= $terminal.color deployment['DeploymentSlot'].upcase, :bold %>
|
2
|
+
Label: <%= Base64.decode64 deployment['Label'] %>
|
3
|
+
Name: <%= deployment['Name'] %>
|
4
|
+
Status: <%= deployment['Status'] %>
|
5
|
+
Url: <%= deployment['Url'] %>
|
6
|
+
% if deployment['SdkVersion']
|
7
|
+
SDK version: <%= deployment['SdkVersion'] %>
|
8
|
+
% end
|
9
|
+
% if deployment['UpgradeStatus']
|
10
|
+
Upgrade status: <%= deployment['UpgradeStatus']['UpgradeType'] %> upgrade in progress, on upgrade domain <%= deployment['UpgradeStatus'][0]['CurrentUpgradeDomain'] %>/<%= deployment['UpgradeDomainCount'] %>
|
11
|
+
% end
|
12
|
+
% instances = deployment['RoleInstanceList']['RoleInstance']
|
13
|
+
% instances = [instances] unless instances.kind_of? Array
|
14
|
+
% instances = instances.group_by {|i| i['RoleName']}
|
15
|
+
<%= $terminal.color "ROLES", :bold %>
|
16
|
+
% roles = deployment['RoleList']['Role']
|
17
|
+
% roles = [roles] unless roles.kind_of? Array
|
18
|
+
% roles.each do |role|
|
19
|
+
<%= role['RoleName'] %> (<%= role['OsVersion'] %>)
|
20
|
+
% if not expand
|
21
|
+
<%= instances[role['RoleName']].group_by{|i| i['InstanceStatus']}.map{|key, value| "#{value.count} #{key}"}.join(', ') %> (use --expand to see details)
|
22
|
+
% else
|
23
|
+
% instances[role['RoleName']].each do |instance|
|
24
|
+
<%= instance['InstanceName'] %> (<%= instance['InstanceStatus'] %>)
|
25
|
+
% end
|
26
|
+
% end
|
27
|
+
% end
|
28
|
+
<%= $terminal.color "ENDPOINTS", :bold %>
|
29
|
+
% endpoints = deployment['InputEndpointList']['InputEndpoint']
|
30
|
+
% endpoints = [endpoints] unless endpoints.kind_of? Array
|
31
|
+
% endpoints.each do |endpoint|
|
32
|
+
<%= endpoint['Vip'] %>:<%= endpoint['Port'] %> on <%= endpoint['RoleName'] %>
|
33
|
+
% end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
% if history['SubscriptionOperation'].nil?
|
2
|
+
No history to display.
|
3
|
+
% else
|
4
|
+
% operations = history['SubscriptionOperation']
|
5
|
+
% operations = [operations] unless operations.kind_of? Array
|
6
|
+
% operations.each do |operation|
|
7
|
+
<%= $terminal.color operation['OperationName'], :bold %>
|
8
|
+
ID: <%= operation['OperationId'] %>
|
9
|
+
Parameters: <%= operation['OperationParameters']['OperationParameter'].sort{|a,b| a['a:Name'] <=> b['a:Name']}.map{|p| "#{p['a:Name']}=#{p['a:Value'].length > 30 ? '...' : p['a:Value']}"}.join ', ' %>
|
10
|
+
Status: <%= operation['OperationStatus']['Status'] %> (<%= operation['OperationStatus']['HttpStatusCode'] %>)
|
11
|
+
Start: <%= operation['OperationStartedTime'] %>
|
12
|
+
% if operation['OperationCompletedTime']
|
13
|
+
Completed: <%= operation['OperationCompletedTime'] %>
|
14
|
+
% end
|
15
|
+
% if operation['OperationCaller']['UsedServiceManagementApi'] == 'true'
|
16
|
+
Caller: <%= operation['OperationCaller']['ClientIP'] %> (<%= operation['OperationCaller']['SubscriptionCertificateThumbprint'] %>)
|
17
|
+
% else
|
18
|
+
Caller: <%= operation['OperationCaller']['UserEmailAddress'] %>
|
19
|
+
% end
|
20
|
+
% end
|
21
|
+
% end
|
data/lib/waz-cmd.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
module Waz
|
2
|
+
module Cmd
|
3
|
+
require 'faster_require'
|
4
|
+
require 'rexml/document'
|
5
|
+
require 'rexml/xpath'
|
6
|
+
require 'iconv'
|
7
|
+
begin
|
8
|
+
require 'Win32/Console/ANSI' if RUBY_PLATFORM =~ /win32|mingw32/
|
9
|
+
rescue
|
10
|
+
puts 'WARNING: Output will look weird on Windows unless you install the "win32console" gem.'
|
11
|
+
end
|
12
|
+
require 'openssl'
|
13
|
+
require 'RestClient'
|
14
|
+
require 'tilt'
|
15
|
+
require 'base64'
|
16
|
+
require 'yaml'
|
17
|
+
require 'crack'
|
18
|
+
require 'nokogiri'
|
19
|
+
|
20
|
+
def set_defaults(options)
|
21
|
+
options.default :pem => "#{ENV['HOME']}/.waz/cert.pem", :cer => "#{ENV['HOME']}/.waz/cert.cer", :key => "#{ENV['HOME']}/.waz/key.pem"
|
22
|
+
options.default :subscriptionId => $config['subscriptionId'] if $config['subscriptionId']
|
23
|
+
end
|
24
|
+
|
25
|
+
def make_call(path, method, options, body=nil)
|
26
|
+
headers = { 'x-ms-version' => '2011-06-01' }
|
27
|
+
headers['Content-Type'] = 'application/xml' unless body.nil?
|
28
|
+
options = {
|
29
|
+
:url => "https://management.core.windows.net/#{options.subscriptionId}/#{path}",
|
30
|
+
:method => method,
|
31
|
+
:headers => headers,
|
32
|
+
:ssl_client_cert => OpenSSL::X509::Certificate.new(File.read(options.pem)),
|
33
|
+
:ssl_client_key => OpenSSL::PKey::RSA.new(File.read(options.key))
|
34
|
+
}
|
35
|
+
options[:payload] = body unless body.nil?
|
36
|
+
if block_given?
|
37
|
+
yield RestClient::Request.execute options
|
38
|
+
else
|
39
|
+
Crack::XML.parse RestClient::Request.execute options
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def arrayize(x)
|
44
|
+
return [] if x.nil?
|
45
|
+
return x if x.kind_of?(Array)
|
46
|
+
return [x]
|
47
|
+
end
|
48
|
+
|
49
|
+
def restar_instance(star)
|
50
|
+
command "re#{star} instance" do |c|
|
51
|
+
c.syntax = "waz re#{star} instance <application> <production|staging> <instance> [options]"
|
52
|
+
c.description = "Re#{star} the specified instance"
|
53
|
+
c.action do |args, options|
|
54
|
+
set_defaults options
|
55
|
+
raise 'Subscription ID is required' unless options.subscriptionId
|
56
|
+
raise 'Application name is required' unless args[0]
|
57
|
+
raise 'Deployment name is required' unless args[1]
|
58
|
+
raise 'Instance name is required' unless args[2]
|
59
|
+
make_call("services/hostedservices/#{args[0]}/deploymentslots/#{args[1]}/roleinstances/#{args[2]}?comp=re#{star}", :post, options, '') do |response|
|
60
|
+
wait_for_completion options, response.headers[:x_ms_request_id]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def startstop(verb, status)
|
67
|
+
command verb do |c|
|
68
|
+
c.syntax = "waz #{verb} <application> <production|staging> [options]"
|
69
|
+
c.description = "#{verb.capitalize} the deployment in the specified slot of the specified application"
|
70
|
+
c.action do |args, options|
|
71
|
+
set_defaults options
|
72
|
+
raise 'Subscription ID is required' unless options.subscriptionId
|
73
|
+
raise 'Application name is required' unless args[0]
|
74
|
+
raise 'Deployment slot is required' unless args[1]
|
75
|
+
|
76
|
+
make_call("services/hostedservices/#{args[0]}/deploymentslots/#{args[1]}/?comp=status", :post, options,
|
77
|
+
Nokogiri::XML::Builder.new(:encoding => 'utf-8') { |xml|
|
78
|
+
xml.UpdateDeploymentStatus('xmlns' => 'http://schemas.microsoft.com/windowsazure') {
|
79
|
+
xml.Status status
|
80
|
+
}
|
81
|
+
}.to_xml) do |response|
|
82
|
+
wait_for_completion options, response.headers[:x_ms_request_id]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def wait_for_completion(options, requestId)
|
89
|
+
puts 'Waiting for operation to complete...'
|
90
|
+
done = false
|
91
|
+
while not done
|
92
|
+
status = make_call("operations/#{requestId}", :get, options)['Operation']
|
93
|
+
done = status['Status'] != 'InProgress'
|
94
|
+
if done
|
95
|
+
puts "Operation #{status['Status'].downcase} (#{status['HttpStatusCode']})"
|
96
|
+
if status['Error']
|
97
|
+
puts "#{status['Error']['Code']}: #{status['Error']['Message']}"
|
98
|
+
end
|
99
|
+
else
|
100
|
+
sleep 10
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
data/waz-cmd.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "waz-cmd/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "waz-cmd"
|
7
|
+
s.version = Waz::Cmd::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Steve Marx"]
|
10
|
+
s.email = ["Steve.Marx@microsoft.com"]
|
11
|
+
s.homepage = "http://blog.smarx.com"
|
12
|
+
s.summary = %q{Command-line tool to manage Windows Azure applications and storage accounts.}
|
13
|
+
s.description = %q{This gem allows you to perform most of the available operations in the Windows Azure Service Management API from a friendly commandline tool.}
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
s.add_dependency('commander')
|
21
|
+
s.add_dependency('faster_require')
|
22
|
+
s.add_dependency('tilt')
|
23
|
+
s.add_dependency('crack')
|
24
|
+
s.add_dependency('nokogiri')
|
25
|
+
s.add_dependency('waz-storage')
|
26
|
+
s.add_dependency('uuidtools')
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: waz-cmd
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Steve Marx
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-07-01 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: commander
|
16
|
+
requirement: &25037928 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *25037928
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: faster_require
|
27
|
+
requirement: &25037316 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *25037316
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: tilt
|
38
|
+
requirement: &25036848 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *25036848
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: crack
|
49
|
+
requirement: &25036296 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *25036296
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: nokogiri
|
60
|
+
requirement: &25035756 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :runtime
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *25035756
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: waz-storage
|
71
|
+
requirement: &25035240 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :runtime
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *25035240
|
80
|
+
- !ruby/object:Gem::Dependency
|
81
|
+
name: uuidtools
|
82
|
+
requirement: &21821088 !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
type: :runtime
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: *21821088
|
91
|
+
description: This gem allows you to perform most of the available operations in the
|
92
|
+
Windows Azure Service Management API from a friendly commandline tool.
|
93
|
+
email:
|
94
|
+
- Steve.Marx@microsoft.com
|
95
|
+
executables:
|
96
|
+
- waz
|
97
|
+
extensions: []
|
98
|
+
extra_rdoc_files: []
|
99
|
+
files:
|
100
|
+
- .gitignore
|
101
|
+
- Gemfile
|
102
|
+
- LICENSE
|
103
|
+
- README.md
|
104
|
+
- Rakefile
|
105
|
+
- bin/waz
|
106
|
+
- lib/waz-cmd.rb
|
107
|
+
- lib/waz-cmd/templates/get_keys.erb
|
108
|
+
- lib/waz-cmd/templates/list_affinity_groups.erb
|
109
|
+
- lib/waz-cmd/templates/list_applications.erb
|
110
|
+
- lib/waz-cmd/templates/list_locations.erb
|
111
|
+
- lib/waz-cmd/templates/show_affinity_group.erb
|
112
|
+
- lib/waz-cmd/templates/show_application.erb
|
113
|
+
- lib/waz-cmd/templates/show_configuration.erb
|
114
|
+
- lib/waz-cmd/templates/show_deployment.erb
|
115
|
+
- lib/waz-cmd/templates/show_history.erb
|
116
|
+
- lib/waz-cmd/templates/show_storage.erb
|
117
|
+
- lib/waz-cmd/version.rb
|
118
|
+
- waz-cmd.gemspec
|
119
|
+
homepage: http://blog.smarx.com
|
120
|
+
licenses: []
|
121
|
+
post_install_message:
|
122
|
+
rdoc_options: []
|
123
|
+
require_paths:
|
124
|
+
- lib
|
125
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
126
|
+
none: false
|
127
|
+
requirements:
|
128
|
+
- - ! '>='
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0'
|
131
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
132
|
+
none: false
|
133
|
+
requirements:
|
134
|
+
- - ! '>='
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: '0'
|
137
|
+
requirements: []
|
138
|
+
rubyforge_project:
|
139
|
+
rubygems_version: 1.8.5
|
140
|
+
signing_key:
|
141
|
+
specification_version: 3
|
142
|
+
summary: Command-line tool to manage Windows Azure applications and storage accounts.
|
143
|
+
test_files: []
|