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,38 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'thor'
|
|
2
4
|
require 'json'
|
|
3
5
|
|
|
4
|
-
require_relative '
|
|
5
|
-
require_relative '../
|
|
6
|
+
require_relative '../inventory_context'
|
|
7
|
+
require_relative '../operations/query_inventory'
|
|
6
8
|
|
|
7
9
|
module Moose
|
|
8
10
|
module Inventory
|
|
9
11
|
module Cli
|
|
10
|
-
##
|
|
11
12
|
# Implementation of the "host list" method of the CLI
|
|
12
13
|
class Host
|
|
13
14
|
desc 'list', 'List the contents of the inventory by host'
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
option :group, type: :string, desc: 'Only include hosts in all comma-separated groups'
|
|
16
|
+
option :tag, type: :string, desc: 'Only include hosts with all comma-separated tags'
|
|
17
|
+
option :var, type: :string, desc: 'Only include hosts with comma-separated key=value variables'
|
|
18
|
+
def list
|
|
19
|
+
fmt.dump(inventory_query.list_hosts(filters: host_list_filters), output_format)
|
|
20
|
+
end
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def host_list_filters
|
|
25
|
+
{
|
|
26
|
+
groups: csv_option_names(options[:group]),
|
|
27
|
+
tags: csv_option_names(options[:tag]),
|
|
28
|
+
variables: variable_filter_options(options[:var])
|
|
29
|
+
}
|
|
30
|
+
end
|
|
25
31
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
32
|
+
def variable_filter_options(value)
|
|
33
|
+
csv_option_names(value).to_h do |entry|
|
|
34
|
+
key, variable_value = entry.split('=', 2)
|
|
35
|
+
abort("ERROR: Invalid variable filter '#{entry}'. Expected key=value.") if variable_value.nil?
|
|
30
36
|
|
|
31
|
-
|
|
32
|
-
results[host[:name].to_sym][:hostvars] = hostvars
|
|
33
|
-
end
|
|
37
|
+
[key, variable_value]
|
|
34
38
|
end
|
|
35
|
-
fmt.dump(results)
|
|
36
39
|
end
|
|
37
40
|
end
|
|
38
41
|
end
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'thor'
|
|
2
4
|
require 'json'
|
|
3
5
|
|
|
4
|
-
require_relative '
|
|
5
|
-
require_relative '../
|
|
6
|
+
require_relative '../inventory_context'
|
|
7
|
+
require_relative '../operations/query_inventory'
|
|
6
8
|
|
|
7
9
|
module Moose
|
|
8
10
|
module Inventory
|
|
@@ -13,68 +15,13 @@ module Moose
|
|
|
13
15
|
#==========================
|
|
14
16
|
desc 'listvar', 'List all variables associated with the host'
|
|
15
17
|
def listvars(*argv)
|
|
16
|
-
|
|
17
|
-
confopts = Moose::Inventory::Config._confopts
|
|
18
|
-
|
|
19
|
-
# sanity
|
|
20
|
-
if confopts[:ansible] == true
|
|
21
|
-
if argv.length != 1
|
|
22
|
-
abort('ERROR: Wrong number of arguments for Ansible mode, '\
|
|
23
|
-
"#{args.length} for 1.")
|
|
24
|
-
end
|
|
25
|
-
else
|
|
26
|
-
if argv.empty?
|
|
27
|
-
abort('ERROR: Wrong number of arguments, '\
|
|
28
|
-
"#{args.length} for 1 or more.")
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
# Convenience
|
|
33
|
-
db = Moose::Inventory::DB
|
|
34
|
-
fmt = Moose::Inventory::Cli::Formatter
|
|
35
|
-
|
|
36
|
-
# Arguments
|
|
37
|
-
names = argv.uniq.map(&:downcase)
|
|
38
|
-
|
|
39
|
-
# process
|
|
40
|
-
results = {}
|
|
41
|
-
|
|
42
|
-
if confopts[:ansible] == true
|
|
43
|
-
# This is the implementation per Ansible specs
|
|
44
|
-
name = names.first
|
|
45
|
-
host = db.models[:host].find(name: name)
|
|
46
|
-
if host.nil?
|
|
47
|
-
fmt.warn "The host #{name} does not exist.\n"
|
|
48
|
-
else
|
|
49
|
-
host.hostvars_dataset.each do |hv|
|
|
50
|
-
results[hv[:name].to_sym] = hv[:value]
|
|
51
|
-
end
|
|
52
|
-
end
|
|
18
|
+
validate_listvars_args(argv)
|
|
53
19
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
results['_meta'.to_sym]['hostvars'.to_sym] = {}
|
|
58
|
-
db.models[:host].each do |host|
|
|
59
|
-
results['_meta'.to_sym]['hostvars'.to_sym][host.name.to_sym] = {}
|
|
60
|
-
host.hostvars_dataset.each do |hv|
|
|
61
|
-
results['_meta'.to_sym]['hostvars'.to_sym][host.name.to_sym][hv[:name].to_sym] = hv[:value]
|
|
62
|
-
end
|
|
63
|
-
end
|
|
20
|
+
names = normalize_names(argv)
|
|
21
|
+
results = inventory_query.list_host_vars(names: names, ansible: ansible_mode?)
|
|
22
|
+
warn_if_missing_ansible_listvars_entity(:host, names.first)
|
|
64
23
|
|
|
65
|
-
|
|
66
|
-
# This our more flexible implementation, which is not compatible
|
|
67
|
-
# with the Ansible specs
|
|
68
|
-
names.each do |name|
|
|
69
|
-
host = db.models[:host].find(name: name)
|
|
70
|
-
next if host.nil?
|
|
71
|
-
results[name.to_sym] = {}
|
|
72
|
-
host.hostvars_dataset.each do |hv|
|
|
73
|
-
results[name.to_sym][hv[:name].to_sym] = hv[:value]
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
fmt.dump(results)
|
|
24
|
+
fmt.dump(results, output_format)
|
|
78
25
|
end
|
|
79
26
|
end
|
|
80
27
|
end
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'thor'
|
|
2
4
|
require 'json'
|
|
3
5
|
|
|
4
|
-
require_relative '
|
|
5
|
-
require_relative '../
|
|
6
|
+
require_relative '../inventory_context'
|
|
7
|
+
require_relative '../operations/remove_hosts'
|
|
6
8
|
|
|
7
9
|
module Moose
|
|
8
10
|
module Inventory
|
|
@@ -13,49 +15,65 @@ module Moose
|
|
|
13
15
|
#==========================
|
|
14
16
|
desc 'rm HOSTNAME_1 [HOSTNAME_2 ...]',
|
|
15
17
|
'Remove hosts HOSTNAME_n from the inventory'
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
option :dry_run, type: :boolean
|
|
19
|
+
option :yes, type: :boolean, desc: 'Confirm destructive removal without prompting'
|
|
20
|
+
option :plan_format, type: :string, desc: 'Emit dry-run plan events as yaml|json|pjson'
|
|
21
|
+
def rm(*argv)
|
|
22
|
+
abort_if_missing_args(argv, 1, '1 or more')
|
|
23
|
+
validate_machine_plan_request!
|
|
24
|
+
|
|
25
|
+
names = normalize_names(argv)
|
|
26
|
+
confirm_destructive_action!("host rm #{names.join(',')}")
|
|
27
|
+
result = remove_hosts_operation.call(names: names, dry_run: options[:dry_run])
|
|
28
|
+
return if machine_plan_output_rendered?(result, command: 'host rm')
|
|
29
|
+
|
|
30
|
+
record_audit({ command: 'host rm', action: 'remove', entity_type: 'host',
|
|
31
|
+
entity_names: names }, result: result, dry_run: options[:dry_run])
|
|
32
|
+
render_remove_hosts_events(result.events)
|
|
33
|
+
print_warning_summary(result)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def remove_hosts_operation
|
|
39
|
+
build_operation(Moose::Inventory::Operations::RemoveHosts)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def render_remove_hosts_events(events)
|
|
43
|
+
events.each { |event| render_remove_hosts_event(event) }
|
|
44
|
+
end
|
|
23
45
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
end
|
|
50
|
-
fmt.puts 2, '- All OK'
|
|
51
|
-
end
|
|
52
|
-
end # Transaction end
|
|
53
|
-
if warn_count == 0
|
|
54
|
-
puts 'Succeeded.'
|
|
55
|
-
else
|
|
56
|
-
puts 'Succeeded, with warnings.'
|
|
46
|
+
def render_remove_hosts_event(event)
|
|
47
|
+
payload = event.payload
|
|
48
|
+
|
|
49
|
+
return render_host_rm_progress(event.type, payload) if host_rm_progress_event?(event.type)
|
|
50
|
+
return render_host_rm_warning(payload) if event.type == :host_missing
|
|
51
|
+
return fmt.puts(payload[:indent], '- No such host, skipping.') if event.type == :missing_skipping
|
|
52
|
+
return fmt.puts(payload[:indent], '- OK') if event.type == :ok
|
|
53
|
+
|
|
54
|
+
return fmt.puts 2, '- All OK' if event.type == :host_complete
|
|
55
|
+
|
|
56
|
+
puts 'Dry run complete. No changes applied.' if event.type == :dry_run_summary
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def host_rm_progress_event?(type)
|
|
60
|
+
%i[host_started retrieving_host destroying_host].include?(type)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def render_host_rm_progress(type, payload)
|
|
64
|
+
case type
|
|
65
|
+
when :host_started
|
|
66
|
+
puts "Remove host '#{payload[:name]}':"
|
|
67
|
+
when :retrieving_host
|
|
68
|
+
fmt.puts 2, "- Retrieve host '#{payload[:name]}'..."
|
|
69
|
+
when :destroying_host
|
|
70
|
+
fmt.puts 2, "- Destroy host '#{payload[:name]}'..."
|
|
57
71
|
end
|
|
58
72
|
end
|
|
73
|
+
|
|
74
|
+
def render_host_rm_warning(payload)
|
|
75
|
+
fmt.warn "Host '#{payload[:name]}' does not exist, skipping.\n"
|
|
76
|
+
end
|
|
59
77
|
end
|
|
60
78
|
end
|
|
61
79
|
end
|
|
@@ -17,65 +17,46 @@ module Moose
|
|
|
17
17
|
#==========================
|
|
18
18
|
desc 'rmgroup HOSTNAME GROUPNAME [GROUPNAME ...]',
|
|
19
19
|
'dissociation the host from a group'
|
|
20
|
+
option :dry_run, type: :boolean
|
|
21
|
+
option :yes, type: :boolean, desc: 'Confirm destructive dissociation without prompting'
|
|
22
|
+
option :plan_format, type: :string, desc: 'Emit dry-run plan events as yaml|json|pjson'
|
|
20
23
|
def rmgroup(*args)
|
|
21
24
|
abort_if_missing_args(args, 2, '2 or more')
|
|
25
|
+
validate_machine_plan_request!
|
|
22
26
|
|
|
23
27
|
name = args[0].downcase
|
|
24
28
|
groups = normalize_names(args.slice(1, args.length - 1))
|
|
25
29
|
|
|
26
30
|
abort_if_automatic_group(groups)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
host
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
fmt.puts 2, '- All OK'
|
|
31
|
+
confirm_destructive_action!("host rmgroup #{name} #{groups.join(',')}")
|
|
32
|
+
|
|
33
|
+
result = remove_groups_from_host(name, groups)
|
|
34
|
+
unless machine_plan_output_rendered?(
|
|
35
|
+
result, command: 'host rmgroup'
|
|
36
|
+
)
|
|
37
|
+
record_audit({ command: 'host rmgroup', action: 'dissociate', entity_type: 'host',
|
|
38
|
+
entity_names: name }, result: result, dry_run: options[:dry_run])
|
|
39
|
+
print_warning_summary(result, success_message: 'Succeeded',
|
|
40
|
+
warning_message: 'Succeeded')
|
|
38
41
|
end
|
|
39
|
-
puts 'Succeeded'
|
|
40
42
|
end
|
|
41
43
|
|
|
42
44
|
private
|
|
43
45
|
|
|
44
|
-
def
|
|
45
|
-
|
|
46
|
-
host
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def render_host_rmgroup_events(events)
|
|
54
|
-
events.each { |event| render_host_rmgroup_event(event) }
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def render_host_rmgroup_event(event)
|
|
58
|
-
payload = event.payload
|
|
59
|
-
|
|
60
|
-
return render_host_rmgroup_warning(payload) if event.type == :host_group_association_missing
|
|
61
|
-
return render_host_rmgroup_missing(payload) if event.type == :missing_skipping
|
|
62
|
-
|
|
63
|
-
case event.type
|
|
64
|
-
when :removing_host_group_association
|
|
65
|
-
fmt.puts 2, "- Remove association {host:#{payload[:host]} <-> group:#{payload[:group]}}..."
|
|
66
|
-
when :adding_automatic_group
|
|
67
|
-
fmt.puts 2, "- Add automatic association {host:#{payload[:host]} <-> group:ungrouped}..."
|
|
68
|
-
when :ok
|
|
69
|
-
fmt.puts payload[:indent], '- OK'
|
|
46
|
+
def remove_groups_from_host(name, groups)
|
|
47
|
+
operation = build_operation(Moose::Inventory::Operations::RemoveAssociations)
|
|
48
|
+
run_host_relation_transaction(heading: "Dissociate host '#{name}' from groups '#{groups.join(',')}':") do
|
|
49
|
+
host = fetch_existing_host_or_raise(name)
|
|
50
|
+
result = operation.host_from_groups(host: host, host_name: name, group_names: groups,
|
|
51
|
+
dry_run: options[:dry_run])
|
|
52
|
+
render_host_rmgroup_events(result.events) unless machine_plan_output_requested?
|
|
53
|
+
result
|
|
70
54
|
end
|
|
71
55
|
end
|
|
72
56
|
|
|
73
|
-
def
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def render_host_rmgroup_missing(payload)
|
|
78
|
-
fmt.puts payload[:indent], "- Doesn't exist, skipping."
|
|
57
|
+
def render_host_rmgroup_events(events)
|
|
58
|
+
emitter = host_group_association_removal_emitter(perspective: :host)
|
|
59
|
+
events.each { |event| emitter.call(event) }
|
|
79
60
|
end
|
|
80
61
|
end
|
|
81
62
|
end
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'thor'
|
|
2
4
|
require 'json'
|
|
3
5
|
|
|
4
|
-
require_relative '
|
|
5
|
-
require_relative '../
|
|
6
|
+
require_relative '../inventory_context'
|
|
7
|
+
require_relative '../operations/remove_variables'
|
|
6
8
|
|
|
7
9
|
module Moose
|
|
8
10
|
module Inventory
|
|
@@ -12,56 +14,40 @@ module Moose
|
|
|
12
14
|
class Host
|
|
13
15
|
#==========================
|
|
14
16
|
desc 'rmvar', 'Remove a variable from the host'
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
# Convenience
|
|
24
|
-
db = Moose::Inventory::DB
|
|
25
|
-
fmt = Moose::Inventory::Cli::Formatter
|
|
17
|
+
option :dry_run, type: :boolean
|
|
18
|
+
option :yes, type: :boolean, desc: 'Confirm destructive removal without prompting'
|
|
19
|
+
option :plan_format, type: :string, desc: 'Emit dry-run plan events as yaml|json|pjson'
|
|
20
|
+
def rmvar(*args)
|
|
21
|
+
abort_if_missing_args(args, 2, '2 or more')
|
|
22
|
+
validate_machine_plan_request!
|
|
26
23
|
|
|
27
|
-
# Arguments
|
|
28
24
|
name = args[0].downcase
|
|
29
25
|
vars = args.slice(1, args.length - 1).uniq
|
|
26
|
+
confirm_destructive_action!("host rmvar #{name} #{vars.join(',')}")
|
|
27
|
+
operation = build_operation(Moose::Inventory::Operations::RemoveVariables,
|
|
28
|
+
entity_type: :host,
|
|
29
|
+
emitter: machine_plan_emitter(host_rmvar_emitter(name, vars)))
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
result = db.transaction do
|
|
32
|
+
operation.call(name: name, vars: vars, dry_run: options[:dry_run])
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
return if machine_plan_output_rendered?(result, command: 'host rmvar')
|
|
34
36
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"The host '#{name}' does not exist."
|
|
40
|
-
end
|
|
41
|
-
fmt.puts 4, '- OK'
|
|
37
|
+
record_audit({ command: 'host rmvar', action: 'remove_variable', entity_type: 'host',
|
|
38
|
+
entity_names: name }, result: result, dry_run: options[:dry_run])
|
|
39
|
+
print_success_summary
|
|
40
|
+
end
|
|
42
41
|
|
|
43
|
-
|
|
44
|
-
vars.each do |v|
|
|
45
|
-
fmt.puts 2, "- remove variable '#{v}'..."
|
|
46
|
-
vararray = v.split('=')
|
|
47
|
-
if v.start_with?('=') || v.scan('=').count > 1
|
|
48
|
-
fail db.exceptions[:moose],
|
|
49
|
-
"Incorrect format in {#{v}}. " \
|
|
50
|
-
'Expected \'key\' or \'key=value\'.'
|
|
51
|
-
end
|
|
42
|
+
private
|
|
52
43
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
fmt.puts 4, '- OK'
|
|
61
|
-
end
|
|
62
|
-
fmt.puts 2, '- all OK'
|
|
63
|
-
end # Transaction end
|
|
64
|
-
puts 'Succeeded.'
|
|
44
|
+
def host_rmvar_emitter(name, vars)
|
|
45
|
+
variable_operation_emitter(
|
|
46
|
+
action: :remove,
|
|
47
|
+
entity_label: 'host',
|
|
48
|
+
entity_name: name,
|
|
49
|
+
variables_label: vars.join(',')
|
|
50
|
+
)
|
|
65
51
|
end
|
|
66
52
|
end
|
|
67
53
|
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moose
|
|
4
|
+
module Inventory
|
|
5
|
+
module Cli
|
|
6
|
+
class Host
|
|
7
|
+
desc 'addtag HOST TAG_1 [TAG_2 ...]', 'Add metadata tags to a host'
|
|
8
|
+
def addtag(*args)
|
|
9
|
+
abort_if_missing_args(args, 2, '2 or more')
|
|
10
|
+
|
|
11
|
+
add_tags('host', args[0].downcase, args.slice(1, args.length - 1))
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
desc 'rmtag HOST TAG_1 [TAG_2 ...]', 'Remove metadata tags from a host'
|
|
15
|
+
option :yes, type: :boolean, desc: 'Confirm destructive tag removal without prompting'
|
|
16
|
+
def rmtag(*args)
|
|
17
|
+
abort_if_missing_args(args, 2, '2 or more')
|
|
18
|
+
confirm_destructive_action!("host rmtag #{args[0].downcase} #{args.slice(1, args.length - 1).join(',')}")
|
|
19
|
+
|
|
20
|
+
remove_tags('host', args[0].downcase, args.slice(1, args.length - 1))
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
desc 'listtags HOST', 'List metadata tags for a host'
|
|
24
|
+
option :format, type: :string, desc: 'Emit tags as yaml|json|pjson'
|
|
25
|
+
def listtags(*args)
|
|
26
|
+
abort_if_missing_args(args, 1, '1')
|
|
27
|
+
|
|
28
|
+
list_tags('host', args[0].downcase)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moose
|
|
4
|
+
module Inventory
|
|
5
|
+
module Cli
|
|
6
|
+
# Shared argument and warning helpers for host/group listvars commands.
|
|
7
|
+
module ListvarsSupport
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def validate_listvars_args(args)
|
|
11
|
+
if ansible_mode?
|
|
12
|
+
abort_if_wrong_ansible_listvars_arg_count(args, 1)
|
|
13
|
+
else
|
|
14
|
+
abort_if_missing_args(args, 1, '1 or more')
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def warn_if_missing_ansible_listvars_entity(entity_type, name)
|
|
19
|
+
return unless ansible_mode?
|
|
20
|
+
return unless missing_listvars_entity?(entity_type, name)
|
|
21
|
+
|
|
22
|
+
fmt.warn missing_ansible_listvars_warning(entity_type, name)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def abort_if_wrong_ansible_listvars_arg_count(args, expected)
|
|
26
|
+
return if args.length == expected
|
|
27
|
+
|
|
28
|
+
abort("ERROR: Wrong number of arguments for Ansible mode, #{args.length} for #{expected}.")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def missing_listvars_entity?(entity_type, name)
|
|
32
|
+
case entity_type
|
|
33
|
+
when :host
|
|
34
|
+
inventory_context.find_host(name).nil?
|
|
35
|
+
when :group
|
|
36
|
+
inventory_context.find_group(name).nil?
|
|
37
|
+
else
|
|
38
|
+
raise ArgumentError, "Unsupported listvars entity type: #{entity_type.inspect}"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def missing_ansible_listvars_warning(entity_type, name)
|
|
43
|
+
case entity_type
|
|
44
|
+
when :host
|
|
45
|
+
"The host #{name} does not exist.\n"
|
|
46
|
+
when :group
|
|
47
|
+
"The Group #{name} does not exist."
|
|
48
|
+
else
|
|
49
|
+
raise ArgumentError, "Unsupported listvars entity type: #{entity_type.inspect}"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moose
|
|
4
|
+
module Inventory
|
|
5
|
+
module Cli
|
|
6
|
+
# Shared machine-readable dry-run plan rendering helpers.
|
|
7
|
+
module PlanRendering
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def machine_plan_output_requested?
|
|
11
|
+
respond_to?(:options) && !options[:plan_format].nil?
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def validate_machine_plan_request!
|
|
15
|
+
abort('ERROR: --plan-format requires --dry-run.') if machine_plan_output_requested? && !options[:dry_run]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def machine_plan_emitter(emitter)
|
|
19
|
+
machine_plan_output_requested? ? nil : emitter
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def machine_plan_output_rendered?(result, command:)
|
|
23
|
+
return false unless machine_plan_output_requested?
|
|
24
|
+
|
|
25
|
+
fmt.dump(
|
|
26
|
+
{
|
|
27
|
+
command: command,
|
|
28
|
+
dry_run: true,
|
|
29
|
+
changes_applied: false,
|
|
30
|
+
events: result.events.map { |event| serialize_plan_event(event) }
|
|
31
|
+
},
|
|
32
|
+
options[:plan_format].downcase
|
|
33
|
+
)
|
|
34
|
+
true
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def serialize_plan_event(event)
|
|
38
|
+
{
|
|
39
|
+
type: event.type.to_s,
|
|
40
|
+
payload: stringify_plan_payload(event.payload)
|
|
41
|
+
}
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def stringify_plan_payload(payload)
|
|
45
|
+
payload.to_h.transform_keys(&:to_s)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moose
|
|
4
|
+
module Inventory
|
|
5
|
+
module Cli
|
|
6
|
+
# Shared transaction/fetch helpers for host/group relation commands.
|
|
7
|
+
module RelationTransactionSupport
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def fetch_existing_group_or_abort(name)
|
|
11
|
+
fmt.puts 2, "- retrieve group '#{name}'..." unless machine_plan_output_requested?
|
|
12
|
+
group = inventory_context.find_group(name)
|
|
13
|
+
abort("ERROR: The group '#{name}' does not exist.") if group.nil?
|
|
14
|
+
|
|
15
|
+
fmt.puts 4, '- OK' unless machine_plan_output_requested?
|
|
16
|
+
group
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def fetch_existing_host_or_raise(name)
|
|
20
|
+
fmt.puts 2, "- Retrieve host '#{name}'..." unless machine_plan_output_requested?
|
|
21
|
+
host = inventory_context.find_host(name)
|
|
22
|
+
raise db.exceptions[:moose], "The host '#{name}' was not found in the database." if host.nil?
|
|
23
|
+
|
|
24
|
+
fmt.puts 4, '- OK' unless machine_plan_output_requested?
|
|
25
|
+
host
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def run_group_relation_transaction(heading:, on_error: nil, &)
|
|
29
|
+
run_relation_transaction(heading: heading, all_ok_message: '- all OK', on_error: on_error, &)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def run_host_relation_transaction(heading:, on_error: nil, &)
|
|
33
|
+
run_relation_transaction(heading: heading, all_ok_message: '- All OK', on_error: on_error, &)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def run_relation_transaction(heading:, all_ok_message:, on_error: nil)
|
|
37
|
+
result = nil
|
|
38
|
+
db.transaction do
|
|
39
|
+
puts heading unless machine_plan_output_requested?
|
|
40
|
+
result = yield
|
|
41
|
+
fmt.puts 2, all_ok_message unless machine_plan_output_requested?
|
|
42
|
+
end
|
|
43
|
+
result
|
|
44
|
+
rescue db.exceptions[:moose] => e
|
|
45
|
+
message = on_error ? on_error.call(e) : e.message
|
|
46
|
+
abort("ERROR: #{message}")
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|