moose-inventory 2.0 → 2.1
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/release.yml +2 -0
- data/.gitignore +2 -1
- data/.rubocop.yml +21 -0
- data/BACKLOG.md +630 -8
- data/Gemfile +2 -0
- data/Gemfile.lock +1 -1
- data/README.md +315 -39
- data/Rakefile +2 -0
- data/bin/moose-inventory +2 -1
- data/docs/architecture/architecture-and-trust-boundaries.md +444 -0
- data/docs/compatibility/cli-output-compatibility.md +76 -0
- data/docs/governance/approval-register.md +37 -0
- data/docs/maintenance/database-backup-restore-guidance.md +162 -0
- data/docs/maintenance/package-maintenance-and-agent-boundaries.md +260 -0
- data/docs/process/conformance-gap-analysis-2026-05-28.md +192 -0
- data/docs/product/product-brief.md +161 -0
- data/docs/product/requirements-baseline.md +477 -0
- data/docs/qa/qa-documentation-and-release-gates.md +283 -0
- data/docs/release/package-provenance-hardening.md +126 -0
- data/docs/release/publishing.md +11 -3
- data/docs/release/release-environment-protection.md +70 -0
- data/docs/release/release-readiness.md +23 -4
- data/docs/security/accepted-risk-register.md +84 -0
- data/docs/security/security-privacy-process.md +287 -0
- data/docs/security-audit-2026-05-26-rerun.md +2 -2
- data/docs/ux/cli-workflow-notes.md +287 -0
- data/examples/ansible/ansible.cfg +3 -0
- data/examples/ansible/inventory/moose_inventory.yml +5 -0
- data/examples/ansible/inventory_plugins/moose_inventory.py +100 -0
- data/examples/ci/README.md +16 -0
- data/examples/ci/github-actions/inventory-review.yml +38 -0
- data/examples/ci/inventory/example-snapshot.yml +19 -0
- data/examples/ci/scripts/validate-inventory-snapshot.sh +30 -0
- data/lib/moose_inventory/cli/application.rb +133 -5
- data/lib/moose_inventory/cli/association_rendering.rb +74 -0
- data/lib/moose_inventory/cli/association_rendering_support.rb +89 -0
- data/lib/moose_inventory/cli/audit.rb +62 -0
- data/lib/moose_inventory/cli/audit_recording.rb +40 -0
- data/lib/moose_inventory/cli/child_relation_rendering.rb +110 -0
- data/lib/moose_inventory/cli/console.rb +135 -0
- data/lib/moose_inventory/cli/db.rb +64 -0
- data/lib/moose_inventory/cli/factory.rb +28 -0
- data/lib/moose_inventory/cli/formatter.rb +8 -12
- data/lib/moose_inventory/cli/group.rb +5 -2
- data/lib/moose_inventory/cli/group_add.rb +11 -9
- data/lib/moose_inventory/cli/group_addchild.rb +23 -65
- data/lib/moose_inventory/cli/group_addhost.rb +16 -67
- data/lib/moose_inventory/cli/group_addvar.rb +27 -47
- data/lib/moose_inventory/cli/group_get.rb +8 -42
- data/lib/moose_inventory/cli/group_list.rb +7 -40
- data/lib/moose_inventory/cli/group_listvars.rb +9 -55
- data/lib/moose_inventory/cli/group_rm.rb +12 -10
- data/lib/moose_inventory/cli/group_rmchild.rb +26 -82
- data/lib/moose_inventory/cli/group_rmhost.rb +18 -53
- data/lib/moose_inventory/cli/group_rmvar.rb +30 -41
- data/lib/moose_inventory/cli/group_tags.rb +33 -0
- data/lib/moose_inventory/cli/helpers.rb +68 -1
- data/lib/moose_inventory/cli/host.rb +6 -3
- data/lib/moose_inventory/cli/host_add.rb +69 -29
- data/lib/moose_inventory/cli/host_addgroup.rb +22 -58
- data/lib/moose_inventory/cli/host_addvar.rb +28 -52
- data/lib/moose_inventory/cli/host_get.rb +9 -37
- data/lib/moose_inventory/cli/host_list.rb +24 -21
- data/lib/moose_inventory/cli/host_listvars.rb +9 -62
- data/lib/moose_inventory/cli/host_rm.rb +60 -42
- data/lib/moose_inventory/cli/host_rmgroup.rb +25 -44
- data/lib/moose_inventory/cli/host_rmvar.rb +31 -45
- data/lib/moose_inventory/cli/host_tags.rb +33 -0
- data/lib/moose_inventory/cli/listvars_support.rb +55 -0
- data/lib/moose_inventory/cli/plan_rendering.rb +50 -0
- data/lib/moose_inventory/cli/relation_transaction_support.rb +51 -0
- data/lib/moose_inventory/cli/tag_support.rb +97 -0
- data/lib/moose_inventory/cli/variable_rendering.rb +67 -0
- data/lib/moose_inventory/config/config.rb +185 -108
- data/lib/moose_inventory/db/db.rb +170 -195
- data/lib/moose_inventory/db/exceptions.rb +6 -3
- data/lib/moose_inventory/db/models.rb +16 -0
- data/lib/moose_inventory/db/schema_migrations.rb +248 -0
- data/lib/moose_inventory/inventory_context.rb +68 -2
- data/lib/moose_inventory/operations/add_associations.rb +20 -16
- data/lib/moose_inventory/operations/add_groups.rb +21 -13
- data/lib/moose_inventory/operations/add_hosts.rb +30 -17
- data/lib/moose_inventory/operations/add_variables.rb +77 -0
- data/lib/moose_inventory/operations/entity_variable_operation_support.rb +46 -0
- data/lib/moose_inventory/operations/group_child_relations.rb +23 -16
- data/lib/moose_inventory/operations/group_cleanup.rb +23 -8
- data/lib/moose_inventory/operations/import_inventory_snapshot.rb +41 -0
- data/lib/moose_inventory/operations/inventory_doctor.rb +172 -0
- data/lib/moose_inventory/operations/inventory_snapshot.rb +60 -0
- data/lib/moose_inventory/operations/inventory_snapshot_applier.rb +112 -0
- data/lib/moose_inventory/operations/inventory_snapshot_preview.rb +174 -0
- data/lib/moose_inventory/operations/inventory_snapshot_validator.rb +134 -0
- data/lib/moose_inventory/operations/operation_event_support.rb +27 -0
- data/lib/moose_inventory/operations/query_inventory/base_query.rb +24 -0
- data/lib/moose_inventory/operations/query_inventory/group_queries.rb +86 -0
- data/lib/moose_inventory/operations/query_inventory/host_queries.rb +106 -0
- data/lib/moose_inventory/operations/query_inventory.rb +47 -0
- data/lib/moose_inventory/operations/remove_associations.rb +30 -18
- data/lib/moose_inventory/operations/remove_groups.rb +12 -12
- data/lib/moose_inventory/operations/remove_hosts.rb +68 -0
- data/lib/moose_inventory/operations/remove_variables.rb +67 -0
- data/lib/moose_inventory/runtime_options.rb +31 -0
- data/lib/moose_inventory/version.rb +3 -1
- data/lib/moose_inventory.rb +10 -7
- data/moose-inventory.gemspec +19 -35
- data/scripts/check.sh +1 -0
- data/scripts/ci/check_generated_artifacts.sh +41 -0
- data/scripts/ci/check_permissions.sh +2 -0
- data/scripts/ci/check_rubocop.sh +30 -25
- data/scripts/files.rb +5 -4
- data/spec/examples/ci_examples_spec.rb +37 -0
- data/spec/lib/moose_inventory/ansible_plugin_examples_spec.rb +29 -0
- data/spec/lib/moose_inventory/cli/application_doctor_spec.rb +50 -0
- data/spec/lib/moose_inventory/cli/application_import_export_spec.rb +100 -0
- data/spec/lib/moose_inventory/cli/application_spec.rb +25 -15
- data/spec/lib/moose_inventory/cli/audit_spec.rb +56 -0
- data/spec/lib/moose_inventory/cli/cli_spec.rb +15 -19
- data/spec/lib/moose_inventory/cli/console_spec.rb +98 -0
- data/spec/lib/moose_inventory/cli/factory_spec.rb +27 -0
- data/spec/lib/moose_inventory/cli/formatter_spec.rb +95 -3
- data/spec/lib/moose_inventory/cli/group_add_spec.rb +140 -116
- data/spec/lib/moose_inventory/cli/group_addchild_spec.rb +89 -35
- data/spec/lib/moose_inventory/cli/group_addhost_spec.rb +81 -84
- data/spec/lib/moose_inventory/cli/group_addvar_spec.rb +65 -68
- data/spec/lib/moose_inventory/cli/group_get_spec.rb +17 -33
- data/spec/lib/moose_inventory/cli/group_list_spec.rb +16 -38
- data/spec/lib/moose_inventory/cli/group_listvar_spec.rb +33 -40
- data/spec/lib/moose_inventory/cli/group_rm_spec.rb +136 -96
- data/spec/lib/moose_inventory/cli/group_rmchild_spec.rb +66 -41
- data/spec/lib/moose_inventory/cli/group_rmhost_spec.rb +76 -78
- data/spec/lib/moose_inventory/cli/group_rmvar_spec.rb +57 -63
- data/spec/lib/moose_inventory/cli/group_spec.rb +2 -0
- data/spec/lib/moose_inventory/cli/helpers_spec.rb +146 -0
- data/spec/lib/moose_inventory/cli/host_add_spec.rb +170 -116
- data/spec/lib/moose_inventory/cli/host_addgroup_spec.rb +100 -83
- data/spec/lib/moose_inventory/cli/host_addvar_spec.rb +92 -74
- data/spec/lib/moose_inventory/cli/host_get_spec.rb +14 -33
- data/spec/lib/moose_inventory/cli/host_list_spec.rb +41 -33
- data/spec/lib/moose_inventory/cli/host_listvar_spec.rb +45 -53
- data/spec/lib/moose_inventory/cli/host_rm_spec.rb +66 -48
- data/spec/lib/moose_inventory/cli/host_rmgroup_spec.rb +73 -83
- data/spec/lib/moose_inventory/cli/host_rmvar_spec.rb +56 -63
- data/spec/lib/moose_inventory/cli/host_spec.rb +2 -0
- data/spec/lib/moose_inventory/cli/tags_spec.rb +81 -0
- data/spec/lib/moose_inventory/config/config_spec.rb +41 -3
- data/spec/lib/moose_inventory/db/db_spec.rb +396 -36
- data/spec/lib/moose_inventory/db/exceptions_spec.rb +18 -0
- data/spec/lib/moose_inventory/db/models_spec.rb +7 -3
- data/spec/lib/moose_inventory/db_lifecycle_spec.rb +73 -0
- data/spec/lib/moose_inventory/inventory_context_spec.rb +10 -0
- data/spec/lib/moose_inventory/operations/add_associations_spec.rb +34 -0
- data/spec/lib/moose_inventory/operations/add_groups_spec.rb +15 -0
- data/spec/lib/moose_inventory/operations/add_hosts_spec.rb +13 -0
- data/spec/lib/moose_inventory/operations/add_variables_spec.rb +103 -0
- data/spec/lib/moose_inventory/operations/group_child_relations_spec.rb +46 -0
- data/spec/lib/moose_inventory/operations/import_inventory_snapshot_spec.rb +226 -0
- data/spec/lib/moose_inventory/operations/inventory_doctor_spec.rb +77 -0
- data/spec/lib/moose_inventory/operations/inventory_snapshot_spec.rb +50 -0
- data/spec/lib/moose_inventory/operations/operation_event_support_spec.rb +78 -0
- data/spec/lib/moose_inventory/operations/query_inventory_spec.rb +146 -0
- data/spec/lib/moose_inventory/operations/remove_associations_spec.rb +35 -0
- data/spec/lib/moose_inventory/operations/remove_groups_spec.rb +21 -0
- data/spec/lib/moose_inventory/operations/remove_hosts_spec.rb +55 -0
- data/spec/lib/moose_inventory/operations/remove_variables_spec.rb +83 -0
- data/spec/shared/shared_config_setup.rb +4 -3
- data/spec/spec_helper.rb +50 -40
- data/spec/support/cli_harness.rb +33 -0
- metadata +80 -41
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'operation_event_support'
|
|
4
|
+
|
|
3
5
|
module Moose
|
|
4
6
|
module Inventory
|
|
5
7
|
module Operations
|
|
6
8
|
# Removes host/group associations for existing primary entities.
|
|
7
9
|
class RemoveAssociations
|
|
8
10
|
AUTOMATIC_GROUP = 'ungrouped'
|
|
9
|
-
|
|
10
|
-
Result = Struct.new(:events, :warning_count, keyword_init: true)
|
|
11
|
+
include OperationEventSupport
|
|
11
12
|
|
|
12
13
|
def initialize(context:)
|
|
13
14
|
@context = context
|
|
14
15
|
end
|
|
15
16
|
|
|
16
|
-
def host_from_groups(host:, host_name:, group_names:)
|
|
17
|
+
def host_from_groups(host:, host_name:, group_names:, dry_run: false)
|
|
17
18
|
events = []
|
|
18
19
|
warning_count = 0
|
|
20
|
+
@dry_run = dry_run
|
|
19
21
|
|
|
20
22
|
group_names.each do |group_name|
|
|
21
23
|
next if group_name.nil? || group_name.empty?
|
|
@@ -23,14 +25,17 @@ module Moose
|
|
|
23
25
|
warning_count += remove_group_from_host(host, host_name, group_name, events)
|
|
24
26
|
end
|
|
25
27
|
|
|
26
|
-
add_automatic_group_if_needed(host, host_name, events
|
|
28
|
+
add_automatic_group_if_needed(host, host_name, events,
|
|
29
|
+
planned_empty: planned_host_groups_empty?(host, group_names))
|
|
30
|
+
emit(events, :dry_run_summary) if dry_run
|
|
27
31
|
|
|
28
|
-
|
|
32
|
+
operation_result(events: events, warning_count: warning_count)
|
|
29
33
|
end
|
|
30
34
|
|
|
31
|
-
def group_from_hosts(group:, group_name:, host_names:)
|
|
35
|
+
def group_from_hosts(group:, group_name:, host_names:, dry_run: false)
|
|
32
36
|
events = []
|
|
33
37
|
warning_count = 0
|
|
38
|
+
@dry_run = dry_run
|
|
34
39
|
hosts_dataset = group.hosts_dataset
|
|
35
40
|
|
|
36
41
|
host_names.each do |host_name|
|
|
@@ -39,12 +44,14 @@ module Moose
|
|
|
39
44
|
warning_count += remove_host_from_group(group, group_name, host_name, hosts_dataset, events)
|
|
40
45
|
end
|
|
41
46
|
|
|
42
|
-
|
|
47
|
+
emit(events, :dry_run_summary) if dry_run
|
|
48
|
+
|
|
49
|
+
operation_result(events: events, warning_count: warning_count)
|
|
43
50
|
end
|
|
44
51
|
|
|
45
52
|
private
|
|
46
53
|
|
|
47
|
-
attr_reader :context
|
|
54
|
+
attr_reader :context, :dry_run
|
|
48
55
|
|
|
49
56
|
def remove_group_from_host(host, host_name, group_name, events)
|
|
50
57
|
groups_dataset = host.groups_dataset
|
|
@@ -58,7 +65,7 @@ module Moose
|
|
|
58
65
|
end
|
|
59
66
|
|
|
60
67
|
group = context.find_group(group_name)
|
|
61
|
-
host.remove_group(group) unless group.nil?
|
|
68
|
+
host.remove_group(group) unless group.nil? || dry_run
|
|
62
69
|
emit(events, :ok, indent: 4)
|
|
63
70
|
0
|
|
64
71
|
end
|
|
@@ -74,26 +81,31 @@ module Moose
|
|
|
74
81
|
end
|
|
75
82
|
|
|
76
83
|
host = context.find_host(host_name)
|
|
77
|
-
group.remove_host(host) unless host.nil?
|
|
84
|
+
group.remove_host(host) unless host.nil? || dry_run
|
|
78
85
|
emit(events, :ok, indent: 4)
|
|
79
|
-
add_automatic_group_if_needed(host, host_name, events
|
|
86
|
+
add_automatic_group_if_needed(host, host_name, events,
|
|
87
|
+
planned_empty: dry_run && !host.nil? && host.groups_dataset.one?)
|
|
80
88
|
0
|
|
81
89
|
end
|
|
82
90
|
|
|
83
|
-
def add_automatic_group_if_needed(host, host_name, events)
|
|
84
|
-
return
|
|
91
|
+
def add_automatic_group_if_needed(host, host_name, events, planned_empty: false)
|
|
92
|
+
return if host.nil?
|
|
93
|
+
return unless planned_empty || host.groups_dataset.none?
|
|
85
94
|
|
|
86
95
|
emit(events, :adding_automatic_group, host: host_name)
|
|
87
|
-
host.add_group(context.automatic_group)
|
|
96
|
+
host.add_group(context.automatic_group) unless dry_run
|
|
88
97
|
emit(events, :ok, indent: 4)
|
|
89
98
|
end
|
|
90
99
|
|
|
91
|
-
def
|
|
92
|
-
|
|
100
|
+
def planned_host_groups_empty?(host, group_names)
|
|
101
|
+
return false unless dry_run
|
|
102
|
+
|
|
103
|
+
remaining = host.groups_dataset.map(&:name) - group_names
|
|
104
|
+
remaining.empty?
|
|
93
105
|
end
|
|
94
106
|
|
|
95
|
-
def
|
|
96
|
-
|
|
107
|
+
def association_exists?(dataset, name)
|
|
108
|
+
!dataset.nil? && !dataset[name: name].nil?
|
|
97
109
|
end
|
|
98
110
|
end
|
|
99
111
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'operation_event_support'
|
|
4
|
+
|
|
3
5
|
require_relative 'group_cleanup'
|
|
4
6
|
|
|
5
7
|
module Moose
|
|
@@ -7,8 +9,7 @@ module Moose
|
|
|
7
9
|
module Operations
|
|
8
10
|
# Removes top-level groups and their direct associations.
|
|
9
11
|
class RemoveGroups
|
|
10
|
-
|
|
11
|
-
Result = Struct.new(:events, :warning_count, keyword_init: true)
|
|
12
|
+
include OperationEventSupport
|
|
12
13
|
|
|
13
14
|
def initialize(context:)
|
|
14
15
|
@context = context
|
|
@@ -18,20 +19,23 @@ module Moose
|
|
|
18
19
|
)
|
|
19
20
|
end
|
|
20
21
|
|
|
21
|
-
def call(names:, recursive: false)
|
|
22
|
+
def call(names:, recursive: false, dry_run: false)
|
|
22
23
|
events = []
|
|
23
24
|
warning_count = 0
|
|
25
|
+
@dry_run = dry_run
|
|
26
|
+
cleanup.dry_run = dry_run
|
|
24
27
|
|
|
25
28
|
names.each do |name|
|
|
26
29
|
warning_count += remove_group(name, events, recursive: recursive)
|
|
27
30
|
end
|
|
31
|
+
emit(events, :dry_run_summary) if dry_run
|
|
28
32
|
|
|
29
|
-
|
|
33
|
+
operation_result(events: events, warning_count: warning_count)
|
|
30
34
|
end
|
|
31
35
|
|
|
32
36
|
private
|
|
33
37
|
|
|
34
|
-
attr_reader :cleanup, :context
|
|
38
|
+
attr_reader :cleanup, :context, :dry_run
|
|
35
39
|
|
|
36
40
|
def remove_group(name, events, recursive:)
|
|
37
41
|
emit(events, :group_started, name: name)
|
|
@@ -56,7 +60,7 @@ module Moose
|
|
|
56
60
|
def remove_parent_associations(group, name, events)
|
|
57
61
|
group.parents_dataset.each do |parent|
|
|
58
62
|
emit(events, :removing_parent_association, group: name, related_group: parent.name)
|
|
59
|
-
parent.remove_child(group)
|
|
63
|
+
parent.remove_child(group) unless dry_run
|
|
60
64
|
emit(events, :ok, indent: 4)
|
|
61
65
|
end
|
|
62
66
|
end
|
|
@@ -64,15 +68,11 @@ module Moose
|
|
|
64
68
|
def remove_child_associations(group, name, events, recursive:)
|
|
65
69
|
group.children_dataset.each do |child|
|
|
66
70
|
emit(events, :removing_child_association, group: name, related_group: child.name)
|
|
67
|
-
group.remove_child(child)
|
|
71
|
+
group.remove_child(child) unless dry_run
|
|
68
72
|
emit(events, :ok, indent: 4)
|
|
69
|
-
cleanup.delete_orphaned_group(child, events) if recursive
|
|
73
|
+
cleanup.delete_orphaned_group(child, events, ignored_parent: group) if recursive
|
|
70
74
|
end
|
|
71
75
|
end
|
|
72
|
-
|
|
73
|
-
def emit(events, type, payload = {})
|
|
74
|
-
events << Event.new(type: type, payload: payload)
|
|
75
|
-
end
|
|
76
76
|
end
|
|
77
77
|
end
|
|
78
78
|
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'operation_event_support'
|
|
4
|
+
|
|
5
|
+
module Moose
|
|
6
|
+
module Inventory
|
|
7
|
+
module Operations
|
|
8
|
+
# Removes hosts and returns structured progress/warning events.
|
|
9
|
+
class RemoveHosts
|
|
10
|
+
include OperationEventSupport
|
|
11
|
+
|
|
12
|
+
def initialize(context:)
|
|
13
|
+
@context = context
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def call(names:, dry_run: false)
|
|
17
|
+
events = []
|
|
18
|
+
warning_count = 0
|
|
19
|
+
@dry_run = dry_run
|
|
20
|
+
|
|
21
|
+
if dry_run
|
|
22
|
+
names.each do |name|
|
|
23
|
+
warning_count += remove_host(name, events)
|
|
24
|
+
end
|
|
25
|
+
emit(events, :dry_run_summary)
|
|
26
|
+
return operation_result(events: events, warning_count: warning_count)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
context.transaction do
|
|
30
|
+
names.each do |name|
|
|
31
|
+
warning_count += remove_host(name, events)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
operation_result(events: events, warning_count: warning_count)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
attr_reader :context, :dry_run
|
|
41
|
+
|
|
42
|
+
def remove_host(name, events)
|
|
43
|
+
emit(events, :host_started, name: name)
|
|
44
|
+
emit(events, :retrieving_host, name: name)
|
|
45
|
+
host = context.find_host(name)
|
|
46
|
+
|
|
47
|
+
if host.nil?
|
|
48
|
+
emit(events, :host_missing, name: name)
|
|
49
|
+
emit(events, :missing_skipping, indent: 4)
|
|
50
|
+
emit(events, :ok, indent: 4)
|
|
51
|
+
emit(events, :host_complete)
|
|
52
|
+
return 1
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
emit(events, :ok, indent: 4)
|
|
56
|
+
emit(events, :destroying_host, name: name)
|
|
57
|
+
unless dry_run
|
|
58
|
+
host.remove_all_groups
|
|
59
|
+
host.destroy
|
|
60
|
+
end
|
|
61
|
+
emit(events, :ok, indent: 4)
|
|
62
|
+
emit(events, :host_complete)
|
|
63
|
+
0
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'entity_variable_operation_support'
|
|
4
|
+
|
|
5
|
+
module Moose
|
|
6
|
+
module Inventory
|
|
7
|
+
module Operations
|
|
8
|
+
# Removes host/group variables by key.
|
|
9
|
+
class RemoveVariables
|
|
10
|
+
include EntityVariableOperationSupport
|
|
11
|
+
|
|
12
|
+
def call(name:, vars:, dry_run: false)
|
|
13
|
+
@events = []
|
|
14
|
+
@dry_run = dry_run
|
|
15
|
+
|
|
16
|
+
emit(:entity_started, name: name)
|
|
17
|
+
emit(:retrieving_entity, name: name)
|
|
18
|
+
entity = find_entity(name)
|
|
19
|
+
raise_missing_entity(name) if entity.nil?
|
|
20
|
+
|
|
21
|
+
emit(:ok, indent: 4)
|
|
22
|
+
|
|
23
|
+
dataset = entity.public_send("#{entity_type}vars_dataset")
|
|
24
|
+
vars.each do |variable|
|
|
25
|
+
remove_variable(entity, dataset, variable)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
emit(:entity_complete)
|
|
29
|
+
emit(:dry_run_summary) if dry_run
|
|
30
|
+
operation_result(events: events)
|
|
31
|
+
ensure
|
|
32
|
+
@events = nil
|
|
33
|
+
@dry_run = nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
attr_reader :dry_run
|
|
39
|
+
|
|
40
|
+
def remove_variable(entity, dataset, variable)
|
|
41
|
+
emit(:removing_variable, variable: variable)
|
|
42
|
+
key = parse_variable_name(variable)
|
|
43
|
+
|
|
44
|
+
existing = dataset[name: key]
|
|
45
|
+
unless existing.nil? || dry_run
|
|
46
|
+
entity.public_send("remove_#{entity_type}var", existing)
|
|
47
|
+
existing.destroy
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
emit(:ok, indent: 4)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def parse_variable_name(variable)
|
|
54
|
+
invalid = variable.start_with?('=') || variable.count('=') > 1
|
|
55
|
+
raise_invalid_variable(variable) if invalid
|
|
56
|
+
|
|
57
|
+
variable.split('=').first
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def raise_invalid_variable(variable)
|
|
61
|
+
raise context.moose_exception_class,
|
|
62
|
+
"Incorrect format in {#{variable}}. Expected 'key' or 'key=value'."
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moose
|
|
4
|
+
module Inventory
|
|
5
|
+
# Small value object for resolved runtime CLI options.
|
|
6
|
+
class RuntimeOptions
|
|
7
|
+
attr_reader :argv, :config, :env, :format
|
|
8
|
+
|
|
9
|
+
def initialize(argv:, config:, env:, format:, flags:)
|
|
10
|
+
@argv = argv
|
|
11
|
+
@config = config
|
|
12
|
+
@env = env
|
|
13
|
+
@format = format
|
|
14
|
+
@ansible = flags[:ansible] == true
|
|
15
|
+
@trace = flags[:trace] == true
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def ansible?
|
|
19
|
+
@ansible
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def trace?
|
|
23
|
+
@trace
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def output_format
|
|
27
|
+
format.to_s.downcase
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
data/lib/moose_inventory.rb
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'thor'
|
|
2
|
-
require_relative '
|
|
3
|
-
require_relative '
|
|
4
|
-
require_relative '
|
|
4
|
+
require_relative 'moose_inventory/config/config'
|
|
5
|
+
require_relative 'moose_inventory/db/db'
|
|
6
|
+
require_relative 'moose_inventory/cli/application'
|
|
5
7
|
|
|
6
8
|
module Moose
|
|
7
9
|
module Inventory
|
|
@@ -12,13 +14,14 @@ module Moose
|
|
|
12
14
|
extend self
|
|
13
15
|
# rubocop:enable Style/ModuleFunction
|
|
14
16
|
|
|
15
|
-
def start(args
|
|
17
|
+
def start(args, config: Moose::Inventory::Config, db: Moose::Inventory::DB,
|
|
18
|
+
application: Moose::Inventory::Cli::Application)
|
|
16
19
|
# initialization stuff.
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
config.init(args)
|
|
21
|
+
db.init
|
|
19
22
|
|
|
20
23
|
# Start the main application
|
|
21
|
-
|
|
24
|
+
application.start(config.application_args)
|
|
22
25
|
end
|
|
23
26
|
end
|
|
24
27
|
end
|
data/moose-inventory.gemspec
CHANGED
|
@@ -1,57 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# -*- mode: ruby -*-
|
|
2
4
|
# vi: set ft=ruby :
|
|
3
5
|
|
|
4
|
-
lib = File.expand_path('
|
|
6
|
+
lib = File.expand_path('lib', __dir__)
|
|
5
7
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
6
8
|
require 'moose_inventory/version'
|
|
7
9
|
|
|
10
|
+
# rubocop:disable Metrics/BlockLength
|
|
8
11
|
Gem::Specification.new do |spec|
|
|
9
12
|
spec.name = 'moose-inventory'
|
|
10
13
|
spec.version = Moose::Inventory::VERSION
|
|
11
14
|
spec.authors = ['Russell Davies']
|
|
12
15
|
spec.email = ['russell@blakemere.ca']
|
|
13
16
|
spec.summary = 'Moose-tools inventory manager'
|
|
14
|
-
|
|
15
|
-
spec.description = 'The Moosecastle CLI tool for Ansible-compatable dynamic inventory management.'
|
|
16
|
-
# rubocop:enable Metrics/LineLength
|
|
17
|
+
spec.description = 'The Moosecastle CLI tool for Ansible-compatible dynamic inventory management.'
|
|
17
18
|
spec.homepage = 'https://github.com/RusDavies/moose-inventory'
|
|
18
19
|
spec.license = 'MIT'
|
|
19
20
|
spec.required_ruby_version = '>= 3.2'
|
|
21
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
20
22
|
|
|
21
23
|
spec.files = `git ls-files -z`.split("\x0")
|
|
22
24
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
23
|
-
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
24
25
|
spec.require_paths = ['lib']
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
#
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
# spec.add_development_dependency 'bundler', '~> 1.7'
|
|
38
|
-
# spec.add_development_dependency 'coveralls', '~> 0.8'
|
|
39
|
-
# spec.add_development_dependency 'guard', '~> 2.12'
|
|
40
|
-
# spec.add_development_dependency 'guard-rspec', '~> 4.5'
|
|
41
|
-
# spec.add_development_dependency 'guard-rubocop', '~> 1.2'
|
|
42
|
-
# spec.add_development_dependency 'rake', '~> 10.1'
|
|
43
|
-
# spec.add_development_dependency 'rspec', '~>3.2'
|
|
44
|
-
# spec.add_development_dependency 'rubocop', '>= 0.19'
|
|
45
|
-
# spec.add_development_dependency 'simplecov', '~> 0.10'
|
|
46
|
-
|
|
47
|
-
spec.add_runtime_dependency 'indentation', '~> 0'
|
|
48
|
-
spec.add_runtime_dependency 'json', '>= 2.7', '< 3'
|
|
49
|
-
spec.add_runtime_dependency 'mysql2', '>= 0.5.7', '< 0.6'
|
|
50
|
-
spec.add_runtime_dependency 'pg', '>= 1.5', '< 2'
|
|
51
|
-
spec.add_runtime_dependency 'sequel', '>= 5.80', '< 6'
|
|
52
|
-
spec.add_runtime_dependency 'sqlite3', '>= 1.7', '< 3'
|
|
53
|
-
spec.add_runtime_dependency 'thor', '>= 1.3', '< 2'
|
|
54
|
-
|
|
27
|
+
spec.add_dependency 'indentation', '~> 0'
|
|
28
|
+
spec.add_dependency 'json', '>= 2.7', '< 3'
|
|
29
|
+
spec.add_dependency 'mysql2', '>= 0.5.7', '< 0.6'
|
|
30
|
+
spec.add_dependency 'pg', '>= 1.5', '< 2'
|
|
31
|
+
spec.add_dependency 'sequel', '>= 5.80', '< 6'
|
|
32
|
+
spec.add_dependency 'sqlite3', '>= 1.7', '< 3'
|
|
33
|
+
spec.add_dependency 'thor', '>= 1.3', '< 2'
|
|
34
|
+
|
|
35
|
+
# rubocop:disable Gemspec/DevelopmentDependencies
|
|
36
|
+
# Development dependencies intentionally remain here because this project uses
|
|
37
|
+
# `gemspec` as its Gemfile dependency source.
|
|
55
38
|
spec.add_development_dependency 'bundler', '>= 2.2.33', '< 3'
|
|
56
39
|
spec.add_development_dependency 'bundler-audit', '>= 0.9', '< 1'
|
|
57
40
|
spec.add_development_dependency 'parallel', '>= 1.10', '< 2.0'
|
|
@@ -59,5 +42,6 @@ Gem::Specification.new do |spec|
|
|
|
59
42
|
spec.add_development_dependency 'rspec', '~> 3'
|
|
60
43
|
spec.add_development_dependency 'rubocop', '>= 1.72', '< 2'
|
|
61
44
|
spec.add_development_dependency 'simplecov', '~> 0'
|
|
62
|
-
|
|
45
|
+
# rubocop:enable Gemspec/DevelopmentDependencies
|
|
63
46
|
end
|
|
47
|
+
# rubocop:enable Metrics/BlockLength
|
data/scripts/check.sh
CHANGED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Generated/local artifacts are useful during development but must not become
|
|
5
|
+
# source inputs, package contents, or review noise. Keep this list aligned with
|
|
6
|
+
# .gitignore and scanner excludes.
|
|
7
|
+
generated_paths=(
|
|
8
|
+
".openclaw-security-audit"
|
|
9
|
+
"coverage"
|
|
10
|
+
"pkg"
|
|
11
|
+
"spec/reports"
|
|
12
|
+
"tmp"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
tracked=()
|
|
16
|
+
for path in "${generated_paths[@]}"; do
|
|
17
|
+
while IFS= read -r file; do
|
|
18
|
+
tracked+=("$file")
|
|
19
|
+
done < <(git ls-files "$path" "$path/**")
|
|
20
|
+
done
|
|
21
|
+
|
|
22
|
+
if (( ${#tracked[@]} > 0 )); then
|
|
23
|
+
echo "Generated artifact paths are tracked and must be removed from source commits:" >&2
|
|
24
|
+
printf ' %s\n' "${tracked[@]}" >&2
|
|
25
|
+
exit 1
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
ignored_failures=()
|
|
29
|
+
for path in "${generated_paths[@]}"; do
|
|
30
|
+
if ! git check-ignore -q "$path/placeholder"; then
|
|
31
|
+
ignored_failures+=("$path/")
|
|
32
|
+
fi
|
|
33
|
+
done
|
|
34
|
+
|
|
35
|
+
if (( ${#ignored_failures[@]} > 0 )); then
|
|
36
|
+
echo "Generated artifact paths are not ignored:" >&2
|
|
37
|
+
printf ' %s\n' "${ignored_failures[@]}" >&2
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
echo "Generated artifact guard passed."
|
|
@@ -3,10 +3,12 @@ set -euo pipefail
|
|
|
3
3
|
|
|
4
4
|
allowed_executables=(
|
|
5
5
|
"bin/moose-inventory"
|
|
6
|
+
"examples/ci/scripts/validate-inventory-snapshot.sh"
|
|
6
7
|
"scripts/check.sh"
|
|
7
8
|
"scripts/ci/check_permissions.sh"
|
|
8
9
|
"scripts/ci/check_rubocop.sh"
|
|
9
10
|
"scripts/ci/check_secrets.sh"
|
|
11
|
+
"scripts/ci/check_generated_artifacts.sh"
|
|
10
12
|
"scripts/ci/check_security.sh"
|
|
11
13
|
"scripts/ci/install_security_tools.sh"
|
|
12
14
|
"scripts/ci/package_sanity.sh"
|
data/scripts/ci/check_rubocop.sh
CHANGED
|
@@ -1,28 +1,33 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
4
|
+
repo_root="$(git rev-parse --show-toplevel)"
|
|
5
|
+
cd "$repo_root"
|
|
6
|
+
|
|
7
|
+
rubocop_files=(
|
|
8
|
+
Gemfile
|
|
9
|
+
Rakefile
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
while IFS= read -r -d '' file; do
|
|
13
|
+
rubocop_files+=("$file")
|
|
14
|
+
done < <(find bin -maxdepth 1 -type f -print0 | sort -z)
|
|
15
|
+
|
|
16
|
+
while IFS= read -r -d '' file; do
|
|
17
|
+
rubocop_files+=("$file")
|
|
18
|
+
done < <(
|
|
19
|
+
find \
|
|
20
|
+
lib \
|
|
21
|
+
scripts \
|
|
22
|
+
spec \
|
|
23
|
+
-path 'spec/reports' -prune -o \
|
|
24
|
+
-path 'spec/reports/*' -prune -o \
|
|
25
|
+
-type f \( -name '*.rb' -o -name '*.gemspec' \) \
|
|
26
|
+
-print0 | sort -z
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
while IFS= read -r -d '' file; do
|
|
30
|
+
rubocop_files+=("$file")
|
|
31
|
+
done < <(find . -maxdepth 1 -type f -name '*.gemspec' -print0 | sort -z)
|
|
32
|
+
|
|
33
|
+
bundle exec rubocop "${rubocop_files[@]}"
|
data/scripts/files.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
|
|
3
4
|
require 'yaml'
|
|
4
5
|
|
|
@@ -8,8 +9,8 @@ test_files = files.grep(%r{^(test|spec|features)/})
|
|
|
8
9
|
require_paths = ['lib']
|
|
9
10
|
|
|
10
11
|
out = {}
|
|
11
|
-
out[
|
|
12
|
-
out[
|
|
13
|
-
out[
|
|
12
|
+
out[:Executables] = executables
|
|
13
|
+
out[:Test_Files] = test_files
|
|
14
|
+
out[:Require_Paths] = require_paths
|
|
14
15
|
|
|
15
|
-
puts out.to_yaml
|
|
16
|
+
puts out.to_yaml
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'spec_helper'
|
|
5
|
+
require 'tmpdir'
|
|
6
|
+
require 'yaml'
|
|
7
|
+
|
|
8
|
+
RSpec.describe 'CI/CD examples' do
|
|
9
|
+
it 'ships parseable inventory and GitHub Actions examples' do
|
|
10
|
+
expect(YAML.safe_load_file('examples/ci/inventory/example-snapshot.yml')).to include('version' => 1)
|
|
11
|
+
workflow = YAML.safe_load_file('examples/ci/github-actions/inventory-review.yml')
|
|
12
|
+
|
|
13
|
+
expect(workflow).to include('name' => 'Inventory review example')
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'keeps the snapshot validation script syntax-valid' do
|
|
17
|
+
expect(system('bash', '-n', 'examples/ci/scripts/validate-inventory-snapshot.sh')).to eq(true)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'validates a snapshot and writes review artifacts without production credentials' do
|
|
21
|
+
Dir.mktmpdir do |dir|
|
|
22
|
+
command = 'bundle exec ruby -Ilib bin/moose-inventory'
|
|
23
|
+
result = system(
|
|
24
|
+
{ 'MOOSE_INVENTORY_CMD' => command },
|
|
25
|
+
'examples/ci/scripts/validate-inventory-snapshot.sh',
|
|
26
|
+
'examples/ci/inventory/example-snapshot.yml',
|
|
27
|
+
dir
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
expect(result).to eq(true)
|
|
31
|
+
expect(File).to exist(File.join(dir, 'doctor.txt'))
|
|
32
|
+
expect(YAML.safe_load_file(File.join(dir, 'inventory.yml'))).to include('hosts')
|
|
33
|
+
expect(JSON.parse(File.read(File.join(dir, 'hosts.json')))).to include('web01')
|
|
34
|
+
expect(JSON.parse(File.read(File.join(dir, 'ansible-inventory.json')))).to include('web')
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|