gce-host 0.1.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.
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,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="GCE.html" title="GCE (class)">GCE</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 Wed Nov 23 20:08:31 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>
@@ -0,0 +1,8 @@
1
+ AUTH_METHOD=json_key
2
+ GOOGLE_CREDENTIAL_FILE=example/your-project-000.json
3
+ ROLES_KEY=roles
4
+ ROLE_VALUE_DELIMITER=:
5
+ OPTIONAL_STRING_KEYS=service,status
6
+ OPTIONAL_ARRAY_KEYS=tags
7
+ ARRAY_VALUE_DELIMITER=,
8
+ LOG_LEVEL=info
@@ -0,0 +1,6 @@
1
+ require 'gce-host'
2
+ require 'pp'
3
+
4
+ GCE::Host.new(zone: 'asia-northeast1-a').each do |host|
5
+ pp host
6
+ end
data/gce-host.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ Gem::Specification.new do |gem|
2
+ gem.name = "gce-host"
3
+ gem.version = '0.1.0'
4
+ gem.author = ['Naotoshi Seo']
5
+ gem.email = ['sonots@gmail.com']
6
+ gem.homepage = 'https://github.com/sonots/gce-host'
7
+ gem.summary = "Search hosts on GCP GCE"
8
+ gem.description = "Search hosts on GCP GCE"
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 'google-api-client'
17
+ gem.add_runtime_dependency 'dotenv'
18
+
19
+ gem.add_development_dependency 'yard'
20
+ gem.add_development_dependency 'rspec'
21
+ gem.add_development_dependency 'simplecov'
22
+ gem.add_development_dependency 'pry'
23
+ gem.add_development_dependency 'pry-nav'
24
+ gem.add_development_dependency 'rake'
25
+ gem.add_development_dependency 'bundler'
26
+ end
data/lib/gce-host.rb ADDED
@@ -0,0 +1,7 @@
1
+ require_relative 'gce/host/string_util'
2
+ require_relative 'gce/host/hash_util'
3
+ require_relative 'gce/host/config'
4
+ require_relative 'gce/host/host_data'
5
+ require_relative 'gce/host/role_data'
6
+ require_relative 'gce/host/gce_client'
7
+ require_relative 'gce/host'
data/lib/gce/host.rb ADDED
@@ -0,0 +1,120 @@
1
+ require 'socket'
2
+
3
+ class GCE
4
+ # Search GCE hosts from labels
5
+ #
6
+ # require 'gce-host'
7
+ # # Search by hostname
8
+ # GCE::Host.new(hostname: 'test').first # => test
9
+ #
10
+ # # Search by `roles` label
11
+ # GCE::Host.new(
12
+ # role: 'admin:haikanko',
13
+ # ).each do |host|
14
+ # # ...
15
+ # end
16
+ #
17
+ # or
18
+ #
19
+ # GCE::Host.new(
20
+ # role1: 'admin',
21
+ # role2: 'haikanko',
22
+ # ).each do |host|
23
+ # # ...
24
+ # end
25
+ #
26
+ # # Or search
27
+ # GCE::Host.new(
28
+ # {
29
+ # role1: 'db',
30
+ # role2: 'master',
31
+ # },
32
+ # {
33
+ # role1: 'web',
34
+ # }
35
+ # ).each do |host|
36
+ # # ...
37
+ # end
38
+ #
39
+ # GCE::Host.me.hostname # => 'test'
40
+ class Host
41
+ include Enumerable
42
+
43
+ # @return [Host::Data] representing myself
44
+ def self.me
45
+ new(hostname: Socket.gethostname).each do |d|
46
+ return d
47
+ end
48
+ raise 'Not Found'
49
+ end
50
+
51
+ # Configure GCE::Host
52
+ #
53
+ # @param [Hash] params see GCE::Host::Config for configurable parameters
54
+ def self.configure(params = {})
55
+ Config.configure(params)
56
+ end
57
+
58
+ def self.gce_client
59
+ @gce_client ||= GCEClient.new
60
+ end
61
+
62
+ def gce_client
63
+ self.class.gce_client
64
+ end
65
+
66
+ attr_reader :conditions, :options
67
+
68
+ # @param [Array of Hash, or Hash] conditions (and options)
69
+ #
70
+ # GCE::Host.new(
71
+ # hostname: 'test',
72
+ # options: {a: 'b'}
73
+ # )
74
+ #
75
+ # GCE::Host.new(
76
+ # {
77
+ # hostname: 'foo',
78
+ # },
79
+ # {
80
+ # hostname: 'bar',
81
+ # },
82
+ # options: {a: 'b'}
83
+ # )
84
+ def initialize(*conditions)
85
+ conditions = [{}] if conditions.empty?
86
+ conditions = [conditions] if conditions.kind_of?(Hash)
87
+ @options = {}
88
+ if conditions.size == 1
89
+ @options = conditions.first.delete(:options) || {}
90
+ else
91
+ index = conditions.find_index {|condition| condition.has_key?(:options) }
92
+ @options = conditions.delete_at(index)[:options] if index
93
+ end
94
+ raise ArgumentError, "Hash expected (options)" unless @options.is_a?(Hash)
95
+ @conditions = []
96
+ conditions.each do |condition|
97
+ @conditions << Hash[condition.map {|k, v| [k, Array(v).map(&:to_s)]}]
98
+ end
99
+ raise ArgumentError, "Array of Hash, or Hash expected (conditions)" unless @conditions.all? {|h| h.kind_of?(Hash)}
100
+ end
101
+
102
+ # @yieldparam [Host::Data] data entry
103
+ def each(&block)
104
+ @conditions.each do |condition|
105
+ search(gce_client.instances(condition), condition, &block)
106
+ end
107
+ return self
108
+ end
109
+
110
+ private
111
+
112
+ def search(instances, condition)
113
+ instances.each do |i|
114
+ d = GCE::Host::HostData.new(i)
115
+ next unless d.match?(condition)
116
+ yield d
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,151 @@
1
+ require 'gce-host'
2
+ require 'optparse'
3
+
4
+ class GCE
5
+ class Host
6
+ class CLI
7
+ attr_reader :options
8
+
9
+ def initialize(argv = ARGV)
10
+ @options = parse_options(argv)
11
+ end
12
+
13
+ def parse_options(argv = ARGV)
14
+ op = OptionParser.new
15
+
16
+ self.class.module_eval do
17
+ define_method(:usage) do |msg = nil|
18
+ puts op.to_s
19
+ puts "error: #{msg}" if msg
20
+ exit 1
21
+ end
22
+ end
23
+
24
+ opts = {
25
+ state: ["running"]
26
+ }
27
+
28
+ op.on('--hostname one,two,three', Array, "name") {|v|
29
+ opts[:hostname] = v
30
+ }
31
+ op.on('-r', '--role one,two,three', Array, "role") {|v|
32
+ opts[:role] = v
33
+ }
34
+ op.on('--r1', '--role1 one,two,three', Array, "role1, the 1st part of role delimited by #{Config.role_value_delimiter}") {|v|
35
+ opts[:role1] = v
36
+ }
37
+ op.on('--r2', '--role2 one,two,three', Array, "role2, the 2st part of role delimited by #{Config.role_value_delimiter}") {|v|
38
+ opts[:role2] = v
39
+ }
40
+ op.on('--r3', '--role3 one,two,three', Array, "role3, the 3st part of role delimited by #{Config.role_value_delimiter}") {|v|
41
+ opts[:role3] = v
42
+ }
43
+ op.on('--instance-id one,two,three', Array, "instance_id") {|v|
44
+ opts[:instance_id] = v
45
+ }
46
+ op.on("--#{Config.status} one,two,three", Array, "filter with instance #{Config.status} (default: running)") {|v|
47
+ opts[Config.status.to_sym] = v
48
+ }
49
+ Config.optional_options.keys.each do |opt|
50
+ op.on("--#{opt.to_s.gsub('_', '-')} one,two,three", Array, opt) {|v|
51
+ opts[opt.to_sym] = v
52
+ }
53
+ end
54
+ op.on('-a', '--all', "list all hosts (remove default filter)") {|v|
55
+ [:hostname, :role, :role1, :role2, :role3, :instance_id, Config.status.to_sym].each do |key|
56
+ opts.delete(key)
57
+ end
58
+ Config.optional_options.keys.each do |opt|
59
+ opts.delete(opt.to_sym)
60
+ end
61
+ }
62
+ op.on('--private-ip', '--ip', "show private ip address instead of hostname") {|v|
63
+ opts[:private_ip] = v
64
+ }
65
+ op.on('--public-ip', "show public ip address instead of hostname") {|v|
66
+ opts[:public_ip] = v
67
+ }
68
+ op.on('-i', '--info', "show host info") {|v|
69
+ opts[:info] = v
70
+ }
71
+ op.on('-j', '--jsonl', "show host info in line delimited json") {|v|
72
+ opts[:jsonl] = v
73
+ }
74
+ op.on('--json', "show host info in json") {|v|
75
+ opts[:json] = v
76
+ }
77
+ op.on('--pretty-json', "show host info in pretty json") {|v|
78
+ opts[:pretty_json] = v
79
+ }
80
+ op.on('--debug', "debug mode") {|v|
81
+ opts[:debug] = v
82
+ }
83
+ op.on('-h', '--help', "show help") {|v|
84
+ opts[:help] = v
85
+ }
86
+
87
+ begin
88
+ args = op.parse(argv)
89
+ rescue OptionParser::InvalidOption => e
90
+ usage e.message
91
+ end
92
+
93
+ if opts[:help]
94
+ usage
95
+ end
96
+
97
+ opts
98
+ end
99
+
100
+ def run
101
+ hosts = GCE::Host.new(condition)
102
+ if options[:info]
103
+ hosts.each do |host|
104
+ $stdout.puts host.info
105
+ end
106
+ elsif options[:jsonl]
107
+ hosts.each do |host|
108
+ $stdout.puts host.to_hash.to_json
109
+ end
110
+ elsif options[:json]
111
+ $stdout.puts hosts.map(&:to_hash).to_json
112
+ elsif options[:pretty_json]
113
+ $stdout.puts JSON.pretty_generate(hosts.map(&:to_hash))
114
+ elsif options[:private_ip]
115
+ hosts.each do |host|
116
+ $stdout.puts host.private_ip_address
117
+ end
118
+ elsif options[:public_ip]
119
+ hosts.each do |host|
120
+ $stdout.puts host.public_ip_address
121
+ end
122
+ else
123
+ hosts.each do |host|
124
+ $stdout.puts host.hostname
125
+ end
126
+ end
127
+ end
128
+
129
+ private
130
+
131
+ def condition
132
+ return @condition if @condition
133
+ _condition = HashUtil.except(options, :info, :jsonl, :json, :pretty_json, :debug, :private_ip, :public_ip)
134
+ @condition = {}
135
+ _condition.each do |key, val|
136
+ if label = Config.optional_options[key.to_s]
137
+ field = StringUtil.underscore(label)
138
+ @condition[field.to_sym] = val
139
+ else
140
+ @condition[key.to_sym] = val
141
+ end
142
+ end
143
+ if options[:debug]
144
+ $stderr.puts(options: options)
145
+ $stderr.puts(condition: @condition)
146
+ end
147
+ @condition
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,109 @@
1
+ require 'dotenv'
2
+ Dotenv.load
3
+
4
+ class GCE
5
+ class Host
6
+ class Config
7
+ def self.configure(params)
8
+ params.each do |key, val|
9
+ send("#{key}=", val)
10
+ end
11
+ end
12
+
13
+ def self.config_file
14
+ @config_file ||= ENV.fetch('GCE_HOST_CONFIG_FILE', '/etc/sysconfig/gce-host')
15
+ end
16
+
17
+ def self.auth_method
18
+ @auth_method ||= ENV['AUTH_METHOD'] || config.fetch('AUTH_METHOD', 'application_default')
19
+ end
20
+
21
+ def self.credential_file
22
+ @credential_file ||= ENV['GOOGLE_CREDENTIAL_FILE'] || config.fetch('GOOGLE_CREDENTIAL_FILE', nil)
23
+ end
24
+
25
+ def self.project
26
+ return @project if @project
27
+ # ref. terraform https://www.terraform.io/docs/providers/google/
28
+ @project ||= ENV['GOOGLE_PROJECT'] || config.fetch('GOOGLE_PROJECT', nil)
29
+ if @project.nil? and credential_file and File.readable?(credential_file)
30
+ @project ||= (JSON.parse(File.read(credential_file)) || {})['project_id']
31
+ end
32
+ @project
33
+ end
34
+
35
+ def self.log_level
36
+ @log_level ||= ENV['LOG_LEVEL'] || config.fetch('LOG_LEVEL', 'info')
37
+ end
38
+
39
+ def self.retries
40
+ @retries ||= ENV['RETRIES'] || config.fetch('RETRIES', 5)
41
+ end
42
+
43
+ def self.timeout_sec
44
+ @timeout_sec ||= ENV['TIMEOUT_SEC'] || config.fetch('TIMEOUT_SEC', 300)
45
+ end
46
+
47
+ def self.open_timeout_sec
48
+ @open_timeout_sec ||= ENV['OPEN_TIMEOUT_SEC'] || config.fetch('OPEN_TIMEOUT_SEC', 300)
49
+ end
50
+
51
+ def self.roles_key
52
+ @roles_key ||= ENV['ROLES_KEY'] || config.fetch('ROLES_KEY', 'roles')
53
+ end
54
+
55
+ def self.optional_array_keys
56
+ @optional_array_keys ||= (ENV['OPTIONAL_ARRAY_KEYS'] || config.fetch('OPTIONAL_ARRAY_KEYS', '')).split(',')
57
+ end
58
+
59
+ def self.optional_string_keys
60
+ @optional_string_keys ||= (ENV['OPTIONAL_STRING_KEYS'] || config.fetch('OPTIONAL_STRING_KEYS', '')).split(',')
61
+ end
62
+
63
+ def self.role_value_delimiter
64
+ @role_value_delimiter ||= ENV['ROLE_VALUE_DELIMITER'] || config.fetch('ROLE_VALUE_DELIMITER', ':')
65
+ end
66
+
67
+ def self.array_value_delimiter
68
+ @array_value_delimiter ||= ENV['ARRAY_VALUE_DELIMITER'] || config.fetch('ARRAY_VALUE_DELIMITER', ',')
69
+ end
70
+
71
+ # this makes configurable to change status to state to make compatible with AWS
72
+ # usually, users do not need to care of this
73
+ def self.status
74
+ @status ||= ENV['STATUS'] || config.fetch('STATUS', 'status')
75
+ end
76
+
77
+ # private
78
+
79
+ def self.optional_array_options
80
+ @optional_array_options ||= Hash[optional_array_keys.map {|key|
81
+ [StringUtil.singularize(StringUtil.underscore(key)), key]
82
+ }]
83
+ end
84
+
85
+ def self.optional_string_options
86
+ @optional_string_options ||= Hash[optional_string_keys.map {|key|
87
+ [StringUtil.underscore(key), key]
88
+ }]
89
+ end
90
+
91
+ def self.optional_options
92
+ @optional_options ||= optional_array_options.merge(optional_string_options)
93
+ end
94
+
95
+ def self.config
96
+ return @config if @config
97
+ @config = {}
98
+ if File.exist?(config_file)
99
+ File.readlines(config_file).each do |line|
100
+ next if line.start_with?('#')
101
+ key, val = line.chomp.split('=', 2)
102
+ @config[key] = val
103
+ end
104
+ end
105
+ @config
106
+ end
107
+ end
108
+ end
109
+ end