leap_cli 1.2.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. data/bin/leap +81 -0
  2. data/lib/core_ext/boolean.rb +14 -0
  3. data/lib/core_ext/hash.rb +35 -0
  4. data/lib/core_ext/json.rb +42 -0
  5. data/lib/core_ext/nil.rb +5 -0
  6. data/lib/core_ext/string.rb +14 -0
  7. data/lib/leap/platform.rb +52 -0
  8. data/lib/leap_cli/commands/ca.rb +430 -0
  9. data/lib/leap_cli/commands/clean.rb +16 -0
  10. data/lib/leap_cli/commands/compile.rb +134 -0
  11. data/lib/leap_cli/commands/deploy.rb +172 -0
  12. data/lib/leap_cli/commands/facts.rb +93 -0
  13. data/lib/leap_cli/commands/inspect.rb +140 -0
  14. data/lib/leap_cli/commands/list.rb +122 -0
  15. data/lib/leap_cli/commands/new.rb +126 -0
  16. data/lib/leap_cli/commands/node.rb +272 -0
  17. data/lib/leap_cli/commands/pre.rb +99 -0
  18. data/lib/leap_cli/commands/shell.rb +67 -0
  19. data/lib/leap_cli/commands/test.rb +55 -0
  20. data/lib/leap_cli/commands/user.rb +140 -0
  21. data/lib/leap_cli/commands/util.rb +50 -0
  22. data/lib/leap_cli/commands/vagrant.rb +201 -0
  23. data/lib/leap_cli/config/macros.rb +369 -0
  24. data/lib/leap_cli/config/manager.rb +369 -0
  25. data/lib/leap_cli/config/node.rb +37 -0
  26. data/lib/leap_cli/config/object.rb +336 -0
  27. data/lib/leap_cli/config/object_list.rb +174 -0
  28. data/lib/leap_cli/config/secrets.rb +43 -0
  29. data/lib/leap_cli/config/tag.rb +18 -0
  30. data/lib/leap_cli/constants.rb +7 -0
  31. data/lib/leap_cli/leapfile.rb +97 -0
  32. data/lib/leap_cli/load_paths.rb +15 -0
  33. data/lib/leap_cli/log.rb +166 -0
  34. data/lib/leap_cli/logger.rb +216 -0
  35. data/lib/leap_cli/markdown_document_listener.rb +134 -0
  36. data/lib/leap_cli/path.rb +84 -0
  37. data/lib/leap_cli/remote/leap_plugin.rb +204 -0
  38. data/lib/leap_cli/remote/puppet_plugin.rb +66 -0
  39. data/lib/leap_cli/remote/rsync_plugin.rb +35 -0
  40. data/lib/leap_cli/remote/tasks.rb +36 -0
  41. data/lib/leap_cli/requirements.rb +19 -0
  42. data/lib/leap_cli/ssh_key.rb +130 -0
  43. data/lib/leap_cli/util/remote_command.rb +110 -0
  44. data/lib/leap_cli/util/secret.rb +54 -0
  45. data/lib/leap_cli/util/x509.rb +32 -0
  46. data/lib/leap_cli/util.rb +431 -0
  47. data/lib/leap_cli/version.rb +9 -0
  48. data/lib/leap_cli.rb +46 -0
  49. data/lib/lib_ext/capistrano_connections.rb +16 -0
  50. data/lib/lib_ext/gli.rb +52 -0
  51. data/lib/lib_ext/markdown_document_listener.rb +122 -0
  52. data/vendor/certificate_authority/lib/certificate_authority/certificate.rb +200 -0
  53. data/vendor/certificate_authority/lib/certificate_authority/certificate_revocation_list.rb +77 -0
  54. data/vendor/certificate_authority/lib/certificate_authority/distinguished_name.rb +97 -0
  55. data/vendor/certificate_authority/lib/certificate_authority/extensions.rb +266 -0
  56. data/vendor/certificate_authority/lib/certificate_authority/key_material.rb +148 -0
  57. data/vendor/certificate_authority/lib/certificate_authority/ocsp_handler.rb +144 -0
  58. data/vendor/certificate_authority/lib/certificate_authority/pkcs11_key_material.rb +65 -0
  59. data/vendor/certificate_authority/lib/certificate_authority/revocable.rb +14 -0
  60. data/vendor/certificate_authority/lib/certificate_authority/serial_number.rb +10 -0
  61. data/vendor/certificate_authority/lib/certificate_authority/signing_entity.rb +16 -0
  62. data/vendor/certificate_authority/lib/certificate_authority/signing_request.rb +56 -0
  63. data/vendor/certificate_authority/lib/certificate_authority.rb +21 -0
  64. data/vendor/rsync_command/lib/rsync_command/ssh_options.rb +159 -0
  65. data/vendor/rsync_command/lib/rsync_command/thread_pool.rb +36 -0
  66. data/vendor/rsync_command/lib/rsync_command/version.rb +3 -0
  67. data/vendor/rsync_command/lib/rsync_command.rb +96 -0
  68. data/vendor/rsync_command/test/rsync_test.rb +74 -0
  69. data/vendor/rsync_command/test/ssh_options_test.rb +61 -0
  70. data/vendor/vagrant_ssh_keys/vagrant.key +27 -0
  71. data/vendor/vagrant_ssh_keys/vagrant.pub +1 -0
  72. 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