leap_cli 1.2.5
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.
- 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
|