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
|
@@ -1,44 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# rubocop:disable Metrics/BlockLength
|
|
1
4
|
require 'spec_helper'
|
|
2
5
|
|
|
3
|
-
# TODO: the usual respond_to? method doesn't seem to work on Thor objects.
|
|
4
6
|
# Why not? For now, we'll check against instance_methods.
|
|
5
7
|
|
|
6
8
|
RSpec.describe Moose::Inventory::Cli::Group do
|
|
7
9
|
before(:all) do
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
@mockargs = []
|
|
16
|
-
@mockarg_parts.each do |key, val|
|
|
17
|
-
@mockargs << "--#{key}"
|
|
18
|
-
@mockargs << val
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
@console = Moose::Inventory::Cli::Formatter
|
|
22
|
-
@config = Moose::Inventory::Config
|
|
23
|
-
@config.init(@mockargs)
|
|
24
|
-
|
|
25
|
-
@db = Moose::Inventory::DB
|
|
26
|
-
@db.init if @db.db.nil?
|
|
27
|
-
|
|
28
|
-
@group = Moose::Inventory::Cli::Group
|
|
29
|
-
@host = Moose::Inventory::Cli::Host
|
|
30
|
-
@app = Moose::Inventory::Cli::Application
|
|
10
|
+
setup_cli_harness(
|
|
11
|
+
command_class: Moose::Inventory::Cli::Group,
|
|
12
|
+
command_ivar: :@group,
|
|
13
|
+
extra_commands: { :@host => Moose::Inventory::Cli::Host }
|
|
14
|
+
)
|
|
31
15
|
end
|
|
32
16
|
|
|
33
17
|
before(:each) do
|
|
34
|
-
|
|
18
|
+
reset_cli_harness
|
|
35
19
|
end
|
|
36
20
|
|
|
37
21
|
#====================
|
|
38
22
|
describe 'rmgroup' do
|
|
39
23
|
#----------------
|
|
40
24
|
it 'should be responsive' do
|
|
41
|
-
result = @group.
|
|
25
|
+
result = @group.method_defined?(:rmhost, false)
|
|
42
26
|
expect(result).to eq(true)
|
|
43
27
|
end
|
|
44
28
|
|
|
@@ -47,7 +31,7 @@ RSpec.describe Moose::Inventory::Cli::Group do
|
|
|
47
31
|
#------------------------
|
|
48
32
|
it '<missing args> ... should abort with an error' do
|
|
49
33
|
actual = runner do
|
|
50
|
-
@app.start(%w
|
|
34
|
+
@app.start(%w[group rmhost]) # <- no group or hosts given
|
|
51
35
|
end
|
|
52
36
|
|
|
53
37
|
# Check output
|
|
@@ -61,16 +45,16 @@ RSpec.describe Moose::Inventory::Cli::Group do
|
|
|
61
45
|
group_name = 'not-a-group'
|
|
62
46
|
host_name = 'example'
|
|
63
47
|
actual = runner do
|
|
64
|
-
@app.start(%W
|
|
48
|
+
@app.start(%W[group rmhost #{group_name} #{host_name} --yes])
|
|
65
49
|
end
|
|
66
50
|
|
|
67
51
|
# Check output
|
|
68
52
|
desired = { aborted: true }
|
|
69
53
|
desired[:STDOUT] =
|
|
70
|
-
"Dissociate group '#{group_name}' from host(s) '#{host_name}':\n"\
|
|
71
|
-
"
|
|
54
|
+
"Dissociate group '#{group_name}' from host(s) '#{host_name}':\n " \
|
|
55
|
+
"- retrieve group '#{group_name}'...\n"
|
|
72
56
|
desired[:STDERR] =
|
|
73
|
-
"ERROR: The group '#{group_name}' does not exist.\n"\
|
|
57
|
+
"ERROR: The group '#{group_name}' does not exist.\n" \
|
|
74
58
|
"An error occurred during a transaction, any changes have been rolled back.\n"
|
|
75
59
|
expected(actual, desired)
|
|
76
60
|
end
|
|
@@ -80,9 +64,9 @@ RSpec.describe Moose::Inventory::Cli::Group do
|
|
|
80
64
|
host_name = 'test1'
|
|
81
65
|
group_name = 'group1'
|
|
82
66
|
|
|
83
|
-
runner { @app.start(%W
|
|
84
|
-
runner { @app.start(%W
|
|
85
|
-
runner { @app.start(%W
|
|
67
|
+
runner { @app.start(%W[host add #{host_name}]) }
|
|
68
|
+
runner { @app.start(%W[group add #{group_name}]) }
|
|
69
|
+
runner { @app.start(%W[group addhost #{group_name} #{host_name}]) }
|
|
86
70
|
|
|
87
71
|
#
|
|
88
72
|
# Dissociate the host
|
|
@@ -90,32 +74,48 @@ RSpec.describe Moose::Inventory::Cli::Group do
|
|
|
90
74
|
# 2. expect that no association with ungrouped is made.
|
|
91
75
|
|
|
92
76
|
actual = runner do
|
|
93
|
-
@app.start(%W
|
|
77
|
+
@app.start(%W[group rmhost #{group_name} #{host_name} --yes])
|
|
94
78
|
end
|
|
95
79
|
|
|
96
80
|
# @console.dump(actual, 'y')
|
|
97
81
|
|
|
98
|
-
# rubocop:disable Metrics/LineLength
|
|
99
82
|
desired = { aborted: false }
|
|
100
83
|
desired[:STDOUT] =
|
|
101
|
-
"Dissociate group '#{group_name}' from host(s) '#{host_name}':\n"\
|
|
102
|
-
"
|
|
103
|
-
"
|
|
104
|
-
"
|
|
105
|
-
"
|
|
106
|
-
"
|
|
107
|
-
"
|
|
108
|
-
"
|
|
84
|
+
"Dissociate group '#{group_name}' from host(s) '#{host_name}':\n " \
|
|
85
|
+
"- retrieve group '#{group_name}'...\n " \
|
|
86
|
+
"- OK\n " \
|
|
87
|
+
"- remove association {group:#{group_name} <-> host:#{host_name}}...\n " \
|
|
88
|
+
"- OK\n " \
|
|
89
|
+
"- add automatic association {group:ungrouped <-> host:#{host_name}}...\n " \
|
|
90
|
+
"- OK\n " \
|
|
91
|
+
"- all OK\n" \
|
|
109
92
|
"Succeeded.\n"
|
|
110
93
|
expected(actual, desired)
|
|
111
|
-
# rubocop:enable Metrics/LineLength
|
|
112
|
-
|
|
113
94
|
# We should have the correct group associations
|
|
114
95
|
group = @db.models[:group].find(name: group_name)
|
|
115
96
|
hosts = group.hosts_dataset
|
|
116
97
|
expect(hosts.count).to eq(0)
|
|
117
98
|
end
|
|
118
99
|
|
|
100
|
+
#------------------------
|
|
101
|
+
it 'group rmhost GROUP HOST --dry-run should not remove membership or add ungrouped' do
|
|
102
|
+
host_name = 'test1'
|
|
103
|
+
group_name = 'group1'
|
|
104
|
+
runner { @app.start(%W[host add #{host_name}]) }
|
|
105
|
+
runner { @app.start(%W[group add #{group_name}]) }
|
|
106
|
+
runner { @app.start(%W[group addhost #{group_name} #{host_name}]) }
|
|
107
|
+
|
|
108
|
+
actual = runner { @app.start(%W[group rmhost #{group_name} #{host_name} --dry-run]) }
|
|
109
|
+
|
|
110
|
+
expect(actual[:unexpected]).to eq(false)
|
|
111
|
+
expect(actual[:aborted]).to eq(false)
|
|
112
|
+
expect(actual[:STDOUT]).to include('Dry run complete. No changes applied.')
|
|
113
|
+
group = @db.models[:group].find(name: group_name)
|
|
114
|
+
host = @db.models[:host].find(name: host_name)
|
|
115
|
+
expect(group.hosts_dataset[name: host_name]).not_to be_nil
|
|
116
|
+
expect(host.groups_dataset[name: 'ungrouped']).to be_nil
|
|
117
|
+
end
|
|
118
|
+
|
|
119
119
|
#------------------------
|
|
120
120
|
it 'GROUP HOST ... should warn about non-existing associations' do
|
|
121
121
|
# 1. Should warn that the association doesn't exist.
|
|
@@ -123,28 +123,27 @@ RSpec.describe Moose::Inventory::Cli::Group do
|
|
|
123
123
|
|
|
124
124
|
host_name = 'test_host'
|
|
125
125
|
group_name = 'test_group'
|
|
126
|
-
runner { @app.start(%W
|
|
127
|
-
runner { @app.start(%W
|
|
128
|
-
runner { @app.start(%W
|
|
126
|
+
runner { @app.start(%W[host add #{host_name}]) }
|
|
127
|
+
runner { @app.start(%W[group add #{group_name}]) }
|
|
128
|
+
runner { @app.start(%W[group addhost #{host_name}]) }
|
|
129
129
|
|
|
130
130
|
actual = runner do
|
|
131
|
-
@app.start(%W
|
|
131
|
+
@app.start(%W[group rmhost #{group_name} #{host_name} --yes])
|
|
132
132
|
end
|
|
133
133
|
|
|
134
|
-
# rubocop:disable Metrics/LineLength
|
|
135
134
|
desired = { aborted: false }
|
|
136
135
|
desired[:STDOUT] =
|
|
137
|
-
"Dissociate group '#{group_name}' from host(s) '#{host_name}':\n"\
|
|
138
|
-
"
|
|
139
|
-
"
|
|
140
|
-
"
|
|
141
|
-
"
|
|
142
|
-
"
|
|
143
|
-
"
|
|
136
|
+
"Dissociate group '#{group_name}' from host(s) '#{host_name}':\n " \
|
|
137
|
+
"- retrieve group '#{group_name}'...\n " \
|
|
138
|
+
"- OK\n " \
|
|
139
|
+
"- remove association {group:#{group_name} <-> host:#{host_name}}...\n " \
|
|
140
|
+
"- doesn't exist, skipping.\n " \
|
|
141
|
+
"- OK\n " \
|
|
142
|
+
"- all OK\n" \
|
|
144
143
|
"Succeeded, with warnings.\n"
|
|
145
144
|
desired[:STDERR] =
|
|
146
|
-
"WARNING: Association {group:#{group_name} <-> host:#{host_name}} "\
|
|
147
|
-
|
|
145
|
+
"WARNING: Association {group:#{group_name} <-> host:#{host_name}} " \
|
|
146
|
+
"doesn't exist, skipping.\n"
|
|
148
147
|
|
|
149
148
|
expected(actual, desired)
|
|
150
149
|
end
|
|
@@ -154,9 +153,9 @@ RSpec.describe Moose::Inventory::Cli::Group do
|
|
|
154
153
|
host_name = 'test_host'
|
|
155
154
|
group_name = 'ungrouped'
|
|
156
155
|
|
|
157
|
-
runner { @app.start(%W
|
|
156
|
+
runner { @app.start(%W[host add #{name}]) } # <- auto creates the association with ungrouped
|
|
158
157
|
|
|
159
|
-
actual = runner { @app.start(%W
|
|
158
|
+
actual = runner { @app.start(%W[group rmhost #{group_name} #{host_name} --yes]) }
|
|
160
159
|
|
|
161
160
|
desired = { aborted: true }
|
|
162
161
|
desired[:STDERR] =
|
|
@@ -165,41 +164,39 @@ RSpec.describe Moose::Inventory::Cli::Group do
|
|
|
165
164
|
end
|
|
166
165
|
|
|
167
166
|
#------------------------
|
|
168
|
-
it 'GROUP HOST1 HOST2 ... should dissociate the group from'\
|
|
169
|
-
|
|
167
|
+
it 'GROUP HOST1 HOST2 ... should dissociate the group from ' \
|
|
168
|
+
'multiple hosts at once' do
|
|
170
169
|
# 1. Should dissociate hosts from the group
|
|
171
170
|
# 2. Should add each host to the 'ungrouped' automatic group
|
|
172
171
|
# if it has no other groups.
|
|
173
172
|
|
|
174
173
|
group_name = 'test_group'
|
|
175
|
-
host_names = %w
|
|
174
|
+
host_names = %w[test_host1 test_host2 test_host3]
|
|
176
175
|
|
|
177
|
-
runner { @app.start(%W
|
|
178
|
-
runner { @app.start(%W
|
|
176
|
+
runner { @app.start(%W[group add #{group_name}]) }
|
|
177
|
+
runner { @app.start(%W[group addhost #{group_name}] + host_names) }
|
|
179
178
|
|
|
180
179
|
actual = runner do
|
|
181
|
-
@app.start(%W
|
|
180
|
+
@app.start(%W[group rmhost #{group_name} --yes] + host_names)
|
|
182
181
|
end
|
|
183
182
|
|
|
184
183
|
# @console.out(actual, 'y')
|
|
185
184
|
desired = { aborted: false }
|
|
186
185
|
desired[:STDOUT] =
|
|
187
|
-
"Dissociate group '#{group_name}' from host(s) '#{host_names.join(',')}':\n"\
|
|
188
|
-
"
|
|
189
|
-
"
|
|
186
|
+
"Dissociate group '#{group_name}' from host(s) '#{host_names.join(',')}':\n " \
|
|
187
|
+
"- retrieve group '#{group_name}'...\n " \
|
|
188
|
+
"- OK\n"
|
|
190
189
|
host_names.each do |host|
|
|
191
190
|
desired[:STDOUT] = desired[:STDOUT] +
|
|
192
|
-
" - remove association {group:#{group_name} <-> host:#{host}}...\n"\
|
|
193
|
-
"
|
|
194
|
-
"
|
|
195
|
-
"
|
|
191
|
+
" - remove association {group:#{group_name} <-> host:#{host}}...\n " \
|
|
192
|
+
"- OK\n " \
|
|
193
|
+
"- add automatic association {group:ungrouped <-> host:#{host}}...\n " \
|
|
194
|
+
"- OK\n" \
|
|
196
195
|
end
|
|
197
196
|
desired[:STDOUT] = desired[:STDOUT] +
|
|
198
|
-
" - all OK\n"\
|
|
197
|
+
" - all OK\n" \
|
|
199
198
|
"Succeeded.\n"
|
|
200
199
|
expected(actual, desired)
|
|
201
|
-
# rubocop:enable Metrics/LineLength
|
|
202
|
-
|
|
203
200
|
# We should have the correct group associations
|
|
204
201
|
group = @db.models[:group].find(name: group_name)
|
|
205
202
|
hosts = group.hosts_dataset
|
|
@@ -210,3 +207,4 @@ RSpec.describe Moose::Inventory::Cli::Group do
|
|
|
210
207
|
end
|
|
211
208
|
end
|
|
212
209
|
end
|
|
210
|
+
# rubocop:enable Metrics/BlockLength
|
|
@@ -1,48 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# rubocop:disable Metrics/BlockLength
|
|
1
4
|
require 'spec_helper'
|
|
2
5
|
|
|
3
|
-
# TODO: the usual respond_to? method doesn't seem to work on Thor objects.
|
|
4
6
|
# Why not? For now, we'll check against instance_methods.
|
|
5
7
|
|
|
6
8
|
RSpec.describe Moose::Inventory::Cli::Group do
|
|
7
9
|
before(:all) do
|
|
8
|
-
|
|
9
|
-
@mockarg_parts = {
|
|
10
|
-
config: File.join(spec_root, 'config/config.yml'),
|
|
11
|
-
format: 'yaml',
|
|
12
|
-
env: 'test',
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
@mockargs = []
|
|
16
|
-
@mockarg_parts.each do |key, val|
|
|
17
|
-
@mockargs << "--#{key}"
|
|
18
|
-
@mockargs << val
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
@config = Moose::Inventory::Config
|
|
22
|
-
@config.init(@mockargs)
|
|
23
|
-
|
|
24
|
-
@db = Moose::Inventory::DB
|
|
25
|
-
@db.init if @db.db.nil?
|
|
26
|
-
|
|
27
|
-
@console = Moose::Inventory::Cli::Formatter
|
|
28
|
-
@group = Moose::Inventory::Cli::Group
|
|
29
|
-
@app = Moose::Inventory::Cli::Application
|
|
10
|
+
setup_cli_harness(command_class: Moose::Inventory::Cli::Group, command_ivar: :@group)
|
|
30
11
|
end
|
|
31
12
|
|
|
32
13
|
before(:each) do
|
|
33
|
-
|
|
14
|
+
reset_cli_harness
|
|
34
15
|
end
|
|
35
16
|
|
|
36
17
|
describe 'rmvar' do
|
|
37
18
|
it 'should be responsive' do
|
|
38
|
-
result = @group.
|
|
19
|
+
result = @group.method_defined?(:rmvar, false)
|
|
39
20
|
expect(result).to eq(true)
|
|
40
21
|
end
|
|
41
22
|
|
|
42
23
|
#-----------------
|
|
43
24
|
it '<missing args> ... should abort with an error' do
|
|
44
25
|
actual = runner do
|
|
45
|
-
@app.start(%w
|
|
26
|
+
@app.start(%w[group rmvar])
|
|
46
27
|
end
|
|
47
28
|
|
|
48
29
|
# Check output
|
|
@@ -56,16 +37,16 @@ RSpec.describe Moose::Inventory::Cli::Group do
|
|
|
56
37
|
group_name = 'does-not-exist'
|
|
57
38
|
var_name = 'foo=bar'
|
|
58
39
|
actual = runner do
|
|
59
|
-
@app.start(%W
|
|
40
|
+
@app.start(%W[group rmvar #{group_name} #{var_name} --yes])
|
|
60
41
|
end
|
|
61
42
|
|
|
62
43
|
# Check output
|
|
63
44
|
desired = { aborted: true }
|
|
64
45
|
desired[:STDOUT] =
|
|
65
|
-
"Remove variable(s) '#{var_name}' from group '#{group_name}':\n"\
|
|
66
|
-
"
|
|
46
|
+
"Remove variable(s) '#{var_name}' from group '#{group_name}':\n " \
|
|
47
|
+
"- retrieve group '#{group_name}'...\n"
|
|
67
48
|
desired[:STDERR] =
|
|
68
|
-
"An error occurred during a transaction, any changes have been rolled back.\n"\
|
|
49
|
+
"An error occurred during a transaction, any changes have been rolled back.\n" \
|
|
69
50
|
"ERROR: The group '#{group_name}' does not exist.\n"
|
|
70
51
|
expected(actual, desired)
|
|
71
52
|
end
|
|
@@ -77,44 +58,57 @@ RSpec.describe Moose::Inventory::Cli::Group do
|
|
|
77
58
|
|
|
78
59
|
group_name = 'test1'
|
|
79
60
|
@db.models[:group].create(name: group_name)
|
|
80
|
-
|
|
81
|
-
var = { name: 'foo', value: 'bar' }
|
|
82
|
-
cases = %w(
|
|
61
|
+
cases = %w[
|
|
83
62
|
=bar
|
|
84
63
|
foo=bar=
|
|
85
64
|
=foo=bar
|
|
86
65
|
foo=bar=extra
|
|
87
|
-
|
|
66
|
+
]
|
|
88
67
|
|
|
89
68
|
cases.each do |args|
|
|
90
69
|
actual = runner do
|
|
91
|
-
@app.start(%W
|
|
70
|
+
@app.start(%W[group rmvar #{group_name} #{args} --yes])
|
|
92
71
|
end
|
|
93
72
|
# @console.out(actual,'p')
|
|
94
73
|
|
|
95
74
|
desired = { aborted: true }
|
|
96
75
|
desired[:STDOUT] =
|
|
97
|
-
"Remove variable(s) '#{args}' from group '#{group_name}':\n"\
|
|
98
|
-
"
|
|
99
|
-
"
|
|
100
|
-
"
|
|
76
|
+
"Remove variable(s) '#{args}' from group '#{group_name}':\n " \
|
|
77
|
+
"- retrieve group '#{group_name}'...\n " \
|
|
78
|
+
"- OK\n " \
|
|
79
|
+
"- remove variable '#{args}'...\n"
|
|
101
80
|
desired[:STDERR] =
|
|
102
|
-
"An error occurred during a transaction, any changes have been rolled back.\n"\
|
|
81
|
+
"An error occurred during a transaction, any changes have been rolled back.\n" \
|
|
103
82
|
"ERROR: Incorrect format in {#{args}}. Expected 'key' or 'key=value'.\n"
|
|
104
83
|
|
|
105
84
|
expected(actual, desired)
|
|
106
85
|
end
|
|
107
86
|
end
|
|
108
87
|
|
|
88
|
+
#------------------------
|
|
89
|
+
it 'GROUP key --dry-run should not remove the group variable' do
|
|
90
|
+
group_name = 'group_test'
|
|
91
|
+
@db.models[:group].create(name: group_name)
|
|
92
|
+
runner { @app.start(%W[group addvar #{group_name} var1=val1]) }
|
|
93
|
+
|
|
94
|
+
actual = runner { @app.start(%W[group rmvar #{group_name} var1 --dry-run]) }
|
|
95
|
+
|
|
96
|
+
expect(actual[:unexpected]).to eq(false)
|
|
97
|
+
expect(actual[:aborted]).to eq(false)
|
|
98
|
+
expect(actual[:STDOUT]).to include('Dry run complete. No changes applied.')
|
|
99
|
+
group = @db.models[:group].find(name: group_name)
|
|
100
|
+
expect(group.groupvars_dataset[name: 'var1']).not_to be_nil
|
|
101
|
+
end
|
|
102
|
+
|
|
109
103
|
#------------------------
|
|
110
104
|
it 'GROUP <valid args> ... should remove the group variable' do
|
|
111
105
|
group_name = 'group_test'
|
|
112
106
|
var = { name: 'foo', value: 'bar' }
|
|
113
|
-
cases = %W
|
|
107
|
+
cases = %W[
|
|
114
108
|
#{var[:name]}
|
|
115
109
|
#{var[:name]}=
|
|
116
110
|
#{var[:name]}=#{var[:value]}
|
|
117
|
-
|
|
111
|
+
]
|
|
118
112
|
cases.each do |example|
|
|
119
113
|
# reset the db
|
|
120
114
|
@db.reset
|
|
@@ -122,24 +116,24 @@ RSpec.describe Moose::Inventory::Cli::Group do
|
|
|
122
116
|
# Add an initial group and groupvar
|
|
123
117
|
@db.models[:group].create(name: group_name)
|
|
124
118
|
runner do
|
|
125
|
-
@app.start(%W
|
|
119
|
+
@app.start(%W[group addvar #{group_name} #{var[:name]}=#{var[:value]}])
|
|
126
120
|
end
|
|
127
121
|
|
|
128
122
|
# Try to remove the groupvar using the case example valid args
|
|
129
123
|
actual = runner do
|
|
130
|
-
@app.start(%W
|
|
124
|
+
@app.start(%W[group rmvar #{group_name} #{example} --yes])
|
|
131
125
|
end
|
|
132
126
|
# @console.out(actual,'p')
|
|
133
127
|
|
|
134
128
|
# Check the output
|
|
135
129
|
desired = { aborted: false }
|
|
136
130
|
desired[:STDOUT] =
|
|
137
|
-
"Remove variable(s) '#{example}' from group '#{group_name}':\n"\
|
|
138
|
-
"
|
|
139
|
-
"
|
|
140
|
-
"
|
|
141
|
-
"
|
|
142
|
-
"
|
|
131
|
+
"Remove variable(s) '#{example}' from group '#{group_name}':\n " \
|
|
132
|
+
"- retrieve group '#{group_name}'...\n " \
|
|
133
|
+
"- OK\n " \
|
|
134
|
+
"- remove variable '#{example}'...\n " \
|
|
135
|
+
"- OK\n " \
|
|
136
|
+
"- all OK\n" \
|
|
143
137
|
"Succeeded.\n"
|
|
144
138
|
|
|
145
139
|
# @console.out(desired,'p')
|
|
@@ -160,36 +154,35 @@ RSpec.describe Moose::Inventory::Cli::Group do
|
|
|
160
154
|
group_name = 'test_group'
|
|
161
155
|
varsarray = [
|
|
162
156
|
{ name: 'var1', value: 'val1' },
|
|
163
|
-
{ name: 'var2', value: 'val2' }
|
|
157
|
+
{ name: 'var2', value: 'val2' }
|
|
164
158
|
]
|
|
165
159
|
|
|
166
|
-
vars =
|
|
167
|
-
|
|
168
|
-
vars << "#{var[:name]}=#{var[:value]}"
|
|
160
|
+
vars = varsarray.map do |var|
|
|
161
|
+
"#{var[:name]}=#{var[:value]}"
|
|
169
162
|
end
|
|
170
163
|
|
|
171
164
|
@db.models[:group].create(name: group_name)
|
|
172
|
-
|
|
173
|
-
@app.start(%W
|
|
165
|
+
runner do
|
|
166
|
+
@app.start(%W[group addvar #{group_name}] + vars)
|
|
174
167
|
end
|
|
175
168
|
|
|
176
169
|
actual = runner do
|
|
177
|
-
@app.start(%W
|
|
170
|
+
@app.start(%W[group rmvar #{group_name} --yes] + vars)
|
|
178
171
|
end
|
|
179
172
|
# @console.out(actual,'y')
|
|
180
173
|
|
|
181
174
|
desired = { aborted: false }
|
|
182
175
|
desired[:STDOUT] =
|
|
183
|
-
"Remove variable(s) '#{vars.join(',')}' from group '#{group_name}':\n"\
|
|
184
|
-
"
|
|
185
|
-
"
|
|
176
|
+
"Remove variable(s) '#{vars.join(',')}' from group '#{group_name}':\n " \
|
|
177
|
+
"- retrieve group '#{group_name}'...\n " \
|
|
178
|
+
"- OK\n"
|
|
186
179
|
vars.each do |var|
|
|
187
180
|
desired[:STDOUT] = desired[:STDOUT] +
|
|
188
|
-
" - remove variable '#{var}'...\n"\
|
|
189
|
-
"
|
|
181
|
+
" - remove variable '#{var}'...\n " \
|
|
182
|
+
"- OK\n"
|
|
190
183
|
end
|
|
191
184
|
desired[:STDOUT] = desired[:STDOUT] +
|
|
192
|
-
" - all OK\n"\
|
|
185
|
+
" - all OK\n" \
|
|
193
186
|
"Succeeded.\n"
|
|
194
187
|
expected(actual, desired)
|
|
195
188
|
|
|
@@ -200,3 +193,4 @@ RSpec.describe Moose::Inventory::Cli::Group do
|
|
|
200
193
|
end
|
|
201
194
|
end
|
|
202
195
|
end
|
|
196
|
+
# rubocop:enable Metrics/BlockLength
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
# rubocop:disable Metrics/BlockLength
|
|
6
|
+
RSpec.describe Moose::Inventory::Cli::Helpers do
|
|
7
|
+
subject(:helper) { helper_class.new }
|
|
8
|
+
|
|
9
|
+
let(:helper_class) do
|
|
10
|
+
Class.new do
|
|
11
|
+
include Moose::Inventory::Cli::Helpers
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
let(:inventory_context) { instance_double(Moose::Inventory::InventoryContext) }
|
|
16
|
+
let(:automatic_group) { instance_double('AutomaticGroup') }
|
|
17
|
+
|
|
18
|
+
before do
|
|
19
|
+
helper.instance_variable_set(:@inventory_context, inventory_context)
|
|
20
|
+
allow(inventory_context).to receive(:automatic_group).and_return(automatic_group)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
describe 'small helper methods' do
|
|
24
|
+
it 'checks whether an association dataset contains a named record' do
|
|
25
|
+
dataset = instance_double('Dataset')
|
|
26
|
+
|
|
27
|
+
expect(helper.send(:association_exists?, nil, 'alpha')).to eq(false)
|
|
28
|
+
allow(dataset).to receive(:[]).with(name: 'alpha').and_return(nil)
|
|
29
|
+
expect(helper.send(:association_exists?, dataset, 'alpha')).to eq(false)
|
|
30
|
+
allow(dataset).to receive(:[]).with(name: 'beta').and_return({ name: 'beta' })
|
|
31
|
+
expect(helper.send(:association_exists?, dataset, 'beta')).to eq(true)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'converts exceptions to strings' do
|
|
35
|
+
expect(helper.send(:exception_to_s, RuntimeError.new('boom'))).to eq('boom')
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it 'returns the automatic group through the inventory context' do
|
|
39
|
+
expect(helper.send(:automatic_group)).to eq(automatic_group)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
describe 'run_group_relation_transaction' do
|
|
44
|
+
it 'prints the heading and success marker for a successful transaction' do
|
|
45
|
+
allow(Moose::Inventory::DB).to receive(:transaction).and_yield
|
|
46
|
+
expect(Moose::Inventory::Cli::Formatter).to receive(:puts).with(2, '- all OK')
|
|
47
|
+
|
|
48
|
+
result = nil
|
|
49
|
+
actual = runner do
|
|
50
|
+
result = helper.send(:run_group_relation_transaction, heading: 'Heading') { :done }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
expect(result).to eq(:done)
|
|
54
|
+
expected(actual, aborted: false, STDOUT: "Heading\n", STDERR: '')
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it 'aborts with the Moose exception message when no custom handler is supplied' do
|
|
58
|
+
error = Moose::Inventory::DB::MooseDBException.new('boom')
|
|
59
|
+
allow(Moose::Inventory::DB).to receive(:transaction).and_raise(error)
|
|
60
|
+
allow(Moose::Inventory::DB).to receive(:exceptions).and_return({ moose: Moose::Inventory::DB::MooseDBException })
|
|
61
|
+
|
|
62
|
+
actual = runner do
|
|
63
|
+
helper.send(:run_group_relation_transaction, heading: 'Heading') { :done }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
expected(actual, aborted: true, STDOUT: '', STDERR: "ERROR: boom\n")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it 'uses the custom Moose exception handler when provided' do
|
|
70
|
+
error = Moose::Inventory::DB::MooseDBException.new('boom')
|
|
71
|
+
allow(Moose::Inventory::DB).to receive(:transaction).and_raise(error)
|
|
72
|
+
allow(Moose::Inventory::DB).to receive(:exceptions).and_return({ moose: Moose::Inventory::DB::MooseDBException })
|
|
73
|
+
|
|
74
|
+
actual = runner do
|
|
75
|
+
helper.send(
|
|
76
|
+
:run_group_relation_transaction,
|
|
77
|
+
heading: 'Heading',
|
|
78
|
+
on_error: ->(e) { "wrapped #{e.message}" }
|
|
79
|
+
) { :done }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
expected(actual, aborted: true, STDOUT: '', STDERR: "ERROR: wrapped boom\n")
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
describe 'automatic-group helpers' do
|
|
87
|
+
it 'removes the automatic group from a host when present' do
|
|
88
|
+
host = instance_double('Host')
|
|
89
|
+
dataset = instance_double('Dataset')
|
|
90
|
+
ungrouped = instance_double('Group')
|
|
91
|
+
allow(host).to receive(:groups_dataset).and_return(dataset)
|
|
92
|
+
allow(dataset).to receive(:[]).with(name: 'ungrouped').and_return(ungrouped)
|
|
93
|
+
expect(Moose::Inventory::Cli::Formatter).to receive(:puts).with(2, 'remove auto')
|
|
94
|
+
expect(host).to receive(:remove_group).with(ungrouped)
|
|
95
|
+
expect(Moose::Inventory::Cli::Formatter).to receive(:puts).with(4, '- OK')
|
|
96
|
+
|
|
97
|
+
helper.send(:remove_automatic_group_from_host, host, indent: 2, message: 'remove auto')
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it 'does nothing when the automatic group is not present' do
|
|
101
|
+
host = instance_double('Host')
|
|
102
|
+
dataset = instance_double('Dataset')
|
|
103
|
+
allow(host).to receive(:groups_dataset).and_return(dataset)
|
|
104
|
+
allow(dataset).to receive(:[]).with(name: 'ungrouped').and_return(nil)
|
|
105
|
+
|
|
106
|
+
expect(Moose::Inventory::Cli::Formatter).not_to receive(:puts)
|
|
107
|
+
expect(host).not_to receive(:remove_group)
|
|
108
|
+
|
|
109
|
+
helper.send(:remove_automatic_group_from_host, host, indent: 2, message: 'remove auto')
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it 'adds the automatic group when the last non-automatic group is removed' do
|
|
113
|
+
host = instance_double('Host')
|
|
114
|
+
dataset = instance_double('Dataset', count: 1)
|
|
115
|
+
allow(host).to receive(:groups_dataset).and_return(dataset)
|
|
116
|
+
expect(Moose::Inventory::Cli::Formatter).to receive(:puts).with(2, 'add auto')
|
|
117
|
+
expect(host).to receive(:add_group).with(automatic_group)
|
|
118
|
+
expect(Moose::Inventory::Cli::Formatter).to receive(:puts).with(4, '- OK')
|
|
119
|
+
|
|
120
|
+
helper.send(:add_automatic_group_to_host_if_last_group, host, indent: 2, message: 'add auto')
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
it 'adds the automatic group when a host has no groups' do
|
|
124
|
+
host = instance_double('Host')
|
|
125
|
+
dataset = instance_double('Dataset', count: 0)
|
|
126
|
+
allow(host).to receive(:groups_dataset).and_return(dataset)
|
|
127
|
+
expect(Moose::Inventory::Cli::Formatter).to receive(:puts).with(2, 'add auto')
|
|
128
|
+
expect(host).to receive(:add_group).with(automatic_group)
|
|
129
|
+
expect(Moose::Inventory::Cli::Formatter).to receive(:puts).with(4, '- OK')
|
|
130
|
+
|
|
131
|
+
helper.send(:add_automatic_group_to_host_if_no_groups, host, indent: 2, message: 'add auto')
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
it 'does nothing when the group count does not match the requested threshold' do
|
|
135
|
+
host = instance_double('Host')
|
|
136
|
+
dataset = instance_double('Dataset', count: 2)
|
|
137
|
+
allow(host).to receive(:groups_dataset).and_return(dataset)
|
|
138
|
+
|
|
139
|
+
expect(Moose::Inventory::Cli::Formatter).not_to receive(:puts)
|
|
140
|
+
expect(host).not_to receive(:add_group)
|
|
141
|
+
|
|
142
|
+
helper.send(:add_automatic_group_to_host_if_group_count, host, 1, indent: 2, message: 'add auto')
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
# rubocop:enable Metrics/BlockLength
|