gcloud_hosts 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/README.md +58 -0
- data/Rakefile +6 -0
- data/bin/gcloud_hosts +6 -0
- data/gcloud_hosts.gemspec +30 -0
- data/lib/gcloud_hosts/hosts.rb +49 -0
- data/lib/gcloud_hosts/options.rb +68 -0
- data/lib/gcloud_hosts/runner.rb +52 -0
- data/lib/gcloud_hosts/updater.rb +132 -0
- data/lib/gcloud_hosts/version.rb +3 -0
- data/lib/gcloud_hosts.rb +11 -0
- metadata +102 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5ba67d06a8c97f3882fac1ff37cc025d3d64a7f1
|
4
|
+
data.tar.gz: 7520d469eec780d23438d6985d92e2cf87c75678
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5a2b239695545db654fe3ad1b01e11e84f2a17da16753f79c9dfea8fee940cfe9b2bf3b1e5c0daf40442df1c2a0817c9783cfb9524902121be993d5aaab24677
|
7
|
+
data.tar.gz: ca94497f4fc039bb66504cecbee41223ab61e11952b775f0949d1354dcedb6f1e5704ca1bf3e15a4fe9b20ff3451edf23678ee2a11ded8fae6ac587d07bda0e5
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# gcloud_hosts
|
2
|
+
|
3
|
+
Update your hosts file based on gcloud compute instances.
|
4
|
+
|
5
|
+
This is handy when used in conjunction with something like [sshuttle](https://github.com/sshuttle/sshuttle),
|
6
|
+
allowing you to have a "poor man's vpn".
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
```shell
|
11
|
+
$ gem install gcloud_hosts
|
12
|
+
```
|
13
|
+
|
14
|
+
## Requirements
|
15
|
+
|
16
|
+
Requires [gcloud tool](https://cloud.google.com/sdk/gcloud/) installed and authenticated against at least 1 GCP project.
|
17
|
+
|
18
|
+
## Usage
|
19
|
+
|
20
|
+
```shell
|
21
|
+
$ gcloud_hosts -h
|
22
|
+
Usage: $ gcloud_hosts [options]
|
23
|
+
-g, --gcloud GCLOUD Path to gcloud executable. Defaults to PATH location
|
24
|
+
-p, --project PROJECT gcloud project to use. Defaults to default gcloud configuration.
|
25
|
+
-n, --network NETWORK gcloud network to filter on. Defaults nil.
|
26
|
+
-d, --domain DOMAIN Domain to append to all hosts. Default: "c.[PROJECT].internal"
|
27
|
+
--public PUBLIC Pattern to match for public/bastion hosts. Use public IP for these. Defaults to nil
|
28
|
+
-f, --file FILE Hosts file to update. Defaults to /etc/hosts
|
29
|
+
-b, --backup BACKUP Path to backup original hosts file to. Defaults to FILE with '.bak' extension appended.
|
30
|
+
--[no-]dry-run Dry run, do not modify hosts file. Defaults to false
|
31
|
+
--[no-]delete Delete the project from hosts file. Defaults to false
|
32
|
+
--help Show this message
|
33
|
+
--version Show version
|
34
|
+
```
|
35
|
+
|
36
|
+
## Example
|
37
|
+
|
38
|
+
Update your hosts file using gcloud_hosts:
|
39
|
+
|
40
|
+
```shell
|
41
|
+
$ sudo gcloud_hosts -p my-cool-project --public bastion
|
42
|
+
|
43
|
+
```
|
44
|
+
Start sshuttle session:
|
45
|
+
|
46
|
+
```shell
|
47
|
+
$ sshuttle --remote=bastion01 --daemon --pidfile=/tmp/sshuttle.pid 192.168.1.0/24
|
48
|
+
```
|
49
|
+
|
50
|
+
Now your hosts file will contain entries for all compute instances in the project,
|
51
|
+
and you can ssh directly to them from your local machine.
|
52
|
+
|
53
|
+
Hosts matching the pattern passed in with the `--public` flag will have their public
|
54
|
+
IP address added to your host file instead of the their private internal IP address.
|
55
|
+
|
56
|
+
## Contributing
|
57
|
+
|
58
|
+
Bug reports and pull requests are welcome on GitHub at [https://github.com/atongen/gcloud_hosts](https://github.com/atongen/gcloud_hosts).
|
data/Rakefile
ADDED
data/bin/gcloud_hosts
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'gcloud_hosts/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "gcloud_hosts"
|
8
|
+
spec.version = GcloudHosts::VERSION
|
9
|
+
spec.authors = ["Andrew Tongen"]
|
10
|
+
spec.email = ["atongen@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Update your hosts file based on gcloud compute instances}
|
13
|
+
spec.description = %q{Update your hosts file based on gcloud compute instances}
|
14
|
+
spec.homepage = "https://github.com/atongen/gcloud_hosts"
|
15
|
+
|
16
|
+
if spec.respond_to?(:metadata)
|
17
|
+
spec.metadata['allowed_push_host'] = "https://rubygems.org"
|
18
|
+
else
|
19
|
+
raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
20
|
+
end
|
21
|
+
|
22
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
23
|
+
spec.bindir = "bin"
|
24
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
25
|
+
spec.require_paths = ["lib"]
|
26
|
+
|
27
|
+
spec.add_development_dependency "bundler", "~> 1.11"
|
28
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
29
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
30
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module GcloudHosts
|
4
|
+
module Hosts
|
5
|
+
|
6
|
+
def self.instances(gcloud_path, project, network)
|
7
|
+
JSON.parse(%x{ #{gcloud_path} compute instances list --project #{project} --format json 2>/dev/null })
|
8
|
+
.select { |i| i["status"] == "RUNNING" }
|
9
|
+
.select do |i|
|
10
|
+
network.to_s.strip == "" ||
|
11
|
+
i["networkInterfaces"].any? { |ni| ni["network"] == network }
|
12
|
+
end.sort { |x,y| x["name"] <=> y["name"] }
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.hosts(gcloud_path, project, network, domain, public_pattern)
|
16
|
+
instances(gcloud_path, project, network).inject([]) do |list, i|
|
17
|
+
begin
|
18
|
+
if public_pattern.to_s.strip != "" && i["name"].downcase.include?(public_pattern)
|
19
|
+
# get external ip address
|
20
|
+
i["networkInterfaces"].each do |ni|
|
21
|
+
ni["accessConfigs"].each do |ac|
|
22
|
+
if ac["name"].downcase.include?("nat") && ac["type"].downcase.include?("nat")
|
23
|
+
if ip = ac["natIP"]
|
24
|
+
str = "#{ip} #{i["name"]}"
|
25
|
+
list << str
|
26
|
+
raise HostError.new
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
else
|
32
|
+
# get first internal private network interface
|
33
|
+
i["networkInterfaces"].each do |ni|
|
34
|
+
if ni["name"] == "nic0"
|
35
|
+
if ip = ni["networkIP"]
|
36
|
+
str = "#{ip} #{i["name"]}"
|
37
|
+
str << " #{i["name"]}.#{domain}" unless domain.to_s.strip == ""
|
38
|
+
list << str
|
39
|
+
raise HostError.new
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
rescue HostError; end
|
45
|
+
list
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module GcloudHosts
|
4
|
+
class Options
|
5
|
+
|
6
|
+
attr_reader :options
|
7
|
+
|
8
|
+
def initialize(args)
|
9
|
+
@options = {
|
10
|
+
gcloud: %x{ which gcloud 2>/dev/null }.to_s.strip,
|
11
|
+
project: nil,
|
12
|
+
network: nil,
|
13
|
+
domain: nil,
|
14
|
+
public: nil,
|
15
|
+
file: '/etc/hosts',
|
16
|
+
backup: nil,
|
17
|
+
dry_run: false,
|
18
|
+
delete: false
|
19
|
+
}
|
20
|
+
parser.parse!(args)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def parser
|
26
|
+
@parser ||= begin
|
27
|
+
OptionParser.new do |opts|
|
28
|
+
opts.banner = "Usage: $ gcloud_hosts [options]"
|
29
|
+
opts.on('-g', '--gcloud GCLOUD', "Path to gcloud executable. Defaults to PATH location") do |opt|
|
30
|
+
@options[:project] = opt
|
31
|
+
end
|
32
|
+
opts.on('-p', '--project PROJECT', "gcloud project to use. Defaults to default gcloud configuration.") do |opt|
|
33
|
+
@options[:project] = opt
|
34
|
+
end
|
35
|
+
opts.on('-n', '--network NETWORK', "gcloud network to filter on. Defaults nil.") do |opt|
|
36
|
+
@options[:network] = opt
|
37
|
+
end
|
38
|
+
opts.on('-d', '--domain DOMAIN', "Domain to append to all hosts. Default: \"c.[PROJECT].internal\"") do |opt|
|
39
|
+
@options[:domain] = opt
|
40
|
+
end
|
41
|
+
opts.on('--public PUBLIC', "Pattern to match for public/bastion hosts. Use public IP for these. Defaults to nil") do |opt|
|
42
|
+
@options[:public] = opt
|
43
|
+
end
|
44
|
+
opts.on('-f', '--file FILE', "Hosts file to update. Defaults to /etc/hosts") do |opt|
|
45
|
+
@options[:file] = opt
|
46
|
+
end
|
47
|
+
opts.on('-b', '--backup BACKUP', "Path to backup original hosts file to. Defaults to FILE with '.bak' extension appended.") do |opt|
|
48
|
+
@options[:file] = opt
|
49
|
+
end
|
50
|
+
opts.on('--[no-]dry-run', "Dry run, do not modify hosts file. Defaults to false") do |opt|
|
51
|
+
@options[:dry_run] = opt
|
52
|
+
end
|
53
|
+
opts.on('--[no-]delete', "Delete the project from hosts file. Defaults to false") do |opt|
|
54
|
+
@options[:delete] = opt
|
55
|
+
end
|
56
|
+
opts.on_tail("--help", "Show this message") do
|
57
|
+
puts opts
|
58
|
+
exit
|
59
|
+
end
|
60
|
+
opts.on_tail("--version", "Show version") do
|
61
|
+
puts ::GcloudHosts::VERSION.join('.')
|
62
|
+
exit
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module GcloudHosts
|
4
|
+
class Runner
|
5
|
+
|
6
|
+
def initialize(args)
|
7
|
+
@options = Options.new(args).options
|
8
|
+
end
|
9
|
+
|
10
|
+
def run!
|
11
|
+
project = @options[:project]
|
12
|
+
if project.to_s.strip == ""
|
13
|
+
project = env["core"]["project"]
|
14
|
+
end
|
15
|
+
if project.to_s.strip == ""
|
16
|
+
raise AuthError.new("No gcloud project specified.")
|
17
|
+
end
|
18
|
+
|
19
|
+
if @options[:domain]
|
20
|
+
domain = @options[:domain].to_s.strip
|
21
|
+
else
|
22
|
+
domain = "c.#{project}.internal"
|
23
|
+
end
|
24
|
+
|
25
|
+
backup = @options[:backup] ||
|
26
|
+
@options[:file] + '.bak'
|
27
|
+
|
28
|
+
if @options[:delete]
|
29
|
+
new_hosts_list = []
|
30
|
+
else
|
31
|
+
new_hosts_list = Hosts.hosts(@options[:gcloud], project, @options[:network], domain, @options[:public])
|
32
|
+
end
|
33
|
+
Updater.update(new_hosts_list.join("\n"), project, @options[:file], backup, @options[:dry_run], @options[:delete])
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def env
|
39
|
+
@env ||= begin
|
40
|
+
gcloud = @options[:gcloud]
|
41
|
+
if gcloud.to_s.strip == ""
|
42
|
+
raise AuthError.new("gcloud command not found.")
|
43
|
+
end
|
44
|
+
env = JSON.parse(%x{ #{gcloud} config list --format json 2>/dev/null })
|
45
|
+
if env["core"]["account"].to_s.strip == ""
|
46
|
+
raise AuthError.new("Please log into gcloud.")
|
47
|
+
end
|
48
|
+
env
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
module GcloudHosts
|
2
|
+
# Updater implements a very state machine which is used to update
|
3
|
+
# content between zero or more blocks of content which start and end
|
4
|
+
# with pre-defined "marker" lines.
|
5
|
+
module Updater
|
6
|
+
|
7
|
+
module Marker
|
8
|
+
BEFORE = 0
|
9
|
+
INSIDE = 1
|
10
|
+
AFTER = 2
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.update(new_hosts, project, file, backup_file, dry_run, delete)
|
14
|
+
start_marker = "# START GCLOUD HOSTS - #{project} #"
|
15
|
+
end_marker = "# END GCLOUD HOSTS - #{project} #"
|
16
|
+
|
17
|
+
old_hosts = File.read(file)
|
18
|
+
|
19
|
+
if old_hosts.include?(start_marker) && old_hosts.include?(end_marker)
|
20
|
+
# valid markers exists
|
21
|
+
if delete
|
22
|
+
new_content = delete_project_hosts(old_hosts, start_marker, end_marker)
|
23
|
+
else
|
24
|
+
new_content = gen_new_hosts(old_hosts, new_hosts, start_marker, end_marker)
|
25
|
+
end
|
26
|
+
# remove zero or more white space characters at end of file with
|
27
|
+
# a single new-line
|
28
|
+
new_content.gsub!(/\s+$/, "\n")
|
29
|
+
|
30
|
+
if dry_run
|
31
|
+
puts new_content
|
32
|
+
elsif new_content != old_hosts
|
33
|
+
# backup old host file
|
34
|
+
File.open(backup_file, 'w') { |f| f << old_hosts }
|
35
|
+
# write new content
|
36
|
+
File.open(file, 'w') { |f| f << new_content }
|
37
|
+
end
|
38
|
+
elsif old_hosts.include?(start_marker) || old_hosts.include?(end_marker)
|
39
|
+
raise UpdaterError.new("Invalid marker present in existing hosts content")
|
40
|
+
else
|
41
|
+
# marker doesn't exist
|
42
|
+
if delete
|
43
|
+
new_content = old_hosts
|
44
|
+
else
|
45
|
+
new_content = [old_hosts, start_marker, new_hosts, end_marker].join("\n")
|
46
|
+
end
|
47
|
+
# remove one or more white space characters at end of file with
|
48
|
+
# a single new-line
|
49
|
+
new_content.gsub!(/\s+$/, "\n")
|
50
|
+
|
51
|
+
if dry_run
|
52
|
+
puts new_content
|
53
|
+
elsif new_content != old_hosts
|
54
|
+
# backup old host file
|
55
|
+
File.open(backup_file, 'w') { |f| f << old_hosts }
|
56
|
+
# write new content
|
57
|
+
File.open(file, 'w') { |f| f << new_content }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
true
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.gen_new_hosts(hosts, new_hosts, start_marker, end_marker)
|
65
|
+
new_content = ''
|
66
|
+
marker_state = Marker::BEFORE
|
67
|
+
hosts.split("\n").each do |line|
|
68
|
+
if line == start_marker
|
69
|
+
if marker_state == Marker::BEFORE
|
70
|
+
# transition to inside the marker
|
71
|
+
new_content << start_marker + "\n"
|
72
|
+
marker_state = Marker::INSIDE
|
73
|
+
# add new host content
|
74
|
+
new_hosts.split("\n").each do |host|
|
75
|
+
new_content << host + "\n"
|
76
|
+
end
|
77
|
+
else
|
78
|
+
raise UpdaterError.new("Invalid marker state")
|
79
|
+
end
|
80
|
+
elsif line == end_marker
|
81
|
+
if marker_state == Marker::INSIDE
|
82
|
+
# transition to after the marker
|
83
|
+
new_content << end_marker + "\n"
|
84
|
+
marker_state = Marker::AFTER
|
85
|
+
else
|
86
|
+
raise UpdaterError.new("Invalid marker state")
|
87
|
+
end
|
88
|
+
else
|
89
|
+
case marker_state
|
90
|
+
when Marker::BEFORE, Marker::AFTER
|
91
|
+
new_content << line + "\n"
|
92
|
+
when Marker::INSIDE
|
93
|
+
# skip everything between old markers
|
94
|
+
next
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
new_content
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.delete_project_hosts(hosts, start_marker, end_marker)
|
102
|
+
new_content = ''
|
103
|
+
marker_state = Marker::BEFORE
|
104
|
+
hosts.split("\n").each do |line|
|
105
|
+
if line == start_marker
|
106
|
+
if marker_state == Marker::BEFORE
|
107
|
+
marker_state = Marker::INSIDE
|
108
|
+
# don't add any content, we're deleting this block
|
109
|
+
else
|
110
|
+
raise UpdaterError.new("Invalid marker state")
|
111
|
+
end
|
112
|
+
elsif line == end_marker
|
113
|
+
if marker_state == Marker::INSIDE
|
114
|
+
marker_state = Marker::AFTER
|
115
|
+
else
|
116
|
+
raise UpdaterError.new("Invalid marker state")
|
117
|
+
end
|
118
|
+
else
|
119
|
+
case marker_state
|
120
|
+
when Marker::BEFORE, Marker::AFTER
|
121
|
+
new_content << line + "\n"
|
122
|
+
when Marker::INSIDE
|
123
|
+
# skip everything between old markers
|
124
|
+
next
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
new_content
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
end
|
data/lib/gcloud_hosts.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'gcloud_hosts/version'
|
2
|
+
require 'gcloud_hosts/options'
|
3
|
+
require 'gcloud_hosts/hosts'
|
4
|
+
require 'gcloud_hosts/updater'
|
5
|
+
require 'gcloud_hosts/runner'
|
6
|
+
|
7
|
+
module GcloudHosts
|
8
|
+
class HostError < StandardError; end
|
9
|
+
class UpdaterError < StandardError; end
|
10
|
+
class AuthError < StandardError; end
|
11
|
+
end
|
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gcloud_hosts
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Andrew Tongen
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-02-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.11'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.11'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
description: Update your hosts file based on gcloud compute instances
|
56
|
+
email:
|
57
|
+
- atongen@gmail.com
|
58
|
+
executables:
|
59
|
+
- gcloud_hosts
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- ".gitignore"
|
64
|
+
- ".rspec"
|
65
|
+
- ".travis.yml"
|
66
|
+
- Gemfile
|
67
|
+
- README.md
|
68
|
+
- Rakefile
|
69
|
+
- bin/gcloud_hosts
|
70
|
+
- gcloud_hosts.gemspec
|
71
|
+
- lib/gcloud_hosts.rb
|
72
|
+
- lib/gcloud_hosts/hosts.rb
|
73
|
+
- lib/gcloud_hosts/options.rb
|
74
|
+
- lib/gcloud_hosts/runner.rb
|
75
|
+
- lib/gcloud_hosts/updater.rb
|
76
|
+
- lib/gcloud_hosts/version.rb
|
77
|
+
homepage: https://github.com/atongen/gcloud_hosts
|
78
|
+
licenses: []
|
79
|
+
metadata:
|
80
|
+
allowed_push_host: https://rubygems.org
|
81
|
+
post_install_message:
|
82
|
+
rdoc_options: []
|
83
|
+
require_paths:
|
84
|
+
- lib
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
requirements: []
|
96
|
+
rubyforge_project:
|
97
|
+
rubygems_version: 2.4.5.1
|
98
|
+
signing_key:
|
99
|
+
specification_version: 4
|
100
|
+
summary: Update your hosts file based on gcloud compute instances
|
101
|
+
test_files: []
|
102
|
+
has_rdoc:
|