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,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Moose::Inventory::DB::MooseDBException do
|
|
6
|
+
it 'uses the provided exception message through RuntimeError' do
|
|
7
|
+
error = described_class.new('boom')
|
|
8
|
+
|
|
9
|
+
expect(error.message).to eq('boom')
|
|
10
|
+
expect(error.full_message).to include('boom')
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it 'falls back to a default exception message' do
|
|
14
|
+
error = described_class.new
|
|
15
|
+
|
|
16
|
+
expect(error.message).to eq('An undefined Moose exception occurred')
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'spec_helper'
|
|
2
4
|
|
|
5
|
+
# rubocop:disable Metrics/BlockLength
|
|
3
6
|
RSpec.describe 'models' do
|
|
4
7
|
#=============================
|
|
5
8
|
# Initialization
|
|
@@ -8,9 +11,9 @@ RSpec.describe 'models' do
|
|
|
8
11
|
before(:all) do
|
|
9
12
|
# Set up the configuration object
|
|
10
13
|
@mockarg_parts = {
|
|
11
|
-
config:
|
|
12
|
-
format:
|
|
13
|
-
env:
|
|
14
|
+
config: File.join(spec_root, 'config/config.yml'),
|
|
15
|
+
format: 'yaml',
|
|
16
|
+
env: 'test'
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
@mockargs = []
|
|
@@ -173,3 +176,4 @@ RSpec.describe 'models' do
|
|
|
173
176
|
end
|
|
174
177
|
end
|
|
175
178
|
end
|
|
179
|
+
# rubocop:enable Metrics/BlockLength
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# rubocop:disable Metrics/BlockLength
|
|
4
|
+
require 'spec_helper'
|
|
5
|
+
require 'tmpdir'
|
|
6
|
+
|
|
7
|
+
RSpec.describe 'database lifecycle commands' do
|
|
8
|
+
before(:all) do
|
|
9
|
+
setup_cli_harness(command_class: Moose::Inventory::Cli::Application)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
before(:each) do
|
|
13
|
+
reset_cli_harness
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'records the current schema version during database initialization' do
|
|
17
|
+
expect(Moose::Inventory::DB.schema_version).to eq(Moose::Inventory::DB::SCHEMA_VERSION)
|
|
18
|
+
expect(Moose::Inventory::DB.status[:tables][:schema_info]).to eq(true)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'prints database status' do
|
|
22
|
+
actual = runner { @app.start(%w[db status]) }
|
|
23
|
+
|
|
24
|
+
expect(actual[:unexpected]).to eq(false)
|
|
25
|
+
expect(actual[:aborted]).to eq(false)
|
|
26
|
+
expect(actual[:STDOUT]).to include('Adapter: sqlite3')
|
|
27
|
+
expect(actual[:STDOUT]).to include("Expected schema version: #{Moose::Inventory::DB::SCHEMA_VERSION}")
|
|
28
|
+
expect(actual[:STDOUT]).to include('- schema_info: present')
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it 'runs db doctor successfully when schema state is current' do
|
|
32
|
+
actual = runner { @app.start(%w[db doctor]) }
|
|
33
|
+
|
|
34
|
+
expected(actual, aborted: false, STDOUT: "Database doctor found no issues.\n", STDERR: '')
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'reports dirty partial schema state through db doctor' do
|
|
38
|
+
Moose::Inventory::DB.db.drop_table(:audit_events)
|
|
39
|
+
|
|
40
|
+
actual = runner { @app.start(%w[db doctor]) }
|
|
41
|
+
|
|
42
|
+
expect(actual[:unexpected]).to eq(false)
|
|
43
|
+
expect(actual[:aborted]).to eq(true)
|
|
44
|
+
expect(actual[:STDOUT]).to include('Database doctor found issue(s):')
|
|
45
|
+
expect(actual[:STDOUT]).to include('- Missing tables: audit_events')
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it 'migrates missing lifecycle metadata' do
|
|
49
|
+
Moose::Inventory::DB.db.drop_table(:schema_info)
|
|
50
|
+
|
|
51
|
+
actual = runner { @app.start(%w[db migrate]) }
|
|
52
|
+
|
|
53
|
+
expect(actual[:unexpected]).to eq(false)
|
|
54
|
+
expect(actual[:aborted]).to eq(false)
|
|
55
|
+
expect(actual[:STDOUT]).to include("Database schema is at version #{Moose::Inventory::DB::SCHEMA_VERSION}.")
|
|
56
|
+
expect(Moose::Inventory::DB.schema_version).to eq(Moose::Inventory::DB::SCHEMA_VERSION)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it 'backs up the sqlite database file' do
|
|
60
|
+
Dir.mktmpdir do |dir|
|
|
61
|
+
destination = File.join(dir, 'backup.sqlite3')
|
|
62
|
+
|
|
63
|
+
actual = runner { @app.start(['db', 'backup', destination]) }
|
|
64
|
+
|
|
65
|
+
expect(actual[:unexpected]).to eq(false)
|
|
66
|
+
expect(actual[:aborted]).to eq(false)
|
|
67
|
+
expect(actual[:STDOUT]).to include("Backed up database to #{destination}.")
|
|
68
|
+
expect(File).to exist(destination)
|
|
69
|
+
expect(File.size(destination)).to be_positive
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
# rubocop:enable Metrics/BlockLength
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'inventory_context'
|
|
5
|
+
|
|
6
|
+
RSpec.describe Moose::Inventory::InventoryContext do
|
|
7
|
+
it 'requires an explicit db dependency' do
|
|
8
|
+
expect { described_class.new }.to raise_error(ArgumentError)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'inventory_context'
|
|
5
|
+
require 'operations/add_associations'
|
|
6
|
+
|
|
7
|
+
RSpec.describe Moose::Inventory::Operations::AddAssociations do
|
|
8
|
+
before(:all) do
|
|
9
|
+
@mockargs = [
|
|
10
|
+
'--config', File.join(spec_root, 'config/config.yml'),
|
|
11
|
+
'--format', 'yaml',
|
|
12
|
+
'--env', 'test'
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
Moose::Inventory::Config.init(@mockargs)
|
|
16
|
+
@db = Moose::Inventory::DB
|
|
17
|
+
@db.init if @db.db.nil?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
before(:each) do
|
|
21
|
+
@db.reset
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def operation
|
|
25
|
+
described_class.new(
|
|
26
|
+
context: Moose::Inventory::InventoryContext.new(db: @db)
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'adds groups to an existing host and reports creation/duplicate events' do
|
|
31
|
+
host = @db.models[:host].create(name: 'host1')
|
|
32
|
+
ungrouped = @db.models[:group].find_or_create(name: 'ungrouped')
|
|
33
|
+
host.add_group(ungrouped)
|
|
34
|
+
existing_group = @db.models[:group].create(name: 'existing')
|
|
35
|
+
host.add_group(existing_group)
|
|
36
|
+
|
|
37
|
+
result = operation.host_to_groups(
|
|
38
|
+
host: host,
|
|
39
|
+
host_name: 'host1',
|
|
40
|
+
group_names: %w[existing created]
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
expect(result.warning_count).to eq(2)
|
|
44
|
+
expect(result.events.map(&:type)).to include(
|
|
45
|
+
:host_group_association_exists,
|
|
46
|
+
:group_missing_created,
|
|
47
|
+
:removing_automatic_group
|
|
48
|
+
)
|
|
49
|
+
expect(host.groups_dataset[name: 'created']).not_to be_nil
|
|
50
|
+
expect(host.groups_dataset[name: 'ungrouped']).to be_nil
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'dry-runs adding groups to a host without creating groups or changing automatic membership' do
|
|
54
|
+
host = @db.models[:host].create(name: 'host1')
|
|
55
|
+
ungrouped = @db.models[:group].find_or_create(name: 'ungrouped')
|
|
56
|
+
host.add_group(ungrouped)
|
|
57
|
+
|
|
58
|
+
result = operation.host_to_groups(
|
|
59
|
+
host: host,
|
|
60
|
+
host_name: 'host1',
|
|
61
|
+
group_names: ['created'],
|
|
62
|
+
dry_run: true
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
expect(result.warning_count).to eq(1)
|
|
66
|
+
expect(result.events.map(&:type)).to include(:group_missing_created, :removing_automatic_group, :dry_run_summary)
|
|
67
|
+
expect(@db.models[:group].find(name: 'created')).to be_nil
|
|
68
|
+
expect(host.groups_dataset[name: 'created']).to be_nil
|
|
69
|
+
expect(host.groups_dataset[name: 'ungrouped']).not_to be_nil
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it 'dry-runs adding hosts to a group without creating hosts or changing automatic membership' do
|
|
73
|
+
group = @db.models[:group].create(name: 'group1')
|
|
74
|
+
|
|
75
|
+
result = operation.group_to_hosts(
|
|
76
|
+
group: group,
|
|
77
|
+
group_name: 'group1',
|
|
78
|
+
host_names: ['host1'],
|
|
79
|
+
dry_run: true
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
expect(result.warning_count).to eq(1)
|
|
83
|
+
expect(result.events.map(&:type)).to include(:host_missing_created, :dry_run_summary)
|
|
84
|
+
expect(@db.models[:host].find(name: 'host1')).to be_nil
|
|
85
|
+
expect(group.hosts_dataset[name: 'host1']).to be_nil
|
|
86
|
+
end
|
|
87
|
+
it 'adds hosts to an existing group and reports creation/duplicate events' do
|
|
88
|
+
group = @db.models[:group].create(name: 'group1')
|
|
89
|
+
duplicate_host = @db.models[:host].create(name: 'host1')
|
|
90
|
+
existing_host = @db.models[:host].create(name: 'host3')
|
|
91
|
+
ungrouped = @db.models[:group].find_or_create(name: 'ungrouped')
|
|
92
|
+
duplicate_host.add_group(ungrouped)
|
|
93
|
+
existing_host.add_group(ungrouped)
|
|
94
|
+
group.add_host(duplicate_host)
|
|
95
|
+
|
|
96
|
+
result = operation.group_to_hosts(
|
|
97
|
+
group: group,
|
|
98
|
+
group_name: 'group1',
|
|
99
|
+
host_names: %w[host1 host2 host3]
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
expect(result.warning_count).to eq(2)
|
|
103
|
+
expect(result.events.map(&:type)).to include(
|
|
104
|
+
:group_host_association_exists,
|
|
105
|
+
:host_missing_created,
|
|
106
|
+
:removing_automatic_group
|
|
107
|
+
)
|
|
108
|
+
expect(group.hosts_dataset[name: 'host2']).not_to be_nil
|
|
109
|
+
expect(existing_host.groups_dataset[name: 'ungrouped']).to be_nil
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'inventory_context'
|
|
5
|
+
require 'operations/add_groups'
|
|
6
|
+
|
|
7
|
+
RSpec.describe Moose::Inventory::Operations::AddGroups do
|
|
8
|
+
before(:all) do
|
|
9
|
+
@mockargs = [
|
|
10
|
+
'--config', File.join(spec_root, 'config/config.yml'),
|
|
11
|
+
'--format', 'yaml',
|
|
12
|
+
'--env', 'test'
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
Moose::Inventory::Config.init(@mockargs)
|
|
16
|
+
@db = Moose::Inventory::DB
|
|
17
|
+
@db.init if @db.db.nil?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
before(:each) do
|
|
21
|
+
@db.reset
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def operation
|
|
25
|
+
described_class.new(
|
|
26
|
+
context: Moose::Inventory::InventoryContext.new(db: @db)
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'adds a group and returns structured events without rendering output' do
|
|
31
|
+
actual = runner do
|
|
32
|
+
@result = operation.call(names: ['testgroup'], hosts: [])
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
expected(actual, STDOUT: '', STDERR: '')
|
|
36
|
+
expect(@result.warning_count).to eq(0)
|
|
37
|
+
expect(@result.events.map(&:type)).to eq(%i[group_started creating_group ok group_complete])
|
|
38
|
+
|
|
39
|
+
group = @db.models[:group].find(name: 'testgroup')
|
|
40
|
+
expect(group).not_to be_nil
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it 'returns dry-run events without creating groups, hosts, or associations' do
|
|
44
|
+
result = operation.call(
|
|
45
|
+
names: ['testgroup'],
|
|
46
|
+
hosts: ['missinghost'],
|
|
47
|
+
dry_run: true
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
expect(result.warning_count).to eq(1)
|
|
51
|
+
expect(result.events.map(&:type)).to eq(
|
|
52
|
+
%i[group_started creating_group ok adding_association host_missing_created host_creating_now ok ok group_complete
|
|
53
|
+
dry_run_summary]
|
|
54
|
+
)
|
|
55
|
+
expect(@db.models[:group].find(name: 'testgroup')).to be_nil
|
|
56
|
+
expect(@db.models[:host].find(name: 'missinghost')).to be_nil
|
|
57
|
+
end
|
|
58
|
+
it 'reports existing groups, created hosts, duplicate associations, and ungrouped removal as events' do
|
|
59
|
+
host = @db.models[:host].create(name: 'testhost')
|
|
60
|
+
ungrouped = @db.models[:group].find_or_create(name: 'ungrouped')
|
|
61
|
+
host.add_group(ungrouped)
|
|
62
|
+
group = @db.models[:group].create(name: 'testgroup')
|
|
63
|
+
group.add_host(host)
|
|
64
|
+
|
|
65
|
+
@result = operation.call(
|
|
66
|
+
names: ['testgroup'],
|
|
67
|
+
hosts: %w[testhost newhost]
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
expect(@result.warning_count).to eq(3)
|
|
71
|
+
expect(@result.events.map(&:type)).to include(
|
|
72
|
+
:group_exists,
|
|
73
|
+
:association_exists,
|
|
74
|
+
:host_missing_created,
|
|
75
|
+
:removing_automatic_group
|
|
76
|
+
)
|
|
77
|
+
expect(@db.models[:host].find(name: 'newhost')).not_to be_nil
|
|
78
|
+
expect(host.groups_dataset[name: 'ungrouped']).to be_nil
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Moose::Inventory::Operations::AddHosts do
|
|
6
|
+
before(:all) do
|
|
7
|
+
@mockargs = [
|
|
8
|
+
'--config', File.join(spec_root, 'config/config.yml'),
|
|
9
|
+
'--format', 'yaml',
|
|
10
|
+
'--env', 'test'
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
Moose::Inventory::Config.init(@mockargs)
|
|
14
|
+
@db = Moose::Inventory::DB
|
|
15
|
+
@db.init if @db.db.nil?
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
before(:each) do
|
|
19
|
+
@db.reset
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def operation
|
|
23
|
+
described_class.new(
|
|
24
|
+
context: Moose::Inventory::InventoryContext.new(db: @db)
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
describe '#call' do
|
|
29
|
+
it 'adds a host and returns structured events without rendering output' do
|
|
30
|
+
actual = runner do
|
|
31
|
+
@result = operation.call(names: ['testhost'], groups: [])
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
expected(actual, STDOUT: '', STDERR: '')
|
|
35
|
+
expect(@result.events.map(&:type)).to eq(
|
|
36
|
+
%i[host_started creating_host ok adding_automatic_group ok host_complete]
|
|
37
|
+
)
|
|
38
|
+
expect(@result.events[0].payload).to eq(name: 'testhost')
|
|
39
|
+
expect(@result.events[3].payload).to eq(host: 'testhost', group: 'ungrouped')
|
|
40
|
+
|
|
41
|
+
host = @db.models[:host].find(name: 'testhost')
|
|
42
|
+
expect(host).not_to be_nil
|
|
43
|
+
expect(host.groups_dataset[name: 'ungrouped']).not_to be_nil
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'returns dry-run events without creating hosts, groups, or associations' do
|
|
47
|
+
@result = operation.call(
|
|
48
|
+
names: ['testhost'],
|
|
49
|
+
groups: ['missinggroup'],
|
|
50
|
+
dry_run: true
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
expect(@result.events.map(&:type)).to eq(
|
|
54
|
+
%i[host_started creating_host ok adding_association group_missing_created ok host_complete dry_run_summary]
|
|
55
|
+
)
|
|
56
|
+
expect(@db.models[:host].find(name: 'testhost')).to be_nil
|
|
57
|
+
expect(@db.models[:group].find(name: 'missinggroup')).to be_nil
|
|
58
|
+
end
|
|
59
|
+
it 'reports existing hosts, missing groups, and duplicate associations as events' do
|
|
60
|
+
host = @db.models[:host].create(name: 'testhost')
|
|
61
|
+
group = @db.models[:group].create(name: 'existinggroup')
|
|
62
|
+
host.add_group(group)
|
|
63
|
+
|
|
64
|
+
@result = operation.call(
|
|
65
|
+
names: ['testhost'],
|
|
66
|
+
groups: %w[existinggroup missinggroup]
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
expect(@result.events.map(&:type)).to include(
|
|
70
|
+
:host_exists,
|
|
71
|
+
:association_exists,
|
|
72
|
+
:group_missing_created
|
|
73
|
+
)
|
|
74
|
+
expect(@result.events.find { |event| event.type == :host_exists }.payload).to eq(name: 'testhost')
|
|
75
|
+
expect(@result.events.find { |event| event.type == :association_exists }.payload).to eq(
|
|
76
|
+
host: 'testhost',
|
|
77
|
+
group: 'existinggroup'
|
|
78
|
+
)
|
|
79
|
+
expect(@db.models[:group].find(name: 'missinggroup')).not_to be_nil
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'inventory_context'
|
|
5
|
+
require 'operations/add_variables'
|
|
6
|
+
|
|
7
|
+
RSpec.describe Moose::Inventory::Operations::AddVariables do
|
|
8
|
+
before(:all) do
|
|
9
|
+
@mockargs = [
|
|
10
|
+
'--config', File.join(spec_root, 'config/config.yml'),
|
|
11
|
+
'--format', 'yaml',
|
|
12
|
+
'--env', 'test'
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
Moose::Inventory::Config.init(@mockargs)
|
|
16
|
+
@db = Moose::Inventory::DB
|
|
17
|
+
@db.init if @db.db.nil?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
before(:each) do
|
|
21
|
+
@db.reset
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def build_operation(entity_type, emitter: nil)
|
|
25
|
+
described_class.new(
|
|
26
|
+
context: Moose::Inventory::InventoryContext.new(db: @db),
|
|
27
|
+
entity_type: entity_type,
|
|
28
|
+
emitter: emitter
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'adds host variables and returns structured events without rendering output' do
|
|
33
|
+
@db.models[:host].create(name: 'test1')
|
|
34
|
+
|
|
35
|
+
actual = runner do
|
|
36
|
+
@result = build_operation(:host).call(name: 'test1', vars: ['var1=val1'])
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
expected(actual, STDOUT: '', STDERR: '')
|
|
40
|
+
expect(@result.events.map(&:type)).to eq(
|
|
41
|
+
%i[entity_started retrieving_entity ok adding_variable ok entity_complete]
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
host = @db.models[:host].find(name: 'test1')
|
|
45
|
+
expect(host.hostvars_dataset[name: 'var1'][:value]).to eq('val1')
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it 'updates an existing group variable and emits an update event' do
|
|
49
|
+
group = @db.models[:group].create(name: 'testgroup')
|
|
50
|
+
var = @db.models[:groupvar].create(name: 'var1', value: 'old')
|
|
51
|
+
group.add_groupvar(var)
|
|
52
|
+
|
|
53
|
+
result = build_operation(:group).call(name: 'testgroup', vars: ['var1=new'])
|
|
54
|
+
|
|
55
|
+
expect(result.events.map(&:type)).to include(:updating_existing_variable)
|
|
56
|
+
expect(group.groupvars_dataset[name: 'var1'][:value]).to eq('new')
|
|
57
|
+
expect(@db.models[:groupvar].count).to eq(1)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it 'dry-runs adding and updating variables without writing records' do
|
|
61
|
+
host = @db.models[:host].create(name: 'test1')
|
|
62
|
+
existing = @db.models[:hostvar].create(name: 'var1', value: 'old')
|
|
63
|
+
host.add_hostvar(existing)
|
|
64
|
+
|
|
65
|
+
result = build_operation(:host).call(
|
|
66
|
+
name: 'test1',
|
|
67
|
+
vars: %w[var1=new var2=val2],
|
|
68
|
+
dry_run: true
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
expect(result.events.map(&:type)).to include(:updating_existing_variable, :dry_run_summary)
|
|
72
|
+
expect(host.hostvars_dataset[name: 'var1'][:value]).to eq('old')
|
|
73
|
+
expect(host.hostvars_dataset[name: 'var2']).to be_nil
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it 'uses the shared variable operation support for missing entity errors' do
|
|
77
|
+
operation = build_operation(:host)
|
|
78
|
+
|
|
79
|
+
expect do
|
|
80
|
+
operation.call(name: 'ghost', vars: ['var1=val1'])
|
|
81
|
+
end.to raise_error(Moose::Inventory::DB.exceptions[:moose], "The host 'ghost' does not exist.")
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it 'rejects unsupported entity types via the shared variable operation support' do
|
|
85
|
+
operation = build_operation(:thing)
|
|
86
|
+
|
|
87
|
+
expect do
|
|
88
|
+
operation.call(name: 'whatever', vars: ['var1=val1'])
|
|
89
|
+
end.to raise_error(ArgumentError, /Unsupported entity type/)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it 'emits partial progress before raising on malformed host variable input' do
|
|
93
|
+
@db.models[:host].create(name: 'test1')
|
|
94
|
+
emitted = []
|
|
95
|
+
operation = build_operation(:host, emitter: emitted.method(:<<))
|
|
96
|
+
|
|
97
|
+
expect do
|
|
98
|
+
operation.call(name: 'test1', vars: ['broken'])
|
|
99
|
+
end.to raise_error(Moose::Inventory::DB.exceptions[:moose], /Expected 'key=value'/)
|
|
100
|
+
|
|
101
|
+
expect(emitted.map(&:type)).to eq(%i[entity_started retrieving_entity ok adding_variable])
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'inventory_context'
|
|
5
|
+
require 'operations/group_child_relations'
|
|
6
|
+
|
|
7
|
+
RSpec.describe Moose::Inventory::Operations::GroupChildRelations do
|
|
8
|
+
before(:all) do
|
|
9
|
+
@mockargs = [
|
|
10
|
+
'--config', File.join(spec_root, 'config/config.yml'),
|
|
11
|
+
'--format', 'yaml',
|
|
12
|
+
'--env', 'test'
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
Moose::Inventory::Config.init(@mockargs)
|
|
16
|
+
@db = Moose::Inventory::DB
|
|
17
|
+
@db.init if @db.db.nil?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
before(:each) do
|
|
21
|
+
@db.reset
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def operation
|
|
25
|
+
described_class.new(
|
|
26
|
+
context: Moose::Inventory::InventoryContext.new(db: @db)
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'adds child groups and reports duplicate and auto-create events' do
|
|
31
|
+
parent = @db.models[:group].create(name: 'parent')
|
|
32
|
+
existing = @db.models[:group].create(name: 'existing')
|
|
33
|
+
parent.add_child(existing)
|
|
34
|
+
|
|
35
|
+
result = operation.add_children(
|
|
36
|
+
parent_group: parent,
|
|
37
|
+
parent_name: 'parent',
|
|
38
|
+
child_names: %w[existing created]
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
expect(result.warning_count).to eq(2)
|
|
42
|
+
expect(result.events.map(&:type)).to include(
|
|
43
|
+
:child_association_exists,
|
|
44
|
+
:child_group_missing
|
|
45
|
+
)
|
|
46
|
+
expect(parent.children_dataset[name: 'created']).not_to be_nil
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it 'dry-runs adding child groups without creating children or associations' do
|
|
50
|
+
parent = @db.models[:group].create(name: 'parent')
|
|
51
|
+
|
|
52
|
+
result = operation.add_children(
|
|
53
|
+
parent_group: parent,
|
|
54
|
+
parent_name: 'parent',
|
|
55
|
+
child_names: ['created'],
|
|
56
|
+
dry_run: true
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
expect(result.warning_count).to eq(1)
|
|
60
|
+
expect(result.events.map(&:type)).to include(:child_group_missing, :dry_run_summary)
|
|
61
|
+
expect(@db.models[:group].find(name: 'created')).to be_nil
|
|
62
|
+
expect(parent.children_dataset[name: 'created']).to be_nil
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'dry-runs removing child groups without deleting orphaned descendants' do
|
|
66
|
+
parent = @db.models[:group].create(name: 'parent')
|
|
67
|
+
child = @db.models[:group].create(name: 'child')
|
|
68
|
+
grandchild = @db.models[:group].create(name: 'grandchild')
|
|
69
|
+
host = @db.models[:host].create(name: 'child-host')
|
|
70
|
+
child.add_host(host)
|
|
71
|
+
parent.add_child(child)
|
|
72
|
+
child.add_child(grandchild)
|
|
73
|
+
|
|
74
|
+
result = operation.remove_children(
|
|
75
|
+
parent_group: parent,
|
|
76
|
+
parent_name: 'parent',
|
|
77
|
+
child_names: ['child'],
|
|
78
|
+
delete_orphans: true,
|
|
79
|
+
dry_run: true
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
expect(result.warning_count).to eq(0)
|
|
83
|
+
expect(result.events.map(&:type)).to include(
|
|
84
|
+
:recursively_delete_orphaned_group,
|
|
85
|
+
:destroying_group,
|
|
86
|
+
:adding_automatic_group_to_host,
|
|
87
|
+
:dry_run_summary
|
|
88
|
+
)
|
|
89
|
+
expect(parent.children_dataset[name: 'child']).not_to be_nil
|
|
90
|
+
expect(@db.models[:group].find(name: 'child')).not_to be_nil
|
|
91
|
+
expect(@db.models[:group].find(name: 'grandchild')).not_to be_nil
|
|
92
|
+
expect(host.groups_dataset[name: 'ungrouped']).to be_nil
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it 'removes child groups and recursively deletes orphan groups when requested' do
|
|
96
|
+
parent = @db.models[:group].create(name: 'parent')
|
|
97
|
+
child = @db.models[:group].create(name: 'child')
|
|
98
|
+
grandchild = @db.models[:group].create(name: 'grandchild')
|
|
99
|
+
host = @db.models[:host].create(name: 'child-host')
|
|
100
|
+
child.add_host(host)
|
|
101
|
+
parent.add_child(child)
|
|
102
|
+
child.add_child(grandchild)
|
|
103
|
+
|
|
104
|
+
result = operation.remove_children(
|
|
105
|
+
parent_group: parent,
|
|
106
|
+
parent_name: 'parent',
|
|
107
|
+
child_names: %w[missing child],
|
|
108
|
+
delete_orphans: true
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
expect(result.warning_count).to eq(1)
|
|
112
|
+
expect(result.events.map(&:type)).to include(
|
|
113
|
+
:child_association_missing,
|
|
114
|
+
:recursively_delete_orphaned_group,
|
|
115
|
+
:destroying_group,
|
|
116
|
+
:adding_automatic_group_to_host
|
|
117
|
+
)
|
|
118
|
+
expect(@db.models[:group].find(name: 'child')).to be_nil
|
|
119
|
+
expect(@db.models[:group].find(name: 'grandchild')).to be_nil
|
|
120
|
+
expect(host.groups_dataset[name: 'ungrouped']).not_to be_nil
|
|
121
|
+
end
|
|
122
|
+
end
|