moose-inventory 1.0.9 → 2.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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +15 -1
  3. data/.github/workflows/release.yml +58 -0
  4. data/.gitleaks.toml +9 -0
  5. data/.rubocop.yml +28 -0
  6. data/BACKLOG.md +130 -24
  7. data/Gemfile.lock +36 -1
  8. data/README.md +26 -6
  9. data/Rakefile +1 -1
  10. data/docs/release/publishing.md +44 -48
  11. data/docs/release/release-readiness.md +14 -0
  12. data/docs/security-audit-2026-05-26-rerun.md +75 -0
  13. data/docs/security-audit-2026-05-26.md +63 -0
  14. data/lib/moose_inventory/cli/group.rb +3 -0
  15. data/lib/moose_inventory/cli/group_add.rb +89 -73
  16. data/lib/moose_inventory/cli/group_addchild.rb +77 -60
  17. data/lib/moose_inventory/cli/group_addhost.rb +78 -65
  18. data/lib/moose_inventory/cli/group_rm.rb +101 -71
  19. data/lib/moose_inventory/cli/group_rmchild.rb +99 -53
  20. data/lib/moose_inventory/cli/group_rmhost.rb +64 -56
  21. data/lib/moose_inventory/cli/helpers.rb +76 -0
  22. data/lib/moose_inventory/cli/host.rb +3 -0
  23. data/lib/moose_inventory/cli/host_add.rb +47 -62
  24. data/lib/moose_inventory/cli/host_addgroup.rb +73 -64
  25. data/lib/moose_inventory/cli/host_rmgroup.rb +58 -55
  26. data/lib/moose_inventory/db/db.rb +27 -7
  27. data/lib/moose_inventory/inventory_context.rb +50 -0
  28. data/lib/moose_inventory/operations/add_associations.rb +127 -0
  29. data/lib/moose_inventory/operations/add_groups.rb +115 -0
  30. data/lib/moose_inventory/operations/add_hosts.rb +110 -0
  31. data/lib/moose_inventory/operations/group_child_relations.rb +118 -0
  32. data/lib/moose_inventory/operations/group_cleanup.rb +55 -0
  33. data/lib/moose_inventory/operations/remove_associations.rb +101 -0
  34. data/lib/moose_inventory/operations/remove_groups.rb +79 -0
  35. data/lib/moose_inventory/version.rb +1 -1
  36. data/moose-inventory.gemspec +3 -0
  37. data/scripts/check.sh +2 -0
  38. data/scripts/ci/check_permissions.sh +3 -0
  39. data/scripts/ci/check_rubocop.sh +28 -0
  40. data/scripts/ci/check_secrets.sh +26 -0
  41. data/scripts/ci/check_security.sh +18 -0
  42. data/scripts/ci/install_security_tools.sh +47 -0
  43. data/scripts/install_dependencies.sh +2 -0
  44. data/spec/lib/moose_inventory/cli/group_rm_spec.rb +40 -0
  45. data/spec/lib/moose_inventory/cli/group_rmchild_spec.rb +45 -0
  46. data/spec/lib/moose_inventory/db/db_spec.rb +162 -0
  47. data/spec/lib/moose_inventory/operations/add_associations_spec.rb +77 -0
  48. data/spec/lib/moose_inventory/operations/add_groups_spec.rb +65 -0
  49. data/spec/lib/moose_inventory/operations/add_hosts_spec.rb +69 -0
  50. data/spec/lib/moose_inventory/operations/group_child_relations_spec.rb +76 -0
  51. data/spec/lib/moose_inventory/operations/remove_associations_spec.rb +78 -0
  52. data/spec/lib/moose_inventory/operations/remove_groups_spec.rb +57 -0
  53. metadata +90 -1
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moose
4
+ module Inventory
5
+ module Cli
6
+ ##
7
+ # Shared helpers for Thor command classes.
8
+ module Helpers
9
+ AUTOMATIC_GROUP = 'ungrouped'
10
+
11
+ private
12
+
13
+ def db
14
+ Moose::Inventory::DB
15
+ end
16
+
17
+ def fmt
18
+ Moose::Inventory::Cli::Formatter
19
+ end
20
+
21
+ def normalize_names(values)
22
+ values.uniq.map(&:downcase)
23
+ end
24
+
25
+ def csv_option_names(value)
26
+ (value || '').downcase.split(',').uniq
27
+ end
28
+
29
+ def abort_if_missing_args(args, minimum, label)
30
+ return unless args.length < minimum
31
+
32
+ abort("ERROR: Wrong number of arguments, #{args.length} for #{label}.")
33
+ end
34
+
35
+ def abort_if_automatic_group(names, message = nil)
36
+ return unless names.include?(AUTOMATIC_GROUP)
37
+
38
+ abort(message || "ERROR: Cannot manually manipulate the automatic group '#{AUTOMATIC_GROUP}'.")
39
+ end
40
+
41
+ def association_exists?(dataset, name)
42
+ !dataset.nil? && !dataset[name: name].nil?
43
+ end
44
+
45
+ def automatic_group
46
+ db.models[:group].find_or_create(name: AUTOMATIC_GROUP)
47
+ end
48
+
49
+ def remove_automatic_group_from_host(host, indent:, message:)
50
+ ungrouped = host.groups_dataset[name: AUTOMATIC_GROUP]
51
+ return if ungrouped.nil?
52
+
53
+ fmt.puts indent, message
54
+ host.remove_group(ungrouped)
55
+ fmt.puts indent + 2, '- OK'
56
+ end
57
+
58
+ def add_automatic_group_to_host_if_last_group(host, indent:, message:)
59
+ add_automatic_group_to_host_if_group_count(host, 1, indent: indent, message: message)
60
+ end
61
+
62
+ def add_automatic_group_to_host_if_no_groups(host, indent:, message:)
63
+ add_automatic_group_to_host_if_group_count(host, 0, indent: indent, message: message)
64
+ end
65
+
66
+ def add_automatic_group_to_host_if_group_count(host, group_count, indent:, message:)
67
+ return unless host.groups_dataset.count == group_count
68
+
69
+ fmt.puts indent, message
70
+ host.add_group(automatic_group)
71
+ fmt.puts indent + 2, '- OK'
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -2,6 +2,7 @@ require 'thor'
2
2
  require 'json'
3
3
 
4
4
  require_relative './formatter.rb'
5
+ require_relative './helpers.rb'
5
6
  require_relative '../db/exceptions.rb'
6
7
 
7
8
  module Moose
@@ -10,6 +11,8 @@ module Moose
10
11
  ##
11
12
  # Class implementing the "host" methods of the CLI
12
13
  class Host < Thor
14
+ include Moose::Inventory::Cli::Helpers
15
+
13
16
  require_relative 'host_add'
14
17
  require_relative 'host_get'
15
18
  require_relative 'host_list'
@@ -1,9 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thor'
2
4
  require 'json'
3
5
  require 'indentation'
4
6
 
5
- require_relative './formatter.rb'
6
- require_relative '../db/exceptions.rb'
7
+ require_relative 'formatter'
8
+ require_relative '../db/exceptions'
9
+ require_relative '../inventory_context'
10
+ require_relative '../operations/add_hosts'
7
11
 
8
12
  module Moose
9
13
  module Inventory
@@ -15,77 +19,58 @@ module Moose
15
19
  desc 'add HOSTNAME_1 [HOSTNAME_2 ...]',
16
20
  'Add a hosts HOSTNAME_n to the inventory'
17
21
  option :groups
18
- # rubocop:disable Metrics/LineLength
19
- def add(*argv) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
20
- # rubocop:enable Metrics/LineLength
21
- if argv.empty?
22
- abort('ERROR: Wrong number of arguments, '\
23
- "#{argv.length} for 1 or more.")
24
- end
25
-
26
- # Convenience
27
- db = Moose::Inventory::DB
28
- fmt = Moose::Inventory::Cli::Formatter
22
+ def add(*argv)
23
+ abort_if_missing_args(argv, 1, '1 or more')
29
24
 
30
25
  # Arguments
31
- names = argv.uniq.map(&:downcase)
26
+ names = normalize_names(argv)
32
27
 
33
28
  # split(/\W+/) splits on hyphens too, which is not what we want
34
29
  # groups = options[:groups].downcase.split(/\W+/).uniq
35
- options[:groups].nil? && options[:groups] = ''
36
- groups = options[:groups].downcase.split(',').uniq
30
+ groups = csv_option_names(options[:groups])
37
31
 
38
32
  # Sanity
39
- if groups.include?('ungrouped')
40
- abort('ERROR: Cannot manually manipulate '\
41
- "the automatic group 'ungrouped'.")
42
- end
33
+ abort_if_automatic_group(groups)
43
34
 
44
- # Process
45
- db.transaction do # Transaction start
46
- fmt.reset_indent
35
+ result = Moose::Inventory::Operations::AddHosts
36
+ .new(context: Moose::Inventory::InventoryContext.new(db: db))
37
+ .call(names: names, groups: groups)
38
+ render_add_hosts_events(result.events)
39
+ puts 'Succeeded'
40
+ end
47
41
 
48
- names.each do |name|
49
- puts "Add host '#{name}':"
50
- fmt.puts 2, "- Creating host '#{name}'..."
51
- host = db.models[:host].find(name: name)
52
- groups_ds = nil
53
- if host.nil?
54
- host = db.models[:host].create(name: name)
55
- else
56
- fmt.warn "The host '#{name}' already exists, skipping creation.\n"
57
- groups_ds = host.groups_dataset
58
- end
59
- fmt.puts 4, '- OK'
42
+ private
60
43
 
61
- groups.each do |g|
62
- next if g.nil? || g.empty?
63
- fmt.puts 2, "- Adding association {host:#{name} <-> group:#{g}}..."
64
- group = db.models[:group].find(name: g)
65
- if group.nil?
66
- fmt.warn "The group '#{g}' doesn't exist, but will be created.\n"
67
- group = db.models[:group].create(name: g)
68
- end
69
- if !groups_ds.nil? && !groups_ds[name: g].nil?
70
- fmt.warn "Association {host:#{name} <-> group:#{g}} already exists, skipping creation.\n"
71
- else
72
- host.add_group(group)
73
- end
74
- fmt.puts 4, '- OK'
75
- end
44
+ def render_add_hosts_events(events)
45
+ fmt.reset_indent
46
+ events.each { |event| render_add_hosts_event(event) }
47
+ end
76
48
 
77
- # Handle the automatic 'ungrouped' group
78
- groups_ds = host.groups_dataset
79
- if !groups_ds.nil? && groups_ds.count == 0
80
- fmt.puts 2, "- Adding automatic association {host:#{name} <-> group:ungrouped}..."
81
- ungrouped = db.models[:group].find_or_create(name: 'ungrouped')
82
- host.add_group(ungrouped)
83
- fmt.puts 4, '- OK'
84
- end
85
- fmt.puts 2, '- All OK'
86
- end
87
- end # Transaction end
88
- puts 'Succeeded'
49
+ def render_add_hosts_event(event) # rubocop:disable Metrics/CyclomaticComplexity
50
+ payload = event.payload
51
+ case event.type
52
+ when :host_started
53
+ puts "Add host '#{payload[:name]}':"
54
+ when :creating_host
55
+ fmt.puts 2, "- Creating host '#{payload[:name]}'..."
56
+ when :host_exists
57
+ fmt.warn "The host '#{payload[:name]}' already exists, skipping creation.\n"
58
+ when :ok
59
+ fmt.puts payload[:indent], '- OK'
60
+ when :adding_association
61
+ fmt.puts 2, "- Adding association {host:#{payload[:host]} <-> group:#{payload[:group]}}..."
62
+ when :group_missing_created
63
+ fmt.warn "The group '#{payload[:name]}' doesn't exist, but will be created.\n"
64
+ when :association_exists
65
+ fmt.warn(
66
+ "Association {host:#{payload[:host]} <-> group:#{payload[:group]}} " \
67
+ "already exists, skipping creation.\n"
68
+ )
69
+ when :adding_automatic_group
70
+ fmt.puts 2, "- Adding automatic association {host:#{payload[:host]} <-> group:#{payload[:group]}}..."
71
+ when :host_complete
72
+ fmt.puts 2, '- All OK'
73
+ end
89
74
  end
90
75
  end
91
76
  end
@@ -1,8 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thor'
2
4
  require 'json'
3
5
 
4
- require_relative './formatter.rb'
5
- require_relative '../db/exceptions.rb'
6
+ require_relative 'formatter'
7
+ require_relative '../db/exceptions'
8
+ require_relative '../inventory_context'
9
+ require_relative '../operations/add_associations'
6
10
 
7
11
  module Moose
8
12
  module Inventory
@@ -12,76 +16,81 @@ module Moose
12
16
  class Host
13
17
  desc 'addgroup HOSTNAME GROUPNAME [GROUPNAME ...]',
14
18
  'Associate the host with a group'
15
- # rubocop:disable Metrics/LineLength
16
- def addgroup(*args) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
17
- # rubocop:enable Metrics/LineLength
18
- # Sanity
19
- if args.length < 2
20
- abort('ERROR: Wrong number of arguments, '\
21
- "#{args.length} for 2 or more.")
22
- end
19
+ def addgroup(*args)
20
+ abort_if_missing_args(args, 2, '2 or more')
23
21
 
24
- # Convenience
25
- db = Moose::Inventory::DB
26
- fmt = Moose::Inventory::Cli::Formatter
22
+ name = args[0].downcase
23
+ groups = normalize_names(args.slice(1, args.length - 1))
27
24
 
28
- # Arguments
29
- name = args[0].downcase
30
- groups = args.slice(1, args.length - 1).uniq.map(&:downcase)
25
+ abort_if_automatic_group(groups)
31
26
 
32
- # Sanity
33
- if groups.include?('ungrouped')
34
- abort 'ERROR: Cannot manually manipulate the automatic '\
35
- 'group \'ungrouped\'.'
36
- end
27
+ context = Moose::Inventory::InventoryContext.new(db: db)
28
+ operation = Moose::Inventory::Operations::AddAssociations.new(context: context)
37
29
 
38
- # Transaction
39
- db.transaction do # Transaction start
30
+ db.transaction do
40
31
  puts "Associate host '#{name}' with groups '#{groups.join(',')}':"
41
- # Get the target host
42
- fmt.puts 2, "- Retrieve host '#{name}'..."
43
- host = db.models[:host].find(name: name)
44
- if host.nil?
45
- fail db.exceptions[:moose], "The host '#{name}' "\
46
- 'was not found in the database.'
47
- end
48
- fmt.puts 4, '- OK'
49
-
50
- # Associate host with the groups
51
- groups_ds = host.groups_dataset
52
- groups.each do |g|
53
- fmt.puts 2, "- Add association {host:#{name} <-> group:#{g}}..."
54
-
55
- # Check against existing associations
56
- if !groups_ds[name: g].nil?
57
- fmt.warn "Association {host:#{name} <-> group:#{g}} already exists, skipping."
58
- fmt.puts 4, '- Already exists, skipping.'
59
- else
60
- # Add new association
61
- group = db.models[:group].find(name: g)
62
- if group.nil?
63
- fmt.warn "Group '#{g}' does not exist and will be created."
64
- fmt.puts 4, '- Group does not exist, creating now...'
65
- group = db.models[:group].create(name: g)
66
- fmt.puts 6, '- OK'
67
- end
68
- host.add_group(group)
69
- end
70
- fmt.puts 4, '- OK'
71
- end
72
-
73
- # Handle 'ungrouped' group automation
74
- unless groups_ds[name: 'ungrouped'].nil?
75
- fmt.puts 2, '- Remove automatic association '\
76
- "{host:#{name} <-> group:ungrouped}..."
77
- ungrouped = db.models[:group].find(name: 'ungrouped')
78
- host.remove_group(ungrouped) unless ungrouped.nil?
79
- fmt.puts 4, '- OK'
80
- end
32
+ host = fetch_existing_host_for_addgroup(context, name)
33
+ render_host_addgroup_events(
34
+ operation.host_to_groups(host: host, host_name: name, group_names: groups).events
35
+ )
81
36
  fmt.puts 2, '- All OK'
82
- end # Transaction end
37
+ end
38
+
83
39
  puts 'Succeeded'
84
40
  end
41
+
42
+ private
43
+
44
+ def fetch_existing_host_for_addgroup(context, name)
45
+ fmt.puts 2, "- Retrieve host '#{name}'..."
46
+ host = context.find_host(name)
47
+ raise db.exceptions[:moose], "The host '#{name}' was not found in the database." if host.nil?
48
+
49
+ fmt.puts 4, '- OK'
50
+ host
51
+ end
52
+
53
+ def render_host_addgroup_events(events)
54
+ events.each { |event| render_host_addgroup_event(event) }
55
+ end
56
+
57
+ def render_host_addgroup_event(event)
58
+ payload = event.payload
59
+
60
+ return render_host_addgroup_warning(event.type, payload) if host_addgroup_warning?(event.type)
61
+ return render_host_addgroup_status(payload) if event.type == :already_exists_skipping
62
+
63
+ render_host_addgroup_output(event.type, payload)
64
+ end
65
+
66
+ def host_addgroup_warning?(type)
67
+ %i[host_group_association_exists group_missing_created].include?(type)
68
+ end
69
+
70
+ def render_host_addgroup_warning(type, payload)
71
+ if type == :host_group_association_exists
72
+ fmt.warn "Association {host:#{payload[:host]} <-> group:#{payload[:group]}} already exists, skipping."
73
+ else
74
+ fmt.warn "Group '#{payload[:name]}' does not exist and will be created."
75
+ end
76
+ end
77
+
78
+ def render_host_addgroup_status(payload)
79
+ fmt.puts payload[:indent], '- Already exists, skipping.'
80
+ end
81
+
82
+ def render_host_addgroup_output(type, payload)
83
+ case type
84
+ when :adding_host_group_association
85
+ fmt.puts 2, "- Add association {host:#{payload[:host]} <-> group:#{payload[:group]}}..."
86
+ when :group_creating_now
87
+ fmt.puts 4, '- Group does not exist, creating now...'
88
+ when :removing_automatic_group
89
+ fmt.puts 2, "- Remove automatic association {host:#{payload[:host]} <-> group:ungrouped}..."
90
+ when :ok
91
+ fmt.puts payload[:indent], '- OK'
92
+ end
93
+ end
85
94
  end
86
95
  end
87
96
  end
@@ -1,8 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thor'
2
4
  require 'json'
3
5
 
4
- require_relative './formatter.rb'
5
- require_relative '../db/exceptions.rb'
6
+ require_relative 'formatter'
7
+ require_relative '../db/exceptions'
8
+ require_relative '../inventory_context'
9
+ require_relative '../operations/remove_associations'
6
10
 
7
11
  module Moose
8
12
  module Inventory
@@ -13,67 +17,66 @@ module Moose
13
17
  #==========================
14
18
  desc 'rmgroup HOSTNAME GROUPNAME [GROUPNAME ...]',
15
19
  'dissociation the host from a group'
16
- # rubocop:disable Metrics/LineLength
17
- def rmgroup(*args) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
18
- # rubocop:enable Metrics/LineLength
19
- if args.length < 2
20
- abort('ERROR: Wrong number of arguments, '\
21
- "#{args.length} for 2 or more.")
22
- end
20
+ def rmgroup(*args)
21
+ abort_if_missing_args(args, 2, '2 or more')
23
22
 
24
- # Convenience
25
- db = Moose::Inventory::DB
26
- fmt = Moose::Inventory::Cli::Formatter
23
+ name = args[0].downcase
24
+ groups = normalize_names(args.slice(1, args.length - 1))
27
25
 
28
- # arguments
29
- name = args[0].downcase
30
- groups = args.slice(1, args.length - 1).uniq.map(&:downcase)
26
+ abort_if_automatic_group(groups)
31
27
 
32
- # Sanity
33
- if groups.include?('ungrouped')
34
- abort 'ERROR: Cannot manually manipulate the automatic '\
35
- 'group \'ungrouped\'.'
36
- end
28
+ context = Moose::Inventory::InventoryContext.new(db: db)
29
+ operation = Moose::Inventory::Operations::RemoveAssociations.new(context: context)
37
30
 
38
- # Transaction
39
- db.transaction do # Transaction start
31
+ db.transaction do
40
32
  puts "Dissociate host '#{name}' from groups '#{groups.join(',')}':"
41
- fmt.puts 2, "- Retrieve host '#{name}'..."
42
- host = db.models[:host].find(name: name)
43
- if host.nil?
44
- fail db.exceptions[:moose],
45
- "The host '#{name}' was not found in the database."
46
- end
47
- fmt.puts 4, '- OK'
48
-
49
- # dissociate host from the groups
50
- groups_ds = host.groups_dataset
51
- groups.each do |g|
52
- fmt.puts 2, "- Remove association {host:#{name} <-> group:#{g}}..."
53
-
54
- # Check against existing associations
55
- if groups_ds[name: g].nil?
56
- fmt.warn "Association {host:#{name} <-> group:#{g}} doesn't exist, skipping.\n"
57
- fmt.puts 4, "- Doesn't exist, skipping."
58
- else
59
- group = db.models[:group].find(name: g)
60
- host.remove_group(group) unless group.nil?
61
- end
62
- fmt.puts 4, '- OK'
63
- end
64
-
65
- # Handle 'ungrouped' group automation
66
- if host.groups_dataset.count == 0
67
- fmt.puts 2, '- Add automatic association '\
68
- "{host:#{name} <-> group:ungrouped}..."
69
- ungrouped = db.models[:group].find_or_create(name: 'ungrouped')
70
- host.add_group(ungrouped) unless ungrouped.nil?
71
- fmt.puts 4, '- OK'
72
- end
33
+ host = fetch_existing_host_for_rmgroup(context, name)
34
+ render_host_rmgroup_events(
35
+ operation.host_from_groups(host: host, host_name: name, group_names: groups).events
36
+ )
73
37
  fmt.puts 2, '- All OK'
74
- end # End transaction
38
+ end
75
39
  puts 'Succeeded'
76
40
  end
41
+
42
+ private
43
+
44
+ def fetch_existing_host_for_rmgroup(context, name)
45
+ fmt.puts 2, "- Retrieve host '#{name}'..."
46
+ host = context.find_host(name)
47
+ raise db.exceptions[:moose], "The host '#{name}' was not found in the database." if host.nil?
48
+
49
+ fmt.puts 4, '- OK'
50
+ host
51
+ end
52
+
53
+ def render_host_rmgroup_events(events)
54
+ events.each { |event| render_host_rmgroup_event(event) }
55
+ end
56
+
57
+ def render_host_rmgroup_event(event)
58
+ payload = event.payload
59
+
60
+ return render_host_rmgroup_warning(payload) if event.type == :host_group_association_missing
61
+ return render_host_rmgroup_missing(payload) if event.type == :missing_skipping
62
+
63
+ case event.type
64
+ when :removing_host_group_association
65
+ fmt.puts 2, "- Remove association {host:#{payload[:host]} <-> group:#{payload[:group]}}..."
66
+ when :adding_automatic_group
67
+ fmt.puts 2, "- Add automatic association {host:#{payload[:host]} <-> group:ungrouped}..."
68
+ when :ok
69
+ fmt.puts payload[:indent], '- OK'
70
+ end
71
+ end
72
+
73
+ def render_host_rmgroup_warning(payload)
74
+ fmt.warn "Association {host:#{payload[:host]} <-> group:#{payload[:group]}} doesn't exist, skipping.\n"
75
+ end
76
+
77
+ def render_host_rmgroup_missing(payload)
78
+ fmt.puts payload[:indent], "- Doesn't exist, skipping."
79
+ end
77
80
  end
78
81
  end
79
82
  end
@@ -92,7 +92,7 @@ module Moose
92
92
  case tries
93
93
  when 1..10
94
94
  if Moose::Inventory::Config._confopts[:trace] == true
95
- STDERR.puts e.message
95
+ $stderr.puts e.message
96
96
  end
97
97
  sleep rand
98
98
  retry
@@ -108,8 +108,8 @@ module Moose
108
108
  warn 'An error occurred during a transaction, any changes have been rolled back.'
109
109
 
110
110
  if Moose::Inventory::Config._confopts[:trace] == true
111
- STDERR.puts $ERROR_INFO.backtrace
112
- abort("ERROR: #{e}")
111
+ $stderr.puts e.full_message(highlight: false, order: :top)
112
+ abort("ERROR: #{e.message}")
113
113
  else
114
114
  abort("ERROR: #{e.message}")
115
115
  end
@@ -272,15 +272,16 @@ module Moose
272
272
 
273
273
  # Quick check that expected keys are at least present
274
274
  config = Moose::Inventory::Config._settings[:config][:db]
275
- [:host, :database, :user, :password].each do |key|
275
+ [:host, :database, :user].each do |key|
276
276
  if config[key].nil?
277
277
  fail @exceptions[:moose],
278
278
  "Expected key #{key} missing in mysql configuration"
279
279
  end
280
280
  end
281
+ password = db_password(config, 'mysql')
281
282
 
282
283
  @db = Sequel.mysql2(user: config[:user],
283
- password: config[:password],
284
+ password: password,
284
285
  host: config[:host],
285
286
  database: config[:database])
286
287
  end
@@ -291,18 +292,37 @@ module Moose
291
292
 
292
293
  # Quick check that expected keys are at least present
293
294
  config = Moose::Inventory::Config._settings[:config][:db]
294
- [:host, :database, :user, :password].each do |key|
295
+ [:host, :database, :user].each do |key|
295
296
  if config[key].nil?
296
297
  fail @exceptions[:moose],
297
298
  "Expected key #{key} missing in postgresql configuration"
298
299
  end
299
300
  end
301
+ password = db_password(config, 'postgresql')
300
302
 
301
303
  @db = Sequel.postgres(user: config[:user],
302
- password: config[:password],
304
+ password: password,
303
305
  host: config[:host],
304
306
  database: config[:database])
305
307
  end
308
+
309
+ #--------------------
310
+ def self.db_password(config, adapter)
311
+ return config[:password] unless config[:password].nil?
312
+
313
+ if config[:password_env].nil?
314
+ fail @exceptions[:moose],
315
+ "Expected key password or password_env missing in #{adapter} configuration"
316
+ end
317
+
318
+ password = ENV.fetch(config[:password_env].to_s, nil)
319
+ if password.nil? || password.empty?
320
+ fail @exceptions[:moose],
321
+ "Environment variable #{config[:password_env]} is not set for #{adapter} password"
322
+ end
323
+
324
+ password
325
+ end
306
326
  end
307
327
  end
308
328
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moose
4
+ module Inventory
5
+ ##
6
+ # Thin facade over the current DB singleton.
7
+ #
8
+ # This gives new operation/service objects a small inventory-facing seam
9
+ # without forcing the legacy CLI to stop using the DB singleton all at once.
10
+ class InventoryContext
11
+ AUTOMATIC_GROUP = 'ungrouped'
12
+
13
+ def initialize(db: Moose::Inventory::DB)
14
+ @db = db
15
+ end
16
+
17
+ def transaction(&)
18
+ db.transaction(&)
19
+ end
20
+
21
+ def find_host(name)
22
+ db.models[:host].find(name: name)
23
+ end
24
+
25
+ def create_host(name)
26
+ db.models[:host].create(name: name)
27
+ end
28
+
29
+ def find_group(name)
30
+ db.models[:group].find(name: name)
31
+ end
32
+
33
+ def create_group(name)
34
+ db.models[:group].create(name: name)
35
+ end
36
+
37
+ def find_or_create_group(name)
38
+ db.models[:group].find_or_create(name: name)
39
+ end
40
+
41
+ def automatic_group
42
+ find_or_create_group(AUTOMATIC_GROUP)
43
+ end
44
+
45
+ private
46
+
47
+ attr_reader :db
48
+ end
49
+ end
50
+ end