leap_cli 1.2.5
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/leap +81 -0
- data/lib/core_ext/boolean.rb +14 -0
- data/lib/core_ext/hash.rb +35 -0
- data/lib/core_ext/json.rb +42 -0
- data/lib/core_ext/nil.rb +5 -0
- data/lib/core_ext/string.rb +14 -0
- data/lib/leap/platform.rb +52 -0
- data/lib/leap_cli/commands/ca.rb +430 -0
- data/lib/leap_cli/commands/clean.rb +16 -0
- data/lib/leap_cli/commands/compile.rb +134 -0
- data/lib/leap_cli/commands/deploy.rb +172 -0
- data/lib/leap_cli/commands/facts.rb +93 -0
- data/lib/leap_cli/commands/inspect.rb +140 -0
- data/lib/leap_cli/commands/list.rb +122 -0
- data/lib/leap_cli/commands/new.rb +126 -0
- data/lib/leap_cli/commands/node.rb +272 -0
- data/lib/leap_cli/commands/pre.rb +99 -0
- data/lib/leap_cli/commands/shell.rb +67 -0
- data/lib/leap_cli/commands/test.rb +55 -0
- data/lib/leap_cli/commands/user.rb +140 -0
- data/lib/leap_cli/commands/util.rb +50 -0
- data/lib/leap_cli/commands/vagrant.rb +201 -0
- data/lib/leap_cli/config/macros.rb +369 -0
- data/lib/leap_cli/config/manager.rb +369 -0
- data/lib/leap_cli/config/node.rb +37 -0
- data/lib/leap_cli/config/object.rb +336 -0
- data/lib/leap_cli/config/object_list.rb +174 -0
- data/lib/leap_cli/config/secrets.rb +43 -0
- data/lib/leap_cli/config/tag.rb +18 -0
- data/lib/leap_cli/constants.rb +7 -0
- data/lib/leap_cli/leapfile.rb +97 -0
- data/lib/leap_cli/load_paths.rb +15 -0
- data/lib/leap_cli/log.rb +166 -0
- data/lib/leap_cli/logger.rb +216 -0
- data/lib/leap_cli/markdown_document_listener.rb +134 -0
- data/lib/leap_cli/path.rb +84 -0
- data/lib/leap_cli/remote/leap_plugin.rb +204 -0
- data/lib/leap_cli/remote/puppet_plugin.rb +66 -0
- data/lib/leap_cli/remote/rsync_plugin.rb +35 -0
- data/lib/leap_cli/remote/tasks.rb +36 -0
- data/lib/leap_cli/requirements.rb +19 -0
- data/lib/leap_cli/ssh_key.rb +130 -0
- data/lib/leap_cli/util/remote_command.rb +110 -0
- data/lib/leap_cli/util/secret.rb +54 -0
- data/lib/leap_cli/util/x509.rb +32 -0
- data/lib/leap_cli/util.rb +431 -0
- data/lib/leap_cli/version.rb +9 -0
- data/lib/leap_cli.rb +46 -0
- data/lib/lib_ext/capistrano_connections.rb +16 -0
- data/lib/lib_ext/gli.rb +52 -0
- data/lib/lib_ext/markdown_document_listener.rb +122 -0
- data/vendor/certificate_authority/lib/certificate_authority/certificate.rb +200 -0
- data/vendor/certificate_authority/lib/certificate_authority/certificate_revocation_list.rb +77 -0
- data/vendor/certificate_authority/lib/certificate_authority/distinguished_name.rb +97 -0
- data/vendor/certificate_authority/lib/certificate_authority/extensions.rb +266 -0
- data/vendor/certificate_authority/lib/certificate_authority/key_material.rb +148 -0
- data/vendor/certificate_authority/lib/certificate_authority/ocsp_handler.rb +144 -0
- data/vendor/certificate_authority/lib/certificate_authority/pkcs11_key_material.rb +65 -0
- data/vendor/certificate_authority/lib/certificate_authority/revocable.rb +14 -0
- data/vendor/certificate_authority/lib/certificate_authority/serial_number.rb +10 -0
- data/vendor/certificate_authority/lib/certificate_authority/signing_entity.rb +16 -0
- data/vendor/certificate_authority/lib/certificate_authority/signing_request.rb +56 -0
- data/vendor/certificate_authority/lib/certificate_authority.rb +21 -0
- data/vendor/rsync_command/lib/rsync_command/ssh_options.rb +159 -0
- data/vendor/rsync_command/lib/rsync_command/thread_pool.rb +36 -0
- data/vendor/rsync_command/lib/rsync_command/version.rb +3 -0
- data/vendor/rsync_command/lib/rsync_command.rb +96 -0
- data/vendor/rsync_command/test/rsync_test.rb +74 -0
- data/vendor/rsync_command/test/ssh_options_test.rb +61 -0
- data/vendor/vagrant_ssh_keys/vagrant.key +27 -0
- data/vendor/vagrant_ssh_keys/vagrant.pub +1 -0
- metadata +345 -0
@@ -0,0 +1,134 @@
|
|
1
|
+
|
2
|
+
module LeapCli
|
3
|
+
module Commands
|
4
|
+
|
5
|
+
desc "Compile generated files."
|
6
|
+
command :compile do |c|
|
7
|
+
c.desc 'Compiles node configuration files into hiera files used for deployment.'
|
8
|
+
c.command :all do |all|
|
9
|
+
all.action do |global_options,options,args|
|
10
|
+
compile_hiera_files
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
c.desc "Compile a DNS zone file for your provider."
|
15
|
+
c.command :zone do |zone|
|
16
|
+
zone.action do |global_options, options, args|
|
17
|
+
compile_zone_file
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
c.default_command :all
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
def compile_hiera_files(nodes=nil)
|
27
|
+
# these must come first
|
28
|
+
update_compiled_ssh_configs
|
29
|
+
|
30
|
+
# export generated files
|
31
|
+
manager.export_nodes(nodes)
|
32
|
+
manager.export_secrets(nodes.nil?) # only do a "clean" export if we are examining all the nodes
|
33
|
+
end
|
34
|
+
|
35
|
+
def update_compiled_ssh_configs
|
36
|
+
update_authorized_keys
|
37
|
+
update_known_hosts
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
## ZONE FILE
|
42
|
+
##
|
43
|
+
|
44
|
+
def relative_hostname(fqdn)
|
45
|
+
@domain_regexp ||= /\.?#{Regexp.escape(provider.domain)}$/
|
46
|
+
fqdn.sub(@domain_regexp, '')
|
47
|
+
end
|
48
|
+
|
49
|
+
#
|
50
|
+
# serial is any number less than 2^32 (4294967296)
|
51
|
+
#
|
52
|
+
def compile_zone_file
|
53
|
+
hosts_seen = {}
|
54
|
+
f = $stdout
|
55
|
+
f.puts ZONE_HEADER % {:domain => provider.domain, :ns => provider.domain, :contact => provider.contacts.default.first.sub('@','.')}
|
56
|
+
max_width = manager.nodes.values.inject(0) {|max, node| [max, relative_hostname(node.domain.full).length].max }
|
57
|
+
put_line = lambda do |host, line|
|
58
|
+
host = '@' if host == ''
|
59
|
+
f.puts("%-#{max_width}s %s" % [host, line])
|
60
|
+
end
|
61
|
+
|
62
|
+
f.puts ORIGIN_HEADER
|
63
|
+
# 'A' records for primary domain
|
64
|
+
manager.nodes[:environment => '!local'].each_node do |node|
|
65
|
+
if node.dns['aliases'] && node.dns.aliases.include?(provider.domain)
|
66
|
+
put_line.call "", "IN A #{node.ip_address}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# NS records
|
71
|
+
if provider['dns'] && provider.dns['nameservers']
|
72
|
+
provider.dns.nameservers.each do |ns|
|
73
|
+
put_line.call "", "IN NS #{ns}."
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# all other records
|
78
|
+
manager.environments.each do |env|
|
79
|
+
next if env == 'local'
|
80
|
+
nodes = manager.nodes[:environment => env]
|
81
|
+
next unless nodes.any?
|
82
|
+
f.puts ENV_HEADER % (env.nil? ? 'default' : env)
|
83
|
+
nodes.each_node do |node|
|
84
|
+
if node.dns.public
|
85
|
+
hostname = relative_hostname(node.domain.full)
|
86
|
+
put_line.call relative_hostname(node.domain.full), "IN A #{node.ip_address}"
|
87
|
+
end
|
88
|
+
if node.dns['aliases']
|
89
|
+
node.dns.aliases.each do |host_alias|
|
90
|
+
if host_alias != node.domain.full && host_alias != provider.domain
|
91
|
+
put_line.call relative_hostname(host_alias), "IN CNAME #{relative_hostname(node.domain.full)}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
if node.services.include? 'mx'
|
96
|
+
put_line.call relative_hostname(node.domain.full_suffix), "IN MX 10 #{relative_hostname(node.domain.full)}"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
ENV_HEADER = %[
|
103
|
+
;;
|
104
|
+
;; ENVIRONMENT %s
|
105
|
+
;;
|
106
|
+
|
107
|
+
]
|
108
|
+
|
109
|
+
ZONE_HEADER = %[
|
110
|
+
;;
|
111
|
+
;; BIND data file for %{domain}
|
112
|
+
;;
|
113
|
+
|
114
|
+
$TTL 600
|
115
|
+
$ORIGIN %{domain}.
|
116
|
+
|
117
|
+
@ IN SOA %{ns}. %{contact}. (
|
118
|
+
0000 ; serial
|
119
|
+
7200 ; refresh ( 24 hours)
|
120
|
+
3600 ; retry ( 2 hours)
|
121
|
+
1209600 ; expire (1000 hours)
|
122
|
+
600 ) ; minimum ( 2 days)
|
123
|
+
;
|
124
|
+
]
|
125
|
+
|
126
|
+
ORIGIN_HEADER = %[
|
127
|
+
;;
|
128
|
+
;; ZONE ORIGIN
|
129
|
+
;;
|
130
|
+
|
131
|
+
]
|
132
|
+
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
|
2
|
+
module LeapCli
|
3
|
+
module Commands
|
4
|
+
|
5
|
+
desc 'Apply recipes to a node or set of nodes.'
|
6
|
+
long_desc 'The FILTER can be the name of a node, service, or tag.'
|
7
|
+
arg_name 'FILTER'
|
8
|
+
command :deploy do |c|
|
9
|
+
|
10
|
+
# --fast
|
11
|
+
c.switch :fast, :desc => 'Makes the deploy command faster by skipping some slow steps. A "fast" deploy can be used safely if you recently completed a normal deploy.',
|
12
|
+
:negatable => false
|
13
|
+
|
14
|
+
# --force
|
15
|
+
c.switch :force, :desc => 'Deploy even if there is a lockfile.', :negatable => false
|
16
|
+
|
17
|
+
# --tags
|
18
|
+
c.flag :tags, :desc => 'Specify tags to pass through to puppet (overriding the default).',
|
19
|
+
:default_value => DEFAULT_TAGS.join(','), :arg_name => 'TAG[,TAG]'
|
20
|
+
|
21
|
+
c.flag :port, :desc => 'Override the default SSH port.',
|
22
|
+
:arg_name => 'PORT'
|
23
|
+
|
24
|
+
c.flag :ip, :desc => 'Override the default SSH IP address.',
|
25
|
+
:arg_name => 'IPADDRESS'
|
26
|
+
|
27
|
+
c.action do |global,options,args|
|
28
|
+
init_submodules
|
29
|
+
|
30
|
+
nodes = filter_deploy_nodes(args)
|
31
|
+
if nodes.size > 1
|
32
|
+
say "Deploying to these nodes: #{nodes.keys.join(', ')}"
|
33
|
+
if !global[:yes] && !agree("Continue? ")
|
34
|
+
quit! "OK. Bye."
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
compile_hiera_files(nodes)
|
39
|
+
|
40
|
+
ssh_connect(nodes, connect_options(options)) do |ssh|
|
41
|
+
ssh.leap.log :checking, 'node' do
|
42
|
+
ssh.leap.check_for_no_deploy
|
43
|
+
ssh.leap.assert_initialized
|
44
|
+
end
|
45
|
+
ssh.leap.log :synching, "configuration files" do
|
46
|
+
sync_hiera_config(ssh)
|
47
|
+
sync_support_files(ssh)
|
48
|
+
end
|
49
|
+
ssh.leap.log :synching, "puppet manifests" do
|
50
|
+
sync_puppet_files(ssh)
|
51
|
+
end
|
52
|
+
ssh.leap.log :applying, "puppet" do
|
53
|
+
ssh.puppet.apply(:verbosity => LeapCli.log_level, :tags => tags(options), :force => options[:force])
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def sync_hiera_config(ssh)
|
62
|
+
dest_dir = provider.hiera_sync_destination
|
63
|
+
ssh.rsync.update do |server|
|
64
|
+
node = manager.node(server.host)
|
65
|
+
hiera_file = Path.relative_path([:hiera, node.name])
|
66
|
+
ssh.leap.log hiera_file + ' -> ' + node.name + ':' + dest_dir + '/hiera.yaml'
|
67
|
+
{
|
68
|
+
:source => hiera_file,
|
69
|
+
:dest => dest_dir + '/hiera.yaml',
|
70
|
+
:flags => "-rltp --chmod=u+rX,go-rwx"
|
71
|
+
}
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def sync_support_files(ssh)
|
76
|
+
dest_dir = provider.hiera_sync_destination
|
77
|
+
ssh.rsync.update do |server|
|
78
|
+
node = manager.node(server.host)
|
79
|
+
files_to_sync = node.file_paths.collect {|path| Path.relative_path(path, Path.provider) }
|
80
|
+
if files_to_sync.any?
|
81
|
+
ssh.leap.log(files_to_sync.join(', ') + ' -> ' + node.name + ':' + dest_dir)
|
82
|
+
{
|
83
|
+
:chdir => Path.provider,
|
84
|
+
:source => ".",
|
85
|
+
:dest => dest_dir,
|
86
|
+
:excludes => "*",
|
87
|
+
:includes => calculate_includes_from_files(files_to_sync),
|
88
|
+
:flags => "-rltp --chmod=u+rX,go-rwx --relative --delete --delete-excluded --filter='protect hiera.yaml' --copy-links"
|
89
|
+
}
|
90
|
+
else
|
91
|
+
nil
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def sync_puppet_files(ssh)
|
97
|
+
ssh.rsync.update do |server|
|
98
|
+
ssh.leap.log(Path.platform + '/[bin,tests,puppet] -> ' + server.host + ':' + LeapCli::PUPPET_DESTINATION)
|
99
|
+
{
|
100
|
+
:dest => LeapCli::PUPPET_DESTINATION,
|
101
|
+
:source => '.',
|
102
|
+
:chdir => Path.platform,
|
103
|
+
:excludes => '*',
|
104
|
+
:includes => ['/bin', '/bin/**', '/puppet', '/puppet/**', '/tests', '/tests/**'],
|
105
|
+
:flags => "-rlt --relative --delete --copy-links"
|
106
|
+
}
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def init_submodules
|
111
|
+
Dir.chdir Path.platform do
|
112
|
+
assert_run! "git submodule sync"
|
113
|
+
statuses = assert_run! "git submodule status"
|
114
|
+
statuses.strip.split("\n").each do |status_line|
|
115
|
+
if status_line =~ /^[\+-]/
|
116
|
+
submodule = status_line.split(' ')[1]
|
117
|
+
log "Updating submodule #{submodule}"
|
118
|
+
assert_run! "git submodule update --init #{submodule}"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def calculate_includes_from_files(files)
|
125
|
+
return nil unless files and files.any?
|
126
|
+
|
127
|
+
# prepend '/' (kind of like ^ for rsync)
|
128
|
+
includes = files.collect {|file| '/' + file}
|
129
|
+
|
130
|
+
# include all sub files of specified directories
|
131
|
+
includes.size.times do |i|
|
132
|
+
if includes[i] =~ /\/$/
|
133
|
+
includes << includes[i] + '**'
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# include all parent directories (required because of --exclude '*')
|
138
|
+
includes.size.times do |i|
|
139
|
+
path = File.dirname(includes[i])
|
140
|
+
while(path != '/')
|
141
|
+
includes << path unless includes.include?(path)
|
142
|
+
path = File.dirname(path)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
return includes
|
147
|
+
end
|
148
|
+
|
149
|
+
def tags(options)
|
150
|
+
if options[:tags]
|
151
|
+
tags = options[:tags].split(',')
|
152
|
+
else
|
153
|
+
tags = LeapCli::DEFAULT_TAGS.dup
|
154
|
+
end
|
155
|
+
tags << 'leap_slow' unless options[:fast]
|
156
|
+
tags.join(',')
|
157
|
+
end
|
158
|
+
|
159
|
+
#
|
160
|
+
# for safety, we allow production deploys to be turned off in the Leapfile.
|
161
|
+
#
|
162
|
+
def filter_deploy_nodes(filter)
|
163
|
+
nodes = manager.filter!(filter)
|
164
|
+
if !leapfile.allow_production_deploy
|
165
|
+
nodes = nodes[:environment => "!production"]
|
166
|
+
assert! nodes.any?, "Skipping deploy because @allow_production_deploy is disabled."
|
167
|
+
end
|
168
|
+
nodes
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
#
|
2
|
+
# Gather facter facts
|
3
|
+
#
|
4
|
+
|
5
|
+
module LeapCli; module Commands
|
6
|
+
|
7
|
+
desc 'Gather information on nodes.'
|
8
|
+
command :facts do |facts|
|
9
|
+
facts.desc 'Query servers to update facts.json.'
|
10
|
+
facts.long_desc "Queries every node included in FILTER and saves the important information to facts.json"
|
11
|
+
facts.arg_name 'FILTER'
|
12
|
+
facts.command :update do |update|
|
13
|
+
update.action do |global_options,options,args|
|
14
|
+
update_facts(global_options, options, args)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
def facter_cmd
|
22
|
+
'facter --json ' + Leap::Platform.facts.join(' ')
|
23
|
+
end
|
24
|
+
|
25
|
+
def remove_node_facts(name)
|
26
|
+
if file_exists?(:facts)
|
27
|
+
update_facts_file({name => nil})
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def update_node_facts(name, facts)
|
32
|
+
update_facts_file({name => facts})
|
33
|
+
end
|
34
|
+
|
35
|
+
def rename_node_facts(old_name, new_name)
|
36
|
+
if file_exists?(:facts)
|
37
|
+
facts = JSON.parse(read_file(:facts) || {})
|
38
|
+
facts[new_name] = facts[old_name]
|
39
|
+
facts[old_name] = nil
|
40
|
+
update_facts_file(facts, true)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
#
|
45
|
+
# if overwrite = true, then ignore existing facts.json.
|
46
|
+
#
|
47
|
+
def update_facts_file(new_facts, overwrite=false)
|
48
|
+
replace_file!(:facts) do |content|
|
49
|
+
if overwrite || content.nil? || content.empty?
|
50
|
+
old_facts = {}
|
51
|
+
else
|
52
|
+
old_facts = JSON.parse(content)
|
53
|
+
end
|
54
|
+
facts = old_facts.merge(new_facts)
|
55
|
+
facts.each do |name, value|
|
56
|
+
if value.is_a? String
|
57
|
+
if value == ""
|
58
|
+
value = nil
|
59
|
+
else
|
60
|
+
value = JSON.parse(value)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
if value.is_a? Hash
|
64
|
+
value.delete_if {|key,v| v.nil?}
|
65
|
+
end
|
66
|
+
facts[name] = value
|
67
|
+
end
|
68
|
+
facts.delete_if do |name, value|
|
69
|
+
value.nil? || value.empty?
|
70
|
+
end
|
71
|
+
if facts.empty?
|
72
|
+
nil
|
73
|
+
else
|
74
|
+
JSON.sorted_generate(facts) + "\n"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def update_facts(global_options, options, args)
|
82
|
+
nodes = manager.filter(args)
|
83
|
+
new_facts = {}
|
84
|
+
ssh_connect(nodes) do |ssh|
|
85
|
+
ssh.leap.run_with_progress(facter_cmd) do |response|
|
86
|
+
new_facts[response[:host]] = response[:data].strip
|
87
|
+
end
|
88
|
+
end
|
89
|
+
overwrite_existing = args.empty?
|
90
|
+
update_facts_file(new_facts, overwrite_existing)
|
91
|
+
end
|
92
|
+
|
93
|
+
end; end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
module LeapCli; module Commands
|
2
|
+
|
3
|
+
desc 'Prints details about a file. Alternately, the argument FILE can be the name of a node, service or tag.'
|
4
|
+
arg_name 'FILE'
|
5
|
+
command :inspect do |c|
|
6
|
+
c.switch 'base', :desc => 'Inspect the FILE from the provider_base (i.e. without local inheritance).', :negatable => false
|
7
|
+
c.action do |global_options,options,args|
|
8
|
+
object = args.first
|
9
|
+
assert! object, 'A file path or node/service/tag name is required'
|
10
|
+
method = inspection_method(object)
|
11
|
+
if method && defined?(method)
|
12
|
+
self.send(method, object, options)
|
13
|
+
else
|
14
|
+
log "Sorry, I don't know how to inspect that."
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
FTYPE_MAP = {
|
22
|
+
"PEM certificate" => :inspect_x509_cert,
|
23
|
+
"PEM RSA private key" => :inspect_x509_key,
|
24
|
+
"OpenSSH RSA public key" => :inspect_ssh_pub_key,
|
25
|
+
"PEM certificate request" => :inspect_x509_csr
|
26
|
+
}
|
27
|
+
|
28
|
+
def inspection_method(object)
|
29
|
+
if File.exists?(object)
|
30
|
+
ftype = `file #{object}`.split(':').last.strip
|
31
|
+
log 2, "file is of type '#{ftype}'"
|
32
|
+
if FTYPE_MAP[ftype]
|
33
|
+
FTYPE_MAP[ftype]
|
34
|
+
elsif File.extname(object) == ".json"
|
35
|
+
full_path = File.expand_path(object, Dir.pwd)
|
36
|
+
if path_match?(:node_config, full_path)
|
37
|
+
:inspect_node
|
38
|
+
elsif path_match?(:service_config, full_path)
|
39
|
+
:inspect_service
|
40
|
+
elsif path_match?(:tag_config, full_path)
|
41
|
+
:inspect_tag
|
42
|
+
elsif path_match?(:provider_config, full_path)
|
43
|
+
:inspect_provider
|
44
|
+
elsif path_match?(:common_config, full_path)
|
45
|
+
:inspect_common
|
46
|
+
else
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
end
|
50
|
+
elsif manager.nodes[object]
|
51
|
+
:inspect_node
|
52
|
+
elsif manager.services[object]
|
53
|
+
:inspect_service
|
54
|
+
elsif manager.tags[object]
|
55
|
+
:inspect_tag
|
56
|
+
elsif object == "common"
|
57
|
+
:inspect_common
|
58
|
+
elsif object == "provider"
|
59
|
+
:inspect_provider
|
60
|
+
else
|
61
|
+
nil
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
#
|
66
|
+
# inspectors
|
67
|
+
#
|
68
|
+
|
69
|
+
def inspect_x509_key(file_path, options)
|
70
|
+
assert_bin! 'openssl'
|
71
|
+
puts assert_run! 'openssl rsa -in %s -text -check' % file_path
|
72
|
+
end
|
73
|
+
|
74
|
+
def inspect_x509_cert(file_path, options)
|
75
|
+
assert_bin! 'openssl'
|
76
|
+
puts assert_run! 'openssl x509 -in %s -text -noout' % file_path
|
77
|
+
log 0, :"SHA256 fingerprint", X509.fingerprint("SHA256", file_path)
|
78
|
+
end
|
79
|
+
|
80
|
+
def inspect_x509_csr(file_path, options)
|
81
|
+
assert_bin! 'openssl'
|
82
|
+
puts assert_run! 'openssl req -text -noout -verify -in %s' % file_path
|
83
|
+
end
|
84
|
+
|
85
|
+
#def inspect_ssh_pub_key(file_path)
|
86
|
+
#end
|
87
|
+
|
88
|
+
def inspect_node(arg, options)
|
89
|
+
inspect_json manager.nodes[name(arg)]
|
90
|
+
end
|
91
|
+
|
92
|
+
def inspect_service(arg, options)
|
93
|
+
if options[:base]
|
94
|
+
inspect_json manager.base_services[name(arg)]
|
95
|
+
else
|
96
|
+
inspect_json manager.services[name(arg)]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def inspect_tag(arg, options)
|
101
|
+
if options[:base]
|
102
|
+
inspect_json manager.base_tags[name(arg)]
|
103
|
+
else
|
104
|
+
inspect_json manager.tags[name(arg)]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def inspect_provider(arg, options)
|
109
|
+
if options[:base]
|
110
|
+
inspect_json manager.base_provider
|
111
|
+
else
|
112
|
+
inspect_json manager.provider
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def inspect_common(arg, options)
|
117
|
+
if options[:base]
|
118
|
+
inspect_json manager.base_common
|
119
|
+
else
|
120
|
+
inspect_json manager.common
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
#
|
125
|
+
# helpers
|
126
|
+
#
|
127
|
+
|
128
|
+
def name(arg)
|
129
|
+
File.basename(arg).sub(/\.json$/, '')
|
130
|
+
end
|
131
|
+
|
132
|
+
def inspect_json(config)
|
133
|
+
puts JSON.sorted_generate(config)
|
134
|
+
end
|
135
|
+
|
136
|
+
def path_match?(path_symbol, path)
|
137
|
+
Dir.glob(Path.named_path([path_symbol, '*'])).include?(path)
|
138
|
+
end
|
139
|
+
|
140
|
+
end; end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'command_line_reporter'
|
2
|
+
|
3
|
+
module LeapCli; module Commands
|
4
|
+
|
5
|
+
desc 'List nodes and their classifications'
|
6
|
+
long_desc 'Prints out a listing of nodes, services, or tags. ' +
|
7
|
+
'If present, the FILTER can be a list of names of nodes, services, or tags. ' +
|
8
|
+
'If the name is prefixed with +, this acts like an AND condition. ' +
|
9
|
+
"For example:\n\n" +
|
10
|
+
"`leap list node1 node2` matches all nodes named \"node1\" OR \"node2\"\n\n" +
|
11
|
+
"`leap list openvpn +local` matches all nodes with service \"openvpn\" AND tag \"local\""
|
12
|
+
|
13
|
+
arg_name 'FILTER', :optional => true
|
14
|
+
command :list do |c|
|
15
|
+
c.flag 'print', :desc => 'What attributes to print (optional)'
|
16
|
+
c.switch 'disabled', :desc => 'Include disabled nodes in the list.', :negatable => false
|
17
|
+
c.action do |global_options,options,args|
|
18
|
+
puts
|
19
|
+
if options['disabled']
|
20
|
+
manager.load(:include_disabled => true) # reload, with disabled nodes
|
21
|
+
end
|
22
|
+
if options['print']
|
23
|
+
print_node_properties(manager.filter(args), options['print'])
|
24
|
+
else
|
25
|
+
if args.any?
|
26
|
+
NodeTable.new(manager.filter(args)).run
|
27
|
+
else
|
28
|
+
TagTable.new('SERVICES', manager.services).run
|
29
|
+
TagTable.new('TAGS', manager.tags).run
|
30
|
+
NodeTable.new(manager.nodes).run
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def self.print_node_properties(nodes, properties)
|
39
|
+
node_list = manager.nodes
|
40
|
+
properties = properties.split(',')
|
41
|
+
max_width = nodes.keys.inject(0) {|max,i| [i.size,max].max}
|
42
|
+
nodes.each_node do |node|
|
43
|
+
node.evaluate
|
44
|
+
value = properties.collect{|prop|
|
45
|
+
if node[prop].nil?
|
46
|
+
"null"
|
47
|
+
elsif node[prop] == ""
|
48
|
+
"empty"
|
49
|
+
else
|
50
|
+
node[prop]
|
51
|
+
end
|
52
|
+
}.join(', ')
|
53
|
+
printf("%#{max_width}s %s\n", node.name, value)
|
54
|
+
end
|
55
|
+
puts
|
56
|
+
end
|
57
|
+
|
58
|
+
class TagTable
|
59
|
+
include CommandLineReporter
|
60
|
+
def initialize(heading, tag_list)
|
61
|
+
@heading = heading
|
62
|
+
@tag_list = tag_list
|
63
|
+
end
|
64
|
+
def run
|
65
|
+
tags = @tag_list.keys.sort
|
66
|
+
max_width = [20, (tags+[@heading]).inject(0) {|max,i| [i.size,max].max}].max
|
67
|
+
table :border => false do
|
68
|
+
row :header => true, :color => 'cyan' do
|
69
|
+
column @heading, :align => 'right', :width => max_width
|
70
|
+
column "NODES", :width => HighLine::SystemExtensions.terminal_size.first - max_width - 2, :padding => 2
|
71
|
+
end
|
72
|
+
tags.each do |tag|
|
73
|
+
row do
|
74
|
+
column tag
|
75
|
+
column @tag_list[tag].node_list.keys.sort.join(', ')
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
vertical_spacing
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
#
|
84
|
+
# might be handy: HighLine::SystemExtensions.terminal_size.first
|
85
|
+
#
|
86
|
+
class NodeTable
|
87
|
+
include CommandLineReporter
|
88
|
+
def initialize(node_list)
|
89
|
+
@node_list = node_list
|
90
|
+
end
|
91
|
+
def run
|
92
|
+
rows = @node_list.keys.sort.collect do |node_name|
|
93
|
+
[node_name, @node_list[node_name].services.sort.join(', '), @node_list[node_name].tags.sort.join(', ')]
|
94
|
+
end
|
95
|
+
unless rows.any?
|
96
|
+
puts Paint["no results", :red]
|
97
|
+
puts
|
98
|
+
return
|
99
|
+
end
|
100
|
+
padding = 2
|
101
|
+
max_node_width = [20, (rows.map{|i|i[0]} + ["NODES"] ).inject(0) {|max,i| [i.size,max].max}].max
|
102
|
+
max_service_width = (rows.map{|i|i[1]} + ["SERVICES"]).inject(0) {|max,i| [i.size+padding+padding,max].max}
|
103
|
+
max_tag_width = (rows.map{|i|i[2]} + ["TAGS"] ).inject(0) {|max,i| [i.size,max].max}
|
104
|
+
table :border => false do
|
105
|
+
row :header => true, :color => 'cyan' do
|
106
|
+
column "NODES", :align => 'right', :width => max_node_width
|
107
|
+
column "SERVICES", :width => max_service_width, :padding => 2
|
108
|
+
column "TAGS", :width => max_tag_width
|
109
|
+
end
|
110
|
+
rows.each do |r|
|
111
|
+
row do
|
112
|
+
column r[0]
|
113
|
+
column r[1]
|
114
|
+
column r[2]
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
vertical_spacing
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
end; end
|