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