motoko 1.0.0

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