hieracles 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/Gemfile +2 -0
  4. data/README.md +54 -8
  5. data/bin/hc +12 -6
  6. data/bin/ppdb +42 -0
  7. data/hc.1 +50 -8
  8. data/lib/hieracles.rb +2 -2
  9. data/lib/hieracles/config.rb +31 -19
  10. data/lib/hieracles/format.rb +4 -0
  11. data/lib/hieracles/formats/console.rb +49 -16
  12. data/lib/hieracles/formats/csv.rb +12 -8
  13. data/lib/hieracles/formats/json.rb +19 -6
  14. data/lib/hieracles/formats/plain.rb +24 -3
  15. data/lib/hieracles/formats/rawyaml.rb +4 -0
  16. data/lib/hieracles/formats/yaml.rb +4 -0
  17. data/lib/hieracles/node.rb +55 -10
  18. data/lib/hieracles/notification.rb +31 -0
  19. data/lib/hieracles/options/hc.rb +109 -0
  20. data/lib/hieracles/options/ppdb.rb +32 -0
  21. data/lib/hieracles/optparse.rb +4 -43
  22. data/lib/hieracles/puppetdb.rb +12 -0
  23. data/lib/hieracles/puppetdb/apierror.rb +10 -0
  24. data/lib/hieracles/puppetdb/client.rb +63 -0
  25. data/lib/hieracles/puppetdb/filter.rb +15 -0
  26. data/lib/hieracles/puppetdb/fixsslconnectionadapter.rb +25 -0
  27. data/lib/hieracles/puppetdb/query.rb +79 -0
  28. data/lib/hieracles/puppetdb/request.rb +44 -0
  29. data/lib/hieracles/puppetdb/response.rb +14 -0
  30. data/ppdb.1 +158 -0
  31. data/spec/files/config.yml +2 -0
  32. data/spec/files/config_withdb.yml +9 -0
  33. data/spec/files/facts.json +110 -0
  34. data/spec/files/facts.yaml +103 -0
  35. data/spec/files/hiera_columns.yaml +16 -0
  36. data/spec/files/hiera_deep.yaml +17 -0
  37. data/spec/files/hiera_deeper.yaml +17 -0
  38. data/spec/files/ssl/bad-ca.crt +1 -0
  39. data/spec/files/ssl/bad-cert.crt +1 -0
  40. data/spec/files/ssl/bad-key.pem +1 -0
  41. data/spec/files/ssl/ca.crt +16 -0
  42. data/spec/files/ssl/cert.crt +16 -0
  43. data/spec/files/ssl/key-pass.pem +18 -0
  44. data/spec/files/ssl/key.pem +15 -0
  45. data/spec/lib/config_spec.rb +51 -11
  46. data/spec/lib/format_spec.rb +5 -0
  47. data/spec/lib/formats/console_spec.rb +24 -3
  48. data/spec/lib/formats/csv_spec.rb +15 -0
  49. data/spec/lib/formats/json_spec.rb +22 -2
  50. data/spec/lib/formats/plain_spec.rb +23 -3
  51. data/spec/lib/formats/rawyaml_spec.rb +13 -0
  52. data/spec/lib/formats/yaml_spec.rb +138 -48
  53. data/spec/lib/hiera_spec.rb +0 -1
  54. data/spec/lib/hieracles_spec.rb +8 -0
  55. data/spec/lib/interpolate_spec.rb +49 -0
  56. data/spec/lib/node_spec.rb +50 -9
  57. data/spec/lib/notification_spec.rb +29 -0
  58. data/spec/lib/options/hc_spec.rb +82 -0
  59. data/spec/lib/optparse_spec.rb +7 -7
  60. data/spec/lib/puppetdb/apierror_spec.rb +11 -0
  61. data/spec/lib/puppetdb/client_spec.rb +64 -0
  62. data/spec/lib/puppetdb/fixsslconnectionadapter_spec.rb +107 -0
  63. data/spec/lib/puppetdb/query_spec.rb +39 -0
  64. data/spec/lib/puppetdb/request_spec.rb +61 -0
  65. data/spec/lib/puppetdb/response_spec.rb +13 -0
  66. data/spec/spec_helper.rb +4 -4
  67. metadata +133 -30
  68. data/Rakefile +0 -14
  69. data/hieracles.gemspec +0 -90
  70. data/lib/hieracles/help.rb +0 -38
  71. data/spec/lib/help_spec.rb +0 -8
@@ -8,6 +8,10 @@ module Hieracles
8
8
  "#{__callee__} not implemented, please inherit from the Hieracles::Format class to implement a format.\n"
9
9
  end
10
10
 
11
+ def facts(_)
12
+ "#{__callee__} not implemented, please inherit from the Hieracles::Format class to implement a format.\n"
13
+ end
14
+
11
15
  def files(_)
12
16
  "#{__callee__} not implemented, please inherit from the Hieracles::Format class to implement a format.\n"
13
17
  end
@@ -1,3 +1,5 @@
1
+ require 'awesome_print'
2
+
1
3
  module Hieracles
2
4
  module Formats
3
5
  # format accepting colors
@@ -14,7 +16,8 @@ module Hieracles
14
16
  "\e[36m%s\e[0m",
15
17
  "\e[37m%s\e[0m",
16
18
  "\e[38m%s\e[0m",
17
- "\e[97m%s\e[0m"
19
+ "\e[97m%s\e[0m",
20
+ "\e[35;1m%s\e[0m"
18
21
  ]
19
22
 
20
23
  def initialize(node)
@@ -22,16 +25,40 @@ module Hieracles
22
25
  super(node)
23
26
  end
24
27
 
25
- def info(_)
28
+ def info(filter)
29
+ build_list(@node.info, @node.notifications, filter)
30
+ end
31
+
32
+ def facts(filter)
33
+ build_list(@node.facts, @node.notifications, filter)
34
+ end
35
+
36
+ def build_list(hash, notifications, filter)
26
37
  back = ''
27
- length = max_key_length(@node.info) + 2
38
+ back << build_notifications(notifications) if notifications
39
+ if filter[0]
40
+ hash.select! { |k, v| Regexp.new(filter[0]).match(k.to_s) }
41
+ end
42
+ length = max_key_length(hash) + 2
28
43
  title = format(COLORS[8], "%-#{length}s")
29
- @node.info.each do |k, v|
44
+ hash.each do |k, v|
45
+ if v.class.name == 'Hash' || v.class.name == 'Array'
46
+ v = v.ai({ indent: 10, raw: true}).strip
47
+ end
30
48
  back << format("#{title} %s\n", k, v)
31
49
  end
32
50
  back
33
51
  end
34
52
 
53
+ def build_notifications(notifications)
54
+ back = "\n"
55
+ notifications.each do |v|
56
+ back << format("#{COLORS[9]}\n", "*** #{v.source}: #{v.message} ***")
57
+ end
58
+ back << "\n"
59
+ back
60
+ end
61
+
35
62
  def files(_)
36
63
  @node.files.join("\n") + "\n"
37
64
  end
@@ -52,24 +79,30 @@ module Hieracles
52
79
  def build_params_line(key, value, filter)
53
80
  output = ''
54
81
  if !filter || Regexp.new(filter).match(key)
55
- first = value.pop
56
- filecolor_index = @colors[first[:file]]
57
- filecolor = COLORS[filecolor_index]
58
- if is_merged? first
59
- output << format("%s #{COLORS[5]} %s\n", "[-]", key, sanitize(first[:merged]) )
60
- output << format(" #{COLORS[8]}\n", "[#{filecolor_index}] #{key} #{sanitize(first[:value])}" )
61
- else
62
- output << format("#{filecolor} #{COLORS[5]} %s\n", "[#{filecolor_index}]", key, sanitize(first[:value]) )
63
- end
82
+ output << build_first(key, value.pop)
64
83
  while value.count > 0
65
- overriden = value.pop
66
- filecolor_index = @colors[overriden[:file]]
67
- output << format(" #{COLORS[8]}\n", "[#{filecolor_index}] #{key} #{overriden[:value]}")
84
+ output << build_next(key, value.pop)
68
85
  end
69
86
  end
70
87
  output
71
88
  end
72
89
 
90
+ def build_first(key, first)
91
+ filecolor_index = @colors[first[:file]]
92
+ filecolor = COLORS[filecolor_index]
93
+ if is_merged? first
94
+ format("%s #{COLORS[5]} %s\n", "[-]", key, sanitize(first[:merged]) ) +
95
+ format(" #{COLORS[8]}\n", "[#{filecolor_index}] #{key} #{sanitize(first[:value])}" )
96
+ else
97
+ format("#{filecolor} #{COLORS[5]} %s\n", "[#{filecolor_index}]", key, sanitize(first[:value]) )
98
+ end
99
+ end
100
+
101
+ def build_next(key, overriden)
102
+ filecolor_index = @colors[overriden[:file]]
103
+ format(" #{COLORS[8]}\n", "[#{filecolor_index}] #{key} #{overriden[:value]}")
104
+ end
105
+
73
106
  def build_modules_line(key, value)
74
107
  length = max_key_length(@node.modules) + 3
75
108
  value_color = '%s'
@@ -8,6 +8,10 @@ module Hieracles
8
8
  make_csv @node.info.values
9
9
  end
10
10
 
11
+ def facts(_)
12
+ make_csv(@node.facts.keys) + make_csv(@node.facts.values)
13
+ end
14
+
11
15
  def files(_)
12
16
  make_csv @node.files
13
17
  end
@@ -30,18 +34,14 @@ module Hieracles
30
34
  if !filter || Regexp.new(filter).match(key)
31
35
  first = value.pop
32
36
  if is_merged? first
33
- output << make_csv(in_what_file('-') +
34
- [key, first[:merged].to_s, '0'])
35
- output << make_csv(in_what_file(first[:file]) +
36
- [key, first[:value].to_s, '1'])
37
+ output << build_line('-', key, first[:merged])
38
+ output << build_line(first[:file], key, first[:value], '1')
37
39
  else
38
- output << make_csv(in_what_file(first[:file]) +
39
- [key, first[:value].to_s, '0'])
40
+ output << build_line(first[:file], key, first[:value])
40
41
  end
41
42
  while value.count > 0
42
43
  overriden = value.pop
43
- output << make_csv(in_what_file(overriden[:file]) +
44
- [key, overriden[:value].to_s, '1'])
44
+ output << build_line(overriden[:file], key, overriden[:value], '1')
45
45
  end
46
46
  end
47
47
  output
@@ -53,6 +53,10 @@ module Hieracles
53
53
 
54
54
  private
55
55
 
56
+ def build_line(whatfile, key, value, overriden = '0')
57
+ make_csv(in_what_file(whatfile) + [key, value.to_s, overriden])
58
+ end
59
+
56
60
  def make_csv(array)
57
61
  array.join(CVS_DELIM) + "\n"
58
62
  end
@@ -6,29 +6,42 @@ module Hieracles
6
6
  class Json < Hieracles::Format
7
7
 
8
8
  def info(_)
9
- @node.info.to_json
9
+ @node.info.merge(alerts).to_json
10
+ end
11
+
12
+ def facts(_)
13
+ @node.facts.merge(alerts).to_json
10
14
  end
11
15
 
12
16
  def files(_)
13
- @node.files.to_json
17
+ { 'files' => @node.files }.merge(alerts).to_json
14
18
  end
15
19
 
16
20
  def paths(_)
17
- @node.paths.to_json
21
+ { 'paths' => @node.paths }.merge(alerts).to_json
18
22
  end
19
23
 
20
24
  def modules(_)
21
- @node.modules.to_json
25
+ @node.modules.merge(alerts).to_json
22
26
  end
23
27
 
24
28
  def params(args)
25
- @node.params(true).to_json
29
+ @node.params(true).merge(alerts).to_json
26
30
  end
27
31
 
28
32
  def allparams(args)
29
- @node.params(false).to_json
33
+ @node.params(false).merge(alerts).to_json
30
34
  end
31
35
 
36
+ private
37
+
38
+ def alerts
39
+ if @node.notifications.count > 0
40
+ { 'alerts' => @node.notifications.map(&:to_hash) }
41
+ else
42
+ {}
43
+ end
44
+ end
32
45
  end
33
46
  end
34
47
  end
@@ -10,15 +10,36 @@ module Hieracles
10
10
  super(node)
11
11
  end
12
12
 
13
- def info(_)
13
+ def info(filter)
14
+ build_list(@node.info, @node.notifications, filter)
15
+ end
16
+
17
+ def facts(filter)
18
+ build_list(@node.facts, @node.notifications, filter)
19
+ end
20
+
21
+ def build_list(hash, notifications, filter)
14
22
  back = ''
15
- length = max_key_length(@node.info) + 2
16
- @node.info.each do |k, v|
23
+ back << build_notifications(notifications) if notifications
24
+ if filter[0]
25
+ hash.select! { |k, v| Regexp.new(filter[0]).match(k) }
26
+ end
27
+ length = max_key_length(hash) + 2
28
+ hash.each do |k, v|
17
29
  back << format("%-#{length}s %s\n", k, v)
18
30
  end
19
31
  back
20
32
  end
21
33
 
34
+ def build_notifications(notifications)
35
+ back = "\n"
36
+ notifications.each do |v|
37
+ back << "*** #{v.source}: #{v.message} ***\n"
38
+ end
39
+ back << "\n"
40
+ back
41
+ end
42
+
22
43
  def files(_)
23
44
  @node.files.join("\n") + "\n"
24
45
  end
@@ -7,6 +7,10 @@ module Hieracles
7
7
  @node.info.to_yaml
8
8
  end
9
9
 
10
+ def facts(_)
11
+ @node.facts.to_yaml
12
+ end
13
+
10
14
  def files(_)
11
15
  @node.files.to_yaml
12
16
  end
@@ -7,6 +7,10 @@ module Hieracles
7
7
  @node.info.to_yaml
8
8
  end
9
9
 
10
+ def facts(_)
11
+ @node.facts.to_yaml
12
+ end
13
+
10
14
  def files(_)
11
15
  @node.files.to_yaml
12
16
  end
@@ -8,21 +8,23 @@ module Hieracles
8
8
  include Hieracles::Utils
9
9
  include Hieracles::Interpolate
10
10
 
11
- attr_reader :hiera_params, :hiera
11
+ attr_reader :hiera_params, :hiera, :facts, :notifications
12
12
 
13
13
  def initialize(fqdn, options)
14
+ @fqdn = fqdn
14
15
  Config.load(options)
15
16
  @hiera = Hieracles::Hiera.new
16
- @hiera_params = { fqdn: fqdn }.
17
- merge(get_hiera_params(fqdn)).
18
- merge(Config.scope).
17
+ @hiera_params = { fqdn: @fqdn }.
18
+ merge(get_hiera_params(@fqdn)).
19
19
  merge(Config.extraparams)
20
- @fqdn = fqdn
20
+ @facts = deep_sort(@hiera_params.
21
+ merge(Config.scope).
22
+ merge(puppet_facts))
21
23
  end
22
24
 
23
25
  def get_hiera_params(fqdn)
24
- if File.exist?(File.join(Config.path('encpath'), "#{fqdn}.yaml"))
25
- load = YAML.load_file(File.join(Config.path('encpath'), "#{fqdn}.yaml"))
26
+ @__hiera_params ||= if File.exist?(File.join(Config.encpath, "#{fqdn}.yaml"))
27
+ load = YAML.load_file(File.join(Config.encpath, "#{fqdn}.yaml"))
26
28
  sym_keys(load['parameters']).merge({ classes: load['classes']})
27
29
  else
28
30
  raise "Node not found"
@@ -31,7 +33,7 @@ module Hieracles
31
33
 
32
34
  def files(without_common = true)
33
35
  @__files ||= @hiera.hierarchy.reduce([]) do |a, f|
34
- file = parse("#{f}.yaml", @hiera_params, Config.interactive)
36
+ file = parse("#{f}.yaml", @facts, Config.interactive)
35
37
  if file &&
36
38
  File.exist?(File.join(@hiera.datapath, file)) &&
37
39
  (!without_common ||
@@ -72,6 +74,10 @@ module Hieracles
72
74
  end
73
75
 
74
76
  def modules
77
+ @_modules ||= _get_modules
78
+ end
79
+
80
+ def _get_modules
75
81
  modules = {}
76
82
  classfiles.each do |c|
77
83
  if File.exist?(c)
@@ -88,12 +94,20 @@ module Hieracles
88
94
  end
89
95
 
90
96
  def info
91
- @hiera_params
97
+ @_info ||= _get_info
98
+ end
99
+
100
+ def _get_info
101
+ extra = {}
102
+ if Config.usedb
103
+ extra = puppetdb_info
104
+ end
105
+ @hiera_params.merge extra
92
106
  end
93
107
 
94
108
  def classfiles
95
109
  @hiera_params[:classes].map do |cl|
96
- format(Config.path('classpath'), cl)
110
+ format(Config.classpath, cl)
97
111
  end
98
112
  end
99
113
 
@@ -114,6 +128,29 @@ module Hieracles
114
128
  modules
115
129
  end
116
130
 
131
+ def puppetdb_info
132
+ request_db.node_info(@fqdn).data
133
+ end
134
+
135
+ def puppet_facts
136
+ if Config.usedb
137
+ resp = request_db.node_facts(@fqdn)
138
+ @notifications = resp.notifications
139
+ if resp.total_records > 0
140
+ resp.data
141
+ else
142
+ error "not found in puppetdb."
143
+ {}
144
+ end
145
+ else
146
+ {}
147
+ end
148
+ end
149
+
150
+ def request_db
151
+ @_request ||= Hieracles::Puppetdb::Request.new Config.puppetdb
152
+ end
153
+
117
154
  def merge_trees(left, right)
118
155
  case @hiera.merge_behavior
119
156
  when :deeper
@@ -146,5 +183,13 @@ module Hieracles
146
183
  end
147
184
  end
148
185
 
186
+ def error(message)
187
+ if @notifications
188
+ @notifications << Notification.new('node', message, 'error')
189
+ else
190
+ @notifications = [ Notification.new('node', message, 'error') ]
191
+ end
192
+ end
193
+
149
194
  end
150
195
  end
@@ -0,0 +1,31 @@
1
+ module Hieracles
2
+ class Notification
3
+ attr_reader :level, :message, :timestamp, :source
4
+
5
+ LEVEL = {
6
+ 'fatal' => 0,
7
+ 'error' => 1,
8
+ 'warning' => 2,
9
+ 'info' => 3,
10
+ 'debug' => 4
11
+ }
12
+
13
+ def initialize(source, message, level = 'info')
14
+ @source = source
15
+ @level = level
16
+ @message = message
17
+ @timestamp = Time.new
18
+ end
19
+
20
+ def to_hash
21
+ {
22
+ 'source' => @source,
23
+ 'level' => @level,
24
+ 'message' => @message,
25
+ 'timestamp' => @timestamp
26
+ }
27
+ end
28
+
29
+ end
30
+ end
31
+
@@ -0,0 +1,109 @@
1
+ require 'hieracles/optparse'
2
+
3
+ module Hieracles
4
+ module Options
5
+ class Hc < Hieracles::Optparse
6
+
7
+ def available_options
8
+ {
9
+ config: {
10
+ has_arg: true,
11
+ aliases: ['c', 'conf', 'config']
12
+ },
13
+ format: {
14
+ has_arg: true,
15
+ aliases: ['f', 'format']
16
+ },
17
+ params: {
18
+ has_arg: true,
19
+ aliases: ['p', 'params']
20
+ },
21
+ hierafile: {
22
+ has_arg: true,
23
+ aliases: ['h', 'hierafile']
24
+ },
25
+ basepath: {
26
+ has_arg: true,
27
+ aliases: ['b', 'basepath']
28
+ },
29
+ encpath: {
30
+ has_arg: true,
31
+ aliases: ['e', 'encpath']
32
+ },
33
+ version: {
34
+ has_arg: false,
35
+ aliases: ['v', 'version']
36
+ },
37
+ yaml_facts: {
38
+ has_arg: true,
39
+ aliases: ['y', 'yaml']
40
+ },
41
+ json_facts: {
42
+ has_arg: true,
43
+ aliases: ['j', 'json']
44
+ },
45
+ interactive: {
46
+ has_arg: false,
47
+ aliases: ['i', 'interactive']
48
+ },
49
+ db: {
50
+ has_arg: false,
51
+ aliases: ['db']
52
+ },
53
+ nodb: {
54
+ has_arg: false,
55
+ aliases: ['nodb', 'no-db', 'no']
56
+ }
57
+ }
58
+ end
59
+
60
+ def self.usage
61
+ return <<-END
62
+
63
+ Usage: hc <fqdn> <command> [extra_args]
64
+
65
+ Available commands:
66
+ info provides the farm, datacenter, country
67
+ associated to the given fqdn
68
+ An extra param can be added for filtering
69
+ eg. hc <fqdn> info timestamp
70
+ eg. hc <fqdn> info farm
71
+ facts lists facts, either provided as a fact file
72
+ or grabbed from puppetdb.
73
+ An extra param can be added for filtering
74
+ eg. hc <fqdn> facts architecture
75
+ eg. hc <fqdn> facts 'memory.*mb'
76
+ files list all files containing params affecting this fqdn
77
+ (in more than commons)
78
+ paths list all file paths for files with params
79
+ modules list modules included in the farm where the node is
80
+ params list params for the node matching the fqdn
81
+ An extra filter string can be added to limit the list
82
+ use ruby regexp without the enclosing slashes
83
+ eg. hc <fqdn> params postfix.*version
84
+ eg. hc <fqdn> params '^postfix'
85
+ eg. hc <fqdn> params 'version$'
86
+ allparams same as params but including the common.yaml params (huge)
87
+ Also accepts a search string
88
+
89
+ Extra args:
90
+ -f <plain|console|csv|yaml|rawyaml|json> default console
91
+ -p extraparam=what;anotherparam=this
92
+ -c <configfile>
93
+ -h <hierafile>
94
+ -b <basepath> default ./
95
+ -e <encdir>
96
+ -y <fact_file> - facts in yaml format
97
+ -j <fact_file> - facts in json format
98
+ -v just displays the version of Hieracles
99
+ -i - interactive mode
100
+ -db - query puppetdb
101
+ -nodb - do not query puppetdb
102
+ END
103
+ end
104
+
105
+
106
+ end
107
+ end
108
+ end
109
+