ec2cli 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,71 @@
1
+ = ec2cli
2
+
3
+ == Description
4
+
5
+ ec2cli is an interactive command-line client of Amazon EC2.
6
+
7
+ == Source Code
8
+
9
+ https://bitbucket.org/winebarrel/ec2cli
10
+
11
+ == Install
12
+
13
+ shell> gem install ec2cli
14
+ shell> export AWS_ACCESS_KEY_ID='...'
15
+ shell> export AWS_SECRET_ACCESS_KEY='...'
16
+ shell> export EC2_REGION=ap-northeast-1
17
+ shell> ec2cli -h
18
+ Usage: ec2cli [options]
19
+ -k, --access-key=ACCESS_KEY
20
+ -s, --secret-key=SECRET_KEY
21
+ -r, --region=REGION_OR_ENDPOINT
22
+ --debug
23
+ -h, --help
24
+ ...
25
+ shell> ec2cli # show prompt
26
+ ap-northeast-1> desc instances;
27
+ [
28
+ ["cthulhu","i-01bd4903","stopped","10.0.114.178","t1.micro",["default"],{"Name":"cthulhu"},"ap-northeast-1a"],
29
+ ["hastur","i-e78d79e5","stopped","10.0.130.129","m1.large",["default"],{"Name":"hastur","aaa":"1100"},"ap-northeast-1b"],
30
+ ["nyar","i-a5e119a7","stopped","10.0.254.210","c1.xlarge",["default"],{"aaa":"1100","Name":"nyar"},"ap-northeast-1b"],
31
+ ]
32
+ // 3 rows in set (0.23 sec)
33
+
34
+ == Help
35
+
36
+ ##### Query #####
37
+
38
+ DESC[RIBE] INSTANCES [WHERE name = '...' AND ...]
39
+ Describes one or more of your instances.
40
+
41
+ DESC[RIBE] IMAGES [WHERE name = '...' AND ...]
42
+ DESC[RIBE] ALL IMAGES [WHERE [Owner = {amazon|aws-marketplace|self} AND] name = '...' AND ...]
43
+ Describes the images.
44
+
45
+ RUN INSTANCES image_id
46
+ Launches instances
47
+
48
+ START INSTANCES id_or_name
49
+ START INSTANCES (id_or_name, ...)
50
+ Starts an Amazon EBS-backed AMI that you've previously stopped.
51
+
52
+ STOP INSTANCES id_or_name
53
+ STOP INSTANCES (id_or_name, ...)
54
+ Stops an Amazon EBS-backed instance
55
+
56
+ SET TAGS name = value, ... [WHERE name = '...' AND ...]
57
+ Adds or overwrites one or more tags for the specified EC2 resource or resources.
58
+
59
+ USE region_or_endpoint
60
+ changes an endpoint
61
+
62
+ SHOW REGIONS
63
+ displays a region list
64
+
65
+ ##### Command #####
66
+
67
+ .help displays this message
68
+ .quit | .exit exits sdbcli
69
+ .debug (true|false)? displays a debug status or changes it
70
+ .version displays a version
71
+
data/bin/ec2cli ADDED
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
3
+
4
+ Version = '0.1.0'
5
+
6
+ HISTORY_FILE = File.join((ENV['HOME'] || ENV['USERPROFILE'] || '.'), '.ec2cli_history')
7
+ HISTSIZE = 500
8
+
9
+ require 'rubygems'
10
+ require 'ec2cli'
11
+ require 'readline'
12
+
13
+ options = parse_options
14
+
15
+ driver = EC2::Driver.new(
16
+ options.access_key_id,
17
+ options.secret_access_key,
18
+ options.ec2_endpoint_or_region)
19
+
20
+ driver.debug = options.debug
21
+
22
+ # load history file
23
+ if File.exist?(HISTORY_FILE)
24
+ open(HISTORY_FILE) do |f|
25
+ f.each_line do |line|
26
+ line = line.strip
27
+ Readline::HISTORY.push(line) unless line.empty?
28
+ end
29
+ end
30
+ end
31
+
32
+ # interactive mode
33
+ src = ''
34
+ prompt1 = lambda { "#{driver.region || 'unknown'}> " }
35
+ prompt2 = lambda { "#{' ' * (prompt1.call.length - 3)}-> " }
36
+
37
+ while buf = Readline.readline((src.empty? ? prompt1.call : prompt2.call), true)
38
+ # ignore blank lines
39
+ if /\A\s*\Z/ =~ buf
40
+ Readline::HISTORY.pop
41
+ next
42
+ end
43
+
44
+ if src.empty? and buf =~ /\A\.(.+)/
45
+ evaluate_command(driver, $1)
46
+ else
47
+ begin
48
+ src << (src.empty? ? buf : ("\n" + buf))
49
+ evaluate_query(driver, src, :show_rows => true)
50
+ rescue => e
51
+ print_error(e.message)
52
+ print_error(e.backtrace) if driver.debug
53
+ end
54
+
55
+ prompt = src.empty? ? prompt1.call : prompt2.call
56
+ end
57
+ end # of while
58
+
59
+ # save history file
60
+ unless Readline::HISTORY.empty?
61
+ open(HISTORY_FILE, 'wb') do |f|
62
+ (Readline::HISTORY.to_a.slice(-(Readline::HISTORY.length < HISTSIZE ? Readline::HISTORY.length : HISTSIZE)..-1) || []).each do |line|
63
+ next if /\A\s*\Z/ =~ line
64
+ f.puts line
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,53 @@
1
+ require 'ec2cli/ec2-driver'
2
+
3
+ def evaluate_query(driver, src, opts = {})
4
+ ss = StringScanner.new(src.dup)
5
+ buf = ''
6
+
7
+ until ss.eos?
8
+ if (tok = ss.scan %r{[^`'";\\/#]+}) #'
9
+ buf << tok
10
+ elsif (tok = ss.scan /`(?:[^`]|``)*`/)
11
+ buf << tok
12
+ elsif (tok = ss.scan /'(?:[^']|'')*'/) #'
13
+ buf << tok
14
+ elsif (tok = ss.scan /"(?:[^"]|"")*"/) #"
15
+ buf << tok
16
+ elsif (tok = ss.scan %r{/\*/?(?:\n|[^/]|[^*]/)*\*/})
17
+ # nothing to do
18
+ elsif (tok = ss.scan /--[^\r\n]*(?:\r\n|\r|\n|\Z)/)
19
+ # nothing to do
20
+ elsif (tok = ss.scan /#[^\r\n]*(?:\r\n|\r|\n|\Z)/)
21
+ # nothing to do
22
+ elsif (tok = ss.scan /(?:\\;)/)
23
+ buf << ';' # escape of ';'
24
+ elsif (tok = ss.scan /(?:;|\\G)/)
25
+ src.replace(ss.rest)
26
+ query = buf
27
+ buf = ''
28
+
29
+ if query.strip.empty?
30
+ print_error('No query specified')
31
+ next
32
+ end
33
+
34
+ start_time = Time.new
35
+ out = driver.execute(query)
36
+ elapsed = Time.now - start_time
37
+
38
+ if out.kind_of?(EC2::Driver::Rownum)
39
+ print_rownum(out, :time => elapsed)
40
+ elsif out.kind_of?(String)
41
+ puts out
42
+ elsif out
43
+ opts = opts.merge(:inline => (tok != '\G'), :time => elapsed)
44
+ print_json(out, opts)
45
+ end
46
+ elsif (tok = ss.scan /./)
47
+ buf << tok # 落ち穂拾い
48
+ end
49
+ end
50
+
51
+ src.replace(buf.strip)
52
+ buf
53
+ end
@@ -0,0 +1,104 @@
1
+ require 'json'
2
+
3
+ def print_error(errmsg, opts = {})
4
+ errmsg = errmsg.join("\n") if errmsg.kind_of?(Array)
5
+ errmsg = errmsg.strip.split("\n").map {|i| "// #{i.strip}" }.join("\n")
6
+ errmsg += "\n\n" unless opts[:strip]
7
+ $stderr.puts errmsg
8
+ end
9
+
10
+ def print_rownum(data, opts = {})
11
+ rownum = data.to_i
12
+ msg = "// #{rownum} #{rownum > 1 ? 'rows' : 'row'} changed"
13
+ msg << " (%.2f sec)" % opts[:time] if opts[:time]
14
+ msg << "\n\n"
15
+ puts msg
16
+ end
17
+
18
+ def print_json(data, opts = {})
19
+ str = nil
20
+
21
+ if data.kind_of?(Array) and opts[:inline]
22
+ str = "[\n"
23
+
24
+ data.each_with_index do |item, i|
25
+ str << " #{item.to_json}"
26
+ str << ',' if i < (data.length - 1)
27
+ str << "\n"
28
+ end
29
+
30
+ str << "]"
31
+ else
32
+ if data.kind_of?(Array) or data.kind_of?(Hash)
33
+ str = JSON.pretty_generate(data)
34
+ else
35
+ str = data.to_json
36
+ end
37
+ end
38
+
39
+ str.sub!(/(?:\r\n|\r|\n)*\Z/, "\n")
40
+
41
+ if opts[:show_rows] and data.kind_of?(Array)
42
+ str << "// #{data.length} #{data.length > 1 ? 'rows' : 'row'} in set"
43
+ str << " (%.2f sec)" % opts[:time] if opts[:time]
44
+ str << "\n"
45
+ end
46
+
47
+ str << "\n"
48
+ puts str
49
+ end
50
+
51
+ def print_version
52
+ puts "ec2cli #{Version}"
53
+ end
54
+
55
+ def evaluate_command(driver, cmd_arg)
56
+ cmd, arg = cmd_arg.split(/\s+/, 2).map {|i| i.strip }
57
+ arg = nil if (arg || '').strip.empty?
58
+
59
+ r = /\A#{Regexp.compile(cmd)}/i
60
+
61
+ commands = {
62
+ 'help' => lambda {
63
+ print_help
64
+ },
65
+
66
+ ['exit', 'quit'] => lambda {
67
+ exit 0
68
+ },
69
+
70
+ 'debug' => lambda {
71
+ if arg
72
+ r_arg = /\A#{Regexp.compile(arg)}/i
73
+
74
+ if r_arg =~ 'true'
75
+ driver.debug = true
76
+ elsif r_arg =~ 'false'
77
+ driver.debug = false
78
+ else
79
+ print_error('Invalid argument')
80
+ end
81
+ else
82
+ puts driver.debug
83
+ end
84
+ },
85
+
86
+ 'version' => lambda {
87
+ print_version
88
+ }
89
+ }
90
+
91
+ cmd_name, cmd_proc = commands.find do |name, proc|
92
+ if name.kind_of?(Array)
93
+ name.any? {|i| r =~ i }
94
+ else
95
+ r =~ name
96
+ end
97
+ end
98
+
99
+ if cmd_proc
100
+ cmd_proc.call
101
+ else
102
+ print_error('Unknown command')
103
+ end
104
+ end
@@ -0,0 +1,40 @@
1
+ def print_help
2
+ puts <<EOS
3
+ ##### Query #####
4
+
5
+ DESC[RIBE] INSTANCES [WHERE name = '...' AND ...]
6
+ Describes one or more of your instances.
7
+
8
+ DESC[RIBE] IMAGES [WHERE name = '...' AND ...]
9
+ DESC[RIBE] ALL IMAGES [WHERE [Owner = {amazon|aws-marketplace|self} AND] name = '...' AND ...]
10
+ Describes the images.
11
+
12
+ RUN INSTANCES image_id
13
+ Launches instances
14
+
15
+ START INSTANCES id_or_name
16
+ START INSTANCES (id_or_name, ...)
17
+ Starts an Amazon EBS-backed AMI that you've previously stopped.
18
+
19
+ STOP INSTANCES id_or_name
20
+ STOP INSTANCES (id_or_name, ...)
21
+ Stops an Amazon EBS-backed instance
22
+
23
+ SET TAGS name = value, ... [WHERE name = '...' AND ...]
24
+ Adds or overwrites one or more tags for the specified EC2 resource or resources.
25
+
26
+ USE region_or_endpoint
27
+ changes an endpoint
28
+
29
+ SHOW REGIONS
30
+ displays a region list
31
+
32
+ ##### Command #####
33
+
34
+ .help displays this message
35
+ .quit | .exit exits sdbcli
36
+ .debug (true|false)? displays a debug status or changes it
37
+ .version displays a version
38
+
39
+ EOS
40
+ end
@@ -0,0 +1,35 @@
1
+ require 'optparse'
2
+ require 'ostruct'
3
+
4
+ def parse_options
5
+ options = OpenStruct.new
6
+ options.access_key_id = ENV['AWS_ACCESS_KEY_ID']
7
+ options.secret_access_key = ENV['AWS_SECRET_ACCESS_KEY']
8
+ options.ec2_endpoint_or_region = ENV['EC2_ENDPOINT'] || ENV['EC2_REGION'] || 'ec2.us-east-1.amazonaws.com'
9
+
10
+ # default value
11
+ options.debug = false
12
+
13
+ ARGV.options do |opt|
14
+ opt.on('-k', '--access-key=ACCESS_KEY') {|v| options.access_key_id = v }
15
+ opt.on('-s', '--secret-key=SECRET_KEY') {|v| options.secret_access_key = v }
16
+ opt.on('-r', '--region=REGION_OR_ENDPOINT') {|v| options.ddb_endpoint_or_region = v }
17
+ opt.on('', '--debug') { options.debug = true }
18
+
19
+ opt.on('-h', '--help') {
20
+ puts opt.help
21
+ puts
22
+ print_help
23
+ exit
24
+ }
25
+
26
+ opt.parse!
27
+
28
+ unless options.access_key_id and options.secret_access_key and options.ec2_endpoint_or_region
29
+ puts opt.help
30
+ exit 1
31
+ end
32
+ end
33
+
34
+ options
35
+ end
@@ -0,0 +1,108 @@
1
+ require 'cgi'
2
+ require 'base64'
3
+ require 'net/https'
4
+ require 'openssl'
5
+ require 'optparse'
6
+ require 'pp'
7
+ require 'rexml/document'
8
+
9
+ require 'ec2cli/ec2-error'
10
+ require 'ec2cli/ec2-endpoint'
11
+
12
+ module EC2
13
+ class Client
14
+
15
+ API_VERSION = '2013-02-01'
16
+ SIGNATURE_VERSION = 2
17
+ SIGNATURE_ALGORITHM = :SHA256
18
+
19
+ attr_reader :endpoint
20
+ attr_reader :region
21
+ attr_accessor :debug
22
+
23
+ def initialize(accessKeyId, secretAccessKey, endpoint_or_region)
24
+ @accessKeyId = accessKeyId
25
+ @secretAccessKey = secretAccessKey
26
+ set_endpoint_and_region(endpoint_or_region)
27
+ @debug = false
28
+ end
29
+
30
+ def set_endpoint_and_region(endpoint_or_region)
31
+ @endpoint, @region = EC2::Endpoint.endpoint_and_region(endpoint_or_region)
32
+ end
33
+
34
+ def query(action, params = {})
35
+ params = {
36
+ :Action => action,
37
+ :Version => API_VERSION,
38
+ :Timestamp => Time.now.getutc.strftime('%Y-%m-%dT%H:%M:%SZ'),
39
+ :SignatureVersion => SIGNATURE_VERSION,
40
+ :SignatureMethod => "Hmac#{SIGNATURE_ALGORITHM}",
41
+ :AWSAccessKeyId => @accessKeyId,
42
+ }.merge(params)
43
+
44
+ signature = aws_sign(params)
45
+ params[:Signature] = signature
46
+
47
+ if @debug
48
+ $stderr.puts(<<EOS)
49
+ ---request begin---
50
+ #{params.pretty_inspect}
51
+ ---request end---
52
+ EOS
53
+ end
54
+
55
+ https = Net::HTTP.new(@endpoint, 443)
56
+ https.use_ssl = true
57
+ https.verify_mode = OpenSSL::SSL::VERIFY_NONE
58
+
59
+ source = nil
60
+
61
+ https.start do |w|
62
+ req = Net::HTTP::Post.new('/',
63
+ 'Host' => @endpoint,
64
+ 'Content-Type' => 'application/x-www-form-urlencoded'
65
+ )
66
+
67
+ req.set_form_data(params)
68
+ res = w.request(req)
69
+
70
+ unless res.kind_of?(Net::HTTPOK)
71
+ raise EC2::Error, "#{res.code} #{res.message}"
72
+ end
73
+
74
+ source = res.body
75
+ end
76
+
77
+ if /<Response>\s*<Errors>\s*<Error>/ =~ source
78
+ errors = []
79
+
80
+ REXML::Document.new(source).each_element('//Errors/Error') do |element|
81
+ code = element.text('Code')
82
+ message = element.text('Message')
83
+ errors << "#{code}:#{message}"
84
+ end
85
+
86
+ rraise EC2::Error, errors.join(', ') unless errors.empty?
87
+ end
88
+
89
+ if @debug
90
+ $stderr.puts(<<EOS)
91
+ ---response begin---
92
+ #{source}
93
+ ---response end---
94
+ EOS
95
+ end
96
+
97
+ return source
98
+ end
99
+
100
+ def aws_sign(params)
101
+ params = params.sort_by {|a, b| a.to_s }.map {|k, v| "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}" }.join('&')
102
+ string_to_sign = "POST\n#{@endpoint}\n/\n#{params}"
103
+ digest = OpenSSL::HMAC.digest(OpenSSL::Digest.const_get(SIGNATURE_ALGORITHM).new, @secretAccessKey, string_to_sign)
104
+ Base64.encode64(digest).gsub("\n", '')
105
+ end
106
+
107
+ end # Client
108
+ end # EC2