moose-inventory 1.0.9 → 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/ci.yml +15 -1
- data/.github/workflows/release.yml +60 -0
- data/.gitignore +2 -1
- data/.gitleaks.toml +9 -0
- data/.rubocop.yml +49 -0
- data/BACKLOG.md +752 -24
- data/Gemfile +2 -0
- data/Gemfile.lock +36 -1
- data/README.md +340 -44
- 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 +54 -50
- data/docs/release/release-environment-protection.md +70 -0
- data/docs/release/release-readiness.md +37 -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 +75 -0
- data/docs/security-audit-2026-05-26.md +63 -0
- 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 +7 -1
- data/lib/moose_inventory/cli/group_add.rb +91 -73
- data/lib/moose_inventory/cli/group_addchild.rb +41 -66
- data/lib/moose_inventory/cli/group_addhost.rb +33 -71
- 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 +105 -73
- data/lib/moose_inventory/cli/group_rmchild.rb +47 -57
- data/lib/moose_inventory/cli/group_rmhost.rb +34 -61
- 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 +143 -0
- data/lib/moose_inventory/cli/host.rb +8 -2
- data/lib/moose_inventory/cli/host_add.rb +91 -66
- data/lib/moose_inventory/cli/host_addgroup.rb +39 -66
- 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 +39 -55
- 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 +188 -193
- 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 +116 -0
- data/lib/moose_inventory/operations/add_associations.rb +131 -0
- data/lib/moose_inventory/operations/add_groups.rb +123 -0
- data/lib/moose_inventory/operations/add_hosts.rb +123 -0
- 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 +125 -0
- data/lib/moose_inventory/operations/group_cleanup.rb +70 -0
- 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 +113 -0
- data/lib/moose_inventory/operations/remove_groups.rb +79 -0
- 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 +22 -35
- data/scripts/check.sh +3 -0
- data/scripts/ci/check_generated_artifacts.sh +41 -0
- data/scripts/ci/check_permissions.sh +5 -0
- data/scripts/ci/check_rubocop.sh +33 -0
- data/scripts/ci/check_secrets.sh +26 -0
- data/scripts/ci/check_security.sh +18 -0
- data/scripts/ci/install_security_tools.sh +47 -0
- data/scripts/files.rb +5 -4
- data/scripts/install_dependencies.sh +2 -0
- 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 +165 -85
- data/spec/lib/moose_inventory/cli/group_rmchild_spec.rb +100 -30
- 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 +551 -29
- 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 +111 -0
- data/spec/lib/moose_inventory/operations/add_groups_spec.rb +80 -0
- data/spec/lib/moose_inventory/operations/add_hosts_spec.rb +82 -0
- data/spec/lib/moose_inventory/operations/add_variables_spec.rb +103 -0
- data/spec/lib/moose_inventory/operations/group_child_relations_spec.rb +122 -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 +113 -0
- data/spec/lib/moose_inventory/operations/remove_groups_spec.rb +78 -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 +163 -35
|
@@ -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
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'time'
|
|
4
|
+
|
|
5
|
+
module Moose
|
|
6
|
+
module Inventory
|
|
7
|
+
##
|
|
8
|
+
# Thin facade over an explicitly supplied DB implementation.
|
|
9
|
+
#
|
|
10
|
+
# This gives new operation/service objects a small inventory-facing seam
|
|
11
|
+
# without forcing the legacy CLI to stop using the DB singleton all at once.
|
|
12
|
+
class InventoryContext
|
|
13
|
+
AUTOMATIC_GROUP = 'ungrouped'
|
|
14
|
+
|
|
15
|
+
def initialize(db:)
|
|
16
|
+
@db = db
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def transaction(&)
|
|
20
|
+
db.transaction(&)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def find_host(name)
|
|
24
|
+
db.models[:host].find(name: name)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def create_host(name)
|
|
28
|
+
db.models[:host].create(name: name)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def find_group(name)
|
|
32
|
+
db.models[:group].find(name: name)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def create_group(name)
|
|
36
|
+
db.models[:group].create(name: name)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def find_or_create_group(name)
|
|
40
|
+
db.models[:group].find_or_create(name: name)
|
|
41
|
+
end
|
|
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
|
+
|
|
67
|
+
def automatic_group
|
|
68
|
+
find_or_create_group(AUTOMATIC_GROUP)
|
|
69
|
+
end
|
|
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
|
+
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
def variable_model_key(entity_type)
|
|
110
|
+
:"#{entity_type}var"
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
attr_reader :db
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'operation_event_support'
|
|
4
|
+
|
|
5
|
+
module Moose
|
|
6
|
+
module Inventory
|
|
7
|
+
module Operations
|
|
8
|
+
# Adds host/group associations for existing primary entities.
|
|
9
|
+
class AddAssociations
|
|
10
|
+
AUTOMATIC_GROUP = 'ungrouped'
|
|
11
|
+
include OperationEventSupport
|
|
12
|
+
|
|
13
|
+
def initialize(context:)
|
|
14
|
+
@context = context
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def host_to_groups(host:, host_name:, group_names:, dry_run: false)
|
|
18
|
+
events = []
|
|
19
|
+
warning_count = 0
|
|
20
|
+
@dry_run = dry_run
|
|
21
|
+
|
|
22
|
+
group_names.each do |group_name|
|
|
23
|
+
next if group_name.nil? || group_name.empty?
|
|
24
|
+
|
|
25
|
+
warning_count += add_group_to_host(host, host_name, group_name, events)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
remove_automatic_group_from_host(host, host_name, events)
|
|
29
|
+
emit(events, :dry_run_summary) if dry_run
|
|
30
|
+
|
|
31
|
+
operation_result(events: events, warning_count: warning_count)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def group_to_hosts(group:, group_name:, host_names:, dry_run: false)
|
|
35
|
+
events = []
|
|
36
|
+
warning_count = 0
|
|
37
|
+
@dry_run = dry_run
|
|
38
|
+
hosts_dataset = group.hosts_dataset
|
|
39
|
+
|
|
40
|
+
host_names.each do |host_name|
|
|
41
|
+
next if host_name.nil? || host_name.empty?
|
|
42
|
+
|
|
43
|
+
warning_count += add_host_to_group(
|
|
44
|
+
group,
|
|
45
|
+
group_name,
|
|
46
|
+
host_name,
|
|
47
|
+
hosts_dataset,
|
|
48
|
+
events
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
emit(events, :dry_run_summary) if dry_run
|
|
53
|
+
|
|
54
|
+
operation_result(events: events, warning_count: warning_count)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
attr_reader :context, :dry_run
|
|
60
|
+
|
|
61
|
+
def add_group_to_host(host, host_name, group_name, events)
|
|
62
|
+
warning_count = 0
|
|
63
|
+
groups_dataset = host.groups_dataset
|
|
64
|
+
|
|
65
|
+
emit(events, :adding_host_group_association, host: host_name, group: group_name)
|
|
66
|
+
|
|
67
|
+
if association_exists?(groups_dataset, group_name)
|
|
68
|
+
emit(events, :host_group_association_exists, host: host_name, group: group_name)
|
|
69
|
+
emit(events, :already_exists_skipping, indent: 4)
|
|
70
|
+
emit(events, :ok, indent: 4)
|
|
71
|
+
return warning_count + 1
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
group = context.find_group(group_name)
|
|
75
|
+
if group.nil?
|
|
76
|
+
emit(events, :group_missing_created, name: group_name)
|
|
77
|
+
emit(events, :group_creating_now, name: group_name)
|
|
78
|
+
group = context.create_group(group_name) unless dry_run
|
|
79
|
+
emit(events, :ok, indent: 6)
|
|
80
|
+
warning_count += 1
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
host.add_group(group) unless dry_run
|
|
84
|
+
emit(events, :ok, indent: 4)
|
|
85
|
+
warning_count
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def add_host_to_group(group, group_name, host_name, hosts_dataset, events)
|
|
89
|
+
warning_count = 0
|
|
90
|
+
emit(events, :adding_group_host_association, group: group_name, host: host_name)
|
|
91
|
+
|
|
92
|
+
if association_exists?(hosts_dataset, host_name)
|
|
93
|
+
emit(events, :group_host_association_exists, group: group_name, host: host_name)
|
|
94
|
+
emit(events, :already_exists_skipping, indent: 4)
|
|
95
|
+
emit(events, :ok, indent: 4)
|
|
96
|
+
return warning_count + 1
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
host = context.find_host(host_name)
|
|
100
|
+
if host.nil?
|
|
101
|
+
emit(events, :host_missing_created, name: host_name)
|
|
102
|
+
emit(events, :host_creating_now, name: host_name)
|
|
103
|
+
host = context.create_host(host_name) unless dry_run
|
|
104
|
+
emit(events, :ok, indent: 6)
|
|
105
|
+
warning_count += 1
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
group.add_host(host) unless dry_run
|
|
109
|
+
emit(events, :ok, indent: 4)
|
|
110
|
+
remove_automatic_group_from_host(host, host_name, events)
|
|
111
|
+
warning_count
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def remove_automatic_group_from_host(host, host_name, events)
|
|
115
|
+
return if host.nil?
|
|
116
|
+
|
|
117
|
+
ungrouped = host.groups_dataset[name: AUTOMATIC_GROUP]
|
|
118
|
+
return if ungrouped.nil?
|
|
119
|
+
|
|
120
|
+
emit(events, :removing_automatic_group, host: host_name)
|
|
121
|
+
host.remove_group(ungrouped) unless dry_run
|
|
122
|
+
emit(events, :ok, indent: 4)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def association_exists?(dataset, name)
|
|
126
|
+
!dataset.nil? && !dataset[name: name].nil?
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'operation_event_support'
|
|
4
|
+
|
|
5
|
+
module Moose
|
|
6
|
+
module Inventory
|
|
7
|
+
module Operations
|
|
8
|
+
##
|
|
9
|
+
# Adds groups and their optional host associations.
|
|
10
|
+
class AddGroups
|
|
11
|
+
AUTOMATIC_GROUP = 'ungrouped'
|
|
12
|
+
include OperationEventSupport
|
|
13
|
+
|
|
14
|
+
def initialize(context:)
|
|
15
|
+
@context = context
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def call(names:, hosts:, dry_run: false)
|
|
19
|
+
events = []
|
|
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
|
|
30
|
+
|
|
31
|
+
context.transaction do
|
|
32
|
+
names.each do |name|
|
|
33
|
+
warning_count += add_group(name, hosts, events)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
operation_result(events: events, warning_count: warning_count)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
attr_reader :context, :dry_run
|
|
43
|
+
|
|
44
|
+
def add_group(name, hosts, events)
|
|
45
|
+
warning_count = 0
|
|
46
|
+
emit(events, :group_started, name: name)
|
|
47
|
+
group, hosts_dataset, created = create_or_find_group(name, events)
|
|
48
|
+
warning_count += 1 unless created
|
|
49
|
+
|
|
50
|
+
hosts.each do |host_name|
|
|
51
|
+
next if host_name.nil? || host_name.empty?
|
|
52
|
+
|
|
53
|
+
warning_count += add_host_association(group, name, host_name, hosts_dataset, events)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
emit(events, :group_complete)
|
|
57
|
+
warning_count
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def create_or_find_group(name, events)
|
|
61
|
+
emit(events, :creating_group)
|
|
62
|
+
group = context.find_group(name)
|
|
63
|
+
|
|
64
|
+
if group.nil?
|
|
65
|
+
group = context.create_group(name) unless dry_run
|
|
66
|
+
emit(events, :ok, indent: 4)
|
|
67
|
+
[group, nil, true]
|
|
68
|
+
else
|
|
69
|
+
emit(events, :group_exists, name: name)
|
|
70
|
+
emit(events, :already_exists_skipping, indent: 4)
|
|
71
|
+
emit(events, :ok, indent: 4)
|
|
72
|
+
[group, group.hosts_dataset, false]
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def add_host_association(group, group_name, host_name, hosts_dataset, events)
|
|
77
|
+
warning_count = 0
|
|
78
|
+
emit(events, :adding_association, group: group_name, host: host_name)
|
|
79
|
+
host, created = find_or_create_host(host_name, events)
|
|
80
|
+
warning_count += 1 if created == :warned_create
|
|
81
|
+
|
|
82
|
+
if association_exists?(hosts_dataset, host_name)
|
|
83
|
+
emit(events, :association_exists, group: group_name, host: host_name)
|
|
84
|
+
emit(events, :already_exists_skipping, indent: 4)
|
|
85
|
+
warning_count += 1
|
|
86
|
+
elsif !dry_run
|
|
87
|
+
group.add_host(host)
|
|
88
|
+
end
|
|
89
|
+
emit(events, :ok, indent: 4)
|
|
90
|
+
|
|
91
|
+
remove_automatic_group_from_host(host, host_name, events)
|
|
92
|
+
warning_count
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def find_or_create_host(name, events)
|
|
96
|
+
host = context.find_host(name)
|
|
97
|
+
return [host, :existing] unless host.nil?
|
|
98
|
+
|
|
99
|
+
emit(events, :host_missing_created, name: name)
|
|
100
|
+
emit(events, :host_creating_now, name: name)
|
|
101
|
+
host = context.create_host(name) unless dry_run
|
|
102
|
+
emit(events, :ok, indent: 6)
|
|
103
|
+
[host, :warned_create]
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def remove_automatic_group_from_host(host, host_name, events)
|
|
107
|
+
return if host.nil?
|
|
108
|
+
|
|
109
|
+
ungrouped = host.groups_dataset[name: AUTOMATIC_GROUP]
|
|
110
|
+
return if ungrouped.nil?
|
|
111
|
+
|
|
112
|
+
emit(events, :removing_automatic_group, host: host_name)
|
|
113
|
+
host.remove_group(ungrouped) unless dry_run
|
|
114
|
+
emit(events, :ok, indent: 4)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def association_exists?(dataset, name)
|
|
118
|
+
!dataset.nil? && !dataset[name: name].nil?
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|