moose-inventory 2.0 → 2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/release.yml +2 -0
- data/.gitignore +2 -1
- data/.rubocop.yml +21 -0
- data/BACKLOG.md +630 -8
- data/Gemfile +2 -0
- data/Gemfile.lock +1 -1
- data/README.md +315 -39
- data/Rakefile +2 -0
- data/bin/moose-inventory +2 -1
- data/docs/architecture/architecture-and-trust-boundaries.md +444 -0
- data/docs/compatibility/cli-output-compatibility.md +76 -0
- data/docs/governance/approval-register.md +37 -0
- data/docs/maintenance/database-backup-restore-guidance.md +162 -0
- data/docs/maintenance/package-maintenance-and-agent-boundaries.md +260 -0
- data/docs/process/conformance-gap-analysis-2026-05-28.md +192 -0
- data/docs/product/product-brief.md +161 -0
- data/docs/product/requirements-baseline.md +477 -0
- data/docs/qa/qa-documentation-and-release-gates.md +283 -0
- data/docs/release/package-provenance-hardening.md +126 -0
- data/docs/release/publishing.md +11 -3
- data/docs/release/release-environment-protection.md +70 -0
- data/docs/release/release-readiness.md +23 -4
- data/docs/security/accepted-risk-register.md +84 -0
- data/docs/security/security-privacy-process.md +287 -0
- data/docs/security-audit-2026-05-26-rerun.md +2 -2
- data/docs/ux/cli-workflow-notes.md +287 -0
- data/examples/ansible/ansible.cfg +3 -0
- data/examples/ansible/inventory/moose_inventory.yml +5 -0
- data/examples/ansible/inventory_plugins/moose_inventory.py +100 -0
- data/examples/ci/README.md +16 -0
- data/examples/ci/github-actions/inventory-review.yml +38 -0
- data/examples/ci/inventory/example-snapshot.yml +19 -0
- data/examples/ci/scripts/validate-inventory-snapshot.sh +30 -0
- data/lib/moose_inventory/cli/application.rb +133 -5
- data/lib/moose_inventory/cli/association_rendering.rb +74 -0
- data/lib/moose_inventory/cli/association_rendering_support.rb +89 -0
- data/lib/moose_inventory/cli/audit.rb +62 -0
- data/lib/moose_inventory/cli/audit_recording.rb +40 -0
- data/lib/moose_inventory/cli/child_relation_rendering.rb +110 -0
- data/lib/moose_inventory/cli/console.rb +135 -0
- data/lib/moose_inventory/cli/db.rb +64 -0
- data/lib/moose_inventory/cli/factory.rb +28 -0
- data/lib/moose_inventory/cli/formatter.rb +8 -12
- data/lib/moose_inventory/cli/group.rb +5 -2
- data/lib/moose_inventory/cli/group_add.rb +11 -9
- data/lib/moose_inventory/cli/group_addchild.rb +23 -65
- data/lib/moose_inventory/cli/group_addhost.rb +16 -67
- data/lib/moose_inventory/cli/group_addvar.rb +27 -47
- data/lib/moose_inventory/cli/group_get.rb +8 -42
- data/lib/moose_inventory/cli/group_list.rb +7 -40
- data/lib/moose_inventory/cli/group_listvars.rb +9 -55
- data/lib/moose_inventory/cli/group_rm.rb +12 -10
- data/lib/moose_inventory/cli/group_rmchild.rb +26 -82
- data/lib/moose_inventory/cli/group_rmhost.rb +18 -53
- data/lib/moose_inventory/cli/group_rmvar.rb +30 -41
- data/lib/moose_inventory/cli/group_tags.rb +33 -0
- data/lib/moose_inventory/cli/helpers.rb +68 -1
- data/lib/moose_inventory/cli/host.rb +6 -3
- data/lib/moose_inventory/cli/host_add.rb +69 -29
- data/lib/moose_inventory/cli/host_addgroup.rb +22 -58
- data/lib/moose_inventory/cli/host_addvar.rb +28 -52
- data/lib/moose_inventory/cli/host_get.rb +9 -37
- data/lib/moose_inventory/cli/host_list.rb +24 -21
- data/lib/moose_inventory/cli/host_listvars.rb +9 -62
- data/lib/moose_inventory/cli/host_rm.rb +60 -42
- data/lib/moose_inventory/cli/host_rmgroup.rb +25 -44
- data/lib/moose_inventory/cli/host_rmvar.rb +31 -45
- data/lib/moose_inventory/cli/host_tags.rb +33 -0
- data/lib/moose_inventory/cli/listvars_support.rb +55 -0
- data/lib/moose_inventory/cli/plan_rendering.rb +50 -0
- data/lib/moose_inventory/cli/relation_transaction_support.rb +51 -0
- data/lib/moose_inventory/cli/tag_support.rb +97 -0
- data/lib/moose_inventory/cli/variable_rendering.rb +67 -0
- data/lib/moose_inventory/config/config.rb +185 -108
- data/lib/moose_inventory/db/db.rb +170 -195
- data/lib/moose_inventory/db/exceptions.rb +6 -3
- data/lib/moose_inventory/db/models.rb +16 -0
- data/lib/moose_inventory/db/schema_migrations.rb +248 -0
- data/lib/moose_inventory/inventory_context.rb +68 -2
- data/lib/moose_inventory/operations/add_associations.rb +20 -16
- data/lib/moose_inventory/operations/add_groups.rb +21 -13
- data/lib/moose_inventory/operations/add_hosts.rb +30 -17
- data/lib/moose_inventory/operations/add_variables.rb +77 -0
- data/lib/moose_inventory/operations/entity_variable_operation_support.rb +46 -0
- data/lib/moose_inventory/operations/group_child_relations.rb +23 -16
- data/lib/moose_inventory/operations/group_cleanup.rb +23 -8
- data/lib/moose_inventory/operations/import_inventory_snapshot.rb +41 -0
- data/lib/moose_inventory/operations/inventory_doctor.rb +172 -0
- data/lib/moose_inventory/operations/inventory_snapshot.rb +60 -0
- data/lib/moose_inventory/operations/inventory_snapshot_applier.rb +112 -0
- data/lib/moose_inventory/operations/inventory_snapshot_preview.rb +174 -0
- data/lib/moose_inventory/operations/inventory_snapshot_validator.rb +134 -0
- data/lib/moose_inventory/operations/operation_event_support.rb +27 -0
- data/lib/moose_inventory/operations/query_inventory/base_query.rb +24 -0
- data/lib/moose_inventory/operations/query_inventory/group_queries.rb +86 -0
- data/lib/moose_inventory/operations/query_inventory/host_queries.rb +106 -0
- data/lib/moose_inventory/operations/query_inventory.rb +47 -0
- data/lib/moose_inventory/operations/remove_associations.rb +30 -18
- data/lib/moose_inventory/operations/remove_groups.rb +12 -12
- data/lib/moose_inventory/operations/remove_hosts.rb +68 -0
- data/lib/moose_inventory/operations/remove_variables.rb +67 -0
- data/lib/moose_inventory/runtime_options.rb +31 -0
- data/lib/moose_inventory/version.rb +3 -1
- data/lib/moose_inventory.rb +10 -7
- data/moose-inventory.gemspec +19 -35
- data/scripts/check.sh +1 -0
- data/scripts/ci/check_generated_artifacts.sh +41 -0
- data/scripts/ci/check_permissions.sh +2 -0
- data/scripts/ci/check_rubocop.sh +30 -25
- data/scripts/files.rb +5 -4
- data/spec/examples/ci_examples_spec.rb +37 -0
- data/spec/lib/moose_inventory/ansible_plugin_examples_spec.rb +29 -0
- data/spec/lib/moose_inventory/cli/application_doctor_spec.rb +50 -0
- data/spec/lib/moose_inventory/cli/application_import_export_spec.rb +100 -0
- data/spec/lib/moose_inventory/cli/application_spec.rb +25 -15
- data/spec/lib/moose_inventory/cli/audit_spec.rb +56 -0
- data/spec/lib/moose_inventory/cli/cli_spec.rb +15 -19
- data/spec/lib/moose_inventory/cli/console_spec.rb +98 -0
- data/spec/lib/moose_inventory/cli/factory_spec.rb +27 -0
- data/spec/lib/moose_inventory/cli/formatter_spec.rb +95 -3
- data/spec/lib/moose_inventory/cli/group_add_spec.rb +140 -116
- data/spec/lib/moose_inventory/cli/group_addchild_spec.rb +89 -35
- data/spec/lib/moose_inventory/cli/group_addhost_spec.rb +81 -84
- data/spec/lib/moose_inventory/cli/group_addvar_spec.rb +65 -68
- data/spec/lib/moose_inventory/cli/group_get_spec.rb +17 -33
- data/spec/lib/moose_inventory/cli/group_list_spec.rb +16 -38
- data/spec/lib/moose_inventory/cli/group_listvar_spec.rb +33 -40
- data/spec/lib/moose_inventory/cli/group_rm_spec.rb +136 -96
- data/spec/lib/moose_inventory/cli/group_rmchild_spec.rb +66 -41
- data/spec/lib/moose_inventory/cli/group_rmhost_spec.rb +76 -78
- data/spec/lib/moose_inventory/cli/group_rmvar_spec.rb +57 -63
- data/spec/lib/moose_inventory/cli/group_spec.rb +2 -0
- data/spec/lib/moose_inventory/cli/helpers_spec.rb +146 -0
- data/spec/lib/moose_inventory/cli/host_add_spec.rb +170 -116
- data/spec/lib/moose_inventory/cli/host_addgroup_spec.rb +100 -83
- data/spec/lib/moose_inventory/cli/host_addvar_spec.rb +92 -74
- data/spec/lib/moose_inventory/cli/host_get_spec.rb +14 -33
- data/spec/lib/moose_inventory/cli/host_list_spec.rb +41 -33
- data/spec/lib/moose_inventory/cli/host_listvar_spec.rb +45 -53
- data/spec/lib/moose_inventory/cli/host_rm_spec.rb +66 -48
- data/spec/lib/moose_inventory/cli/host_rmgroup_spec.rb +73 -83
- data/spec/lib/moose_inventory/cli/host_rmvar_spec.rb +56 -63
- data/spec/lib/moose_inventory/cli/host_spec.rb +2 -0
- data/spec/lib/moose_inventory/cli/tags_spec.rb +81 -0
- data/spec/lib/moose_inventory/config/config_spec.rb +41 -3
- data/spec/lib/moose_inventory/db/db_spec.rb +396 -36
- data/spec/lib/moose_inventory/db/exceptions_spec.rb +18 -0
- data/spec/lib/moose_inventory/db/models_spec.rb +7 -3
- data/spec/lib/moose_inventory/db_lifecycle_spec.rb +73 -0
- data/spec/lib/moose_inventory/inventory_context_spec.rb +10 -0
- data/spec/lib/moose_inventory/operations/add_associations_spec.rb +34 -0
- data/spec/lib/moose_inventory/operations/add_groups_spec.rb +15 -0
- data/spec/lib/moose_inventory/operations/add_hosts_spec.rb +13 -0
- data/spec/lib/moose_inventory/operations/add_variables_spec.rb +103 -0
- data/spec/lib/moose_inventory/operations/group_child_relations_spec.rb +46 -0
- data/spec/lib/moose_inventory/operations/import_inventory_snapshot_spec.rb +226 -0
- data/spec/lib/moose_inventory/operations/inventory_doctor_spec.rb +77 -0
- data/spec/lib/moose_inventory/operations/inventory_snapshot_spec.rb +50 -0
- data/spec/lib/moose_inventory/operations/operation_event_support_spec.rb +78 -0
- data/spec/lib/moose_inventory/operations/query_inventory_spec.rb +146 -0
- data/spec/lib/moose_inventory/operations/remove_associations_spec.rb +35 -0
- data/spec/lib/moose_inventory/operations/remove_groups_spec.rb +21 -0
- data/spec/lib/moose_inventory/operations/remove_hosts_spec.rb +55 -0
- data/spec/lib/moose_inventory/operations/remove_variables_spec.rb +83 -0
- data/spec/shared/shared_config_setup.rb +4 -3
- data/spec/spec_helper.rb +50 -40
- data/spec/support/cli_harness.rb +33 -0
- metadata +80 -41
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'sequel'
|
|
4
|
+
require 'fileutils'
|
|
2
5
|
require 'json'
|
|
3
6
|
|
|
4
|
-
require_relative '
|
|
7
|
+
require_relative 'exceptions'
|
|
8
|
+
require_relative 'schema_migrations'
|
|
5
9
|
|
|
6
10
|
module Moose
|
|
7
11
|
module Inventory
|
|
@@ -12,53 +16,46 @@ module Moose
|
|
|
12
16
|
extend self
|
|
13
17
|
# rubocop:enable Style/ModuleFunction
|
|
14
18
|
|
|
15
|
-
@db
|
|
16
|
-
@models
|
|
19
|
+
@db = nil
|
|
20
|
+
@models = nil
|
|
17
21
|
@exceptions = nil
|
|
22
|
+
attr_reader :db, :models, :exceptions
|
|
23
|
+
|
|
24
|
+
extend SchemaMigrations
|
|
25
|
+
|
|
26
|
+
SCHEMA_VERSION = SchemaMigrations::SCHEMA_VERSION
|
|
27
|
+
TABLE_DEFINITIONS = SchemaMigrations::TABLE_DEFINITIONS
|
|
28
|
+
SCHEMA_MIGRATIONS = SchemaMigrations::SCHEMA_MIGRATIONS
|
|
29
|
+
INDEX_DEFINITIONS = SchemaMigrations::INDEX_DEFINITIONS
|
|
30
|
+
|
|
31
|
+
MODEL_KEYS = {
|
|
32
|
+
host: :Host,
|
|
33
|
+
hostvar: :Hostvar,
|
|
34
|
+
group: :Group,
|
|
35
|
+
groupvar: :Groupvar,
|
|
36
|
+
audit_event: :AuditEvent,
|
|
37
|
+
tag: :Tag
|
|
38
|
+
}.freeze
|
|
18
39
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
40
|
+
BUSY_RETRY_LIMIT = 10
|
|
41
|
+
BUSY_RETRY_BASE_DELAY_SECONDS = 0.05
|
|
42
|
+
BUSY_RETRY_MAX_DELAY_SECONDS = 1.0
|
|
22
43
|
|
|
23
44
|
#----------------------
|
|
24
45
|
def self.init
|
|
25
46
|
init_exceptions
|
|
26
|
-
|
|
27
|
-
# If we allow init more than once, then the db connection is remade,
|
|
28
|
-
# which changes Sequel:DATABASES[0], thereby invalidating the sequel
|
|
29
|
-
# models. This causes unexpected behavour. That is to say, because
|
|
30
|
-
# of the way Sequel initializes models, this method is not idempotent.
|
|
31
|
-
# In our single-shot application, this shouldn't be a problem. However,
|
|
32
|
-
# our unit tests like to call init multiple times, which borks things.
|
|
33
|
-
# So, we allow init only once, gated by whether @db is nil. In effect,
|
|
34
|
-
# this means we pool the DB connection for the life of the application.
|
|
35
|
-
# Again, not a problem for our one-shot app, but it may be an issue in
|
|
36
|
-
# long-running code. Personally, I don't like this pooling regime -
|
|
37
|
-
# perhaps I'm not understanding how it's supposed to be used?
|
|
38
|
-
#
|
|
39
|
-
# QUESTION: can the models be refreshed, to make then again valid? What if
|
|
40
|
-
# we "load" instead of "require" the models?
|
|
41
|
-
# ANSWER: Nope, still borks even if we use a load.
|
|
42
|
-
#
|
|
43
|
-
# @db = nil # <- fails for unit tests
|
|
44
|
-
return unless @db.nil? # <- works for unit tests
|
|
47
|
+
return unless @db.nil?
|
|
45
48
|
|
|
46
49
|
Sequel::Model.plugin :json_serializer
|
|
47
50
|
connect
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
Sequel::DATABASES[0] = @db
|
|
52
|
-
require_relative 'models'
|
|
53
|
-
# load( load_dir = File.join(File.dirname(__FILE__), "models.rb") )
|
|
54
|
-
|
|
55
|
-
# For convenience
|
|
56
|
-
@models = {}
|
|
57
|
-
@models[:host] = Moose::Inventory::DB::Host
|
|
58
|
-
@models[:hostvar] = Moose::Inventory::DB::Hostvar
|
|
59
|
-
@models[:group] = Moose::Inventory::DB::Group
|
|
60
|
-
@models[:groupvar] = Moose::Inventory::DB::Groupvar
|
|
51
|
+
migrate_schema!
|
|
52
|
+
bind_models!
|
|
53
|
+
end
|
|
61
54
|
|
|
55
|
+
def self.reset_runtime_state
|
|
56
|
+
@db = nil
|
|
57
|
+
@models = nil
|
|
58
|
+
@exceptions = nil
|
|
62
59
|
end
|
|
63
60
|
|
|
64
61
|
#--------------------
|
|
@@ -68,154 +65,138 @@ module Moose
|
|
|
68
65
|
end
|
|
69
66
|
|
|
70
67
|
#--------------------
|
|
71
|
-
def self.transaction
|
|
72
|
-
|
|
68
|
+
def self.transaction(&)
|
|
69
|
+
raise('Database connection has not been established') if @db.nil?
|
|
73
70
|
|
|
74
71
|
tries = 0
|
|
75
72
|
|
|
76
73
|
begin
|
|
77
|
-
@db.transaction(savepoint: true)
|
|
78
|
-
yield
|
|
79
|
-
end
|
|
80
|
-
|
|
74
|
+
@db.transaction(savepoint: true, &)
|
|
81
75
|
rescue Sequel::DatabaseError => e
|
|
82
|
-
|
|
83
|
-
# and re-raises it as Sequel::DatabaseError, with a message referencing
|
|
84
|
-
# the original exception class
|
|
76
|
+
raise unless busy_database_error?(e)
|
|
85
77
|
|
|
86
|
-
# We look into e, to see whether it is a BusyException. If not,
|
|
87
|
-
# we re-raise immediately.
|
|
88
|
-
raise unless e.message.include?('BusyException')
|
|
89
|
-
|
|
90
|
-
# Looks like a BusyException, so we retry, after a random delay.
|
|
91
78
|
tries += 1
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if Moose::Inventory::Config._confopts[:trace] == true
|
|
95
|
-
$stderr.puts e.message
|
|
96
|
-
end
|
|
97
|
-
sleep rand
|
|
98
|
-
retry
|
|
99
|
-
else
|
|
100
|
-
warn('The database appears to be locked by another process, and '\
|
|
101
|
-
" did not become free after #{tries} tries. Giving up. ")
|
|
102
|
-
|
|
103
|
-
# TODO: Some useful advice to the user, as to what to do about this error.
|
|
104
|
-
raise
|
|
105
|
-
end
|
|
106
|
-
|
|
79
|
+
retry_busy_transaction(e, tries)
|
|
80
|
+
retry
|
|
107
81
|
rescue @exceptions[:moose] => e
|
|
108
82
|
warn 'An error occurred during a transaction, any changes have been rolled back.'
|
|
109
83
|
|
|
110
|
-
if Moose::Inventory::Config.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
else
|
|
114
|
-
abort("ERROR: #{e.message}")
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
rescue Exception => e
|
|
84
|
+
warn e.full_message(highlight: false, order: :top) if Moose::Inventory::Config.trace_enabled?
|
|
85
|
+
abort("ERROR: #{e.message}")
|
|
86
|
+
rescue SystemExit, StandardError
|
|
118
87
|
warn 'An error occurred during a transaction, any changes have been rolled back.'
|
|
119
|
-
raise
|
|
88
|
+
raise
|
|
120
89
|
end
|
|
121
90
|
end
|
|
122
91
|
|
|
123
92
|
#--------------------
|
|
124
93
|
def self.reset
|
|
125
|
-
|
|
126
|
-
|
|
94
|
+
raise('Database connection has not been established') if @db.nil?
|
|
95
|
+
|
|
127
96
|
purge
|
|
128
|
-
|
|
97
|
+
migrate_schema!
|
|
129
98
|
end
|
|
130
99
|
|
|
131
100
|
#===============================
|
|
132
101
|
|
|
133
|
-
|
|
102
|
+
def self.bind_models!
|
|
103
|
+
Sequel::DATABASES[0] = @db
|
|
104
|
+
require_relative 'models'
|
|
105
|
+
@models = MODEL_KEYS.transform_values do |name|
|
|
106
|
+
Moose::Inventory::DB.const_get(name)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
134
109
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
adapter.downcase!
|
|
139
|
-
|
|
140
|
-
if adapter == 'sqlite3'
|
|
141
|
-
# HACK: SQLite3 supposedly supports CASCADE, see
|
|
142
|
-
# https://www.sqlite.org/foreignkeys.html#fk_actions
|
|
143
|
-
# However, when we do a drop_table with :cascade=>true
|
|
144
|
-
# on an sqlite3 database, it throws errors regarding
|
|
145
|
-
# foreign keys constraints. Instead, the following is
|
|
146
|
-
# less efficient, but does work.
|
|
147
|
-
|
|
148
|
-
Group.all.each do |g|
|
|
149
|
-
g.remove_all_hosts
|
|
150
|
-
g.remove_all_groupvars
|
|
151
|
-
g.remove_all_children
|
|
152
|
-
g.destroy
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
Host.all.each do |h|
|
|
156
|
-
h.remove_all_groups
|
|
157
|
-
h.remove_all_hostvars
|
|
158
|
-
h.destroy
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
Groupvar.all.each(&:destroy)
|
|
162
|
-
Hostvar.all.each(&:destroy)
|
|
110
|
+
def self.busy_database_error?(error)
|
|
111
|
+
error.message.include?('BusyException')
|
|
112
|
+
end
|
|
163
113
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
114
|
+
def self.busy_retry_delay(tries)
|
|
115
|
+
delay = BUSY_RETRY_BASE_DELAY_SECONDS * (2**(tries - 1))
|
|
116
|
+
[delay, BUSY_RETRY_MAX_DELAY_SECONDS].min
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def self.retry_busy_transaction(error, tries, sleeper: method(:sleep))
|
|
120
|
+
if tries <= BUSY_RETRY_LIMIT
|
|
121
|
+
warn error.message if Moose::Inventory::Config.trace_enabled?
|
|
122
|
+
sleeper.call(busy_retry_delay(tries))
|
|
123
|
+
return
|
|
168
124
|
end
|
|
125
|
+
|
|
126
|
+
warn('The database appears to be locked by another process, and ' \
|
|
127
|
+
"did not become free after #{tries} tries. Giving up. ")
|
|
128
|
+
raise error
|
|
169
129
|
end
|
|
170
130
|
|
|
171
131
|
#--------------------
|
|
172
|
-
def self.
|
|
173
|
-
|
|
174
|
-
@db.create_table(:hosts) do
|
|
175
|
-
primary_key :id
|
|
176
|
-
column :name, :text, unique: true
|
|
177
|
-
end
|
|
178
|
-
end
|
|
132
|
+
def self.purge
|
|
133
|
+
return purge_sqlite_associations if sqlite_adapter?
|
|
179
134
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
column :name, :text
|
|
185
|
-
column :value, :text
|
|
186
|
-
end
|
|
187
|
-
end
|
|
135
|
+
@db.drop_table(:hosts, :hostvars,
|
|
136
|
+
:groups, :groupvars, :group_hosts,
|
|
137
|
+
if_exists: true, cascade: true)
|
|
138
|
+
end
|
|
188
139
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
140
|
+
def self.status
|
|
141
|
+
{
|
|
142
|
+
adapter: normalized_adapter,
|
|
143
|
+
schema_version: schema_version,
|
|
144
|
+
expected_schema_version: SCHEMA_VERSION,
|
|
145
|
+
tables: TABLE_DEFINITIONS.keys.to_h { |name| [name, @db.table_exists?(name)] },
|
|
146
|
+
sqlite_file: sqlite_adapter? ? sqlite_file : nil
|
|
147
|
+
}
|
|
148
|
+
end
|
|
195
149
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
150
|
+
def self.migrate!
|
|
151
|
+
migrate_schema!
|
|
152
|
+
status
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def self.backup(path)
|
|
156
|
+
raise @exceptions[:moose], 'Database backup is currently supported for sqlite3 only.' unless sqlite_adapter?
|
|
157
|
+
|
|
158
|
+
source = sqlite_file
|
|
159
|
+
raise @exceptions[:moose], "SQLite database file #{source} does not exist." unless File.exist?(source)
|
|
160
|
+
|
|
161
|
+
destination = File.expand_path(path)
|
|
162
|
+
FileUtils.mkdir_p(File.dirname(destination))
|
|
163
|
+
FileUtils.cp(source, destination)
|
|
164
|
+
destination
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def self.sqlite_adapter?
|
|
168
|
+
normalized_adapter == 'sqlite3'
|
|
169
|
+
end
|
|
203
170
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
171
|
+
def self.sqlite_file
|
|
172
|
+
File.expand_path(config_db_settings[:file])
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def self.purge_sqlite_associations
|
|
176
|
+
purge_sqlite_groups
|
|
177
|
+
purge_sqlite_hosts
|
|
178
|
+
Groupvar.all.each(&:destroy)
|
|
179
|
+
Hostvar.all.each(&:destroy)
|
|
180
|
+
AuditEvent.all.each(&:destroy) if @db.table_exists?(:audit_events)
|
|
181
|
+
Tag.all.each(&:destroy) if @db.table_exists?(:tags)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def self.purge_sqlite_groups
|
|
185
|
+
Group.all.each do |group|
|
|
186
|
+
group.remove_all_hosts
|
|
187
|
+
group.remove_all_groupvars
|
|
188
|
+
group.remove_all_children
|
|
189
|
+
group.remove_all_tags if @db.table_exists?(:groups_tags)
|
|
190
|
+
group.destroy
|
|
211
191
|
end
|
|
192
|
+
end
|
|
212
193
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
194
|
+
def self.purge_sqlite_hosts
|
|
195
|
+
Host.all.each do |host|
|
|
196
|
+
host.remove_all_groups
|
|
197
|
+
host.remove_all_hostvars
|
|
198
|
+
host.remove_all_tags if @db.table_exists?(:hosts_tags)
|
|
199
|
+
host.destroy
|
|
219
200
|
end
|
|
220
201
|
end
|
|
221
202
|
|
|
@@ -223,61 +204,51 @@ module Moose
|
|
|
223
204
|
def self.connect
|
|
224
205
|
return unless @db.nil?
|
|
225
206
|
|
|
226
|
-
|
|
227
|
-
adapter.downcase!
|
|
228
|
-
|
|
229
|
-
case adapter
|
|
207
|
+
case normalized_adapter
|
|
230
208
|
when 'sqlite3'
|
|
231
209
|
init_sqlite3
|
|
232
|
-
|
|
233
210
|
when 'mysql'
|
|
234
211
|
init_mysql
|
|
235
|
-
|
|
236
212
|
when 'postgresql'
|
|
237
213
|
init_postgresql
|
|
238
|
-
|
|
239
214
|
else
|
|
240
|
-
|
|
241
|
-
|
|
215
|
+
raise @exceptions[:moose],
|
|
216
|
+
"database adapter #{normalized_adapter} is not yet supported."
|
|
242
217
|
end
|
|
243
218
|
end
|
|
244
219
|
|
|
220
|
+
def self.config_db_settings
|
|
221
|
+
Moose::Inventory::Config.db_settings
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def self.normalized_adapter
|
|
225
|
+
config_db_settings[:adapter].downcase
|
|
226
|
+
end
|
|
227
|
+
|
|
245
228
|
#--------------------
|
|
246
|
-
def self.init_sqlite3
|
|
229
|
+
def self.init_sqlite3
|
|
247
230
|
require 'sqlite3'
|
|
248
231
|
require 'fileutils'
|
|
232
|
+
init_exceptions
|
|
249
233
|
|
|
250
|
-
|
|
251
|
-
config
|
|
252
|
-
[:file].
|
|
253
|
-
if config[key].nil?
|
|
254
|
-
fail @exceptions[:moose],
|
|
255
|
-
"Expected key #{key} missing in sqlite3 configuration"
|
|
256
|
-
end
|
|
257
|
-
end
|
|
258
|
-
config[:file].empty? && fail("SQLite3 DB 'file' cannot be empty")
|
|
234
|
+
config = config_db_settings
|
|
235
|
+
ensure_required_config_keys!(config, [:file], 'sqlite3')
|
|
236
|
+
raise("SQLite3 DB 'file' cannot be empty") if config[:file].empty?
|
|
259
237
|
|
|
260
|
-
# Make sure the directory exists
|
|
261
238
|
dbfile = File.expand_path(config[:file])
|
|
262
239
|
dbdir = File.dirname(dbfile)
|
|
263
|
-
FileUtils.mkdir_p(dbdir)
|
|
240
|
+
FileUtils.mkdir_p(dbdir)
|
|
264
241
|
|
|
265
|
-
# Create and/or open the database file
|
|
266
242
|
@db = Sequel.sqlite(dbfile)
|
|
267
243
|
end
|
|
268
244
|
|
|
269
245
|
#--------------------
|
|
270
246
|
def self.init_mysql
|
|
271
247
|
require 'mysql2'
|
|
248
|
+
init_exceptions
|
|
272
249
|
|
|
273
|
-
|
|
274
|
-
config
|
|
275
|
-
[:host, :database, :user].each do |key|
|
|
276
|
-
if config[key].nil?
|
|
277
|
-
fail @exceptions[:moose],
|
|
278
|
-
"Expected key #{key} missing in mysql configuration"
|
|
279
|
-
end
|
|
280
|
-
end
|
|
250
|
+
config = config_db_settings
|
|
251
|
+
ensure_required_config_keys!(config, %i[host database user], 'mysql')
|
|
281
252
|
password = db_password(config, 'mysql')
|
|
282
253
|
|
|
283
254
|
@db = Sequel.mysql2(user: config[:user],
|
|
@@ -289,15 +260,10 @@ module Moose
|
|
|
289
260
|
#--------------------
|
|
290
261
|
def self.init_postgresql
|
|
291
262
|
require 'pg'
|
|
263
|
+
init_exceptions
|
|
292
264
|
|
|
293
|
-
|
|
294
|
-
config
|
|
295
|
-
[:host, :database, :user].each do |key|
|
|
296
|
-
if config[key].nil?
|
|
297
|
-
fail @exceptions[:moose],
|
|
298
|
-
"Expected key #{key} missing in postgresql configuration"
|
|
299
|
-
end
|
|
300
|
-
end
|
|
265
|
+
config = config_db_settings
|
|
266
|
+
ensure_required_config_keys!(config, %i[host database user], 'postgresql')
|
|
301
267
|
password = db_password(config, 'postgresql')
|
|
302
268
|
|
|
303
269
|
@db = Sequel.postgres(user: config[:user],
|
|
@@ -306,19 +272,28 @@ module Moose
|
|
|
306
272
|
database: config[:database])
|
|
307
273
|
end
|
|
308
274
|
|
|
275
|
+
def self.ensure_required_config_keys!(config, keys, adapter)
|
|
276
|
+
keys.each do |key|
|
|
277
|
+
next unless config[key].nil?
|
|
278
|
+
|
|
279
|
+
raise @exceptions[:moose],
|
|
280
|
+
"Expected key #{key} missing in #{adapter} configuration"
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
|
|
309
284
|
#--------------------
|
|
310
285
|
def self.db_password(config, adapter)
|
|
311
286
|
return config[:password] unless config[:password].nil?
|
|
312
287
|
|
|
313
288
|
if config[:password_env].nil?
|
|
314
|
-
|
|
315
|
-
|
|
289
|
+
raise @exceptions[:moose],
|
|
290
|
+
"Expected key password or password_env missing in #{adapter} configuration"
|
|
316
291
|
end
|
|
317
292
|
|
|
318
293
|
password = ENV.fetch(config[:password_env].to_s, nil)
|
|
319
294
|
if password.nil? || password.empty?
|
|
320
|
-
|
|
321
|
-
|
|
295
|
+
raise @exceptions[:moose],
|
|
296
|
+
"Environment variable #{config[:password_env]} is not set for #{adapter} password"
|
|
322
297
|
end
|
|
323
298
|
|
|
324
299
|
password
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Moose
|
|
2
4
|
module Inventory
|
|
3
5
|
module DB
|
|
4
6
|
##
|
|
5
7
|
# This class provides a Moose-specific db exception error
|
|
6
8
|
class MooseDBException < RuntimeError
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
DEFAULT_MESSAGE = 'An undefined Moose exception occurred'
|
|
10
|
+
|
|
11
|
+
def initialize(message = nil)
|
|
12
|
+
super(message || DEFAULT_MESSAGE)
|
|
10
13
|
end
|
|
11
14
|
end
|
|
12
15
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Moose
|
|
2
4
|
module Inventory
|
|
3
5
|
module DB
|
|
@@ -5,6 +7,7 @@ module Moose
|
|
|
5
7
|
# Model for the hosts table
|
|
6
8
|
class Host < Sequel::Model
|
|
7
9
|
many_to_many :groups
|
|
10
|
+
many_to_many :tags
|
|
8
11
|
one_to_many :hostvars
|
|
9
12
|
end
|
|
10
13
|
|
|
@@ -22,6 +25,7 @@ module Moose
|
|
|
22
25
|
class: self
|
|
23
26
|
|
|
24
27
|
many_to_many :hosts
|
|
28
|
+
many_to_many :tags
|
|
25
29
|
one_to_many :groupvars
|
|
26
30
|
end
|
|
27
31
|
|
|
@@ -36,6 +40,18 @@ module Moose
|
|
|
36
40
|
class Groupvar < Sequel::Model
|
|
37
41
|
many_to_one :groups
|
|
38
42
|
end
|
|
43
|
+
|
|
44
|
+
##
|
|
45
|
+
# Append-only audit event records.
|
|
46
|
+
class AuditEvent < Sequel::Model
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
##
|
|
50
|
+
# Host/group metadata tag record.
|
|
51
|
+
class Tag < Sequel::Model
|
|
52
|
+
many_to_many :hosts
|
|
53
|
+
many_to_many :groups
|
|
54
|
+
end
|
|
39
55
|
end
|
|
40
56
|
end
|
|
41
57
|
end
|