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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +15 -1
- data/.github/workflows/release.yml +58 -0
- data/.gitleaks.toml +9 -0
- data/.rubocop.yml +28 -0
- data/BACKLOG.md +130 -24
- data/Gemfile.lock +36 -1
- data/README.md +26 -6
- data/Rakefile +1 -1
- data/docs/release/publishing.md +44 -48
- data/docs/release/release-readiness.md +14 -0
- data/docs/security-audit-2026-05-26-rerun.md +75 -0
- data/docs/security-audit-2026-05-26.md +63 -0
- data/lib/moose_inventory/cli/group.rb +3 -0
- data/lib/moose_inventory/cli/group_add.rb +89 -73
- data/lib/moose_inventory/cli/group_addchild.rb +77 -60
- data/lib/moose_inventory/cli/group_addhost.rb +78 -65
- data/lib/moose_inventory/cli/group_rm.rb +101 -71
- data/lib/moose_inventory/cli/group_rmchild.rb +99 -53
- data/lib/moose_inventory/cli/group_rmhost.rb +64 -56
- data/lib/moose_inventory/cli/helpers.rb +76 -0
- data/lib/moose_inventory/cli/host.rb +3 -0
- data/lib/moose_inventory/cli/host_add.rb +47 -62
- data/lib/moose_inventory/cli/host_addgroup.rb +73 -64
- data/lib/moose_inventory/cli/host_rmgroup.rb +58 -55
- data/lib/moose_inventory/db/db.rb +27 -7
- data/lib/moose_inventory/inventory_context.rb +50 -0
- data/lib/moose_inventory/operations/add_associations.rb +127 -0
- data/lib/moose_inventory/operations/add_groups.rb +115 -0
- data/lib/moose_inventory/operations/add_hosts.rb +110 -0
- data/lib/moose_inventory/operations/group_child_relations.rb +118 -0
- data/lib/moose_inventory/operations/group_cleanup.rb +55 -0
- data/lib/moose_inventory/operations/remove_associations.rb +101 -0
- data/lib/moose_inventory/operations/remove_groups.rb +79 -0
- data/lib/moose_inventory/version.rb +1 -1
- data/moose-inventory.gemspec +3 -0
- data/scripts/check.sh +2 -0
- data/scripts/ci/check_permissions.sh +3 -0
- data/scripts/ci/check_rubocop.sh +28 -0
- data/scripts/ci/check_secrets.sh +26 -0
- data/scripts/ci/check_security.sh +18 -0
- data/scripts/ci/install_security_tools.sh +47 -0
- data/scripts/install_dependencies.sh +2 -0
- data/spec/lib/moose_inventory/cli/group_rm_spec.rb +40 -0
- data/spec/lib/moose_inventory/cli/group_rmchild_spec.rb +45 -0
- data/spec/lib/moose_inventory/db/db_spec.rb +162 -0
- data/spec/lib/moose_inventory/operations/add_associations_spec.rb +77 -0
- data/spec/lib/moose_inventory/operations/add_groups_spec.rb +65 -0
- data/spec/lib/moose_inventory/operations/add_hosts_spec.rb +69 -0
- data/spec/lib/moose_inventory/operations/group_child_relations_spec.rb +76 -0
- data/spec/lib/moose_inventory/operations/remove_associations_spec.rb +78 -0
- data/spec/lib/moose_inventory/operations/remove_groups_spec.rb +57 -0
- 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 '
|
|
6
|
-
require_relative '../db/exceptions
|
|
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
|
-
|
|
19
|
-
|
|
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
|
|
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
|
-
|
|
36
|
-
groups = options[:groups].downcase.split(',').uniq
|
|
30
|
+
groups = csv_option_names(options[:groups])
|
|
37
31
|
|
|
38
32
|
# Sanity
|
|
39
|
-
|
|
40
|
-
abort('ERROR: Cannot manually manipulate '\
|
|
41
|
-
"the automatic group 'ungrouped'.")
|
|
42
|
-
end
|
|
33
|
+
abort_if_automatic_group(groups)
|
|
43
34
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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 '
|
|
5
|
-
require_relative '../db/exceptions
|
|
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
|
-
|
|
16
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
fmt = Moose::Inventory::Cli::Formatter
|
|
22
|
+
name = args[0].downcase
|
|
23
|
+
groups = normalize_names(args.slice(1, args.length - 1))
|
|
27
24
|
|
|
28
|
-
|
|
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
|
-
|
|
33
|
-
|
|
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
|
-
|
|
39
|
-
db.transaction do # Transaction start
|
|
30
|
+
db.transaction do
|
|
40
31
|
puts "Associate host '#{name}' with groups '#{groups.join(',')}':"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
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 '
|
|
5
|
-
require_relative '../db/exceptions
|
|
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
|
-
|
|
17
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
fmt = Moose::Inventory::Cli::Formatter
|
|
23
|
+
name = args[0].downcase
|
|
24
|
+
groups = normalize_names(args.slice(1, args.length - 1))
|
|
27
25
|
|
|
28
|
-
|
|
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
|
-
|
|
33
|
-
|
|
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
|
-
|
|
39
|
-
db.transaction do # Transaction start
|
|
31
|
+
db.transaction do
|
|
40
32
|
puts "Dissociate host '#{name}' from groups '#{groups.join(',')}':"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
|
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:
|
|
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
|