hieracles 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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
+