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.
- data/CHANGES.txt +8 -9
- data/README.rdoc +48 -7
- data/Rakefile +102 -7
- data/Rudyfile +28 -0
- data/bin/ird +162 -0
- data/bin/rudy +287 -93
- data/lib/annoy.rb +227 -0
- data/lib/aws_sdb/service.rb +1 -1
- data/lib/console.rb +20 -4
- data/lib/escape.rb +305 -0
- data/lib/rudy.rb +265 -125
- data/lib/rudy/aws.rb +61 -26
- data/lib/rudy/aws/ec2.rb +20 -296
- data/lib/rudy/aws/ec2/address.rb +121 -0
- data/lib/rudy/aws/ec2/group.rb +241 -0
- data/lib/rudy/aws/ec2/image.rb +46 -0
- data/lib/rudy/aws/ec2/instance.rb +407 -0
- data/lib/rudy/aws/ec2/keypair.rb +92 -0
- data/lib/rudy/aws/ec2/snapshot.rb +87 -0
- data/lib/rudy/aws/ec2/volume.rb +234 -0
- data/lib/rudy/aws/simpledb.rb +33 -15
- data/lib/rudy/cli.rb +142 -0
- data/lib/rudy/cli/addresses.rb +85 -0
- data/lib/rudy/cli/backups.rb +175 -0
- data/lib/rudy/{command → cli}/config.rb +18 -13
- data/lib/rudy/cli/deploy.rb +12 -0
- data/lib/rudy/cli/disks.rb +125 -0
- data/lib/rudy/cli/domains.rb +17 -0
- data/lib/rudy/cli/groups.rb +77 -0
- data/lib/rudy/{command → cli}/images.rb +18 -6
- data/lib/rudy/cli/instances.rb +142 -0
- data/lib/rudy/cli/keypairs.rb +47 -0
- data/lib/rudy/cli/manager.rb +51 -0
- data/lib/rudy/{command → cli}/release.rb +10 -10
- data/lib/rudy/cli/routines.rb +80 -0
- data/lib/rudy/cli/volumes.rb +121 -0
- data/lib/rudy/command/addresses.rb +62 -39
- data/lib/rudy/command/backups.rb +60 -170
- data/lib/rudy/command/disks-old.rb +322 -0
- data/lib/rudy/command/disks.rb +5 -209
- data/lib/rudy/command/domains.rb +34 -0
- data/lib/rudy/command/groups.rb +105 -48
- data/lib/rudy/command/instances.rb +263 -70
- data/lib/rudy/command/keypairs.rb +149 -0
- data/lib/rudy/command/manager.rb +65 -0
- data/lib/rudy/command/volumes.rb +110 -49
- data/lib/rudy/config.rb +90 -70
- data/lib/rudy/config/objects.rb +67 -0
- data/lib/rudy/huxtable.rb +253 -0
- data/lib/rudy/metadata/backup.rb +23 -48
- data/lib/rudy/metadata/disk.rb +79 -68
- data/lib/rudy/metadata/machine.rb +34 -0
- data/lib/rudy/routines.rb +54 -0
- data/lib/rudy/routines/disk_handler.rb +190 -0
- data/lib/rudy/routines/release.rb +15 -0
- data/lib/rudy/routines/script_runner.rb +65 -0
- data/lib/rudy/routines/shutdown.rb +42 -0
- data/lib/rudy/routines/startup.rb +48 -0
- data/lib/rudy/utils.rb +57 -2
- data/lib/storable.rb +11 -5
- data/lib/sysinfo.rb +274 -0
- data/rudy.gemspec +84 -20
- data/support/randomize-root-password +45 -0
- data/support/rudy-ec2-startup +5 -5
- data/support/update-ec2-ami-tools +20 -0
- data/test/05_config/00_setup_test.rb +24 -0
- data/test/05_config/30_machines_test.rb +69 -0
- data/test/20_sdb/00_setup_test.rb +31 -0
- data/test/20_sdb/10_domains_test.rb +113 -0
- data/test/25_ec2/00_setup_test.rb +34 -0
- data/test/25_ec2/10_keypairs_test.rb +33 -0
- data/test/25_ec2/20_groups_test.rb +139 -0
- data/test/25_ec2/30_addresses_test.rb +35 -0
- data/test/25_ec2/40_volumes_test.rb +46 -0
- data/test/25_ec2/50_snapshots_test.rb +69 -0
- data/test/26_ec2_instances/00_setup_test.rb +33 -0
- data/test/26_ec2_instances/10_instances_test.rb +81 -0
- data/test/26_ec2_instances/50_images_test.rb +13 -0
- data/test/30_sdb_metadata/00_setup_test.rb +28 -0
- data/test/30_sdb_metadata/10_disks_test.rb +99 -0
- data/test/30_sdb_metadata/20_backups_test.rb +102 -0
- data/test/50_commands/00_setup_test.rb +11 -0
- data/test/50_commands/10_keypairs_test.rb +79 -0
- data/test/50_commands/20_groups_test.rb +77 -0
- data/test/50_commands/40_volumes_test.rb +55 -0
- data/test/50_commands/50_instances_test.rb +110 -0
- data/test/coverage.txt +51 -0
- data/test/helper.rb +35 -0
- data/tryouts/disks.rb +55 -0
- data/tryouts/nested_methods.rb +36 -0
- data/tryouts/session_tryout.rb +48 -0
- metadata +94 -25
- data/bin/rudy-ec2 +0 -108
- data/lib/rudy/command/base.rb +0 -839
- data/lib/rudy/command/deploy.rb +0 -12
- data/lib/rudy/command/environment.rb +0 -74
- data/lib/rudy/command/machines.rb +0 -170
- data/lib/rudy/command/metadata.rb +0 -41
- data/lib/rudy/metadata.rb +0 -26
data/bin/rudy
CHANGED
@@ -1,24 +1,32 @@
|
|
1
1
|
#!/usr/bin/ruby
|
2
2
|
|
3
|
-
# Rudy
|
3
|
+
# = Rudy
|
4
|
+
#
|
5
|
+
# === Not your granparent's deployment tool
|
4
6
|
#
|
5
7
|
# See rudy -h for usage
|
6
8
|
#
|
7
9
|
|
8
|
-
|
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'
|
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
|
-
|
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
|
-
|
39
|
-
#
|
46
|
+
|
47
|
+
# --------------------------------- RUDY MACHINE COMMANDS --------
|
48
|
+
# ------------------------------------------------------------------
|
40
49
|
|
41
50
|
|
42
|
-
|
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
|
-
|
46
|
-
|
47
|
-
|
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
|
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::
|
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
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
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
|
-
|
114
|
-
|
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
|
-
|
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
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
desc "
|
147
|
-
|
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
|
150
|
-
option :
|
151
|
-
option :
|
152
|
-
|
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::
|
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::
|
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 :
|
167
|
-
|
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
|
-
|
173
|
-
|
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
|
+
|