zt 0.1.1
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 +7 -0
- data/.editorconfig +15 -0
- data/.env.example +1 -0
- data/.gitignore +31 -0
- data/.idea/codeStyles/Project.xml +52 -0
- data/.idea/codeStyles/codeStyleConfig.xml +5 -0
- data/.idea/dbnavigator.xml +454 -0
- data/.idea/inspectionProfiles/Project_Default.xml +13 -0
- data/.idea/markdown-exported-files.xml +8 -0
- data/.idea/markdown-navigator.xml +88 -0
- data/.idea/markdown-navigator/profiles_settings.xml +3 -0
- data/.idea/misc.xml +7 -0
- data/.idea/modules.xml +8 -0
- data/.idea/vcs.xml +6 -0
- data/.idea/workspace.xml +475 -0
- data/.idea/zt.iml +41 -0
- data/.rspec +3 -0
- data/.rubocop.yml +4457 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -0
- data/.versions.conf +4 -0
- data/CODE_OF_CONDUCT.md +75 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +103 -0
- data/Rakefile +6 -0
- data/bin/console +13 -0
- data/bin/setup +8 -0
- data/exe/zt +4 -0
- data/lib/zt.rb +14 -0
- data/lib/zt/cli.rb +91 -0
- data/lib/zt/conf.rb +97 -0
- data/lib/zt/constants.rb +46 -0
- data/lib/zt/errors.rb +3 -0
- data/lib/zt/errors/conf_errors.rb +26 -0
- data/lib/zt/exporters.rb +32 -0
- data/lib/zt/exporters/_base_exporter.rb +16 -0
- data/lib/zt/exporters/hosts_file_exporter.rb +32 -0
- data/lib/zt/importers.rb +28 -0
- data/lib/zt/importers/_base_importer.rb +44 -0
- data/lib/zt/importers/network_importer.rb +46 -0
- data/lib/zt/importers/node_importer.rb +54 -0
- data/lib/zt/remote_api.rb +39 -0
- data/lib/zt/version.rb +5 -0
- data/zt.gemspec +57 -0
- metadata +259 -0
data/lib/zt/constants.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'xdg'
|
4
|
+
|
5
|
+
module Zt
|
6
|
+
module Constants
|
7
|
+
ALL_EXPORTERS = %w[
|
8
|
+
HostsFileExporter
|
9
|
+
].freeze
|
10
|
+
ALL_IMPORTERS = %w[
|
11
|
+
NetworkImporter
|
12
|
+
NodeImporter
|
13
|
+
].freeze
|
14
|
+
AUTH_TOKEN_LENGTH = 32
|
15
|
+
AUTH_TOKEN_REGEX = /^[A-Za-z0-9]+$/.freeze
|
16
|
+
CONF_DIR = "#{XDG['CONFIG_HOME']}/zt"
|
17
|
+
CONF_SECTIONS = {
|
18
|
+
domains: 'domains.yaml',
|
19
|
+
networks: 'networks.yaml',
|
20
|
+
nodes: 'nodes.yaml',
|
21
|
+
zt: 'zt.conf.yaml'
|
22
|
+
}.freeze
|
23
|
+
# noinspection RubyStringKeysInHashInspection
|
24
|
+
INITIAL_CONF = {
|
25
|
+
'nodes.yaml' => {
|
26
|
+
'node_id' => {
|
27
|
+
'hostname' => 'node-hostname',
|
28
|
+
'networks' => %w[network_id_1 network_id_2]
|
29
|
+
}
|
30
|
+
},
|
31
|
+
'networks.yaml' => {
|
32
|
+
'network_id' => {
|
33
|
+
'name' => 'network_name'
|
34
|
+
}
|
35
|
+
},
|
36
|
+
'domains.yaml' => {
|
37
|
+
'network_id' => 'domain-name.zt'
|
38
|
+
},
|
39
|
+
'zt.conf.yaml' => {
|
40
|
+
'manual_pulls' => false,
|
41
|
+
'token' => 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
|
42
|
+
'top_level_domain' => 'zt'
|
43
|
+
}
|
44
|
+
}.freeze
|
45
|
+
end
|
46
|
+
end
|
data/lib/zt/errors.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Zt
|
4
|
+
module Errors
|
5
|
+
# Config does not exist on disk and cannot be created
|
6
|
+
class ZtConfDiskError < StandardError
|
7
|
+
def initialize
|
8
|
+
super
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# Config exists on disk but cannot be parsed
|
13
|
+
class ZtConfSyntaxError < StandardError
|
14
|
+
def initialize
|
15
|
+
super
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Invalid section specifier for config
|
20
|
+
class ZtConfInvalidSectionError < StandardError
|
21
|
+
def initialize
|
22
|
+
super
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/zt/exporters.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'zt/constants'
|
4
|
+
require 'zt/exporters/_base_exporter'
|
5
|
+
require 'zt/exporters/hosts_file_exporter'
|
6
|
+
|
7
|
+
module Zt
|
8
|
+
module Exporters
|
9
|
+
class Exporter
|
10
|
+
def initialize(*exporter_names)
|
11
|
+
exporter_names = Zt::Constants::ALL_EXPORTERS if exporter_names.empty?
|
12
|
+
|
13
|
+
exporter_classes = exporter_names.map { |n| Zt::Exporters.const_get(n) }
|
14
|
+
@exporters = exporter_classes.map(&:new)
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [Array[Hash]] an array of hashes from each exporter
|
18
|
+
# The hash is in form { data: Object, mangle: lambda }
|
19
|
+
# with the :mangle lambda containing any steps to apply the Object
|
20
|
+
# like rewriting /etc/hosts or sending the data elsewhere.
|
21
|
+
# NOTE there may be value in attributing each hash to its
|
22
|
+
# importer, consider that for later as a Hash[Hash].
|
23
|
+
def export
|
24
|
+
@exporters.map(&:export)
|
25
|
+
end
|
26
|
+
|
27
|
+
def export_one_format(format)
|
28
|
+
Zt::Exporters::HostsFileExporter.new.export if format == :hosts
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'zt/exporters/_base_exporter'
|
4
|
+
require 'zt/conf'
|
5
|
+
|
6
|
+
module Zt
|
7
|
+
module Exporters
|
8
|
+
class HostsFileExporter < BaseExporter
|
9
|
+
def export
|
10
|
+
# process the normalised Hash @data and return a String and a Block
|
11
|
+
# defining what to do with the String
|
12
|
+
conf = Zt::Conf.instance.conf
|
13
|
+
output = ''
|
14
|
+
conf.nodes.each_key do |node_id|
|
15
|
+
node = conf.nodes[node_id]
|
16
|
+
node_nets = node[:local][:networks]
|
17
|
+
node_nets.each_key do |net_id|
|
18
|
+
net_zone = conf.networks[net_id][:local][:dns_zone]
|
19
|
+
node_name = node[:remote][:node_name]
|
20
|
+
node_addrs = node[:local][:networks][net_id]
|
21
|
+
node_addrs.each do |node_addr|
|
22
|
+
output += "#{node_addr}\t\t#{node_name}.#{net_zone}\n"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
"# BEGIN zt\n" + output.lines.sort_by do |a|
|
27
|
+
a.split[1]
|
28
|
+
end.join + "# END zt\n"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/zt/importers.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'zt/constants'
|
4
|
+
require 'zt/importers/_base_importer'
|
5
|
+
require 'zt/importers/network_importer'
|
6
|
+
require 'zt/importers/node_importer'
|
7
|
+
|
8
|
+
module Zt
|
9
|
+
module Importers
|
10
|
+
class Importer
|
11
|
+
attr_accessor :importers
|
12
|
+
def initialize(networks, nodes, *importer_names)
|
13
|
+
importer_names = Zt::Constants::ALL_IMPORTERS if importer_names.empty?
|
14
|
+
importer_classes = importer_names.map do |n|
|
15
|
+
Zt::Importers.const_get(n)
|
16
|
+
end
|
17
|
+
@importers = importer_classes.map { |c| c.new(networks, nodes) }
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [Array[Hash]] an array of hashes from each exporter.
|
21
|
+
# NOTE there may be value in attributing each hash to its
|
22
|
+
# importer, consider that for later as a Hash[Hash].
|
23
|
+
def import
|
24
|
+
@importers.map(&:import)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'zt/conf'
|
4
|
+
|
5
|
+
module Zt
|
6
|
+
module Importers
|
7
|
+
class BaseImporter
|
8
|
+
attr_accessor :networks
|
9
|
+
attr_accessor :nodes
|
10
|
+
def initialize(networks, nodes)
|
11
|
+
@networks = networks
|
12
|
+
@nodes = nodes
|
13
|
+
end
|
14
|
+
|
15
|
+
def import
|
16
|
+
abort('import called on non-functional superclass importer')
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def dnsify(input)
|
22
|
+
zt_conf = Zt::Conf.instance.conf.zt
|
23
|
+
tld_key = 'top_level_domains'
|
24
|
+
tld = if zt_conf.key?(tld_key) && !zt_conf[tld_key].empty?
|
25
|
+
zt_conf[tld_key]
|
26
|
+
else
|
27
|
+
'zt'
|
28
|
+
end
|
29
|
+
clean = input.tr('-', '_')
|
30
|
+
clean = clean.gsub(/([\W])/, '-')
|
31
|
+
clean = clean.tr('_', '-')
|
32
|
+
clean = clean.gsub(/(-+)/, '-')
|
33
|
+
clean = "network-#{clean}" unless clean.match?(/^[A-Za-z]/)
|
34
|
+
clean = clean.gsub(Regexp.new("-#{tld}$"), '')
|
35
|
+
clean.gsub(/(-+)/, '-')
|
36
|
+
end
|
37
|
+
|
38
|
+
def qualify(zone)
|
39
|
+
zt_conf = Zt::Conf.instance.conf.zt
|
40
|
+
"#{dnsify(zone)}.#{zt_conf['top_level_domain']}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'zt/conf'
|
4
|
+
require 'zt/importers/_base_importer'
|
5
|
+
|
6
|
+
module Zt
|
7
|
+
module Importers
|
8
|
+
class NetworkImporter < BaseImporter
|
9
|
+
def import
|
10
|
+
output = {}
|
11
|
+
# normalise data
|
12
|
+
normalised_networks = networks.map do |n|
|
13
|
+
{
|
14
|
+
network_id: n['id'],
|
15
|
+
network_name: n['config']['name'],
|
16
|
+
network_description: n['description'],
|
17
|
+
network_total_members: n['totalMemberCount'],
|
18
|
+
network_authorized_members: n['authorizedMemberCount'],
|
19
|
+
network_pending_members:
|
20
|
+
(n['totalMemberCount'] - n['authorizedMemberCount'])
|
21
|
+
}
|
22
|
+
end
|
23
|
+
domains_conf = Zt::Conf.instance.conf.domains
|
24
|
+
normalised_networks.each do |n|
|
25
|
+
zone = if domains_conf.key? n[:network_id]
|
26
|
+
if n[:network_id].empty?
|
27
|
+
qualify(n[:network_id])
|
28
|
+
else
|
29
|
+
qualify(domains_conf[n[:network_id]])
|
30
|
+
end
|
31
|
+
else
|
32
|
+
qualify(n[:network_name])
|
33
|
+
end
|
34
|
+
output[n[:network_id]] = {} unless output.key?(n[:network_id])
|
35
|
+
output[n[:network_id]][:remote] = n
|
36
|
+
output[n[:network_id]][:local] = {
|
37
|
+
dns_zone: zone
|
38
|
+
}
|
39
|
+
end
|
40
|
+
{
|
41
|
+
networks: output
|
42
|
+
}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'zt/importers/_base_importer'
|
4
|
+
|
5
|
+
module Zt
|
6
|
+
module Importers
|
7
|
+
class NodeImporter < BaseImporter
|
8
|
+
def import
|
9
|
+
output = {}
|
10
|
+
hostnames = {}
|
11
|
+
memberships = {}
|
12
|
+
# normalise data
|
13
|
+
nodes.each_key do |network_id|
|
14
|
+
net = nodes[network_id]
|
15
|
+
normalised_nodes = net.map do |node|
|
16
|
+
{
|
17
|
+
node_id: node['nodeId'],
|
18
|
+
node_name: node['name'],
|
19
|
+
node_addr: node['config']['ipAssignments'],
|
20
|
+
node_authed: node['config']['authorized']
|
21
|
+
}
|
22
|
+
end
|
23
|
+
normalised_nodes.each do |n|
|
24
|
+
hostnames[n[:node_id]] = [] unless hostnames.key?(n[:node_id])
|
25
|
+
memberships[n[:node_id]] = [] unless memberships.key?(n[:node_id])
|
26
|
+
|
27
|
+
hostnames[n[:node_id]].append(n[:node_name])
|
28
|
+
memberships[n[:node_id]].append([network_id, n[:node_addr]])
|
29
|
+
n.delete(:node_addr)
|
30
|
+
output[n[:node_id]] = {} unless output.key?(n[:node_id])
|
31
|
+
output[n[:node_id]][:remote] = n
|
32
|
+
output[n[:node_id]][:local] = {}
|
33
|
+
end
|
34
|
+
output.each_key do |k|
|
35
|
+
output[k][:remote][:node_name] = hostnames[k].max_by do |i|
|
36
|
+
hostnames[k].count(i)
|
37
|
+
end
|
38
|
+
memberships[k].each do |m|
|
39
|
+
output[k][:local][:networks] = {} unless
|
40
|
+
output[k][:local].key? :networks
|
41
|
+
|
42
|
+
output[k][:local][:networks][m[0]] = m[1]
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
{
|
48
|
+
nodes: output
|
49
|
+
}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'httparty'
|
4
|
+
require 'zt/conf'
|
5
|
+
|
6
|
+
module Zt
|
7
|
+
module RemoteAPI
|
8
|
+
class ZeroTierAPI
|
9
|
+
include HTTParty
|
10
|
+
|
11
|
+
base_uri 'https://my.zerotier.com/api'
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@ztc = Zt::Conf.instance.conf
|
15
|
+
# noinspection RubyStringKeysInHashInspection
|
16
|
+
@req_opts = {
|
17
|
+
headers: {
|
18
|
+
'Authorization' => "Bearer #{@ztc.zt['token']}"
|
19
|
+
}
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def networks
|
24
|
+
get_parsed('/network')
|
25
|
+
end
|
26
|
+
|
27
|
+
def network_members(network_id)
|
28
|
+
get_parsed("/network/#{network_id}/member")
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def get_parsed(path)
|
34
|
+
self.class.get(path, @req_opts).parsed_response
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/zt/version.rb
ADDED
data/zt.gemspec
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'zt/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'zt'
|
9
|
+
spec.version = Zt::VERSION
|
10
|
+
spec.authors = ['Dave Williams']
|
11
|
+
spec.email = ['dave@dave.io']
|
12
|
+
|
13
|
+
spec.summary = 'ZeroTier administration toolbox'
|
14
|
+
spec.description =
|
15
|
+
'Utilities and glue to make working with ZeroTier networks ' \
|
16
|
+
'a bit more friendly'
|
17
|
+
spec.homepage = 'https://github.com/daveio/zt'
|
18
|
+
spec.license = 'MIT'
|
19
|
+
|
20
|
+
|
21
|
+
if spec.respond_to?(:metadata)
|
22
|
+
# spec.metadata['allowed_push_host'] =
|
23
|
+
# 'http://localhost'
|
24
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
25
|
+
spec.metadata['source_code_uri'] = 'https://github.com/daveio/zt'
|
26
|
+
spec.metadata['changelog_uri'] =
|
27
|
+
'https://raw.githubusercontent.com/daveio/zt/master/CHANGELOG.md'
|
28
|
+
else
|
29
|
+
raise 'RubyGems 2.0 or newer is required to protect ' \
|
30
|
+
'against public gem pushes.'
|
31
|
+
end
|
32
|
+
|
33
|
+
# Specify which files should be added to the gem when it is released.
|
34
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been
|
35
|
+
# added into git.
|
36
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
37
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
38
|
+
f.match(%r{^(test|spec|features)/})
|
39
|
+
end
|
40
|
+
end
|
41
|
+
spec.bindir = 'exe'
|
42
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
43
|
+
spec.require_paths = ['lib']
|
44
|
+
|
45
|
+
spec.add_dependency 'httparty', '~> 0.0', '>= 0.10.0'
|
46
|
+
spec.add_dependency 'thor', '~> 0.0'
|
47
|
+
spec.add_dependency 'xdg', '~> 2.0'
|
48
|
+
|
49
|
+
spec.add_development_dependency 'bundler', '~> 2.0'
|
50
|
+
spec.add_development_dependency 'dotenv', '~> 2.0'
|
51
|
+
spec.add_development_dependency 'pry', '~> 0.0'
|
52
|
+
spec.add_development_dependency 'rake', '~> 12.0'
|
53
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
54
|
+
spec.add_development_dependency 'rubocop', '~> 0.0', '>= 0.49.0'
|
55
|
+
spec.add_development_dependency 'rubocop-performance', '~> 1.0'
|
56
|
+
spec.add_development_dependency 'ruby-debug-ide', '~> 0.0'
|
57
|
+
end
|