gce-host 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/.yardopts +6 -0
  4. data/CHANGELOG.md +3 -0
  5. data/Gemfile +3 -0
  6. data/LICENSE +22 -0
  7. data/README.md +210 -0
  8. data/Rakefile +30 -0
  9. data/bin/gce-host +4 -0
  10. data/docs/GCE.html +132 -0
  11. data/docs/GCE/Host.html +901 -0
  12. data/docs/GCE/Host/CLI.html +610 -0
  13. data/docs/GCE/Host/Config.html +1195 -0
  14. data/docs/GCE/Host/GCEClient.html +215 -0
  15. data/docs/GCE/Host/GCEClient/Error.html +127 -0
  16. data/docs/GCE/Host/GCEClient/NotFound.html +131 -0
  17. data/docs/GCE/Host/HashUtil.html +178 -0
  18. data/docs/GCE/Host/HostData.html +1658 -0
  19. data/docs/GCE/Host/RoleData.html +932 -0
  20. data/docs/GCE/Host/StringUtil.html +359 -0
  21. data/docs/_index.html +231 -0
  22. data/docs/class_list.html +58 -0
  23. data/docs/css/common.css +1 -0
  24. data/docs/css/full_list.css +57 -0
  25. data/docs/css/style.css +339 -0
  26. data/docs/file.LICENSE.html +95 -0
  27. data/docs/file.README.html +312 -0
  28. data/docs/file_list.html +63 -0
  29. data/docs/frames.html +26 -0
  30. data/docs/index.html +312 -0
  31. data/docs/js/app.js +219 -0
  32. data/docs/js/full_list.js +181 -0
  33. data/docs/js/jquery.js +4 -0
  34. data/docs/method_list.html +477 -0
  35. data/docs/top-level-namespace.html +112 -0
  36. data/example/example.conf +8 -0
  37. data/example/example.rb +6 -0
  38. data/gce-host.gemspec +26 -0
  39. data/lib/gce-host.rb +7 -0
  40. data/lib/gce/host.rb +120 -0
  41. data/lib/gce/host/cli.rb +151 -0
  42. data/lib/gce/host/config.rb +109 -0
  43. data/lib/gce/host/gce_client.rb +69 -0
  44. data/lib/gce/host/hash_util.rb +11 -0
  45. data/lib/gce/host/host_data.rb +227 -0
  46. data/lib/gce/host/role_data.rb +64 -0
  47. data/lib/gce/host/string_util.rb +31 -0
  48. data/lib/gce/host/version.rb +5 -0
  49. data/spec/gce_client_spec.rb +29 -0
  50. data/spec/host_spec.rb +199 -0
  51. data/spec/spec_helper.rb +22 -0
  52. data/terraform.tf +52 -0
  53. metadata +226 -0
@@ -0,0 +1,69 @@
1
+ require_relative 'version'
2
+ require 'google/apis/compute_v1'
3
+
4
+ class GCE
5
+ class Host
6
+ class GCEClient
7
+ class Error < ::StandardError; end
8
+ class NotFound < Error; end
9
+
10
+ def instances(condition = {})
11
+ filter = build_filter(condition)
12
+ instances = []
13
+ res = client.list_aggregated_instances(Config.project, filter: filter)
14
+ instances.concat(res.items.values.map(&:instances).compact.flatten(1))
15
+ while res.next_page_token
16
+ res = client.list_aggregated_instances(Config.project, filter: filter, page_token: res.next_page_token)
17
+ instances.concat(res.items.values.map(&:instances).compact.flatten(1))
18
+ end
19
+ instances
20
+ end
21
+
22
+ private
23
+
24
+ def client
25
+ return @client if @client && @client_expiration > Time.now
26
+
27
+ scope = "https://www.googleapis.com/auth/compute.readonly"
28
+ client = Google::Apis::ComputeV1::ComputeService.new
29
+ client.client_options.application_name = 'gce-host'
30
+ client.client_options.application_name = GCE::Host::VERSION
31
+ client.request_options.retries = Config.retries
32
+ client.request_options.timeout_sec = Config.timeout_sec
33
+ client.request_options.open_timeout_sec = Config.open_timeout_sec
34
+
35
+ case Config.auth_method
36
+ when 'compute_engine'
37
+ auth = Google::Auth::GCECredentials.new
38
+
39
+ when 'json_key'
40
+ credential_file = Config.credential_file
41
+ auth = File.open(credential_file) do |f|
42
+ Google::Auth::ServiceAccountCredentials.make_creds(json_key_io: f, scope: scope)
43
+ end
44
+
45
+ when 'application_default'
46
+ auth = Google::Auth.get_application_default([scope])
47
+
48
+ else
49
+ raise ConfigError, "Unknown auth method: #{Config.auth_method}"
50
+ end
51
+
52
+ client.authorization = auth
53
+
54
+ @client_expiration = Time.now + 1800
55
+ @client = client
56
+ end
57
+
58
+ # MEMO: OR did not work
59
+ # MEMO: filter for metadata and tags did not work (metadata.items[0].value eq role)
60
+ def build_filter(condition)
61
+ if names = (condition[:name] || condition[:hostname]) and Array(names).size == 1
62
+ "name eq #{Array(names).first}"
63
+ else
64
+ nil
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,11 @@
1
+ class GCE
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,227 @@
1
+ require 'json'
2
+
3
+ class GCE
4
+ class Host
5
+ # Represents each host
6
+ class HostData
7
+ attr_reader :instance
8
+
9
+ # :hostname, # hostname
10
+ # :roles, # roles.split(',') such as web:app1,db:app1
11
+ # :instance, # Aws::GCE::Types::Instance itself
12
+ #
13
+ # and OPTIONAL_ARRAY_KEYS, OPTIONAL_STRING_KEYS
14
+ def initialize(instance)
15
+ @instance = instance
16
+ end
17
+
18
+ def hostname
19
+ instance.name
20
+ end
21
+
22
+ def roles
23
+ return @roles if @roles
24
+ roles = find_array_key(Config.roles_key)
25
+ @roles = roles.map {|role| GCE::Host::RoleData.build(role) }
26
+ end
27
+
28
+ Config.optional_string_keys.each do |key|
29
+ field = StringUtil.underscore(key)
30
+ define_method(field) do
31
+ instance_variable_get("@#{field}") || instance_variable_set("@#{field}", find_string_key(key))
32
+ end
33
+ end
34
+
35
+ Config.optional_array_keys.each do |key|
36
+ field = StringUtil.underscore(key)
37
+ define_method(field) do
38
+ instance_variable_get("@#{field}") || instance_variable_set("@#{field}", find_array_key(key))
39
+ end
40
+ end
41
+
42
+ define_method(Config.status) do
43
+ instance.status
44
+ end
45
+
46
+ def instance_id
47
+ instance.id
48
+ end
49
+
50
+ def zone
51
+ instance.zone.split('/').last
52
+ end
53
+
54
+ def private_ip_address
55
+ instance.network_interfaces.first.network_ip
56
+ end
57
+
58
+ def private_ip_addresses
59
+ instance.network_interfaces.map(&:network_ip)
60
+ end
61
+
62
+ def public_ip_address
63
+ instance.network_interfaces.first.access_configs.first.nat_ip
64
+ end
65
+
66
+ def public_ip_addresses
67
+ instance.network_interfaces.map {|i| i.access_configs.map(&:nat_ip) }.flatten(1)
68
+ end
69
+
70
+ def creation_timestamp
71
+ instance.creation_timestamp
72
+ end
73
+
74
+ # compatibility with dino-host
75
+ def ip
76
+ private_ip_address
77
+ end
78
+
79
+ # compatibility with dino-host
80
+ def start_date
81
+ creation_timestamp
82
+ end
83
+
84
+ # compatibility with dino-host
85
+ def usages
86
+ roles
87
+ end
88
+
89
+ def terminated?
90
+ instance.status == "TERMINATED"
91
+ end
92
+
93
+ def stopping?
94
+ instance.status == "STOPPING"
95
+ end
96
+
97
+ def running?
98
+ instance.status == "RUNNING"
99
+ end
100
+
101
+ def staging?
102
+ instance.status == "STAGING"
103
+ end
104
+
105
+ def provisioning?
106
+ instance.status == "PROVISIONING"
107
+ end
108
+
109
+ # match with condition or not
110
+ #
111
+ # @param [Hash] condition search parameters
112
+ def match?(condition)
113
+ return false if !condition[Config.status.to_sym] and (terminated? or stopping?)
114
+ return false unless role_match?(condition)
115
+ return false unless status_match?(condition)
116
+ return false unless instance_match?(condition)
117
+ true
118
+ end
119
+
120
+ def to_hash
121
+ params = {
122
+ "hostname" => hostname,
123
+ "roles" => roles,
124
+ "zone" => zone,
125
+ }
126
+ Config.optional_string_keys.each do |key|
127
+ field = StringUtil.underscore(key)
128
+ params[field] = send(field)
129
+ end
130
+ Config.optional_array_keys.each do |key|
131
+ field = StringUtil.underscore(key)
132
+ params[field] = send(field)
133
+ end
134
+ params.merge!(
135
+ "instance_id" => instance_id,
136
+ "private_ip_address" => private_ip_address,
137
+ "public_ip_address" => public_ip_address,
138
+ "creation_timestamp" => creation_timestamp,
139
+ Config.status => send(Config.status),
140
+ )
141
+ end
142
+
143
+ def info
144
+ if self.class.display_short_info?
145
+ info = "#{hostname}:#{status}"
146
+ info << "(#{roles.join(',')})" unless roles.empty?
147
+ info << "[#{tags.join(',')}]" unless tags.empty?
148
+ info << "{#{service}}" unless service.empty?
149
+ info
150
+ else
151
+ to_hash.to_s
152
+ end
153
+ end
154
+
155
+ def inspect
156
+ sprintf "#<GCE::Host::HostData %s>", info
157
+ end
158
+
159
+ private
160
+
161
+ def find_string_key(key)
162
+ item = instance.metadata.items.find {|item| item.key == key } if instance.metadata.items
163
+ item ? item.value : ''
164
+ end
165
+
166
+ def find_array_key(key)
167
+ item = instance.metadata.items.find {|item| item.key == key } if instance.metadata.items
168
+ item ? item.value.split(Config.array_value_delimiter) : []
169
+ end
170
+
171
+ def role_match?(condition)
172
+ # usage is an alias of role
173
+ if role = (condition[:role] || condition[:usage])
174
+ role1, role2, role3 = role.first.split(':')
175
+ else
176
+ role1 = (condition[:role1] || condition[:usage1] || []).first
177
+ role2 = (condition[:role2] || condition[:usage2] || []).first
178
+ role3 = (condition[:role3] || condition[:usage3] || []).first
179
+ end
180
+ if role1
181
+ return false unless roles.find {|role| role.match?(role1, role2, role3) }
182
+ end
183
+ true
184
+ end
185
+
186
+ def status_match?(condition)
187
+ if values = condition[Config.status.to_sym]
188
+ return false unless values.map(&:downcase).include?(send(Config.status).downcase)
189
+ end
190
+ true
191
+ end
192
+
193
+ def instance_match?(condition)
194
+ condition = HashUtil.except(condition, :role, :role1, :role2, :role3, :usage, :usage1, :usage2, :usage3, Config.status.to_sym)
195
+ condition.each do |key, values|
196
+ v = instance_variable_recursive_get(key)
197
+ if v.is_a?(Array)
198
+ return false unless v.find {|_| values.include?(_) }
199
+ else
200
+ return false unless values.include?(v)
201
+ end
202
+ end
203
+ true
204
+ end
205
+
206
+ # "instance.instance_id" => self.instance.instance_id
207
+ def instance_variable_recursive_get(key)
208
+ v = self
209
+ key.to_s.split('.').each {|k| v = v.send(k) }
210
+ v
211
+ end
212
+
213
+ # compatibility with dono-host
214
+ #
215
+ # If service,status,tags keys are defined
216
+ #
217
+ # OPTIONAL_STRING_KEYS=service,status
218
+ # OPTIONAL_ARRAY_KEYS=tags
219
+ #
220
+ # show in short format, otherwise, same with to_hash.to_s
221
+ def self.display_short_info?
222
+ return @display_short_info unless @display_short_info.nil?
223
+ @display_short_info = method_defined?(:service) and method_defined?(:status) and method_defined?(:tags)
224
+ end
225
+ end
226
+ end
227
+ end
@@ -0,0 +1,64 @@
1
+ class GCE
2
+ class Host
3
+ # Represents each role
4
+ class RoleData
5
+ attr_reader :role1, :role2, :role3
6
+
7
+ def initialize(role1, role2 = nil, role3 = nil)
8
+ @role1 = role1
9
+ @role2 = role2
10
+ @role3 = role3
11
+ end
12
+
13
+ def self.build(role)
14
+ role1, role2, role3 = role.split(Config.role_value_delimiter, 3)
15
+ new(role1, role2, role3)
16
+ end
17
+
18
+ # @return [String] something like "admin:jenkins:slave"
19
+ def role
20
+ @role ||= [role1, role2, role3].compact.reject(&:empty?).join(Config.role_value_delimiter)
21
+ end
22
+ alias :to_s :role
23
+
24
+ # @return [Array] something like ["admin", "admin:jenkins", "admin:jenkins:slave"]
25
+ def uppers
26
+ uppers = [RoleData.new(role1)]
27
+ uppers << RoleData.new(role1, role2) if role2 and !role2.empty?
28
+ uppers << RoleData.new(role1, role2, role3) if role3 and !role3.empty?
29
+ uppers
30
+ end
31
+
32
+ def match?(role1, role2 = nil, role3 = nil)
33
+ if role3
34
+ role1 == self.role1 and role2 == self.role2 and role3 == self.role3
35
+ elsif role2
36
+ role1 == self.role1 and role2 == self.role2
37
+ else
38
+ role1 == self.role1
39
+ end
40
+ end
41
+
42
+ # Equality
43
+ #
44
+ # Role::Data.new('admin') == Role::Data.new('admin') #=> true
45
+ # Role::Data.new('admin', 'jenkin') == "admin:jenkins" #=> true
46
+ #
47
+ # @param [Object] other
48
+ def ==(other)
49
+ case other
50
+ when String
51
+ self.role == other
52
+ when GCE::Host::RoleData
53
+ super(other)
54
+ else
55
+ false
56
+ end
57
+ end
58
+
59
+ def inspect
60
+ "\"#{to_s}\""
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,31 @@
1
+ class GCE
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
@@ -0,0 +1,5 @@
1
+ class GCE
2
+ class Host
3
+ VERSION = '0.1.0'
4
+ end
5
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe GCE::Host::GCEClient do
4
+ let(:client) { GCE::Host::GCEClient.new }
5
+ let(:instances) { client.instances }
6
+ let(:instance) { instances.first }
7
+ let(:name) { instance.name }
8
+
9
+ describe '#instances' do
10
+ it do
11
+ expect(instances).not_to be_empty
12
+ end
13
+
14
+ it 'filter with name' do
15
+ instances = client.instances(name: name)
16
+ expect(instances).not_to be_empty
17
+ expect(instances.first.name).to eq(name)
18
+
19
+ instances = client.instances(name: 'something_not_exist')
20
+ expect(instances).to be_empty
21
+ end
22
+
23
+ it 'filter with hostname (alias of name)' do
24
+ instances = client.instances(hostname: name)
25
+ expect(instances).not_to be_empty
26
+ expect(instances.first.name).to eq(name)
27
+ end
28
+ end
29
+ end
data/spec/host_spec.rb ADDED
@@ -0,0 +1,199 @@
1
+ require 'spec_helper'
2
+
3
+ shared_examples_for 'host' do
4
+ it 'should respond_to' do
5
+ [ :hostname,
6
+ :roles,
7
+ :zone,
8
+ GCE::Host::Config.status.to_sym,
9
+ :service,
10
+ :status,
11
+ :tags,
12
+ :instance,
13
+ :instance_id,
14
+ :private_ip_address,
15
+ :private_ip_addresses,
16
+ :public_ip_address,
17
+ :public_ip_addresses,
18
+ :creation_timestamp,
19
+ :ip,
20
+ :start_date,
21
+ :usages,
22
+ ].each do |k|
23
+ expect(subject.respond_to?(k)).to be_truthy
24
+ end
25
+ end
26
+ end
27
+
28
+ describe GCE::Host do
29
+ describe 'options' do
30
+ context do
31
+ let(:subject) { GCE::Host.new(hostname: 'gce-host-web', options: {foo:'bar'}) }
32
+ it { expect(subject.options).to eq({foo:'bar'}) }
33
+ it { expect(subject.conditions).to eq([{hostname: ['gce-host-web']}]) }
34
+ end
35
+
36
+ context do
37
+ let(:subject) { GCE::Host.new({hostname: 'gce-host-web'}, {hostname: 'gce-host-db'}, options: {foo:'bar'}) }
38
+ it { expect(subject.options).to eq({foo:'bar'}) }
39
+ it { expect(subject.conditions).to eq([{hostname: ['gce-host-web']}, {hostname: ['gce-host-db']}]) }
40
+ end
41
+ end
42
+
43
+ context '#to_hash' do
44
+ let(:subject) { GCE::Host.new(service: 'gce-host').first.to_hash }
45
+
46
+ it 'keys' do
47
+ expect(subject.keys).to eq([
48
+ 'hostname',
49
+ 'roles',
50
+ 'zone',
51
+ 'service',
52
+ 'status',
53
+ 'tags',
54
+ 'instance_id',
55
+ 'private_ip_address',
56
+ 'public_ip_address',
57
+ 'creation_timestamp',
58
+ GCE::Host::Config.status,
59
+ ])
60
+ end
61
+
62
+ it 'values are not empty' do
63
+ expect(subject.values.any? {|v| v.nil? or (v.respond_to?(:empty?) and v.empty?) }).to be_falsey
64
+ end
65
+ end
66
+
67
+ context 'by hostname' do
68
+ let(:hosts) { GCE::Host.new(hostname: 'gce-host-web').to_a }
69
+ let(:subject) { hosts.first }
70
+ it_should_behave_like 'host'
71
+ it { expect(hosts.size).to eq(1) }
72
+ it { expect(subject.hostname).to eq('gce-host-web') }
73
+ end
74
+
75
+ context 'by instance_id' do
76
+ let(:instance_id) { GCE::Host.new(service: 'gce-host').first.instance_id }
77
+
78
+ context 'by instance_id' do
79
+ let(:hosts) { GCE::Host.new(instance_id: instance_id).to_a }
80
+ let(:subject) { hosts.first }
81
+ it_should_behave_like 'host'
82
+ it { expect(hosts.size).to eq(1) }
83
+ it { expect(subject.instance_id).to eq(instance_id) }
84
+ end
85
+ end
86
+
87
+ context 'by role' do
88
+ context 'by a role' do
89
+ let(:subject) { GCE::Host.new(role: 'web:test').first }
90
+ it_should_behave_like 'host'
91
+ end
92
+
93
+ context 'by a role1' do
94
+ let(:subject) { GCE::Host.new(role1: 'web').first }
95
+ it_should_behave_like 'host'
96
+ end
97
+
98
+ context 'by multiple roles (or)' do
99
+ let(:hosts) {
100
+ GCE::Host.new(
101
+ {
102
+ role1: 'web',
103
+ role2: 'test',
104
+ },
105
+ {
106
+ role1: 'db',
107
+ role2: 'test',
108
+ },
109
+ ).to_a
110
+ }
111
+ let(:subject) { hosts.first }
112
+ it { expect(hosts.size).to be >= 2 }
113
+ it_should_behave_like 'host'
114
+ end
115
+ end
116
+
117
+ # for compatibility with dino-host
118
+ context 'by usage' do
119
+ context 'by a usage' do
120
+ let(:subject) { GCE::Host.new(usage: 'web:test').first }
121
+ it_should_behave_like 'host'
122
+ end
123
+
124
+ context 'by a usage1' do
125
+ let(:subject) { GCE::Host.new(usage1: 'web').first }
126
+ it_should_behave_like 'host'
127
+ end
128
+
129
+ context 'by multiple usages (or)' do
130
+ let(:hosts) {
131
+ GCE::Host.new(
132
+ {
133
+ usage1: 'web',
134
+ usage2: 'test',
135
+ },
136
+ {
137
+ usage1: 'db',
138
+ usage2: 'test',
139
+ },
140
+ ).to_a
141
+ }
142
+ let(:subject) { hosts.first }
143
+ it { expect(hosts.size).to be >= 2 }
144
+ it_should_behave_like 'host'
145
+ end
146
+ end
147
+
148
+ context 'by status (optional array tags)' do
149
+ context 'by a status' do
150
+ let(:subject) { GCE::Host.new(status: :active).first }
151
+ it_should_behave_like 'host'
152
+ end
153
+
154
+ context 'by multiple status (or)' do
155
+ let(:hosts) { GCE::Host.new(status: [:reserve, :active]).to_a }
156
+ let(:subject) { hosts.first }
157
+ it_should_behave_like 'host'
158
+ it { expect(hosts.size).to be >= 2 }
159
+ end
160
+
161
+ context 'by a string status' do
162
+ let(:subject) { GCE::Host.new(status: 'active').first }
163
+ it_should_behave_like 'host'
164
+ end
165
+
166
+ context 'by multiple string status (or)' do
167
+ let(:hosts) { GCE::Host.new(status: ['reserve', 'active']).to_a }
168
+ let(:subject) { hosts.first }
169
+ it_should_behave_like 'host'
170
+ it { expect(hosts.size).to be >= 2 }
171
+ end
172
+ end
173
+
174
+ context 'by service (optional string tags)' do
175
+ context 'by a service' do
176
+ let(:subject) { GCE::Host.new(service: 'gce-host').first }
177
+ it_should_behave_like 'host'
178
+ end
179
+
180
+ context 'by multiple services (or)' do
181
+ let(:hosts) { GCE::Host.new(service: ['test', 'gce-host']) }
182
+ let(:subject) { hosts.first }
183
+ it_should_behave_like 'host'
184
+ end
185
+ end
186
+
187
+ context 'by tags (optional array tags)' do
188
+ context 'by a tag' do
189
+ let(:subject) { GCE::Host.new(tags: 'master').first }
190
+ it_should_behave_like 'host'
191
+ end
192
+
193
+ context 'by multiple tags (or)' do
194
+ let(:hosts) { GCE::Host.new(tags: ['standby', 'master']) }
195
+ let(:subject) { hosts.first }
196
+ it_should_behave_like 'host'
197
+ end
198
+ end
199
+ end