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
|
@@ -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
|
|
@@ -50,6 +50,40 @@ RSpec.describe Moose::Inventory::Operations::AddAssociations do
|
|
|
50
50
|
expect(host.groups_dataset[name: 'ungrouped']).to be_nil
|
|
51
51
|
end
|
|
52
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
|
|
53
87
|
it 'adds hosts to an existing group and reports creation/duplicate events' do
|
|
54
88
|
group = @db.models[:group].create(name: 'group1')
|
|
55
89
|
duplicate_host = @db.models[:host].create(name: 'host1')
|
|
@@ -40,6 +40,21 @@ RSpec.describe Moose::Inventory::Operations::AddGroups do
|
|
|
40
40
|
expect(group).not_to be_nil
|
|
41
41
|
end
|
|
42
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
|
|
43
58
|
it 'reports existing groups, created hosts, duplicate associations, and ungrouped removal as events' do
|
|
44
59
|
host = @db.models[:host].create(name: 'testhost')
|
|
45
60
|
ungrouped = @db.models[:group].find_or_create(name: 'ungrouped')
|
|
@@ -43,6 +43,19 @@ RSpec.describe Moose::Inventory::Operations::AddHosts do
|
|
|
43
43
|
expect(host.groups_dataset[name: 'ungrouped']).not_to be_nil
|
|
44
44
|
end
|
|
45
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
|
|
46
59
|
it 'reports existing hosts, missing groups, and duplicate associations as events' do
|
|
47
60
|
host = @db.models[:host].create(name: 'testhost')
|
|
48
61
|
group = @db.models[:group].create(name: 'existinggroup')
|
|
@@ -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
|
|
@@ -46,6 +46,52 @@ RSpec.describe Moose::Inventory::Operations::GroupChildRelations do
|
|
|
46
46
|
expect(parent.children_dataset[name: 'created']).not_to be_nil
|
|
47
47
|
end
|
|
48
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
|
+
|
|
49
95
|
it 'removes child groups and recursively deletes orphan groups when requested' do
|
|
50
96
|
parent = @db.models[:group].create(name: 'parent')
|
|
51
97
|
child = @db.models[:group].create(name: 'child')
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# rubocop:disable Metrics/BlockLength
|
|
4
|
+
require 'spec_helper'
|
|
5
|
+
require 'inventory_context'
|
|
6
|
+
require 'operations/inventory_snapshot'
|
|
7
|
+
require 'operations/import_inventory_snapshot'
|
|
8
|
+
|
|
9
|
+
RSpec.describe Moose::Inventory::Operations::ImportInventorySnapshot do
|
|
10
|
+
before(:all) do
|
|
11
|
+
@mockargs = [
|
|
12
|
+
'--config', File.join(spec_root, 'config/config.yml'),
|
|
13
|
+
'--format', 'yaml',
|
|
14
|
+
'--env', 'test'
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
Moose::Inventory::Config.init(@mockargs)
|
|
18
|
+
@db = Moose::Inventory::DB
|
|
19
|
+
@db.init if @db.db.nil?
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
before(:each) do
|
|
23
|
+
@db.reset
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def operation
|
|
27
|
+
described_class.new(context: Moose::Inventory::InventoryContext.new(db: @db))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'imports a validated snapshot transactionally' do
|
|
31
|
+
snapshot = {
|
|
32
|
+
'version' => 1,
|
|
33
|
+
'hosts' => {
|
|
34
|
+
'web01' => { 'groups' => ['web'], 'tags' => [], 'vars' => { 'env' => 'prod' } }
|
|
35
|
+
},
|
|
36
|
+
'groups' => {
|
|
37
|
+
'web' => { 'children' => ['blue'], 'tags' => [], 'vars' => { 'role' => 'frontend' } },
|
|
38
|
+
'blue' => { 'children' => [], 'tags' => [], 'vars' => {} }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
result = operation.call(snapshot: snapshot)
|
|
43
|
+
|
|
44
|
+
expect(result.created_hosts).to eq(1)
|
|
45
|
+
expect(result.created_groups).to eq(2)
|
|
46
|
+
expect(result.updated_variables).to eq(2)
|
|
47
|
+
expect(result.associations).to eq(2)
|
|
48
|
+
host = @db.models[:host].find(name: 'web01')
|
|
49
|
+
group = @db.models[:group].find(name: 'web')
|
|
50
|
+
expect(host.groups_dataset[name: 'web']).not_to be_nil
|
|
51
|
+
expect(host.hostvars_dataset[name: 'env'][:value]).to eq('prod')
|
|
52
|
+
expect(group.children_dataset[name: 'blue']).not_to be_nil
|
|
53
|
+
expect(group.groupvars_dataset[name: 'role'][:value]).to eq('frontend')
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it 'normalizes imported tag casing and deduplicates tags before applying' do
|
|
57
|
+
snapshot = {
|
|
58
|
+
version: 1,
|
|
59
|
+
hosts: {
|
|
60
|
+
web01: { groups: ['web'], tags: ['Prod', 'prod', ' OWNER-Platform ', ''], vars: {} }
|
|
61
|
+
},
|
|
62
|
+
groups: {
|
|
63
|
+
web: { children: [], tags: %w[Frontend frontend], vars: {} }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
result = operation.call(snapshot: snapshot)
|
|
68
|
+
|
|
69
|
+
host = @db.models[:host].find(name: 'web01')
|
|
70
|
+
group = @db.models[:group].find(name: 'web')
|
|
71
|
+
expect(result.associations).to eq(4)
|
|
72
|
+
expect(host.tags_dataset.order(:name).map(:name)).to eq(%w[owner-platform prod])
|
|
73
|
+
expect(group.tags_dataset.order(:name).map(:name)).to eq(%w[frontend])
|
|
74
|
+
expect(@db.models[:tag].order(:name).map(:name)).to eq(%w[frontend owner-platform prod])
|
|
75
|
+
expect(@db.models[:tag].where(name: 'Prod').count).to eq(0)
|
|
76
|
+
|
|
77
|
+
exported = Moose::Inventory::Operations::InventorySnapshot.new(
|
|
78
|
+
context: Moose::Inventory::InventoryContext.new(db: @db)
|
|
79
|
+
).export
|
|
80
|
+
expect(exported.dig('hosts', 'web01', 'tags')).to eq(%w[owner-platform prod])
|
|
81
|
+
expect(exported.dig('groups', 'web', 'tags')).to eq(%w[frontend])
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it 'rejects unknown group references before writing anything' do
|
|
85
|
+
snapshot = {
|
|
86
|
+
version: 1,
|
|
87
|
+
hosts: { web01: { groups: ['missing'], vars: {} } },
|
|
88
|
+
groups: {}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
expect do
|
|
92
|
+
operation.call(snapshot: snapshot)
|
|
93
|
+
end.to raise_error(Moose::Inventory::DB.exceptions[:moose], /references unknown group 'missing'/)
|
|
94
|
+
|
|
95
|
+
expect(@db.models[:host].count).to eq(0)
|
|
96
|
+
expect(@db.models[:group].count).to eq(0)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it 'rejects whitespace-only entity names before writing anything' do
|
|
100
|
+
snapshot = {
|
|
101
|
+
version: 1,
|
|
102
|
+
hosts: { ' ' => { groups: [], vars: {} } },
|
|
103
|
+
groups: {}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
expect do
|
|
107
|
+
operation.call(snapshot: snapshot)
|
|
108
|
+
end.to raise_error(Moose::Inventory::DB.exceptions[:moose], /host name cannot be empty/)
|
|
109
|
+
|
|
110
|
+
expect(@db.models[:host].count).to eq(0)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
it 'rejects duplicate normalized keys before applying the snapshot' do
|
|
114
|
+
snapshot = {
|
|
115
|
+
'version' => 1,
|
|
116
|
+
:version => 1,
|
|
117
|
+
hosts: {},
|
|
118
|
+
groups: {}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
expect do
|
|
122
|
+
operation.call(snapshot: snapshot)
|
|
123
|
+
end.to raise_error(Moose::Inventory::DB.exceptions[:moose], /duplicate normalized key 'version'/)
|
|
124
|
+
|
|
125
|
+
expect(@db.models[:host].count).to eq(0)
|
|
126
|
+
expect(@db.models[:group].count).to eq(0)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
it 'rejects whitespace-only variable names before writing anything' do
|
|
130
|
+
snapshot = {
|
|
131
|
+
version: 1,
|
|
132
|
+
hosts: { web01: { groups: [], vars: { ' ' => 'prod' } } },
|
|
133
|
+
groups: {}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
expect do
|
|
137
|
+
operation.call(snapshot: snapshot)
|
|
138
|
+
end.to raise_error(Moose::Inventory::DB.exceptions[:moose], /variable name cannot be empty/)
|
|
139
|
+
|
|
140
|
+
expect(@db.models[:host].count).to eq(0)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
it 'rejects circular group hierarchies before writing anything' do
|
|
144
|
+
snapshot = {
|
|
145
|
+
version: 1,
|
|
146
|
+
hosts: {},
|
|
147
|
+
groups: {
|
|
148
|
+
parent: { children: ['child'], vars: {} },
|
|
149
|
+
child: { children: ['parent'], vars: {} }
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
expect do
|
|
154
|
+
operation.call(snapshot: snapshot)
|
|
155
|
+
end.to raise_error(Moose::Inventory::DB.exceptions[:moose], /contains a cycle/)
|
|
156
|
+
|
|
157
|
+
expect(@db.models[:group].count).to eq(0)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
# rubocop:enable Metrics/BlockLength
|
|
161
|
+
|
|
162
|
+
# rubocop:disable Metrics/BlockLength
|
|
163
|
+
|
|
164
|
+
RSpec.describe Moose::Inventory::Operations::ImportInventorySnapshot, '#preview' do
|
|
165
|
+
before(:all) do
|
|
166
|
+
@mockargs = [
|
|
167
|
+
'--config', File.join(spec_root, 'config/config.yml'),
|
|
168
|
+
'--format', 'yaml',
|
|
169
|
+
'--env', 'test'
|
|
170
|
+
]
|
|
171
|
+
|
|
172
|
+
Moose::Inventory::Config.init(@mockargs)
|
|
173
|
+
@db = Moose::Inventory::DB
|
|
174
|
+
@db.init if @db.db.nil?
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
before(:each) do
|
|
178
|
+
@db.reset
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def operation
|
|
182
|
+
described_class.new(context: Moose::Inventory::InventoryContext.new(db: @db))
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
it 'returns a non-mutating snapshot import diff' do
|
|
186
|
+
runner = operation
|
|
187
|
+
@db.models[:host].create(name: 'existing')
|
|
188
|
+
group = @db.models[:group].create(name: 'web')
|
|
189
|
+
var = @db.models[:groupvar].create(name: 'role', value: 'old')
|
|
190
|
+
group.add_groupvar(var)
|
|
191
|
+
|
|
192
|
+
snapshot = {
|
|
193
|
+
version: 1,
|
|
194
|
+
hosts: {
|
|
195
|
+
web01: { groups: ['web'], tags: ['Prod'], vars: { env: 'prod' } }
|
|
196
|
+
},
|
|
197
|
+
groups: {
|
|
198
|
+
web: { children: ['blue'], tags: ['Frontend'], vars: { role: 'frontend' } },
|
|
199
|
+
blue: { children: [], tags: [], vars: {} }
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
preview = runner.preview(snapshot: snapshot)
|
|
204
|
+
|
|
205
|
+
expect(preview['schema_version']).to eq('snapshot-import-preview-v1')
|
|
206
|
+
expect(preview['changes_applied']).to eq(false)
|
|
207
|
+
expect(preview['summary']).to include(
|
|
208
|
+
'hosts_created' => 1,
|
|
209
|
+
'groups_created' => 1,
|
|
210
|
+
'variables_changed' => 2,
|
|
211
|
+
'associations_added' => 4,
|
|
212
|
+
'ignored_existing_hosts' => 1,
|
|
213
|
+
'destructive_changes' => 0
|
|
214
|
+
)
|
|
215
|
+
expect(preview.dig('creates', 'hosts')).to eq(['web01'])
|
|
216
|
+
expect(preview.dig('creates', 'groups')).to eq(['blue'])
|
|
217
|
+
expect(preview.dig('updates', 'group_vars')).to include(
|
|
218
|
+
'entity' => 'web', 'name' => 'role', 'from' => 'old', 'to' => 'frontend'
|
|
219
|
+
)
|
|
220
|
+
expect(preview.dig('ignored', 'existing_hosts_not_in_snapshot')).to eq(['existing'])
|
|
221
|
+
expect(@db.models[:host].where(name: 'web01').count).to eq(0)
|
|
222
|
+
expect(@db.models[:group].where(name: 'blue').count).to eq(0)
|
|
223
|
+
expect(group.groupvars_dataset[name: 'role'][:value]).to eq('old')
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
# rubocop:enable Metrics/BlockLength
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# rubocop:disable Metrics/BlockLength
|
|
4
|
+
require 'spec_helper'
|
|
5
|
+
require 'inventory_context'
|
|
6
|
+
require 'operations/inventory_doctor'
|
|
7
|
+
|
|
8
|
+
RSpec.describe Moose::Inventory::Operations::InventoryDoctor do
|
|
9
|
+
before(:all) do
|
|
10
|
+
@mockargs = [
|
|
11
|
+
'--config', File.join(spec_root, 'config/config.yml'),
|
|
12
|
+
'--format', 'yaml',
|
|
13
|
+
'--env', 'test'
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
Moose::Inventory::Config.init(@mockargs)
|
|
17
|
+
@db = Moose::Inventory::DB
|
|
18
|
+
@db.init if @db.db.nil?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
before(:each) do
|
|
22
|
+
@db.reset
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def doctor(config: Moose::Inventory::Config)
|
|
26
|
+
described_class.new(context: Moose::Inventory::InventoryContext.new(db: @db), config: config)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'reports ok when the inventory and config have no findings' do
|
|
30
|
+
web = @db.models[:group].create(name: 'web')
|
|
31
|
+
host = @db.models[:host].create(name: 'web01')
|
|
32
|
+
host.add_group(web)
|
|
33
|
+
|
|
34
|
+
report = doctor.call
|
|
35
|
+
|
|
36
|
+
expect(report[:ok]).to eq(true)
|
|
37
|
+
expect(report[:issues]).to eq([])
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'detects inventory health findings' do
|
|
41
|
+
ungrouped = @db.models[:group].create(name: 'ungrouped')
|
|
42
|
+
orphan = @db.models[:group].create(name: 'orphan')
|
|
43
|
+
duplicate = @db.models[:group].create(name: 'or-phan')
|
|
44
|
+
parent = @db.models[:group].create(name: 'parent')
|
|
45
|
+
child = @db.models[:group].create(name: 'child')
|
|
46
|
+
host = @db.models[:host].create(name: 'lonely')
|
|
47
|
+
bad_var = @db.models[:hostvar].create(name: '', value: 'oops')
|
|
48
|
+
host.add_group(ungrouped)
|
|
49
|
+
host.add_hostvar(bad_var)
|
|
50
|
+
parent.add_child(child)
|
|
51
|
+
child.add_child(parent)
|
|
52
|
+
|
|
53
|
+
report = doctor.call
|
|
54
|
+
@db.db[:groups_groups].delete
|
|
55
|
+
|
|
56
|
+
expect(report[:ok]).to eq(false)
|
|
57
|
+
expect(report[:issues].map { |issue| issue[:id] }).to include(
|
|
58
|
+
'host_only_in_ungrouped',
|
|
59
|
+
'orphaned_group',
|
|
60
|
+
'empty_group',
|
|
61
|
+
'duplicateish_group_names',
|
|
62
|
+
'invalid_variable_shape',
|
|
63
|
+
'circular_group_relationship'
|
|
64
|
+
)
|
|
65
|
+
expect(report[:issues].map { |issue| issue[:subject] }).to include(orphan.name)
|
|
66
|
+
expect(report[:issues].map { |issue| issue[:subject] }).to include([duplicate.name, orphan.name].sort)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it 'detects plaintext password configuration' do
|
|
70
|
+
config = instance_double('Config', db_settings: { adapter: 'mysql', password: 'secret' })
|
|
71
|
+
|
|
72
|
+
report = doctor(config: config).call
|
|
73
|
+
|
|
74
|
+
expect(report[:issues].map { |issue| issue[:id] }).to include('plaintext_password_config')
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
# rubocop:enable Metrics/BlockLength
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# rubocop:disable Metrics/BlockLength
|
|
4
|
+
require 'spec_helper'
|
|
5
|
+
require 'inventory_context'
|
|
6
|
+
require 'operations/inventory_snapshot'
|
|
7
|
+
|
|
8
|
+
RSpec.describe Moose::Inventory::Operations::InventorySnapshot do
|
|
9
|
+
before(:all) do
|
|
10
|
+
@mockargs = [
|
|
11
|
+
'--config', File.join(spec_root, 'config/config.yml'),
|
|
12
|
+
'--format', 'yaml',
|
|
13
|
+
'--env', 'test'
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
Moose::Inventory::Config.init(@mockargs)
|
|
17
|
+
@db = Moose::Inventory::DB
|
|
18
|
+
@db.init if @db.db.nil?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
before(:each) do
|
|
22
|
+
@db.reset
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'exports hosts, groups, variables, and child relationships in canonical order' do
|
|
26
|
+
host = @db.models[:host].create(name: 'web01')
|
|
27
|
+
group = @db.models[:group].create(name: 'web')
|
|
28
|
+
child = @db.models[:group].create(name: 'blue')
|
|
29
|
+
hostvar = @db.models[:hostvar].create(name: 'env', value: 'prod')
|
|
30
|
+
groupvar = @db.models[:groupvar].create(name: 'role', value: 'frontend')
|
|
31
|
+
host.add_group(group)
|
|
32
|
+
host.add_hostvar(hostvar)
|
|
33
|
+
group.add_child(child)
|
|
34
|
+
group.add_groupvar(groupvar)
|
|
35
|
+
|
|
36
|
+
snapshot = described_class.new(context: Moose::Inventory::InventoryContext.new(db: @db)).export
|
|
37
|
+
|
|
38
|
+
expect(snapshot).to eq(
|
|
39
|
+
'version' => 1,
|
|
40
|
+
'hosts' => {
|
|
41
|
+
'web01' => { 'groups' => ['web'], 'tags' => [], 'vars' => { 'env' => 'prod' } }
|
|
42
|
+
},
|
|
43
|
+
'groups' => {
|
|
44
|
+
'blue' => { 'children' => [], 'tags' => [], 'vars' => {} },
|
|
45
|
+
'web' => { 'children' => ['blue'], 'tags' => [], 'vars' => { 'role' => 'frontend' } }
|
|
46
|
+
}
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
# rubocop:enable Metrics/BlockLength
|