rudy 0.3.0 → 0.3.2

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.
data/bin/rudy CHANGED
@@ -27,7 +27,7 @@ require 'drydock'
27
27
  require 'rudy'
28
28
  extend Drydock
29
29
 
30
- debug :off
30
+
31
31
 
32
32
  default :commands
33
33
 
@@ -35,11 +35,17 @@ default :commands
35
35
  global_usage "rudy [global options] COMMAND [command options]"
36
36
  global_option :A, :access_key, "AWS Access Key", String
37
37
  global_option :S, :secret_key, "AWS Secret Access Key", String
38
+ global_option :R, :region, String, "Connect to a specific EC2 region (default: #{Rudy::DEFAULT_REGION})"
39
+
40
+ #global_option :z, :zone, String, "Connect to a specific EC2 zone (default: #{Rudy::DEFAULT_ZONE})"
41
+ global_option :e, :environment, String, "Connect to the specified environment (default: #{Rudy::DEFAULT_ENVIRONMENT})"
42
+ global_option :r, :role, String, "Connect to a machine with the specified role (defalt: #{Rudy::DEFAULT_ROLE})"
43
+ global_option :p, :position, String, "Position in the machine in its group (default: #{Rudy::DEFAULT_POSITION})"
44
+
45
+ global_option :u, :user, String, "Provide a username (default: #{Rudy::DEFAULT_USER})"
46
+
47
+ #global_option :c, :config, String, "Specify the config file to read (default: #{Rudy::RUDY_CONFIG})"
38
48
 
39
- global_option :p, :position, String, "Position in the machine in its group (default: 01)"
40
- global_option :u, :user, String, "Provide a username (default: rudy)"
41
- global_option :e, :environment, String, "Connect to the specified environment (default: stage)"
42
- global_option :r, :role, String, "Connect to a machine with the specified role (defalt: app)"
43
49
 
44
50
  global_option :V, :version, "Display version number" do
45
51
  puts "Rudy version: #{Rudy::VERSION}"
@@ -49,8 +55,11 @@ global_option :v, :verbose, "Increase verbosity of output (i.e. -v or -vv or -vv
49
55
  @verbose ||= 0
50
56
  @verbose += 1
51
57
  end
58
+ global_option :q, :quiet, "Run with less output"
52
59
 
53
- command :commands do
60
+ command :commands => Rudy::Command::Metadata do |obj|
61
+ obj.print_header
62
+
54
63
  puts "Rudy can do all of these things:", ""
55
64
  command_names.sort.each do |cmd|
56
65
  puts " %16s" % cmd
@@ -60,6 +69,8 @@ command :commands do
60
69
  puts
61
70
  end
62
71
 
72
+ debug :off
73
+
63
74
  usage "rudy init"
64
75
  command :init => Rudy::Command::Metadata do |obj|
65
76
  obj.setup
@@ -70,6 +81,13 @@ command :info => Rudy::Command::Metadata do |obj|
70
81
  obj.info
71
82
  end
72
83
 
84
+ option :a, :all, "Display config settings for all machines"
85
+ option :d, :defaults, "Display the default value for the supplied parameter"
86
+ usage "rudy [-f config-file] config [param-name]"
87
+ command :config => Rudy::Command::Environment do |obj, argv|
88
+ obj.config(argv.first)
89
+ end
90
+
73
91
 
74
92
  option :e, :external, "Display only external IP address"
75
93
  option :i, :internal, "Display only internal IP address"
@@ -172,7 +190,8 @@ command :disks => Rudy::Command::Disks do |obj, argv|
172
190
  end
173
191
  end
174
192
 
175
-
193
+ option :s, :snapshot, String, "Create a backup entry from an existing snapshot"
194
+ option :Z, :synchronize, "Check for and delete backup metadata with no snapshot. DOES NOT delete snapshots."
176
195
  #option :T, :tidy, "Tidy existing backups"
177
196
  option :D, :destroy, "Destroy a backup"
178
197
  option :C, :create, "Create a backup"
@@ -181,7 +200,13 @@ command :backups => Rudy::Command::Disks do |obj, argv|
181
200
  capture(:stderr) do
182
201
  obj.print_header
183
202
  if obj.create
184
- obj.create_backup
203
+ obj.create_backup(argv.first)
204
+ elsif obj.synchronize
205
+ unless argv.empty?
206
+ puts "The disk you specified will be ignored."
207
+ argv.clear
208
+ end
209
+ obj.sync_backups
185
210
  elsif obj.destroy
186
211
  raise "No backup specified" if argv.empty?
187
212
  #exit unless are_you_sure?
@@ -235,7 +260,7 @@ option :a, :account, String, "Your Amazon Account Number"
235
260
  option :i, :image_name, String, "The name of the image"
236
261
  option :b, :bucket_name, String, "The name of the bucket that will store the image"
237
262
  option :C, :create, "Create an image"
238
- option :D, :destroy, "Deregister an image"
263
+ option :D, :destroy, "Deregister an image (currently _does not_ remove images files from S3)"
239
264
  usage "rudy images [-C -i name -b bucket -a account] [-D AMI-ID]"
240
265
  command :images => Rudy::Command::Images do |obj, argv|
241
266
  capture(:stderr) do
@@ -245,14 +270,19 @@ command :images => Rudy::Command::Images do |obj, argv|
245
270
  puts "Make sure the machine is clean. I don't want archive no crud!"
246
271
  exit unless are_you_sure?
247
272
  obj.create_image
273
+
248
274
  elsif obj.destroy
249
275
  obj.deregister(argv.first)
276
+
250
277
  else
251
278
  obj.print_images
252
279
  end
280
+
253
281
  end
254
282
  end
255
283
 
284
+
285
+
256
286
  command :stage => Rudy::Command::Stage do |obj|
257
287
  capture(:stderr) do
258
288
  obj.print_header
data/lib/rudy.rb CHANGED
@@ -7,6 +7,65 @@ require 'yaml'
7
7
  require 'console'
8
8
  require 'storable'
9
9
 
10
+ module Rudy #:nodoc:
11
+ RUDY_DOMAIN = ENV['RUDY_DOMAIN'] || "rudy_state"
12
+ RUDY_DELIM = ENV['RUDY_DELIM'] || '-'
13
+ RUDY_CONFIG = File.join(ENV['HOME'] || ENV['USERPROFILE'], '.rudy')
14
+
15
+ DEFAULT_REGION = ENV['EC2_DEFAULT_REGION'] || 'us-east-1'
16
+ DEFAULT_ZONE = ENV['EC2_DEFAULT_ZONE'] || 'us-east-1b'
17
+ DEFAULT_ENVIRONMENT = ENV['EC2_DEFAULT_ENVIRONMENT'] || 'stage'
18
+ DEFAULT_ROLE = ENV['EC2_DEFAULT_ROLE'] || 'app'
19
+ DEFAULT_POSITION = ENV['EC2_DEFAULT_POSITION'] || '01'
20
+
21
+ DEFAULT_USER = ENV['EC2_DEFAULT_USER'] || 'rudy'
22
+
23
+ module VERSION #:nodoc:
24
+ MAJOR = 0.freeze unless defined? MAJOR
25
+ MINOR = 3.freeze unless defined? MINOR
26
+ TINY = 2.freeze unless defined? TINY
27
+ def self.to_s
28
+ [MAJOR, MINOR, TINY].join('.')
29
+ end
30
+ def self.to_f
31
+ self.to_s.to_f
32
+ end
33
+ end
34
+
35
+ # Determine if we're running directly on EC2 or
36
+ # "some other machine". We do this by checking if
37
+ # the file /etc/ec2/instance-id exists. This
38
+ # file is written by /etc/init.d/rudy-ec2-startup.
39
+ # NOTE: Is there a way to know definitively that this is EC2?
40
+ # We could make a request to the metadata IP addresses.
41
+ def self.in_situ?
42
+ File.exists?('/etc/ec2/instance-id')
43
+ end
44
+
45
+ class Config < Storable
46
+
47
+ attr_accessor :path
48
+
49
+ field :userdata => Hash
50
+
51
+ def initialize(args={:path => nil})
52
+ @path = args[:path] || RUDY_CONFIG
53
+ @userdata = {
54
+ 'default' => {
55
+ }
56
+ }
57
+ end
58
+
59
+ def get(name)
60
+
61
+ end
62
+
63
+ def exists?
64
+ File.exists?(@path)
65
+ end
66
+ end
67
+ end
68
+
10
69
  require 'rudy/aws'
11
70
  require 'rudy/metadata'
12
71
  require 'rudy/scm/svn'
@@ -26,26 +85,6 @@ rescue LoadError => ex
26
85
  exit 1
27
86
  end
28
87
 
29
- module Rudy #:nodoc:
30
- RUDY_DOMAIN = ENV['RUDY_DOMAIN'] || "rudy_state"
31
- RUDY_DELIM = ENV['RUDY_DELIM'] || '-'
32
- DEFAULT_REGION = ENV['EC2_DEFAULT_REGION'] || 'us-east-1'
33
- DEFAULT_ZONE = ENV['EC2_DEFAULT_ZONE'] || 'us-east-1b'
34
-
35
- module VERSION #:nodoc:
36
- MAJOR = 0.freeze unless defined? MAJOR
37
- MINOR = 3.freeze unless defined? MINOR
38
- TINY = 0.freeze unless defined? TINY
39
- def self.to_s
40
- [MAJOR, MINOR, TINY].join('.')
41
- end
42
- def self.to_f
43
- self.to_s.to_f
44
- end
45
- end
46
- end
47
-
48
-
49
88
 
50
89
  # Capture STDOUT or STDERR to prevent it from being printed.
51
90
  #
data/lib/rudy/aws/ec2.rb CHANGED
@@ -2,6 +2,10 @@
2
2
  module Rudy::AWS
3
3
 
4
4
  class EC2
5
+ class UserData
6
+
7
+ end
8
+
5
9
  class Images
6
10
  include Rudy::AWS::ObjectBase
7
11
 
@@ -31,6 +31,8 @@ module Rudy
31
31
  attr_reader :domains
32
32
  attr_reader :machine_images
33
33
 
34
+ attr_reader :config
35
+
34
36
  def init
35
37
  @access_key = ENV['AWS_ACCESS_KEY'] unless @access_key
36
38
  @secret_key = ENV['AWS_SECRET_KEY'] unless @secret_key
@@ -42,14 +44,16 @@ module Rudy
42
44
  if ENV['RUDY_SVN_BASE']
43
45
  @scm = Rudy::SCM::SVN.new(ENV['RUDY_SVN_BASE'])
44
46
  end
47
+
48
+ @config = File.exists?(RUDY_CONFIG) ? Rudy::Config.from_file(RUDY_CONFIG) : Rudy::Config.new
45
49
 
46
- @user ||= 'rudy'
47
- @environment ||= 'stage'
48
- @role ||= 'app'
49
- @position ||= '01'
50
-
51
- @zone ||= DEFAULT_ZONE
52
50
  @region ||= DEFAULT_REGION
51
+ @zone ||= DEFAULT_ZONE
52
+ @environment ||= DEFAULT_ENVIRONMENT
53
+ @role ||= DEFAULT_ROLE
54
+ @position ||= DEFAULT_POSITION
55
+
56
+ @user ||= DEFAULT_USER
53
57
 
54
58
  @keypairs = {}
55
59
  ENV.keys.select { |key| key.match /EC2_KEYPAIR/i }.each do |key|
@@ -81,12 +85,15 @@ module Rudy
81
85
  end
82
86
  end
83
87
 
84
- @ec2 = Rudy::AWS::EC2.new(@access_key, @secret_key)
85
- # RightAws::SdbInterface is on thin ice. No select support! Or sort!
86
- @sdb = Rudy::AWS::SimpleDB.new(@access_key, @secret_key)
87
- #@s3 = Rudy::AWS::SimpleDB.new(@access_key, @secret_key)
88
+ if @access_key && @secret_key
89
+ @ec2 = Rudy::AWS::EC2.new(@access_key, @secret_key)
90
+ # RightAws::SdbInterface is on thin ice. No select support! Or sort!
91
+ @sdb = Rudy::AWS::SimpleDB.new(@access_key, @secret_key)
92
+ #@s3 = Rudy::AWS::SimpleDB.new(@access_key, @secret_key)
93
+ end
88
94
  end
89
95
 
96
+
90
97
  # Raises exceptions if the requested user does
91
98
  # not have a valid keypair configured. (See: EC2_KEYPAIR_*)
92
99
  def check_keys
@@ -119,8 +126,9 @@ module Rudy
119
126
  @machine_images[machine_group]
120
127
  end
121
128
 
129
+ # TODO: fix machine_group to include zone
122
130
  def machine_name
123
- [machine_group, @position].join(RUDY_DELIM)
131
+ [@zone, machine_group, @position].join(RUDY_DELIM)
124
132
  end
125
133
 
126
134
  def keypairname
@@ -213,8 +221,13 @@ module Rudy
213
221
  !!!!!!!!! YOU ARE PLAYING WITH PRODUCTION !!!!!!!!!
214
222
  =======================================================
215
223
  =======================================================)
216
- puts msg.color(:bright, :red, :bg_white)
217
- puts
224
+ puts msg.color(:bright, :red, :bg_white), $/ unless @quiet
225
+
226
+ end
227
+
228
+ if Rudy.in_situ?
229
+ msg = %q(============ THIS IS EC2 ============)
230
+ puts msg.color(:bright, :blue, :bg_white), $/ unless @quiet
218
231
  end
219
232
 
220
233
  end
@@ -13,6 +13,43 @@ module Rudy
13
13
  end
14
14
  end
15
15
 
16
+ # Check for backups pointing to snapshots that don't exist.
17
+ def sync_backups
18
+ criteria = [@zone]
19
+ criteria += [@environment, @role] unless @all
20
+
21
+ puts "Looking for backup metadata with delinquent snapshots..."
22
+ to_be_deleted = {} # snap-id => backup
23
+ Rudy::MetaData::Backup.list(@sdb, *criteria).each do |backup|
24
+ to_be_deleted[backup.awsid] = backup unless @ec2.snapshots.exists?(backup.awsid)
25
+ end
26
+
27
+ if to_be_deleted.empty?
28
+ puts "All backups are in-sync with snapshots. Nothing to do."
29
+ return
30
+ end
31
+
32
+ puts
33
+ puts "These backup metadata will be deleted:"
34
+ to_be_deleted.each do |snap_id, backup|
35
+ puts "%s: %s" % [snap_id, backup.name]
36
+ end
37
+
38
+ puts
39
+ are_you_sure?
40
+
41
+ puts
42
+ puts "Deleting..."
43
+ to_be_deleted.each do |snap_id, backup|
44
+ print " -> #{backup.name}... "
45
+ @sdb.destroy(RUDY_DOMAIN, backup.name)
46
+ puts "done"
47
+ end
48
+
49
+ puts "Done!"
50
+ end
51
+
52
+
16
53
  def destroy_backup(name)
17
54
  puts "Destroying #{name}"
18
55
  begin
@@ -36,53 +73,90 @@ module Rudy
36
73
  puts "Done."
37
74
  end
38
75
 
39
- def create_backup
76
+ def create_backup(diskname=nil)
40
77
  machine = find_current_machine
41
- disks = Rudy::MetaData::Disk.list(@sdb, machine[:aws_availability_zone], @environment, @role, @position)
42
- volumes = @ec2.instances.volumes(machine[:aws_instance_id])
43
-
44
- puts "Machine: #{machine_name}"
45
78
 
79
+ disks = Rudy::MetaData::Disk.list(@sdb, machine[:aws_availability_zone], @environment, @role, @position)
46
80
  raise "The machine #{machine_name} does not have any disk metadata" if disks.empty?
47
- raise "The machine #{machine_name} does not have any volumes attached." if volumes.empty?
48
81
 
49
- puts "#{disks.size} Disk(s) defined with #{volumes.size} Volume(s) running"
82
+ puts "Machine: #{machine_name}"
50
83
 
51
- volumes.each do |volume|
52
- print "Volume #{volume[:aws_id]}... "
53
- disk = Rudy::MetaData::Disk.from_volume(@sdb, volume[:aws_id])
54
- backup = Rudy::MetaData::Backup.new
55
-
56
- # TODO: Look for the disk based on the machine
57
- raise "No disk associated to volume #{volume[:aws_id]}" unless disk
84
+ if @snapshot
85
+ raise "You must supply a diskname when using an existing snapshot" unless diskname
86
+ raise "The snapshot #{@snapshot} does not exist" unless @ec2.snapshots.exists?(@snapshot)
87
+ disk = Rudy::MetaData::Disk.get(@sdb, diskname)
58
88
 
89
+ raise "The disk #{diskname} does not exist" unless disk
90
+ backup = Rudy::MetaData::Backup.new
91
+ backup.awsid = @snapshot
59
92
  backup.time_stamp
60
- backup.volume = volume[:aws_id]
61
-
93
+
62
94
  # Populate machine infos
63
95
  [:zone, :environment, :role, :position].each do |n|
64
96
  val = instance_variable_get("@#{n}")
65
97
  backup.send("#{n}=", val) if val
66
98
  end
67
-
99
+
68
100
  # Populate disk infos
69
101
  [:path, :size].each do |n|
70
102
  backup.send("#{n}=", disk.send(n)) if disk.send(n)
71
103
  end
72
104
 
73
- snap = @ec2.snapshots.create(volume[:aws_id])
74
-
75
- if !snap || !snap.is_a?(Hash)
76
- puts "There was an unknown problem creating #{backup.name}. Continuing..."
77
- next
78
- end
79
-
80
- backup.awsid = snap[:aws_id]
81
105
 
82
106
  Rudy::MetaData::Backup.save(@sdb, backup)
83
107
 
84
108
  puts backup.name
109
+
110
+ else
111
+ volumes = @ec2.instances.volumes(machine[:aws_instance_id])
112
+ raise "The machine #{machine_name} does not have any volumes attached." if volumes.empty?
113
+
114
+ puts "#{disks.size} Disk(s) defined with #{volumes.size} Volume(s) running"
115
+
116
+ volumes.each do |volume|
117
+ print "Volume #{volume[:aws_id]}... "
118
+ disk = Rudy::MetaData::Disk.from_volume(@sdb, volume[:aws_id])
119
+ backup = Rudy::MetaData::Backup.new
120
+
121
+ # TODO: Look for the disk based on the machine
122
+ raise "No disk associated to volume #{volume[:aws_id]}" unless disk
123
+
124
+ backup.volume = volume[:aws_id]
125
+
126
+ snap = @ec2.snapshots.create(volume[:aws_id])
127
+
128
+ backup.awsid = snap[:aws_id]
129
+
130
+ if !snap || !snap.is_a?(Hash)
131
+ puts "There was an unknown problem creating #{backup.name}. Continuing with the next volume..."
132
+ next
133
+ end
134
+
135
+ backup.time_stamp
136
+
137
+ # Populate machine infos
138
+ [:zone, :environment, :role, :position].each do |n|
139
+ val = instance_variable_get("@#{n}")
140
+ backup.send("#{n}=", val) if val
141
+ end
142
+
143
+ # Populate disk infos
144
+ [:path, :size].each do |n|
145
+ backup.send("#{n}=", disk.send(n)) if disk.send(n)
146
+ end
147
+
148
+
149
+ Rudy::MetaData::Backup.save(@sdb, backup)
150
+
151
+ puts backup.name
152
+
153
+ end
154
+
85
155
  end
156
+
157
+
158
+
159
+
86
160
  end
87
161
 
88
162
 
@@ -101,7 +175,8 @@ module Rudy
101
175
 
102
176
  print_disks
103
177
  end
104
-
178
+
179
+
105
180
  def print_disks
106
181
  criteria = [@zone]
107
182
  criteria += [@environment, @role] unless @all
@@ -14,7 +14,58 @@ module Rudy
14
14
  module Command
15
15
  class Environment < Rudy::Command::Base
16
16
 
17
+
18
+ # Display configuration from the local user data file (~/.rudy).
19
+ # This config contains user data which is sent to each EC2 when
20
+ # it's created.
21
+ #
22
+ # The primary purpose of this command is to give other apps a way
23
+ # to check various configuration values. (This is probably mostly
24
+ # useful on an instance itself).
25
+ #
26
+ # # Display the default value.
27
+ # $ rudy config -d param-name
28
+ #
29
+ # # Display the value for a specific machine.
30
+ # $ rudy -e prod -r db config param-name
31
+ #
32
+ # # Display all configuration
33
+ # $ rudy config --all
34
+ #
35
+ def config(name=nil)
36
+ return unless @config.exists?
37
+ puts "Config: #{@config.path}" if @verbose > 0
17
38
 
39
+
40
+ which = @defaults ? @user : machine_name
41
+ puts "Machine: #{which}" if @verbose > 0
42
+
43
+ return unless @config.userdata && @config.userdata.is_a?(Hash)
44
+ #return unless @config.userdata[which] && @config.userdata[which].is_a?(Hash)
45
+
46
+ # The .rudy config files have a different structure.
47
+ # There's no userdata -> which. Just the config settings.
48
+ if Rudy.in_situ?
49
+ if name && @config.userdata.has_key?(name)
50
+ puts @config.userdata[name]
51
+ else
52
+ puts @config.to_yaml
53
+ end
54
+ else
55
+ if name && @config.userdata.has_key?(which)
56
+ value = @config.userdata[which][name.to_s]
57
+ puts value if value
58
+ elsif @all
59
+ puts @config.to_yaml
60
+ else
61
+ value = @config.userdata[which]
62
+ puts value.to_yaml if value
63
+ end
64
+ end
65
+
66
+ end
67
+
68
+
18
69
  def connect
19
70
  check_keys
20
71
  machine = find_current_machine
@@ -33,64 +84,7 @@ module Rudy
33
84
  end
34
85
 
35
86
 
36
- # Start EC2 instances to run the stage.
37
- # Returns a hash in the same format as +instances+
38
- def build_stage
39
-
40
- rig = running_instance_groups(@app_group)
41
- raise "You already have a stage: #{rig.join(', ')}" unless rig.empty?
42
-
43
- root_keypair_name = File.basename(@app_root_keypair)
44
-
45
- # This is read by the Rudy start up script /etc/init.d/rudy-ec2-startup
46
- user_data = {
47
- :dbmaster => "localhost",
48
- :role => "FE",
49
- :env => "stage"
50
- }
51
-
52
- ret = @ec2.run_instances(@app_ami, 1, 1, [@app_group], root_keypair_name, user_data.to_yaml, 'public')
53
- puts "The instance has started. Please wait while it boots..."
54
- puts "Check '#{$0} state' for aws_state 'running'."
55
- puts "Then run '#{$0} stage --start"
56
- associate_address
57
- ret
58
- end
59
-
60
-
61
- # Start (rake bootstrap, etc...) the rails app on the stage
62
- def start_stage
63
-
64
- rig = running_instance_groups(@app_group)
65
- raise "There is no stage start!" if rig.empty?
66
- cmds =[]
67
- cmds.each do |cmd|
68
- puts cmd
69
- `#{cmd}`
70
- end
71
-
72
- end
73
-
74
- # Shutdown all instances in the stage security group
75
- def destroy_stage
76
-
77
- rig = running_instance_groups(@app_group)
78
- raise "There is no stage to destroy!" if rig.empty?
79
- stopped = []
80
- rig.each do |inst|
81
- stopped << inst[:aws_instance_id]
82
- end
83
- destroy_instances(stopped)
84
- end
85
87
 
86
- #def svn_tag_exists?(rtag, prefix)
87
- # return false if rtag.empty?
88
- # `svn info tag/`
89
- #end
90
-
91
- def tag_release
92
-
93
- end
94
88
 
95
89
  end
96
90
  end
@@ -50,14 +50,36 @@ module Rudy
50
50
  raise "No SSH key provided for #{keypairname}!" unless has_keypair?(keypairname)
51
51
  raise "SSH key provided but cannot be found! (#{keypairpath})" unless File.exists?(keypairpath)
52
52
 
53
- user_data = {
54
- :dbmaster => "localhost",
53
+
54
+ machine_data = {
55
+ # Give the machine an identity
56
+ :zone => @zone,
57
+ :environment => @environment,
55
58
  :role => @role,
56
- :env => @environment
59
+ :position => @position,
60
+
61
+ # Add hosts to the /etc/hosts file
62
+ :hosts => {
63
+ :dbmaster => "127.0.0.1",
64
+ },
65
+
66
+ :userdata => {}
57
67
  }
58
68
 
69
+ # Populate userdata with settings from ~/.rudy
70
+ if @config.userdata.is_a?(Hash) && !@config.userdata.empty?
71
+ # Build a set of parameters for each user on the requested
72
+ # machine. Set default values first and overwrite. (TODO)
73
+ @config.userdata.each_pair do |n,hash|
74
+ user = n # TODO: should be prepared to parse "us-east-1b-stage-app-rudy" and "us-east-1b-stage-app-01-rudy"
75
+ machine_data[:userdata][user] = hash
76
+ end
77
+ end
78
+
59
79
  puts "Starting an instance in #{machine_group}"
60
- instances = @ec2.instances.create(@image, machine_group.to_s, File.basename(keypairpath), user_data.to_yaml, @zone)
80
+ puts "with userdata:", machine_data.to_yaml
81
+
82
+ instances = @ec2.instances.create(@image, machine_group.to_s, File.basename(keypairpath), machine_data.to_yaml, @zone)
61
83
  inst = instances.first
62
84
  id, state = inst[:aws_instance_id], inst[:aws_state]
63
85
 
@@ -0,0 +1,8 @@
1
+
2
+
3
+
4
+ module Rudy
5
+ module MetaData
6
+
7
+ end
8
+ end
data/lib/storable.rb CHANGED
@@ -86,8 +86,9 @@ class Storable
86
86
  end
87
87
 
88
88
  # Create a new instance of the object using data from file.
89
- def self.from_file(file_path=nil, format=nil)
89
+ def self.from_file(file_path, format='yaml')
90
90
  raise "Cannot read file (#{file_path})" unless File.exists?(file_path)
91
+ raise "#{self} doesn't support from_#{format}" unless self.respond_to?("from_#{format}")
91
92
  format = format || File.extname(file_path).tr('.', '')
92
93
  me = send("from_#{format}", read_file_to_array(file_path))
93
94
  me.format = format
@@ -116,6 +117,8 @@ class Storable
116
117
 
117
118
  if field_types[index] == Array
118
119
  ((value ||= []) << stored_value).flatten
120
+ elsif field_types[index] == Hash
121
+ value = stored_value
119
122
  else
120
123
  # SimpleDB stores attribute shit as lists of values
121
124
  value = stored_value.first if stored_value.is_a?(Array) && stored_value.size == 1
data/rudy.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  @spec = Gem::Specification.new do |s|
2
2
  s.name = "rudy"
3
- s.version = "0.3.0"
3
+ s.version = "0.3.2"
4
4
  s.summary = "Rudy is a handy staging and deployment tool for Amazon EC2."
5
5
  s.description = "Rudy is a handy staging and deployment tool for Amazon EC2."
6
6
  s.author = "Delano Mandelbaum"
@@ -27,7 +27,6 @@
27
27
  lib/rudy/aws/simpledb.rb
28
28
  lib/rudy/command/addresses.rb
29
29
  lib/rudy/command/base.rb
30
- lib/rudy/command/commit.rb
31
30
  lib/rudy/command/disks.rb
32
31
  lib/rudy/command/environment.rb
33
32
  lib/rudy/command/groups.rb
@@ -38,8 +37,8 @@
38
37
  lib/rudy/command/volumes.rb
39
38
  lib/rudy/metadata.rb
40
39
  lib/rudy/metadata/backup.rb
40
+ lib/rudy/metadata/config.rb
41
41
  lib/rudy/metadata/disk.rb
42
- lib/rudy/metadata/ec2startup.rb
43
42
  lib/rudy/metadata/environment.rb
44
43
  lib/rudy/scm/svn.rb
45
44
  lib/rudy/utils.rb
@@ -2,17 +2,14 @@
2
2
 
3
3
  # what: Rudy EC2 startup script
4
4
  # who: delano@solutious.com
5
- # when: 2009-02-20 (rev: 3)
5
+ # when: 2009-02-25 (rev: 4)
6
+
7
+ # NOTE: This is a prototype version of this script. A cleaner version
8
+ # with better documentation is forthcoming.
6
9
 
7
10
  # Runs when an ec2 instance startups up.
8
11
  # Grabs configuration from the run time user data and stores it locally.
9
- # Expects message data in the yaml format:
10
- #
11
- # :dbmaster: IP ADDRESS or LOCAL HOSTNAME (default: localhost)
12
- # :role: app, db, etc... (default: app)
13
- # :env: stage, prod (default: stage)
14
- # :access_key: amazon access key
15
- # :secret_key: amazon secret key
12
+ # See Rudy::Command::Instances#start_instance for the expected yaml format.
16
13
 
17
14
  # Put this in /etc/init.d. Then:
18
15
  # * chmod 755 rudy-ec2-startup
@@ -23,145 +20,155 @@
23
20
  # * cd /etc/rc5.d
24
21
  # * ln -s ../init.d/rudy-ec2-startup S17rudy
25
22
 
26
- # NOTE: This is a prototype version of this script. A cleaner version
27
- # with better documentation is forthcoming.
28
-
29
-
30
23
  require 'yaml'
31
24
  require 'resolv'
32
25
 
33
26
  LOGFILE = '/var/log/rudy-ec2-startup'
34
27
  USERDATA = 'http://169.254.169.254/2008-02-01/user-data'
35
28
  METADATA = 'http://169.254.169.254/2008-02-01/meta-data'
36
- METADATAPARAMS = ['instance-id', 'instance-type']
37
29
 
30
+ #LOGFILE = '/tmp/rudy-ec2-startup'
31
+ #USERDATA = 'http://127.0.0.1/2008-02-01/user-data'
32
+ #METADATA = 'http://127.0.0.1/2008-02-01/meta-data'
38
33
 
39
- def get_metadata
40
- metadata = {}
41
-
42
- begin
43
- METADATAPARAMS.each do |param|
44
- log " ---> #{param}"
45
- metadata[param.to_s] = run("curl -s #{METADATA}/#{param}")
46
- end
47
- rescue => ex
48
- log("Problem getting metadata: #{ex.message}")
49
- end
50
- metadata
51
- end
34
+ METADATAPARAMS = ['instance-id', 'instance-type']
52
35
 
53
- def run(command, input='')
54
- IO.popen(command, 'r+') do |io|
55
- #io.puts input
56
- #io.close_write
57
- return io.read
58
- end
59
- end
36
+ RUDY_CONFIG_FILE='.rudy'
60
37
 
61
- def get_formatted_time
62
- t = Time.now
63
- t_out = t.strftime("%H:%M:%S%p (%m/%d/%Y)")
64
- end
38
+ module Rudy
39
+ module EC2Startup
40
+ extend self
41
+
42
+ VERSION = 4.freeze
43
+
44
+ def process_config
45
+ begin
46
+ File.unlink(LOGFILE) if File.exists?(LOGFILE)
47
+ log "RUDY (rev#{VERSION}) is in the house"
48
+
49
+ log "Grabing configuration..."
50
+ config_yaml = run("curl -s #{USERDATA}")
51
+
52
+ log "Grabing meta-data..."
53
+ metadata = get_metadata
54
+
55
+ log "Processing metadata..."
56
+ METADATAPARAMS.each do |name|
57
+ default = name.gsub('instance-', '')
58
+ value = (metadata[name] && !metadata[name].empty?) ? metadata[name] : default
59
+ log " ---> #{name}: #{value}"
60
+ write_to_file("/etc/ec2/#{name}", value, "w")
61
+ end
62
+
63
+ log "Processing userdata..."
64
+
65
+ if !config_yaml || config_yaml.empty?
66
+ raise "Nothing to process (did you supply user-data when you created the instance)"
67
+ end
68
+
69
+ config = YAML::load(config_yaml)
70
+ config ||= {}
71
+
72
+ zone = config[:zone] ? config[:zone].downcase : "zone"
73
+ environment = config[:environment] ? config[:environment].downcase : "env"
74
+ role = config[:role] ? config[:role].downcase : "role"
75
+ position = config[:position] ? config[:position].downcase : "00"
76
+
77
+ hostname = [zone, environment, role, position].join('-')
78
+ log "Setting hostname: #{hostname}"
79
+ `hostname #{hostname}`
80
+
81
+ config[:hosts] ||= {}
82
+ config[:hosts][hostname] = '127.0.0.1'
83
+
84
+ if config[:userdata] && config[:userdata].is_a?(Hash)
85
+ # TODO: How to we get the path to any user's home directory?
86
+ # (when we're not running as that user.)
87
+ config[:userdata].each_pair do |n,hash|
88
+ fileparts = (n === "root") ? ['/root'] : ['/home', n]
89
+ fileparts << RUDY_CONFIG_FILE
90
+ filepath = File.join(fileparts)
91
+ log "Writing to #{filepath}"
92
+ user_data = {
93
+ :userdata => hash
94
+ }
95
+ write_to_file(filepath, user_data.to_yaml, "w")
96
+ end
97
+ end
98
+
99
+ if config[:hosts] && config[:hosts].is_a?(Hash)
100
+ log "Updating /etc/hosts..."
101
+ config[:hosts].each_pair do |name, address|
102
+ # Respect existing entries
103
+ next if read_file('/etc/hosts') =~ /#{name}/
104
+
105
+ ip_address = (address !~ /^\d.+/) ? Resolv.getaddress(address) : address
106
+
107
+ log " ---> #{name}: #{ip_address}"
108
+ write_to_file("/etc/hosts", "\n#{ip_address}\t#{name}\n")
109
+ end
110
+ end
111
+
112
+ log "Done!"
113
+
114
+ rescue => ex
115
+ log "ERROR: #{ex.message}"
116
+ end
117
+ end
118
+
119
+
120
+ def get_metadata
121
+ metadata = {}
122
+
123
+ begin
124
+ METADATAPARAMS.each do |param|
125
+ log " ---> #{param}"
126
+ metadata[param.to_s] = run("curl -s #{METADATA}/#{param}")
127
+ metadata[param.to_s] = nil if metadata[param.to_s].empty?
128
+ end
129
+ rescue => ex
130
+ log("Problem getting metadata: #{ex.message}")
131
+ end
132
+ metadata
133
+ end
65
134
 
66
- def write_to_file (filename, s, type='a')
67
- f = File.open(filename,type)
68
- f.puts s
69
- f.close
70
- end
135
+ def run(command, input='')
136
+ IO.popen(command, 'r+') do |io|
137
+ io.read
138
+ end
139
+ end
71
140
 
72
- def read_file(path)
73
- read_file_to_array(path).join('')
74
- end
141
+ def get_formatted_time
142
+ t = Time.now
143
+ t_out = t.strftime("%H:%M:%S%p (%m/%d/%Y)")
144
+ end
75
145
 
76
- def read_file_to_array(path)
77
- contents = []
78
- return contents unless File.exists?(path)
79
- open(path, 'r') do |l|
80
- contents = l.readlines
81
- end
82
- contents
83
- end
146
+ def write_to_file (filename, s, type='a')
147
+ f = File.open(filename,type)
148
+ f.puts s
149
+ f.close
150
+ end
84
151
 
85
- def log(s)
86
- msg = "#{get_formatted_time}: #{s}"
87
- write_to_file(LOGFILE, msg)
88
- puts msg
89
- end
152
+ def read_file(path)
153
+ read_file_to_array(path).join('')
154
+ end
90
155
 
91
- begin
92
- File.unlink(LOGFILE) if File.exists?(LOGFILE)
93
- log "Deleted previous logfile (#{LOGFILE})"
94
- log "Grabing configuration..."
95
- config_yaml = run("curl -s #{USERDATA}")
96
-
97
- log "Grabing meta-data..."
98
- metadata = get_metadata
99
-
100
- raise "Ooooooops, no configuration" if !config_yaml || config_yaml.empty?
101
-
102
- config = YAML::load(config_yaml)
103
-
104
- if config && config[:role]
105
- myrole = config[:role].downcase
106
-
107
- else
108
- myrole = "app"
109
- end
110
-
111
- log "My role is: #{myrole}"
112
-
113
- env = config && config[:env] ? config[:env] : "stage"
114
-
115
- unless read_file('/etc/hosts') =~ /dbmaster/
116
- if config && config[:dbmaster]
117
- log "Updating dbmaster in /etc/hosts..."
118
-
119
- case config[:dbmaster]
120
- when /^\d.+/
121
- ip_address = config[:dbmaster]
122
- else
123
- ip_address = Resolv.getaddress(config[:dbmaster])
156
+ def read_file_to_array(path)
157
+ contents = []
158
+ return contents unless File.exists?(path)
159
+ open(path, 'r') do |l|
160
+ contents = l.readlines
124
161
  end
125
-
126
- log "The IP address for my dbmaster is: #{ip_address}"
127
- write_to_file("/etc/hosts", "\n#{ip_address}\tdbmaster\n")
128
-
129
- else
130
- write_to_file("/etc/hosts", "\n127.0.0.1\tdbmaster\n")
162
+ contents
131
163
  end
132
- end
133
-
134
164
 
135
- hostname = ""
136
- myid = "default-"
137
- if metadata['instance-id']
138
- myid = metadata['instance-id'][2..metadata['instance-id'].length]
139
- log "My ID: #{myid}"
140
- write_to_file("/etc/instance-id", myid, "w")
141
- end
142
-
143
- mytype = "unknown"
144
- if metadata['instance-type']
145
- mytype = metadata['instance-type']
146
- log "My instance type: #{metadata['instance-type']}"
147
- write_to_file("/etc/instance-type", metadata['instance-type'], "w")
148
- end
149
-
150
-
151
- hostname = "#{env}-#{myrole}-#{mytype.gsub('.', '')}-#{myid}"
152
- log "Setting hostname to #{hostname}"
153
- `hostname #{hostname}`
154
-
155
- unless read_file('/etc/hosts') =~ /#{hostname}/
156
- log "Adding an entry to /etc/hosts for: #{hostname}"
157
- write_to_file("/etc/hosts", "\n127.0.0.1\t#{hostname}")
165
+ def log(s)
166
+ msg = "#{get_formatted_time}: #{s}"
167
+ write_to_file(LOGFILE, msg)
168
+ puts msg
169
+ end
170
+
158
171
  end
159
-
160
- log "Done!"
161
-
162
- rescue => ex
163
- log ex.message
164
172
  end
165
-
166
-
167
173
 
174
+ Rudy::EC2Startup.process_config
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rudy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Delano Mandelbaum
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-02-25 00:00:00 -05:00
12
+ date: 2009-02-26 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -40,7 +40,6 @@ files:
40
40
  - lib/rudy/aws/simpledb.rb
41
41
  - lib/rudy/command/addresses.rb
42
42
  - lib/rudy/command/base.rb
43
- - lib/rudy/command/commit.rb
44
43
  - lib/rudy/command/disks.rb
45
44
  - lib/rudy/command/environment.rb
46
45
  - lib/rudy/command/groups.rb
@@ -51,8 +50,8 @@ files:
51
50
  - lib/rudy/command/volumes.rb
52
51
  - lib/rudy/metadata.rb
53
52
  - lib/rudy/metadata/backup.rb
53
+ - lib/rudy/metadata/config.rb
54
54
  - lib/rudy/metadata/disk.rb
55
- - lib/rudy/metadata/ec2startup.rb
56
55
  - lib/rudy/metadata/environment.rb
57
56
  - lib/rudy/scm/svn.rb
58
57
  - lib/rudy/utils.rb
@@ -1,10 +0,0 @@
1
-
2
-
3
- module Rudy
4
- module Command
5
- class Commit < Rudy::Command::Base
6
-
7
- end
8
- end
9
- end
10
-
@@ -1,2 +0,0 @@
1
-
2
-