motoko 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/exe/inventory ADDED
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Or use Puppet's Ruby: #!/opt/puppetlabs/puppet/bin/ruby
5
+ #
6
+ # Call-seq: ./inventory.rb -F form_factor=Server -v
7
+
8
+ require 'motoko'
9
+ require 'mcollective'
10
+
11
+ include MCollective::RPC # rubocop:disable Style/MixinUsage
12
+
13
+ formatter = Motoko::Formatter.new
14
+
15
+ options = rpcoptions do |parser, local_options|
16
+ parser.banner = "usage: #{File.basename(__FILE__)} [options]"
17
+
18
+ Motoko::OptionParser.add_inventory_options(parser, formatter)
19
+
20
+ parser.on('--[no-]stats', 'Display statistics') do |v|
21
+ local_options[:stats] = v
22
+ end
23
+
24
+ Motoko::OptionParser.add_shortcut_options(parser, formatter, local_options)
25
+ end
26
+
27
+ options[:stats] = true if options[:stats].nil?
28
+
29
+ util = rpcclient('rpcutil', options: options)
30
+ util.progress = false
31
+
32
+ (options[:with_class] || []).compact.each do |klass|
33
+ util.class_filter(klass)
34
+ end
35
+
36
+ (options[:with_fact] || []).compact.each do |fact|
37
+ util.fact_filter(fact)
38
+ end
39
+
40
+ util.inventory do |_, resp|
41
+ formatter.nodes << Motoko::Node::Choria.new(resp)
42
+ end
43
+
44
+ puts formatter.to_s
45
+
46
+ printrpcstats if options[:stats]
data/exe/pdb-inventory ADDED
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'mcollective'
5
+ require 'motoko'
6
+ require 'optparse'
7
+
8
+ filters = []
9
+
10
+ oparser = Motoko::OptionParser.new
11
+
12
+ options = {}
13
+
14
+ oparser.parse do |parser|
15
+ parser.separator ''
16
+ parser.separator 'Host Filters'
17
+
18
+ parser.on('-C', '--wc', '--with-class CLASS', 'Match hosts with a certain config management class') do |with_class|
19
+ filters << Motoko::Utils::PuppetDB.class_filter(with_class)
20
+ end
21
+
22
+ parser.on('-F', '--wf', '--with-fact fact=val', 'Match hosts with a certain fact') do |with_fact|
23
+ filters << Motoko::Utils::PuppetDB.fact_filter(with_fact)
24
+ end
25
+
26
+ parser.on('-I', '--wi', '--with-identity IDENT', 'Match hosts with a certain configured identity') do |with_ident|
27
+ filters << Motoko::Utils::PuppetDB.identity_filter(with_ident)
28
+ end
29
+
30
+ Motoko::OptionParser.add_shortcut_options(parser, oparser.formatter, options)
31
+ end
32
+
33
+ (options[:with_class] || []).compact.each do |klass|
34
+ filters << Motoko::Utils::PuppetDB.class_filter(klass)
35
+ end
36
+
37
+ (options[:with_fact] || []).compact.each do |fact|
38
+ filters << Motoko::Utils::PuppetDB.fact_filter(fact)
39
+ end
40
+
41
+ config = MCollective::Config.instance
42
+ config.loadconfig(MCollective::Util.config_file_for_user)
43
+
44
+ client = MCollective::Util::Choria.new
45
+
46
+ response = client.pql_query("facts[certname, name, value] { #{filters.map { |f| "(#{f})" }.join(' and ')} }")
47
+
48
+ nodes = Hash.new { |hash, value| hash[value] = {} }
49
+
50
+ response.each do |fact|
51
+ nodes[fact['certname']][fact['name']] = fact['value']
52
+ end
53
+
54
+ nodes.each do |sender, facts|
55
+ oparser.formatter.nodes << Motoko::Node.new(sender, facts)
56
+ end
57
+
58
+ puts oparser.formatter.to_s
data/lib/motoko.rb ADDED
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'motoko/config'
4
+ require 'motoko/formatter'
5
+ require 'motoko/formatters/base_formatter'
6
+ require 'motoko/formatters/boolean'
7
+ require 'motoko/formatters/datetime'
8
+ require 'motoko/formatters/datetime_ago'
9
+ require 'motoko/formatters/ellipsis'
10
+ require 'motoko/formatters/timestamp'
11
+ require 'motoko/formatters/timestamp_ago'
12
+ require 'motoko/node'
13
+ require 'motoko/option_parser'
14
+ require 'motoko/resolvers/base_resolver'
15
+ require 'motoko/resolvers/cpu'
16
+ require 'motoko/resolvers/fact'
17
+ require 'motoko/resolvers/identity'
18
+ require 'motoko/resolvers/os'
19
+ require 'motoko/resolvers/reboot_required'
20
+ require 'motoko/utils/puppet_db'
21
+ require 'motoko/utils/snake_to_camel'
22
+ require 'motoko/utils/time_ago'
23
+ require 'motoko/version'
24
+
25
+ module Motoko
26
+ class Error < StandardError; end
27
+ # Your code goes here...
28
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+
5
+ module Motoko
6
+ class Config
7
+ attr_accessor :columns, :sort_by, :columns_spec, :shortcuts
8
+
9
+ def initialize
10
+ @columns = %w[host customer role]
11
+ @sort_by = %w[customer host]
12
+ @shortcuts = Hash.new { {} }
13
+ @columns_spec = Hash.new { {} }.merge(default_columns_spec)
14
+
15
+ load_system_config
16
+ load_user_config
17
+ end
18
+
19
+ def load_system_config
20
+ [
21
+ '/usr/local/etc/motoko',
22
+ '/etc/motoko',
23
+ ].each do |d|
24
+ if File.directory?(d)
25
+ load_config(d)
26
+ break
27
+ end
28
+ end
29
+ end
30
+
31
+ def load_user_config
32
+ d = File.expand_path('~/.config/motoko')
33
+ load_config(d) if File.directory?(d)
34
+ end
35
+
36
+ def load_config(directory)
37
+ load_classes(directory)
38
+
39
+ filename = File.join(directory, 'config.yaml')
40
+
41
+ return unless File.readable?(filename)
42
+
43
+ config = YAML.safe_load(File.read(filename))
44
+
45
+ @columns = config.delete('columns') if config.key?('columns')
46
+ @sort_by = config.delete('sort_by') if config.key?('sort_by')
47
+
48
+ @shortcuts.merge!(config.delete('shortcuts')) if config.key?('shortcuts')
49
+ @columns_spec.merge!(config.delete('columns_spec')) if config.key?('columns_spec')
50
+ end
51
+
52
+ def load_classes(directory)
53
+ Dir["#{directory}/formatters/*.rb", "#{directory}/resolvers/*.rb"].sort.each do |file|
54
+ require file
55
+ end
56
+ end
57
+
58
+ def default_columns_spec
59
+ YAML.safe_load(<<~COLUMNS_SPEC)
60
+ ---
61
+ host:
62
+ resolver: identity
63
+ customer:
64
+ formatter: ellipsis
65
+ max_length: 20
66
+ cpu:
67
+ resolver: cpu
68
+ os:
69
+ resolver: os
70
+ human_name: Operating System
71
+ reboot_required:
72
+ resolver: reboot_required
73
+ formatter: boolean
74
+ human_name: R
75
+ COLUMNS_SPEC
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'skittlize'
4
+ require 'terminal-table'
5
+
6
+ require 'motoko/utils/snake_to_camel'
7
+
8
+ module Motoko
9
+ class Formatter
10
+ attr_reader :options, :columns_spec, :shortcuts
11
+ attr_accessor :columns, :nodes, :mono, :wide, :count, :sort_by
12
+
13
+ include Motoko::Utils::SnakeToCamel
14
+
15
+ def initialize
16
+ config = Config.new
17
+ @nodes = []
18
+ @columns = config.columns
19
+ @mono = false
20
+ @wide = false
21
+ @count = false
22
+ @sort_by = config.sort_by
23
+ @columns_spec = config.columns_spec
24
+ @shortcuts = config.shortcuts
25
+ end
26
+
27
+ def to_s
28
+ return '' if nodes.empty?
29
+
30
+ @rows = nil
31
+ @column_resolvers = nil
32
+
33
+ columns.uniq!
34
+
35
+ table = ::Terminal::Table.new headings: headings, rows: data
36
+ column_resolvers.each_with_index do |column, idx|
37
+ table.align_column(idx, column.align) if column.align
38
+ end
39
+ table.to_s
40
+ end
41
+
42
+ def column_resolvers
43
+ @column_resolvers ||= columns.map do |column|
44
+ klass = columns_spec[column].delete('resolver') || 'Fact'
45
+
46
+ Object.const_get("Motoko::Resolvers::#{snake_to_camel_case(klass)}").new(column, columns_spec[column])
47
+ end
48
+ end
49
+
50
+ def data
51
+ return @data if @data
52
+
53
+ @data = sorted_nodes.map! do |node|
54
+ column_resolvers.map do |column|
55
+ column.value(node)
56
+ end
57
+ end
58
+
59
+ @data.skittlize!(split: "\n", join: ', ') unless mono
60
+
61
+ @data
62
+ end
63
+
64
+ def headings
65
+ column_resolvers.each_with_index.map do |column, idx|
66
+ name = column.human_name
67
+ if count
68
+ different_values = data.map { |line| line[idx] }.uniq.compact.count
69
+ name += " (#{different_values})" if different_values > 1
70
+ end
71
+ {
72
+ value: mono ? name : "\e[1m#{name}\e[0m",
73
+ alignment: :center,
74
+ }
75
+ end
76
+ end
77
+
78
+ def sorted_nodes
79
+ nodes.sort_by { |a| sort_by.map { |c| a.fact(c) || '' } + [a.identity] }
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motoko
4
+ module Formatters
5
+ class BaseFormatter
6
+ def initialize(options = {}) end
7
+
8
+ def format(value)
9
+ case value
10
+ when Array
11
+ value.join("\n")
12
+ when Hash
13
+ value.keys.join("\n")
14
+ else
15
+ value.to_s
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motoko
4
+ module Formatters
5
+ class Boolean < BaseFormatter
6
+ def format(value)
7
+ value ? '√' : nil
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+
5
+ module Motoko
6
+ module Formatters
7
+ class Datetime < BaseFormatter
8
+ def format(value)
9
+ DateTime.parse(value).to_time.getlocal.to_s
10
+ rescue ArgumentError, TypeError
11
+ nil
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'motoko/utils/time_ago'
4
+
5
+ require 'date'
6
+
7
+ module Motoko
8
+ module Formatters
9
+ class DatetimeAgo < BaseFormatter
10
+ include Motoko::Utils::TimeAgo
11
+
12
+ def format(value)
13
+ return nil unless value
14
+
15
+ seconds_to_human(Time.now - DateTime.parse(value).to_time)
16
+ rescue ArgumentError
17
+ nil
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motoko
4
+ module Formatters
5
+ class Ellipsis < BaseFormatter
6
+ attr_accessor :max_length
7
+
8
+ def initialize(options = {})
9
+ super
10
+ @max_length = options.delete('max_length') || 20
11
+ end
12
+
13
+ def format(value)
14
+ return nil unless value
15
+
16
+ res = value.dup
17
+ res[(max_length - 1)..-1] = '…' if res.length > max_length
18
+ res
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+
5
+ module Motoko
6
+ module Formatters
7
+ class Timestamp < BaseFormatter
8
+ def format(value)
9
+ Time.at(Integer(value)).getlocal.to_s
10
+ rescue ArgumentError, TypeError
11
+ nil
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'motoko/utils/time_ago'
4
+
5
+ require 'date'
6
+
7
+ module Motoko
8
+ module Formatters
9
+ class TimestampAgo < BaseFormatter
10
+ include Motoko::Utils::TimeAgo
11
+
12
+ def format(value)
13
+ return nil unless value
14
+
15
+ seconds_to_human(Time.now - Time.at(Integer(value)))
16
+ rescue ArgumentError
17
+ nil
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motoko
4
+ class Node
5
+ attr_reader :identity
6
+
7
+ def initialize(identity, facts)
8
+ @identity = identity
9
+ @facts = facts
10
+ end
11
+
12
+ def fact(name)
13
+ result = @facts
14
+ components = name.to_s.split('.')
15
+ while (component = components.shift)
16
+ case result
17
+ when Hash
18
+ result = result[component]
19
+ when Array
20
+ result = result[Integer(component)]
21
+ when NilClass
22
+ return nil
23
+ end
24
+ end
25
+ result
26
+ end
27
+
28
+ class Choria < Motoko::Node
29
+ def initialize(node)
30
+ super(node[:sender], node[:data][:facts])
31
+ end
32
+ end
33
+ end
34
+ end