ec2-host 0.3.1 → 0.4.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.
@@ -0,0 +1,112 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
4
+ <head>
5
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
6
+ <title>
7
+ Top Level Namespace
8
+
9
+ &mdash; Documentation by YARD 0.8.7.6
10
+
11
+ </title>
12
+
13
+ <link rel="stylesheet" href="css/style.css" type="text/css" charset="utf-8" />
14
+
15
+ <link rel="stylesheet" href="css/common.css" type="text/css" charset="utf-8" />
16
+
17
+ <script type="text/javascript" charset="utf-8">
18
+ hasFrames = window.top.frames.main ? true : false;
19
+ relpath = '';
20
+ framesUrl = "frames.html#!top-level-namespace.html";
21
+ </script>
22
+
23
+
24
+ <script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
25
+
26
+ <script type="text/javascript" charset="utf-8" src="js/app.js"></script>
27
+
28
+
29
+ </head>
30
+ <body>
31
+ <div id="header">
32
+ <div id="menu">
33
+
34
+ <a href="_index.html">Index</a> &raquo;
35
+
36
+
37
+ <span class="title">Top Level Namespace</span>
38
+
39
+
40
+ <div class="noframes"><span class="title">(</span><a href="." target="_top">no frames</a><span class="title">)</span></div>
41
+ </div>
42
+
43
+ <div id="search">
44
+
45
+ <a class="full_list_link" id="class_list_link"
46
+ href="class_list.html">
47
+ Class List
48
+ </a>
49
+
50
+ <a class="full_list_link" id="method_list_link"
51
+ href="method_list.html">
52
+ Method List
53
+ </a>
54
+
55
+ <a class="full_list_link" id="file_list_link"
56
+ href="file_list.html">
57
+ File List
58
+ </a>
59
+
60
+ </div>
61
+ <div class="clear"></div>
62
+ </div>
63
+
64
+ <iframe id="search_frame"></iframe>
65
+
66
+ <div id="content"><h1>Top Level Namespace
67
+
68
+
69
+
70
+ </h1>
71
+
72
+ <dl class="box">
73
+
74
+
75
+
76
+
77
+
78
+
79
+
80
+
81
+ </dl>
82
+ <div class="clear"></div>
83
+
84
+ <h2>Defined Under Namespace</h2>
85
+ <p class="children">
86
+
87
+
88
+
89
+
90
+ <strong class="classes">Classes:</strong> <span class='object_link'><a href="EC2.html" title="EC2 (class)">EC2</a></span>
91
+
92
+
93
+ </p>
94
+
95
+
96
+
97
+
98
+
99
+
100
+
101
+
102
+
103
+ </div>
104
+
105
+ <div id="footer">
106
+ Generated on Thu Nov 24 10:25:36 2016 by
107
+ <a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
108
+ 0.8.7.6 (ruby-2.3.2).
109
+ </div>
110
+
111
+ </body>
112
+ </html>
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |gem|
2
2
  gem.name = "ec2-host"
3
- gem.version = '0.3.1'
3
+ gem.version = '0.4.0'
4
4
  gem.author = ['Naotoshi Seo']
5
5
  gem.email = ['sonots@gmail.com']
6
6
  gem.homepage = 'https://github.com/sonots/ec2-host'
@@ -14,7 +14,6 @@ Gem::Specification.new do |gem|
14
14
  gem.require_paths = ["lib"]
15
15
 
16
16
  gem.add_runtime_dependency 'aws-sdk'
17
- gem.add_runtime_dependency 'hashie'
18
17
  gem.add_runtime_dependency 'dotenv'
19
18
 
20
19
  gem.add_development_dependency 'yard'
@@ -1,6 +1,6 @@
1
1
  AWS_ACCESS_KEY_ID=xxxxxxx
2
2
  AWS_SECRET_ACCESS_KEY=xxxxxx
3
- AWS_REGION=ap-northeast-1
3
+ AWS_DEFAULT_REGION=ap-northeast-1
4
4
  HOSTNAME_TAG=Name
5
5
  ROLES_TAG=Roles
6
6
  ROLE_TAG_DELIMITER=:
@@ -3,5 +3,5 @@ require_relative 'ec2/host/hash_util'
3
3
  require_relative 'ec2/host/config'
4
4
  require_relative 'ec2/host/host_data'
5
5
  require_relative 'ec2/host/role_data'
6
- require_relative 'ec2/host/client_util'
6
+ require_relative 'ec2/host/ec2_client'
7
7
  require_relative 'ec2/host'
@@ -40,7 +40,7 @@ class EC2
40
40
 
41
41
  # @return [Host::Data] representing myself
42
42
  def self.me
43
- new(instance_id: ClientUtil.instance_id).each do |d|
43
+ new(instance_id: ec2_client.instance_id).each do |d|
44
44
  return d
45
45
  end
46
46
  raise 'Not Found'
@@ -48,11 +48,19 @@ class EC2
48
48
 
49
49
  # Configure EC2::Host
50
50
  #
51
- # @params [Hash] params see EC2::Host::Config for configurable parameters
51
+ # @param [Hash] params see EC2::Host::Config for configurable parameters
52
52
  def self.configure(params = {})
53
53
  Config.configure(params)
54
54
  end
55
55
 
56
+ def self.ec2_client
57
+ @ec2_client ||= EC2Client.new
58
+ end
59
+
60
+ def ec2_client
61
+ self.class.ec2_client
62
+ end
63
+
56
64
  attr_reader :conditions, :options
57
65
 
58
66
  # @param [Array of Hash, or Hash] conditions (and options)
@@ -92,7 +100,7 @@ class EC2
92
100
  # @yieldparam [Host::Data] data entry
93
101
  def each(&block)
94
102
  @conditions.each do |condition|
95
- search(ClientUtil.instances(condition), condition, &block)
103
+ search(ec2_client.instances(condition), condition, &block)
96
104
  end
97
105
  return self
98
106
  end
@@ -101,7 +109,7 @@ class EC2
101
109
 
102
110
  def search(instances, condition)
103
111
  instances.each do |i|
104
- d = EC2::Host::HostData.initialize(i)
112
+ d = EC2::Host::HostData.new(i)
105
113
  next unless d.match?(condition)
106
114
  yield d
107
115
  end
@@ -10,7 +10,7 @@ class EC2
10
10
  :aws_profile,
11
11
  :aws_access_key_id,
12
12
  :aws_secret_access_key,
13
- :aws_credentials_file,
13
+ :aws_credential_file,
14
14
  :log_level,
15
15
  :hostname_tag,
16
16
  :roles_tag,
@@ -29,24 +29,31 @@ class EC2
29
29
  end
30
30
 
31
31
  def self.aws_region
32
- @aws_region ||= ENV['AWS_REGION'] || config.fetch('AWS_REGION')
32
+ @aws_region ||=
33
+ ENV['AWS_REGION'] || config.fetch('AWS_REGION', nil) || # ref. old aws cli
34
+ ENV['AWS_DEFAULT_REGION'] || config.fetch('AWS_DEFAULT_REGION') # ref. aws cli and terraform
33
35
  end
34
36
 
35
37
  def self.aws_profile
36
- @aws_profile ||= ENV['AWS_PROFILE'] || config.fetch('AWS_PROFILE', 'default')
38
+ @aws_profile ||=
39
+ ENV['AWS_PROFILE'] || config.fetch('AWS_PROFILE', 'nil') || # ref. old aws cli
40
+ ENV['AWS_DEFAULT_PROFILE'] || config.fetch('AWS_DEFAULT_PROFILE', 'default') # ref. aws cli and terraform
37
41
  end
38
42
 
39
43
  def self.aws_access_key_id
44
+ # ref. aws cli and terraform
40
45
  @aws_access_key_id ||= ENV['AWS_ACCESS_KEY_ID'] || config.fetch('AWS_ACCESS_KEY_ID', nil)
41
46
  end
42
47
 
43
48
  def self.aws_secret_access_key
49
+ # ref. aws cli and terraform
44
50
  @aws_secret_access_key ||= ENV['AWS_SECRET_ACCESS_KEY'] || config.fetch('AWS_SECRET_ACCESS_KEY', nil)
45
51
  end
46
52
 
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)
53
+ def self.aws_credential_file
54
+ @aws_credential_file ||=
55
+ ENV['AWS_CREDENTIALS_FILE'] || config.fetch('AWS_CREDENTIALS_FILE', nil) ||
56
+ ENV['AWS_CREDENTIAL_FILE'] || config.fetch('AWS_CREDENTIAL_FILE', nil) # ref. aws cli (supported lately)
50
57
  end
51
58
 
52
59
  def self.log_level
@@ -79,15 +86,6 @@ class EC2
79
86
 
80
87
  # private
81
88
 
82
- def self.aws_credentials
83
- @aws_credentials ||=
84
- if aws_access_key_id and aws_secret_access_key
85
- Aws::Credentials.new(aws_access_key_id, aws_secret_access_key)
86
- else
87
- Aws::SharedCredentials.new(profile_name: aws_profile, path: aws_credentials_file)
88
- end
89
- end
90
-
91
89
  def self.optional_array_options
92
90
  @optional_array_options ||= Hash[optional_array_tags.map {|tag|
93
91
  [StringUtil.singularize(StringUtil.underscore(tag)), tag]
@@ -0,0 +1,60 @@
1
+ require 'net/http'
2
+ require 'aws-sdk'
3
+
4
+ class EC2
5
+ class Host
6
+ class EC2Client
7
+ def instances(condition)
8
+ filters = build_filters(condition)
9
+ instances =
10
+ if filters
11
+ ec2.describe_instances(filters: filters)
12
+ elsif instance_id = condition[:instance_id]
13
+ ec2.describe_instances(instance_ids: Array(instance_id))
14
+ else
15
+ ec2.describe_instances
16
+ end
17
+ instances.reservations.map(&:instances).flatten
18
+ end
19
+
20
+ def instance_id
21
+ return @instance_id if @instance_id
22
+ begin
23
+ http_conn = Net::HTTP.new('169.254.169.254')
24
+ http_conn.open_timeout = 5
25
+ @instance_id = http_conn.start {|http| http.get('/latest/meta-data/instance-id').body }
26
+ rescue Net::OpenTimeout
27
+ raise "HTTP connection to 169.254.169.254 is timeout. Probably, not an EC2 instance?"
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def ec2
34
+ @ec2 ||= Aws::EC2::Client.new(region: Config.aws_region, credentials: credentials)
35
+ end
36
+
37
+ def credentials
38
+ if Config.aws_access_key_id and Config.aws_secret_access_key
39
+ Aws::Credentials.new(Config.aws_access_key_id, Config.aws_secret_access_key)
40
+ else
41
+ Aws::SharedCredentials.new(profile_name: Config.aws_profile, path: Config.aws_credential_file)
42
+ end
43
+ end
44
+
45
+ def build_filters(condition)
46
+ if role = (condition[:role] || condition[:usage]) and role.size == 1
47
+ [{name: "tag:#{Config.roles_tag}", values: ["*#{role.first}*"]}]
48
+ elsif role1 = (condition[:role1] || condition[:usage1]) and role1.size == 1
49
+ [{name: "tag:#{Config.roles_tag}", values: ["*#{role1.first}*"]}]
50
+ elsif role2 = (condition[:role2] || condition[:usage2]) and role2.size == 1
51
+ [{name: "tag:#{Config.roles_tag}", values: ["*#{role2.first}*"]}]
52
+ elsif role3 = (condition[:role3] || condition[:usage3]) and role3.size == 1
53
+ [{name: "tag:#{Config.roles_tag}", values: ["*#{role3.first}*"]}]
54
+ else
55
+ nil
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -1,98 +1,93 @@
1
- require 'forwardable'
2
- require 'hashie/mash'
3
1
  require 'json'
4
2
 
5
3
  class EC2
6
4
  class Host
7
5
  # Represents each host
8
- class HostData < Hashie::Mash
6
+ class HostData
7
+ attr_reader :instance
8
+
9
9
  # :hostname, # tag:Name or hostname part of private_dns_name
10
10
  # :roles, # tag:Roles.split(',') such as web:app1,db:app1
11
11
  # :region, # ENV['AWS_REGION'],
12
12
  # :instance, # Aws::EC2::Types::Instance itself
13
13
  #
14
14
  # and OPTIONAL_ARRAY_TAGS, OPTIONAL_STRING_TAGS
15
+ def initialize(instance)
16
+ @instance = instance
17
+ end
15
18
 
16
- extend Forwardable
17
- def_delegators :instance,
18
- :instance_id,
19
- :private_ip_address,
20
- :public_ip_address,
21
- :launch_time
22
- def state; self[:instance].state.name; end
23
- def monitoring; self[:instance].monitoring.state; end
24
-
25
- alias_method :ip, :private_ip_address
26
- alias_method :start_date, :launch_time
27
- def usages; self[:roles]; end
28
-
29
- def self.initialize(instance)
30
- d = self.new
31
- d.instance = instance
32
- d.set_hostname
33
- d.set_roles
34
- d.set_region
35
- d.set_string_tags
36
- d.set_array_tags
37
- d
19
+ def hostname
20
+ return @hostname if @hostname
21
+ @hostname = find_string_tag(Config.hostname_tag)
22
+ @hostname = instance.private_dns_name.split('.').first if @hostname.empty?
23
+ @hostname
38
24
  end
39
25
 
40
- # match with condition or not
41
- #
42
- # @param [Hash] condition search parameters
43
- def match?(condition)
44
- return false if !condition[:state] and (terminated? or shutting_down?)
45
- return false unless role_match?(condition)
46
- condition = HashUtil.except(condition,
47
- :role, :role1, :role2, :role3,
48
- :usage, :usage1, :usage2, :usage3
49
- )
50
- condition.each do |key, values|
51
- v = get_value(key)
52
- if v.is_a?(Array)
53
- return false unless v.find {|_| values.include?(_) }
54
- else
55
- return false unless values.include?(v)
56
- end
57
- end
58
- true
26
+ def roles
27
+ return @roles if @roles
28
+ roles = find_array_tag(Config.roles_tag)
29
+ @roles = roles.map {|role| EC2::Host::RoleData.build(role) }
59
30
  end
60
31
 
61
- def inspect
62
- sprintf "#<Aws::Host::HostData %s>", info
32
+ def region
33
+ Config.aws_region
63
34
  end
64
35
 
65
- def info
66
- if self[:hostname] and self[:status] and self[:roles] and self[:tags] and self[:service]
67
- # special treatment for DeNA ;)
68
- info = "#{self[:hostname]}:#{self[:status]}"
69
- info << "(#{self[:roles].join(' ')})" unless self[:roles].empty?
70
- info << "[#{self[:tags].join(' ')}]" unless self[:tags].empty?
71
- info << "{#{self[:service]}}" unless self[:service].empty?
72
- info
73
- else
74
- to_hash.to_s
36
+ Config.optional_string_tags.each do |tag|
37
+ field = StringUtil.underscore(tag)
38
+ define_method(field) do
39
+ instance_variable_get("@#{field}") || instance_variable_set("@#{field}", find_string_tag(tag))
75
40
  end
76
41
  end
77
42
 
78
- def to_hash
79
- HashUtil.except(self, :instance).to_h.merge(
80
- instance_id: instance_id,
81
- private_ip_address: private_ip_address,
82
- public_ip_address: public_ip_address,
83
- launch_time: launch_time,
84
- state: state,
85
- monitoring: monitoring,
86
- )
43
+ Config.optional_array_tags.each do |tag|
44
+ field = StringUtil.underscore(tag)
45
+ define_method(field) do
46
+ instance_variable_get("@#{field}") || instance_variable_set("@#{field}", find_array_tag(tag))
47
+ end
87
48
  end
88
49
 
89
- # private
50
+ def instance_id
51
+ instance.instance_id
52
+ end
90
53
 
91
- # "instance.instance_id" => self.send("instance").send("instance_id")
92
- def get_value(key)
93
- v = self
94
- key.to_s.split('.').each {|k| v = v[k] || v.send(k) }
95
- v
54
+ def instance_type
55
+ instance.instance_type
56
+ end
57
+
58
+ def private_ip_address
59
+ instance.private_ip_address
60
+ end
61
+
62
+ def public_ip_address
63
+ instance.public_ip_address
64
+ end
65
+
66
+ def launch_time
67
+ instance.launch_time
68
+ end
69
+
70
+ def state
71
+ instance.state.name
72
+ end
73
+
74
+ def monitoring
75
+ instance.monitoring.state
76
+ end
77
+
78
+ # compatibility with dino-host
79
+ def ip
80
+ private_ip_address
81
+ end
82
+
83
+ # compatibility with dino-host
84
+ def start_date
85
+ launch_time
86
+ end
87
+
88
+ # compatibility with dino-host
89
+ def usages
90
+ roles
96
91
  end
97
92
 
98
93
  def terminated?
@@ -119,49 +114,59 @@ class EC2
119
114
  state == "pending"
120
115
  end
121
116
 
122
- def role_match?(condition)
123
- # usage is an alias of role
124
- if role = (condition[:role] || condition[:usage])
125
- role1, role2, role3 = role.first.split(':')
126
- else
127
- role1 = (condition[:role1] || condition[:usage1] || []).first
128
- role2 = (condition[:role2] || condition[:usage2] || []).first
129
- role3 = (condition[:role3] || condition[:usage3] || []).first
130
- end
131
- if role1
132
- return false unless self[:roles].find {|role| role.match?(role1, role2, role3) }
133
- end
117
+ # match with condition or not
118
+ #
119
+ # @param [Hash] condition search parameters
120
+ def match?(condition)
121
+ return false if !condition[:state] and (terminated? or shutting_down?)
122
+ return false unless role_match?(condition)
123
+ return false unless instance_match?(condition)
134
124
  true
135
125
  end
136
126
 
137
- def set_hostname
138
- self[:hostname] = find_string_tag(Config.hostname_tag)
139
- self[:hostname] = self[:instance].private_dns_name.split('.').first if self[:hostname].empty?
140
- end
141
-
142
- def set_roles
143
- roles = find_array_tag(Config.roles_tag)
144
- self[:roles] = roles.map {|role| EC2::Host::RoleData.initialize(role) }
145
- end
146
-
147
- def set_region
148
- self[:region] = Config.aws_region
149
- end
150
-
151
- def set_string_tags
127
+ def to_hash
128
+ params = {
129
+ "hostname" => hostname,
130
+ "roles" => roles,
131
+ }
152
132
  Config.optional_string_tags.each do |tag|
153
133
  field = StringUtil.underscore(tag)
154
- self[field] = find_string_tag(tag)
134
+ params[field] = send(field)
155
135
  end
156
- end
157
-
158
- def set_array_tags
159
136
  Config.optional_array_tags.each do |tag|
160
137
  field = StringUtil.underscore(tag)
161
- self[field] = find_array_tag(tag)
138
+ params[field] = send(field)
162
139
  end
140
+ params.merge!(
141
+ "region" => region,
142
+ "instance_id" => instance_id,
143
+ "instance_type" => instance_type,
144
+ "private_ip_address" => private_ip_address,
145
+ "public_ip_address" => public_ip_address,
146
+ "launch_time" => launch_time,
147
+ "state" => state,
148
+ "monitoring" => monitoring,
149
+ )
163
150
  end
164
151
 
152
+ def info
153
+ if self.class.display_short_info?
154
+ info = "#{hostname}:#{status}"
155
+ info << "(#{roles.join(',')})" unless roles.empty?
156
+ info << "[#{tags.join(',')}]" unless tags.empty?
157
+ info << "{#{service}}" unless service.empty?
158
+ info
159
+ else
160
+ to_hash.to_s
161
+ end
162
+ end
163
+
164
+ def inspect
165
+ sprintf "#<Aws::Host::HostData %s>", info
166
+ end
167
+
168
+ private
169
+
165
170
  def find_string_tag(key)
166
171
  v = instance.tags.find {|tag| tag.key == key }
167
172
  v ? v.value : ''
@@ -171,6 +176,54 @@ class EC2
171
176
  v = instance.tags.find {|tag| tag.key == key }
172
177
  v ? v.value.split(Config.array_tag_delimiter) : []
173
178
  end
179
+
180
+ def role_match?(condition)
181
+ # usage is an alias of role
182
+ if role = (condition[:role] || condition[:usage])
183
+ role1, role2, role3 = role.first.split(':')
184
+ else
185
+ role1 = (condition[:role1] || condition[:usage1] || []).first
186
+ role2 = (condition[:role2] || condition[:usage2] || []).first
187
+ role3 = (condition[:role3] || condition[:usage3] || []).first
188
+ end
189
+ if role1
190
+ return false unless roles.find {|role| role.match?(role1, role2, role3) }
191
+ end
192
+ true
193
+ end
194
+
195
+ def instance_match?(condition)
196
+ condition = HashUtil.except(condition, :role, :role1, :role2, :role3, :usage, :usage1, :usage2, :usage3)
197
+ condition.each do |key, values|
198
+ v = instance_variable_recursive_get(key)
199
+ if v.is_a?(Array)
200
+ return false unless v.find {|_| values.include?(_) }
201
+ else
202
+ return false unless values.include?(v)
203
+ end
204
+ end
205
+ true
206
+ end
207
+
208
+ # "instance.instance_id" => self.instance.instance_id
209
+ def instance_variable_recursive_get(key)
210
+ v = self
211
+ key.to_s.split('.').each {|k| v = v.send(k) }
212
+ v
213
+ end
214
+
215
+ # compatibility with dono-host
216
+ #
217
+ # If Service,Status,Tags tags are defined
218
+ #
219
+ # OPTIONAL_STRING_TAGS=Service,Status
220
+ # OPTIONAL_ARRAY_TAGS=Tags
221
+ #
222
+ # show in short format, otherwise, same with to_hash.to_s
223
+ def self.display_short_info?
224
+ return @display_short_info unless @display_short_info.nil?
225
+ @display_short_info = method_defined?(:service) and method_defined?(:status) and method_defined?(:tags)
226
+ end
174
227
  end
175
228
  end
176
229
  end