rudy 0.2.4 → 0.3.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.
@@ -4,6 +4,87 @@ module Rudy
4
4
  module Command
5
5
  class Disks < Rudy::Command::Base
6
6
 
7
+ def print_backups
8
+ criteria = [@zone]
9
+ criteria += [@environment, @role] unless @all
10
+
11
+ Rudy::MetaData::Backup.list(@sdb, *criteria).each do |backup|
12
+ puts "%s (%s)" % [backup.name, backup.awsid]
13
+ end
14
+ end
15
+
16
+ def destroy_backup(name)
17
+ puts "Destroying #{name}"
18
+ begin
19
+ backup = Rudy::MetaData::Backup.get(@sdb, name)
20
+ rescue => ex
21
+ puts "Error deleteing backup: #{ex.message}"
22
+ end
23
+
24
+ return unless backup
25
+
26
+ begin
27
+ puts " -> deleting snapshot..."
28
+ @ec2.snapshots.destroy(backup.awsid)
29
+ rescue => ex
30
+ puts "Error deleting snapshot: #{ex.message}."
31
+ puts "Continuing..."
32
+ ensure
33
+ puts " -> deleting metadata..."
34
+ @sdb.destroy(RUDY_DOMAIN, name)
35
+ end
36
+ puts "Done."
37
+ end
38
+
39
+ def create_backup
40
+ 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
+
46
+ 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
+
49
+ puts "#{disks.size} Disk(s) defined with #{volumes.size} Volume(s) running"
50
+
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
58
+
59
+ backup.time_stamp
60
+ backup.volume = volume[:aws_id]
61
+
62
+ # Populate machine infos
63
+ [:zone, :environment, :role, :position].each do |n|
64
+ val = instance_variable_get("@#{n}")
65
+ backup.send("#{n}=", val) if val
66
+ end
67
+
68
+ # Populate disk infos
69
+ [:path, :size].each do |n|
70
+ backup.send("#{n}=", disk.send(n)) if disk.send(n)
71
+ end
72
+
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
+
82
+ Rudy::MetaData::Backup.save(@sdb, backup)
83
+
84
+ puts backup.name
85
+ end
86
+ end
87
+
7
88
 
8
89
  def create_disk
9
90
  disk = Rudy::MetaData::Disk.new
@@ -26,17 +107,43 @@ module Rudy
26
107
  criteria += [@environment, @role] unless @all
27
108
 
28
109
  Rudy::MetaData::Disk.list(@sdb, *criteria).each do |disk|
29
- print_disk disk
110
+ backups = Rudy::MetaData::Backup.for_disk(@sdb, disk, 2)
111
+ print_disk(disk, backups)
112
+
30
113
  end
31
114
 
32
115
  end
33
116
 
117
+ def unattach_disk(name)
118
+ puts "Looking for #{name}"
119
+ disk = Rudy::MetaData::Disk.get(@sdb, name)
120
+ instances = @ec2.instances.list(machine_group)
121
+ @user = "root"
122
+ check_keys
123
+ raise "That is not a valid disk" unless disk
124
+ raise "There are no instances running in #{machine_group}" if !instances || instances.empty?
125
+ raise "The disk has no attached volume " unless disk.awsid
126
+
127
+ machine = instances.values.first
128
+
129
+ puts "Unmounting #{disk.path}..."
130
+ ssh machine[:dns_name], keypairpath, user, "umount #{disk.path}"
131
+ sleep 1
132
+
133
+ puts "Detaching #{disk.awsid}"
134
+ @ec2.volumes.detach(disk.awsid)
135
+
136
+ Rudy::MetaData::Backup.for_disk(@sdb, disk, 2)
137
+
138
+ puts "Done!"
139
+ end
140
+
34
141
  def attach_disk(name)
35
142
  puts "Looking for #{name}"
36
143
  disk = Rudy::MetaData::Disk.get(@sdb, name)
37
144
  instances = @ec2.instances.list(machine_group)
38
145
  raise "There are no instances running in #{machine_group}" if !instances || instances.empty?
39
- instance_id = instances.keys.first
146
+ instance_id = instances.keys.first # <--- TODO: This is bad!
40
147
  machine = instances.values.first
41
148
 
42
149
  do_dirty_disk_volume_deeds(disk, machine)
@@ -14,29 +14,21 @@ module Rudy
14
14
  module Command
15
15
  class Environment < Rudy::Command::Base
16
16
 
17
- # Returns a hash of info for the requested machine. If the requested machine
18
- # is not running, it will raise an exception.
19
- def find_requested_machine
20
- machine_list = @ec2.instances.list(machine_group)
21
- machine = machine_list.values.first # NOTE: Only one machine per group, for now...
22
- raise "There's no machine running in #{machine_group}" unless machine
23
- raise "The primary machine in #{machine_group} is not in a running state" unless machine[:aws_state] == 'running'
24
17
 
25
- machine
26
- end
27
-
28
18
  def connect
29
19
  check_keys
30
- machine = find_requested_machine
20
+ machine = find_current_machine
31
21
  ssh machine[:dns_name], keypairpath, user, false, false, false, @print
32
22
  end
33
23
 
34
24
  # +paths+ an array of paths to copy. The last element is the "to" path.
35
25
  def copy(paths)
36
26
  check_keys
37
- machine = find_requested_machine
27
+ machine = find_current_machine
28
+
38
29
  paths = paths.flatten
39
30
  to_path = paths.pop
31
+
40
32
  scp machine[:dns_name], keypairpath, user, paths, to_path, @remote, false, @print
41
33
  end
42
34
 
@@ -53,8 +53,21 @@ module Rudy
53
53
  ssh machine[:dns_name], keypairpath, user, "ec2-bundle-vol -r i386 -p #{@image_name} -k /mnt/pk-*pem -c /mnt/cert*pem -u #{@account}"
54
54
  ssh machine[:dns_name], keypairpath, user, "ec2-upload-bundle -b #{@bucket_name} -m /tmp/#{@image_name}.manifest.xml -a #{@access_key} -s #{@secret_key}"
55
55
 
56
- # Why did I not use the Ruby API?
57
- sh "ec2-register -K #{@ec2_private_key} -C #{@ec2_cert} #{@bucket_name}/#{@image_name}.manifest.xml"
56
+ @ec2.images.register("#{@bucket_name}/#{@image_name}.manifest.xml")
57
+ end
58
+
59
+ def deregister(ami)
60
+ raise "You must supply an AMI ID (ami-XXXXXXX)" unless ami
61
+ puts "Deregistering AMI: #{ami}"
62
+
63
+ are_you_sure?
64
+
65
+ if @ec2.images.deregister(ami)
66
+ puts "Done!"
67
+ else
68
+ puts "There was an unknown problem!"
69
+ end
70
+
58
71
  end
59
72
 
60
73
  end
@@ -35,7 +35,7 @@ module Rudy
35
35
 
36
36
  puts "Running #{basename}..."
37
37
  scp machine[:dns_name], keypairpath, user, @rscripts[keypairname], "~/"
38
- ssh machine[:dns_name], keypairpath, user, "chmod 755 ~/#{basename} && ~/#{basename} test #{@access_key} #{@secret_key} r1ll1r1ll1"
38
+ ssh machine[:dns_name], keypairpath, user, "chmod 755 ~/#{basename} && ~/#{basename} #{@access_key} #{@secret_key}"
39
39
 
40
40
  end
41
41
 
@@ -4,9 +4,46 @@ module Rudy
4
4
  module Command
5
5
  class Volumes < Rudy::Command::Base
6
6
 
7
+ def destroy_volume(id)
8
+ raise "No volume ID provided" unless id
9
+ raise "I will not help you destroy production!" if @environment == "prod"
10
+ raise "The volume #{id} doesn't exist!" unless @ec2.volumes.exists?(id)
11
+ disk = Rudy::MetaData::Disk.from_volume(@sdb, id)
12
+
13
+ puts "Destroying #{id}!"
14
+ @ec2.volumes.destroy id
15
+
16
+ if disk
17
+ disk.awsid = nil
18
+ end
19
+
20
+ Rudy::MetaData::Disk.save(@sdb, disk)
21
+
22
+ end
23
+
7
24
  def print_volumes
8
- y @ec2.volumes.list
25
+ machines = {}
26
+ volumes = @ec2.volumes.list
27
+ @ec2.volumes.list.each do |volume|
28
+ machine = @ec2.instances.get(volume[:aws_instance_id])
29
+ machines[ volume[:aws_instance_id] ] ||= {
30
+ :machine => machine,
31
+ :volumes => []
32
+ }
33
+ machines[ volume[:aws_instance_id] ][:volumes] << volume
34
+ end
35
+
36
+ machines.each_pair do |instance_id, hash|
37
+ machine = hash[:machine]
38
+ env = (machine[:aws_groups]) ? machine[:aws_groups] : "Not-attached"
39
+ puts "Environment: #{env}"
40
+ hash[:volumes].each do |vol|
41
+ disk = Rudy::MetaData::Disk.from_volume(@sdb, vol[:aws_id])
42
+ print_volume(vol, disk)
43
+ end
44
+ end
9
45
  end
46
+
10
47
  end
11
48
  end
12
49
  end
@@ -0,0 +1,160 @@
1
+
2
+ require 'date'
3
+
4
+ module Rudy
5
+ module MetaData
6
+ class Backup < Storable
7
+ include Rudy::MetaData::ObjectBase
8
+ extend Rudy::MetaData::ObjectBase
9
+
10
+ @@rtype = "back"
11
+
12
+ field :rtype
13
+ field :awsid
14
+
15
+ field :region
16
+ field :zone
17
+ field :environment
18
+ field :role
19
+ field :position
20
+ field :path
21
+
22
+ field :date
23
+ field :time
24
+ field :second
25
+
26
+ field :unixtime => Integer
27
+
28
+ field :size
29
+ field :volume
30
+
31
+ def initialize
32
+ @zone = DEFAULT_ZONE
33
+ @region = DEFAULT_REGION
34
+ @position = "01"
35
+ @rtype = @@rtype
36
+ end
37
+
38
+ def rtype
39
+ @@rtype
40
+ end
41
+
42
+ def self.rtype
43
+ @@rtype
44
+ end
45
+
46
+
47
+ def name
48
+ time = Time.at(@unixtime)
49
+ Backup.generate_name(@zone, @environment, @role, @position, @path, time)
50
+ end
51
+
52
+ def valid?
53
+ @zone && @environment && @role && @position && @path && @date && @time && @sec
54
+ end
55
+
56
+ def time_stamp
57
+ #return [@date, @time] if @date && @time
58
+ now = Time.now.utc
59
+ datetime = Backup.format_timestamp(now).split(RUDY_DELIM)
60
+ @unixtime = now.to_i
61
+ @date, @time, @second = datetime
62
+ end
63
+
64
+ def nice_time
65
+ return "" unless @date && @time
66
+ t = @date.scan(/(\d\d\d\d)(\d\d)(\d\d)/).join('-')
67
+ t << " " << @time.scan(/(\d\d)(\d\d)/).join(':')
68
+ t
69
+ end
70
+
71
+ def to_query(more=[], remove=[])
72
+ criteria = [:rtype, :zone, :environment, :role, :position, :path, :date, :time, :second, *more]
73
+ criteria -= [*remove].flatten
74
+ query = "select * from #{RUDY_DOMAIN} where unixtime > '0' "
75
+ criteria.each do |n|
76
+ query << "and #{n} = '#{self.send(n.to_sym)}'"
77
+ end
78
+ query << " order by unixtime desc"
79
+ end
80
+
81
+ def to_s
82
+ str = ""
83
+ field_names.each do |key|
84
+ str << sprintf(" %22s: %s#{$/}", key, self.send(key.to_sym))
85
+ end
86
+ str
87
+ end
88
+
89
+ def disk
90
+ Disk.generate_name(@zone, @environment, @role, @position, @path)
91
+ end
92
+
93
+ # 20090224-1813-36
94
+ def Backup.format_timestamp(dat)
95
+ mon, day, hour, min, sec = [dat.mon, dat.day, dat.hour, dat.min, dat.sec].collect { |v| v.to_s.rjust(2, "0") }
96
+ [dat.year, mon, day, RUDY_DELIM, hour, min, RUDY_DELIM, sec].join
97
+ end
98
+
99
+ # Times are converted to UTC
100
+ # back-us-east-1b-stage-app-01-rilli-app-20090224-1813-36
101
+ def Backup.generate_name(zon, env, rol, pos, pat, dat, sep=File::SEPARATOR)
102
+ raise "The date you provided is not a Time object" unless dat.is_a?(Time)
103
+ pos = pos.to_s.rjust 2, '0'
104
+ dirs = pat.split sep if pat
105
+ dirs.shift while dirs && (dirs[0].nil? || dirs[0].empty?)
106
+ timestamp = Backup.format_timestamp(dat.utc)
107
+ [@@rtype, zon, env, rol, pos, dirs, timestamp].flatten.join(RUDY_DELIM)
108
+ end
109
+
110
+
111
+ def Backup.for_disk(sdb, disk, max=50)
112
+ list = Backup.list(sdb, disk.zone, disk.environment, disk.role, disk.position, disk.path) || []
113
+ list[0..(max-1)]
114
+ end
115
+
116
+ def Backup.get(sdb, name)
117
+ object = sdb.get_attributes(RUDY_DOMAIN, name)
118
+ raise "Object #{name} does not exist!" unless object.has_key?(:attributes) && !object[:attributes].empty?
119
+ self.from_hash(object[:attributes])
120
+ end
121
+
122
+ def Backup.save(sdb, obj, replace = :replace)
123
+ sdb.store(RUDY_DOMAIN, obj.name, obj.to_hash, replace)
124
+ end
125
+
126
+ def Backup.list(sdb, zon, env=nil, rol=nil, pos=nil, path=nil, date=nil)
127
+ query = "select * from #{RUDY_DOMAIN} where "
128
+ query << "rtype = '#{rtype}' "
129
+ query << " and zone = '#{zon}'" if zon
130
+ query << " and environment = '#{env}'" if env
131
+ query << " and role = '#{rol}'" if rol
132
+ query << " and position = '#{pos}'" if pos
133
+ query << " and path = '#{path}'" if path
134
+ query << " and date = '#{date}'" if date
135
+ query << " and unixtime != '0' order by unixtime desc"
136
+ list = []
137
+ sdb.select(query).each do |obj|
138
+ list << self.from_hash(obj)
139
+ end
140
+ list
141
+ end
142
+
143
+ def Backup.destroy(sdb, name)
144
+ back = Backup.get(sdb, name) # get raises an exception if the disk doesn't exist
145
+ sdb.destroy(RUDY_DOMAIN, name)
146
+ true # wtf: RightAws::SimpleDB doesn't tell us whether it succeeds. We'll assume!
147
+ end
148
+
149
+
150
+
151
+ def Backup.is_defined?(sdb, backup)
152
+ query = backup.to_query()
153
+ puts query
154
+ !sdb.select(query).empty?
155
+ end
156
+
157
+ end
158
+ end
159
+ end
160
+
@@ -1,17 +1,17 @@
1
1
 
2
2
 
3
3
  module Rudy
4
- class MetaData
5
- attr_accessor :sdb
6
-
7
- def initalize(sdb)
8
- @sdb = sdb
9
- end
10
-
11
- end
12
- class MetaData
4
+
5
+ module MetaData
13
6
  class Disk < Storable
14
7
 
8
+ @@rtype = 'disk'
9
+
10
+ # This is a flag used internally to specify that a volume has been
11
+ # created for this disk, but not formated.
12
+ attr_accessor :raw_volume
13
+
14
+ field :rtype
15
15
  field :awsid
16
16
 
17
17
  field :environment
@@ -22,7 +22,7 @@ module Rudy
22
22
  field :zone
23
23
  field :region
24
24
  field :device
25
- field :backups => Array
25
+ #field :backups => Array
26
26
  field :size
27
27
 
28
28
  def initialize
@@ -30,7 +30,15 @@ module Rudy
30
30
  @zone = DEFAULT_ZONE
31
31
  @region = DEFAULT_REGION
32
32
  @backups = []
33
-
33
+ @rtype = @@rtype.to_s
34
+ @raw_volume = false
35
+ end
36
+
37
+ def rtype
38
+ @@rtype.to_s
39
+ end
40
+
41
+ def rtype=(val)
34
42
  end
35
43
 
36
44
  def name
@@ -42,11 +50,11 @@ module Rudy
42
50
  end
43
51
 
44
52
  def to_query(more=[], remove=[])
45
- criteria = [:zone, :environment, :role, :position, :path, *more]
53
+ criteria = [:rtype, :zone, :environment, :role, :position, :path, *more]
46
54
  criteria -= [*remove].flatten
47
55
  query = []
48
56
  criteria.each do |n|
49
- query << "['#{n}' = '#{self.send(n.to_sym)}']"
57
+ query << "['#{n}' = '#{self.send(n.to_sym)}'] "
50
58
  end
51
59
  query.join(" intersection ")
52
60
  end
@@ -68,6 +76,7 @@ module Rudy
68
76
 
69
77
  def Disk.get(sdb, name)
70
78
  disk = sdb.get_attributes(RUDY_DOMAIN, name)
79
+
71
80
  raise "Disk #{name} does not exist!" unless disk && disk.has_key?(:attributes)
72
81
  Rudy::MetaData::Disk.from_hash(disk[:attributes])
73
82
  end
@@ -89,6 +98,16 @@ module Rudy
89
98
  !sdb.query_with_attributes(RUDY_DOMAIN, query).empty?
90
99
  end
91
100
 
101
+ def Disk.from_volume(sdb, vol_id)
102
+ query = "['awsid' = '#{vol_id}']"
103
+ res = sdb.query_with_attributes(RUDY_DOMAIN, query)
104
+ if res.empty?
105
+ nil
106
+ else
107
+ disk = Rudy::MetaData::Disk.from_hash(res.values.first)
108
+ end
109
+ end
110
+
92
111
  def Disk.update_volume(sdb, ec2, disk, machine)
93
112
 
94
113
  disk = Disk.get(sdb, disk) if disk.is_a?(String)
@@ -103,19 +122,29 @@ module Rudy
103
122
  unless disk.awsid
104
123
  puts "No active EBS volume found for #{disk.name}"
105
124
 
106
- backup = disk.backups.last
107
- if backup
108
- puts "We'll use the most recent backup..."
109
- volume = ec2.volumes.create(disk.zone, disk.size, backup)
110
- puts "Attaching #{disk.awsid} to #{id} (#{disk.device})"
111
- ec2.volumes.attach(machine[:aws_instance_id], disk.awsid, disk.device)
125
+ # TODO: pull actual backups
126
+ backups = Rudy::MetaData::Backup.for_disk(sdb, disk, 2)
127
+
128
+ if backups.is_a?(Array) && !backups.empty?
129
+ backup = backups.first
130
+ if ec2.snapshots.exists?(backup.awsid)
131
+ puts "We'll use the most recent backup (#{backup.awsid})..."
132
+ volume = ec2.volumes.create(disk.zone, disk.size, backup.awsid)
133
+ else
134
+ puts "The backup refers to a snapshot that doesn't exist."
135
+ puts backup.name, backup.awsid
136
+ puts "You need to delete this backup metadata before continuing."
137
+ exit 1
138
+ end
112
139
  else
113
140
  puts "We'll create one from scratch..."
114
141
  volume = ec2.volumes.create(disk.zone, disk.size, nil)
115
- disk.awsid = volume[:aws_id]
116
- puts "Saving disk metadata"
117
- Disk.save(sdb, disk)
142
+ disk.raw_volume = true
118
143
  end
144
+
145
+ puts "Saving disk metadata"
146
+ disk.awsid = volume[:aws_id]
147
+ Disk.save(sdb, disk)
119
148
  puts ""
120
149
  end
121
150
 
@@ -124,13 +153,15 @@ module Rudy
124
153
 
125
154
  def Disk.list(sdb, zon, env=nil, rol=nil, pos=nil)
126
155
  query = ''
127
- query << "['zone' = '#{zon}']" if zon
156
+ query << "['rtype' = '#{@@rtype}']" if zon
157
+ query << " intersection ['zone' = '#{zon}']" if zon
128
158
  query << " intersection ['environment' = '#{env}']" if env
129
159
  query << " intersection ['role' = '#{rol}']" if rol
130
160
  query << " intersection ['position' = '#{pos}']" if pos
131
161
 
132
162
  list = []
133
163
  sdb.query_with_attributes(RUDY_DOMAIN, query).each_pair do |name, hash|
164
+ #puts "DISK: #{hash.to_yaml}"
134
165
  list << Rudy::MetaData::Disk.from_hash(hash)
135
166
  end
136
167
  list
@@ -0,0 +1,2 @@
1
+
2
+
@@ -0,0 +1,26 @@
1
+
2
+ require 'aws_sdb'
3
+
4
+ module Rudy
5
+ module MetaData
6
+ attr_accessor :sdb
7
+ attr_accessor :ec2
8
+
9
+ def initalize(sdb, ec2)
10
+ @sdb = sdb
11
+ @ec2 = ec2
12
+ end
13
+
14
+ end
15
+
16
+ module MetaData
17
+
18
+ module ObjectBase
19
+ extend self
20
+
21
+
22
+
23
+ end
24
+
25
+ end
26
+ end
data/lib/rudy.rb CHANGED
@@ -4,9 +4,11 @@ require 'stringio'
4
4
  require 'ostruct'
5
5
  require 'yaml'
6
6
 
7
+ require 'console'
7
8
  require 'storable'
8
9
 
9
10
  require 'rudy/aws'
11
+ require 'rudy/metadata'
10
12
  require 'rudy/scm/svn'
11
13
  require 'rudy/utils'
12
14
  require 'rudy/command/base'
@@ -32,8 +34,8 @@ module Rudy #:nodoc:
32
34
 
33
35
  module VERSION #:nodoc:
34
36
  MAJOR = 0.freeze unless defined? MAJOR
35
- MINOR = 2.freeze unless defined? MINOR
36
- TINY = 4.freeze unless defined? TINY
37
+ MINOR = 3.freeze unless defined? MINOR
38
+ TINY = 0.freeze unless defined? TINY
37
39
  def self.to_s
38
40
  [MAJOR, MINOR, TINY].join('.')
39
41
  end
@@ -68,10 +70,34 @@ def capture(stream)
68
70
  result
69
71
  end
70
72
 
71
- def you_are_sure?
72
- print "Are you sure? "
73
- STDIN.gets =~ /^y|yes|ya$/i
73
+
74
+ def are_you_sure?(len=3)
75
+ if STDIN.tty? # Only ask a question if there's a human
76
+ challenge = strand len
77
+ STDOUT.print "Are you sure? To continue type \"#{challenge}\": "
78
+ STDOUT.flush
79
+ if ((STDIN.gets || "").gsub(/["']/, '') =~ /^#{challenge}$/)
80
+ true
81
+ else
82
+ puts "Nothing changed"
83
+ exit 0
84
+ end
85
+ else
86
+ true
87
+ end
88
+ end
89
+
90
+ #
91
+ # Generates a string of random alphanumeric characters
92
+ # These are used as IDs throughout the system
93
+ def strand( len=8, safe=true )
94
+ chars = ("a".."z").to_a + ("0".."9").to_a
95
+ chars = [("a".."h").to_a, "j", "k", "m", "n", ("p".."z").to_a, ("2".."9").to_a].flatten if safe
96
+ newpass = ""
97
+ 1.upto(len) { |i| newpass << chars[rand(chars.size-1)] }
98
+ newpass
74
99
  end
100
+
75
101
  def sh(command, chdir=false)
76
102
  prevdir = Dir.pwd
77
103
  Dir.chdir chdir if chdir
@@ -92,20 +118,25 @@ end
92
118
 
93
119
 
94
120
  def scp(host, keypair, user, paths, to_path, to_local=false, verbose=false, printonly=false)
95
- puts "CONNECTING TO #{host}..."
121
+
96
122
  paths = [paths] unless paths.is_a?(Array)
97
123
  from_paths = ""
98
124
  if to_local
99
125
  paths.each do |path|
100
126
  from_paths << "#{user}@#{host}:#{path} "
101
- end
127
+ end
128
+ puts "Copying FROM remote TO this machine", $/
129
+
102
130
  else
103
131
  to_path = "#{user}@#{host}:#{to_path}"
104
132
  from_paths = paths.join(' ')
133
+ puts "Copying FROM this machine TO remote", $/
105
134
  end
106
135
 
136
+
107
137
  cmd = "scp -i #{keypair} #{from_paths} #{to_path}"
108
138
 
139
+ puts "CONNECTING TO #{host}..."
109
140
  puts cmd if verbose
110
141
  printonly ? (puts cmd) : system(cmd)
111
142
  end