moose-inventory 2.0 → 2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/release.yml +2 -0
- data/.gitignore +2 -1
- data/.rubocop.yml +21 -0
- data/BACKLOG.md +630 -8
- data/Gemfile +2 -0
- data/Gemfile.lock +1 -1
- data/README.md +315 -39
- data/Rakefile +2 -0
- data/bin/moose-inventory +2 -1
- data/docs/architecture/architecture-and-trust-boundaries.md +444 -0
- data/docs/compatibility/cli-output-compatibility.md +76 -0
- data/docs/governance/approval-register.md +37 -0
- data/docs/maintenance/database-backup-restore-guidance.md +162 -0
- data/docs/maintenance/package-maintenance-and-agent-boundaries.md +260 -0
- data/docs/process/conformance-gap-analysis-2026-05-28.md +192 -0
- data/docs/product/product-brief.md +161 -0
- data/docs/product/requirements-baseline.md +477 -0
- data/docs/qa/qa-documentation-and-release-gates.md +283 -0
- data/docs/release/package-provenance-hardening.md +126 -0
- data/docs/release/publishing.md +11 -3
- data/docs/release/release-environment-protection.md +70 -0
- data/docs/release/release-readiness.md +23 -4
- data/docs/security/accepted-risk-register.md +84 -0
- data/docs/security/security-privacy-process.md +287 -0
- data/docs/security-audit-2026-05-26-rerun.md +2 -2
- data/docs/ux/cli-workflow-notes.md +287 -0
- data/examples/ansible/ansible.cfg +3 -0
- data/examples/ansible/inventory/moose_inventory.yml +5 -0
- data/examples/ansible/inventory_plugins/moose_inventory.py +100 -0
- data/examples/ci/README.md +16 -0
- data/examples/ci/github-actions/inventory-review.yml +38 -0
- data/examples/ci/inventory/example-snapshot.yml +19 -0
- data/examples/ci/scripts/validate-inventory-snapshot.sh +30 -0
- data/lib/moose_inventory/cli/application.rb +133 -5
- data/lib/moose_inventory/cli/association_rendering.rb +74 -0
- data/lib/moose_inventory/cli/association_rendering_support.rb +89 -0
- data/lib/moose_inventory/cli/audit.rb +62 -0
- data/lib/moose_inventory/cli/audit_recording.rb +40 -0
- data/lib/moose_inventory/cli/child_relation_rendering.rb +110 -0
- data/lib/moose_inventory/cli/console.rb +135 -0
- data/lib/moose_inventory/cli/db.rb +64 -0
- data/lib/moose_inventory/cli/factory.rb +28 -0
- data/lib/moose_inventory/cli/formatter.rb +8 -12
- data/lib/moose_inventory/cli/group.rb +5 -2
- data/lib/moose_inventory/cli/group_add.rb +11 -9
- data/lib/moose_inventory/cli/group_addchild.rb +23 -65
- data/lib/moose_inventory/cli/group_addhost.rb +16 -67
- data/lib/moose_inventory/cli/group_addvar.rb +27 -47
- data/lib/moose_inventory/cli/group_get.rb +8 -42
- data/lib/moose_inventory/cli/group_list.rb +7 -40
- data/lib/moose_inventory/cli/group_listvars.rb +9 -55
- data/lib/moose_inventory/cli/group_rm.rb +12 -10
- data/lib/moose_inventory/cli/group_rmchild.rb +26 -82
- data/lib/moose_inventory/cli/group_rmhost.rb +18 -53
- data/lib/moose_inventory/cli/group_rmvar.rb +30 -41
- data/lib/moose_inventory/cli/group_tags.rb +33 -0
- data/lib/moose_inventory/cli/helpers.rb +68 -1
- data/lib/moose_inventory/cli/host.rb +6 -3
- data/lib/moose_inventory/cli/host_add.rb +69 -29
- data/lib/moose_inventory/cli/host_addgroup.rb +22 -58
- data/lib/moose_inventory/cli/host_addvar.rb +28 -52
- data/lib/moose_inventory/cli/host_get.rb +9 -37
- data/lib/moose_inventory/cli/host_list.rb +24 -21
- data/lib/moose_inventory/cli/host_listvars.rb +9 -62
- data/lib/moose_inventory/cli/host_rm.rb +60 -42
- data/lib/moose_inventory/cli/host_rmgroup.rb +25 -44
- data/lib/moose_inventory/cli/host_rmvar.rb +31 -45
- data/lib/moose_inventory/cli/host_tags.rb +33 -0
- data/lib/moose_inventory/cli/listvars_support.rb +55 -0
- data/lib/moose_inventory/cli/plan_rendering.rb +50 -0
- data/lib/moose_inventory/cli/relation_transaction_support.rb +51 -0
- data/lib/moose_inventory/cli/tag_support.rb +97 -0
- data/lib/moose_inventory/cli/variable_rendering.rb +67 -0
- data/lib/moose_inventory/config/config.rb +185 -108
- data/lib/moose_inventory/db/db.rb +170 -195
- data/lib/moose_inventory/db/exceptions.rb +6 -3
- data/lib/moose_inventory/db/models.rb +16 -0
- data/lib/moose_inventory/db/schema_migrations.rb +248 -0
- data/lib/moose_inventory/inventory_context.rb +68 -2
- data/lib/moose_inventory/operations/add_associations.rb +20 -16
- data/lib/moose_inventory/operations/add_groups.rb +21 -13
- data/lib/moose_inventory/operations/add_hosts.rb +30 -17
- data/lib/moose_inventory/operations/add_variables.rb +77 -0
- data/lib/moose_inventory/operations/entity_variable_operation_support.rb +46 -0
- data/lib/moose_inventory/operations/group_child_relations.rb +23 -16
- data/lib/moose_inventory/operations/group_cleanup.rb +23 -8
- data/lib/moose_inventory/operations/import_inventory_snapshot.rb +41 -0
- data/lib/moose_inventory/operations/inventory_doctor.rb +172 -0
- data/lib/moose_inventory/operations/inventory_snapshot.rb +60 -0
- data/lib/moose_inventory/operations/inventory_snapshot_applier.rb +112 -0
- data/lib/moose_inventory/operations/inventory_snapshot_preview.rb +174 -0
- data/lib/moose_inventory/operations/inventory_snapshot_validator.rb +134 -0
- data/lib/moose_inventory/operations/operation_event_support.rb +27 -0
- data/lib/moose_inventory/operations/query_inventory/base_query.rb +24 -0
- data/lib/moose_inventory/operations/query_inventory/group_queries.rb +86 -0
- data/lib/moose_inventory/operations/query_inventory/host_queries.rb +106 -0
- data/lib/moose_inventory/operations/query_inventory.rb +47 -0
- data/lib/moose_inventory/operations/remove_associations.rb +30 -18
- data/lib/moose_inventory/operations/remove_groups.rb +12 -12
- data/lib/moose_inventory/operations/remove_hosts.rb +68 -0
- data/lib/moose_inventory/operations/remove_variables.rb +67 -0
- data/lib/moose_inventory/runtime_options.rb +31 -0
- data/lib/moose_inventory/version.rb +3 -1
- data/lib/moose_inventory.rb +10 -7
- data/moose-inventory.gemspec +19 -35
- data/scripts/check.sh +1 -0
- data/scripts/ci/check_generated_artifacts.sh +41 -0
- data/scripts/ci/check_permissions.sh +2 -0
- data/scripts/ci/check_rubocop.sh +30 -25
- data/scripts/files.rb +5 -4
- data/spec/examples/ci_examples_spec.rb +37 -0
- data/spec/lib/moose_inventory/ansible_plugin_examples_spec.rb +29 -0
- data/spec/lib/moose_inventory/cli/application_doctor_spec.rb +50 -0
- data/spec/lib/moose_inventory/cli/application_import_export_spec.rb +100 -0
- data/spec/lib/moose_inventory/cli/application_spec.rb +25 -15
- data/spec/lib/moose_inventory/cli/audit_spec.rb +56 -0
- data/spec/lib/moose_inventory/cli/cli_spec.rb +15 -19
- data/spec/lib/moose_inventory/cli/console_spec.rb +98 -0
- data/spec/lib/moose_inventory/cli/factory_spec.rb +27 -0
- data/spec/lib/moose_inventory/cli/formatter_spec.rb +95 -3
- data/spec/lib/moose_inventory/cli/group_add_spec.rb +140 -116
- data/spec/lib/moose_inventory/cli/group_addchild_spec.rb +89 -35
- data/spec/lib/moose_inventory/cli/group_addhost_spec.rb +81 -84
- data/spec/lib/moose_inventory/cli/group_addvar_spec.rb +65 -68
- data/spec/lib/moose_inventory/cli/group_get_spec.rb +17 -33
- data/spec/lib/moose_inventory/cli/group_list_spec.rb +16 -38
- data/spec/lib/moose_inventory/cli/group_listvar_spec.rb +33 -40
- data/spec/lib/moose_inventory/cli/group_rm_spec.rb +136 -96
- data/spec/lib/moose_inventory/cli/group_rmchild_spec.rb +66 -41
- data/spec/lib/moose_inventory/cli/group_rmhost_spec.rb +76 -78
- data/spec/lib/moose_inventory/cli/group_rmvar_spec.rb +57 -63
- data/spec/lib/moose_inventory/cli/group_spec.rb +2 -0
- data/spec/lib/moose_inventory/cli/helpers_spec.rb +146 -0
- data/spec/lib/moose_inventory/cli/host_add_spec.rb +170 -116
- data/spec/lib/moose_inventory/cli/host_addgroup_spec.rb +100 -83
- data/spec/lib/moose_inventory/cli/host_addvar_spec.rb +92 -74
- data/spec/lib/moose_inventory/cli/host_get_spec.rb +14 -33
- data/spec/lib/moose_inventory/cli/host_list_spec.rb +41 -33
- data/spec/lib/moose_inventory/cli/host_listvar_spec.rb +45 -53
- data/spec/lib/moose_inventory/cli/host_rm_spec.rb +66 -48
- data/spec/lib/moose_inventory/cli/host_rmgroup_spec.rb +73 -83
- data/spec/lib/moose_inventory/cli/host_rmvar_spec.rb +56 -63
- data/spec/lib/moose_inventory/cli/host_spec.rb +2 -0
- data/spec/lib/moose_inventory/cli/tags_spec.rb +81 -0
- data/spec/lib/moose_inventory/config/config_spec.rb +41 -3
- data/spec/lib/moose_inventory/db/db_spec.rb +396 -36
- data/spec/lib/moose_inventory/db/exceptions_spec.rb +18 -0
- data/spec/lib/moose_inventory/db/models_spec.rb +7 -3
- data/spec/lib/moose_inventory/db_lifecycle_spec.rb +73 -0
- data/spec/lib/moose_inventory/inventory_context_spec.rb +10 -0
- data/spec/lib/moose_inventory/operations/add_associations_spec.rb +34 -0
- data/spec/lib/moose_inventory/operations/add_groups_spec.rb +15 -0
- data/spec/lib/moose_inventory/operations/add_hosts_spec.rb +13 -0
- data/spec/lib/moose_inventory/operations/add_variables_spec.rb +103 -0
- data/spec/lib/moose_inventory/operations/group_child_relations_spec.rb +46 -0
- data/spec/lib/moose_inventory/operations/import_inventory_snapshot_spec.rb +226 -0
- data/spec/lib/moose_inventory/operations/inventory_doctor_spec.rb +77 -0
- data/spec/lib/moose_inventory/operations/inventory_snapshot_spec.rb +50 -0
- data/spec/lib/moose_inventory/operations/operation_event_support_spec.rb +78 -0
- data/spec/lib/moose_inventory/operations/query_inventory_spec.rb +146 -0
- data/spec/lib/moose_inventory/operations/remove_associations_spec.rb +35 -0
- data/spec/lib/moose_inventory/operations/remove_groups_spec.rb +21 -0
- data/spec/lib/moose_inventory/operations/remove_hosts_spec.rb +55 -0
- data/spec/lib/moose_inventory/operations/remove_variables_spec.rb +83 -0
- data/spec/shared/shared_config_setup.rb +4 -3
- data/spec/spec_helper.rb +50 -40
- data/spec/support/cli_harness.rb +33 -0
- metadata +80 -41
|
@@ -1,43 +1,24 @@
|
|
|
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::Host 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
|
-
@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
|
-
@host = Moose::Inventory::Cli::Host
|
|
29
|
-
@app = Moose::Inventory::Cli::Application
|
|
10
|
+
setup_cli_harness(command_class: Moose::Inventory::Cli::Host, command_ivar: :@host)
|
|
30
11
|
end
|
|
31
12
|
|
|
32
13
|
before(:each) do
|
|
33
|
-
|
|
14
|
+
reset_cli_harness
|
|
34
15
|
end
|
|
35
16
|
|
|
36
17
|
#====================
|
|
37
18
|
describe 'rmgroup' do
|
|
38
19
|
#----------------
|
|
39
20
|
it 'should be responsive' do
|
|
40
|
-
result = @host.
|
|
21
|
+
result = @host.method_defined?(:rmgroup, false)
|
|
41
22
|
expect(result).to eq(true)
|
|
42
23
|
end
|
|
43
24
|
|
|
@@ -46,7 +27,7 @@ RSpec.describe Moose::Inventory::Cli::Host do
|
|
|
46
27
|
#------------------------
|
|
47
28
|
it 'host rmgroup <missing args> ... should abort with an error' do
|
|
48
29
|
actual = runner do
|
|
49
|
-
@app.start(%w
|
|
30
|
+
@app.start(%w[host rmgroup]) # <- no group given
|
|
50
31
|
end
|
|
51
32
|
|
|
52
33
|
# Check output
|
|
@@ -60,16 +41,16 @@ RSpec.describe Moose::Inventory::Cli::Host do
|
|
|
60
41
|
host_name = 'not-a-host'
|
|
61
42
|
group_name = 'example'
|
|
62
43
|
actual = runner do
|
|
63
|
-
@app.start(%W
|
|
44
|
+
@app.start(%W[host rmgroup #{host_name} #{group_name} --yes])
|
|
64
45
|
end
|
|
65
46
|
|
|
66
47
|
# Check output
|
|
67
48
|
desired = { aborted: true }
|
|
68
49
|
desired[:STDOUT] =
|
|
69
|
-
"Dissociate host '#{host_name}' from groups '#{group_name}':\n"\
|
|
70
|
-
"
|
|
50
|
+
"Dissociate host '#{host_name}' from groups '#{group_name}':\n " \
|
|
51
|
+
"- Retrieve host '#{host_name}'...\n"
|
|
71
52
|
desired[:STDERR] =
|
|
72
|
-
"An error occurred during a transaction, any changes have been rolled back.\n"\
|
|
53
|
+
"An error occurred during a transaction, any changes have been rolled back.\n" \
|
|
73
54
|
"ERROR: The host '#{host_name}' was not found in the database.\n"
|
|
74
55
|
expected(actual, desired)
|
|
75
56
|
end
|
|
@@ -81,10 +62,10 @@ RSpec.describe Moose::Inventory::Cli::Host do
|
|
|
81
62
|
# if it has no other groups.
|
|
82
63
|
|
|
83
64
|
host_name = 'test1'
|
|
84
|
-
runner { @app.start(%W
|
|
65
|
+
runner { @app.start(%W[host add #{host_name}]) }
|
|
85
66
|
|
|
86
|
-
group_names = %w
|
|
87
|
-
|
|
67
|
+
group_names = %w[group1 group2]
|
|
68
|
+
runner { @app.start(%W[host addgroup #{host_name} #{group_names[0]} #{group_names[1]}]) }
|
|
88
69
|
|
|
89
70
|
#
|
|
90
71
|
# Dissociate from the first group
|
|
@@ -92,23 +73,20 @@ RSpec.describe Moose::Inventory::Cli::Host do
|
|
|
92
73
|
# 2. expect that no association with ungrouped is made.
|
|
93
74
|
|
|
94
75
|
actual = runner do
|
|
95
|
-
@app.start(%W
|
|
76
|
+
@app.start(%W[host rmgroup #{host_name} #{group_names[0]} --yes])
|
|
96
77
|
end
|
|
97
78
|
# @console.dump(actual, 'y')
|
|
98
79
|
|
|
99
|
-
# rubocop:disable Metrics/LineLength
|
|
100
80
|
desired = { aborted: false }
|
|
101
81
|
desired[:STDOUT] =
|
|
102
|
-
"Dissociate host '#{host_name}' from groups '#{group_names[0]}':\n"\
|
|
103
|
-
"
|
|
104
|
-
"
|
|
105
|
-
"
|
|
106
|
-
"
|
|
107
|
-
"
|
|
82
|
+
"Dissociate host '#{host_name}' from groups '#{group_names[0]}':\n " \
|
|
83
|
+
"- Retrieve host '#{host_name}'...\n " \
|
|
84
|
+
"- OK\n " \
|
|
85
|
+
"- Remove association {host:#{host_name} <-> group:#{group_names[0]}}...\n " \
|
|
86
|
+
"- OK\n " \
|
|
87
|
+
"- All OK\n" \
|
|
108
88
|
"Succeeded\n"
|
|
109
89
|
expected(actual, desired)
|
|
110
|
-
# rubocop:enable Metrics/LineLength
|
|
111
|
-
|
|
112
90
|
# We should have the correct group associations
|
|
113
91
|
host = @db.models[:host].find(name: host_name)
|
|
114
92
|
groups = host.groups_dataset
|
|
@@ -122,24 +100,21 @@ RSpec.describe Moose::Inventory::Cli::Host do
|
|
|
122
100
|
# 1. expect that the group association is removed
|
|
123
101
|
# 2. expect that an association will be made with 'ungrouped'.
|
|
124
102
|
actual = runner do
|
|
125
|
-
@app.start(
|
|
103
|
+
@app.start(%W[host rmgroup #{host_name} #{group_names[1]} --yes])
|
|
126
104
|
end
|
|
127
105
|
|
|
128
|
-
# rubocop:disable Metrics/LineLength
|
|
129
106
|
desired = { aborted: false }
|
|
130
107
|
desired[:STDOUT] =
|
|
131
|
-
"Dissociate host '#{host_name}' from groups '#{group_names[1]}':\n"\
|
|
132
|
-
"
|
|
133
|
-
"
|
|
134
|
-
"
|
|
135
|
-
"
|
|
136
|
-
"
|
|
137
|
-
"
|
|
138
|
-
"
|
|
108
|
+
"Dissociate host '#{host_name}' from groups '#{group_names[1]}':\n " \
|
|
109
|
+
"- Retrieve host '#{host_name}'...\n " \
|
|
110
|
+
"- OK\n " \
|
|
111
|
+
"- Remove association {host:#{host_name} <-> group:#{group_names[1]}}...\n " \
|
|
112
|
+
"- OK\n " \
|
|
113
|
+
"- Add automatic association {host:#{host_name} <-> group:ungrouped}...\n " \
|
|
114
|
+
"- OK\n " \
|
|
115
|
+
"- All OK\n" \
|
|
139
116
|
"Succeeded\n"
|
|
140
117
|
expected(actual, desired)
|
|
141
|
-
# rubocop:enable Metrics/LineLength
|
|
142
|
-
|
|
143
118
|
# We should have the correct group associations
|
|
144
119
|
host = @db.models[:host].find(name: host_name)
|
|
145
120
|
groups = host.groups_dataset
|
|
@@ -149,6 +124,23 @@ RSpec.describe Moose::Inventory::Cli::Host do
|
|
|
149
124
|
expect(groups[name: 'ungrouped']).not_to be_nil
|
|
150
125
|
end
|
|
151
126
|
|
|
127
|
+
#------------------------
|
|
128
|
+
it 'host rmgroup HOST GROUP --dry-run should not remove membership or add ungrouped' do
|
|
129
|
+
host_name = 'test1'
|
|
130
|
+
group_name = 'group1'
|
|
131
|
+
runner { @app.start(%W[host add #{host_name}]) }
|
|
132
|
+
runner { @app.start(%W[host addgroup #{host_name} #{group_name}]) }
|
|
133
|
+
|
|
134
|
+
actual = runner { @app.start(%W[host rmgroup #{host_name} #{group_name} --dry-run]) }
|
|
135
|
+
|
|
136
|
+
expect(actual[:unexpected]).to eq(false)
|
|
137
|
+
expect(actual[:aborted]).to eq(false)
|
|
138
|
+
expect(actual[:STDOUT]).to include('Dry run complete. No changes applied.')
|
|
139
|
+
host = @db.models[:host].find(name: host_name)
|
|
140
|
+
expect(host.groups_dataset[name: group_name]).not_to be_nil
|
|
141
|
+
expect(host.groups_dataset[name: 'ungrouped']).to be_nil
|
|
142
|
+
end
|
|
143
|
+
|
|
152
144
|
#------------------------
|
|
153
145
|
it 'host rmgroup HOST GROUP ... should warn about non-existing associations' do
|
|
154
146
|
# 1. Should warn that the group doesn't exist.
|
|
@@ -156,22 +148,21 @@ RSpec.describe Moose::Inventory::Cli::Host do
|
|
|
156
148
|
|
|
157
149
|
host_name = 'test1'
|
|
158
150
|
group_name = 'no-group'
|
|
159
|
-
runner { @app.start(%W
|
|
151
|
+
runner { @app.start(%W[host add #{host_name}]) }
|
|
160
152
|
|
|
161
153
|
actual = runner do
|
|
162
|
-
@app.start(%W
|
|
154
|
+
@app.start(%W[host rmgroup #{host_name} #{group_name} --yes])
|
|
163
155
|
end
|
|
164
156
|
|
|
165
|
-
# rubocop:disable Metrics/LineLength
|
|
166
157
|
desired = { aborted: false }
|
|
167
158
|
desired[:STDOUT] =
|
|
168
|
-
"Dissociate host '#{host_name}' from groups '#{group_name}':\n"\
|
|
169
|
-
"
|
|
170
|
-
"
|
|
171
|
-
"
|
|
172
|
-
"
|
|
173
|
-
"
|
|
174
|
-
"
|
|
159
|
+
"Dissociate host '#{host_name}' from groups '#{group_name}':\n " \
|
|
160
|
+
"- Retrieve host '#{host_name}'...\n " \
|
|
161
|
+
"- OK\n " \
|
|
162
|
+
"- Remove association {host:#{host_name} <-> group:#{group_name}}...\n " \
|
|
163
|
+
"- Doesn't exist, skipping.\n " \
|
|
164
|
+
"- OK\n " \
|
|
165
|
+
"- All OK\n" \
|
|
175
166
|
"Succeeded\n"
|
|
176
167
|
desired[:STDERR] = "WARNING: Association {host:#{host_name} <-> group:#{group_name}} doesn't exist, skipping.\n"
|
|
177
168
|
|
|
@@ -183,9 +174,9 @@ RSpec.describe Moose::Inventory::Cli::Host do
|
|
|
183
174
|
name = 'test1'
|
|
184
175
|
groupname = 'ungrouped'
|
|
185
176
|
|
|
186
|
-
runner { @app.start(%W
|
|
177
|
+
runner { @app.start(%W[host add #{name}]) }
|
|
187
178
|
|
|
188
|
-
actual = runner { @app.start(%W
|
|
179
|
+
actual = runner { @app.start(%W[host rmgroup #{name} #{groupname} --yes]) }
|
|
189
180
|
|
|
190
181
|
desired = { aborted: true }
|
|
191
182
|
desired[:STDERR] =
|
|
@@ -194,41 +185,39 @@ RSpec.describe Moose::Inventory::Cli::Host do
|
|
|
194
185
|
end
|
|
195
186
|
|
|
196
187
|
#------------------------
|
|
197
|
-
it 'host rmgroup GROUP1 GROUP1 ... should dissociate the host from'\
|
|
198
|
-
|
|
188
|
+
it 'host rmgroup GROUP1 GROUP1 ... should dissociate the host from ' \
|
|
189
|
+
'multiple groups at once' do
|
|
199
190
|
# 1. Should rm the host to the group
|
|
200
191
|
# 2. Should add the host from the 'ungrouped' automatic group
|
|
201
192
|
# if it has no other groups.
|
|
202
193
|
|
|
203
194
|
host_name = 'test1'
|
|
204
|
-
runner { @app.start(%W
|
|
195
|
+
runner { @app.start(%W[host add #{host_name}]) }
|
|
205
196
|
|
|
206
|
-
group_names = %w
|
|
197
|
+
group_names = %w[group1 group2]
|
|
207
198
|
group_names.each do |group|
|
|
208
|
-
runner { @app.start(%W
|
|
199
|
+
runner { @app.start(%W[host addgroup #{host_name} #{group}]) }
|
|
209
200
|
end
|
|
210
201
|
|
|
211
202
|
actual = runner do
|
|
212
|
-
@app.start(%W
|
|
203
|
+
@app.start(%W[host rmgroup #{host_name} --yes] + group_names)
|
|
213
204
|
end
|
|
214
205
|
desired = { aborted: false }
|
|
215
206
|
desired[:STDOUT] =
|
|
216
|
-
"Dissociate host '#{host_name}' from groups '#{group_names.join(',')}':\n"\
|
|
217
|
-
"
|
|
218
|
-
"
|
|
207
|
+
"Dissociate host '#{host_name}' from groups '#{group_names.join(',')}':\n " \
|
|
208
|
+
"- Retrieve host '#{host_name}'...\n " \
|
|
209
|
+
"- OK\n"
|
|
219
210
|
group_names.each do |group|
|
|
220
211
|
desired[:STDOUT] = desired[:STDOUT] +
|
|
221
|
-
" - Remove association {host:#{host_name} <-> group:#{group}}...\n"\
|
|
222
|
-
"
|
|
212
|
+
" - Remove association {host:#{host_name} <-> group:#{group}}...\n " \
|
|
213
|
+
"- OK\n" \
|
|
223
214
|
end
|
|
224
215
|
desired[:STDOUT] = desired[:STDOUT] +
|
|
225
|
-
" - Add automatic association {host:#{host_name} <-> group:ungrouped}...\n"\
|
|
226
|
-
"
|
|
227
|
-
"
|
|
216
|
+
" - Add automatic association {host:#{host_name} <-> group:ungrouped}...\n " \
|
|
217
|
+
"- OK\n " \
|
|
218
|
+
"- All OK\n" \
|
|
228
219
|
"Succeeded\n"
|
|
229
220
|
expected(actual, desired)
|
|
230
|
-
# rubocop:enable Metrics/LineLength
|
|
231
|
-
|
|
232
221
|
# We should have the correct group associations
|
|
233
222
|
host = @db.models[:host].find(name: host_name)
|
|
234
223
|
groups = host.groups_dataset
|
|
@@ -240,3 +229,4 @@ RSpec.describe Moose::Inventory::Cli::Host do
|
|
|
240
229
|
end
|
|
241
230
|
end
|
|
242
231
|
end
|
|
232
|
+
# 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::Host 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
|
-
@host = Moose::Inventory::Cli::Host
|
|
29
|
-
@app = Moose::Inventory::Cli::Application
|
|
10
|
+
setup_cli_harness(command_class: Moose::Inventory::Cli::Host, command_ivar: :@host)
|
|
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 = @host.
|
|
19
|
+
result = @host.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[host rmvar]) # <- no group given
|
|
46
27
|
end
|
|
47
28
|
|
|
48
29
|
# Check output
|
|
@@ -56,16 +37,16 @@ RSpec.describe Moose::Inventory::Cli::Host do
|
|
|
56
37
|
host_name = 'not-a-host'
|
|
57
38
|
var_name = 'foo=bar'
|
|
58
39
|
actual = runner do
|
|
59
|
-
@app.start(%W
|
|
40
|
+
@app.start(%W[host rmvar #{host_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 host '#{host_name}':\n"\
|
|
66
|
-
"
|
|
46
|
+
"Remove variable(s) '#{var_name}' from host '#{host_name}':\n " \
|
|
47
|
+
"- retrieve host '#{host_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 host '#{host_name}' does not exist.\n"
|
|
70
51
|
expected(actual, desired)
|
|
71
52
|
end
|
|
@@ -77,29 +58,27 @@ RSpec.describe Moose::Inventory::Cli::Host do
|
|
|
77
58
|
|
|
78
59
|
host_name = 'test1'
|
|
79
60
|
@db.models[:host].create(name: host_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[host rmvar #{host_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 host '#{host_name}':\n"\
|
|
98
|
-
"
|
|
99
|
-
"
|
|
100
|
-
"
|
|
76
|
+
"Remove variable(s) '#{args}' from host '#{host_name}':\n " \
|
|
77
|
+
"- retrieve host '#{host_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)
|
|
@@ -107,6 +86,20 @@ RSpec.describe Moose::Inventory::Cli::Host do
|
|
|
107
86
|
end
|
|
108
87
|
|
|
109
88
|
#------------------------
|
|
89
|
+
it 'host rmvar HOST key --dry-run should not remove the host variable' do
|
|
90
|
+
host_name = 'test1'
|
|
91
|
+
@db.models[:host].create(name: host_name)
|
|
92
|
+
runner { @app.start(%W[host addvar #{host_name} var1=val1]) }
|
|
93
|
+
|
|
94
|
+
actual = runner { @app.start(%W[host rmvar #{host_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
|
+
host = @db.models[:host].find(name: host_name)
|
|
100
|
+
expect(host.hostvars_dataset[name: 'var1']).not_to be_nil
|
|
101
|
+
end
|
|
102
|
+
|
|
110
103
|
it 'host rmvar HOST <valid args> ... should remove the host variable' do
|
|
111
104
|
# 1. Should add the var to the db
|
|
112
105
|
# 2. Should associate the host with the var
|
|
@@ -114,11 +107,11 @@ RSpec.describe Moose::Inventory::Cli::Host do
|
|
|
114
107
|
host_name = 'test1'
|
|
115
108
|
|
|
116
109
|
var = { name: 'foo', value: 'bar' }
|
|
117
|
-
cases = %W
|
|
110
|
+
cases = %W[
|
|
118
111
|
#{var[:name]}
|
|
119
112
|
#{var[:name]}=
|
|
120
113
|
#{var[:name]}=#{var[:value]}
|
|
121
|
-
|
|
114
|
+
]
|
|
122
115
|
cases.each do |example|
|
|
123
116
|
# reset the db
|
|
124
117
|
@db.reset
|
|
@@ -126,24 +119,24 @@ RSpec.describe Moose::Inventory::Cli::Host do
|
|
|
126
119
|
# Add an initial host and hostvar
|
|
127
120
|
@db.models[:host].create(name: host_name)
|
|
128
121
|
runner do
|
|
129
|
-
@app.start(%W
|
|
122
|
+
@app.start(%W[host addvar #{host_name} #{var[:name]}=#{var[:value]}])
|
|
130
123
|
end
|
|
131
124
|
|
|
132
125
|
# Try to remove the hostvar using the case example valid args
|
|
133
126
|
actual = runner do
|
|
134
|
-
@app.start(%W
|
|
127
|
+
@app.start(%W[host rmvar #{host_name} #{example} --yes])
|
|
135
128
|
end
|
|
136
129
|
# @console.out(actual,'p')
|
|
137
130
|
|
|
138
131
|
# Check the output
|
|
139
132
|
desired = { aborted: false }
|
|
140
133
|
desired[:STDOUT] =
|
|
141
|
-
"Remove variable(s) '#{example}' from host '#{host_name}':\n"\
|
|
142
|
-
"
|
|
143
|
-
"
|
|
144
|
-
"
|
|
145
|
-
"
|
|
146
|
-
"
|
|
134
|
+
"Remove variable(s) '#{example}' from host '#{host_name}':\n " \
|
|
135
|
+
"- retrieve host '#{host_name}'...\n " \
|
|
136
|
+
"- OK\n " \
|
|
137
|
+
"- remove variable '#{example}'...\n " \
|
|
138
|
+
"- OK\n " \
|
|
139
|
+
"- all OK\n" \
|
|
147
140
|
"Succeeded.\n"
|
|
148
141
|
|
|
149
142
|
# @console.out(desired,'p')
|
|
@@ -164,36 +157,35 @@ RSpec.describe Moose::Inventory::Cli::Host do
|
|
|
164
157
|
host_name = 'test1'
|
|
165
158
|
varsarray = [
|
|
166
159
|
{ name: 'var1', value: 'val1' },
|
|
167
|
-
{ name: 'var2', value: 'val2' }
|
|
160
|
+
{ name: 'var2', value: 'val2' }
|
|
168
161
|
]
|
|
169
162
|
|
|
170
|
-
vars =
|
|
171
|
-
|
|
172
|
-
vars << "#{var[:name]}=#{var[:value]}"
|
|
163
|
+
vars = varsarray.map do |var|
|
|
164
|
+
"#{var[:name]}=#{var[:value]}"
|
|
173
165
|
end
|
|
174
166
|
|
|
175
167
|
@db.models[:host].create(name: host_name)
|
|
176
|
-
|
|
177
|
-
@app.start(%W
|
|
168
|
+
runner do
|
|
169
|
+
@app.start(%W[host addvar #{host_name}] + vars)
|
|
178
170
|
end
|
|
179
171
|
|
|
180
172
|
actual = runner do
|
|
181
|
-
@app.start(%W
|
|
173
|
+
@app.start(%W[host rmvar #{host_name} --yes] + vars)
|
|
182
174
|
end
|
|
183
175
|
# @console.out(actual,'p')
|
|
184
176
|
|
|
185
177
|
desired = { aborted: false }
|
|
186
178
|
desired[:STDOUT] =
|
|
187
|
-
"Remove variable(s) '#{vars.join(',')}' from host '#{host_name}':\n"\
|
|
188
|
-
"
|
|
189
|
-
"
|
|
179
|
+
"Remove variable(s) '#{vars.join(',')}' from host '#{host_name}':\n " \
|
|
180
|
+
"- retrieve host '#{host_name}'...\n " \
|
|
181
|
+
"- OK\n"
|
|
190
182
|
vars.each do |var|
|
|
191
183
|
desired[:STDOUT] = desired[:STDOUT] +
|
|
192
|
-
" - remove variable '#{var}'...\n"\
|
|
193
|
-
"
|
|
184
|
+
" - remove variable '#{var}'...\n " \
|
|
185
|
+
"- OK\n"
|
|
194
186
|
end
|
|
195
187
|
desired[:STDOUT] = desired[:STDOUT] +
|
|
196
|
-
" - all OK\n"\
|
|
188
|
+
" - all OK\n" \
|
|
197
189
|
"Succeeded.\n"
|
|
198
190
|
expected(actual, desired)
|
|
199
191
|
|
|
@@ -204,3 +196,4 @@ RSpec.describe Moose::Inventory::Cli::Host do
|
|
|
204
196
|
end
|
|
205
197
|
end
|
|
206
198
|
end
|
|
199
|
+
# rubocop:enable Metrics/BlockLength
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'spec_helper'
|
|
5
|
+
|
|
6
|
+
# rubocop:disable Metrics/BlockLength
|
|
7
|
+
RSpec.describe 'host and group metadata tags' 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 'adds and lists host tags' do
|
|
17
|
+
runner { @app.start(%w[host add app01]) }
|
|
18
|
+
|
|
19
|
+
add = runner { @app.start(%w[host addtag app01 prod critical]) }
|
|
20
|
+
list = runner { @app.start(%w[host listtags app01]) }
|
|
21
|
+
|
|
22
|
+
expect(add[:unexpected]).to eq(false)
|
|
23
|
+
expect(add[:STDOUT]).to include("Added host tag(s) to 'app01': prod, critical.")
|
|
24
|
+
expect(list[:STDOUT]).to eq("Host 'app01' tags: critical, prod\n")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'normalizes host tag casing and deduplicates values' do
|
|
28
|
+
runner { @app.start(%w[host add app01]) }
|
|
29
|
+
|
|
30
|
+
add = runner { @app.start(%w[host addtag app01 Prod PROD owner-platform]) }
|
|
31
|
+
list = runner { @app.start(%w[host listtags app01]) }
|
|
32
|
+
|
|
33
|
+
expect(add[:unexpected]).to eq(false)
|
|
34
|
+
expect(add[:STDOUT]).to include("Added host tag(s) to 'app01': prod, owner-platform.")
|
|
35
|
+
expect(list[:STDOUT]).to eq("Host 'app01' tags: owner-platform, prod\n")
|
|
36
|
+
expect(@db.models[:tag].where(name: 'Prod').count).to eq(0)
|
|
37
|
+
expect(@db.models[:tag].where(name: 'prod').count).to eq(1)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'removes host tags' do
|
|
41
|
+
runner { @app.start(%w[host add app01]) }
|
|
42
|
+
runner { @app.start(%w[host addtag app01 prod critical]) }
|
|
43
|
+
|
|
44
|
+
remove = runner { @app.start(%w[host rmtag app01 PROD --yes]) }
|
|
45
|
+
list = runner { @app.start(%w[host listtags app01]) }
|
|
46
|
+
|
|
47
|
+
expect(remove[:unexpected]).to eq(false)
|
|
48
|
+
expect(remove[:STDOUT]).to include("Removed host tag(s) from 'app01': prod.")
|
|
49
|
+
expect(list[:STDOUT]).to eq("Host 'app01' tags: critical\n")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it 'adds and lists group tags as JSON' do
|
|
53
|
+
runner { @app.start(%w[group add web]) }
|
|
54
|
+
runner { @app.start(%w[group addtag web frontend owner-platform]) }
|
|
55
|
+
|
|
56
|
+
actual = runner { @app.start(%w[group listtags web --format json]) }
|
|
57
|
+
parsed = JSON.parse(actual[:STDOUT])
|
|
58
|
+
|
|
59
|
+
expect(actual[:unexpected]).to eq(false)
|
|
60
|
+
expect(parsed).to eq('group' => 'web', 'tags' => %w[frontend owner-platform])
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it 'records audit events for tag changes' do
|
|
64
|
+
runner { @app.start(%w[host add app01]) }
|
|
65
|
+
runner { @app.start(%w[host addtag app01 prod]) }
|
|
66
|
+
|
|
67
|
+
event = @db.models[:audit_event].last
|
|
68
|
+
expect(event.command).to eq('host addtag')
|
|
69
|
+
expect(event.action).to eq('add_tag')
|
|
70
|
+
expect(event.entity_type).to eq('host')
|
|
71
|
+
expect(event.entity_name).to eq('app01')
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it 'aborts when tagging a missing entity' do
|
|
75
|
+
actual = runner { @app.start(%w[group addtag missing prod]) }
|
|
76
|
+
|
|
77
|
+
expect(actual[:aborted]).to eq(true)
|
|
78
|
+
expect(actual[:STDERR]).to include("ERROR: The group 'missing' does not exist.")
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
# rubocop:enable Metrics/BlockLength
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'spec_helper'
|
|
2
4
|
|
|
3
5
|
RSpec.describe 'Moose::Inventory::Config' do
|
|
4
6
|
before(:all) do
|
|
5
7
|
# Set up the configuration object
|
|
6
8
|
@mockarg_parts = {
|
|
7
|
-
config:
|
|
8
|
-
format:
|
|
9
|
-
env:
|
|
9
|
+
config: File.join(spec_root, 'config/config.yml'),
|
|
10
|
+
format: 'yaml',
|
|
11
|
+
env: 'test'
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
@mockargs = []
|
|
@@ -24,6 +26,26 @@ RSpec.describe 'Moose::Inventory::Config' do
|
|
|
24
26
|
result = @config.respond_to?(:init)
|
|
25
27
|
expect(result).to eq(true)
|
|
26
28
|
end
|
|
29
|
+
|
|
30
|
+
it 'resets runtime state before parsing new arguments' do
|
|
31
|
+
@config.init(@mockargs)
|
|
32
|
+
@config._settings[:junk] = true
|
|
33
|
+
@config._argv << '--junk'
|
|
34
|
+
|
|
35
|
+
@config.init(['--config', @mockarg_parts[:config]])
|
|
36
|
+
|
|
37
|
+
expect(@config._settings[:junk]).to be_nil
|
|
38
|
+
expect(@config._argv).not_to include('--junk')
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it 'builds a runtime options object from the resolved arguments' do
|
|
42
|
+
@config.init(@mockargs)
|
|
43
|
+
|
|
44
|
+
expect(@config.runtime_options.argv).to eq([])
|
|
45
|
+
expect(@config.runtime_options.output_format).to eq('yaml')
|
|
46
|
+
expect(@config.runtime_options.ansible?).to eq(false)
|
|
47
|
+
expect(@config.application_args).to eq([])
|
|
48
|
+
end
|
|
27
49
|
end
|
|
28
50
|
|
|
29
51
|
# ._configopts
|
|
@@ -60,6 +82,22 @@ RSpec.describe 'Moose::Inventory::Config' do
|
|
|
60
82
|
end
|
|
61
83
|
end
|
|
62
84
|
|
|
85
|
+
describe 'flag parsing' do
|
|
86
|
+
it 'raises when --config is missing its value' do
|
|
87
|
+
expect { @config.init(['--config']) }.to raise_error(RuntimeError, 'Expected a value after --config')
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it 'raises when --env is missing its value' do
|
|
91
|
+
expect { @config.init(['--env', '--config', @mockarg_parts[:config]]) }
|
|
92
|
+
.to raise_error(RuntimeError, 'Expected a value after --env')
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it 'raises when --format is missing its value' do
|
|
96
|
+
expect { @config.init(['--config', @mockarg_parts[:config], '--format']) }
|
|
97
|
+
.to raise_error(RuntimeError, 'Expected a value after --format')
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
63
101
|
# ._settings
|
|
64
102
|
describe '._settings' do
|
|
65
103
|
it 'should be responsive' do
|