vagrant-dnsdock-hostupdater 0.0.23 → 0.0.24
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.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/lib/client.rb +4 -0
- data/lib/event-watcher.rb +51 -0
- data/lib/host-manager.rb +298 -0
- data/lib/launch-control +46 -0
- data/lib/server.rb +3 -0
- data/lib/vagrant-dnsdock-hostupdater.rb +10 -0
- data/lib/version.rb +5 -0
- data/vagrant-dnsdock-hostupdater.gemspec +2 -2
- metadata +9 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a0dced9da0ff736e5641dd7d6e5e0154f9db87a4
|
4
|
+
data.tar.gz: ab09fc9827e6618275ca8eb3d86fc8d0f6c0afa6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8c9a89a41498583338779437ed08ca5b08a3fc313b20ae7f2f75194b8fdbd4a6b4c030de210b76eac59c93c1829b49f2b1dc2872fb31669ef46737bead77e954
|
7
|
+
data.tar.gz: b8fac1c93d16df2f9f056ae1c990fb364156e339599fc7c00b49fc3e45269fe62f6246cba578af698cdfd29e9d120dc343bcf209307fca7d476ebbd9f6e2b407
|
data/Gemfile.lock
CHANGED
data/lib/client.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require_relative 'host-manager'
|
2
|
+
require 'yaml'
|
3
|
+
require 'pp'
|
4
|
+
|
5
|
+
module HostManager
|
6
|
+
class DockerEvent
|
7
|
+
|
8
|
+
def self.update(ip, port, host_suffix)
|
9
|
+
client = HostManager::Client.create(ip, port)
|
10
|
+
client.host_suffix host_suffix
|
11
|
+
client.sync_hosts Docker::Container.all({:all => true})
|
12
|
+
client.close
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.run(ip, port, host_suffix)
|
16
|
+
# sync existing host entries on start (normally this will remove old left-over entries)
|
17
|
+
self.update(ip, port, host_suffix)
|
18
|
+
|
19
|
+
sleep(2)
|
20
|
+
loop {
|
21
|
+
run_once(ip, port, host_suffix)
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def self.run_once(ip, port, host_suffix)
|
27
|
+
begin
|
28
|
+
Docker::Event.stream { |event|
|
29
|
+
HostManager.log.info 'Connected to docker socket. Listening for events.'
|
30
|
+
# respond to all container events
|
31
|
+
|
32
|
+
begin
|
33
|
+
if event.type == 'container' && %w(start die).include?(event.action)
|
34
|
+
self.update(ip, port, host_suffix)
|
35
|
+
end
|
36
|
+
|
37
|
+
rescue => e
|
38
|
+
puts e.inspect
|
39
|
+
end
|
40
|
+
|
41
|
+
break
|
42
|
+
}
|
43
|
+
|
44
|
+
rescue Docker::Error::TimeoutError
|
45
|
+
HostManager.log.info 'Docker socket connection timed out whilst listening for events. Reconnecting...'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
HostManager::DockerEvent.run('192.168.200.1', 2991, '.local.signal.sh')
|
data/lib/host-manager.rb
ADDED
@@ -0,0 +1,298 @@
|
|
1
|
+
require 'socket' # Get sockets from stdlib
|
2
|
+
require 'json'
|
3
|
+
require_relative 'hosts/hosts'
|
4
|
+
require 'docker'
|
5
|
+
require 'logger'
|
6
|
+
require 'log4r'
|
7
|
+
|
8
|
+
module OS
|
9
|
+
def OS.windows?
|
10
|
+
(/cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM) != nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def OS.mac?
|
14
|
+
(/darwin/ =~ RUBY_PLATFORM) != nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def OS.unix?
|
18
|
+
!OS.windows?
|
19
|
+
end
|
20
|
+
|
21
|
+
def OS.linux?
|
22
|
+
OS.unix? and not OS.mac?
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module HostManager
|
27
|
+
|
28
|
+
def self.log
|
29
|
+
unless @log
|
30
|
+
@log = Log4r::Logger.new('main')
|
31
|
+
@log.outputters << Log4r::Outputter.stdout
|
32
|
+
@log.outputters << Log4r::FileOutputter.new('logmain', :filename => 'host-manager.log')
|
33
|
+
end
|
34
|
+
|
35
|
+
@log
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.comment_prefix
|
39
|
+
'Managed by Vagrant plugin - do not remove.'
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.host_file_path
|
43
|
+
OS.windows? ? `echo %SystemRoot%\\System32\\drivers\\etc\\hosts`.chomp : '/etc/hosts'
|
44
|
+
end
|
45
|
+
|
46
|
+
class Base
|
47
|
+
|
48
|
+
def log
|
49
|
+
HostManager.log
|
50
|
+
end
|
51
|
+
|
52
|
+
def host_name(container_name)
|
53
|
+
if container_name.include?('.')
|
54
|
+
container_name
|
55
|
+
else
|
56
|
+
sanitize_host_value(container_name) + host_suffix
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def compose_host_names(container)
|
61
|
+
names = []
|
62
|
+
labels = container.info['Config']['Labels']
|
63
|
+
|
64
|
+
name = labels['com.docker.compose.project']
|
65
|
+
service = labels['com.docker.compose.service']
|
66
|
+
|
67
|
+
|
68
|
+
if name != nil && service != nil
|
69
|
+
service = sanitize_host_value(service)
|
70
|
+
name = sanitize_host_value(name)
|
71
|
+
|
72
|
+
container_num = labels['com.docker.compose.container-number']
|
73
|
+
if container_num == '1'
|
74
|
+
names.push("#{service}.#{name}#{host_suffix}")
|
75
|
+
else
|
76
|
+
names.push("#{service}-#{container_num}.#{name}#{host_suffix}")
|
77
|
+
end
|
78
|
+
else
|
79
|
+
names.push(host_name(container.info['Name']))
|
80
|
+
end
|
81
|
+
|
82
|
+
names
|
83
|
+
end
|
84
|
+
|
85
|
+
def host_suffix(suffix = nil)
|
86
|
+
if suffix != nil
|
87
|
+
@domain_suffix = suffix
|
88
|
+
else
|
89
|
+
@domain_suffix
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Determine if entry is managed by this utility or not
|
94
|
+
def valid_entry?(element)
|
95
|
+
# check it's a managed host first
|
96
|
+
if element.respond_to?(:comment) && element.comment.to_s.start_with?(HostManager.comment_prefix)
|
97
|
+
# check we have hostname and ip
|
98
|
+
if element.respond_to?(:name) && element.respond_to?(:address)
|
99
|
+
return true
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def get_hosts
|
105
|
+
unless @host_manager
|
106
|
+
@host_manager = Hosts::File.read(HostManager.host_file_path)
|
107
|
+
end
|
108
|
+
|
109
|
+
@host_manager
|
110
|
+
end
|
111
|
+
|
112
|
+
def build_hosts_data(container_data)
|
113
|
+
data = []
|
114
|
+
|
115
|
+
if container_data && container_data.is_a?(Array)
|
116
|
+
container_data.each do |container_info|
|
117
|
+
container = Docker::Container.get(container_info.info['id'])
|
118
|
+
|
119
|
+
if container.info['State']['Running']
|
120
|
+
name = container.info['Name']
|
121
|
+
|
122
|
+
if name
|
123
|
+
ip = container.info['NetworkSettings']['IPAddress']
|
124
|
+
|
125
|
+
if ip
|
126
|
+
compose_host_names(container).each do |hostname|
|
127
|
+
item = {
|
128
|
+
:hostname => hostname,
|
129
|
+
:ip => ip,
|
130
|
+
:containerId => container.info['id']
|
131
|
+
}
|
132
|
+
|
133
|
+
data.push(item)
|
134
|
+
end
|
135
|
+
else
|
136
|
+
log.warn("Ignored request to create hosts entry due to no IP being returned from container: #{name}")
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
data
|
145
|
+
end
|
146
|
+
|
147
|
+
def update_hosts_file(data)
|
148
|
+
if data.is_a?(Hash)
|
149
|
+
required_keys = %w(action hostname ip containerId)
|
150
|
+
required_keys.each do |key|
|
151
|
+
unless data.key?(key)
|
152
|
+
raise "Data for key #{key} not provided."
|
153
|
+
end
|
154
|
+
end
|
155
|
+
execute(data['action'], data['hostname'], data['ip'], data['containerId'])
|
156
|
+
|
157
|
+
elsif data.is_a?(Array)
|
158
|
+
hosts = get_hosts
|
159
|
+
|
160
|
+
# initially remove all entries
|
161
|
+
hosts.elements.delete_if do |element|
|
162
|
+
if valid_entry?(element)
|
163
|
+
log.info 'Removing entry for ' + element.name
|
164
|
+
true
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# add recieved entries
|
169
|
+
data.each do |entry|
|
170
|
+
add_entry(entry, hosts)
|
171
|
+
hosts.write
|
172
|
+
end
|
173
|
+
|
174
|
+
hosts.write
|
175
|
+
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def add_entry(entry, hosts)
|
180
|
+
if entry[:ip] && entry[:containerId] && entry[:hostname]
|
181
|
+
comment = HostManager.comment_prefix + (entry['containerId'] ? " Container ID: #{entry[:containerId]}" : '')
|
182
|
+
log.info "Adding entry: #{entry[:hostname]} => #{entry[:ip]}"
|
183
|
+
hosts.elements << Hosts::Entry.new(entry[:ip], entry[:hostname], :comment => comment)
|
184
|
+
|
185
|
+
else
|
186
|
+
log.warn 'Unable to write entry due to missing values [ip,containerId,hostname]: ' + entry.to_json
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def execute(action, hostname, ip, container_id = nil)
|
191
|
+
hosts = get_hosts
|
192
|
+
|
193
|
+
if action == 'create'
|
194
|
+
hosts.elements.each do |element|
|
195
|
+
if valid_entry?(element) && hostname == element.name
|
196
|
+
raise "Entry already exists! (#{ip} #{hostname})"
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
comment = HostManager.comment_prefix + (container_id ? " Container ID: #{container_id}" : '')
|
201
|
+
|
202
|
+
hosts.elements << Hosts::Entry.new(ip, hostname, :comment => comment)
|
203
|
+
hosts.write
|
204
|
+
|
205
|
+
elsif action == 'delete'
|
206
|
+
hosts.elements.delete_if do |element|
|
207
|
+
if valid_entry?(element)
|
208
|
+
if hostname == element.name
|
209
|
+
log.info 'Removing entry for ' + element.name
|
210
|
+
true
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
hosts.write
|
216
|
+
else
|
217
|
+
raise 'No valid action specified.'
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
private
|
222
|
+
|
223
|
+
def sanitize_host_value(value)
|
224
|
+
value.sub(/^\//, '').gsub(/_/, '-')
|
225
|
+
end
|
226
|
+
|
227
|
+
end
|
228
|
+
|
229
|
+
class Server < Base
|
230
|
+
def run(ip, port)
|
231
|
+
HostManager.log.info "Running on host manager service on #{ip}:#{port}"
|
232
|
+
@tcp_server = TCPServer.new ip, port
|
233
|
+
|
234
|
+
loop {
|
235
|
+
|
236
|
+
client = @tcp_server.accept
|
237
|
+
content = client.gets
|
238
|
+
|
239
|
+
log.debug("Recieved data: #{content.to_s.strip}")
|
240
|
+
|
241
|
+
begin
|
242
|
+
unless content.to_s.chomp.strip.empty?
|
243
|
+
data = JSON.parse(content)
|
244
|
+
update_hosts_file(data)
|
245
|
+
end
|
246
|
+
|
247
|
+
rescue => e
|
248
|
+
log.error("Caught exception attempting to execute with data #{content}. #{e}")
|
249
|
+
end
|
250
|
+
|
251
|
+
client.close
|
252
|
+
}
|
253
|
+
|
254
|
+
end
|
255
|
+
|
256
|
+
def self.create(ip, port)
|
257
|
+
server = self.new
|
258
|
+
server.run(ip, port)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
class Client < Base
|
263
|
+
|
264
|
+
def initialize(ip, port)
|
265
|
+
@socket = TCPSocket.new(ip, port)
|
266
|
+
end
|
267
|
+
|
268
|
+
def close
|
269
|
+
log.info('Closing client connection.')
|
270
|
+
@socket.close
|
271
|
+
end
|
272
|
+
|
273
|
+
def sync_hosts(container_data)
|
274
|
+
data = build_hosts_data(container_data)
|
275
|
+
|
276
|
+
begin
|
277
|
+
update_hosts_file(data)
|
278
|
+
|
279
|
+
`service dnsmasq restart`
|
280
|
+
|
281
|
+
rescue => e
|
282
|
+
log.error("Caught exception attempting to write to hosts file with data #{data.to_json}. #{e}")
|
283
|
+
end
|
284
|
+
|
285
|
+
send(data)
|
286
|
+
end
|
287
|
+
|
288
|
+
def send(data)
|
289
|
+
log.info("Sending data via client: #{data.to_json}")
|
290
|
+
@socket.write data.to_json + "\n"
|
291
|
+
end
|
292
|
+
|
293
|
+
def self.create(ip, port)
|
294
|
+
self.new(ip, port)
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
end
|
data/lib/launch-control
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- mode: ruby -*-
|
3
|
+
# vi: set ft=ruby :
|
4
|
+
require 'docker'
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
|
8
|
+
# build up hash of id => project name
|
9
|
+
id_project_hash = {}
|
10
|
+
Docker::Container.all(all: true, filters: { status: ["running"] }.to_json).each do |container|
|
11
|
+
# puts container.info['id']
|
12
|
+
# puts container.info['Labels']['com.docker.compose.project'].inspect
|
13
|
+
|
14
|
+
id_project_hash[container.info['id']] = container.info['Labels']['com.docker.compose.project']
|
15
|
+
# puts container.info['NetworkSettings']['IPAddress']
|
16
|
+
# puts container.info.to_yaml
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
module HostManager
|
21
|
+
def self.register(hostname, ip)
|
22
|
+
entry = "#{ip} #{hostname}"
|
23
|
+
puts entry
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
Docker::Network.all.each do |network|
|
28
|
+
# puts network.info['Options'].inspect
|
29
|
+
# puts network.inspect
|
30
|
+
network.info['Containers'].each do |pair|
|
31
|
+
id = pair[0]
|
32
|
+
container = pair[1]
|
33
|
+
ip = container['IPv4Address'][/[^\/]+/]
|
34
|
+
if id_project_hash.key?(id) and id_project_hash[id]
|
35
|
+
project_name = id_project_hash[id]
|
36
|
+
HostManager.register("#{container['Name']}.#{project_name}.local", ip)
|
37
|
+
elsif
|
38
|
+
HostManager.register("#{container['Name']}.local", ip)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Docker::Event.stream { |event|
|
44
|
+
# # respond to all events
|
45
|
+
# puts event.Attributes.name;
|
46
|
+
# }
|
data/lib/server.rb
ADDED
data/lib/version.rb
ADDED
@@ -11,9 +11,9 @@ DNSDock should be configured on the guest machine to enable containers to resolv
|
|
11
11
|
DESCRIPTION
|
12
12
|
s.authors = ['Brian Coit']
|
13
13
|
s.email = 'brian.coit@cellosignal.com'
|
14
|
-
s.files = Dir['{lib}'] + ['Rakefile', 'Gemfile', 'Gemfile.lock', 'vagrant-dnsdock-hostupdater.gemspec']
|
14
|
+
s.files = Dir['{lib}/**'] + ['Rakefile', 'Gemfile', 'Gemfile.lock', 'vagrant-dnsdock-hostupdater.gemspec']
|
15
15
|
s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
16
|
-
s.require_paths = ['lib
|
16
|
+
s.require_paths = ['lib']
|
17
17
|
s.homepage = 'https://bitbucket.org/briancoit'
|
18
18
|
s.license = 'ISC'
|
19
19
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: vagrant-dnsdock-hostupdater
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.24
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brian Coit
|
@@ -64,6 +64,13 @@ files:
|
|
64
64
|
- Gemfile
|
65
65
|
- Gemfile.lock
|
66
66
|
- Rakefile
|
67
|
+
- lib/client.rb
|
68
|
+
- lib/event-watcher.rb
|
69
|
+
- lib/host-manager.rb
|
70
|
+
- lib/launch-control
|
71
|
+
- lib/server.rb
|
72
|
+
- lib/vagrant-dnsdock-hostupdater.rb
|
73
|
+
- lib/version.rb
|
67
74
|
- vagrant-dnsdock-hostupdater.gemspec
|
68
75
|
homepage: https://bitbucket.org/briancoit
|
69
76
|
licenses:
|
@@ -72,7 +79,7 @@ metadata: {}
|
|
72
79
|
post_install_message:
|
73
80
|
rdoc_options: []
|
74
81
|
require_paths:
|
75
|
-
- lib
|
82
|
+
- lib
|
76
83
|
required_ruby_version: !ruby/object:Gem::Requirement
|
77
84
|
requirements:
|
78
85
|
- - ">="
|