moose-inventory 2.0 → 2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/release.yml +2 -0
- data/.gitignore +2 -1
- data/.rubocop.yml +21 -0
- data/BACKLOG.md +630 -8
- data/Gemfile +2 -0
- data/Gemfile.lock +1 -1
- data/README.md +315 -39
- data/Rakefile +2 -0
- data/bin/moose-inventory +2 -1
- data/docs/architecture/architecture-and-trust-boundaries.md +444 -0
- data/docs/compatibility/cli-output-compatibility.md +76 -0
- data/docs/governance/approval-register.md +37 -0
- data/docs/maintenance/database-backup-restore-guidance.md +162 -0
- data/docs/maintenance/package-maintenance-and-agent-boundaries.md +260 -0
- data/docs/process/conformance-gap-analysis-2026-05-28.md +192 -0
- data/docs/product/product-brief.md +161 -0
- data/docs/product/requirements-baseline.md +477 -0
- data/docs/qa/qa-documentation-and-release-gates.md +283 -0
- data/docs/release/package-provenance-hardening.md +126 -0
- data/docs/release/publishing.md +11 -3
- data/docs/release/release-environment-protection.md +70 -0
- data/docs/release/release-readiness.md +23 -4
- data/docs/security/accepted-risk-register.md +84 -0
- data/docs/security/security-privacy-process.md +287 -0
- data/docs/security-audit-2026-05-26-rerun.md +2 -2
- data/docs/ux/cli-workflow-notes.md +287 -0
- data/examples/ansible/ansible.cfg +3 -0
- data/examples/ansible/inventory/moose_inventory.yml +5 -0
- data/examples/ansible/inventory_plugins/moose_inventory.py +100 -0
- data/examples/ci/README.md +16 -0
- data/examples/ci/github-actions/inventory-review.yml +38 -0
- data/examples/ci/inventory/example-snapshot.yml +19 -0
- data/examples/ci/scripts/validate-inventory-snapshot.sh +30 -0
- data/lib/moose_inventory/cli/application.rb +133 -5
- data/lib/moose_inventory/cli/association_rendering.rb +74 -0
- data/lib/moose_inventory/cli/association_rendering_support.rb +89 -0
- data/lib/moose_inventory/cli/audit.rb +62 -0
- data/lib/moose_inventory/cli/audit_recording.rb +40 -0
- data/lib/moose_inventory/cli/child_relation_rendering.rb +110 -0
- data/lib/moose_inventory/cli/console.rb +135 -0
- data/lib/moose_inventory/cli/db.rb +64 -0
- data/lib/moose_inventory/cli/factory.rb +28 -0
- data/lib/moose_inventory/cli/formatter.rb +8 -12
- data/lib/moose_inventory/cli/group.rb +5 -2
- data/lib/moose_inventory/cli/group_add.rb +11 -9
- data/lib/moose_inventory/cli/group_addchild.rb +23 -65
- data/lib/moose_inventory/cli/group_addhost.rb +16 -67
- data/lib/moose_inventory/cli/group_addvar.rb +27 -47
- data/lib/moose_inventory/cli/group_get.rb +8 -42
- data/lib/moose_inventory/cli/group_list.rb +7 -40
- data/lib/moose_inventory/cli/group_listvars.rb +9 -55
- data/lib/moose_inventory/cli/group_rm.rb +12 -10
- data/lib/moose_inventory/cli/group_rmchild.rb +26 -82
- data/lib/moose_inventory/cli/group_rmhost.rb +18 -53
- data/lib/moose_inventory/cli/group_rmvar.rb +30 -41
- data/lib/moose_inventory/cli/group_tags.rb +33 -0
- data/lib/moose_inventory/cli/helpers.rb +68 -1
- data/lib/moose_inventory/cli/host.rb +6 -3
- data/lib/moose_inventory/cli/host_add.rb +69 -29
- data/lib/moose_inventory/cli/host_addgroup.rb +22 -58
- data/lib/moose_inventory/cli/host_addvar.rb +28 -52
- data/lib/moose_inventory/cli/host_get.rb +9 -37
- data/lib/moose_inventory/cli/host_list.rb +24 -21
- data/lib/moose_inventory/cli/host_listvars.rb +9 -62
- data/lib/moose_inventory/cli/host_rm.rb +60 -42
- data/lib/moose_inventory/cli/host_rmgroup.rb +25 -44
- data/lib/moose_inventory/cli/host_rmvar.rb +31 -45
- data/lib/moose_inventory/cli/host_tags.rb +33 -0
- data/lib/moose_inventory/cli/listvars_support.rb +55 -0
- data/lib/moose_inventory/cli/plan_rendering.rb +50 -0
- data/lib/moose_inventory/cli/relation_transaction_support.rb +51 -0
- data/lib/moose_inventory/cli/tag_support.rb +97 -0
- data/lib/moose_inventory/cli/variable_rendering.rb +67 -0
- data/lib/moose_inventory/config/config.rb +185 -108
- data/lib/moose_inventory/db/db.rb +170 -195
- data/lib/moose_inventory/db/exceptions.rb +6 -3
- data/lib/moose_inventory/db/models.rb +16 -0
- data/lib/moose_inventory/db/schema_migrations.rb +248 -0
- data/lib/moose_inventory/inventory_context.rb +68 -2
- data/lib/moose_inventory/operations/add_associations.rb +20 -16
- data/lib/moose_inventory/operations/add_groups.rb +21 -13
- data/lib/moose_inventory/operations/add_hosts.rb +30 -17
- data/lib/moose_inventory/operations/add_variables.rb +77 -0
- data/lib/moose_inventory/operations/entity_variable_operation_support.rb +46 -0
- data/lib/moose_inventory/operations/group_child_relations.rb +23 -16
- data/lib/moose_inventory/operations/group_cleanup.rb +23 -8
- data/lib/moose_inventory/operations/import_inventory_snapshot.rb +41 -0
- data/lib/moose_inventory/operations/inventory_doctor.rb +172 -0
- data/lib/moose_inventory/operations/inventory_snapshot.rb +60 -0
- data/lib/moose_inventory/operations/inventory_snapshot_applier.rb +112 -0
- data/lib/moose_inventory/operations/inventory_snapshot_preview.rb +174 -0
- data/lib/moose_inventory/operations/inventory_snapshot_validator.rb +134 -0
- data/lib/moose_inventory/operations/operation_event_support.rb +27 -0
- data/lib/moose_inventory/operations/query_inventory/base_query.rb +24 -0
- data/lib/moose_inventory/operations/query_inventory/group_queries.rb +86 -0
- data/lib/moose_inventory/operations/query_inventory/host_queries.rb +106 -0
- data/lib/moose_inventory/operations/query_inventory.rb +47 -0
- data/lib/moose_inventory/operations/remove_associations.rb +30 -18
- data/lib/moose_inventory/operations/remove_groups.rb +12 -12
- data/lib/moose_inventory/operations/remove_hosts.rb +68 -0
- data/lib/moose_inventory/operations/remove_variables.rb +67 -0
- data/lib/moose_inventory/runtime_options.rb +31 -0
- data/lib/moose_inventory/version.rb +3 -1
- data/lib/moose_inventory.rb +10 -7
- data/moose-inventory.gemspec +19 -35
- data/scripts/check.sh +1 -0
- data/scripts/ci/check_generated_artifacts.sh +41 -0
- data/scripts/ci/check_permissions.sh +2 -0
- data/scripts/ci/check_rubocop.sh +30 -25
- data/scripts/files.rb +5 -4
- data/spec/examples/ci_examples_spec.rb +37 -0
- data/spec/lib/moose_inventory/ansible_plugin_examples_spec.rb +29 -0
- data/spec/lib/moose_inventory/cli/application_doctor_spec.rb +50 -0
- data/spec/lib/moose_inventory/cli/application_import_export_spec.rb +100 -0
- data/spec/lib/moose_inventory/cli/application_spec.rb +25 -15
- data/spec/lib/moose_inventory/cli/audit_spec.rb +56 -0
- data/spec/lib/moose_inventory/cli/cli_spec.rb +15 -19
- data/spec/lib/moose_inventory/cli/console_spec.rb +98 -0
- data/spec/lib/moose_inventory/cli/factory_spec.rb +27 -0
- data/spec/lib/moose_inventory/cli/formatter_spec.rb +95 -3
- data/spec/lib/moose_inventory/cli/group_add_spec.rb +140 -116
- data/spec/lib/moose_inventory/cli/group_addchild_spec.rb +89 -35
- data/spec/lib/moose_inventory/cli/group_addhost_spec.rb +81 -84
- data/spec/lib/moose_inventory/cli/group_addvar_spec.rb +65 -68
- data/spec/lib/moose_inventory/cli/group_get_spec.rb +17 -33
- data/spec/lib/moose_inventory/cli/group_list_spec.rb +16 -38
- data/spec/lib/moose_inventory/cli/group_listvar_spec.rb +33 -40
- data/spec/lib/moose_inventory/cli/group_rm_spec.rb +136 -96
- data/spec/lib/moose_inventory/cli/group_rmchild_spec.rb +66 -41
- data/spec/lib/moose_inventory/cli/group_rmhost_spec.rb +76 -78
- data/spec/lib/moose_inventory/cli/group_rmvar_spec.rb +57 -63
- data/spec/lib/moose_inventory/cli/group_spec.rb +2 -0
- data/spec/lib/moose_inventory/cli/helpers_spec.rb +146 -0
- data/spec/lib/moose_inventory/cli/host_add_spec.rb +170 -116
- data/spec/lib/moose_inventory/cli/host_addgroup_spec.rb +100 -83
- data/spec/lib/moose_inventory/cli/host_addvar_spec.rb +92 -74
- data/spec/lib/moose_inventory/cli/host_get_spec.rb +14 -33
- data/spec/lib/moose_inventory/cli/host_list_spec.rb +41 -33
- data/spec/lib/moose_inventory/cli/host_listvar_spec.rb +45 -53
- data/spec/lib/moose_inventory/cli/host_rm_spec.rb +66 -48
- data/spec/lib/moose_inventory/cli/host_rmgroup_spec.rb +73 -83
- data/spec/lib/moose_inventory/cli/host_rmvar_spec.rb +56 -63
- data/spec/lib/moose_inventory/cli/host_spec.rb +2 -0
- data/spec/lib/moose_inventory/cli/tags_spec.rb +81 -0
- data/spec/lib/moose_inventory/config/config_spec.rb +41 -3
- data/spec/lib/moose_inventory/db/db_spec.rb +396 -36
- data/spec/lib/moose_inventory/db/exceptions_spec.rb +18 -0
- data/spec/lib/moose_inventory/db/models_spec.rb +7 -3
- data/spec/lib/moose_inventory/db_lifecycle_spec.rb +73 -0
- data/spec/lib/moose_inventory/inventory_context_spec.rb +10 -0
- data/spec/lib/moose_inventory/operations/add_associations_spec.rb +34 -0
- data/spec/lib/moose_inventory/operations/add_groups_spec.rb +15 -0
- data/spec/lib/moose_inventory/operations/add_hosts_spec.rb +13 -0
- data/spec/lib/moose_inventory/operations/add_variables_spec.rb +103 -0
- data/spec/lib/moose_inventory/operations/group_child_relations_spec.rb +46 -0
- data/spec/lib/moose_inventory/operations/import_inventory_snapshot_spec.rb +226 -0
- data/spec/lib/moose_inventory/operations/inventory_doctor_spec.rb +77 -0
- data/spec/lib/moose_inventory/operations/inventory_snapshot_spec.rb +50 -0
- data/spec/lib/moose_inventory/operations/operation_event_support_spec.rb +78 -0
- data/spec/lib/moose_inventory/operations/query_inventory_spec.rb +146 -0
- data/spec/lib/moose_inventory/operations/remove_associations_spec.rb +35 -0
- data/spec/lib/moose_inventory/operations/remove_groups_spec.rb +21 -0
- data/spec/lib/moose_inventory/operations/remove_hosts_spec.rb +55 -0
- data/spec/lib/moose_inventory/operations/remove_variables_spec.rb +83 -0
- data/spec/shared/shared_config_setup.rb +4 -3
- data/spec/spec_helper.rb +50 -40
- data/spec/support/cli_harness.rb +33 -0
- metadata +80 -41
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moose
|
|
4
|
+
module Inventory
|
|
5
|
+
module Cli
|
|
6
|
+
# Shared host/group metadata tag commands.
|
|
7
|
+
module TagSupport
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def add_tags(entity_type, entity_name, tag_names)
|
|
11
|
+
entity = fetch_tag_entity(entity_type, entity_name)
|
|
12
|
+
normalized = normalize_tags(tag_names)
|
|
13
|
+
changed = []
|
|
14
|
+
|
|
15
|
+
db.transaction do
|
|
16
|
+
normalized.each do |tag_name|
|
|
17
|
+
tag = inventory_context.find_or_create_tag(tag_name)
|
|
18
|
+
next unless entity.tags_dataset[name: tag_name].nil?
|
|
19
|
+
|
|
20
|
+
entity.add_tag(tag)
|
|
21
|
+
changed << tag_name
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
result = tag_result(events: changed.map { |tag| tag_event(:tag_added, entity_type, entity_name, tag) })
|
|
26
|
+
record_tag_audit(command: 'addtag', action: 'add_tag', entity_type: entity_type,
|
|
27
|
+
entity_name: entity_name, result: result, changed: changed)
|
|
28
|
+
puts "Added #{entity_type} tag(s) to '#{entity_name}': #{changed.join(', ')}."
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def remove_tags(entity_type, entity_name, tag_names)
|
|
32
|
+
entity = fetch_tag_entity(entity_type, entity_name)
|
|
33
|
+
normalized = normalize_tags(tag_names)
|
|
34
|
+
changed = []
|
|
35
|
+
|
|
36
|
+
db.transaction do
|
|
37
|
+
normalized.each do |tag_name|
|
|
38
|
+
tag = entity.tags_dataset[name: tag_name]
|
|
39
|
+
next if tag.nil?
|
|
40
|
+
|
|
41
|
+
entity.remove_tag(tag)
|
|
42
|
+
changed << tag_name
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
result = tag_result(events: changed.map { |tag| tag_event(:tag_removed, entity_type, entity_name, tag) })
|
|
47
|
+
record_tag_audit(command: 'rmtag', action: 'remove_tag', entity_type: entity_type,
|
|
48
|
+
entity_name: entity_name, result: result, changed: changed)
|
|
49
|
+
puts "Removed #{entity_type} tag(s) from '#{entity_name}': #{changed.join(', ')}."
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def list_tags(entity_type, entity_name)
|
|
53
|
+
entity = fetch_tag_entity(entity_type, entity_name)
|
|
54
|
+
tags = entity.tags_dataset.order(:name).map(:name)
|
|
55
|
+
|
|
56
|
+
if options[:format]
|
|
57
|
+
fmt.dump({ entity_type => entity_name, tags: tags }, options[:format].downcase)
|
|
58
|
+
elsif tags.empty?
|
|
59
|
+
puts "#{entity_type.capitalize} '#{entity_name}' has no tags."
|
|
60
|
+
else
|
|
61
|
+
puts "#{entity_type.capitalize} '#{entity_name}' tags: #{tags.join(', ')}"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def fetch_tag_entity(entity_type, entity_name)
|
|
66
|
+
entity = inventory_context.public_send("find_#{entity_type}", entity_name)
|
|
67
|
+
abort("ERROR: The #{entity_type} '#{entity_name}' does not exist.") if entity.nil?
|
|
68
|
+
|
|
69
|
+
entity
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def normalize_tags(values)
|
|
73
|
+
inventory_context.normalize_tag_names(values)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def tag_result(events:)
|
|
77
|
+
Moose::Inventory::Operations::OperationEventSupport::Result.new(events: events, warning_count: 0)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def tag_event(type, entity_type, entity_name, tag_name)
|
|
81
|
+
Moose::Inventory::Operations::OperationEventSupport::Event.new(
|
|
82
|
+
type: type,
|
|
83
|
+
payload: { entity_type: entity_type, entity_name: entity_name, tag: tag_name }
|
|
84
|
+
)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def record_tag_audit(metadata)
|
|
88
|
+
return if metadata.fetch(:changed).empty?
|
|
89
|
+
|
|
90
|
+
record_audit({ command: "#{metadata.fetch(:entity_type)} #{metadata.fetch(:command)}",
|
|
91
|
+
action: metadata.fetch(:action), entity_type: metadata.fetch(:entity_type),
|
|
92
|
+
entity_names: metadata.fetch(:entity_name) }, result: metadata.fetch(:result))
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moose
|
|
4
|
+
module Inventory
|
|
5
|
+
module Cli
|
|
6
|
+
# Shared rendering helpers for variable add/remove commands.
|
|
7
|
+
module VariableRendering
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def variable_operation_emitter(action:, entity_label:, entity_name:, variables_label:)
|
|
11
|
+
lambda do |event|
|
|
12
|
+
render_variable_event(
|
|
13
|
+
event,
|
|
14
|
+
action: action,
|
|
15
|
+
entity_label: entity_label,
|
|
16
|
+
entity_name: entity_name,
|
|
17
|
+
variables_label: variables_label
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def render_variable_event(event, action:, entity_label:, entity_name:, variables_label:)
|
|
23
|
+
if event.type == :entity_started
|
|
24
|
+
return puts(variable_operation_heading(action:, entity_label:, entity_name:,
|
|
25
|
+
variables_label:))
|
|
26
|
+
end
|
|
27
|
+
return render_variable_change(event, entity_label) if variable_change_event?(event.type)
|
|
28
|
+
return puts 'Dry run complete. No changes applied.' if event.type == :dry_run_summary
|
|
29
|
+
|
|
30
|
+
render_variable_status(event)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def variable_operation_heading(action:, entity_label:, entity_name:, variables_label:)
|
|
34
|
+
return "Add variables '#{variables_label}' to #{entity_label} '#{entity_name}':" if action == :add
|
|
35
|
+
|
|
36
|
+
"Remove variable(s) '#{variables_label}' from #{entity_label} '#{entity_name}':"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def variable_change_event?(type)
|
|
40
|
+
%i[retrieving_entity adding_variable removing_variable updating_existing_variable].include?(type)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def render_variable_change(event, entity_label)
|
|
44
|
+
case event.type
|
|
45
|
+
when :retrieving_entity
|
|
46
|
+
fmt.puts 2, "- retrieve #{entity_label} '#{event.payload[:name]}'..."
|
|
47
|
+
when :adding_variable
|
|
48
|
+
fmt.puts 2, "- add variable '#{event.payload[:variable]}'..."
|
|
49
|
+
when :removing_variable
|
|
50
|
+
fmt.puts 2, "- remove variable '#{event.payload[:variable]}'..."
|
|
51
|
+
when :updating_existing_variable
|
|
52
|
+
fmt.puts 4, '- already exists, applying as an update...'
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def render_variable_status(event)
|
|
57
|
+
case event.type
|
|
58
|
+
when :entity_complete
|
|
59
|
+
fmt.puts 2, '- all OK'
|
|
60
|
+
when :ok
|
|
61
|
+
fmt.puts event.payload[:indent], '- OK'
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# Configuration
|
|
2
4
|
|
|
3
5
|
require 'yaml'
|
|
4
6
|
|
|
7
|
+
require_relative '../runtime_options'
|
|
8
|
+
|
|
5
9
|
module Moose
|
|
6
10
|
module Inventory
|
|
7
11
|
##
|
|
@@ -11,16 +15,16 @@ module Moose
|
|
|
11
15
|
extend self
|
|
12
16
|
# rubocop:enable Style/ModuleFunction
|
|
13
17
|
|
|
14
|
-
@_argv
|
|
18
|
+
@_argv = []
|
|
15
19
|
@_confopts = {}
|
|
16
20
|
@_settings = {}
|
|
21
|
+
@runtime_options = nil
|
|
17
22
|
|
|
18
|
-
attr_reader :_argv
|
|
19
|
-
attr_reader :_confopts
|
|
20
|
-
attr_reader :_settings
|
|
23
|
+
attr_reader :_argv, :_confopts, :_settings
|
|
21
24
|
|
|
22
25
|
#----------------------
|
|
23
26
|
def self.init(args)
|
|
27
|
+
reset_runtime_state
|
|
24
28
|
@_argv = args.dup
|
|
25
29
|
|
|
26
30
|
top_level_help
|
|
@@ -28,6 +32,46 @@ module Moose
|
|
|
28
32
|
ansible_args
|
|
29
33
|
resolve_config_file
|
|
30
34
|
load
|
|
35
|
+
refresh_runtime_options
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.reset_runtime_state
|
|
39
|
+
@_argv = []
|
|
40
|
+
@_confopts = default_confopts
|
|
41
|
+
@_settings = {}
|
|
42
|
+
@runtime_options = nil
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.default_confopts
|
|
46
|
+
{ env: '', format: 'json', ansible: false, trace: false }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.runtime_options
|
|
50
|
+
@runtime_options ||= build_runtime_options
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def self.application_args
|
|
54
|
+
runtime_options.argv
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.output_format
|
|
58
|
+
runtime_options.output_format
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def self.ansible?
|
|
62
|
+
runtime_options.ansible? || @_confopts[:ansible] == true
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def self.trace_enabled?
|
|
66
|
+
runtime_options.trace? || @_confopts[:trace] == true
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def self.db_settings
|
|
70
|
+
@_settings[:config][:db]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def self._runtime_options
|
|
74
|
+
@runtime_options
|
|
31
75
|
end
|
|
32
76
|
|
|
33
77
|
#----------------------
|
|
@@ -51,50 +95,26 @@ module Moose
|
|
|
51
95
|
# -- trace => Enable more complete exceptions for db transactions
|
|
52
96
|
# Default is not to trace.
|
|
53
97
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
%w(config env format).each do |var|
|
|
58
|
-
@_argv.each_with_index do |val, index|
|
|
59
|
-
next if val != "--#{var}"
|
|
60
|
-
@_confopts[var.to_sym] = @_argv[index + 1]
|
|
61
|
-
1.downto(0) { |offset| @_argv.delete_at(index + offset) }
|
|
62
|
-
break
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
# Check for one-part flags
|
|
67
|
-
%w(ansible trace).each do |var|
|
|
68
|
-
@_argv.each_with_index do |val, index|
|
|
69
|
-
next if val != "--#{var}"
|
|
70
|
-
@_confopts[var.to_sym] = true
|
|
71
|
-
@_argv.delete_at(index)
|
|
72
|
-
break
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
# Sanity
|
|
77
|
-
# - Ansible output format must be json - pjson is permitted, but yaml is not.
|
|
78
|
-
if @_confopts[:ansible] == true
|
|
79
|
-
unless @_confopts[:format] =~ /p|pjson|j|json/
|
|
80
|
-
@_confopts[:format] = 'json'
|
|
81
|
-
end
|
|
82
|
-
end
|
|
98
|
+
extract_value_flags(%w[config env format])
|
|
99
|
+
extract_boolean_flags(%w[ansible trace])
|
|
100
|
+
normalize_ansible_format!
|
|
83
101
|
end
|
|
84
102
|
|
|
85
103
|
#----------------------
|
|
86
104
|
def self.top_level_help
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
105
|
+
return unless @_argv[0] == 'help'
|
|
106
|
+
|
|
107
|
+
puts 'Global flags:'
|
|
108
|
+
printf ' %-31s %-10s', '--ansible', "# Force Ansible mode (automatically set when using ansible flags)\n"
|
|
109
|
+
printf ' %-31s %-10s', '--config FILE', "# Specifies a configuration file to use\n"
|
|
110
|
+
printf ' %-31s %-10s', '--env ENV', "# Specifies the environment section of the config to use\n"
|
|
111
|
+
printf ' %-31s %-10s', '--format yaml|json|pjson',
|
|
112
|
+
"# Format for the output of 'get', 'list', and 'listvars' subcommands\n"
|
|
113
|
+
printf ' %-31s %-10s', '--trace', "# Enable more complete exception dumps for database transactions\n"
|
|
114
|
+
puts "\nAnsible flags:"
|
|
115
|
+
printf ' %-31s %-10s', '--host HOSTNAME',
|
|
116
|
+
"# Retrieves host variables for the specified host (alias for 'host listvars HOSTNAME')\n"
|
|
117
|
+
printf ' %-31s %-10s', '--list', "# Retrieves the list of groups (alias for 'group list')\n\n"
|
|
98
118
|
end
|
|
99
119
|
|
|
100
120
|
#----------------------
|
|
@@ -108,95 +128,152 @@ module Moose
|
|
|
108
128
|
|
|
109
129
|
case @_argv[0]
|
|
110
130
|
when '--list'
|
|
111
|
-
|
|
112
|
-
@_confopts[:format] = 'json' unless @_confopts[:format] =~ /p|pjson|j|json/
|
|
113
|
-
@_argv.clear
|
|
114
|
-
@_argv.concat(%w(group list)).flatten
|
|
131
|
+
apply_ansible_alias!(%w[group list])
|
|
115
132
|
when '--host'
|
|
116
|
-
@_confopts[:ansible] = true
|
|
117
|
-
@_confopts[:format] = 'json' unless @_confopts[:format] =~ /p|pjson|j|json/
|
|
118
133
|
host = @_argv[1]
|
|
119
|
-
|
|
120
|
-
@_argv.concat(['host', 'listvars', host.to_s]).flatten
|
|
134
|
+
apply_ansible_alias!(['host', 'listvars', host.to_s])
|
|
121
135
|
end
|
|
122
136
|
end
|
|
123
137
|
|
|
124
138
|
#----------------------
|
|
125
139
|
def self.resolve_config_file
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
end
|
|
133
|
-
else
|
|
134
|
-
possibles = ['./.moose-tools/inventory/config',
|
|
135
|
-
'~/.moose-tools/inventory/config',
|
|
136
|
-
'~/local/etc/moose-tools/inventory/config',
|
|
137
|
-
'/etc/moose-tools/inventory/config']
|
|
138
|
-
possibles.each do |f|
|
|
139
|
-
file = File.expand_path(f)
|
|
140
|
-
@_confopts[:config] = file if File.exist?(file)
|
|
141
|
-
end
|
|
142
|
-
end
|
|
140
|
+
explicit_path = @_confopts[:config]
|
|
141
|
+
@_confopts[:config] = if explicit_path.nil?
|
|
142
|
+
find_default_config_file
|
|
143
|
+
else
|
|
144
|
+
validated_config_path(explicit_path)
|
|
145
|
+
end
|
|
143
146
|
|
|
144
|
-
if @_confopts[:config].nil?
|
|
145
|
-
fail('No configuration either given or found in standard locations.')
|
|
146
|
-
end
|
|
147
|
+
raise('No configuration either given or found in standard locations.') if @_confopts[:config].nil?
|
|
147
148
|
end
|
|
148
149
|
|
|
149
150
|
#----------------------
|
|
150
151
|
def self.symbolize_keys(hash)
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
new_key = case key
|
|
154
|
-
when String then key.to_sym
|
|
155
|
-
else key
|
|
156
|
-
end
|
|
157
|
-
new_value = case value
|
|
158
|
-
when Hash then symbolize_keys(value)
|
|
159
|
-
else value
|
|
160
|
-
end
|
|
161
|
-
result[new_key] = new_value
|
|
162
|
-
result
|
|
152
|
+
hash.each_with_object({}) do |(key, value), result|
|
|
153
|
+
result[symbolize_key(key)] = value.is_a?(Hash) ? symbolize_keys(value) : value
|
|
163
154
|
end
|
|
164
|
-
# rubocop:enable Style/EachWithObject
|
|
165
155
|
end
|
|
166
156
|
|
|
167
157
|
#----------------------
|
|
168
|
-
# rubocop:disable PerceivedComplexity
|
|
169
|
-
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/AbcSize
|
|
170
158
|
def self.load
|
|
171
|
-
newsets =
|
|
172
|
-
@_confopts[:config],
|
|
173
|
-
aliases: false,
|
|
174
|
-
permitted_classes: [],
|
|
175
|
-
permitted_symbols: []
|
|
176
|
-
))
|
|
177
|
-
|
|
159
|
+
newsets = load_config_file(@_confopts[:config])
|
|
178
160
|
path = @_confopts[:config]
|
|
179
161
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
162
|
+
@_settings[:general] = fetch_general_settings(newsets, path)
|
|
163
|
+
|
|
164
|
+
env, settings = resolve_environment_settings(newsets, path)
|
|
165
|
+
@_settings[:config] = settings
|
|
166
|
+
@_settings[:config].nil? && raise("Missing '#{env}' root in #{path}")
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def self.load_config_file(path)
|
|
170
|
+
symbolize_keys(YAML.safe_load_file(
|
|
171
|
+
path,
|
|
172
|
+
aliases: false,
|
|
173
|
+
permitted_classes: [],
|
|
174
|
+
permitted_symbols: []
|
|
175
|
+
))
|
|
176
|
+
end
|
|
183
177
|
|
|
184
|
-
|
|
178
|
+
def self.fetch_general_settings(newsets, path)
|
|
179
|
+
general = newsets[:general]
|
|
180
|
+
general.nil? && raise("Missing 'general' root in #{path}")
|
|
181
|
+
general
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def self.resolve_environment_settings(newsets, path)
|
|
185
|
+
env = selected_environment(newsets, path)
|
|
186
|
+
[env, newsets[env.to_sym]]
|
|
187
|
+
end
|
|
185
188
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
189
|
+
def self.selected_environment(newsets, path)
|
|
190
|
+
return @_confopts[:env] if @_confopts[:env] && !@_confopts[:env].empty?
|
|
191
|
+
|
|
192
|
+
env = newsets.dig(:general, :defaultenv)
|
|
193
|
+
(env.nil? || env.empty?) && raise("No defaultenv set in #{path}")
|
|
194
|
+
env
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def self.standard_config_paths
|
|
198
|
+
['./.moose-tools/inventory/config',
|
|
199
|
+
'~/.moose-tools/inventory/config',
|
|
200
|
+
'~/local/etc/moose-tools/inventory/config',
|
|
201
|
+
'/etc/moose-tools/inventory/config']
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def self.find_default_config_file
|
|
205
|
+
standard_config_paths.map { |path| File.expand_path(path) }.find do |path|
|
|
206
|
+
File.exist?(path)
|
|
193
207
|
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def self.validated_config_path(path)
|
|
211
|
+
expanded = File.expand_path(path)
|
|
212
|
+
raise("The configuration file #{expanded} does not exist") unless File.exist?(expanded)
|
|
213
|
+
|
|
214
|
+
expanded
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def self.extract_value_flags(flags)
|
|
218
|
+
flags.each { |flag| extract_value_flag(flag) }
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def self.extract_value_flag(flag)
|
|
222
|
+
index = @_argv.index("--#{flag}")
|
|
223
|
+
return if index.nil?
|
|
194
224
|
|
|
195
|
-
@
|
|
225
|
+
value = @_argv[index + 1]
|
|
226
|
+
raise("Expected a value after --#{flag}") if value.nil? || value.start_with?('--')
|
|
227
|
+
|
|
228
|
+
@_confopts[flag.to_sym] = value
|
|
229
|
+
@_argv.slice!(index, 2)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def self.extract_boolean_flags(flags)
|
|
233
|
+
flags.each { |flag| extract_boolean_flag(flag) }
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def self.extract_boolean_flag(flag)
|
|
237
|
+
index = @_argv.index("--#{flag}")
|
|
238
|
+
return if index.nil?
|
|
239
|
+
|
|
240
|
+
@_confopts[flag.to_sym] = true
|
|
241
|
+
@_argv.delete_at(index)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def self.normalize_ansible_format!
|
|
245
|
+
return unless @_confopts[:ansible] == true
|
|
246
|
+
return if @_confopts[:format] =~ /p|pjson|j|json/
|
|
247
|
+
|
|
248
|
+
@_confopts[:format] = 'json'
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def self.apply_ansible_alias!(argv)
|
|
252
|
+
@_confopts[:ansible] = true
|
|
253
|
+
normalize_ansible_format!
|
|
254
|
+
@_argv = argv
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def self.refresh_runtime_options
|
|
258
|
+
@runtime_options = build_runtime_options
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def self.build_runtime_options
|
|
262
|
+
Moose::Inventory::RuntimeOptions.new(
|
|
263
|
+
argv: @_argv.dup,
|
|
264
|
+
config: @_confopts[:config],
|
|
265
|
+
env: @_confopts[:env],
|
|
266
|
+
format: @_confopts[:format],
|
|
267
|
+
flags: {
|
|
268
|
+
ansible: @_confopts[:ansible],
|
|
269
|
+
trace: @_confopts[:trace]
|
|
270
|
+
}
|
|
271
|
+
)
|
|
272
|
+
end
|
|
196
273
|
|
|
197
|
-
|
|
274
|
+
def self.symbolize_key(key)
|
|
275
|
+
key.is_a?(String) ? key.to_sym : key
|
|
198
276
|
end
|
|
199
|
-
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/AbcSize
|
|
200
277
|
end
|
|
201
278
|
end
|
|
202
279
|
end
|