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,248 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moose
|
|
4
|
+
module Inventory
|
|
5
|
+
module DB
|
|
6
|
+
# Schema definitions, ordered migrations, and schema-artifact helpers for Moose Inventory DB.
|
|
7
|
+
# rubocop:disable Metrics/ModuleLength
|
|
8
|
+
module SchemaMigrations
|
|
9
|
+
SCHEMA_VERSION = 4
|
|
10
|
+
|
|
11
|
+
TABLE_DEFINITIONS = {
|
|
12
|
+
hosts: lambda do |db|
|
|
13
|
+
db.create_table(:hosts) do
|
|
14
|
+
primary_key :id
|
|
15
|
+
column :name, :text, unique: true
|
|
16
|
+
end
|
|
17
|
+
end,
|
|
18
|
+
hostvars: lambda do |db|
|
|
19
|
+
db.create_table(:hostvars) do
|
|
20
|
+
primary_key :id
|
|
21
|
+
foreign_key :host_id
|
|
22
|
+
column :name, :text
|
|
23
|
+
column :value, :text
|
|
24
|
+
end
|
|
25
|
+
end,
|
|
26
|
+
groups: lambda do |db|
|
|
27
|
+
db.create_table(:groups) do
|
|
28
|
+
primary_key :id
|
|
29
|
+
column :name, :text, unique: true
|
|
30
|
+
end
|
|
31
|
+
end,
|
|
32
|
+
groups_groups: lambda do |db|
|
|
33
|
+
db.create_table(:groups_groups) do
|
|
34
|
+
primary_key :id
|
|
35
|
+
foreign_key :parent_id, :groups
|
|
36
|
+
foreign_key :child_id, :groups
|
|
37
|
+
end
|
|
38
|
+
end,
|
|
39
|
+
groupvars: lambda do |db|
|
|
40
|
+
db.create_table(:groupvars) do
|
|
41
|
+
primary_key :id
|
|
42
|
+
foreign_key :group_id
|
|
43
|
+
column :name, :text
|
|
44
|
+
column :value, :text
|
|
45
|
+
end
|
|
46
|
+
end,
|
|
47
|
+
groups_hosts: lambda do |db|
|
|
48
|
+
db.create_table(:groups_hosts) do
|
|
49
|
+
primary_key :id
|
|
50
|
+
foreign_key :host_id, :hosts
|
|
51
|
+
foreign_key :group_id, :groups
|
|
52
|
+
end
|
|
53
|
+
end,
|
|
54
|
+
schema_info: lambda do |db|
|
|
55
|
+
db.create_table(:schema_info) do
|
|
56
|
+
primary_key :id
|
|
57
|
+
column :version, :integer, null: false
|
|
58
|
+
end
|
|
59
|
+
end,
|
|
60
|
+
audit_events: lambda do |db|
|
|
61
|
+
db.create_table(:audit_events) do
|
|
62
|
+
primary_key :id
|
|
63
|
+
column :created_at, :text, null: false
|
|
64
|
+
column :actor, :text
|
|
65
|
+
column :command, :text, null: false
|
|
66
|
+
column :action, :text, null: false
|
|
67
|
+
column :entity_type, :text
|
|
68
|
+
column :entity_name, :text
|
|
69
|
+
column :details, :text
|
|
70
|
+
end
|
|
71
|
+
end,
|
|
72
|
+
tags: lambda do |db|
|
|
73
|
+
db.create_table(:tags) do
|
|
74
|
+
primary_key :id
|
|
75
|
+
column :name, :text, unique: true, null: false
|
|
76
|
+
end
|
|
77
|
+
end,
|
|
78
|
+
hosts_tags: lambda do |db|
|
|
79
|
+
db.create_table(:hosts_tags) do
|
|
80
|
+
primary_key :id
|
|
81
|
+
foreign_key :host_id, :hosts
|
|
82
|
+
foreign_key :tag_id, :tags
|
|
83
|
+
end
|
|
84
|
+
end,
|
|
85
|
+
groups_tags: lambda do |db|
|
|
86
|
+
db.create_table(:groups_tags) do
|
|
87
|
+
primary_key :id
|
|
88
|
+
foreign_key :group_id, :groups
|
|
89
|
+
foreign_key :tag_id, :tags
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
}.freeze
|
|
93
|
+
|
|
94
|
+
SCHEMA_MIGRATIONS = {
|
|
95
|
+
1 => %i[hosts hostvars groups groups_groups groupvars groups_hosts schema_info],
|
|
96
|
+
2 => %i[audit_events],
|
|
97
|
+
3 => %i[tags hosts_tags groups_tags],
|
|
98
|
+
4 => []
|
|
99
|
+
}.freeze
|
|
100
|
+
|
|
101
|
+
INDEX_DEFINITIONS = [
|
|
102
|
+
{ table: :hostvars, columns: %i[host_id name], unique: true, name: :idx_hostvars_host_id_name },
|
|
103
|
+
{ table: :groupvars, columns: %i[group_id name], unique: true, name: :idx_groupvars_group_id_name },
|
|
104
|
+
{ table: :groups_hosts, columns: %i[host_id group_id], unique: true,
|
|
105
|
+
name: :idx_groups_hosts_host_id_group_id },
|
|
106
|
+
{ table: :groups_groups, columns: %i[parent_id child_id], unique: true,
|
|
107
|
+
name: :idx_groups_groups_parent_id_child_id },
|
|
108
|
+
{ table: :hosts_tags, columns: %i[host_id tag_id], unique: true, name: :idx_hosts_tags_host_id_tag_id },
|
|
109
|
+
{ table: :groups_tags, columns: %i[group_id tag_id], unique: true, name: :idx_groups_tags_group_id_tag_id },
|
|
110
|
+
{ table: :groups_hosts, columns: %i[group_id host_id], unique: false,
|
|
111
|
+
name: :idx_groups_hosts_group_id_host_id },
|
|
112
|
+
{ table: :groups_groups, columns: %i[child_id parent_id], unique: false,
|
|
113
|
+
name: :idx_groups_groups_child_id_parent_id },
|
|
114
|
+
{ table: :hosts_tags, columns: %i[tag_id host_id], unique: false, name: :idx_hosts_tags_tag_id_host_id },
|
|
115
|
+
{ table: :groups_tags, columns: %i[tag_id group_id], unique: false, name: :idx_groups_tags_tag_id_group_id }
|
|
116
|
+
].freeze
|
|
117
|
+
|
|
118
|
+
def migration_versions
|
|
119
|
+
SCHEMA_MIGRATIONS.keys.sort
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def schema_version
|
|
123
|
+
return nil unless @db.table_exists?(:schema_info)
|
|
124
|
+
|
|
125
|
+
@db[:schema_info].order(:id).last&.fetch(:version)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def migrate_schema!
|
|
129
|
+
reject_future_schema!
|
|
130
|
+
migration_versions.each do |version|
|
|
131
|
+
next if schema_version.to_i >= version && !schema_migration_artifacts_missing?(version)
|
|
132
|
+
|
|
133
|
+
apply_schema_migration!(version)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def schema_migration_artifacts_missing?(version)
|
|
138
|
+
schema_migration_tables_missing?(version) || (version == 4 && schema_indexes_missing?)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def schema_migration_tables_missing?(version)
|
|
142
|
+
SCHEMA_MIGRATIONS.fetch(version).any? { |table_name| !@db.table_exists?(table_name) }
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def schema_indexes_missing?
|
|
146
|
+
INDEX_DEFINITIONS.any? do |definition|
|
|
147
|
+
!@db.table_exists?(definition.fetch(:table)) || !index_exists?(definition.fetch(:table),
|
|
148
|
+
definition.fetch(:name))
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def apply_schema_migration!(version)
|
|
153
|
+
tables = SCHEMA_MIGRATIONS.fetch(version)
|
|
154
|
+
tables.each { |table_name| create_table(table_name) }
|
|
155
|
+
apply_schema_indexes! if version == 4
|
|
156
|
+
record_schema_version!(version)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def apply_schema_indexes!
|
|
160
|
+
clean_duplicate_index_rows!
|
|
161
|
+
INDEX_DEFINITIONS.each { |definition| add_index(definition) }
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def clean_duplicate_index_rows!
|
|
165
|
+
dedupe_duplicate_rows!(:hostvars, %i[host_id name], value_columns: [:value])
|
|
166
|
+
dedupe_duplicate_rows!(:groupvars, %i[group_id name], value_columns: [:value])
|
|
167
|
+
dedupe_duplicate_rows!(:groups_hosts, %i[host_id group_id])
|
|
168
|
+
dedupe_duplicate_rows!(:groups_groups, %i[parent_id child_id])
|
|
169
|
+
dedupe_duplicate_rows!(:hosts_tags, %i[host_id tag_id])
|
|
170
|
+
dedupe_duplicate_rows!(:groups_tags, %i[group_id tag_id])
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def dedupe_duplicate_rows!(table_name, columns, value_columns: [])
|
|
174
|
+
duplicate_keys(table_name, columns).each do |key|
|
|
175
|
+
rows = @db[table_name].where(key).order(:id).all
|
|
176
|
+
reject_conflicting_duplicates!(table_name, key, rows, value_columns)
|
|
177
|
+
@db[table_name].where(id: rows.drop(1).map { |row| row.fetch(:id) }).delete
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def duplicate_keys(table_name, columns)
|
|
182
|
+
@db[table_name]
|
|
183
|
+
.select(*columns)
|
|
184
|
+
.group(*columns)
|
|
185
|
+
.having { count(id) > 1 }
|
|
186
|
+
.all
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def reject_conflicting_duplicates!(table_name, key, rows, value_columns)
|
|
190
|
+
conflicts = value_columns.any? do |column|
|
|
191
|
+
rows.map { |row| row[column] }.uniq.length > 1
|
|
192
|
+
end
|
|
193
|
+
return unless conflicts
|
|
194
|
+
|
|
195
|
+
raise @exceptions[:moose], "Cannot add unique indexes because #{table_name} has conflicting duplicates " \
|
|
196
|
+
"for #{key}. Resolve duplicate values before migrating."
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def add_index(definition)
|
|
200
|
+
return if index_exists?(definition.fetch(:table), definition.fetch(:name))
|
|
201
|
+
|
|
202
|
+
@db.add_index(definition.fetch(:table), definition.fetch(:columns), unique: definition.fetch(:unique),
|
|
203
|
+
name: definition.fetch(:name))
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def index_exists?(table_name, index_name)
|
|
207
|
+
@db.indexes(table_name).key?(index_name)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def record_schema_version!(version)
|
|
211
|
+
unless @db.table_exists?(:schema_info)
|
|
212
|
+
raise @exceptions[:moose],
|
|
213
|
+
'Cannot record schema version before schema_info exists.'
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
if @db[:schema_info].empty?
|
|
217
|
+
@db[:schema_info].insert(version: version)
|
|
218
|
+
else
|
|
219
|
+
@db[:schema_info].update(version: version)
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def reject_future_schema!
|
|
224
|
+
return unless @db.table_exists?(:schema_info)
|
|
225
|
+
|
|
226
|
+
current_version = schema_version
|
|
227
|
+
return if current_version.nil? || current_version <= SCHEMA_VERSION
|
|
228
|
+
|
|
229
|
+
raise @exceptions[:moose], "Database schema version #{current_version} is newer than supported version " \
|
|
230
|
+
"#{SCHEMA_VERSION}. Upgrade moose-inventory before using this database."
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def create_tables
|
|
234
|
+
TABLE_DEFINITIONS.each do |table_name, definition|
|
|
235
|
+
create_table(table_name, definition)
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def create_table(table_name, definition = TABLE_DEFINITIONS.fetch(table_name))
|
|
240
|
+
return if @db.table_exists?(table_name)
|
|
241
|
+
|
|
242
|
+
definition.call(@db)
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
# rubocop:enable Metrics/ModuleLength
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'time'
|
|
4
|
+
|
|
3
5
|
module Moose
|
|
4
6
|
module Inventory
|
|
5
7
|
##
|
|
6
|
-
# Thin facade over
|
|
8
|
+
# Thin facade over an explicitly supplied DB implementation.
|
|
7
9
|
#
|
|
8
10
|
# This gives new operation/service objects a small inventory-facing seam
|
|
9
11
|
# without forcing the legacy CLI to stop using the DB singleton all at once.
|
|
10
12
|
class InventoryContext
|
|
11
13
|
AUTOMATIC_GROUP = 'ungrouped'
|
|
12
14
|
|
|
13
|
-
def initialize(db:
|
|
15
|
+
def initialize(db:)
|
|
14
16
|
@db = db
|
|
15
17
|
end
|
|
16
18
|
|
|
@@ -38,12 +40,76 @@ module Moose
|
|
|
38
40
|
db.models[:group].find_or_create(name: name)
|
|
39
41
|
end
|
|
40
42
|
|
|
43
|
+
def find_tag(name)
|
|
44
|
+
db.models[:tag].find(name: normalize_tag_name(name))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def find_or_create_tag(name)
|
|
48
|
+
db.models[:tag].find_or_create(name: normalize_tag_name(name))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def normalize_tag_name(name)
|
|
52
|
+
name.to_s.downcase.strip
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def normalize_tag_names(names)
|
|
56
|
+
names.map { |name| normalize_tag_name(name) }.reject(&:empty?).uniq
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def hosts_dataset
|
|
60
|
+
db.models[:host].dataset
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def db_dataset(table_name)
|
|
64
|
+
db.db[table_name]
|
|
65
|
+
end
|
|
66
|
+
|
|
41
67
|
def automatic_group
|
|
42
68
|
find_or_create_group(AUTOMATIC_GROUP)
|
|
43
69
|
end
|
|
44
70
|
|
|
71
|
+
def find_variable(entity_type, id)
|
|
72
|
+
db.models[variable_model_key(entity_type)].find(id: id)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def create_variable(entity_type, name:, value:)
|
|
76
|
+
db.models[variable_model_key(entity_type)].create(name: name, value: value)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def all_hosts
|
|
80
|
+
db.models[:host].all
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def all_groups
|
|
84
|
+
db.models[:group].all
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def record_audit_event(attributes)
|
|
88
|
+
db.models[:audit_event].create(
|
|
89
|
+
created_at: Time.now.utc.iso8601,
|
|
90
|
+
actor: attributes[:actor],
|
|
91
|
+
command: attributes.fetch(:command),
|
|
92
|
+
action: attributes.fetch(:action),
|
|
93
|
+
entity_type: attributes[:entity_type],
|
|
94
|
+
entity_name: attributes[:entity_name],
|
|
95
|
+
details: attributes[:details]
|
|
96
|
+
)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def audit_events(limit: 20)
|
|
100
|
+
db.models[:audit_event].reverse_order(:id).limit(limit).all
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def moose_exception_class
|
|
104
|
+
db.exceptions[:moose]
|
|
105
|
+
end
|
|
106
|
+
|
|
45
107
|
private
|
|
46
108
|
|
|
109
|
+
def variable_model_key(entity_type)
|
|
110
|
+
:"#{entity_type}var"
|
|
111
|
+
end
|
|
112
|
+
|
|
47
113
|
attr_reader :db
|
|
48
114
|
end
|
|
49
115
|
end
|
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'operation_event_support'
|
|
4
|
+
|
|
3
5
|
module Moose
|
|
4
6
|
module Inventory
|
|
5
7
|
module Operations
|
|
6
8
|
# Adds host/group associations for existing primary entities.
|
|
7
9
|
class AddAssociations
|
|
8
10
|
AUTOMATIC_GROUP = 'ungrouped'
|
|
9
|
-
|
|
10
|
-
Result = Struct.new(:events, :warning_count, keyword_init: true)
|
|
11
|
+
include OperationEventSupport
|
|
11
12
|
|
|
12
13
|
def initialize(context:)
|
|
13
14
|
@context = context
|
|
14
15
|
end
|
|
15
16
|
|
|
16
|
-
def host_to_groups(host:, host_name:, group_names:)
|
|
17
|
+
def host_to_groups(host:, host_name:, group_names:, dry_run: false)
|
|
17
18
|
events = []
|
|
18
19
|
warning_count = 0
|
|
20
|
+
@dry_run = dry_run
|
|
19
21
|
|
|
20
22
|
group_names.each do |group_name|
|
|
21
23
|
next if group_name.nil? || group_name.empty?
|
|
@@ -24,13 +26,15 @@ module Moose
|
|
|
24
26
|
end
|
|
25
27
|
|
|
26
28
|
remove_automatic_group_from_host(host, host_name, events)
|
|
29
|
+
emit(events, :dry_run_summary) if dry_run
|
|
27
30
|
|
|
28
|
-
|
|
31
|
+
operation_result(events: events, warning_count: warning_count)
|
|
29
32
|
end
|
|
30
33
|
|
|
31
|
-
def group_to_hosts(group:, group_name:, host_names:)
|
|
34
|
+
def group_to_hosts(group:, group_name:, host_names:, dry_run: false)
|
|
32
35
|
events = []
|
|
33
36
|
warning_count = 0
|
|
37
|
+
@dry_run = dry_run
|
|
34
38
|
hosts_dataset = group.hosts_dataset
|
|
35
39
|
|
|
36
40
|
host_names.each do |host_name|
|
|
@@ -45,12 +49,14 @@ module Moose
|
|
|
45
49
|
)
|
|
46
50
|
end
|
|
47
51
|
|
|
48
|
-
|
|
52
|
+
emit(events, :dry_run_summary) if dry_run
|
|
53
|
+
|
|
54
|
+
operation_result(events: events, warning_count: warning_count)
|
|
49
55
|
end
|
|
50
56
|
|
|
51
57
|
private
|
|
52
58
|
|
|
53
|
-
attr_reader :context
|
|
59
|
+
attr_reader :context, :dry_run
|
|
54
60
|
|
|
55
61
|
def add_group_to_host(host, host_name, group_name, events)
|
|
56
62
|
warning_count = 0
|
|
@@ -69,12 +75,12 @@ module Moose
|
|
|
69
75
|
if group.nil?
|
|
70
76
|
emit(events, :group_missing_created, name: group_name)
|
|
71
77
|
emit(events, :group_creating_now, name: group_name)
|
|
72
|
-
group = context.create_group(group_name)
|
|
78
|
+
group = context.create_group(group_name) unless dry_run
|
|
73
79
|
emit(events, :ok, indent: 6)
|
|
74
80
|
warning_count += 1
|
|
75
81
|
end
|
|
76
82
|
|
|
77
|
-
host.add_group(group)
|
|
83
|
+
host.add_group(group) unless dry_run
|
|
78
84
|
emit(events, :ok, indent: 4)
|
|
79
85
|
warning_count
|
|
80
86
|
end
|
|
@@ -94,33 +100,31 @@ module Moose
|
|
|
94
100
|
if host.nil?
|
|
95
101
|
emit(events, :host_missing_created, name: host_name)
|
|
96
102
|
emit(events, :host_creating_now, name: host_name)
|
|
97
|
-
host = context.create_host(host_name)
|
|
103
|
+
host = context.create_host(host_name) unless dry_run
|
|
98
104
|
emit(events, :ok, indent: 6)
|
|
99
105
|
warning_count += 1
|
|
100
106
|
end
|
|
101
107
|
|
|
102
|
-
group.add_host(host)
|
|
108
|
+
group.add_host(host) unless dry_run
|
|
103
109
|
emit(events, :ok, indent: 4)
|
|
104
110
|
remove_automatic_group_from_host(host, host_name, events)
|
|
105
111
|
warning_count
|
|
106
112
|
end
|
|
107
113
|
|
|
108
114
|
def remove_automatic_group_from_host(host, host_name, events)
|
|
115
|
+
return if host.nil?
|
|
116
|
+
|
|
109
117
|
ungrouped = host.groups_dataset[name: AUTOMATIC_GROUP]
|
|
110
118
|
return if ungrouped.nil?
|
|
111
119
|
|
|
112
120
|
emit(events, :removing_automatic_group, host: host_name)
|
|
113
|
-
host.remove_group(ungrouped)
|
|
121
|
+
host.remove_group(ungrouped) unless dry_run
|
|
114
122
|
emit(events, :ok, indent: 4)
|
|
115
123
|
end
|
|
116
124
|
|
|
117
125
|
def association_exists?(dataset, name)
|
|
118
126
|
!dataset.nil? && !dataset[name: name].nil?
|
|
119
127
|
end
|
|
120
|
-
|
|
121
|
-
def emit(events, type, payload = {})
|
|
122
|
-
events << Event.new(type: type, payload: payload)
|
|
123
|
-
end
|
|
124
128
|
end
|
|
125
129
|
end
|
|
126
130
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'operation_event_support'
|
|
4
|
+
|
|
3
5
|
module Moose
|
|
4
6
|
module Inventory
|
|
5
7
|
module Operations
|
|
@@ -7,16 +9,24 @@ module Moose
|
|
|
7
9
|
# Adds groups and their optional host associations.
|
|
8
10
|
class AddGroups
|
|
9
11
|
AUTOMATIC_GROUP = 'ungrouped'
|
|
10
|
-
|
|
11
|
-
Result = Struct.new(:events, :warning_count, keyword_init: true)
|
|
12
|
+
include OperationEventSupport
|
|
12
13
|
|
|
13
14
|
def initialize(context:)
|
|
14
15
|
@context = context
|
|
15
16
|
end
|
|
16
17
|
|
|
17
|
-
def call(names:, hosts:)
|
|
18
|
+
def call(names:, hosts:, dry_run: false)
|
|
18
19
|
events = []
|
|
19
20
|
warning_count = 0
|
|
21
|
+
@dry_run = dry_run
|
|
22
|
+
|
|
23
|
+
if dry_run
|
|
24
|
+
names.each do |name|
|
|
25
|
+
warning_count += add_group(name, hosts, events)
|
|
26
|
+
end
|
|
27
|
+
emit(events, :dry_run_summary)
|
|
28
|
+
return operation_result(events: events, warning_count: warning_count)
|
|
29
|
+
end
|
|
20
30
|
|
|
21
31
|
context.transaction do
|
|
22
32
|
names.each do |name|
|
|
@@ -24,12 +34,12 @@ module Moose
|
|
|
24
34
|
end
|
|
25
35
|
end
|
|
26
36
|
|
|
27
|
-
|
|
37
|
+
operation_result(events: events, warning_count: warning_count)
|
|
28
38
|
end
|
|
29
39
|
|
|
30
40
|
private
|
|
31
41
|
|
|
32
|
-
attr_reader :context
|
|
42
|
+
attr_reader :context, :dry_run
|
|
33
43
|
|
|
34
44
|
def add_group(name, hosts, events)
|
|
35
45
|
warning_count = 0
|
|
@@ -52,7 +62,7 @@ module Moose
|
|
|
52
62
|
group = context.find_group(name)
|
|
53
63
|
|
|
54
64
|
if group.nil?
|
|
55
|
-
group = context.create_group(name)
|
|
65
|
+
group = context.create_group(name) unless dry_run
|
|
56
66
|
emit(events, :ok, indent: 4)
|
|
57
67
|
[group, nil, true]
|
|
58
68
|
else
|
|
@@ -73,7 +83,7 @@ module Moose
|
|
|
73
83
|
emit(events, :association_exists, group: group_name, host: host_name)
|
|
74
84
|
emit(events, :already_exists_skipping, indent: 4)
|
|
75
85
|
warning_count += 1
|
|
76
|
-
|
|
86
|
+
elsif !dry_run
|
|
77
87
|
group.add_host(host)
|
|
78
88
|
end
|
|
79
89
|
emit(events, :ok, indent: 4)
|
|
@@ -88,27 +98,25 @@ module Moose
|
|
|
88
98
|
|
|
89
99
|
emit(events, :host_missing_created, name: name)
|
|
90
100
|
emit(events, :host_creating_now, name: name)
|
|
91
|
-
host = context.create_host(name)
|
|
101
|
+
host = context.create_host(name) unless dry_run
|
|
92
102
|
emit(events, :ok, indent: 6)
|
|
93
103
|
[host, :warned_create]
|
|
94
104
|
end
|
|
95
105
|
|
|
96
106
|
def remove_automatic_group_from_host(host, host_name, events)
|
|
107
|
+
return if host.nil?
|
|
108
|
+
|
|
97
109
|
ungrouped = host.groups_dataset[name: AUTOMATIC_GROUP]
|
|
98
110
|
return if ungrouped.nil?
|
|
99
111
|
|
|
100
112
|
emit(events, :removing_automatic_group, host: host_name)
|
|
101
|
-
host.remove_group(ungrouped)
|
|
113
|
+
host.remove_group(ungrouped) unless dry_run
|
|
102
114
|
emit(events, :ok, indent: 4)
|
|
103
115
|
end
|
|
104
116
|
|
|
105
117
|
def association_exists?(dataset, name)
|
|
106
118
|
!dataset.nil? && !dataset[name: name].nil?
|
|
107
119
|
end
|
|
108
|
-
|
|
109
|
-
def emit(events, type, payload = {})
|
|
110
|
-
events << Event.new(type: type, payload: payload)
|
|
111
|
-
end
|
|
112
120
|
end
|
|
113
121
|
end
|
|
114
122
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'operation_event_support'
|
|
4
|
+
|
|
3
5
|
module Moose
|
|
4
6
|
module Inventory
|
|
5
7
|
module Operations
|
|
@@ -12,26 +14,35 @@ module Moose
|
|
|
12
14
|
# to progress text.
|
|
13
15
|
class AddHosts
|
|
14
16
|
AUTOMATIC_GROUP = 'ungrouped'
|
|
15
|
-
|
|
16
|
-
Result = Struct.new(:events, keyword_init: true)
|
|
17
|
+
include OperationEventSupport
|
|
17
18
|
|
|
18
19
|
def initialize(context:)
|
|
19
20
|
@context = context
|
|
20
21
|
end
|
|
21
22
|
|
|
22
|
-
def call(names:, groups:)
|
|
23
|
+
def call(names:, groups:, dry_run: false)
|
|
23
24
|
events = []
|
|
25
|
+
@dry_run = dry_run
|
|
26
|
+
|
|
27
|
+
if dry_run
|
|
28
|
+
names.each do |name|
|
|
29
|
+
add_host(name, groups, events)
|
|
30
|
+
end
|
|
31
|
+
emit(events, :dry_run_summary)
|
|
32
|
+
return operation_result(events: events)
|
|
33
|
+
end
|
|
34
|
+
|
|
24
35
|
context.transaction do
|
|
25
36
|
names.each do |name|
|
|
26
37
|
add_host(name, groups, events)
|
|
27
38
|
end
|
|
28
39
|
end
|
|
29
|
-
|
|
40
|
+
operation_result(events: events)
|
|
30
41
|
end
|
|
31
42
|
|
|
32
43
|
private
|
|
33
44
|
|
|
34
|
-
attr_reader :context
|
|
45
|
+
attr_reader :context, :dry_run
|
|
35
46
|
|
|
36
47
|
def add_host(name, groups, events)
|
|
37
48
|
emit(events, :host_started, name: name)
|
|
@@ -41,7 +52,7 @@ module Moose
|
|
|
41
52
|
add_group_association(host, name, group_name, groups_dataset, events)
|
|
42
53
|
end
|
|
43
54
|
|
|
44
|
-
add_automatic_group_if_needed(host, name, events)
|
|
55
|
+
add_automatic_group_if_needed(host, name, groups, groups_dataset, events)
|
|
45
56
|
emit(events, :host_complete)
|
|
46
57
|
end
|
|
47
58
|
|
|
@@ -51,7 +62,7 @@ module Moose
|
|
|
51
62
|
groups_dataset = nil
|
|
52
63
|
|
|
53
64
|
if host.nil?
|
|
54
|
-
host = context.create_host(name)
|
|
65
|
+
host = context.create_host(name) unless dry_run
|
|
55
66
|
else
|
|
56
67
|
emit(events, :host_exists, name: name)
|
|
57
68
|
groups_dataset = host.groups_dataset
|
|
@@ -69,7 +80,7 @@ module Moose
|
|
|
69
80
|
|
|
70
81
|
if association_exists?(groups_dataset, group_name)
|
|
71
82
|
emit(events, :association_exists, host: host_name, group: group_name)
|
|
72
|
-
|
|
83
|
+
elsif !dry_run
|
|
73
84
|
host.add_group(group)
|
|
74
85
|
end
|
|
75
86
|
|
|
@@ -81,18 +92,24 @@ module Moose
|
|
|
81
92
|
return group unless group.nil?
|
|
82
93
|
|
|
83
94
|
emit(events, :group_missing_created, name: name)
|
|
84
|
-
context.create_group(name)
|
|
95
|
+
context.create_group(name) unless dry_run
|
|
85
96
|
end
|
|
86
97
|
|
|
87
|
-
def add_automatic_group_if_needed(host, host_name, events)
|
|
88
|
-
|
|
89
|
-
return if groups_dataset.nil? || groups_dataset.any?
|
|
98
|
+
def add_automatic_group_if_needed(host, host_name, requested_groups, groups_dataset, events)
|
|
99
|
+
return unless automatic_group_needed?(host, requested_groups, groups_dataset)
|
|
90
100
|
|
|
91
101
|
emit(events, :adding_automatic_group, host: host_name, group: AUTOMATIC_GROUP)
|
|
92
|
-
host.add_group(automatic_group)
|
|
102
|
+
host.add_group(automatic_group) unless dry_run
|
|
93
103
|
emit(events, :ok, indent: 4)
|
|
94
104
|
end
|
|
95
105
|
|
|
106
|
+
def automatic_group_needed?(host, requested_groups, groups_dataset)
|
|
107
|
+
return requested_groups.empty? && (host.nil? || groups_dataset.nil? || groups_dataset.none?) if dry_run
|
|
108
|
+
|
|
109
|
+
groups_dataset = host.groups_dataset
|
|
110
|
+
!groups_dataset.nil? && groups_dataset.none?
|
|
111
|
+
end
|
|
112
|
+
|
|
96
113
|
def automatic_group
|
|
97
114
|
context.automatic_group
|
|
98
115
|
end
|
|
@@ -100,10 +117,6 @@ module Moose
|
|
|
100
117
|
def association_exists?(dataset, name)
|
|
101
118
|
!dataset.nil? && !dataset[name: name].nil?
|
|
102
119
|
end
|
|
103
|
-
|
|
104
|
-
def emit(events, type, payload = {})
|
|
105
|
-
events << Event.new(type: type, payload: payload)
|
|
106
|
-
end
|
|
107
120
|
end
|
|
108
121
|
end
|
|
109
122
|
end
|