ec2cli 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +71 -0
- data/bin/ec2cli +67 -0
- data/lib/ec2cli/cli/evaluate.rb +53 -0
- data/lib/ec2cli/cli/functions.rb +104 -0
- data/lib/ec2cli/cli/help.rb +40 -0
- data/lib/ec2cli/cli/options.rb +35 -0
- data/lib/ec2cli/ec2-client.rb +108 -0
- data/lib/ec2cli/ec2-driver.rb +322 -0
- data/lib/ec2cli/ec2-endpoint.rb +32 -0
- data/lib/ec2cli/ec2-error.rb +10 -0
- data/lib/ec2cli/ec2-parser.tab.rb +519 -0
- data/lib/ec2cli/ec2-parser.y +233 -0
- data/lib/ec2cli.rb +11 -0
- metadata +74 -0
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
|