moose-inventory 1.0.9 → 2.0
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 +58 -0
- data/.gitleaks.toml +9 -0
- data/.rubocop.yml +28 -0
- data/BACKLOG.md +130 -24
- data/Gemfile.lock +36 -1
- data/README.md +26 -6
- data/Rakefile +1 -1
- data/docs/release/publishing.md +44 -48
- data/docs/release/release-readiness.md +14 -0
- data/docs/security-audit-2026-05-26-rerun.md +75 -0
- data/docs/security-audit-2026-05-26.md +63 -0
- data/lib/moose_inventory/cli/group.rb +3 -0
- data/lib/moose_inventory/cli/group_add.rb +89 -73
- data/lib/moose_inventory/cli/group_addchild.rb +77 -60
- data/lib/moose_inventory/cli/group_addhost.rb +78 -65
- data/lib/moose_inventory/cli/group_rm.rb +101 -71
- data/lib/moose_inventory/cli/group_rmchild.rb +99 -53
- data/lib/moose_inventory/cli/group_rmhost.rb +64 -56
- data/lib/moose_inventory/cli/helpers.rb +76 -0
- data/lib/moose_inventory/cli/host.rb +3 -0
- data/lib/moose_inventory/cli/host_add.rb +47 -62
- data/lib/moose_inventory/cli/host_addgroup.rb +73 -64
- data/lib/moose_inventory/cli/host_rmgroup.rb +58 -55
- data/lib/moose_inventory/db/db.rb +27 -7
- data/lib/moose_inventory/inventory_context.rb +50 -0
- data/lib/moose_inventory/operations/add_associations.rb +127 -0
- data/lib/moose_inventory/operations/add_groups.rb +115 -0
- data/lib/moose_inventory/operations/add_hosts.rb +110 -0
- data/lib/moose_inventory/operations/group_child_relations.rb +118 -0
- data/lib/moose_inventory/operations/group_cleanup.rb +55 -0
- data/lib/moose_inventory/operations/remove_associations.rb +101 -0
- data/lib/moose_inventory/operations/remove_groups.rb +79 -0
- data/lib/moose_inventory/version.rb +1 -1
- data/moose-inventory.gemspec +3 -0
- data/scripts/check.sh +2 -0
- data/scripts/ci/check_permissions.sh +3 -0
- data/scripts/ci/check_rubocop.sh +28 -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/install_dependencies.sh +2 -0
- data/spec/lib/moose_inventory/cli/group_rm_spec.rb +40 -0
- data/spec/lib/moose_inventory/cli/group_rmchild_spec.rb +45 -0
- data/spec/lib/moose_inventory/db/db_spec.rb +162 -0
- data/spec/lib/moose_inventory/operations/add_associations_spec.rb +77 -0
- data/spec/lib/moose_inventory/operations/add_groups_spec.rb +65 -0
- data/spec/lib/moose_inventory/operations/add_hosts_spec.rb +69 -0
- data/spec/lib/moose_inventory/operations/group_child_relations_spec.rb +76 -0
- data/spec/lib/moose_inventory/operations/remove_associations_spec.rb +78 -0
- data/spec/lib/moose_inventory/operations/remove_groups_spec.rb +57 -0
- metadata +90 -1
data/moose-inventory.gemspec
CHANGED
|
@@ -53,8 +53,11 @@ Gem::Specification.new do |spec|
|
|
|
53
53
|
spec.add_runtime_dependency 'thor', '>= 1.3', '< 2'
|
|
54
54
|
|
|
55
55
|
spec.add_development_dependency 'bundler', '>= 2.2.33', '< 3'
|
|
56
|
+
spec.add_development_dependency 'bundler-audit', '>= 0.9', '< 1'
|
|
57
|
+
spec.add_development_dependency 'parallel', '>= 1.10', '< 2.0'
|
|
56
58
|
spec.add_development_dependency 'rake', '>= 13.0', '< 14'
|
|
57
59
|
spec.add_development_dependency 'rspec', '~> 3'
|
|
60
|
+
spec.add_development_dependency 'rubocop', '>= 1.72', '< 2'
|
|
58
61
|
spec.add_development_dependency 'simplecov', '~> 0'
|
|
59
62
|
|
|
60
63
|
end
|
data/scripts/check.sh
CHANGED
|
@@ -5,7 +5,10 @@ allowed_executables=(
|
|
|
5
5
|
"bin/moose-inventory"
|
|
6
6
|
"scripts/check.sh"
|
|
7
7
|
"scripts/ci/check_permissions.sh"
|
|
8
|
+
"scripts/ci/check_rubocop.sh"
|
|
9
|
+
"scripts/ci/check_secrets.sh"
|
|
8
10
|
"scripts/ci/check_security.sh"
|
|
11
|
+
"scripts/ci/install_security_tools.sh"
|
|
9
12
|
"scripts/ci/package_sanity.sh"
|
|
10
13
|
"scripts/files.rb"
|
|
11
14
|
"scripts/install_dependencies.sh"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
bundle exec rubocop \
|
|
5
|
+
lib/moose_inventory/inventory_context.rb \
|
|
6
|
+
lib/moose_inventory/operations/add_hosts.rb \
|
|
7
|
+
lib/moose_inventory/operations/add_groups.rb \
|
|
8
|
+
lib/moose_inventory/operations/add_associations.rb \
|
|
9
|
+
lib/moose_inventory/operations/remove_associations.rb \
|
|
10
|
+
lib/moose_inventory/operations/group_cleanup.rb \
|
|
11
|
+
lib/moose_inventory/operations/group_child_relations.rb \
|
|
12
|
+
lib/moose_inventory/operations/remove_groups.rb \
|
|
13
|
+
lib/moose_inventory/cli/helpers.rb \
|
|
14
|
+
lib/moose_inventory/cli/host_add.rb \
|
|
15
|
+
lib/moose_inventory/cli/group_add.rb \
|
|
16
|
+
lib/moose_inventory/cli/host_addgroup.rb \
|
|
17
|
+
lib/moose_inventory/cli/group_addhost.rb \
|
|
18
|
+
lib/moose_inventory/cli/host_rmgroup.rb \
|
|
19
|
+
lib/moose_inventory/cli/group_rmhost.rb \
|
|
20
|
+
lib/moose_inventory/cli/group_addchild.rb \
|
|
21
|
+
lib/moose_inventory/cli/group_rmchild.rb \
|
|
22
|
+
lib/moose_inventory/cli/group_rm.rb \
|
|
23
|
+
spec/lib/moose_inventory/operations/add_hosts_spec.rb \
|
|
24
|
+
spec/lib/moose_inventory/operations/add_groups_spec.rb \
|
|
25
|
+
spec/lib/moose_inventory/operations/add_associations_spec.rb \
|
|
26
|
+
spec/lib/moose_inventory/operations/remove_associations_spec.rb \
|
|
27
|
+
spec/lib/moose_inventory/operations/group_child_relations_spec.rb \
|
|
28
|
+
spec/lib/moose_inventory/operations/remove_groups_spec.rb
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
BIN_DIR="${MOOSE_INVENTORY_SECURITY_TOOLS_BIN:-$PWD/tmp/security-tools/bin}"
|
|
5
|
+
if command -v gitleaks >/dev/null 2>&1; then
|
|
6
|
+
GITLEAKS=(gitleaks)
|
|
7
|
+
elif [ -x "$BIN_DIR/gitleaks" ]; then
|
|
8
|
+
GITLEAKS=("$BIN_DIR/gitleaks")
|
|
9
|
+
else
|
|
10
|
+
if [ "${MOOSE_INVENTORY_REQUIRE_SECURITY_TOOLS:-0}" = "1" ]; then
|
|
11
|
+
echo "gitleaks is required but was not found. Run scripts/ci/install_security_tools.sh first." >&2
|
|
12
|
+
exit 2
|
|
13
|
+
fi
|
|
14
|
+
echo "gitleaks not found; skipping dedicated secret scan."
|
|
15
|
+
exit 0
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
"${GITLEAKS[@]}" detect \
|
|
19
|
+
--no-git \
|
|
20
|
+
--source . \
|
|
21
|
+
--config .gitleaks.toml \
|
|
22
|
+
--redact \
|
|
23
|
+
--no-banner \
|
|
24
|
+
--log-level warn
|
|
25
|
+
|
|
26
|
+
echo "Gitleaks secret scan passed."
|
|
@@ -48,3 +48,21 @@ if findings:
|
|
|
48
48
|
print(f'- {name} {version}: {vuln_id} {summary}', file=sys.stderr)
|
|
49
49
|
sys.exit(1)
|
|
50
50
|
PY
|
|
51
|
+
|
|
52
|
+
bundle exec bundle-audit check --update
|
|
53
|
+
|
|
54
|
+
BIN_DIR="${MOOSE_INVENTORY_SECURITY_TOOLS_BIN:-$PWD/tmp/security-tools/bin}"
|
|
55
|
+
if command -v osv-scanner >/dev/null 2>&1; then
|
|
56
|
+
OSV_SCANNER=(osv-scanner)
|
|
57
|
+
elif [ -x "$BIN_DIR/osv-scanner" ]; then
|
|
58
|
+
OSV_SCANNER=("$BIN_DIR/osv-scanner")
|
|
59
|
+
else
|
|
60
|
+
if [ "${MOOSE_INVENTORY_REQUIRE_SECURITY_TOOLS:-0}" = "1" ]; then
|
|
61
|
+
echo "osv-scanner is required but was not found. Run scripts/ci/install_security_tools.sh first." >&2
|
|
62
|
+
exit 2
|
|
63
|
+
fi
|
|
64
|
+
echo "osv-scanner not found; skipping osv-scanner lockfile scan."
|
|
65
|
+
exit 0
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
"${OSV_SCANNER[@]}" scan source --lockfile Gemfile.lock .
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Installs optional security audit CLIs used by CI. They are kept out of the
|
|
5
|
+
# gem runtime/development bundle because they are Go command-line tools, not
|
|
6
|
+
# Ruby dependencies.
|
|
7
|
+
|
|
8
|
+
BIN_DIR="${MOOSE_INVENTORY_SECURITY_TOOLS_BIN:-$PWD/tmp/security-tools/bin}"
|
|
9
|
+
GITLEAKS_VERSION="${GITLEAKS_VERSION:-v8.30.0}"
|
|
10
|
+
OSV_SCANNER_VERSION="${OSV_SCANNER_VERSION:-v2.2.3}"
|
|
11
|
+
|
|
12
|
+
mkdir -p "$BIN_DIR"
|
|
13
|
+
|
|
14
|
+
if ! command -v go >/dev/null 2>&1; then
|
|
15
|
+
echo "Go is required to install gitleaks/osv-scanner. Install Go or use a prebuilt package." >&2
|
|
16
|
+
exit 2
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
install_go_tool() {
|
|
20
|
+
local name="$1"
|
|
21
|
+
local module="$2"
|
|
22
|
+
local version="$3"
|
|
23
|
+
|
|
24
|
+
if command -v "$name" >/dev/null 2>&1; then
|
|
25
|
+
echo "$name already available at $(command -v "$name")"
|
|
26
|
+
return
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
if [ -x "$BIN_DIR/$name" ]; then
|
|
30
|
+
echo "$name already installed at $BIN_DIR/$name"
|
|
31
|
+
return
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
echo "Installing $name $version into $BIN_DIR"
|
|
35
|
+
GOBIN="$BIN_DIR" go install "$module@$version"
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
install_go_tool gitleaks github.com/zricethezav/gitleaks/v8 "$GITLEAKS_VERSION"
|
|
39
|
+
install_go_tool osv-scanner github.com/google/osv-scanner/v2/cmd/osv-scanner "$OSV_SCANNER_VERSION"
|
|
40
|
+
|
|
41
|
+
if [ -n "${GITHUB_PATH:-}" ]; then
|
|
42
|
+
echo "$BIN_DIR" >> "$GITHUB_PATH"
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
export PATH="$BIN_DIR:$PATH"
|
|
46
|
+
gitleaks version || true
|
|
47
|
+
osv-scanner --version
|
|
@@ -241,5 +241,45 @@ RSpec.describe Moose::Inventory::Cli::Group do
|
|
|
241
241
|
groups = @db.models[:group].all
|
|
242
242
|
expect(groups.count).to eq(1)
|
|
243
243
|
end
|
|
244
|
+
|
|
245
|
+
#---------------
|
|
246
|
+
it 'GROUP --recursive ... should remove orphaned child groups recursively' do
|
|
247
|
+
runner { @app.start(%w(group add parent)) }
|
|
248
|
+
runner { @app.start(%w(group add child --hosts child-host)) }
|
|
249
|
+
runner { @app.start(%w(group add grandchild)) }
|
|
250
|
+
runner { @app.start(%w(group addchild parent child)) }
|
|
251
|
+
runner { @app.start(%w(group addchild child grandchild)) }
|
|
252
|
+
|
|
253
|
+
actual = runner { @app.start(%w(group rm --recursive parent)) }
|
|
254
|
+
|
|
255
|
+
expect(actual[:unexpected]).to eq(false)
|
|
256
|
+
expect(actual[:aborted]).to eq(false)
|
|
257
|
+
expect(actual[:STDOUT]).to include("- Recursively delete orphaned group 'child'...\n")
|
|
258
|
+
expect(actual[:STDOUT]).to include("- Recursively delete orphaned group 'grandchild'...\n")
|
|
259
|
+
|
|
260
|
+
%w(parent child grandchild).each do |name|
|
|
261
|
+
expect(@db.models[:group].find(name: name)).to be_nil
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
host = @db.models[:host].find(name: 'child-host')
|
|
265
|
+
expect(host.groups_dataset[name: 'ungrouped']).not_to be_nil
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
#---------------
|
|
269
|
+
it 'GROUP --recursive ... should not remove child groups with another parent' do
|
|
270
|
+
runner { @app.start(%w(group add parent other-parent)) }
|
|
271
|
+
runner { @app.start(%w(group addchild parent child)) }
|
|
272
|
+
runner { @app.start(%w(group addchild other-parent child)) }
|
|
273
|
+
|
|
274
|
+
actual = runner { @app.start(%w(group rm --recursive parent)) }
|
|
275
|
+
|
|
276
|
+
expect(actual[:unexpected]).to eq(false)
|
|
277
|
+
expect(actual[:aborted]).to eq(false)
|
|
278
|
+
expect(@db.models[:group].find(name: 'parent')).to be_nil
|
|
279
|
+
|
|
280
|
+
child = @db.models[:group].find(name: 'child')
|
|
281
|
+
expect(child).not_to be_nil
|
|
282
|
+
expect(child.parents_dataset[name: 'other-parent']).not_to be_nil
|
|
283
|
+
end
|
|
244
284
|
end
|
|
245
285
|
end
|
|
@@ -169,5 +169,50 @@ RSpec.describe Moose::Inventory::Cli::Group do
|
|
|
169
169
|
|
|
170
170
|
expected(actual, desired)
|
|
171
171
|
end
|
|
172
|
+
|
|
173
|
+
#------------------------
|
|
174
|
+
it 'GROUP CHILDGROUP --delete-orphans ... should delete orphaned child groups recursively' do
|
|
175
|
+
runner { @app.start(%w(group add parent)) }
|
|
176
|
+
runner { @app.start(%w(group add child --hosts child-host)) }
|
|
177
|
+
runner { @app.start(%w(group add grandchild)) }
|
|
178
|
+
runner { @app.start(%w(group addchild parent child)) }
|
|
179
|
+
runner { @app.start(%w(group addchild child grandchild)) }
|
|
180
|
+
|
|
181
|
+
actual = runner do
|
|
182
|
+
@app.start(%w(group rmchild --delete-orphans parent child))
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
expect(actual[:unexpected]).to eq(false)
|
|
186
|
+
expect(actual[:aborted]).to eq(false)
|
|
187
|
+
expect(actual[:STDOUT]).to include("- Recursively delete orphaned group 'child'...\n")
|
|
188
|
+
expect(actual[:STDOUT]).to include("- Recursively delete orphaned group 'grandchild'...\n")
|
|
189
|
+
|
|
190
|
+
expect(@db.models[:group].find(name: 'parent')).not_to be_nil
|
|
191
|
+
%w(child grandchild).each do |name|
|
|
192
|
+
expect(@db.models[:group].find(name: name)).to be_nil
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
host = @db.models[:host].find(name: 'child-host')
|
|
196
|
+
expect(host.groups_dataset[name: 'ungrouped']).not_to be_nil
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
#------------------------
|
|
200
|
+
it 'GROUP CHILDGROUP --delete-orphans ... should preserve child groups with another parent' do
|
|
201
|
+
runner { @app.start(%w(group add parent other-parent)) }
|
|
202
|
+
runner { @app.start(%w(group addchild parent child)) }
|
|
203
|
+
runner { @app.start(%w(group addchild other-parent child)) }
|
|
204
|
+
|
|
205
|
+
actual = runner do
|
|
206
|
+
@app.start(%w(group rmchild --delete-orphans parent child))
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
expect(actual[:unexpected]).to eq(false)
|
|
210
|
+
expect(actual[:aborted]).to eq(false)
|
|
211
|
+
|
|
212
|
+
child = @db.models[:group].find(name: 'child')
|
|
213
|
+
expect(child).not_to be_nil
|
|
214
|
+
expect(child.parents_dataset[name: 'parent']).to be_nil
|
|
215
|
+
expect(child.parents_dataset[name: 'other-parent']).not_to be_nil
|
|
216
|
+
end
|
|
172
217
|
end
|
|
173
218
|
end
|
|
@@ -190,6 +190,59 @@ RSpec.describe 'Moose::Inventory::DB' do
|
|
|
190
190
|
end
|
|
191
191
|
end
|
|
192
192
|
|
|
193
|
+
it 'raises a Moose DB exception when password and password_env are missing' do
|
|
194
|
+
with_db_config(
|
|
195
|
+
adapter: 'mysql',
|
|
196
|
+
host: 'localhost',
|
|
197
|
+
database: 'moose_inventory_test',
|
|
198
|
+
user: 'moose'
|
|
199
|
+
) do
|
|
200
|
+
expect { @db.init_mysql }.to raise_error(
|
|
201
|
+
Moose::Inventory::DB::MooseDBException,
|
|
202
|
+
/Expected key password or password_env missing in mysql configuration/
|
|
203
|
+
)
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
it 'uses a mysql password from the configured environment variable' do
|
|
208
|
+
saved_db = @db.instance_variable_get(:@db)
|
|
209
|
+
saved_settings = @config._settings.dup
|
|
210
|
+
saved_password = ENV['MOOSE_INVENTORY_MYSQL_PASSWORD']
|
|
211
|
+
mysql_config = {
|
|
212
|
+
adapter: 'mysql',
|
|
213
|
+
host: 'localhost',
|
|
214
|
+
database: 'moose_inventory_test',
|
|
215
|
+
user: 'moose',
|
|
216
|
+
password_env: 'MOOSE_INVENTORY_MYSQL_PASSWORD',
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
begin
|
|
220
|
+
ENV['MOOSE_INVENTORY_MYSQL_PASSWORD'] = 'env-secret'
|
|
221
|
+
@db.instance_variable_set(:@db, nil)
|
|
222
|
+
@config._settings.clear
|
|
223
|
+
@config._settings[:config] = { db: mysql_config }
|
|
224
|
+
|
|
225
|
+
expect(Sequel).to receive(:mysql2).with(
|
|
226
|
+
user: 'moose',
|
|
227
|
+
password: 'env-secret',
|
|
228
|
+
host: 'localhost',
|
|
229
|
+
database: 'moose_inventory_test'
|
|
230
|
+
).and_return(:mysql2_connection)
|
|
231
|
+
|
|
232
|
+
@db.init_mysql
|
|
233
|
+
expect(@db.db).to eq(:mysql2_connection)
|
|
234
|
+
ensure
|
|
235
|
+
if saved_password.nil?
|
|
236
|
+
ENV.delete('MOOSE_INVENTORY_MYSQL_PASSWORD')
|
|
237
|
+
else
|
|
238
|
+
ENV['MOOSE_INVENTORY_MYSQL_PASSWORD'] = saved_password
|
|
239
|
+
end
|
|
240
|
+
@db.instance_variable_set(:@db, saved_db)
|
|
241
|
+
@config._settings.clear
|
|
242
|
+
@config._settings.merge!(saved_settings)
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
193
246
|
it 'uses the mysql2 Sequel adapter with configured connection settings' do
|
|
194
247
|
saved_db = @db.instance_variable_get(:@db)
|
|
195
248
|
saved_settings = @config._settings.dup
|
|
@@ -238,6 +291,67 @@ RSpec.describe 'Moose::Inventory::DB' do
|
|
|
238
291
|
end
|
|
239
292
|
end
|
|
240
293
|
|
|
294
|
+
it 'raises a Moose DB exception when password_env points to an unset variable' do
|
|
295
|
+
saved_password = ENV['MOOSE_INVENTORY_POSTGRES_PASSWORD']
|
|
296
|
+
ENV.delete('MOOSE_INVENTORY_POSTGRES_PASSWORD')
|
|
297
|
+
|
|
298
|
+
begin
|
|
299
|
+
with_db_config(
|
|
300
|
+
adapter: 'postgresql',
|
|
301
|
+
host: 'localhost',
|
|
302
|
+
database: 'moose_inventory_test',
|
|
303
|
+
user: 'moose',
|
|
304
|
+
password_env: 'MOOSE_INVENTORY_POSTGRES_PASSWORD'
|
|
305
|
+
) do
|
|
306
|
+
expect { @db.init_postgresql }.to raise_error(
|
|
307
|
+
Moose::Inventory::DB::MooseDBException,
|
|
308
|
+
/Environment variable MOOSE_INVENTORY_POSTGRES_PASSWORD is not set for postgresql password/
|
|
309
|
+
)
|
|
310
|
+
end
|
|
311
|
+
ensure
|
|
312
|
+
ENV['MOOSE_INVENTORY_POSTGRES_PASSWORD'] = saved_password unless saved_password.nil?
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
it 'uses a postgresql password from the configured environment variable' do
|
|
317
|
+
saved_db = @db.instance_variable_get(:@db)
|
|
318
|
+
saved_settings = @config._settings.dup
|
|
319
|
+
saved_password = ENV['MOOSE_INVENTORY_POSTGRES_PASSWORD']
|
|
320
|
+
postgresql_config = {
|
|
321
|
+
adapter: 'postgresql',
|
|
322
|
+
host: 'localhost',
|
|
323
|
+
database: 'moose_inventory_test',
|
|
324
|
+
user: 'moose',
|
|
325
|
+
password_env: 'MOOSE_INVENTORY_POSTGRES_PASSWORD',
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
begin
|
|
329
|
+
ENV['MOOSE_INVENTORY_POSTGRES_PASSWORD'] = 'env-secret'
|
|
330
|
+
@db.instance_variable_set(:@db, nil)
|
|
331
|
+
@config._settings.clear
|
|
332
|
+
@config._settings[:config] = { db: postgresql_config }
|
|
333
|
+
|
|
334
|
+
expect(Sequel).to receive(:postgres).with(
|
|
335
|
+
user: 'moose',
|
|
336
|
+
password: 'env-secret',
|
|
337
|
+
host: 'localhost',
|
|
338
|
+
database: 'moose_inventory_test'
|
|
339
|
+
).and_return(:postgresql_connection)
|
|
340
|
+
|
|
341
|
+
@db.init_postgresql
|
|
342
|
+
expect(@db.db).to eq(:postgresql_connection)
|
|
343
|
+
ensure
|
|
344
|
+
if saved_password.nil?
|
|
345
|
+
ENV.delete('MOOSE_INVENTORY_POSTGRES_PASSWORD')
|
|
346
|
+
else
|
|
347
|
+
ENV['MOOSE_INVENTORY_POSTGRES_PASSWORD'] = saved_password
|
|
348
|
+
end
|
|
349
|
+
@db.instance_variable_set(:@db, saved_db)
|
|
350
|
+
@config._settings.clear
|
|
351
|
+
@config._settings.merge!(saved_settings)
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
|
|
241
355
|
it 'uses the postgres Sequel adapter with configured connection settings' do
|
|
242
356
|
saved_db = @db.instance_variable_get(:@db)
|
|
243
357
|
saved_settings = @config._settings.dup
|
|
@@ -402,5 +516,53 @@ RSpec.describe 'Moose::Inventory::DB' do
|
|
|
402
516
|
|
|
403
517
|
expect(count[:final]).to eq(count[:initial])
|
|
404
518
|
end
|
|
519
|
+
|
|
520
|
+
it 'prints concise Moose DB transaction errors by default' do
|
|
521
|
+
saved_trace = @config._confopts[:trace]
|
|
522
|
+
@config._confopts[:trace] = false
|
|
523
|
+
|
|
524
|
+
begin
|
|
525
|
+
actual = runner do
|
|
526
|
+
@db.transaction do
|
|
527
|
+
fail @db.exceptions[:moose], 'Trace regression target'
|
|
528
|
+
end
|
|
529
|
+
end
|
|
530
|
+
ensure
|
|
531
|
+
@config._confopts[:trace] = saved_trace
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
expect(actual[:unexpected]).to eq(false)
|
|
535
|
+
expect(actual[:aborted]).to eq(true)
|
|
536
|
+
expect(actual[:STDERR]).to eq(
|
|
537
|
+
"An error occurred during a transaction, any changes have been rolled back.\n" \
|
|
538
|
+
"ERROR: Trace regression target\n"
|
|
539
|
+
)
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
it 'prints the Moose DB exception backtrace when trace is enabled' do
|
|
543
|
+
saved_trace = @config._confopts[:trace]
|
|
544
|
+
@config._confopts[:trace] = true
|
|
545
|
+
|
|
546
|
+
begin
|
|
547
|
+
actual = runner do
|
|
548
|
+
@db.transaction do
|
|
549
|
+
fail @db.exceptions[:moose], 'Trace regression target'
|
|
550
|
+
end
|
|
551
|
+
end
|
|
552
|
+
ensure
|
|
553
|
+
@config._confopts[:trace] = saved_trace
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
expect(actual[:unexpected]).to eq(false)
|
|
557
|
+
expect(actual[:aborted]).to eq(true)
|
|
558
|
+
expect(actual[:STDERR]).to include(
|
|
559
|
+
"An error occurred during a transaction, any changes have been rolled back.\n"
|
|
560
|
+
)
|
|
561
|
+
expect(actual[:STDERR]).to include('Moose::Inventory::DB::MooseDBException')
|
|
562
|
+
expect(actual[:STDERR]).to include('Trace regression target')
|
|
563
|
+
expect(actual[:STDERR]).to include('spec/lib/moose_inventory/db/db_spec.rb')
|
|
564
|
+
expect(actual[:STDERR]).to include("ERROR: Trace regression target\n")
|
|
565
|
+
expect(actual[:STDERR]).not_to include('NoMethodError')
|
|
566
|
+
end
|
|
405
567
|
end
|
|
406
568
|
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'inventory_context'
|
|
5
|
+
require 'operations/add_associations'
|
|
6
|
+
|
|
7
|
+
RSpec.describe Moose::Inventory::Operations::AddAssociations do
|
|
8
|
+
before(:all) do
|
|
9
|
+
@mockargs = [
|
|
10
|
+
'--config', File.join(spec_root, 'config/config.yml'),
|
|
11
|
+
'--format', 'yaml',
|
|
12
|
+
'--env', 'test'
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
Moose::Inventory::Config.init(@mockargs)
|
|
16
|
+
@db = Moose::Inventory::DB
|
|
17
|
+
@db.init if @db.db.nil?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
before(:each) do
|
|
21
|
+
@db.reset
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def operation
|
|
25
|
+
described_class.new(
|
|
26
|
+
context: Moose::Inventory::InventoryContext.new(db: @db)
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'adds groups to an existing host and reports creation/duplicate events' do
|
|
31
|
+
host = @db.models[:host].create(name: 'host1')
|
|
32
|
+
ungrouped = @db.models[:group].find_or_create(name: 'ungrouped')
|
|
33
|
+
host.add_group(ungrouped)
|
|
34
|
+
existing_group = @db.models[:group].create(name: 'existing')
|
|
35
|
+
host.add_group(existing_group)
|
|
36
|
+
|
|
37
|
+
result = operation.host_to_groups(
|
|
38
|
+
host: host,
|
|
39
|
+
host_name: 'host1',
|
|
40
|
+
group_names: %w[existing created]
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
expect(result.warning_count).to eq(2)
|
|
44
|
+
expect(result.events.map(&:type)).to include(
|
|
45
|
+
:host_group_association_exists,
|
|
46
|
+
:group_missing_created,
|
|
47
|
+
:removing_automatic_group
|
|
48
|
+
)
|
|
49
|
+
expect(host.groups_dataset[name: 'created']).not_to be_nil
|
|
50
|
+
expect(host.groups_dataset[name: 'ungrouped']).to be_nil
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'adds hosts to an existing group and reports creation/duplicate events' do
|
|
54
|
+
group = @db.models[:group].create(name: 'group1')
|
|
55
|
+
duplicate_host = @db.models[:host].create(name: 'host1')
|
|
56
|
+
existing_host = @db.models[:host].create(name: 'host3')
|
|
57
|
+
ungrouped = @db.models[:group].find_or_create(name: 'ungrouped')
|
|
58
|
+
duplicate_host.add_group(ungrouped)
|
|
59
|
+
existing_host.add_group(ungrouped)
|
|
60
|
+
group.add_host(duplicate_host)
|
|
61
|
+
|
|
62
|
+
result = operation.group_to_hosts(
|
|
63
|
+
group: group,
|
|
64
|
+
group_name: 'group1',
|
|
65
|
+
host_names: %w[host1 host2 host3]
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
expect(result.warning_count).to eq(2)
|
|
69
|
+
expect(result.events.map(&:type)).to include(
|
|
70
|
+
:group_host_association_exists,
|
|
71
|
+
:host_missing_created,
|
|
72
|
+
:removing_automatic_group
|
|
73
|
+
)
|
|
74
|
+
expect(group.hosts_dataset[name: 'host2']).not_to be_nil
|
|
75
|
+
expect(existing_host.groups_dataset[name: 'ungrouped']).to be_nil
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'inventory_context'
|
|
5
|
+
require 'operations/add_groups'
|
|
6
|
+
|
|
7
|
+
RSpec.describe Moose::Inventory::Operations::AddGroups do
|
|
8
|
+
before(:all) do
|
|
9
|
+
@mockargs = [
|
|
10
|
+
'--config', File.join(spec_root, 'config/config.yml'),
|
|
11
|
+
'--format', 'yaml',
|
|
12
|
+
'--env', 'test'
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
Moose::Inventory::Config.init(@mockargs)
|
|
16
|
+
@db = Moose::Inventory::DB
|
|
17
|
+
@db.init if @db.db.nil?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
before(:each) do
|
|
21
|
+
@db.reset
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def operation
|
|
25
|
+
described_class.new(
|
|
26
|
+
context: Moose::Inventory::InventoryContext.new(db: @db)
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'adds a group and returns structured events without rendering output' do
|
|
31
|
+
actual = runner do
|
|
32
|
+
@result = operation.call(names: ['testgroup'], hosts: [])
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
expected(actual, STDOUT: '', STDERR: '')
|
|
36
|
+
expect(@result.warning_count).to eq(0)
|
|
37
|
+
expect(@result.events.map(&:type)).to eq(%i[group_started creating_group ok group_complete])
|
|
38
|
+
|
|
39
|
+
group = @db.models[:group].find(name: 'testgroup')
|
|
40
|
+
expect(group).not_to be_nil
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it 'reports existing groups, created hosts, duplicate associations, and ungrouped removal as events' do
|
|
44
|
+
host = @db.models[:host].create(name: 'testhost')
|
|
45
|
+
ungrouped = @db.models[:group].find_or_create(name: 'ungrouped')
|
|
46
|
+
host.add_group(ungrouped)
|
|
47
|
+
group = @db.models[:group].create(name: 'testgroup')
|
|
48
|
+
group.add_host(host)
|
|
49
|
+
|
|
50
|
+
@result = operation.call(
|
|
51
|
+
names: ['testgroup'],
|
|
52
|
+
hosts: %w[testhost newhost]
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
expect(@result.warning_count).to eq(3)
|
|
56
|
+
expect(@result.events.map(&:type)).to include(
|
|
57
|
+
:group_exists,
|
|
58
|
+
:association_exists,
|
|
59
|
+
:host_missing_created,
|
|
60
|
+
:removing_automatic_group
|
|
61
|
+
)
|
|
62
|
+
expect(@db.models[:host].find(name: 'newhost')).not_to be_nil
|
|
63
|
+
expect(host.groups_dataset[name: 'ungrouped']).to be_nil
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Moose::Inventory::Operations::AddHosts do
|
|
6
|
+
before(:all) do
|
|
7
|
+
@mockargs = [
|
|
8
|
+
'--config', File.join(spec_root, 'config/config.yml'),
|
|
9
|
+
'--format', 'yaml',
|
|
10
|
+
'--env', 'test'
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
Moose::Inventory::Config.init(@mockargs)
|
|
14
|
+
@db = Moose::Inventory::DB
|
|
15
|
+
@db.init if @db.db.nil?
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
before(:each) do
|
|
19
|
+
@db.reset
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def operation
|
|
23
|
+
described_class.new(
|
|
24
|
+
context: Moose::Inventory::InventoryContext.new(db: @db)
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
describe '#call' do
|
|
29
|
+
it 'adds a host and returns structured events without rendering output' do
|
|
30
|
+
actual = runner do
|
|
31
|
+
@result = operation.call(names: ['testhost'], groups: [])
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
expected(actual, STDOUT: '', STDERR: '')
|
|
35
|
+
expect(@result.events.map(&:type)).to eq(
|
|
36
|
+
%i[host_started creating_host ok adding_automatic_group ok host_complete]
|
|
37
|
+
)
|
|
38
|
+
expect(@result.events[0].payload).to eq(name: 'testhost')
|
|
39
|
+
expect(@result.events[3].payload).to eq(host: 'testhost', group: 'ungrouped')
|
|
40
|
+
|
|
41
|
+
host = @db.models[:host].find(name: 'testhost')
|
|
42
|
+
expect(host).not_to be_nil
|
|
43
|
+
expect(host.groups_dataset[name: 'ungrouped']).not_to be_nil
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'reports existing hosts, missing groups, and duplicate associations as events' do
|
|
47
|
+
host = @db.models[:host].create(name: 'testhost')
|
|
48
|
+
group = @db.models[:group].create(name: 'existinggroup')
|
|
49
|
+
host.add_group(group)
|
|
50
|
+
|
|
51
|
+
@result = operation.call(
|
|
52
|
+
names: ['testhost'],
|
|
53
|
+
groups: %w[existinggroup missinggroup]
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
expect(@result.events.map(&:type)).to include(
|
|
57
|
+
:host_exists,
|
|
58
|
+
:association_exists,
|
|
59
|
+
:group_missing_created
|
|
60
|
+
)
|
|
61
|
+
expect(@result.events.find { |event| event.type == :host_exists }.payload).to eq(name: 'testhost')
|
|
62
|
+
expect(@result.events.find { |event| event.type == :association_exists }.payload).to eq(
|
|
63
|
+
host: 'testhost',
|
|
64
|
+
group: 'existinggroup'
|
|
65
|
+
)
|
|
66
|
+
expect(@db.models[:group].find(name: 'missinggroup')).not_to be_nil
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|