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
@@ -0,0 +1,157 @@
1
+
2
+ require 'drydock'
3
+
4
+ module Rudy
5
+
6
+ # = CLI
7
+ #
8
+ # These classes provide the functionality for the Command
9
+ # line interfaces. See the bin/ files if you're interested.
10
+ #
11
+ module CLI
12
+ class NoCred < RuntimeError #:nodoc
13
+ end
14
+
15
+ class Output < Storable
16
+ # TODO: Use for all CLI responses
17
+ # Messages and errors should be in @@global.format
18
+ # Should print messages as they come
19
+ end
20
+
21
+ class CommandBase < Drydock::Command
22
+ include Rudy::Huxtable
23
+
24
+ attr_reader :config
25
+
26
+ protected
27
+ def init
28
+
29
+ # The CLI wants output!
30
+ Rudy::Huxtable.update_logger STDOUT
31
+
32
+ # Send The Huxtables the global values from the command-line
33
+ Rudy::Huxtable.update_global @global
34
+
35
+ unless @@global.accesskey && @@global.secretkey
36
+ STDERR.puts "No AWS credentials. Check your configs!"
37
+ STDERR.puts "Try: rudy init"
38
+ exit 1
39
+ end
40
+
41
+ if @@global.environment =~ /^prod/ && Drydock.debug?
42
+ puts Rudy::Utils.banner("PRODUCTION ACCESS IS DISABLED IN DEBUG MODE")
43
+ exit 1
44
+ end
45
+
46
+ end
47
+
48
+ def execute_action(emsg="Failed", &action)
49
+ begin
50
+ ret = action.call
51
+ raise emsg unless ret
52
+ ret
53
+ rescue Rudy::AWS::EC2::NoAMI => ex
54
+ raise Drydock::OptError.new('-a', @alias)
55
+ end
56
+ end
57
+
58
+ def execute_check(level=:medium)
59
+ ret = Annoy.are_you_sure?(level)
60
+ exit 0 unless ret
61
+ ret
62
+ end
63
+
64
+ # Print a default header to the screen for every command.
65
+ #
66
+ def print_header
67
+
68
+ # Send The Huxtables the global values again because they could be
69
+ # updated after initialization but before the command was executed
70
+ Rudy::Huxtable.update_global @global
71
+
72
+ puts Rudy::CLI.generate_header(@@global, @@config) if @@global.print_header
73
+
74
+ unless @@global.quiet
75
+ if @@global.environment == "prod"
76
+ msg = "YOU ARE PLAYING WITH PRODUCTION"
77
+ puts Rudy::Utils.banner(msg, :huge, :red), $/
78
+ end
79
+ puts Rudy::Utils.banner("THIS IS EC2"), $/ if Rudy.in_situ?
80
+ end
81
+ end
82
+
83
+ end
84
+
85
+ def self.generate_header(global, config)
86
+ return "" if global.quiet
87
+ header = StringIO.new
88
+ title, name = "RUDY v#{Rudy::VERSION}", config.accounts.aws.name
89
+ now_utc = Time.now.utc.strftime("%Y-%m-%d %H:%M:%S")
90
+ criteria = []
91
+ [:region, :zone, :environment, :role, :position].each do |n|
92
+ key, val = n.to_s.slice(0,1).att, global.send(n)
93
+ key = 'R' if n == :region
94
+ next unless val
95
+ criteria << "#{key.att}:#{val.to_s.bright}"
96
+ end
97
+ if config.accounts && config.accounts.aws
98
+ if global.verbose > 0
99
+ header.puts '%s -- %s -- %s UTC' % [title, name, now_utc]
100
+ end
101
+ header.puts '[%s]' % [criteria.join(" ")], $/
102
+ end
103
+ header.rewind
104
+ header.read
105
+ end
106
+
107
+
108
+ # A base for all Drydock executables (bin/rudy etc...).
109
+ class Base
110
+ extend Drydock
111
+
112
+ before do |obj|
113
+ # Don't print Rudy header unless requested to
114
+ obj.global.print_header = false if (obj.global.verbose == 0)
115
+ @start = Time.now
116
+ end
117
+
118
+ after do |obj|
119
+ if obj.global.verbose > 0
120
+ puts
121
+ @elapsed = Time.now - @start
122
+ puts "Elapsed: %.2f seconds" % @elapsed.to_f if @elapsed > 0.1
123
+ end
124
+ end
125
+
126
+
127
+ # These globals are used by all bin/ executables
128
+ global :A, :accesskey, String, "AWS Access Key"
129
+ global :S, :secretkey, String, "AWS Secret Access Key"
130
+ global :R, :region, String, "Amazon service region (ie: #{Rudy::DEFAULT_REGION})"
131
+ global :z, :zone, String, "Amazon Availability zone (ie: #{Rudy::DEFAULT_ZONE})"
132
+ global :u, :user, String, "Provide a username (ie: #{Rudy::DEFAULT_USER})"
133
+ global :k, :pkey, String, "Path to the private SSH key"
134
+ global :f, :format, String, "Output format"
135
+ global :n, :nocolor, "Disable output colors"
136
+ global :C, :config, String, "Specify another configuration file to read (ie: #{Rudy::CONFIG_FILE})"
137
+ global :Y, :yes, "Assume a correct answer to confirmation questions"
138
+ global :q, :quiet, "Run with less output"
139
+ global :v, :verbose, "Increase verbosity of output (i.e. -v or -vv or -vvv)" do
140
+ @verbose ||= 0
141
+ @verbose += 1
142
+ end
143
+ global :V, :version, "Display version number" do
144
+ puts "Rudy version: #{Rudy::VERSION}"
145
+ exit 0
146
+ end
147
+
148
+ end
149
+
150
+
151
+ end
152
+
153
+ end
154
+
155
+ Rudy::Utils.require_glob(RUDY_LIB, 'rudy', 'cli', '**', '*.rb')
156
+
157
+
@@ -0,0 +1,105 @@
1
+
2
+
3
+ module Rudy; module CLI;
4
+ module AWS; module EC2;
5
+
6
+ class Addresses < Rudy::CLI::CommandBase
7
+
8
+ def addresses_create
9
+ radd = Rudy::AWS::EC2::Addresses.new(@@global.accesskey, @@global.secretkey, @@global.region)
10
+ address = radd.create
11
+ puts @@global.verbose > 0 ? address.inspect : address.dump(@@global.format)
12
+ end
13
+
14
+ def addresses_destroy_valid?
15
+ raise Drydock::ArgError.new("IP address", @alias) unless @argv.ipaddress
16
+ @radd = Rudy::AWS::EC2::Addresses.new(@@global.accesskey, @@global.secretkey, @@global.region)
17
+ raise "#{@argv.ipaddress} is not allocated to you" unless @radd.exists?(@argv.ipaddress)
18
+ raise "#{@argv.ipaddress} is associated!" if @radd.associated?(@argv.ipaddress)
19
+ true
20
+ end
21
+ def addresses_destroy
22
+ address = @radd.get(@argv.ipaddress)
23
+ raise "Could not fetch #{address.ipaddress}" unless address
24
+
25
+ puts "Destroying address: #{@argv.ipaddress}"
26
+ puts "NOTE: this IP address will become available to other EC2 customers.".color(:blue)
27
+ execute_check(:medium)
28
+ execute_action { @radd.destroy(@argv.ipaddress) }
29
+ self.addresses
30
+ end
31
+
32
+ def associate_addresses_valid?
33
+ raise Drydock::ArgError.new('IP address', @alias) if !@argv.ipaddress && !@option.newaddress
34
+ raise Drydock::OptError.new('instance ID', @alias) if !@option.instance
35
+ true
36
+ end
37
+ def associate_addresses
38
+ radd = Rudy::AWS::EC2::Addresses.new(@@global.accesskey, @@global.secretkey, @@global.region)
39
+ rinst = Rudy::AWS::EC2::Instances.new(@@global.accesskey, @@global.secretkey, @@global.region)
40
+
41
+ raise "Instance #{@argv.instid} does not exist!" unless rinst.exists?(@option.instance)
42
+
43
+ if @option.newaddress
44
+ print "Creating address... "
45
+ tmp = radd.create
46
+ puts "#{tmp.ipaddress}"
47
+ address = tmp.ipaddress
48
+ else
49
+ address = @argv.ipaddress
50
+ end
51
+
52
+ raise "#{address} is not allocated to you" unless radd.exists?(address)
53
+ raise "#{address} is already associated!" if radd.associated?(address)
54
+
55
+ instance = rinst.get(@option.instance)
56
+
57
+ # If an instance was recently disassoiciated, the dns_public may
58
+ # not be updated yet
59
+ instance_name = instance.dns_public
60
+ instance_name = instance.awsid if !instance_name || instance_name.empty?
61
+
62
+ puts "Associating #{address} to #{instance_name} (#{instance.groups.join(', ')})"
63
+ execute_check(:low)
64
+ execute_action { radd.associate(address, instance.awsid) }
65
+ address = radd.get(address)
66
+ puts @@global.verbose > 0 ? address.inspect : address.dump(@@global.format)
67
+ end
68
+
69
+ def disassociate_addresses_valid?
70
+ raise "You have not supplied an IP addresses" unless @argv.ipaddress
71
+ true
72
+ end
73
+ def disassociate_addresses
74
+ radd = Rudy::AWS::EC2::Addresses.new(@@global.accesskey, @@global.secretkey, @@global.region)
75
+ rinst = Rudy::AWS::EC2::Instances.new(@@global.accesskey, @@global.secretkey, @@global.region)
76
+ raise "#{@argv.ipaddress} is not allocated to you" unless radd.exists?(@argv.ipaddress)
77
+ raise "#{@argv.ipaddress} is not associated!" unless radd.associated?(@argv.ipaddress)
78
+
79
+ address = radd.get(@argv.ipaddress)
80
+ instance = rinst.get(address.instid)
81
+
82
+ puts "Disassociating #{address.ipaddress} from #{instance.awsid} (#{instance.groups.join(', ')})"
83
+ execute_check(:medium)
84
+ execute_action { radd.disassociate(@argv.ipaddress) }
85
+ address = radd.get(@argv.ipaddress)
86
+ puts @@global.verbose > 0 ? address.inspect : address.dump(@@global.format)
87
+ end
88
+
89
+ def addresses
90
+ radd = Rudy::AWS::EC2::Addresses.new(@@global.accesskey, @@global.secretkey, @@global.region)
91
+ addresses = radd.list || []
92
+
93
+ addresses.each do |address|
94
+ puts @@global.verbose > 0 ? address.inspect : address.dump(@@global.format)
95
+ end
96
+
97
+ puts "No Addresses" if addresses.empty?
98
+ end
99
+
100
+
101
+ end
102
+
103
+ end; end
104
+ end; end
105
+
@@ -0,0 +1,208 @@
1
+
2
+ module Rudy; module CLI;
3
+ module AWS; module EC2;
4
+
5
+ class Candy < Rudy::CLI::CommandBase
6
+
7
+ def status_valid?
8
+ avail = Rudy::Utils.service_available?('status.aws.amazon.com', 80, 5)
9
+ raise ServiceUnavailable, 'status.aws.amazon.com' unless avail
10
+ true
11
+ end
12
+ def status
13
+ url = 'http://status.aws.amazon.com/rss/EC2.rss'
14
+ # TODO: Move to Rudy::AWS
15
+ ec2 = Rudy::Utils::RSSReader.run(url) || {}
16
+
17
+ # TODO: Create Storable object
18
+ if @@global.format == 'yaml'
19
+ puts ec2.to_yaml
20
+ elsif @@global.format == 'json'
21
+ require 'json'
22
+ puts ec2.to_json
23
+ else
24
+ puts "Updated: #{ec2[:pubdate]} (updated every #{ec2[:ttl]} minutes)"
25
+ ec2[:items].each do |i|
26
+ puts
27
+ puts '%s' % i[:title]
28
+ puts ' %s: %s' % [i[:pubdate], i[:description]]
29
+
30
+ end
31
+ end
32
+ end
33
+
34
+ def ssh_valid?
35
+ if @@global.pkey
36
+ raise "Cannot find file #{@@global.pkey}" unless File.exists?(@@global.pkey)
37
+ raise "Insecure permissions for #{@@global.pkey}" unless (File.stat(@@global.pkey).mode & 600) == 0
38
+ end
39
+ if @option.group
40
+ rgroup = Rudy::AWS::EC2::Groups.new(@@global.accesskey, @@global.secretkey, @@global.region)
41
+ raise "Cannot supply group and instance ID" if @option.instid
42
+ raise "Group #{@option.group} does not exist" unless rgroup.exists?(@option.group)
43
+ end
44
+ if @option.instid && !Rudy::Utils.is_id?(:instance, @option.instid)
45
+ raise "#{@option.instid} is not an instance ID"
46
+ end
47
+ true
48
+ end
49
+ def ssh
50
+ opts = {}
51
+ opts[:group] = @option.group if @option.group
52
+ opts[:group] = :any if @option.all
53
+ opts[:id] = @option.instid if @option.instid
54
+
55
+ # Options to be sent to Net::SSH
56
+ ssh_opts = { :user => @global.user || Rudy.sysinfo.user, :debug => nil }
57
+ if @@global.pkey
58
+ raise "Cannot find file #{@@global.pkey}" unless File.exists?(@@global.pkey)
59
+ raise InsecureKeyPermissions, @@global.pkey unless File.stat(@@global.pkey).mode == 33152
60
+ ssh_opts[:keys] = @@global.pkey
61
+ end
62
+
63
+
64
+ # The user specified a command to run. We won't create an interactive
65
+ # session so we need to prepare the command and its arguments
66
+ if @argv.first
67
+ command, command_args = @argv.shift, @argv || []
68
+ puts "#{command} #{command_args.join(' ')}" if @@global.verbose > 1
69
+
70
+ # otherwise, we'll open an ssh session or print command
71
+ else
72
+ command, command_args = :interactive_ssh, @option.print.nil?
73
+ end
74
+
75
+ checked = false
76
+ rudy = Rudy::AWS::EC2::Instances.new(@@global.accesskey, @@global.secretkey, @@global.region)
77
+ lt = rudy.list_group(opts[:group], :running, opts[:id]) do |inst|
78
+
79
+ # Print header
80
+ if @@global.quiet
81
+ print "You are #{ssh_opts[:user].bright}. " if !checked # only the 1st
82
+ else
83
+ print "Connecting #{ssh_opts[:user].bright}@#{inst.dns_public} "
84
+ puts "(#{inst.awsid}, groups: #{inst.groups.join(', ')})"
85
+ end
86
+
87
+ # Make sure we want to run this command on all instances
88
+ if !checked && command != :interactive_ssh
89
+ execute_check(:medium) if ssh_opts[:user] == "root"
90
+ checked = true
91
+ end
92
+
93
+ # Open the connection and run the command
94
+ rbox = Rye::Box.new(inst.dns_public, ssh_opts)
95
+ ret = rbox.send(command, command_args)
96
+ puts ret unless command == :interactive_ssh
97
+ end
98
+ end
99
+
100
+ def copy_valid?
101
+ raise "You must supply a source and a target. See rudy-ec2 #{@alias} -h" unless @argv.size >= 2
102
+ raise "You cannot download and upload at the same time" if @option.download && @alias == 'upload'
103
+ raise "You cannot download and upload at the same time" if @option.upload && @alias == 'download'
104
+ true
105
+ end
106
+ def copy
107
+
108
+ opts = {}
109
+ opts[:group] = @option.group if @option.group
110
+ opts[:group] = :any if @option.all
111
+
112
+ opts[:id] = @argv.shift if Rudy::Utils.is_id?(:instance, @argv.first)
113
+ opts[:id] &&= [opts[:id]].flatten
114
+
115
+ # * +:recursive: recursively transfer directories (default: false)
116
+ # * +:preserve: preserve atimes and ctimes (default: false)
117
+ # * +:task+ one of: :upload (default), :download.
118
+ # * +:paths+ an array of paths to copy. The last element is the "to" path.
119
+ opts[:recursive] = @option.recursive ? true : false
120
+ opts[:preserve] = @option.preserve ? true : false
121
+
122
+ opts[:paths] = @argv
123
+ opts[:dest] = opts[:paths].pop
124
+
125
+ opts[:task] = :download if %w(dl download).member?(@alias) || @option.download
126
+ opts[:task] = :upload if %w(ul upload).member?(@alias)
127
+ opts[:task] ||= :upload
128
+ opts[:user] = @global.user || Rudy.sysinfo.user
129
+
130
+ # Options to be sent to Net::SSH
131
+ ssh_opts = { :user => opts[:user], :debug => nil }
132
+ ssh_opts[:keys] = @@global.pkey if @@global.pkey
133
+
134
+ if @@global.pkey
135
+ raise "Cannot find file #{@@global.pkey}" unless File.exists?(@@global.pkey)
136
+ raise "Insecure permissions for #{@@global.pkey}" unless (File.stat(@@global.pkey).mode & 600) == 0
137
+ end
138
+
139
+ checked = false
140
+ rudy = Rudy::AWS::EC2::Instances.new(@@global.accesskey, @@global.secretkey, @@global.region)
141
+ lt = rudy.list_group(opts[:group], :running, opts[:id]) do |inst|
142
+
143
+ if @option.print
144
+ Rudy::Utils.scp_command inst.dns_public, @@global.pkey, opts[:user], opts[:paths], opts[:dest], (opts[:task] == :download), false, @option.print
145
+ next
146
+ end
147
+
148
+ # Print header
149
+ if @@global.quiet
150
+ print "You are #{ssh_opts[:user].bright}. " if !checked # only the 1st
151
+ else
152
+ print "Connecting #{ssh_opts[:user].bright}@#{inst.dns_public} "
153
+ puts "(#{inst.awsid}, groups: #{inst.groups.join(', ')})"
154
+ end
155
+
156
+ # Make sure we want to run this command on all instances
157
+ if !checked
158
+ #execute_check(:medium) if opts[:user] == "root"
159
+ checked = true
160
+ end
161
+
162
+ scp_opts = {
163
+ :recursive => opts[:recursive],
164
+ :preserve => opts[:preserve],
165
+ :chunk_size => 16384
166
+ }
167
+
168
+ Candy.scp(opts[:task], inst.dns_public, opts[:user], @@global.pkey, opts[:paths], opts[:dest], scp_opts)
169
+ puts
170
+ puts unless @@global.quiet
171
+ end
172
+
173
+ end
174
+
175
+
176
+ private
177
+
178
+ def Candy.scp(task, host, user, keypairpath, paths, dest, opts)
179
+
180
+ connect_opts = {}
181
+ connect_opts[:keys] = [keypairpath] if keypairpath
182
+
183
+ Net::SCP.start(host, user, connect_opts) do |scp|
184
+
185
+ paths.each do |path|
186
+ prev_path = nil
187
+ scp.send("#{task}", path, dest, opts) do |ch, name, sent, total|
188
+ #print "#{name}: #{sent}/#{total}\r"
189
+ msg = ((prev_path == name) ? "\r" : "\n") # new line for new file
190
+ msg << "#{name}: #{sent}/#{total}" # otherwise, update the same line
191
+ print msg
192
+ STDOUT.flush # update the screen every cycle
193
+ prev_path = name
194
+ break if sent == total
195
+ end
196
+ puts unless prev_path == path
197
+ end
198
+
199
+ end
200
+ end
201
+
202
+
203
+
204
+ end
205
+
206
+
207
+ end; end
208
+ end; end