solutious-rudy 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. data/CHANGES.txt +8 -9
  2. data/README.rdoc +48 -7
  3. data/Rakefile +102 -7
  4. data/Rudyfile +28 -0
  5. data/bin/ird +162 -0
  6. data/bin/rudy +287 -93
  7. data/lib/annoy.rb +227 -0
  8. data/lib/aws_sdb/service.rb +1 -1
  9. data/lib/console.rb +20 -4
  10. data/lib/escape.rb +305 -0
  11. data/lib/rudy.rb +265 -125
  12. data/lib/rudy/aws.rb +61 -26
  13. data/lib/rudy/aws/ec2.rb +20 -296
  14. data/lib/rudy/aws/ec2/address.rb +121 -0
  15. data/lib/rudy/aws/ec2/group.rb +241 -0
  16. data/lib/rudy/aws/ec2/image.rb +46 -0
  17. data/lib/rudy/aws/ec2/instance.rb +407 -0
  18. data/lib/rudy/aws/ec2/keypair.rb +92 -0
  19. data/lib/rudy/aws/ec2/snapshot.rb +87 -0
  20. data/lib/rudy/aws/ec2/volume.rb +234 -0
  21. data/lib/rudy/aws/simpledb.rb +33 -15
  22. data/lib/rudy/cli.rb +142 -0
  23. data/lib/rudy/cli/addresses.rb +85 -0
  24. data/lib/rudy/cli/backups.rb +175 -0
  25. data/lib/rudy/{command → cli}/config.rb +18 -13
  26. data/lib/rudy/cli/deploy.rb +12 -0
  27. data/lib/rudy/cli/disks.rb +125 -0
  28. data/lib/rudy/cli/domains.rb +17 -0
  29. data/lib/rudy/cli/groups.rb +77 -0
  30. data/lib/rudy/{command → cli}/images.rb +18 -6
  31. data/lib/rudy/cli/instances.rb +142 -0
  32. data/lib/rudy/cli/keypairs.rb +47 -0
  33. data/lib/rudy/cli/manager.rb +51 -0
  34. data/lib/rudy/{command → cli}/release.rb +10 -10
  35. data/lib/rudy/cli/routines.rb +80 -0
  36. data/lib/rudy/cli/volumes.rb +121 -0
  37. data/lib/rudy/command/addresses.rb +62 -39
  38. data/lib/rudy/command/backups.rb +60 -170
  39. data/lib/rudy/command/disks-old.rb +322 -0
  40. data/lib/rudy/command/disks.rb +5 -209
  41. data/lib/rudy/command/domains.rb +34 -0
  42. data/lib/rudy/command/groups.rb +105 -48
  43. data/lib/rudy/command/instances.rb +263 -70
  44. data/lib/rudy/command/keypairs.rb +149 -0
  45. data/lib/rudy/command/manager.rb +65 -0
  46. data/lib/rudy/command/volumes.rb +110 -49
  47. data/lib/rudy/config.rb +90 -70
  48. data/lib/rudy/config/objects.rb +67 -0
  49. data/lib/rudy/huxtable.rb +253 -0
  50. data/lib/rudy/metadata/backup.rb +23 -48
  51. data/lib/rudy/metadata/disk.rb +79 -68
  52. data/lib/rudy/metadata/machine.rb +34 -0
  53. data/lib/rudy/routines.rb +54 -0
  54. data/lib/rudy/routines/disk_handler.rb +190 -0
  55. data/lib/rudy/routines/release.rb +15 -0
  56. data/lib/rudy/routines/script_runner.rb +65 -0
  57. data/lib/rudy/routines/shutdown.rb +42 -0
  58. data/lib/rudy/routines/startup.rb +48 -0
  59. data/lib/rudy/utils.rb +57 -2
  60. data/lib/storable.rb +11 -5
  61. data/lib/sysinfo.rb +274 -0
  62. data/rudy.gemspec +84 -20
  63. data/support/randomize-root-password +45 -0
  64. data/support/rudy-ec2-startup +5 -5
  65. data/support/update-ec2-ami-tools +20 -0
  66. data/test/05_config/00_setup_test.rb +24 -0
  67. data/test/05_config/30_machines_test.rb +69 -0
  68. data/test/20_sdb/00_setup_test.rb +31 -0
  69. data/test/20_sdb/10_domains_test.rb +113 -0
  70. data/test/25_ec2/00_setup_test.rb +34 -0
  71. data/test/25_ec2/10_keypairs_test.rb +33 -0
  72. data/test/25_ec2/20_groups_test.rb +139 -0
  73. data/test/25_ec2/30_addresses_test.rb +35 -0
  74. data/test/25_ec2/40_volumes_test.rb +46 -0
  75. data/test/25_ec2/50_snapshots_test.rb +69 -0
  76. data/test/26_ec2_instances/00_setup_test.rb +33 -0
  77. data/test/26_ec2_instances/10_instances_test.rb +81 -0
  78. data/test/26_ec2_instances/50_images_test.rb +13 -0
  79. data/test/30_sdb_metadata/00_setup_test.rb +28 -0
  80. data/test/30_sdb_metadata/10_disks_test.rb +99 -0
  81. data/test/30_sdb_metadata/20_backups_test.rb +102 -0
  82. data/test/50_commands/00_setup_test.rb +11 -0
  83. data/test/50_commands/10_keypairs_test.rb +79 -0
  84. data/test/50_commands/20_groups_test.rb +77 -0
  85. data/test/50_commands/40_volumes_test.rb +55 -0
  86. data/test/50_commands/50_instances_test.rb +110 -0
  87. data/test/coverage.txt +51 -0
  88. data/test/helper.rb +35 -0
  89. data/tryouts/disks.rb +55 -0
  90. data/tryouts/nested_methods.rb +36 -0
  91. data/tryouts/session_tryout.rb +48 -0
  92. metadata +94 -25
  93. data/bin/rudy-ec2 +0 -108
  94. data/lib/rudy/command/base.rb +0 -839
  95. data/lib/rudy/command/deploy.rb +0 -12
  96. data/lib/rudy/command/environment.rb +0 -74
  97. data/lib/rudy/command/machines.rb +0 -170
  98. data/lib/rudy/command/metadata.rb +0 -41
  99. data/lib/rudy/metadata.rb +0 -26
data/bin/rudy CHANGED
@@ -1,24 +1,32 @@
1
1
  #!/usr/bin/ruby
2
2
 
3
- # Rudy -- Your friend in staging and deploying to EC2
3
+ # = Rudy
4
+ #
5
+ # === Not your granparent's deployment tool
4
6
  #
5
7
  # See rudy -h for usage
6
8
  #
7
9
 
8
- RUDY_HOME = File.join(File.dirname(__FILE__), '..')
9
- RUDY_LIB = File.join(RUDY_HOME, 'lib')
10
- $:.unshift RUDY_LIB # Put our local lib in first place
10
+ $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') # Put our local lib in first place
11
11
 
12
- require 'rubygems' if RUBY_VERSION < "1.9"
12
+ require 'rubygems' unless defined? Gem
13
13
  require 'date'
14
14
  require 'drydock'
15
+ require 'rudy'
16
+ require 'rudy/cli'
15
17
  extend Drydock
16
18
 
17
- project "Rudy" # This also runs require 'ruby'
19
+ # Is there a bug in Ruby 1.9 open-uri?
20
+ #$ /usr/local/bin/ruby bin/rudy myaddress
21
+ #/usr/local/lib/ruby/1.9.1/open-uri.rb:260:in `require': Insecure operation - require (SecurityError)
22
+ #$SAFE = 2
23
+
24
+
18
25
 
19
26
  global :A, :accesskey, String, "AWS Access Key"
20
27
  global :S, :secretkey, String, "AWS Secret Access Key"
21
28
  #global :R, :region, String, "Connect to a specific EC2 region (ie: #{Rudy::DEFAULT_REGION})"
29
+ global :n, :nocolor, "Disable output colors"
22
30
  global :f, :config, String, "Specify another configuration file to read (ie: #{Rudy::RUDY_CONFIG_FILE})"
23
31
  global :z, :zone, String, "Connect to a specific EC2 zone (ie: #{Rudy::DEFAULT_ZONE})"
24
32
  global :e, :environment, String, "Connect to the specified environment (ie: #{Rudy::DEFAULT_ENVIRONMENT})"
@@ -35,23 +43,141 @@ global :V, :version, "Display version number" do
35
43
  exit 0
36
44
  end
37
45
 
38
- #desc "Run this the first time you use Rudy (it's immutable so running it again does no harm)."
39
- #command :setup => Rudy::Command::Metadata
46
+
47
+ # --------------------------------- RUDY MACHINE COMMANDS --------
48
+ # ------------------------------------------------------------------
40
49
 
41
50
 
42
- # ------------------------------------ RUDY INFO COMMANDS --------
51
+ desc "Machine Status"
52
+ usage "rudy [global options] status [-g group-name] [-s state] [--all] [instance-ID]"
53
+ option :g, :group, String, "A security group name"
54
+ option :s, :state, String, "Machine state. One of: running (default), pending, terminated"
55
+ option :l, :all, "Show all machines in this group, regardless of state."
56
+ argv :awsid
57
+ command :status => Rudy::CLI::Instances
58
+
59
+ usage "rudy [global options] connect [-g group-name] [-i instance-ID] [cmd]"
60
+ desc "Open an SSH connection"
61
+ option :print, "Only print the SSH command, don't connect"
62
+ option :g, :group, String, "A security group name"
63
+ option :i, :awsid, String, "An instance ID"
64
+ argv :cmd
65
+ command :connect => Rudy::CLI::Instances
66
+ command_alias :connect, :ssh
67
+
68
+ usage "rudy [global options] copy [-p] [-r] source target"
69
+ desc "Copy files to or from machines. NOTE: You must use quotes when using a tilda for your remote dir ('~/')."
70
+ option :r, :recursive, "Recursively copy entire directories"
71
+ option :p, :preserve, "Preserve atimes and ctimes."
72
+ option :d, :download, "Download FROM the remote machine to the local machine"
73
+ option :print, "Only print the SSH command, don't connect"
74
+ option :g, :group, String, "A security group name"
75
+ option :i, :awsid, String, "An instance ID"
76
+ command :copy => Rudy::CLI::Instances
77
+ command_alias :copy, :scp
78
+ command_alias :copy, :upload
79
+ command_alias :copy, :download
80
+
81
+
82
+
83
+ # ----------------------------------- AMAZON EC2 COMMANDS --------
43
84
  # ------------------------------------------------------------------
44
85
 
45
- #usage "rudy info"
46
- #desc "Displays info about the current Rudy configuration"
47
- #command :info => Rudy::Command::Metadata
86
+ usage "rudy [global options] addresses [-A address instance ID]"
87
+ desc "Manage Amazon Elastic IP addresses"
88
+ argv :ipaddress, :instid
89
+ action :A, :associate, "Associate an IP address to a running instance"
90
+ action :C, :create, "Create an IP address"
91
+ action :D, :destroy, "Destroy an IP address"
92
+ command :addresses => Rudy::CLI::Addresses
93
+ command_alias :addresses, :address
94
+
95
+ usage "rudy [global options] groups [-C -R -A] [-a IP addresses] [-p ports] [group name]"
96
+ usage "rudy groups -C (create a group)"
97
+ usage "rudy -e prod groups (list groups in the prod environment)"
98
+ usage "rudy groups -A -p 81,82,83 (open ports to a group from this machine)"
99
+ desc "Manage EC2 Security Groups"
100
+ option :all, "Display all security groups"
101
+ option :r, :protocols, Array, "Comma-separated list of protocols. One of: tcp (default), udp, icmp"
102
+ option :p, :ports, Array, "List of comma-separated port ranges in the form FROM:TO (default: 22,80,443)"
103
+ option :a, :addresses, Array, "List of comma-separated IP addresses (default: your current external IP)"
104
+ option :g, :group, String, "A group name to authorize or revoke network rule. Must also supply -o!"
105
+ option :o, :owner, String, "A group owner ID (account number). Must also supply -g!"
106
+ action :C, :create, "Create a security group"
107
+ action :D, :destroy, "Destroy a security group"
108
+ action :A, :authorize, "Authorize a rule for a security group"
109
+ action :R, :revoke, "Revoke a rule for a security group"
110
+ argv :name
111
+ command :group => Rudy::CLI::Groups
112
+ command_alias :group, :groups
113
+
114
+ desc "Manage EC2 Volumes"
115
+ usage "rudy volumes"
116
+ usage "rudy volume -C -s size [-d device-path]"
117
+ usage "rudy volume -A volume-id instance-id"
118
+ usage "rudy volume -N volume-id"
119
+ usage "rudy volume -D volume-id"
120
+ option :s, :size, String, "Size (in GB)"
121
+ option :d, :device, String, "Device path (default: /dev/sdh)"
122
+ action :D, :destroy, "Destroy a volume"
123
+ action :C, :create, "Create a volume"
124
+ action :A, :attach, "Attach a volume to a running instance"
125
+ action :N, :detach, "Detach a volume from an instance"
126
+ argv :volid, :insid
127
+ command :volume => Rudy::CLI::Volumes
128
+ command_alias :volume, :volumes
129
+
130
+ desc "Manage KeyPairs"
131
+ usage "rudy keypairs [-C] [-D] [name]"
132
+ action :D, :destroy, "Destroy KeyPair"
133
+ action :C, :create, "Create KeyPair"
134
+ argv :kpname
135
+ command :keypair => Rudy::CLI::KeyPairs
136
+ command_alias :keypair, :keypairs
137
+
138
+ usage "rudy console [-g group] [instance ID]"
139
+ desc "Displays system console output for given instance(s)"
140
+ option :g, :group, String, "A group name to authorize or revoke network rule. Must also supply -o!"
141
+ argv :awsid
142
+ command :console => Rudy::CLI::Instances
143
+
144
+ desc "Manage Machines"
145
+ usage "rudy [global options] machines [-g group-name] [-s state] [instance-ID]"
146
+ option :g, :group, String, "The security group name"
147
+ option :i, :ami, String, "The machine image ID (ami-)"
148
+ option :t, :itype, String, "The instance type (default: m1.small)"
149
+ option :k, :keypair, String, "The SSH keypair to use for launch"
150
+ option :a, :address, String, "The IP address to associate"
151
+ action :C, :create, "Create a machine instance"
152
+ action :D, :destroy, "Destroy a machine instance"
153
+ argv :awsid
154
+ command :instance => Rudy::CLI::Instances
155
+ command_alias :instance, :instances
156
+
157
+ #usage "rudy images [-C -i name [-b bucket -a account]] [-D AMI-ID]"
158
+ #desc "Manage EC2 Machine Images (AMIs)"
159
+ #option :a, :account, String, "Your Amazon Account Number"
160
+ #option :i, :image_name, String, "The name of the image" # TODO: change to --ami
161
+ #option :p, :print, "Print-only (don't execute commands)"
162
+ #option :b, :bucket_name, String, "The name of the bucket that will store the image"
163
+ #action :C, :create, "Create an image"
164
+ ##action :P, :prepare, "Prepare a running instance to be used as an image"
165
+ #action :D, :destroy, "Deregister an image (currently _does not_ remove images files from S3)"
166
+ #argv :ami
167
+ #command :images => Rudy::CLI::Images
168
+ #command_alias :images, :image
169
+
170
+
171
+ # -------------------------------- MISCELLANEOUS COMMANDS --------
172
+ # ------------------------------------------------------------------
48
173
 
49
174
  usage "rudy [-f config-file] config [param-name]"
50
175
  desc "Check Rudy configuration."
51
- option :l, :all, "Display config settings for all machines"
176
+ option :l, :all, "Display configs for all machines"
52
177
  option :d, :defaults, "Display the default value for the supplied parameter"
178
+ option :g, :group, String, "Display configuration for a specific group"
53
179
  argv :name
54
- command :config => Rudy::Command::Config
180
+ command :config => Rudy::CLI::Config
55
181
 
56
182
  usage "rudy myaddress [-i] [-e]"
57
183
  desc "Displays you current internal and external IP addresses"
@@ -69,107 +195,175 @@ command :myaddress do |obj|
69
195
  end
70
196
  end
71
197
 
198
+ usage "rudy [global options] annoy [-h -m -l] [-e]"
199
+ desc "Play around with Rudy's annoying challenges"
200
+ option :s, :string, "A numeric challenge"
201
+ option :n, :numeric, "A numeric challenge"
202
+ option :i, :insane, "Insane annoyance factor"
203
+ option :h, :high, "High annoyance factor"
204
+ option :m, :medium, "Medium annoyance factor"
205
+ option :l, :low, "Low annoyance factor"
206
+ option :r, :rand, "Random challenge type"
207
+ command :annoy do |obj|
208
+ srand(Time.now.to_f)
209
+ flavor = [:numeric, :string, :rand].detect { |v| obj.option.send(v) } || :string
210
+ factor = [:insane, :high, :medium, :low].detect { |v| obj.option.send(v) } || :medium
211
+ success = Annoy.challenge?("Is this annoying?", factor, flavor)
212
+ puts (success ? "Correct!" : "WRONG!").bright
213
+ end
72
214
 
215
+ desc "Display the current Rudy slogan"
216
+ command :slogan do
217
+ puts "Rudy: Not your grandparent's deployment tool!"
218
+ end
73
219
 
74
- # ----------------------------- RUDY MAINTENANCE COMMANDS --------
75
- # ------------------------------------------------------------------
76
-
77
- usage "#{$/} [global options] disks [-C -p path -d device -s size] [-A] [-D] [disk name]"
78
- desc "Manage Disks"
79
- option :l, :all, "Display all disk definitions"
80
- option :p, :path, String, "The filesystem path to use as the mount point"
81
- option :d, :device, String, "The device id (default: /dev/sdh)"
82
- option :s, :size, Integer, "The size of disk (in GB)"
83
- action :C, :create, "Create a disk definition"
84
- action :D, :destroy, "Destroy a disk definition"
85
- action :A, :attach, "Attach a disk"
86
- action :N, :unattach, "Unattach a disk"
87
- argv :diskname
88
- command :disk => Rudy::Command::Disks
89
-
90
-
91
- usage "rudy [global options] backups [-C] [disk name]"
92
- desc "Manage Backups"
93
- option :s, :snapshot, String, "Create a backup entry from an existing snapshot"
94
- action :Z, :sync, "Check for and delete backup metadata with no snapshot. DOES NOT delete snapshots."
95
- #action :T, :tidy, "Tidy existing backups"
96
- action :D, :destroy, "Destroy a backup and DELETE its snapshots."
97
- action :C, :create, "Create a backup"
98
- argv :disk
99
- command :'backup' => Rudy::Command::Backups
100
- command_alias :backup, :bu
101
-
102
-
103
- usage "rudy [global options] metadata instance-ID"
104
- desc "Display Rudy metadata."
105
- command :metadata => Rudy::Command::Metadata
106
- command_alias :metadata, :md
107
-
220
+ desc "Generates a configuration template to #{Rudy::RUDY_CONFIG_FILE}"
221
+ command :generate_config do |obj|
222
+ unless File.exists?(Rudy::RUDY_CONFIG_FILE)
223
+ Rudy::Config.init_config_dir
224
+ puts "Add your AWS credentials to #{Rudy::RUDY_CONFIG_FILE}"
225
+ else
226
+ puts "#{Rudy::RUDY_CONFIG_FILE} already exists"
227
+ end
228
+ end
108
229
 
109
- desc "Machine Group Status"
110
- command :status => Rudy::Command::Machines
111
230
 
231
+ desc "Initialize Rudy configuration"
232
+ command :init do |obj|
233
+
234
+ unless File.exists?(Rudy::RUDY_CONFIG_FILE)
235
+ Rudy::Config.init_config_dir
236
+ end
237
+
238
+ begin
239
+ rdom = Rudy::Domains.new(:global => obj.global)
240
+
241
+ unless rdom.exists?
242
+ puts "Creating SimpleDB domain #{rdom.name}"
243
+ rdom.create
244
+ puts "Initialized"
245
+ else
246
+ puts "Already Initialized"
247
+ end
248
+
249
+
250
+ exit 0 # a quick hack to not print elapsed time
251
+
252
+ rescue Rudy::NoConfig => ex
253
+ puts "AWS credentials must be configured to continue."
254
+ puts "You can modify these in #{Rudy::RUDY_CONFIG_FILE}"
255
+ exit 1
256
+ end
257
+
112
258
 
113
- desc "Update a Machine Group with the current version of Rudy"
114
- command :update => Rudy::Command::Machines
259
+
260
+ end
115
261
 
262
+ desc "Displays the SimpleDB domains associated to your account"
263
+ command :domains => Rudy::CLI::Domains
116
264
 
117
- usage "rudy [-e env] [-u user] connect [-p] [cmd]"
118
- desc "Open an SSH connection"
119
- option :p, :print, "Only print the SSH command, don't connect"
120
- argv :cmd
121
- command :connect => Rudy::Command::Environment
122
- command_alias :connect, :ssh
123
265
 
124
- usage "rudy [-e env] [-u user] copy [-p] -r [from path] [to path]"
125
- desc "Copy files to or from machines. NOTE: You must use quotes when using a tilda for your remote dir ('~/')."
126
- option :r, :remote, "Copy FROM the remote machine to the local machine"
127
- option :p, :print, "Only print the SSH command, don't connect"
128
- argv :from, :to
129
- command :copy => Rudy::Command::Environment
130
- command_alias :copy, :scp
131
- command_alias :copy, :upload
132
- command_alias :copy, :download
133
-
134
-
135
- # -------------------------------- RUDY ROUTINES COMMANDS --------
266
+ # --------------------------------- RUDY MANAGER COMMANDS --------
136
267
  # ------------------------------------------------------------------
137
268
 
138
- desc "Shutdown a Machine Group"
139
- command :shutdown => Rudy::Command::Machines
140
-
141
- desc "Start a Machine Group"
142
- option :i, :image, String, "EC2 image ID (AMI)"
143
- command :startup => Rudy::Command::Machines
144
- command_alias :startup, :start
145
-
146
- desc "Restart a Machine Group"
147
- command :restart => Rudy::Command::Machines
269
+ #usage "rudy init"
270
+ #desc "Run this the first time you use Rudy (it's immutable so running it again does no harm)."
271
+ #command :create_domain => Rudy::CLI::Manager
272
+ #
273
+ #usage "rudy info"
274
+ #desc "Displays info about the current Rudy configuration"
275
+ #command :info => Rudy::CLI::Manager
276
+
277
+ #desc "Update a Machine Group with the current version of Rudy"
278
+ #option :g, :group, String, "A security group name"
279
+ #command :update => Rudy::CLI::Manager
280
+
281
+
282
+ #usage "#{$/} [global options] disks [-C -p path -d device -s size] [-A] [-D] [path]"
283
+ #desc "Manage Disks"
284
+ #option :l, :all, "Display all disk definitions"
285
+ #option :i, :awsid, String, "EC2 Instance ID"
286
+ #option :g, :group, String, "Machine group name"
287
+ #option :p, :path, String, "The filesystem path to use as the mount point"
288
+ #option :d, :device, String, "The device id (default: /dev/sdh)"
289
+ #option :s, :size, Integer, "The size of disk (in GB)"
290
+ #action :C, :create, "Create a disk definition"
291
+ #action :D, :destroy, "Destroy a disk definition"
292
+ #action :A, :attach, "Attach a disk"
293
+ #action :N, :unattach, "Unattach a disk"
294
+ #argv :diskname
295
+ #command :disk => Rudy::CLI::Disks
296
+ #command_alias :disk, :disks
297
+
298
+
299
+ #usage "rudy [global options] backups [-C] [disk name]"
300
+ #desc "Manage Backups"
301
+ #option :s, :snapshot, String, "Create a backup entry from an existing snapshot"
302
+ #action :Z, :sync, "Check for and delete backup metadata with no snapshot. DOES NOT delete snapshots."
303
+ ##action :T, :tidy, "Tidy existing backups"
304
+ #action :D, :destroy, "Destroy a backup and DELETE its snapshots."
305
+ #action :C, :create, "Create a backup"
306
+ #argv :disk
307
+ #command :'backup' => Rudy::CLI::Backups
308
+ #command_alias :backup, :bu
309
+
310
+ #usage "rudy [global options] metadata instance-ID"
311
+ #desc "Display Rudy metadata."
312
+ #command :metadata => Rudy::CLI::Manager
313
+ #command_alias :metadata, :md
314
+
315
+
316
+ # -------------------------- RUDY RELEASE/DEPLOY COMMANDS --------
317
+ # ------------------------------------------------------------------
148
318
 
149
- desc "Release to a machine group"
150
- option :s, :switch, "Switch to the release branch/tag"
151
- option :m, :msg, String, "A short release note"
152
- command :release => Rudy::Command::Release
319
+ #desc "Release to a Machine Group"
320
+ #option :g, :group, String, "A security group name"
321
+ #option :s, :switch, "Switch to the release branch/tag"
322
+ #option :m, :msg, String, "A short release note"
323
+ #command :release => Rudy::CLI::Release
153
324
 
154
- desc "Update the release currently running in a machine group"
155
- command :rerelease => Rudy::Command::Release
156
- command_alias :rerelease, :rere
325
+ #desc "Update the release currently running in a machine group"
326
+ #command :rerelease => Rudy::CLI::Release
327
+ #command_alias :rerelease, :rere
157
328
 
158
329
  #desc "Deploy disk snapshots from one machine to another"
159
- #command :deploy => Rudy::Command::Deploy
330
+ #command :deploy => Rudy::CLI::Deploy
160
331
 
161
332
 
333
+ #desc "Shutdown a Machine Group"
334
+ #usage "rudy [global options] shutdown [-g group-name] [instance-ID]"
335
+ #option :g, :group, String, "A security group name"
336
+ #argv :awsid
337
+ #command :shutdown => Rudy::CLI::Routines
338
+ #
339
+ #
340
+ #desc "Start a Machine Group"
341
+ #usage "rudy [global options] startup [-g group-name] [-i image-ID]"
342
+ #option :ami, String, "EC2 image ID (AMI)"
343
+ #option :g, :group, String, "A security group name"
344
+ #command :startup => Rudy::CLI::Routines
345
+ #command_alias :startup, :start
346
+
347
+ #desc "Restart a Machine Group"
348
+ #option :g, :group, String, "A security group name"
349
+ #argv :awsid
350
+ #command :restart => Rudy::CLI::Routines
351
+ #
352
+
162
353
 
163
354
 
164
355
  # ------------------------------------------- UGLY STUFFS --------
165
356
  # ------------------------------------------------------------------
166
- debug :on
167
- capture :stderr
357
+ debug :off
358
+ default :status
359
+ #capture :stderr
168
360
  before do
169
361
  @start = Time.now
170
362
  end
171
- after do
172
- @elapsed = Time.now - @start
173
- puts $/, "Elapsed: %.2f seconds" % @elapsed.to_f if @elapsed > 0.1
363
+ after do |obj|
364
+ unless obj.global.quiet
365
+ @elapsed = Time.now - @start
366
+ puts $/, "Elapsed: %.2f seconds" % @elapsed.to_f if @elapsed > 0.1
367
+ end
174
368
  end
175
369
 
data/lib/annoy.rb ADDED
@@ -0,0 +1,227 @@
1
+
2
+ require 'timeout'
3
+ require 'sysinfo'
4
+
5
+
6
+ # Annoy - your annoying friend that asks you questions all the time.
7
+ #
8
+ # TODO: Use Matrix to give a more accurate annoyance factor
9
+ # TODO: Add trivia questions
10
+ #
11
+ class Annoy #:nodoc:all
12
+
13
+ attr_accessor :factor
14
+ attr_accessor :flavor
15
+ attr_accessor :answer
16
+ attr_accessor :writer
17
+ attr_accessor :period
18
+ attr_accessor :system
19
+
20
+ @@operators = {
21
+ :low => %w(+ - *),
22
+ :medium => %w(* % + -),
23
+ :high => %w(* % + -),
24
+ :insane => %w(** << | & *)
25
+ }.freeze
26
+
27
+ @@strlen = {
28
+ :low => 2,
29
+ :medium => 3,
30
+ :high => 4,
31
+ :insane => 32
32
+ }.freeze
33
+
34
+ @@randsize = {
35
+ :low => 10,
36
+ :medium => 100,
37
+ :high => 1000,
38
+ :insane => 1000
39
+ }.freeze
40
+
41
+ @@period = 60.freeze # max seconds to wait
42
+
43
+ @@flavors = [:numeric, :string].freeze
44
+
45
+
46
+ # * +factor+ annoyance factor, one of :low (default), :medium, :high, :insane
47
+ # * +flavor+ annoyance flavor, one of :rand (default), :numeric, string
48
+ # * +writer+ an IO object to write to. Default: STDERR
49
+ # * +period+ the amount of time to wait in seconds. Default: 60
50
+ def initialize(opts={:factor=>:medium, :flavor=>:rand, :writer=>STDOUT, :period=>nil})
51
+ @factor = opts[:factor]
52
+ @flavor = Annoy.get_flavor(opts[:flavor])
53
+ @writer = opts[:writer]
54
+ @period = opts[:period] || @@period
55
+ unless Annoy.respond_to?("#{@flavor}_question")
56
+ raise "Hey, hey, hey. I don't know that flavor! (#{@flavor})"
57
+ end
58
+ end
59
+
60
+ # Generates and returns a question. The correct response is available
61
+ # as +@answer+.
62
+ def question
63
+ q, @answer =Annoy.question(@factor, @flavor)
64
+ q
65
+ end
66
+
67
+ # A wrapper for string_question and numberic_question
68
+ def Annoy.question(factor=:medium, flavor=:rand)
69
+ raise "Come on, you ruined the flavor!" unless flavor
70
+ Annoy.send("#{flavor}_question", factor)
71
+ end
72
+
73
+ # Generates a random string
74
+ def Annoy.string_question(factor=:medium)
75
+ # Strings don't need to be evaluated so the answer is the
76
+ # same as the question.
77
+ str = strand @@strlen[factor]
78
+ [str,str]
79
+ end
80
+
81
+ # * Generates a rudimentary numeric equation in the form: (Integer OPERATOR Integer).
82
+ # * Returns [equation, answer]
83
+ def Annoy.numeric_question(factor=:medium)
84
+ equation = answer = 0
85
+ while answer < 10
86
+ vals = [rand(@@randsize[factor])+1,
87
+ @@operators[factor][ rand(@@operators[factor].size) ],
88
+ rand(@@randsize[factor])+1 ]
89
+ equation = "(%d %s %d)" % vals
90
+ answer = eval(equation)
91
+ end
92
+ [equation, answer]
93
+ end
94
+
95
+ # Prints a question to @writer and waits for a response on STDIN.
96
+ # It checks whether STDIN is connected a tty so it doesn't block on gets.
97
+ # when there's no human around to annoy. It will return <b>TRUE</b> when
98
+ # STDIN is NOT connected to a tty.
99
+ # * +msg+ The message to print. Default: "Please confirm."
100
+ # Returns true when the answer is correct, otherwise false.
101
+ def Annoy.challenge?(msg="Please confirm.", factor=:medium, flavor=:rand, writer=STDOUT, period=nil)
102
+ return true unless STDIN.tty? # Humans only!
103
+ begin
104
+ success = Timeout::timeout(period || @@period) do
105
+ flavor = Annoy.get_flavor(flavor)
106
+ question, answer = Annoy.question(factor, flavor)
107
+ writer.print "#{msg} To continue, #{Annoy.verb(flavor)} #{question}: "
108
+ writer.print "(#{answer}) " if ![:high, :insane].member?(factor) && flavor == :numeric
109
+ writer.flush
110
+ response = Annoy.get_response(writer)
111
+ response = response.to_i if flavor == :numeric
112
+ (response == answer)
113
+ end
114
+ rescue Timeout::Error => ex
115
+ writer.puts $/, "Times up!"
116
+ false
117
+ end
118
+ end
119
+
120
+ # Runs a challenge with the message, "Are you sure?"
121
+ # See: Annoy.challenge?
122
+ def Annoy.are_you_sure?(factor=:medium, flavor=:rand, writer=STDOUT)
123
+ Annoy.challenge?("Are you sure?", factor, flavor, writer)
124
+ end
125
+
126
+ # See: Annoy.challenge?
127
+ # Uses the value of @flavor, @factor, and @writer
128
+ def challenge?(msg="Please confirm.")
129
+ Annoy.challenge?(msg, @factor, @flavor, @writer)
130
+ end
131
+
132
+ # See: Annoy.pose_question
133
+ # Uses the value of @writer
134
+ def pose_question(msg, regexp)
135
+ Annoy.pose_question(msg, regexp, @writer)
136
+ end
137
+
138
+ # Prints a question to writer and waits for a response on STDIN.
139
+ # It checks whether STDIN is connected a tty so it doesn't block on gets.
140
+ # when there's no human around to annoy. It will return <b>TRUE</b> when
141
+ # STDIN is NOT connected to a tty.
142
+ # * +msg+ The question to pose to the user
143
+ # * +regexp+ The regular expression to match the answer.
144
+ def Annoy.pose_question(msg, regexp, writer=STDOUT, period=nil)
145
+ return true unless STDIN.tty? # Only ask a question if there's a human
146
+ begin
147
+ success = Timeout::timeout(period || @@period) do
148
+ regexp &&= Regexp.new regexp
149
+ writer.print msg
150
+ writer.flush if writer.respond_to?(:flush)
151
+ response = Annoy.get_response
152
+ regexp.match(response)
153
+ end
154
+ rescue Timeout::Error => ex
155
+ writer.puts $/, "Times up!"
156
+ false
157
+ end
158
+ end
159
+
160
+
161
+ private
162
+ def Annoy.get_response(writer=STDOUT)
163
+ return true unless STDIN.tty? # Humans only
164
+ # TODO: Count the number of keystrokes to prevent copy/paste
165
+ # We likely need to be more specific but this will do for now.
166
+ #if ::SystemInfo.new.os == :unix
167
+ # begin
168
+ # response = []
169
+ # char = nil
170
+ # system("stty raw -echo") # Raw mode, no echo
171
+ # while char != "\r" || response.size > 5
172
+ # char = STDIN.getc.chr
173
+ # writer.print char
174
+ # writer.flush
175
+ # response << char
176
+ # end
177
+ # writer.print "\n\r"
178
+ # response = response.join('')
179
+ # rescue => ex
180
+ # ensure
181
+ # system("stty -raw echo") # Reset terminal mode
182
+ # end
183
+ #else
184
+ response = (STDIN.gets || "")
185
+ #end
186
+ response.chomp.gsub(/["']/, '')
187
+ end
188
+ # Returns a verb appropriate to the flavor.
189
+ # * :numeric => resolve
190
+ # * :string => type
191
+ def Annoy.verb(flavor)
192
+ case flavor
193
+ when :numeric then "resolve"
194
+ when :string then "type"
195
+ else
196
+ nil
197
+ end
198
+ end
199
+
200
+ #
201
+ # Generates a string of random alphanumeric characters.
202
+ # * +len+ is the length, an Integer. Default: 8
203
+ # * +safe+ in safe-mode, ambiguous characters are removed (default: true):
204
+ # i l o 1 0
205
+ def Annoy.strand( len=8, safe=true )
206
+ chars = ("a".."z").to_a + ("0".."9").to_a
207
+ chars.delete_if { |v| %w(i l o 1 0).member?(v) } if safe
208
+ str = ""
209
+ 1.upto(len) { |i| str << chars[rand(chars.size-1)] }
210
+ str
211
+ end
212
+
213
+ # * +f+ a prospective flavor name
214
+ def Annoy.get_flavor(f)
215
+ f.to_sym == :rand ? flavor_rand : f.to_sym
216
+ end
217
+
218
+ # Return a random flavor
219
+ def Annoy.flavor_rand
220
+ @@flavors[rand(@@flavors.size)]
221
+ end
222
+
223
+
224
+ end
225
+
226
+
227
+