ec2-host 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 94ae5ab54d79d7e269102382f12b40c31f16c349
4
+ data.tar.gz: 14ccb9795b4629b607899c7192ad1567e50c4132
5
+ SHA512:
6
+ metadata.gz: 82e4d30e8df2b2311146a37db0e13e6adac9f70383fb892f4342908401ef37582b4c4ff30defc1f56265f559ba7d7954540b2fc21b0646088b89a94b22aad952
7
+ data.tar.gz: 43d10651c6b326ff1ab898e0807c366926288b3b7fefed673ce04a006eeb7a1b4f1e0523158e4e09cb01f83708b8a844706945c69617c61e9ebfe39966e4adc6
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *~
2
+ .bundle
3
+ .pryrc
4
+ .rvmrc
5
+ .rbenv-version
6
+ .ruby-version
7
+ .yardoc
8
+ .DS_Store
9
+ Gemfile.lock
10
+ coverage
11
+ doc
12
+ log
13
+ tmp
14
+ vendor
15
+ *.swp
16
+ pkg/
17
+ .env
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ # 0.0.1 (2015/08/10)
2
+
3
+ first version
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Naotoshi Seo
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,104 @@
1
+ # ec2-host
2
+
3
+ Search hosts on AWS EC2
4
+
5
+ ## Installation
6
+
7
+ ```
8
+ gem install ec2-host
9
+ ```
10
+
11
+ ## Configuration
12
+
13
+ You can write a configuration file located at `/etc/sysconfig/ec2-host` (You can configure this path by `EC2_HOST_CONFIG_FILE` environment variable), or as environment variables:
14
+
15
+ AWS SDK (CLI) parameters:
16
+
17
+ * **AWS_REGION**; AWS SDK (CLI) region such as `ap-northeast-1`, `us-east-1`.
18
+ * **AWS_ACCESS_KEY_ID**: AWS SDK (CLI) crendentials. Default loads a credentials file
19
+ * **AWS_SECRET_ACCESS_KEY**: AWS SDK (CLI) credentials. Default load a credentials file
20
+ * **AWS_PROFILE**: The profile key of the AWS SDK (CLI) credentails file. Default is `default`
21
+ * **AWS_CREDENTIALS_FILE**: Path of the AWS SDK (CLI) credentails file. Default is `$HOME/.aws/credentials`. See [Configuring the AWS Command Line Interface](http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html#cli-config-files) for details.
22
+
23
+ ec2-host parameters:
24
+
25
+ * **HOSTNAME_TAG**: EC2 tag key used to express a hostname. The default is `Name`.
26
+ * **ROLES_TAG**: EC2 tag keys used to express roles. The default is `Roles`
27
+ * You can assign multiple roles seperated by `,` comma
28
+ * Also, you can express levels of roles delimited by `:`.
29
+ * Example: admin:ami, then `EC2::Host.new(role: 'admin:ami')` and also `EC2::Host.new(role1: 'admin')` returns this host
30
+ * **OPTIONAL_STRING_TAGS**: You may add optional non-array tags. You can specify multiple tags like `Service,Status`.
31
+ * **OPTIONAL_ARRAY_TAGS**: You may add optional array tags. Array tags allows multiple values delimited by `,` (comma) as `Roles` tag.
32
+ * **LOG_LEVEL**: Log level such as `info`, `debug`, `error`. The default is `info`.
33
+
34
+ See [sampel.conf](./sample.conf)
35
+
36
+ ## Tag Example
37
+
38
+ * **Name**: hostname
39
+ * **Roles**: app:web,app:db
40
+ * **Service**: sugoi
41
+ * **Status**: setup
42
+
43
+ ## CLI Usage
44
+
45
+ ```
46
+ $ ec2-host --role1 admin
47
+ host1
48
+ ip-XXX-XXX-XXX-XXX # if Name tag is not assigned
49
+ ```
50
+
51
+ See `ec2-host help get-hosts` for details:
52
+
53
+ ```
54
+ Usage:
55
+ ec2-host get-hosts
56
+
57
+ Options:
58
+ -h, [--hostname=one two three] # name or private_dns_name
59
+ --usage, [--role=one two three] # role
60
+ --r1, --usage1, --u1, [--role1=one two three] # role1, the 1st part of role delimited by :
61
+ --r2, --usage2, --u2, [--role2=one two three] # role2, the 2nd part of role delimited by :
62
+ --r3, --usage3, --u3, [--role3=one two three] # role3, the 3rd part of role delimited by :
63
+ -i, [--info], [--no-info] # show host info, not only hostname
64
+ [--debug], [--no-debug] # debug mode
65
+
66
+ Search EC2 hosts
67
+ ```
68
+
69
+ ## Library Usage
70
+
71
+ See http://sonots.github.io/ec2-host/doc/frames.html.
72
+
73
+ ## ChangeLog
74
+
75
+ See [CHANGELOG.md](CHANGELOG.md) for details.
76
+
77
+ ## For Developers
78
+
79
+ ### ToDo
80
+
81
+ * Support assume-roles
82
+ * Use mock/stub to run test (currently, directly accessing to EC2)
83
+ * Should cache a result of describe-instances in like 30 seconds?
84
+
85
+ ### How to Run test
86
+
87
+ See [spec/README.md](spec/README.md)
88
+
89
+ ### How to Release Gem
90
+
91
+ 1. Update gem.version in the gemspec
92
+ 2. Update CHANGELOG.md
93
+ 3. git commit && git push
94
+ 4. Run `bundle exec rake release`
95
+
96
+ ### How to Update doc
97
+
98
+ 1. Run `./doc.sh`
99
+ 2. git commit && git push (to gh-pages branch)
100
+
101
+ ### Licenses
102
+
103
+ See [LICENSE](LICENSE)
104
+
data/Rakefile ADDED
@@ -0,0 +1,30 @@
1
+ # -*- coding: utf-8; -*-
2
+ require "bundler/gem_tasks"
3
+
4
+ begin
5
+ require 'rspec/core/rake_task'
6
+ RSpec::Core::RakeTask.new :spec do |spec|
7
+ spec.pattern = FileList['spec/**/*_spec.rb']
8
+ end
9
+ task default: :spec
10
+ rescue LoadError, NameError
11
+ # OK, they can be absent on non-development mode.
12
+ end
13
+
14
+ desc "irb console"
15
+ task :console do
16
+ require_relative "lib/ec2-host"
17
+ require 'irb'
18
+ require 'irb/completion'
19
+ ARGV.clear
20
+ IRB.start
21
+ end
22
+ task :c => :console
23
+
24
+ desc "pry console"
25
+ task :pry do
26
+ require_relative "lib/ec2-host"
27
+ require 'pry'
28
+ ARGV.clear
29
+ Pry.start
30
+ end
data/bin/ec2-host ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/ec2/host/cli'
4
+ EC2::Host::CLI.start(ARGV)
data/doc.sh ADDED
@@ -0,0 +1,5 @@
1
+ #!/bin/sh
2
+ bundle exec yardoc
3
+ rsync -au --delete doc/ /tmp/doc
4
+ git checkout gh-pages
5
+ rsync -au --delete /tmp/doc/ doc
data/ec2-host.gemspec ADDED
@@ -0,0 +1,29 @@
1
+ Gem::Specification.new do |gem|
2
+ gem.name = "ec2-host"
3
+ gem.version = '0.0.1'
4
+ gem.author = ['Naotoshi Seo']
5
+ gem.email = ['sonots@gmail.com']
6
+ gem.homepage = 'https://github.com/sonots/ec2-host'
7
+ gem.summary = "Search hosts on AWS EC2"
8
+ gem.description = "Search hosts on AWS EC2"
9
+ gem.licenses = ['MIT']
10
+
11
+ gem.files = `git ls-files`.split("\n")
12
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
13
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
14
+ gem.require_paths = ["lib"]
15
+
16
+ gem.add_runtime_dependency 'aws-sdk'
17
+ gem.add_runtime_dependency 'hashie'
18
+ gem.add_runtime_dependency 'thor'
19
+ gem.add_runtime_dependency 'dotenv'
20
+
21
+ gem.add_development_dependency 'yard'
22
+ gem.add_development_dependency 'rdoc'
23
+ gem.add_development_dependency 'rspec'
24
+ gem.add_development_dependency 'simplecov'
25
+ gem.add_development_dependency 'pry'
26
+ gem.add_development_dependency 'pry-nav'
27
+ gem.add_development_dependency 'rake'
28
+ gem.add_development_dependency 'bundler'
29
+ end
@@ -0,0 +1,15 @@
1
+ require 'pp'
2
+ require 'aws-sdk'
3
+ require 'dotenv'
4
+ Dotenv.load
5
+
6
+ # Aws.config.update({
7
+ # region: 'us-west-2',
8
+ # credentials: Aws::Credentials.new('akid', 'secret'),
9
+ # })
10
+
11
+ ec2 = Aws::EC2::Client.new
12
+ pp instances = ec2.describe_instances.reservations.map(&:instances)
13
+
14
+ require 'pry'
15
+ binding.pry
@@ -0,0 +1,6 @@
1
+ require 'ec2-host'
2
+ require 'pp'
3
+
4
+ EC2::Host.new(role1: 'admin').each do |host|
5
+ pp host
6
+ end
data/lib/ec2-host.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'bundler/setup'
2
+
3
+ require_relative 'ec2/host/string_util'
4
+ require_relative 'ec2/host/hash_util'
5
+ require_relative 'ec2/host/config'
6
+ require_relative 'ec2/host/host_data'
7
+ require_relative 'ec2/host/role_data'
8
+ require_relative 'ec2/host/client_util'
9
+ require_relative 'ec2/host'
data/lib/ec2/host.rb ADDED
@@ -0,0 +1,113 @@
1
+ class EC2
2
+ # Search EC2 hosts from tags
3
+ #
4
+ # require 'ec2-host'
5
+ # # Search by `Name` tag
6
+ # EC2::Host.new(hostname: 'test').first # => test
7
+ #
8
+ # # Search by `Roles` tag
9
+ # EC2::Host.new(
10
+ # role: 'admin:haikanko',
11
+ # ).each do |host|
12
+ # # ...
13
+ # end
14
+ #
15
+ # or
16
+ #
17
+ # EC2::Host.new(
18
+ # role1: 'admin',
19
+ # role2: 'haikanko',
20
+ # ).each do |host|
21
+ # # ...
22
+ # end
23
+ #
24
+ # # Or search
25
+ # EC2::Host.new(
26
+ # {
27
+ # role1: 'db',
28
+ # role2: 'master',
29
+ # },
30
+ # {
31
+ # role1: 'web',
32
+ # }
33
+ # ).each do |host|
34
+ # # ...
35
+ # end
36
+ #
37
+ # EC2::Host.me.hostname # => 'test'
38
+ class Host
39
+ include Enumerable
40
+
41
+ # @return [Host::Data] representing myself
42
+ def self.me
43
+ new(instance_id: ClientUtil.get_instance_id).each do |d|
44
+ return d
45
+ end
46
+ raise 'Not Found'
47
+ end
48
+
49
+ # Configure EC2::Host
50
+ #
51
+ # @params [Hash] params see EC2::Host::Config for configurable parameters
52
+ def self.configure(params = {})
53
+ Config.configure(params)
54
+ end
55
+
56
+ ARRAY_TAG_DELIMITER = ','
57
+ ROLE_TAG_DELIMITER = ':'
58
+
59
+ attr_reader :conditions, :options
60
+
61
+ # @param [Array of Hash, or Hash] conditions (and options)
62
+ #
63
+ # EC2::Host.new(
64
+ # hostname: 'test',
65
+ # options: {a: 'b'}
66
+ # )
67
+ #
68
+ # EC2::Host.new(
69
+ # {
70
+ # hostname: 'foo',
71
+ # },
72
+ # {
73
+ # hostname: 'bar',
74
+ # },
75
+ # options: {a: 'b'}
76
+ # )
77
+ def initialize(*conditions)
78
+ conditions = [{}] if conditions.empty?
79
+ conditions = [conditions] if conditions.kind_of?(Hash)
80
+ @options = {}
81
+ if conditions.size == 1
82
+ @options = conditions.first.delete(:options) || {}
83
+ else
84
+ index = conditions.find_index {|condition| condition.has_key?(:options) }
85
+ @options = conditions.delete_at(index)[:options] if index
86
+ end
87
+ raise ArgumentError, "Hash expected (options)" unless @options.is_a?(Hash)
88
+ @conditions = []
89
+ conditions.each do |condition|
90
+ @conditions << Hash[condition.map {|k, v| [k, Array(v).map(&:to_s)]}]
91
+ end
92
+ raise ArgumentError, "Array of Hash, or Hash expected (conditions)" unless @conditions.all? {|h| h.kind_of?(Hash)}
93
+ end
94
+
95
+ # @yieldparam [Host::Data] data entry
96
+ def each(&block)
97
+ @conditions.each do |condition|
98
+ search(ClientUtil.get_instances, condition, &block)
99
+ end
100
+ return self
101
+ end
102
+
103
+ private
104
+
105
+ def search(instances, condition)
106
+ instances.each do |i|
107
+ d = EC2::Host::HostData.initialize(i)
108
+ next unless d.match?(condition)
109
+ yield d
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,74 @@
1
+ require 'thor'
2
+ require 'ec2-host'
3
+
4
+ class EC2
5
+ class Host
6
+ class CLI < Thor
7
+ default_command :get_hosts
8
+
9
+ desc 'get-hosts', 'Search EC2 hosts (default)'
10
+ option :hostname,
11
+ :aliases => '-h',
12
+ :type => :array,
13
+ :desc => "name or private_dns_name"
14
+ option :role,
15
+ :aliases => %w[--usage],
16
+ :type => :array,
17
+ :desc => "role"
18
+ option :role1,
19
+ :aliases => %w[--r1 --usage1 --u1], # hmm, -r1 is not suppored by thor
20
+ :type => :array,
21
+ :desc => "role1, the 1st part of role delimited by #{ROLE_TAG_DELIMITER}"
22
+ option :role2,
23
+ :aliases => %w[--r2 --usage2 --u2],
24
+ :type => :array,
25
+ :desc => "role2, the 2nd part of role delimited by #{ROLE_TAG_DELIMITER}"
26
+ option :role3,
27
+ :aliases => %w[--r3 --usage3 --u3],
28
+ :type => :array,
29
+ :desc => "role3, the 3rd part of role delimited by #{ROLE_TAG_DELIMITER}"
30
+ Config.optional_options.each do |opt, tag|
31
+ option opt, :type => :array, :desc => opt
32
+ end
33
+ option :info,
34
+ :aliases => %w[-i],
35
+ :type => :boolean,
36
+ :desc => "show host info, not only hostname"
37
+ option :debug,
38
+ :type => :boolean,
39
+ :desc => "debug mode"
40
+ def get_hosts
41
+ if options[:info]
42
+ EC2::Host.new(condition).each do |host|
43
+ $stdout.puts host.info
44
+ end
45
+ else
46
+ EC2::Host.new(condition).each do |host|
47
+ $stdout.puts host.hostname
48
+ end
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def condition
55
+ return @condition if @condition
56
+ _condition = HashUtil.except(options, :info, :debug)
57
+ @condition = {}
58
+ _condition.each do |key, val|
59
+ if tag = Config.optional_options[key.to_s]
60
+ field = StringUtil.underscore(tag)
61
+ @condition[field.to_sym] = val
62
+ else
63
+ @condition[key.to_sym] = val
64
+ end
65
+ end
66
+ if options[:debug]
67
+ $stderr.puts(options: options)
68
+ $stderr.puts(condition: @condition)
69
+ end
70
+ @condition
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,27 @@
1
+ require 'net/http'
2
+ require 'aws-sdk'
3
+
4
+ class EC2
5
+ class Host
6
+ class ClientUtil
7
+ def self.get_instances
8
+ # I do not use describe_instances(filter:) because it does not support array tag ..
9
+ return @instances if @instances
10
+ Aws.config.update(region: Config.aws_region, credentials: Config.aws_credentials)
11
+ ec2 = Aws::EC2::Client.new
12
+ @instances = ec2.describe_instances.reservations.map(&:instances).flatten
13
+ end
14
+
15
+ def self.get_instance_id
16
+ return @instance_id if @instance_id
17
+ begin
18
+ http_conn = Net::HTTP.new('169.254.169.254')
19
+ http_conn.open_timeout = 5
20
+ @instance_id = http_conn.start {|http| http.get('/latest/meta-data/instance-id').body }
21
+ rescue Net::OpenTimeout
22
+ raise "HTTP connection to 169.254.169.254 is timeout. Probably, not an EC2 instance?"
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,111 @@
1
+ require 'dotenv'
2
+ Dotenv.load
3
+
4
+ class EC2
5
+ class Host
6
+ class Config
7
+ class << self
8
+ attr_writer :config_file,
9
+ :aws_region,
10
+ :aws_profile,
11
+ :aws_access_key_id,
12
+ :aws_secret_access_key,
13
+ :aws_credentials_file,
14
+ :log_level,
15
+ :hostname_tag,
16
+ :roles_tag,
17
+ :optional_array_tags,
18
+ :optional_string_tags
19
+ end
20
+
21
+ def self.configure(params)
22
+ params.each do |key, val|
23
+ send("#{key}=", val)
24
+ end
25
+ end
26
+
27
+ def self.config_file
28
+ @config_file ||= ENV.fetch('EC2_HOST_CONFIG_FILE', '/etc/sysconfig/ec2-host')
29
+ end
30
+
31
+ def self.aws_region
32
+ @aws_region ||= ENV['AWS_REGION'] || config.fetch('AWS_REGION')
33
+ end
34
+
35
+ def self.aws_profile
36
+ @aws_profile ||= ENV['AWS_PROFILE'] || config.fetch('AWS_PROFILE', 'default')
37
+ end
38
+
39
+ def self.aws_access_key_id
40
+ @aws_access_key_id ||= ENV['AWS_ACCESS_KEY_ID'] || config.fetch('AWS_ACCESS_KEY_ID', nil)
41
+ end
42
+
43
+ def self.aws_secret_access_key
44
+ @aws_secret_access_key ||= ENV['AWS_SECRET_ACCESS_KEY'] || config.fetch('AWS_SECRET_ACCESS_KEY', nil)
45
+ end
46
+
47
+ # this is not an official aws sdk environment variable
48
+ def self.aws_credentials_file
49
+ @aws_credentials_file ||= ENV['AWS_CREDENTIALS_FILE'] || config.fetch('AWS_CREDENTIALS_FILE', nil)
50
+ end
51
+
52
+ def self.log_level
53
+ @log_level ||= ENV['LOG_LEVEL'] || config.fetch('LOG_LEVEL', 'info')
54
+ end
55
+
56
+ def self.hostname_tag
57
+ @hostname_tag ||= ENV['HOSTNAME_TAG'] || config.fetch('HOSTNAME_TAG', 'Name')
58
+ end
59
+
60
+ def self.roles_tag
61
+ @roles_tag ||= ENV['ROLES_TAG'] || config.fetch('ROLES_TAG', 'Roles')
62
+ end
63
+
64
+ def self.optional_array_tags
65
+ @optional_array_tags ||= (ENV['OPTIONAL_ARRAY_TAGS'] || config.fetch('OPTIONAL_ARRAY_TAGS', '')).split(',')
66
+ end
67
+
68
+ def self.optional_string_tags
69
+ @optional_string_tags ||= (ENV['OPTIONAL_STRING_TAGS'] || config.fetch('OPTIONAL_STRING_TAGS', '')).split(',')
70
+ end
71
+
72
+ # private
73
+
74
+ def self.aws_credentials
75
+ @aws_credentials ||=
76
+ if aws_access_key_id and aws_secret_access_key
77
+ Aws::Credentials.new(aws_access_key_id, aws_secret_access_key)
78
+ else
79
+ Aws::SharedCredentials.new(profile_name: aws_profile, path: aws_credentials_file)
80
+ end
81
+ end
82
+
83
+ def self.optional_array_options
84
+ @optional_array_options ||= Hash[optional_array_tags.map {|tag|
85
+ [StringUtil.singularize(StringUtil.underscore(tag)), tag]
86
+ }]
87
+ end
88
+
89
+ def self.optional_string_options
90
+ @optional_string_options ||= Hash[optional_string_tags.map {|tag|
91
+ [StringUtil.underscore(tag), tag]
92
+ }]
93
+ end
94
+
95
+ def self.optional_options
96
+ @optional_options ||= optional_array_options.merge(optional_string_options)
97
+ end
98
+
99
+ def self.config
100
+ return @config if @config
101
+ @config = {}
102
+ File.readlines(config_file).each do |line|
103
+ next if line.start_with?('#')
104
+ key, val = line.chomp.split('=', 2)
105
+ @config[key] = val
106
+ end
107
+ @config
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,11 @@
1
+ class EC2
2
+ class Host
3
+ module HashUtil
4
+ def self.except(hash, *keys)
5
+ hash = hash.dup
6
+ keys.each {|key| hash.delete(key) }
7
+ hash
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,128 @@
1
+ require 'forwardable'
2
+ require 'hashie/mash'
3
+
4
+ class EC2
5
+ class Host
6
+ # Represents each host
7
+ class HostData < Hashie::Mash
8
+ # :hostname, # tag:Name or hostname part of private_dns_name
9
+ # :roles, # tag:Roles.split(',') such as web:app1,db:app1
10
+ # :region, # ENV['AWS_REGION'],
11
+ # :instance, # Aws::EC2::Types::Instance itself
12
+ #
13
+ # and OPTIONAL_ARRAY_TAGS, OPTIONAL_STRING_TAGS
14
+
15
+ extend Forwardable
16
+ def_delegators :instance,
17
+ :instance_id,
18
+ :private_ip_address,
19
+ :public_ip_address,
20
+ :launch_time,
21
+ :state,
22
+ :monitoring
23
+
24
+ alias_method :ip, :private_ip_address
25
+ alias_method :start_date, :launch_time
26
+ def usages; roles; end
27
+
28
+ def self.initialize(instance)
29
+ d = self.new
30
+ d.instance = instance
31
+ d.set_hostname
32
+ d.set_roles
33
+ d.set_region
34
+ d.set_string_tags
35
+ d.set_array_tags
36
+ d
37
+ end
38
+
39
+ # match with condition or not
40
+ #
41
+ # @param [Hash] condition search parameters
42
+ def match?(condition)
43
+ return false unless role_match?(condition)
44
+ condition = HashUtil.except(condition,
45
+ :role, :role1, :role2, :role3,
46
+ :usage, :usage1, :usage2, :usage3
47
+ )
48
+ condition.each do |key, values|
49
+ if self.send(key).is_a?(Array)
50
+ return false unless self.send(key).find {|v| values.include?(v) }
51
+ else
52
+ return false unless values.include?(self.send(key))
53
+ end
54
+ end
55
+ true
56
+ end
57
+
58
+ def inspect
59
+ sprintf "#<Aws::Host::HostData %s>", info
60
+ end
61
+
62
+ def info
63
+ if hostname and status and roles and tags and service
64
+ # special treatment for DeNA ;)
65
+ sprintf "%s:%s(%s)[%s]{%s}", \
66
+ hostname, status, roles.join(' '), tags.join(' '), service
67
+ else
68
+ HashUtil.except(self, :instance).to_s
69
+ end
70
+ end
71
+
72
+ # private
73
+
74
+ def role_match?(condition)
75
+ # usage is an alias of role
76
+ if role = (condition[:role] || condition[:usage])
77
+ role1, role2, role3 = role.first.split(':')
78
+ else
79
+ role1 = (condition[:role1] || condition[:usage1] || []).first
80
+ role2 = (condition[:role2] || condition[:usage2] || []).first
81
+ role3 = (condition[:role3] || condition[:usage3] || []).first
82
+ end
83
+ if role1
84
+ return false unless self.roles.find {|role| role.match?(role1, role2, role3) }
85
+ end
86
+ true
87
+ end
88
+
89
+ def set_hostname
90
+ self.hostname = find_string_tag(Config.hostname_tag)
91
+ self.hostname = instance.private_dns_name.split('.').first if self.hostname.empty?
92
+ end
93
+
94
+ def set_roles
95
+ roles = find_array_tag(Config.roles_tag)
96
+ self.roles = roles.map {|role| EC2::Host::RoleData.initialize(role) }
97
+ end
98
+
99
+ def set_region
100
+ self.region = Config.aws_region
101
+ end
102
+
103
+ def set_string_tags
104
+ Config.optional_string_tags.each do |tag|
105
+ field = StringUtil.underscore(tag)
106
+ self[field] = find_string_tag(tag)
107
+ end
108
+ end
109
+
110
+ def set_array_tags
111
+ Config.optional_array_tags.each do |tag|
112
+ field = StringUtil.underscore(tag)
113
+ self[field] = find_array_tag(tag)
114
+ end
115
+ end
116
+
117
+ def find_string_tag(key)
118
+ v = instance.tags.find {|tag| tag.key == key }
119
+ v ? v.value : ''
120
+ end
121
+
122
+ def find_array_tag(key)
123
+ v = instance.tags.find {|tag| tag.key == key }
124
+ v ? v.value.split(ARRAY_TAG_DELIMITER) : []
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,60 @@
1
+ class EC2
2
+ class Host
3
+ RoleData = Struct.new(
4
+ :role1, :role2, :role3
5
+ )
6
+
7
+ # Represents each role
8
+ class RoleData
9
+ def self.initialize(role)
10
+ role1, role2, role3 = role.split(ROLE_TAG_DELIMITER)
11
+ self.new(role1, role2, role3)
12
+ end
13
+
14
+ # @return [String] something like "admin:jenkins:slave"
15
+ def role
16
+ @role ||= [role1, role2, role3].compact.reject(&:empty?).join(ROLE_TAG_DELIMITER)
17
+ end
18
+ alias :to_s :role
19
+
20
+ # @return [Array] something like ["admin", "admin:jenkins", "admin:jenkins:slave"]
21
+ def uppers
22
+ uppers = [RoleData.new(role1)]
23
+ uppers << RoleData.new(role1, role2) if role2 and !role2.empty?
24
+ uppers << RoleData.new(role1, role2, role3) if role3 and !role3.empty?
25
+ uppers
26
+ end
27
+
28
+ def match?(role1, role2 = nil, role3 = nil)
29
+ if role3
30
+ role1 == self.role1 and role2 == self.role2 and role3 == self.role3
31
+ elsif role2
32
+ role1 == self.role1 and role2 == self.role2
33
+ else
34
+ role1 == self.role1
35
+ end
36
+ end
37
+
38
+ # Equality
39
+ #
40
+ # Role::Data.new('admin') == Role::Data.new('admin') #=> true
41
+ # Role::Data.new('admin', 'jenkin') == "admin:jenkins" #=> true
42
+ #
43
+ # @param [Object] other
44
+ def ==(other)
45
+ case other
46
+ when String
47
+ self.role == other
48
+ when EC2::Host::RoleData
49
+ super(other)
50
+ else
51
+ false
52
+ end
53
+ end
54
+
55
+ def inspect
56
+ "\"#{to_s}\""
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,31 @@
1
+ class EC2
2
+ class Host
3
+ # If want sophisticated utility, better to use ActiveSupport
4
+ module StringUtil
5
+ def self.camelize(string)
6
+ string = string.sub(/^[a-z\d]*/) { $&.capitalize }
7
+ string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { $2.capitalize }
8
+ string.gsub!(/\//, '::')
9
+ string
10
+ end
11
+
12
+ def self.underscore(camel_cased_word)
13
+ return camel_cased_word unless camel_cased_word =~ /[A-Z-]|::/
14
+ word = camel_cased_word.to_s.gsub(/::/, '/')
15
+ word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
16
+ word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
17
+ word.tr!("-", "_")
18
+ word.downcase!
19
+ word
20
+ end
21
+
22
+ def self.pluralize(string)
23
+ "#{string.chomp('s')}s"
24
+ end
25
+
26
+ def self.singularize(string)
27
+ string.chomp('s')
28
+ end
29
+ end
30
+ end
31
+ end
data/sample.conf ADDED
@@ -0,0 +1,8 @@
1
+ AWS_ACCESS_KEY_ID=xxxxxxx
2
+ AWS_SECRET_ACCESS_KEY=xxxxxx
3
+ AWS_REGION=ap-northeast-1
4
+ HOSTNAME_TAG=Name
5
+ ROLES_TAG=Roles
6
+ OPTIONAL_ARRAY_TAGS=Tags
7
+ OPTIONAL_STRING_TAGS=Service,Status
8
+ LOG_LEVEL=info
data/spec/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # How to run test
2
+
3
+ NOTE: Currently, mock is not supported yet. So, you have to create your own AWS account, and instances.
4
+
5
+ Configure .env file as
6
+
7
+ ```
8
+ AWS_ACCESS_KEY_ID=
9
+ AWS_SECRET_ACCESS_KEY=
10
+ AWS_REGION=
11
+ EC2_HOST_CONFIG_FILE=.env
12
+ OPTIONAL_ARRAY_TAGS=Tags
13
+ OPTIONAL_STRING_TAGS=Service,Status
14
+ ```
15
+
16
+ EC2 instance tags must be configured as followings:
17
+
18
+ ```
19
+ [
20
+ {
21
+ Name: test
22
+ Roles: admin:admin,test
23
+ Service: test
24
+ Status: reserve
25
+ Tags: standby
26
+ }
27
+ {
28
+ Name: isucon4
29
+ Roles: isucon4:qual
30
+ Service: isucon4
31
+ Status: active
32
+ Tags: master
33
+ }
34
+ ]
35
+ ```
data/spec/host_spec.rb ADDED
@@ -0,0 +1,163 @@
1
+ require 'spec_helper'
2
+
3
+ shared_examples_for 'host' do
4
+ it 'should have parameters' do
5
+ [ :hostname,
6
+ :roles,
7
+ :status,
8
+ :tags,
9
+ :service,
10
+ :region,
11
+ :instance,
12
+ :instance_id,
13
+ :private_ip_address,
14
+ :public_ip_address,
15
+ :launch_time,
16
+ :state,
17
+ :monitoring,
18
+ :ip,
19
+ :start_date,
20
+ :usages,
21
+ ].each do |k|
22
+ expect{ subject.__send__(k) }.not_to raise_error
23
+ end
24
+ end
25
+ end
26
+
27
+ describe EC2::Host do
28
+ describe 'options' do
29
+ context do
30
+ let(:subject) { EC2::Host.new(hostname: 'test', options: {foo:'bar'}) }
31
+ it { expect(subject.options).to eq({foo:'bar'}) }
32
+ it { expect(subject.conditions).to eq([{hostname: ['test']}]) }
33
+ end
34
+
35
+ context do
36
+ let(:subject) { EC2::Host.new({hostname: 'foo'}, {hostname: 'bar'}, options: {foo:'bar'}) }
37
+ it { expect(subject.options).to eq({foo:'bar'}) }
38
+ it { expect(subject.conditions).to eq([{hostname: ['foo']}, {hostname: ['bar']}]) }
39
+ end
40
+ end
41
+
42
+ context 'by hostname' do
43
+ let(:hosts) { EC2::Host.new(hostname: 'test').to_a }
44
+ let(:subject) { hosts.first }
45
+ it_should_behave_like 'host'
46
+ it { expect(hosts.size).to eq(1) }
47
+ it { expect(subject.hostname).to eq('test') }
48
+ end
49
+
50
+ context 'by role' do
51
+ context 'by a role' do
52
+ let(:subject) { EC2::Host.new(role1: 'admin').first }
53
+ it_should_behave_like 'host'
54
+ end
55
+
56
+ context 'by multiple roles (or)' do
57
+ let(:hosts) {
58
+ EC2::Host.new(
59
+ {
60
+ role1: 'admin',
61
+ role2: 'ami',
62
+ },
63
+ {
64
+ role1: 'isucon4',
65
+ }
66
+ ).to_a
67
+ }
68
+ let(:subject) { hosts.first }
69
+ it { expect(hosts.size).to be >= 2 }
70
+ it_should_behave_like 'host'
71
+ end
72
+ end
73
+
74
+ # This is for DeNA use
75
+ context 'by usage (an alias of usage)' do
76
+ context 'by a usage' do
77
+ let(:subject) { EC2::Host.new(usage1: 'admin').first }
78
+ it_should_behave_like 'host'
79
+ end
80
+
81
+ context 'by multiple usages (or)' do
82
+ let(:hosts) {
83
+ EC2::Host.new(
84
+ {
85
+ usage1: 'admin',
86
+ usage2: 'ami',
87
+ },
88
+ {
89
+ usage1: 'isucon4',
90
+ }
91
+ ).to_a
92
+ }
93
+ let(:subject) { hosts.first }
94
+ it { expect(hosts.size).to be >= 2 }
95
+ it_should_behave_like 'host'
96
+ end
97
+ end
98
+
99
+ context 'by status' do
100
+ context 'by a status' do
101
+ let(:subject) { EC2::Host.new(status: :active).first }
102
+ it_should_behave_like 'host'
103
+ end
104
+
105
+ context 'by multiple status (or)' do
106
+ let(:hosts) { EC2::Host.new(status: [:reserve, :active]).to_a }
107
+ let(:subject) { hosts.first }
108
+ it_should_behave_like 'host'
109
+ it { expect(hosts.size).to be >= 2 }
110
+ end
111
+
112
+ context 'by a string status' do
113
+ let(:subject) { EC2::Host.new(status: 'active').first }
114
+ it_should_behave_like 'host'
115
+ end
116
+
117
+ context 'by multiple string status (or)' do
118
+ let(:hosts) { EC2::Host.new(status: ['reserve', 'active']).to_a }
119
+ let(:subject) { hosts.first }
120
+ it_should_behave_like 'host'
121
+ it { expect(hosts.size).to be >= 2 }
122
+ end
123
+ end
124
+
125
+ context 'by service' do
126
+ context 'by a service' do
127
+ let(:subject) { EC2::Host.new(service: 'isucon4').first }
128
+ it_should_behave_like 'host'
129
+ end
130
+
131
+ context 'by multiple services (or)' do
132
+ let(:hosts) { EC2::Host.new(service: ['test', 'isucon4']) }
133
+ let(:subject) { hosts.first }
134
+ it_should_behave_like 'host'
135
+ end
136
+ end
137
+
138
+ context 'by region' do
139
+ context 'by a region' do
140
+ let(:subject) { EC2::Host.new(region: 'ap-northeast-1').first }
141
+ it_should_behave_like 'host'
142
+ end
143
+
144
+ context 'by multiple regions (or)' do
145
+ let(:hosts) { EC2::Host.new(region: ['ap-northeast-1']) }
146
+ let(:subject) { hosts.first }
147
+ it_should_behave_like 'host'
148
+ end
149
+ end
150
+
151
+ context 'by tags' do
152
+ context 'by a tag' do
153
+ let(:subject) { EC2::Host.new(tags: 'master').first }
154
+ it_should_behave_like 'host'
155
+ end
156
+
157
+ context 'by multiple tags (or)' do
158
+ let(:hosts) { EC2::Host.new(tags: ['standby', 'master']) }
159
+ let(:subject) { hosts.first }
160
+ it_should_behave_like 'host'
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,22 @@
1
+ ENV['RACK_ENV'] = 'test'
2
+ Bundler.require :test
3
+
4
+ require 'simplecov'
5
+ SimpleCov.start do
6
+ add_filter 'vendor/'
7
+ add_filter 'spec/'
8
+ add_group 'libs', 'lib'
9
+ end
10
+
11
+ Bundler.require :default # <- need this *after* simplecov
12
+ require 'pry'
13
+ require 'ec2-host'
14
+ require 'dotenv'
15
+ Dotenv.load
16
+
17
+ # Requires supporting files with custom matchers and macros, etc,
18
+ # in ./support/ and its subdirectories.
19
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
20
+
21
+ RSpec.configure do |config|
22
+ end
metadata ADDED
@@ -0,0 +1,241 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ec2-host
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Naotoshi Seo
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-08-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aws-sdk
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: hashie
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: thor
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: dotenv
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: yard
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rdoc
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: simplecov
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: pry
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: pry-nav
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: rake
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: bundler
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ description: Search hosts on AWS EC2
182
+ email:
183
+ - sonots@gmail.com
184
+ executables:
185
+ - ec2-host
186
+ extensions: []
187
+ extra_rdoc_files: []
188
+ files:
189
+ - ".gitignore"
190
+ - CHANGELOG.md
191
+ - Gemfile
192
+ - LICENSE
193
+ - README.md
194
+ - Rakefile
195
+ - bin/ec2-host
196
+ - doc.sh
197
+ - ec2-host.gemspec
198
+ - example/aws-sdk.rb
199
+ - example/example.rb
200
+ - lib/ec2-host.rb
201
+ - lib/ec2/host.rb
202
+ - lib/ec2/host/cli.rb
203
+ - lib/ec2/host/client_util.rb
204
+ - lib/ec2/host/config.rb
205
+ - lib/ec2/host/hash_util.rb
206
+ - lib/ec2/host/host_data.rb
207
+ - lib/ec2/host/role_data.rb
208
+ - lib/ec2/host/string_util.rb
209
+ - sample.conf
210
+ - spec/README.md
211
+ - spec/host_spec.rb
212
+ - spec/spec_helper.rb
213
+ homepage: https://github.com/sonots/ec2-host
214
+ licenses:
215
+ - MIT
216
+ metadata: {}
217
+ post_install_message:
218
+ rdoc_options: []
219
+ require_paths:
220
+ - lib
221
+ required_ruby_version: !ruby/object:Gem::Requirement
222
+ requirements:
223
+ - - ">="
224
+ - !ruby/object:Gem::Version
225
+ version: '0'
226
+ required_rubygems_version: !ruby/object:Gem::Requirement
227
+ requirements:
228
+ - - ">="
229
+ - !ruby/object:Gem::Version
230
+ version: '0'
231
+ requirements: []
232
+ rubyforge_project:
233
+ rubygems_version: 2.2.2
234
+ signing_key:
235
+ specification_version: 4
236
+ summary: Search hosts on AWS EC2
237
+ test_files:
238
+ - spec/README.md
239
+ - spec/host_spec.rb
240
+ - spec/spec_helper.rb
241
+ has_rdoc: