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,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moose
|
|
4
|
+
module Inventory
|
|
5
|
+
module Operations
|
|
6
|
+
# Adds host/group associations for existing primary entities.
|
|
7
|
+
class AddAssociations
|
|
8
|
+
AUTOMATIC_GROUP = 'ungrouped'
|
|
9
|
+
Event = Struct.new(:type, :payload, keyword_init: true)
|
|
10
|
+
Result = Struct.new(:events, :warning_count, keyword_init: true)
|
|
11
|
+
|
|
12
|
+
def initialize(context:)
|
|
13
|
+
@context = context
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def host_to_groups(host:, host_name:, group_names:)
|
|
17
|
+
events = []
|
|
18
|
+
warning_count = 0
|
|
19
|
+
|
|
20
|
+
group_names.each do |group_name|
|
|
21
|
+
next if group_name.nil? || group_name.empty?
|
|
22
|
+
|
|
23
|
+
warning_count += add_group_to_host(host, host_name, group_name, events)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
remove_automatic_group_from_host(host, host_name, events)
|
|
27
|
+
|
|
28
|
+
Result.new(events: events, warning_count: warning_count)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def group_to_hosts(group:, group_name:, host_names:)
|
|
32
|
+
events = []
|
|
33
|
+
warning_count = 0
|
|
34
|
+
hosts_dataset = group.hosts_dataset
|
|
35
|
+
|
|
36
|
+
host_names.each do |host_name|
|
|
37
|
+
next if host_name.nil? || host_name.empty?
|
|
38
|
+
|
|
39
|
+
warning_count += add_host_to_group(
|
|
40
|
+
group,
|
|
41
|
+
group_name,
|
|
42
|
+
host_name,
|
|
43
|
+
hosts_dataset,
|
|
44
|
+
events
|
|
45
|
+
)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
Result.new(events: events, warning_count: warning_count)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
attr_reader :context
|
|
54
|
+
|
|
55
|
+
def add_group_to_host(host, host_name, group_name, events)
|
|
56
|
+
warning_count = 0
|
|
57
|
+
groups_dataset = host.groups_dataset
|
|
58
|
+
|
|
59
|
+
emit(events, :adding_host_group_association, host: host_name, group: group_name)
|
|
60
|
+
|
|
61
|
+
if association_exists?(groups_dataset, group_name)
|
|
62
|
+
emit(events, :host_group_association_exists, host: host_name, group: group_name)
|
|
63
|
+
emit(events, :already_exists_skipping, indent: 4)
|
|
64
|
+
emit(events, :ok, indent: 4)
|
|
65
|
+
return warning_count + 1
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
group = context.find_group(group_name)
|
|
69
|
+
if group.nil?
|
|
70
|
+
emit(events, :group_missing_created, name: group_name)
|
|
71
|
+
emit(events, :group_creating_now, name: group_name)
|
|
72
|
+
group = context.create_group(group_name)
|
|
73
|
+
emit(events, :ok, indent: 6)
|
|
74
|
+
warning_count += 1
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
host.add_group(group)
|
|
78
|
+
emit(events, :ok, indent: 4)
|
|
79
|
+
warning_count
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def add_host_to_group(group, group_name, host_name, hosts_dataset, events)
|
|
83
|
+
warning_count = 0
|
|
84
|
+
emit(events, :adding_group_host_association, group: group_name, host: host_name)
|
|
85
|
+
|
|
86
|
+
if association_exists?(hosts_dataset, host_name)
|
|
87
|
+
emit(events, :group_host_association_exists, group: group_name, host: host_name)
|
|
88
|
+
emit(events, :already_exists_skipping, indent: 4)
|
|
89
|
+
emit(events, :ok, indent: 4)
|
|
90
|
+
return warning_count + 1
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
host = context.find_host(host_name)
|
|
94
|
+
if host.nil?
|
|
95
|
+
emit(events, :host_missing_created, name: host_name)
|
|
96
|
+
emit(events, :host_creating_now, name: host_name)
|
|
97
|
+
host = context.create_host(host_name)
|
|
98
|
+
emit(events, :ok, indent: 6)
|
|
99
|
+
warning_count += 1
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
group.add_host(host)
|
|
103
|
+
emit(events, :ok, indent: 4)
|
|
104
|
+
remove_automatic_group_from_host(host, host_name, events)
|
|
105
|
+
warning_count
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def remove_automatic_group_from_host(host, host_name, events)
|
|
109
|
+
ungrouped = host.groups_dataset[name: AUTOMATIC_GROUP]
|
|
110
|
+
return if ungrouped.nil?
|
|
111
|
+
|
|
112
|
+
emit(events, :removing_automatic_group, host: host_name)
|
|
113
|
+
host.remove_group(ungrouped)
|
|
114
|
+
emit(events, :ok, indent: 4)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def association_exists?(dataset, name)
|
|
118
|
+
!dataset.nil? && !dataset[name: name].nil?
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def emit(events, type, payload = {})
|
|
122
|
+
events << Event.new(type: type, payload: payload)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moose
|
|
4
|
+
module Inventory
|
|
5
|
+
module Operations
|
|
6
|
+
##
|
|
7
|
+
# Adds groups and their optional host associations.
|
|
8
|
+
class AddGroups
|
|
9
|
+
AUTOMATIC_GROUP = 'ungrouped'
|
|
10
|
+
Event = Struct.new(:type, :payload, keyword_init: true)
|
|
11
|
+
Result = Struct.new(:events, :warning_count, keyword_init: true)
|
|
12
|
+
|
|
13
|
+
def initialize(context:)
|
|
14
|
+
@context = context
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def call(names:, hosts:)
|
|
18
|
+
events = []
|
|
19
|
+
warning_count = 0
|
|
20
|
+
|
|
21
|
+
context.transaction do
|
|
22
|
+
names.each do |name|
|
|
23
|
+
warning_count += add_group(name, hosts, events)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
Result.new(events: events, warning_count: warning_count)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
attr_reader :context
|
|
33
|
+
|
|
34
|
+
def add_group(name, hosts, events)
|
|
35
|
+
warning_count = 0
|
|
36
|
+
emit(events, :group_started, name: name)
|
|
37
|
+
group, hosts_dataset, created = create_or_find_group(name, events)
|
|
38
|
+
warning_count += 1 unless created
|
|
39
|
+
|
|
40
|
+
hosts.each do |host_name|
|
|
41
|
+
next if host_name.nil? || host_name.empty?
|
|
42
|
+
|
|
43
|
+
warning_count += add_host_association(group, name, host_name, hosts_dataset, events)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
emit(events, :group_complete)
|
|
47
|
+
warning_count
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def create_or_find_group(name, events)
|
|
51
|
+
emit(events, :creating_group)
|
|
52
|
+
group = context.find_group(name)
|
|
53
|
+
|
|
54
|
+
if group.nil?
|
|
55
|
+
group = context.create_group(name)
|
|
56
|
+
emit(events, :ok, indent: 4)
|
|
57
|
+
[group, nil, true]
|
|
58
|
+
else
|
|
59
|
+
emit(events, :group_exists, name: name)
|
|
60
|
+
emit(events, :already_exists_skipping, indent: 4)
|
|
61
|
+
emit(events, :ok, indent: 4)
|
|
62
|
+
[group, group.hosts_dataset, false]
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def add_host_association(group, group_name, host_name, hosts_dataset, events)
|
|
67
|
+
warning_count = 0
|
|
68
|
+
emit(events, :adding_association, group: group_name, host: host_name)
|
|
69
|
+
host, created = find_or_create_host(host_name, events)
|
|
70
|
+
warning_count += 1 if created == :warned_create
|
|
71
|
+
|
|
72
|
+
if association_exists?(hosts_dataset, host_name)
|
|
73
|
+
emit(events, :association_exists, group: group_name, host: host_name)
|
|
74
|
+
emit(events, :already_exists_skipping, indent: 4)
|
|
75
|
+
warning_count += 1
|
|
76
|
+
else
|
|
77
|
+
group.add_host(host)
|
|
78
|
+
end
|
|
79
|
+
emit(events, :ok, indent: 4)
|
|
80
|
+
|
|
81
|
+
remove_automatic_group_from_host(host, host_name, events)
|
|
82
|
+
warning_count
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def find_or_create_host(name, events)
|
|
86
|
+
host = context.find_host(name)
|
|
87
|
+
return [host, :existing] unless host.nil?
|
|
88
|
+
|
|
89
|
+
emit(events, :host_missing_created, name: name)
|
|
90
|
+
emit(events, :host_creating_now, name: name)
|
|
91
|
+
host = context.create_host(name)
|
|
92
|
+
emit(events, :ok, indent: 6)
|
|
93
|
+
[host, :warned_create]
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def remove_automatic_group_from_host(host, host_name, events)
|
|
97
|
+
ungrouped = host.groups_dataset[name: AUTOMATIC_GROUP]
|
|
98
|
+
return if ungrouped.nil?
|
|
99
|
+
|
|
100
|
+
emit(events, :removing_automatic_group, host: host_name)
|
|
101
|
+
host.remove_group(ungrouped)
|
|
102
|
+
emit(events, :ok, indent: 4)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def association_exists?(dataset, name)
|
|
106
|
+
!dataset.nil? && !dataset[name: name].nil?
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def emit(events, type, payload = {})
|
|
110
|
+
events << Event.new(type: type, payload: payload)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moose
|
|
4
|
+
module Inventory
|
|
5
|
+
module Operations
|
|
6
|
+
##
|
|
7
|
+
# Adds hosts and their optional group associations.
|
|
8
|
+
#
|
|
9
|
+
# The operation mutates inventory state and returns structured events for
|
|
10
|
+
# the CLI adapter to render. Keeping output out of this class makes the
|
|
11
|
+
# inventory behavior easier to exercise without binding every domain test
|
|
12
|
+
# to progress text.
|
|
13
|
+
class AddHosts
|
|
14
|
+
AUTOMATIC_GROUP = 'ungrouped'
|
|
15
|
+
Event = Struct.new(:type, :payload, keyword_init: true)
|
|
16
|
+
Result = Struct.new(:events, keyword_init: true)
|
|
17
|
+
|
|
18
|
+
def initialize(context:)
|
|
19
|
+
@context = context
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def call(names:, groups:)
|
|
23
|
+
events = []
|
|
24
|
+
context.transaction do
|
|
25
|
+
names.each do |name|
|
|
26
|
+
add_host(name, groups, events)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
Result.new(events: events)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
attr_reader :context
|
|
35
|
+
|
|
36
|
+
def add_host(name, groups, events)
|
|
37
|
+
emit(events, :host_started, name: name)
|
|
38
|
+
host, groups_dataset = create_or_find_host(name, events)
|
|
39
|
+
|
|
40
|
+
groups.each do |group_name|
|
|
41
|
+
add_group_association(host, name, group_name, groups_dataset, events)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
add_automatic_group_if_needed(host, name, events)
|
|
45
|
+
emit(events, :host_complete)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def create_or_find_host(name, events)
|
|
49
|
+
emit(events, :creating_host, name: name)
|
|
50
|
+
host = context.find_host(name)
|
|
51
|
+
groups_dataset = nil
|
|
52
|
+
|
|
53
|
+
if host.nil?
|
|
54
|
+
host = context.create_host(name)
|
|
55
|
+
else
|
|
56
|
+
emit(events, :host_exists, name: name)
|
|
57
|
+
groups_dataset = host.groups_dataset
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
emit(events, :ok, indent: 4)
|
|
61
|
+
[host, groups_dataset]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def add_group_association(host, host_name, group_name, groups_dataset, events)
|
|
65
|
+
return if group_name.nil? || group_name.empty?
|
|
66
|
+
|
|
67
|
+
emit(events, :adding_association, host: host_name, group: group_name)
|
|
68
|
+
group = find_or_create_group(group_name, events)
|
|
69
|
+
|
|
70
|
+
if association_exists?(groups_dataset, group_name)
|
|
71
|
+
emit(events, :association_exists, host: host_name, group: group_name)
|
|
72
|
+
else
|
|
73
|
+
host.add_group(group)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
emit(events, :ok, indent: 4)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def find_or_create_group(name, events)
|
|
80
|
+
group = context.find_group(name)
|
|
81
|
+
return group unless group.nil?
|
|
82
|
+
|
|
83
|
+
emit(events, :group_missing_created, name: name)
|
|
84
|
+
context.create_group(name)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def add_automatic_group_if_needed(host, host_name, events)
|
|
88
|
+
groups_dataset = host.groups_dataset
|
|
89
|
+
return if groups_dataset.nil? || groups_dataset.any?
|
|
90
|
+
|
|
91
|
+
emit(events, :adding_automatic_group, host: host_name, group: AUTOMATIC_GROUP)
|
|
92
|
+
host.add_group(automatic_group)
|
|
93
|
+
emit(events, :ok, indent: 4)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def automatic_group
|
|
97
|
+
context.automatic_group
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def association_exists?(dataset, name)
|
|
101
|
+
!dataset.nil? && !dataset[name: name].nil?
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def emit(events, type, payload = {})
|
|
105
|
+
events << Event.new(type: type, payload: payload)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'group_cleanup'
|
|
4
|
+
|
|
5
|
+
module Moose
|
|
6
|
+
module Inventory
|
|
7
|
+
module Operations
|
|
8
|
+
class GroupChildRelations
|
|
9
|
+
Event = Struct.new(:type, :payload, keyword_init: true)
|
|
10
|
+
Result = Struct.new(:events, :warning_count, keyword_init: true)
|
|
11
|
+
|
|
12
|
+
def initialize(context:)
|
|
13
|
+
@context = context
|
|
14
|
+
@cleanup = Moose::Inventory::Operations::GroupCleanup.new(
|
|
15
|
+
context: context,
|
|
16
|
+
emitter: method(:emit)
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def add_children(parent_group:, parent_name:, child_names:)
|
|
21
|
+
events = []
|
|
22
|
+
warning_count = 0
|
|
23
|
+
children_dataset = parent_group.children_dataset
|
|
24
|
+
|
|
25
|
+
child_names.each do |child_name|
|
|
26
|
+
next if child_name.nil? || child_name.empty?
|
|
27
|
+
|
|
28
|
+
warning_count += add_child(parent_group, parent_name, child_name, children_dataset, events)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
Result.new(events: events, warning_count: warning_count)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def remove_children(parent_group:, parent_name:, child_names:, delete_orphans: false)
|
|
35
|
+
events = []
|
|
36
|
+
warning_count = 0
|
|
37
|
+
children_dataset = parent_group.children_dataset
|
|
38
|
+
|
|
39
|
+
child_names.each do |child_name|
|
|
40
|
+
next if child_name.nil? || child_name.empty?
|
|
41
|
+
|
|
42
|
+
warning_count += remove_child(
|
|
43
|
+
{
|
|
44
|
+
parent_group: parent_group,
|
|
45
|
+
parent_name: parent_name,
|
|
46
|
+
child_name: child_name,
|
|
47
|
+
children_dataset: children_dataset,
|
|
48
|
+
events: events,
|
|
49
|
+
delete_orphans: delete_orphans
|
|
50
|
+
}
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
Result.new(events: events, warning_count: warning_count)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
attr_reader :cleanup, :context
|
|
60
|
+
|
|
61
|
+
def add_child(parent_group, parent_name, child_name, children_dataset, events)
|
|
62
|
+
emit(events, :adding_child_association, parent: parent_name, child: child_name)
|
|
63
|
+
|
|
64
|
+
if association_exists?(children_dataset, child_name)
|
|
65
|
+
emit(events, :child_association_exists, parent: parent_name, child: child_name)
|
|
66
|
+
emit(events, :already_exists_skipping, indent: 4)
|
|
67
|
+
emit(events, :ok, indent: 4)
|
|
68
|
+
return 1
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
child_group = context.find_group(child_name)
|
|
72
|
+
warning_count = 0
|
|
73
|
+
if child_group.nil?
|
|
74
|
+
emit(events, :child_group_missing, name: child_name)
|
|
75
|
+
emit(events, :child_group_creating_now, name: child_name)
|
|
76
|
+
child_group = context.create_group(child_name)
|
|
77
|
+
emit(events, :ok, indent: 6)
|
|
78
|
+
warning_count = 1
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
parent_group.add_child(child_group)
|
|
82
|
+
emit(events, :ok, indent: 4)
|
|
83
|
+
warning_count
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def remove_child(input)
|
|
87
|
+
emit(
|
|
88
|
+
input[:events],
|
|
89
|
+
:removing_child_association,
|
|
90
|
+
parent: input[:parent_name],
|
|
91
|
+
child: input[:child_name]
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
unless association_exists?(input[:children_dataset], input[:child_name])
|
|
95
|
+
emit(input[:events], :child_association_missing, parent: input[:parent_name], child: input[:child_name])
|
|
96
|
+
emit(input[:events], :missing_skipping, indent: 4)
|
|
97
|
+
emit(input[:events], :ok, indent: 4)
|
|
98
|
+
return 1
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
child_group = context.find_group(input[:child_name])
|
|
102
|
+
input[:parent_group].remove_child(child_group)
|
|
103
|
+
emit(input[:events], :ok, indent: 4)
|
|
104
|
+
cleanup.delete_orphaned_group(child_group, input[:events]) if input[:delete_orphans]
|
|
105
|
+
0
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def association_exists?(dataset, name)
|
|
109
|
+
!dataset.nil? && !dataset[name: name].nil?
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def emit(events, type, payload = {})
|
|
113
|
+
events << Event.new(type: type, payload: payload)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moose
|
|
4
|
+
module Inventory
|
|
5
|
+
module Operations
|
|
6
|
+
# Recursively cleans up orphaned groups and their dependent relations.
|
|
7
|
+
class GroupCleanup
|
|
8
|
+
AUTOMATIC_GROUP = 'ungrouped'
|
|
9
|
+
|
|
10
|
+
def initialize(context:, emitter:)
|
|
11
|
+
@context = context
|
|
12
|
+
@emitter = emitter
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def delete_orphaned_group(group, events)
|
|
16
|
+
return if group.name == AUTOMATIC_GROUP
|
|
17
|
+
return unless group.parents_dataset.none?
|
|
18
|
+
|
|
19
|
+
emit(events, :recursively_delete_orphaned_group, name: group.name)
|
|
20
|
+
group.children_dataset.each do |child|
|
|
21
|
+
emit(events, :removing_recursive_child_association, parent: group.name, child: child.name)
|
|
22
|
+
group.remove_child(child)
|
|
23
|
+
emit(events, :ok, indent: 6)
|
|
24
|
+
delete_orphaned_group(child, events)
|
|
25
|
+
end
|
|
26
|
+
destroy_group(group, events, indent: 4)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def destroy_group(group, events, indent:)
|
|
30
|
+
group.hosts_dataset.each do |host|
|
|
31
|
+
next unless host.groups_dataset.one?
|
|
32
|
+
|
|
33
|
+
emit(events, :adding_automatic_group_to_host, host: host[:name], indent: indent)
|
|
34
|
+
host.add_group(context.automatic_group)
|
|
35
|
+
emit(events, :ok, indent: indent + 2)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
emit(events, :destroying_group, name: group.name, indent: indent)
|
|
39
|
+
group.remove_all_groupvars
|
|
40
|
+
group.remove_all_hosts
|
|
41
|
+
group.destroy
|
|
42
|
+
emit(events, :ok, indent: indent + 2)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
attr_reader :context, :emitter
|
|
48
|
+
|
|
49
|
+
def emit(events, type, payload = {})
|
|
50
|
+
emitter.call(events, type, payload)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moose
|
|
4
|
+
module Inventory
|
|
5
|
+
module Operations
|
|
6
|
+
# Removes host/group associations for existing primary entities.
|
|
7
|
+
class RemoveAssociations
|
|
8
|
+
AUTOMATIC_GROUP = 'ungrouped'
|
|
9
|
+
Event = Struct.new(:type, :payload, keyword_init: true)
|
|
10
|
+
Result = Struct.new(:events, :warning_count, keyword_init: true)
|
|
11
|
+
|
|
12
|
+
def initialize(context:)
|
|
13
|
+
@context = context
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def host_from_groups(host:, host_name:, group_names:)
|
|
17
|
+
events = []
|
|
18
|
+
warning_count = 0
|
|
19
|
+
|
|
20
|
+
group_names.each do |group_name|
|
|
21
|
+
next if group_name.nil? || group_name.empty?
|
|
22
|
+
|
|
23
|
+
warning_count += remove_group_from_host(host, host_name, group_name, events)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
add_automatic_group_if_needed(host, host_name, events)
|
|
27
|
+
|
|
28
|
+
Result.new(events: events, warning_count: warning_count)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def group_from_hosts(group:, group_name:, host_names:)
|
|
32
|
+
events = []
|
|
33
|
+
warning_count = 0
|
|
34
|
+
hosts_dataset = group.hosts_dataset
|
|
35
|
+
|
|
36
|
+
host_names.each do |host_name|
|
|
37
|
+
next if host_name.nil? || host_name.empty?
|
|
38
|
+
|
|
39
|
+
warning_count += remove_host_from_group(group, group_name, host_name, hosts_dataset, events)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
Result.new(events: events, warning_count: warning_count)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
attr_reader :context
|
|
48
|
+
|
|
49
|
+
def remove_group_from_host(host, host_name, group_name, events)
|
|
50
|
+
groups_dataset = host.groups_dataset
|
|
51
|
+
emit(events, :removing_host_group_association, host: host_name, group: group_name)
|
|
52
|
+
|
|
53
|
+
unless association_exists?(groups_dataset, group_name)
|
|
54
|
+
emit(events, :host_group_association_missing, host: host_name, group: group_name)
|
|
55
|
+
emit(events, :missing_skipping, indent: 4)
|
|
56
|
+
emit(events, :ok, indent: 4)
|
|
57
|
+
return 1
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
group = context.find_group(group_name)
|
|
61
|
+
host.remove_group(group) unless group.nil?
|
|
62
|
+
emit(events, :ok, indent: 4)
|
|
63
|
+
0
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def remove_host_from_group(group, group_name, host_name, hosts_dataset, events)
|
|
67
|
+
emit(events, :removing_group_host_association, group: group_name, host: host_name)
|
|
68
|
+
|
|
69
|
+
unless association_exists?(hosts_dataset, host_name)
|
|
70
|
+
emit(events, :group_host_association_missing, group: group_name, host: host_name)
|
|
71
|
+
emit(events, :missing_skipping, indent: 4)
|
|
72
|
+
emit(events, :ok, indent: 4)
|
|
73
|
+
return 1
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
host = context.find_host(host_name)
|
|
77
|
+
group.remove_host(host) unless host.nil?
|
|
78
|
+
emit(events, :ok, indent: 4)
|
|
79
|
+
add_automatic_group_if_needed(host, host_name, events)
|
|
80
|
+
0
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def add_automatic_group_if_needed(host, host_name, events)
|
|
84
|
+
return unless host.groups_dataset.none?
|
|
85
|
+
|
|
86
|
+
emit(events, :adding_automatic_group, host: host_name)
|
|
87
|
+
host.add_group(context.automatic_group)
|
|
88
|
+
emit(events, :ok, indent: 4)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def association_exists?(dataset, name)
|
|
92
|
+
!dataset.nil? && !dataset[name: name].nil?
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def emit(events, type, payload = {})
|
|
96
|
+
events << Event.new(type: type, payload: payload)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'group_cleanup'
|
|
4
|
+
|
|
5
|
+
module Moose
|
|
6
|
+
module Inventory
|
|
7
|
+
module Operations
|
|
8
|
+
# Removes top-level groups and their direct associations.
|
|
9
|
+
class RemoveGroups
|
|
10
|
+
Event = Struct.new(:type, :payload, keyword_init: true)
|
|
11
|
+
Result = Struct.new(:events, :warning_count, keyword_init: true)
|
|
12
|
+
|
|
13
|
+
def initialize(context:)
|
|
14
|
+
@context = context
|
|
15
|
+
@cleanup = Moose::Inventory::Operations::GroupCleanup.new(
|
|
16
|
+
context: context,
|
|
17
|
+
emitter: method(:emit)
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def call(names:, recursive: false)
|
|
22
|
+
events = []
|
|
23
|
+
warning_count = 0
|
|
24
|
+
|
|
25
|
+
names.each do |name|
|
|
26
|
+
warning_count += remove_group(name, events, recursive: recursive)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
Result.new(events: events, warning_count: warning_count)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
attr_reader :cleanup, :context
|
|
35
|
+
|
|
36
|
+
def remove_group(name, events, recursive:)
|
|
37
|
+
emit(events, :group_started, name: name)
|
|
38
|
+
emit(events, :retrieving_group, name: name)
|
|
39
|
+
group = context.find_group(name)
|
|
40
|
+
|
|
41
|
+
if group.nil?
|
|
42
|
+
emit(events, :group_missing, name: name)
|
|
43
|
+
emit(events, :ok, indent: 4)
|
|
44
|
+
emit(events, :group_complete)
|
|
45
|
+
return 1
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
emit(events, :ok, indent: 4)
|
|
49
|
+
remove_parent_associations(group, name, events)
|
|
50
|
+
remove_child_associations(group, name, events, recursive: recursive)
|
|
51
|
+
cleanup.destroy_group(group, events, indent: 2)
|
|
52
|
+
emit(events, :group_complete)
|
|
53
|
+
0
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def remove_parent_associations(group, name, events)
|
|
57
|
+
group.parents_dataset.each do |parent|
|
|
58
|
+
emit(events, :removing_parent_association, group: name, related_group: parent.name)
|
|
59
|
+
parent.remove_child(group)
|
|
60
|
+
emit(events, :ok, indent: 4)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def remove_child_associations(group, name, events, recursive:)
|
|
65
|
+
group.children_dataset.each do |child|
|
|
66
|
+
emit(events, :removing_child_association, group: name, related_group: child.name)
|
|
67
|
+
group.remove_child(child)
|
|
68
|
+
emit(events, :ok, indent: 4)
|
|
69
|
+
cleanup.delete_orphaned_group(child, events) if recursive
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def emit(events, type, payload = {})
|
|
74
|
+
events << Event.new(type: type, payload: payload)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|