rudy 0.3.0 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
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
-