moose-inventory 1.0.9 → 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/ci.yml +15 -1
- data/.github/workflows/release.yml +60 -0
- data/.gitignore +2 -1
- data/.gitleaks.toml +9 -0
- data/.rubocop.yml +49 -0
- data/BACKLOG.md +752 -24
- data/Gemfile +2 -0
- data/Gemfile.lock +36 -1
- data/README.md +340 -44
- 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 +54 -50
- data/docs/release/release-environment-protection.md +70 -0
- data/docs/release/release-readiness.md +37 -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 +75 -0
- data/docs/security-audit-2026-05-26.md +63 -0
- 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 +7 -1
- data/lib/moose_inventory/cli/group_add.rb +91 -73
- data/lib/moose_inventory/cli/group_addchild.rb +41 -66
- data/lib/moose_inventory/cli/group_addhost.rb +33 -71
- 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 +105 -73
- data/lib/moose_inventory/cli/group_rmchild.rb +47 -57
- data/lib/moose_inventory/cli/group_rmhost.rb +34 -61
- 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 +143 -0
- data/lib/moose_inventory/cli/host.rb +8 -2
- data/lib/moose_inventory/cli/host_add.rb +91 -66
- data/lib/moose_inventory/cli/host_addgroup.rb +39 -66
- 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 +39 -55
- 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 +188 -193
- 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 +116 -0
- data/lib/moose_inventory/operations/add_associations.rb +131 -0
- data/lib/moose_inventory/operations/add_groups.rb +123 -0
- data/lib/moose_inventory/operations/add_hosts.rb +123 -0
- 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 +125 -0
- data/lib/moose_inventory/operations/group_cleanup.rb +70 -0
- 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 +113 -0
- data/lib/moose_inventory/operations/remove_groups.rb +79 -0
- 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 +22 -35
- data/scripts/check.sh +3 -0
- data/scripts/ci/check_generated_artifacts.sh +41 -0
- data/scripts/ci/check_permissions.sh +5 -0
- data/scripts/ci/check_rubocop.sh +33 -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/files.rb +5 -4
- data/scripts/install_dependencies.sh +2 -0
- 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 +165 -85
- data/spec/lib/moose_inventory/cli/group_rmchild_spec.rb +100 -30
- 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 +551 -29
- 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 +111 -0
- data/spec/lib/moose_inventory/operations/add_groups_spec.rb +80 -0
- data/spec/lib/moose_inventory/operations/add_hosts_spec.rb +82 -0
- data/spec/lib/moose_inventory/operations/add_variables_spec.rb +103 -0
- data/spec/lib/moose_inventory/operations/group_child_relations_spec.rb +122 -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 +113 -0
- data/spec/lib/moose_inventory/operations/remove_groups_spec.rb +78 -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 +163 -35
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moose
|
|
4
|
+
module Inventory
|
|
5
|
+
module Operations
|
|
6
|
+
class QueryInventory
|
|
7
|
+
# Host-focused read queries.
|
|
8
|
+
class HostQueries < BaseQuery
|
|
9
|
+
def get_hosts(names:)
|
|
10
|
+
names.each_with_object({}) do |name, results|
|
|
11
|
+
host = context.find_host(name)
|
|
12
|
+
next if host.nil?
|
|
13
|
+
|
|
14
|
+
results[host.name.to_sym] = host_data(host)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def list_hosts(filters: {})
|
|
19
|
+
dataset = filtered_hosts_dataset(filters)
|
|
20
|
+
return {} if dataset.nil?
|
|
21
|
+
|
|
22
|
+
dataset.order(:id).all.to_h do |host|
|
|
23
|
+
[host.name.to_sym, host_data(host)]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def list_host_vars(names:, ansible:)
|
|
28
|
+
return ansible_host_vars(names.first) if ansible
|
|
29
|
+
|
|
30
|
+
names.each_with_object({}) do |name, results|
|
|
31
|
+
host = context.find_host(name)
|
|
32
|
+
next if host.nil?
|
|
33
|
+
|
|
34
|
+
results[name.to_sym] = variables_hash(host.hostvars_dataset)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def host_data(host)
|
|
41
|
+
{}.tap do |data|
|
|
42
|
+
groups = host.groups_dataset.map(:name)
|
|
43
|
+
data[:groups] = groups unless groups.empty?
|
|
44
|
+
|
|
45
|
+
tags = host.tags_dataset.map(:name).sort
|
|
46
|
+
data[:tags] = tags unless tags.empty?
|
|
47
|
+
|
|
48
|
+
hostvars = variables_hash(host.hostvars_dataset)
|
|
49
|
+
data[:hostvars] = hostvars unless hostvars.empty?
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def filtered_hosts_dataset(filters)
|
|
54
|
+
dataset = context.hosts_dataset
|
|
55
|
+
dataset = filter_hosts_by_groups(dataset, filters.fetch(:groups, []))
|
|
56
|
+
return nil if dataset.nil?
|
|
57
|
+
|
|
58
|
+
dataset = filter_hosts_by_tags(dataset, filters.fetch(:tags, []))
|
|
59
|
+
return nil if dataset.nil?
|
|
60
|
+
|
|
61
|
+
filter_hosts_by_variables(dataset, filters.fetch(:variables, {}))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def filter_hosts_by_groups(dataset, groups)
|
|
65
|
+
groups.reduce(dataset) do |current_dataset, group_name|
|
|
66
|
+
group = context.find_group(group_name)
|
|
67
|
+
return nil if group.nil?
|
|
68
|
+
|
|
69
|
+
current_dataset.where(id: context.db_dataset(:groups_hosts).where(group_id: group.id).select(:host_id))
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def filter_hosts_by_tags(dataset, tags)
|
|
74
|
+
tags.reduce(dataset) do |current_dataset, tag_name|
|
|
75
|
+
tag = context.find_tag(tag_name)
|
|
76
|
+
return nil if tag.nil?
|
|
77
|
+
|
|
78
|
+
current_dataset.where(id: context.db_dataset(:hosts_tags).where(tag_id: tag.id).select(:host_id))
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def filter_hosts_by_variables(dataset, variables)
|
|
83
|
+
variables.reduce(dataset) do |current_dataset, (name, value)|
|
|
84
|
+
current_dataset.where(
|
|
85
|
+
id: context.db_dataset(:hostvars).where(name: name, value: value).select(:host_id)
|
|
86
|
+
)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def ansible_host_vars(name)
|
|
91
|
+
results = {}
|
|
92
|
+
host = context.find_host(name)
|
|
93
|
+
results.merge!(variables_hash(host.hostvars_dataset)) unless host.nil?
|
|
94
|
+
|
|
95
|
+
results[:_meta] = {
|
|
96
|
+
hostvars: context.all_hosts.to_h do |entry|
|
|
97
|
+
[entry.name.to_sym, variables_hash(entry.hostvars_dataset)]
|
|
98
|
+
end
|
|
99
|
+
}
|
|
100
|
+
results
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'query_inventory/base_query'
|
|
4
|
+
require_relative 'query_inventory/host_queries'
|
|
5
|
+
require_relative 'query_inventory/group_queries'
|
|
6
|
+
|
|
7
|
+
module Moose
|
|
8
|
+
module Inventory
|
|
9
|
+
module Operations
|
|
10
|
+
# Read-only inventory query seam for host/group CLI commands.
|
|
11
|
+
class QueryInventory
|
|
12
|
+
def initialize(context:)
|
|
13
|
+
@host_queries = HostQueries.new(context: context)
|
|
14
|
+
@group_queries = GroupQueries.new(context: context)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def get_hosts(names:)
|
|
18
|
+
host_queries.get_hosts(names: names)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def list_hosts(filters: {})
|
|
22
|
+
host_queries.list_hosts(filters: filters)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def list_host_vars(names:, ansible:)
|
|
26
|
+
host_queries.list_host_vars(names: names, ansible: ansible)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def get_groups(names:)
|
|
30
|
+
group_queries.get_groups(names: names)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def list_groups(ansible:)
|
|
34
|
+
group_queries.list_groups(ansible: ansible)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def list_group_vars(names:, ansible:)
|
|
38
|
+
group_queries.list_group_vars(names: names, ansible: ansible)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
attr_reader :group_queries, :host_queries
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'operation_event_support'
|
|
4
|
+
|
|
5
|
+
module Moose
|
|
6
|
+
module Inventory
|
|
7
|
+
module Operations
|
|
8
|
+
# Removes host/group associations for existing primary entities.
|
|
9
|
+
class RemoveAssociations
|
|
10
|
+
AUTOMATIC_GROUP = 'ungrouped'
|
|
11
|
+
include OperationEventSupport
|
|
12
|
+
|
|
13
|
+
def initialize(context:)
|
|
14
|
+
@context = context
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def host_from_groups(host:, host_name:, group_names:, dry_run: false)
|
|
18
|
+
events = []
|
|
19
|
+
warning_count = 0
|
|
20
|
+
@dry_run = dry_run
|
|
21
|
+
|
|
22
|
+
group_names.each do |group_name|
|
|
23
|
+
next if group_name.nil? || group_name.empty?
|
|
24
|
+
|
|
25
|
+
warning_count += remove_group_from_host(host, host_name, group_name, events)
|
|
26
|
+
end
|
|
27
|
+
|
|
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
|
|
31
|
+
|
|
32
|
+
operation_result(events: events, warning_count: warning_count)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def group_from_hosts(group:, group_name:, host_names:, dry_run: false)
|
|
36
|
+
events = []
|
|
37
|
+
warning_count = 0
|
|
38
|
+
@dry_run = dry_run
|
|
39
|
+
hosts_dataset = group.hosts_dataset
|
|
40
|
+
|
|
41
|
+
host_names.each do |host_name|
|
|
42
|
+
next if host_name.nil? || host_name.empty?
|
|
43
|
+
|
|
44
|
+
warning_count += remove_host_from_group(group, group_name, host_name, hosts_dataset, events)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
emit(events, :dry_run_summary) if dry_run
|
|
48
|
+
|
|
49
|
+
operation_result(events: events, warning_count: warning_count)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
attr_reader :context, :dry_run
|
|
55
|
+
|
|
56
|
+
def remove_group_from_host(host, host_name, group_name, events)
|
|
57
|
+
groups_dataset = host.groups_dataset
|
|
58
|
+
emit(events, :removing_host_group_association, host: host_name, group: group_name)
|
|
59
|
+
|
|
60
|
+
unless association_exists?(groups_dataset, group_name)
|
|
61
|
+
emit(events, :host_group_association_missing, host: host_name, group: group_name)
|
|
62
|
+
emit(events, :missing_skipping, indent: 4)
|
|
63
|
+
emit(events, :ok, indent: 4)
|
|
64
|
+
return 1
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
group = context.find_group(group_name)
|
|
68
|
+
host.remove_group(group) unless group.nil? || dry_run
|
|
69
|
+
emit(events, :ok, indent: 4)
|
|
70
|
+
0
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def remove_host_from_group(group, group_name, host_name, hosts_dataset, events)
|
|
74
|
+
emit(events, :removing_group_host_association, group: group_name, host: host_name)
|
|
75
|
+
|
|
76
|
+
unless association_exists?(hosts_dataset, host_name)
|
|
77
|
+
emit(events, :group_host_association_missing, group: group_name, host: host_name)
|
|
78
|
+
emit(events, :missing_skipping, indent: 4)
|
|
79
|
+
emit(events, :ok, indent: 4)
|
|
80
|
+
return 1
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
host = context.find_host(host_name)
|
|
84
|
+
group.remove_host(host) unless host.nil? || dry_run
|
|
85
|
+
emit(events, :ok, indent: 4)
|
|
86
|
+
add_automatic_group_if_needed(host, host_name, events,
|
|
87
|
+
planned_empty: dry_run && !host.nil? && host.groups_dataset.one?)
|
|
88
|
+
0
|
|
89
|
+
end
|
|
90
|
+
|
|
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?
|
|
94
|
+
|
|
95
|
+
emit(events, :adding_automatic_group, host: host_name)
|
|
96
|
+
host.add_group(context.automatic_group) unless dry_run
|
|
97
|
+
emit(events, :ok, indent: 4)
|
|
98
|
+
end
|
|
99
|
+
|
|
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?
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def association_exists?(dataset, name)
|
|
108
|
+
!dataset.nil? && !dataset[name: name].nil?
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'operation_event_support'
|
|
4
|
+
|
|
5
|
+
require_relative 'group_cleanup'
|
|
6
|
+
|
|
7
|
+
module Moose
|
|
8
|
+
module Inventory
|
|
9
|
+
module Operations
|
|
10
|
+
# Removes top-level groups and their direct associations.
|
|
11
|
+
class RemoveGroups
|
|
12
|
+
include OperationEventSupport
|
|
13
|
+
|
|
14
|
+
def initialize(context:)
|
|
15
|
+
@context = context
|
|
16
|
+
@cleanup = Moose::Inventory::Operations::GroupCleanup.new(
|
|
17
|
+
context: context,
|
|
18
|
+
emitter: method(:emit)
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def call(names:, recursive: false, dry_run: false)
|
|
23
|
+
events = []
|
|
24
|
+
warning_count = 0
|
|
25
|
+
@dry_run = dry_run
|
|
26
|
+
cleanup.dry_run = dry_run
|
|
27
|
+
|
|
28
|
+
names.each do |name|
|
|
29
|
+
warning_count += remove_group(name, events, recursive: recursive)
|
|
30
|
+
end
|
|
31
|
+
emit(events, :dry_run_summary) if dry_run
|
|
32
|
+
|
|
33
|
+
operation_result(events: events, warning_count: warning_count)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
attr_reader :cleanup, :context, :dry_run
|
|
39
|
+
|
|
40
|
+
def remove_group(name, events, recursive:)
|
|
41
|
+
emit(events, :group_started, name: name)
|
|
42
|
+
emit(events, :retrieving_group, name: name)
|
|
43
|
+
group = context.find_group(name)
|
|
44
|
+
|
|
45
|
+
if group.nil?
|
|
46
|
+
emit(events, :group_missing, name: name)
|
|
47
|
+
emit(events, :ok, indent: 4)
|
|
48
|
+
emit(events, :group_complete)
|
|
49
|
+
return 1
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
emit(events, :ok, indent: 4)
|
|
53
|
+
remove_parent_associations(group, name, events)
|
|
54
|
+
remove_child_associations(group, name, events, recursive: recursive)
|
|
55
|
+
cleanup.destroy_group(group, events, indent: 2)
|
|
56
|
+
emit(events, :group_complete)
|
|
57
|
+
0
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def remove_parent_associations(group, name, events)
|
|
61
|
+
group.parents_dataset.each do |parent|
|
|
62
|
+
emit(events, :removing_parent_association, group: name, related_group: parent.name)
|
|
63
|
+
parent.remove_child(group) unless dry_run
|
|
64
|
+
emit(events, :ok, indent: 4)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def remove_child_associations(group, name, events, recursive:)
|
|
69
|
+
group.children_dataset.each do |child|
|
|
70
|
+
emit(events, :removing_child_association, group: name, related_group: child.name)
|
|
71
|
+
group.remove_child(child) unless dry_run
|
|
72
|
+
emit(events, :ok, indent: 4)
|
|
73
|
+
cleanup.delete_orphaned_group(child, events, ignored_parent: group) if recursive
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
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,60 +1,47 @@
|
|
|
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'
|
|
39
|
+
spec.add_development_dependency 'bundler-audit', '>= 0.9', '< 1'
|
|
40
|
+
spec.add_development_dependency 'parallel', '>= 1.10', '< 2.0'
|
|
56
41
|
spec.add_development_dependency 'rake', '>= 13.0', '< 14'
|
|
57
42
|
spec.add_development_dependency 'rspec', '~> 3'
|
|
43
|
+
spec.add_development_dependency 'rubocop', '>= 1.72', '< 2'
|
|
58
44
|
spec.add_development_dependency 'simplecov', '~> 0'
|
|
59
|
-
|
|
45
|
+
# rubocop:enable Gemspec/DevelopmentDependencies
|
|
60
46
|
end
|
|
47
|
+
# rubocop:enable Metrics/BlockLength
|
data/scripts/check.sh
CHANGED
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
4
|
bundle exec rspec --format progress
|
|
5
|
+
scripts/ci/check_rubocop.sh
|
|
5
6
|
git diff --check
|
|
6
7
|
scripts/ci/check_permissions.sh
|
|
8
|
+
scripts/ci/check_generated_artifacts.sh
|
|
7
9
|
scripts/ci/check_security.sh
|
|
10
|
+
scripts/ci/check_secrets.sh
|
|
8
11
|
scripts/ci/package_sanity.sh
|