rudy 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. data/CHANGES.txt +54 -30
  2. data/README.rdoc +100 -12
  3. data/Rakefile +103 -8
  4. data/Rudyfile +119 -0
  5. data/bin/ird +175 -0
  6. data/bin/rudy +259 -156
  7. data/bin/rudy-ec2 +228 -95
  8. data/bin/rudy-s3 +76 -0
  9. data/bin/rudy-sdb +67 -0
  10. data/lib/annoy.rb +270 -0
  11. data/lib/console.rb +30 -9
  12. data/lib/escape.rb +305 -0
  13. data/lib/rudy.rb +151 -182
  14. data/lib/rudy/aws.rb +56 -49
  15. data/lib/rudy/aws/ec2.rb +47 -292
  16. data/lib/rudy/aws/ec2/address.rb +157 -0
  17. data/lib/rudy/aws/ec2/group.rb +301 -0
  18. data/lib/rudy/aws/ec2/image.rb +168 -0
  19. data/lib/rudy/aws/ec2/instance.rb +434 -0
  20. data/lib/rudy/aws/ec2/keypair.rb +104 -0
  21. data/lib/rudy/aws/ec2/snapshot.rb +98 -0
  22. data/lib/rudy/aws/ec2/volume.rb +230 -0
  23. data/lib/rudy/aws/ec2/zone.rb +77 -0
  24. data/lib/rudy/aws/s3.rb +54 -0
  25. data/lib/rudy/aws/sdb.rb +298 -0
  26. data/lib/rudy/aws/sdb/error.rb +46 -0
  27. data/lib/rudy/{metadata/backup.rb → backup.rb} +26 -51
  28. data/lib/rudy/cli.rb +157 -0
  29. data/lib/rudy/cli/aws/ec2/addresses.rb +105 -0
  30. data/lib/rudy/cli/aws/ec2/candy.rb +208 -0
  31. data/lib/rudy/cli/aws/ec2/groups.rb +121 -0
  32. data/lib/rudy/cli/aws/ec2/images.rb +196 -0
  33. data/lib/rudy/cli/aws/ec2/instances.rb +194 -0
  34. data/lib/rudy/cli/aws/ec2/keypairs.rb +53 -0
  35. data/lib/rudy/cli/aws/ec2/snapshots.rb +49 -0
  36. data/lib/rudy/cli/aws/ec2/volumes.rb +104 -0
  37. data/lib/rudy/cli/aws/ec2/zones.rb +22 -0
  38. data/lib/rudy/cli/aws/s3/buckets.rb +50 -0
  39. data/lib/rudy/cli/aws/s3/store.rb +22 -0
  40. data/lib/rudy/cli/aws/sdb/domains.rb +41 -0
  41. data/lib/rudy/cli/candy.rb +8 -0
  42. data/lib/rudy/{command → cli}/config.rb +34 -24
  43. data/lib/rudy/cli/disks.rb +35 -0
  44. data/lib/rudy/cli/machines.rb +94 -0
  45. data/lib/rudy/cli/routines.rb +57 -0
  46. data/lib/rudy/config.rb +77 -72
  47. data/lib/rudy/config/objects.rb +29 -0
  48. data/lib/rudy/disks.rb +248 -0
  49. data/lib/rudy/global.rb +121 -0
  50. data/lib/rudy/huxtable.rb +340 -0
  51. data/lib/rudy/machines.rb +245 -0
  52. data/lib/rudy/metadata.rb +123 -13
  53. data/lib/rudy/routines.rb +47 -0
  54. data/lib/rudy/routines/helpers/diskhelper.rb +101 -0
  55. data/lib/rudy/routines/helpers/scripthelper.rb +91 -0
  56. data/lib/rudy/routines/release.rb +34 -0
  57. data/lib/rudy/routines/shutdown.rb +57 -0
  58. data/lib/rudy/routines/startup.rb +58 -0
  59. data/lib/rudy/scm/svn.rb +1 -1
  60. data/lib/rudy/utils.rb +322 -4
  61. data/lib/storable.rb +26 -17
  62. data/lib/sysinfo.rb +274 -0
  63. data/lib/tryouts.rb +6 -13
  64. data/rudy.gemspec +128 -42
  65. data/support/randomize-root-password +45 -0
  66. data/support/rudy-ec2-startup +9 -9
  67. data/support/update-ec2-ami-tools +20 -0
  68. data/test/05_config/00_setup_test.rb +20 -0
  69. data/test/05_config/30_machines_test.rb +69 -0
  70. data/test/20_sdb/00_setup_test.rb +16 -0
  71. data/test/20_sdb/10_domains_test.rb +115 -0
  72. data/test/25_ec2/00_setup_test.rb +29 -0
  73. data/test/25_ec2/10_keypairs_test.rb +41 -0
  74. data/test/25_ec2/20_groups_test.rb +131 -0
  75. data/test/25_ec2/30_addresses_test.rb +38 -0
  76. data/test/25_ec2/40_volumes_test.rb +49 -0
  77. data/test/25_ec2/50_snapshots_test.rb +74 -0
  78. data/test/26_ec2_instances/00_setup_test.rb +28 -0
  79. data/test/26_ec2_instances/10_instances_test.rb +83 -0
  80. data/test/26_ec2_instances/50_images_test.rb +13 -0
  81. data/test/30_sdb_metadata/00_setup_test.rb +21 -0
  82. data/test/30_sdb_metadata/10_disks_test.rb +109 -0
  83. data/test/30_sdb_metadata/20_backups_test.rb +102 -0
  84. data/test/coverage.txt +51 -0
  85. data/test/helper.rb +36 -0
  86. data/vendor/highline-1.5.1/CHANGELOG +222 -0
  87. data/vendor/highline-1.5.1/INSTALL +35 -0
  88. data/vendor/highline-1.5.1/LICENSE +7 -0
  89. data/vendor/highline-1.5.1/README +63 -0
  90. data/vendor/highline-1.5.1/Rakefile +82 -0
  91. data/vendor/highline-1.5.1/TODO +6 -0
  92. data/vendor/highline-1.5.1/examples/ansi_colors.rb +38 -0
  93. data/vendor/highline-1.5.1/examples/asking_for_arrays.rb +18 -0
  94. data/vendor/highline-1.5.1/examples/basic_usage.rb +75 -0
  95. data/vendor/highline-1.5.1/examples/color_scheme.rb +32 -0
  96. data/vendor/highline-1.5.1/examples/limit.rb +12 -0
  97. data/vendor/highline-1.5.1/examples/menus.rb +65 -0
  98. data/vendor/highline-1.5.1/examples/overwrite.rb +19 -0
  99. data/vendor/highline-1.5.1/examples/page_and_wrap.rb +322 -0
  100. data/vendor/highline-1.5.1/examples/password.rb +7 -0
  101. data/vendor/highline-1.5.1/examples/trapping_eof.rb +22 -0
  102. data/vendor/highline-1.5.1/examples/using_readline.rb +17 -0
  103. data/vendor/highline-1.5.1/lib/highline.rb +758 -0
  104. data/vendor/highline-1.5.1/lib/highline/color_scheme.rb +120 -0
  105. data/vendor/highline-1.5.1/lib/highline/compatibility.rb +17 -0
  106. data/vendor/highline-1.5.1/lib/highline/import.rb +43 -0
  107. data/vendor/highline-1.5.1/lib/highline/menu.rb +395 -0
  108. data/vendor/highline-1.5.1/lib/highline/question.rb +463 -0
  109. data/vendor/highline-1.5.1/lib/highline/system_extensions.rb +193 -0
  110. data/vendor/highline-1.5.1/setup.rb +1360 -0
  111. data/vendor/highline-1.5.1/test/tc_color_scheme.rb +56 -0
  112. data/vendor/highline-1.5.1/test/tc_highline.rb +823 -0
  113. data/vendor/highline-1.5.1/test/tc_import.rb +54 -0
  114. data/vendor/highline-1.5.1/test/tc_menu.rb +429 -0
  115. data/vendor/highline-1.5.1/test/ts_all.rb +15 -0
  116. metadata +141 -38
  117. data/lib/aws_sdb.rb +0 -3
  118. data/lib/aws_sdb/error.rb +0 -42
  119. data/lib/aws_sdb/service.rb +0 -215
  120. data/lib/rudy/aws/simpledb.rb +0 -53
  121. data/lib/rudy/command/addresses.rb +0 -46
  122. data/lib/rudy/command/backups.rb +0 -175
  123. data/lib/rudy/command/base.rb +0 -841
  124. data/lib/rudy/command/deploy.rb +0 -12
  125. data/lib/rudy/command/disks.rb +0 -213
  126. data/lib/rudy/command/environment.rb +0 -73
  127. data/lib/rudy/command/groups.rb +0 -61
  128. data/lib/rudy/command/images.rb +0 -91
  129. data/lib/rudy/command/instances.rb +0 -85
  130. data/lib/rudy/command/machines.rb +0 -161
  131. data/lib/rudy/command/metadata.rb +0 -41
  132. data/lib/rudy/command/release.rb +0 -174
  133. data/lib/rudy/command/volumes.rb +0 -66
  134. data/lib/rudy/metadata/disk.rb +0 -138
  135. data/tryouts/console_tryout.rb +0 -91
@@ -1,53 +0,0 @@
1
-
2
-
3
-
4
- module Rudy::AWS
5
-
6
-
7
- class SimpleDB
8
- class Domains
9
- include Rudy::AWS::ObjectBase
10
-
11
- def create(name)
12
- @aws.create_domain(name)
13
- end
14
-
15
- def destroy(name)
16
- @aws.delete_domain(name)
17
- end
18
-
19
- def list
20
- @aws.list_domains
21
- end
22
- end
23
-
24
- def destroy(domain, item, attributes={})
25
- @aws.delete_attributes(domain, item, attributes)
26
- end
27
-
28
- def store(domain, item, attributes={}, replace=false)
29
- @aws.put_attributes(domain, item, attributes, replace)
30
- end
31
-
32
- def query(domain, query=nil, max=nil)
33
- @aws.query(domain, query, max)
34
- end
35
-
36
- def query_with_attributes(domain, query, max=nil)
37
- items = {}
38
- query(domain, query)[:items].each do |item|
39
- items[item] = get_attributes(domain, item)[:attributes]
40
- end
41
- items
42
- end
43
-
44
- def select(query)
45
- list = @aws2.select(query) || []
46
- list[0]
47
- end
48
-
49
- def get_attributes(domain, item, attribute=nil)
50
- @aws.get_attributes(domain, item, attribute)
51
- end
52
- end
53
- end
@@ -1,46 +0,0 @@
1
-
2
-
3
-
4
- module Rudy
5
- module Command
6
- class Addresses < Rudy::Command::Base
7
-
8
-
9
- def associate_addresses_valid?
10
- raise "You have not supplied an IP addresses" unless @argv.address
11
- raise "You did not supply an instance ID" unless @argv.instanceid
12
-
13
- @inst = @ec2.instances.get(@argv.instanceid)
14
- raise "Instance #{@inst[:aws_instance_id]} does not exist!" unless @inst
15
-
16
- raise "That's not an elastic IP you own!" unless @ec2.addresses.valid?(@argv.address)
17
- raise "#{@argv.address} is already associated!" if @ec2.addresses.associated?(@argv.address)
18
-
19
- true
20
- end
21
-
22
- def associate_addresses
23
- puts "Associating #{@argv.address} to #{@inst[:aws_groups]}: #{@inst[:dns_name]}"
24
- @ec2.addresses.associate(@inst[:aws_instance_id], @argv.address)
25
- puts "Done!"
26
- puts
27
-
28
- addresses
29
- end
30
-
31
- def addresses
32
- puts "Elastic IP mappings:"
33
- @ec2.addresses.list.each do |address|
34
- print "IP: #{address[:public_ip]} "
35
- if address[:instance_id]
36
- inst = @ec2.instances.get(address[:instance_id])
37
- puts "%s: %s %s" % [inst[:aws_groups], inst[:aws_instance_id], inst[:dns_name]]
38
- end
39
- end
40
- puts
41
- end
42
-
43
- end
44
- end
45
- end
46
-
@@ -1,175 +0,0 @@
1
-
2
-
3
-
4
-
5
- module Rudy
6
- module Command
7
- class Backups < Rudy::Command::Base
8
-
9
-
10
- def backup
11
- criteria = [@global.zone]
12
- criteria += [@global.environment, @global.role] unless @option.all
13
-
14
- Rudy::MetaData::Backup.list(@sdb, *criteria).each do |backup|
15
- puts "%s (%s)" % [backup.name, backup.awsid]
16
- end
17
- end
18
-
19
- # Check for backups pointing to snapshots that don't exist.
20
- def sync_backup
21
- unless argv.empty?
22
- puts "The disk you specified will be ignored."
23
- argv.clear
24
- end
25
-
26
- criteria = [@global.zone]
27
- criteria += [@global.environment, @global.role] unless @option.all
28
-
29
- puts "Looking for backup metadata with delinquent snapshots..."
30
- to_be_deleted = {} # snap-id => backup
31
- Rudy::MetaData::Backup.list(@sdb, *criteria).each do |backup|
32
- to_be_deleted[backup.awsid] = backup unless @ec2.snapshots.exists?(backup.awsid)
33
- end
34
-
35
- if to_be_deleted.empty?
36
- puts "All backups are in-sync with snapshots. Nothing to do."
37
- return
38
- end
39
-
40
- puts
41
- puts "These backup metadata will be deleted:"
42
- to_be_deleted.each do |snap_id, backup|
43
- puts "%s: %s" % [snap_id, backup.name]
44
- end
45
-
46
- puts
47
- are_you_sure?
48
-
49
- puts
50
- puts "Deleting..."
51
- to_be_deleted.each do |snap_id, backup|
52
- print " -> #{backup.name}... "
53
- @sdb.destroy(RUDY_DOMAIN, backup.name)
54
- puts "done"
55
- end
56
-
57
- puts "Done!"
58
- end
59
-
60
- def destroy_backup_valid?
61
- raise "No backup specified" if argv.empty?
62
- exit unless are_you_sure?(5)
63
- true
64
- end
65
-
66
- def destroy_backup
67
- name = @argv.first
68
- puts "Destroying #{name}"
69
- begin
70
- backup = Rudy::MetaData::Backup.get(@sdb, name)
71
- rescue => ex
72
- puts "Error deleteing backup: #{ex.message}"
73
- end
74
-
75
- return unless backup
76
-
77
- begin
78
- puts " -> deleting snapshot..."
79
- @ec2.snapshots.destroy(backup.awsid)
80
- rescue => ex
81
- puts "Error deleting snapshot: #{ex.message}."
82
- puts "Continuing..."
83
- ensure
84
- puts " -> deleting metadata..."
85
- @sdb.destroy(RUDY_DOMAIN, name)
86
- end
87
- puts "Done."
88
- end
89
-
90
- def create_backup
91
- diskname = @argv.first
92
-
93
- machine = find_current_machine
94
-
95
- disks = Rudy::MetaData::Disk.list(@sdb, machine[:aws_availability_zone], @global.environment, @global.role, @global.position)
96
- raise "The machine #{machine_name} does not have any disk metadata" if disks.empty?
97
-
98
- puts "Machine: #{machine_name}"
99
-
100
- if @option.snapshot
101
- raise "You must supply a diskname when using an existing snapshot" unless diskname
102
- raise "The snapshot #{@option.snapshot} does not exist" unless @ec2.snapshots.exists?(@option.snapshot)
103
- disk = Rudy::MetaData::Disk.get(@sdb, diskname)
104
-
105
- raise "The disk #{diskname} does not exist" unless disk
106
- backup = Rudy::MetaData::Backup.new
107
- backup.awsid = @option.snapshot
108
- backup.time_stamp
109
-
110
- # Populate machine infos
111
- [:zone, :environment, :role, :position].each do |n|
112
- backup.send("#{n}=", @global.send(n)) if @global.send(n)
113
- end
114
-
115
- # Populate disk infos
116
- [:path, :size].each do |n|
117
- backup.send("#{n}=", disk.send(n)) if disk.send(n)
118
- end
119
-
120
-
121
- Rudy::MetaData::Backup.save(@sdb, backup)
122
-
123
- puts backup.name
124
-
125
- else
126
- volumes = @ec2.instances.volumes(machine[:aws_instance_id])
127
- raise "The machine #{machine_name} does not have any volumes attached." if volumes.empty?
128
-
129
- puts "#{disks.size} Disk(s) defined with #{volumes.size} Volume(s) running"
130
-
131
- volumes.each do |volume|
132
- print "Volume #{volume[:aws_id]}... "
133
- disk = Rudy::MetaData::Disk.find_from_volume(@sdb, volume[:aws_id])
134
- backup = Rudy::MetaData::Backup.new
135
-
136
- # TODO: Look for the disk based on the machine
137
- raise "No disk associated to volume #{volume[:aws_id]}" unless disk
138
-
139
- backup.volume = volume[:aws_id]
140
-
141
- # Populate machine infos
142
- [:zone, :environment, :role, :position].each do |n|
143
- backup.send("#{n}=", @global.send(n)) if @global.send(n)
144
- end
145
-
146
- # Populate disk infos
147
- [:path, :size].each do |n|
148
- backup.send("#{n}=", disk.send(n)) if disk.send(n)
149
- end
150
-
151
- backup.time_stamp
152
-
153
- raise "There was a problem creating the backup metadata" unless backup.valid?
154
-
155
- snap = @ec2.snapshots.create(volume[:aws_id])
156
-
157
- if !snap || !snap.is_a?(Hash)
158
- puts "There was an unknown problem creating #{backup.name}. Continuing with the next volume..."
159
- next
160
- end
161
-
162
- backup.awsid = snap[:aws_id]
163
-
164
- Rudy::MetaData::Backup.save(@sdb, backup)
165
-
166
- puts backup.name
167
-
168
- end
169
- end
170
- end
171
-
172
-
173
- end
174
- end
175
- end
@@ -1,841 +0,0 @@
1
-
2
- module Rudy
3
- class UnknownInstance < RuntimeError; end
4
- end
5
-
6
- module Rudy
7
- module Command
8
- class NoCred < RuntimeError; end;
9
-
10
- class Base < Drydock::Command
11
-
12
- attr_reader :scm
13
-
14
- attr_reader :rscripts
15
- attr_reader :domains
16
- attr_reader :machine_images
17
-
18
- attr_reader :config
19
-
20
-
21
- def init
22
-
23
-
24
- raise "PRODUCTION ACCESS IS DISABLED IN DEBUG MODE" if @global.environment == "prod" && Drydock.debug?
25
-
26
- @global.config ||= RUDY_CONFIG_FILE
27
-
28
- unless File.exists?(@global.config)
29
- init_config_dir
30
- end
31
-
32
- @config = Rudy::Config.new(@global.config, {:verbose => (@global.verbose > 0)} )
33
- @config.look_and_load
34
-
35
- raise "There is no machine group configured" if @config.machines.nil?
36
- raise "There is no AWS info configured" if @config.awsinfo.nil?
37
-
38
-
39
- @global.accesskey ||= @config.awsinfo.accesskey || ENV['AWS_ACCESS_KEY']
40
- @global.secretkey ||= @config.awsinfo.secretkey || ENV['AWS_SECRET_KEY'] || ENV['AWS_SECRET_ACCESS_KEY']
41
- @global.account ||= @config.awsinfo.account || ENV['AWS_ACCOUNT_NUMBER']
42
-
43
- @global.cert ||= @config.awsinfo.cert || ENV['EC2_CERT']
44
- @global.privatekey ||= @config.awsinfo.privatekey || ENV['EC2_PRIVATE_KEY']
45
-
46
- @global.cert = File.expand_path(@global.cert || '')
47
- @global.privatekey = File.expand_path(@global.privatekey || '')
48
-
49
- @global.region ||= @config.defaults.region || DEFAULT_REGION
50
- @global.zone ||= @config.defaults.zone || DEFAULT_ZONE
51
- @global.environment ||= @config.defaults.environment || DEFAULT_ENVIRONMENT
52
- @global.role ||= @config.defaults.role || DEFAULT_ROLE
53
- @global.position ||= @config.defaults.position || DEFAULT_POSITION
54
- @global.user ||= @config.defaults.user || DEFAULT_USER
55
-
56
- @global.local_user = ENV['USER'] || :user
57
- @global.local_hostname = Socket.gethostname || :host
58
-
59
- check_keys
60
-
61
- if @global.verbose > 1
62
- puts "GLOBALS:"
63
- @global.marshal_dump.each_pair do |n,v|
64
- puts "#{n}: #{v}"
65
- end
66
- ["machines", "routines"].each do |type|
67
- puts "#{$/*2}#{type.upcase}:"
68
- val = @config.send(type).find_deferred(@global.environment, @global.role)
69
- puts val.to_hash.to_yaml
70
- end
71
- puts
72
- end
73
-
74
-
75
-
76
- # TODO: enforce home directory permissions
77
- #if File.exists?(RUDY_CONFIG_DIR)
78
- # puts "Checking #{check_environment} permissions..."
79
- #end
80
-
81
- if has_keys?
82
- @ec2 = Rudy::AWS::EC2.new(@global.accesskey, @global.secretkey)
83
- @sdb = Rudy::AWS::SimpleDB.new(@global.accesskey, @global.secretkey)
84
- #@s3 = Rudy::AWS::SimpleDB.new(@global.accesskey, @global.secretkey)
85
- end
86
- end
87
- protected :init
88
-
89
- def machine_data
90
- machine_data = {
91
- # Give the machine an identity
92
- :zone => @global.zone,
93
- :environment => @global.environment,
94
- :role => @global.role,
95
- :position => @global.position,
96
-
97
- # Add hosts to the /etc/hosts file
98
- :hosts => {
99
- :dbmaster => "127.0.0.1",
100
- }
101
- }
102
-
103
- machine_data.to_hash
104
- end
105
-
106
-
107
- # Raises exceptions if the requested user does
108
- # not have a valid keypair configured. (See: EC2_KEYPAIR_*)
109
- def check_keys
110
- raise "No SSH key provided for #{@global.user}! (check #{RUDY_CONFIG_FILE})" unless has_keypair?
111
- raise "SSH key provided but cannot be found! (check #{RUDY_CONFIG_FILE})" unless File.exists?(keypairpath)
112
- end
113
-
114
- def has_pem_keys?
115
- (@global.cert && File.exists?(@global.cert) &&
116
- @global.privatekey && File.exists?(@global.privatekey))
117
- end
118
-
119
- def has_keys?
120
- (@global.accesskey && !@global.accesskey.empty? && @global.secretkey && !@global.secretkey.empty?)
121
- end
122
-
123
- def keypairpath(name=nil)
124
- name ||= @global.user
125
- raise "No default user configured" unless name
126
- kp = @config.machines.find(@global.environment, @global.role, :users, name, :keypair2)
127
- kp ||= @config.machines.find(@global.environment, :users, name, :keypair)
128
- kp ||= @config.machines.find(:users, name, :keypair)
129
- kp &&= File.expand_path(kp)
130
- kp
131
- end
132
- def has_keypair?(name=nil)
133
- kp = keypairpath(name)
134
- (!kp.nil? && File.exists?(kp))
135
- end
136
-
137
- # Opens an SSH session.
138
- # <li>+host+ the hostname to connect to. Defaults to the machine specified
139
- # by @global.environment, @global.role, @global.position.</li>
140
- # <li>+b+ a block to execute on the host. Receives |session|</li>
141
- #
142
- # ssh do |session|
143
- # session.exec(cmd)
144
- # end
145
- #
146
- # See Net::SSH
147
- #
148
- def ssh(host=nil, &b)
149
- host ||= machine_hostname
150
- raise "No host provided for SSH" unless host
151
- raise "No block provided for SSH" unless b
152
-
153
- Net::SSH.start(host, @global.user, :keys => [keypairpath]) do |session|
154
- b.call(session)
155
- end
156
- end
157
-
158
- # Secure copy.
159
- #
160
- # scp do |scp|
161
- # # upload a file to a remote server
162
- # scp.upload! "/local/path", "/remote/path"
163
- #
164
- # # upload from an in-memory buffer
165
- # scp.upload! StringIO.new("some data to upload"), "/remote/path"
166
- #
167
- # # run multiple downloads in parallel
168
- # d1 = scp.download("/remote/path", "/local/path")
169
- # d2 = scp.download("/remote/path2", "/local/path2")
170
- # [d1, d2].each { |d| d.wait }
171
- # end
172
- #
173
- def scp(host=nil, &b)
174
- host ||= machine_hostname
175
- raise "No host provided for scp" unless host
176
- raise "No block provided for scp" unless b
177
-
178
- Net::SCP.start(host, @global.user, :keys => [keypairpath]) do |scp|
179
- b.call(scp)
180
- end
181
- end
182
-
183
- # +name+ the name of the remote user to use for the remainder of the command
184
- # (or until switched again). If no name is provided, the user will be revert
185
- # to whatever it was before the previous switch.
186
- def switch_user(name=nil)
187
- if name == nil && @switch_user_previous
188
- @global.user = @switch_user_previous
189
- elsif @global.user != name
190
- puts "Remote commands will be run as #{name} user"
191
- @switch_user_previous = @global.user
192
- @global.user = name
193
- end
194
- end
195
-
196
- # Returns a hash of info for the requested machine. If the requested machine
197
- # is not running, it will raise an exception.
198
- def find_current_machine
199
- find_machine(machine_group)
200
- end
201
-
202
- def find_machine(group)
203
- machine_list = @ec2.instances.list(group)
204
- machine = machine_list.values.first # NOTE: Only one machine per group, for now...
205
- raise "There's no machine running in #{group}" unless machine
206
- raise "The primary machine in #{group} is not in a running state" unless machine[:aws_state] == 'running'
207
- machine
208
- end
209
-
210
- def machine_hostname(group=nil)
211
- group ||= machine_group
212
- find_machine(group)[:dns_name]
213
- end
214
-
215
- def machine_group
216
- [@global.environment, @global.role].join(RUDY_DELIM)
217
- end
218
-
219
- def machine_image
220
- ami = @config.machines.find_deferred(@global.environment, @global.role, :ami)
221
- raise "There is no AMI configured for #{machine_group}" unless ami
222
- ami
223
- end
224
-
225
- def machine_address
226
- @config.machines.find_deferred(@global.environment, @global.role, :address)
227
- end
228
-
229
- # TODO: fix machine_group to include zone
230
- def machine_name
231
- [@global.zone, machine_group, @global.position].join(RUDY_DELIM)
232
- end
233
-
234
- def instance_id?(id=nil)
235
- (id && id[0,2] == "i-")
236
- end
237
-
238
- def image_id?(id=nil)
239
- (id && id[0,4] == "ami-")
240
- end
241
-
242
- def volume_id?(id=nil)
243
- (id && id[0,4] == "vol-")
244
- end
245
-
246
- def snapshot_id?(id=nil)
247
- (id && id[0,5] == "snap-")
248
- end
249
-
250
-
251
- def wait_for_machine(id)
252
-
253
- print "Waiting for #{id} to become available"
254
- STDOUT.flush
255
-
256
- while @ec2.instances.pending?(id)
257
- sleep 2
258
- print '.'
259
- STDOUT.flush
260
- end
261
-
262
- machine = @ec2.instances.get(id)
263
-
264
- puts " It's up!\a\a" # with bells
265
- print "Waiting for SSH daemon at #{machine[:dns_name]}"
266
- STDOUT.flush
267
-
268
- while !Rudy::Utils.service_available?(machine[:dns_name], 22)
269
- print '.'
270
- STDOUT.flush
271
- end
272
- puts " It's up!\a\a\a"
273
-
274
- end
275
-
276
-
277
- def device_to_path(machine, device)
278
- # /dev/sdr 10321208 154232 9642688 2% /rilli/app
279
- dfoutput = ssh_command(machine[:dns_name], keypairpath, @global.user, "df #{device} | tail -1").chomp
280
- dfvals = dfoutput.scan(/(#{device}).+\s(.+?)$/).flatten # ["/dev/sdr", "/rilli/app"]
281
- dfvals.last
282
- end
283
-
284
- # +action+ is one of: :shutdown, :start, :deploy
285
- # +machine+ is a right_aws machine instance hash
286
- def execute_disk_routines(machines, action)
287
- machines = [machines] unless machines.is_a?( Array)
288
-
289
- puts "Running #{action.to_s.capitalize} DISK routines".att(:bright)
290
-
291
- disks = @config.machines.find_deferred(@global.environment, @global.role, :disks)
292
- routines = @config.routines.find(@global.environment, @global.role, action, :disks)
293
-
294
- unless routines
295
- puts "No #{action} disk routines for #{machine_group}"
296
- return
297
- end
298
-
299
- switch_user("root")
300
-
301
- machines.each do |machine|
302
-
303
- unless machine[:aws_instance_id]
304
- puts "Machine given has no instance ID. Skipping disks."
305
- return
306
- end
307
-
308
- unless machine[:dns_name]
309
- puts "Machine given has no DNS name. Skipping disks."
310
- return
311
- end
312
-
313
- if routines.destroy
314
- disk_paths = routines.destroy.keys
315
- vols = @ec2.instances.volumes(machine[:aws_instance_id]) || []
316
- puts "No volumes to destroy for (#{machine[:aws_instance_id]})" if vols.empty?
317
- vols.each do |vol|
318
- disk = Rudy::MetaData::Disk.find_from_volume(@sdb, vol[:aws_id])
319
- if disk
320
- this_path = disk.path
321
- else
322
- puts "No disk metadata for volume #{vol[:aws_id]}. Going old school..."
323
- this_path = device_to_path(machine, vol[:aws_device])
324
- end
325
-
326
- dconf = disks[this_path]
327
-
328
- unless dconf
329
- puts "#{this_path} is not defined for this machine. Check your machines config."
330
- next
331
- end
332
-
333
- if disk_paths.member?(this_path)
334
-
335
- unless disks.has_key?(this_path)
336
- puts "#{this_path} is not defined as a machine disk. Skipping..."
337
- next
338
- end
339
-
340
- begin
341
- puts "Unmounting #{this_path}..."
342
- ssh_command machine[:dns_name], keypairpath, @global.user, "umount #{this_path}"
343
- sleep 3
344
- rescue => ex
345
- puts "Error while unmounting #{this_path}: #{ex.message}"
346
- puts ex.backtrace if Drydock.debug?
347
- puts "We'll keep going..."
348
- end
349
-
350
- begin
351
-
352
- if @ec2.volumes.attached?(disk.awsid)
353
- puts "Detaching #{vol[:aws_id]}"
354
- @ec2.volumes.detach(vol[:aws_id])
355
- sleep 3 # TODO: replace with something like wait_for_machine
356
- end
357
-
358
- puts "Destroying #{this_path} (#{vol[:aws_id]})"
359
- if @ec2.volumes.available?(disk.awsid)
360
- @ec2.volumes.destroy(vol[:aws_id])
361
- else
362
- puts "Volume is still attached (maybe a web server of database is running?)"
363
- end
364
-
365
- if disk
366
- puts "Deleteing metadata for #{disk.name}"
367
- Rudy::MetaData::Disk.destroy(@sdb, disk)
368
- end
369
-
370
- rescue => ex
371
- puts "Error while detaching volume #{vol[:aws_id]}: #{ex.message}"
372
- puts ex.backtrace if Drydock.debug?
373
- puts "Continuing..."
374
- end
375
-
376
- end
377
- puts
378
-
379
- end
380
-
381
- end
382
-
383
-
384
- if routines.mount
385
- disk_paths = routines.mount.keys
386
- vols = @ec2.instances.volumes(machine[:aws_instance_id]) || []
387
- puts "No volumes to mount for (#{machine[:aws_instance_id]})" if vols.empty?
388
- vols.each do |vol|
389
- disk = Rudy::MetaData::Disk.find_from_volume(@sdb, vol[:aws_id])
390
- if disk
391
- this_path = disk.path
392
- else
393
- puts "No disk metadata for volume #{vol[:aws_id]}. Going old school..."
394
- this_path = device_to_path(machine, vol[:aws_device])
395
- end
396
-
397
- next unless disk_paths.member?(this_path)
398
-
399
- dconf = disks[this_path]
400
-
401
- unless dconf
402
- puts "#{this_path} is not defined for this machine. Check your machines config."
403
- next
404
- end
405
-
406
-
407
- begin
408
- unless @ec2.instances.attached_volume?(machine[:aws_instance_id], vol[:aws_device])
409
- puts "Attaching #{vol[:aws_id]} to #{machine[:aws_instance_id]}".att(:bright)
410
- @ec2.volumes.attach(machine[:aws_instance_id], vol[:aws_id],vol[:aws_device])
411
- sleep 3
412
- end
413
-
414
- puts "Mounting #{this_path} to #{vol[:aws_device]}".att(:bright)
415
- ssh_command machine[:dns_name], keypairpath, @global.user, "mkdir -p #{this_path} && mount -t ext3 #{vol[:aws_device]} #{this_path}"
416
-
417
- sleep 1
418
- rescue => ex
419
- puts "There was an error mounting #{this_path}: #{ex.message}"
420
- puts ex.backtrace if Drydock.debug?
421
- end
422
- puts
423
- end
424
- end
425
-
426
-
427
-
428
- if routines.restore
429
-
430
- routines.restore.each_pair do |path,props|
431
- from = props[:from] || "unknown"
432
- unless from.to_s == "backup"
433
- puts "Sorry! You can currently only restore from backup. Check your routines config."
434
- next
435
- end
436
-
437
- begin
438
- puts "Restoring disk for #{path}"
439
-
440
- dconf = disks[path]
441
-
442
- unless dconf
443
- puts "#{path} is not defined for this machine. Check your machines config."
444
- next
445
- end
446
-
447
- zon = props[:zone] || @global.zone
448
- env = props[:environment] || @global.environment
449
- rol = props[:role] || @global.role
450
- pos = props[:position] || @global.position
451
- puts "Looking for backup from #{zon}-#{env}-#{rol}-#{pos}"
452
- backup = find_most_recent_backup(zon, env, rol, pos, path)
453
-
454
- unless backup
455
- puts "No backups found"
456
- next
457
- end
458
-
459
- puts "Found: #{backup.name}".att(:bright)
460
-
461
- disk = Rudy::MetaData::Disk.new
462
- disk.path = path
463
- [:region, :zone, :environment, :role, :position].each do |n|
464
- disk.send("#{n}=", @global.send(n)) if @global.send(n)
465
- end
466
-
467
- disk.device = dconf[:device]
468
- size = (backup.size.to_i > dconf[:size].to_i) ? backup.size : dconf[:size]
469
- disk.size = size.to_i
470
-
471
-
472
- if Rudy::MetaData::Disk.is_defined?(@sdb, disk)
473
- puts "The disk #{disk.name} already exists."
474
- puts "You probably need to define when to destroy the disk."
475
- puts "Skipping..."
476
- next
477
- end
478
-
479
- if @ec2.instances.attached_volume?(machine[:aws_instance_id], disk.device)
480
- puts "Skipping disk for #{disk.path} (device #{disk.device} is in use)"
481
- next
482
- end
483
-
484
- # NOTE: It's important to use Caesars' hash syntax b/c the disk property
485
- # "size" conflicts with Hash#size which is what we'll get if there's no
486
- # size defined.
487
- unless disk.size.kind_of?(Integer)
488
- puts "Skipping disk for #{disk.path} (size not defined)"
489
- next
490
- end
491
-
492
- if disk.path.nil?
493
- puts "Skipping disk for #{disk.path} (no path defined)"
494
- next
495
- end
496
-
497
- unless disk.valid?
498
- puts "Skipping #{disk.name} (not enough info)"
499
- next
500
- end
501
-
502
- puts "Creating volume... (from #{backup.awsid})".att(:bright)
503
- volume = @ec2.volumes.create(@global.zone, disk.size, backup.awsid)
504
-
505
- puts "Attaching #{volume[:aws_id]} to #{machine[:aws_instance_id]}".att(:bright)
506
- @ec2.volumes.attach(machine[:aws_instance_id], volume[:aws_id], disk.device)
507
- sleep 3
508
-
509
- puts "Mounting #{disk.device} to #{disk.path}".att(:bright)
510
- ssh_command machine[:dns_name], keypairpath, @global.user, "mkdir -p #{disk.path} && mount -t ext3 #{disk.device} #{disk.path}"
511
-
512
- puts "Creating disk metadata for #{disk.name}"
513
- disk.awsid = volume[:aws_id]
514
- Rudy::MetaData::Disk.save(@sdb, disk)
515
-
516
- sleep 1
517
- rescue => ex
518
- puts "There was an error creating #{path}: #{ex.message}"
519
- puts ex.backtrace if Drydock.debug?
520
- if disk
521
- puts "Removing metadata for #{disk.name}"
522
- Rudy::MetaData::Disk.destroy(@sdb, disk)
523
- end
524
- end
525
- puts
526
- end
527
- end
528
-
529
-
530
-
531
- if routines.create
532
- routines.create.each_pair do |path,props|
533
-
534
- begin
535
- puts "Creating disk for #{path}"
536
-
537
- dconf = disks[path]
538
-
539
- unless dconf
540
- puts "#{path} is not defined for this machine. Check your machines config."
541
- next
542
- end
543
-
544
- disk = Rudy::MetaData::Disk.new
545
- disk.path = path
546
- [:region, :zone, :environment, :role, :position].each do |n|
547
- disk.send("#{n}=", @global.send(n)) if @global.send(n)
548
- end
549
- [:device, :size].each do |n|
550
- disk.send("#{n}=", dconf[n]) if dconf.has_key?(n)
551
- end
552
-
553
- if Rudy::MetaData::Disk.is_defined?(@sdb, disk)
554
- puts "The disk #{disk.name} already exists."
555
- puts "You probably need to define when to destroy the disk."
556
- puts "Skipping..."
557
- next
558
- end
559
-
560
- if @ec2.instances.attached_volume?(machine[:aws_instance_id], disk.device)
561
- puts "Skipping disk for #{disk.path} (device #{disk.device} is in use)"
562
- next
563
- end
564
-
565
- # NOTE: It's important to use Caesars' hash syntax b/c the disk property
566
- # "size" conflicts with Hash#size which is what we'll get if there's no
567
- # size defined.
568
- unless disk.size.kind_of?(Integer)
569
- puts "Skipping disk for #{disk.path} (size not defined)"
570
- next
571
- end
572
-
573
- if disk.path.nil?
574
- puts "Skipping disk for #{disk.path} (no path defined)"
575
- next
576
- end
577
-
578
- unless disk.valid?
579
- puts "Skipping #{disk.name} (not enough info)"
580
- next
581
- end
582
-
583
- puts "Creating volume... (#{disk.size}GB in #{@global.zone})".att(:bright)
584
- volume = @ec2.volumes.create(@global.zone, disk.size)
585
-
586
- puts "Attaching #{volume[:aws_id]} to #{machine[:aws_instance_id]}".att(:bright)
587
- @ec2.volumes.attach(machine[:aws_instance_id], volume[:aws_id], disk.device)
588
- sleep 6
589
-
590
- puts "Creating the filesystem (mkfs.ext3 -F #{disk.device})".att(:bright)
591
- ssh_command machine[:dns_name], keypairpath, @global.user, "mkfs.ext3 -F #{disk.device}"
592
- sleep 3
593
-
594
- puts "Mounting #{disk.device} to #{disk.path}".att(:bright)
595
- ssh_command machine[:dns_name], keypairpath, @global.user, "mkdir -p #{disk.path} && mount -t ext3 #{disk.device} #{disk.path}"
596
-
597
- puts "Creating disk metadata for #{disk.name}"
598
- disk.awsid = volume[:aws_id]
599
- Rudy::MetaData::Disk.save(@sdb, disk)
600
-
601
- sleep 1
602
- rescue => ex
603
- puts "There was an error creating #{path}: #{ex.message}"
604
- if disk
605
- puts "Removing metadata for #{disk.name}"
606
- Rudy::MetaData::Disk.destroy(@sdb, disk)
607
- end
608
- end
609
- puts
610
- end
611
- end
612
- end
613
- end
614
-
615
- def find_most_recent_backup(zon, env, rol, pos, path)
616
- criteria = [zon, env, rol, pos, path]
617
- (Rudy::MetaData::Backup.list(@sdb, *criteria) || []).first
618
- end
619
-
620
- def execute_routines(machines, action, before_or_after)
621
- machines = [machines] unless machines.is_a?( Array)
622
- config = @config.routines.find_deferred(@global.environment, @global.role, :config) || {}
623
- config[:global] = @global.marshal_dump
624
- config[:global].reject! { |n,v| n == :cert || n == :privatekey }
625
-
626
- # The config file contains settings from ~/.rudy/config
627
- #
628
- # routines do
629
- # config do
630
- # end
631
- # end
632
- #
633
- config_file = "#{action}-config.yaml"
634
- tf = Tempfile.new(config_file)
635
- write_to_file(tf.path, config.to_hash.to_yaml, 'w')
636
- puts "Running #{action.to_s.capitalize} #{before_or_after.to_s.upcase} routines".att(:bright)
637
- machines.each do |machine|
638
- puts "Machine Group: #{machine_group}"
639
- puts "Hostname: #{machine[:dns_name]}"
640
-
641
- rscripts = @config.routines.find_deferred(@global.environment, @global.role, action, before_or_after) || []
642
- rscripts = [rscripts] unless rscripts.is_a?(Array)
643
-
644
- puts "No scripts defined." if !rscripts || rscripts.empty?
645
-
646
- rscripts.each do |rscript|
647
- user, script = rscript.shift
648
-
649
- switch_user(user) # scp and ssh will run as this user
650
-
651
- puts "Transfering #{config_file}..."
652
- scp do |scp|
653
- scp.upload!(tf.path, "~/#{config_file}") do |ch, name, sent, total|
654
- "#{name}: #{sent}/#{total}"
655
- end
656
- end
657
- ssh do |session|
658
- puts "Running #{script}...".att(:bright)
659
- session.exec!("chmod 700 ~/#{config_file}")
660
- session.exec!("chmod 700 #{script}")
661
- puts session.exec!("#{script}")
662
-
663
- puts "Removing remote copy of #{config_file}..."
664
- session.exec!("rm ~/#{config_file}")
665
- end
666
- puts $/
667
- end
668
- end
669
-
670
- tf.delete # remove local copy of config_file
671
- #switch_user # return to the requested user
672
- end
673
-
674
- # Print a default header to the screen for every command.
675
- # +cmd+ is the name of the command current running.
676
- def print_header(cmd=nil)
677
- title = "RUDY v#{Rudy::VERSION}" unless @global.quiet
678
- now_utc = Time.now.utc.strftime("%Y-%m-%d %H:%M:%S")
679
- criteria = []
680
- [:zone, :environment, :role, :position].each do |n|
681
- val = @global.send(n)
682
- next unless val
683
- criteria << "#{n.to_s.slice(0,1).att :normal}:#{val.att :bright}"
684
- end
685
- puts '%s -- %s UTC' % [title, now_utc] unless @global.quiet
686
- puts '[%s]' % criteria.join(" ") unless @global.quiet
687
-
688
- puts unless @global.quiet
689
-
690
- if (@global.environment == "prod")
691
- msg = without_indent %q(
692
- =======================================================
693
- =======================================================
694
- !!!!!!!!! YOU ARE PLAYING WITH PRODUCTION !!!!!!!!!
695
- =======================================================
696
- =======================================================)
697
- puts msg.colour(:red).bgcolour(:white).att(:bright), $/ unless @global.quiet
698
-
699
- end
700
-
701
- if Rudy.in_situ?
702
- msg = %q(============ THIS IS EC2 ============)
703
- puts msg.colour(:blue).bgcolour(:white).att(:bright), $/ unless @global.quiet
704
- end
705
-
706
- end
707
-
708
- def print_footer
709
-
710
- end
711
-
712
-
713
-
714
-
715
- def group_metadata(env=@global.environment, role=@global.role)
716
- query = "['environment' = '#{env}'] intersection ['role' = '#{role}']"
717
- @sdb.query_with_attributes(RUDY_DOMAIN, query)
718
- end
719
-
720
- private
721
- # Print info about a running instance
722
- # +inst+ is a hash
723
- def print_instance(inst)
724
- puts '-'*60
725
- puts "Instance: #{inst[:aws_instance_id].att(:bright)} (AMI: #{inst[:aws_image_id]})"
726
- [:aws_state, :dns_name, :private_dns_name, :aws_availability_zone, :aws_launch_time, :ssh_key_name].each do |key|
727
- printf(" %22s: %s#{$/}", key, inst[key]) if inst[key]
728
- end
729
- printf(" %22s: %s#{$/}", 'aws_groups', inst[:aws_groups].join(', '))
730
- puts
731
- end
732
-
733
- def print_image(img)
734
- puts '-'*60
735
- puts "Image: #{img[:aws_id].att(:bright)}"
736
- img.each_pair do |key, value|
737
- printf(" %22s: %s#{$/}", key, value) if value
738
- end
739
- puts
740
- end
741
-
742
- def print_disk(disk, backups=[])
743
- puts '-'*60
744
- puts "Disk: #{disk.name.att(:bright)}"
745
- puts disk.to_s
746
- puts "#{backups.size} most recent backups:", backups.collect { |back| "#{back.nice_time} (#{back.awsid})" }
747
- puts
748
- end
749
-
750
-
751
- def print_volume(vol, disk)
752
- puts '-'*60
753
- puts "Volume: #{vol[:aws_id].att(:bright)} (disk: #{disk.name if disk})"
754
- vol.each_pair do |key, value|
755
- printf(" %22s: %s#{$/}", key, value) if value
756
- end
757
- puts
758
- end
759
-
760
- # Print info about a a security group
761
- # +group+ is an OpenStruct
762
- def print_group(group)
763
- puts '-'*60
764
- puts "%12s: %s" % ['GROUP', group[:aws_group_name].att(:bright)]
765
- puts
766
-
767
- group_ip = {}
768
- group[:aws_perms].each do |perm|
769
- (group_ip[ perm[:cidr_ips] ] ||= []) << "#{perm[:protocol]}/#{perm[:from_port]}-#{perm[:to_port]}"
770
- end
771
-
772
- puts "%22s %s" % ["source address/mask", "protocol/ports (from, to)"]
773
-
774
-
775
- group_ip.each_pair do |ip, perms|
776
- puts "%22s %s" % [ip, perms.shift]
777
- perms.each do |perm|
778
- puts "%22s %s" % ['', perm]
779
- end
780
- puts
781
- end
782
- end
783
-
784
- def init_config_dir
785
- unless File.exists?(RUDY_CONFIG_DIR)
786
- puts "Creating #{RUDY_CONFIG_DIR}"
787
- Dir.mkdir(RUDY_CONFIG_DIR, 0700)
788
- end
789
-
790
- unless File.exists?(RUDY_CONFIG_FILE)
791
- puts "Creating #{RUDY_CONFIG_FILE}"
792
- rudy_config = without_indent %Q{
793
- # Amazon Web Services
794
- # Account access indentifiers.
795
- awsinfo do
796
- account ""
797
- accesskey ""
798
- secretkey ""
799
- privatekey "~/path/2/pk-xxxx.pem"
800
- cert "~/path/2/cert-xxxx.pem"
801
- end
802
-
803
- # Machine Configuration
804
- # Specify your private keys here. These can be defined globally
805
- # or by environment and role like in machines.rb.
806
- machines do
807
- users do
808
- root :keypair => "path/2/root-private-key"
809
- end
810
- end
811
-
812
- # Routine Configuration
813
- # Define stuff here that you don't want to be stored in version control.
814
- routines do
815
- config do
816
- # ...
817
- end
818
- end
819
-
820
- # Global Defaults
821
- # Define the values to use unless otherwise specified on the command-line.
822
- defaults do
823
- region "us-east-1"
824
- zone "us-east-1b"
825
- environment "stage"
826
- role "app"
827
- position "01"
828
- user ENV['USER']
829
- end
830
- }
831
- write_to_file(RUDY_CONFIG_FILE, rudy_config, 'w')
832
- end
833
-
834
- #puts "Creating SimpleDB domain called #{RUDY_DOMAIN}"
835
- #@sdb.domains.create(RUDY_DOMAIN)
836
- end
837
- end
838
- end
839
- end
840
-
841
-