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
|
@@ -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
|
|
18
30
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
31
|
+
MODEL_KEYS = {
|
|
32
|
+
host: :Host,
|
|
33
|
+
hostvar: :Hostvar,
|
|
34
|
+
group: :Group,
|
|
35
|
+
groupvar: :Groupvar,
|
|
36
|
+
audit_event: :AuditEvent,
|
|
37
|
+
tag: :Tag
|
|
38
|
+
}.freeze
|
|
39
|
+
|
|
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)
|
|
203
160
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
|
170
|
+
|
|
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,64 +204,55 @@ 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
|
-
|
|
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')
|
|
252
|
+
password = db_password(config, 'mysql')
|
|
281
253
|
|
|
282
254
|
@db = Sequel.mysql2(user: config[:user],
|
|
283
|
-
password:
|
|
255
|
+
password: password,
|
|
284
256
|
host: config[:host],
|
|
285
257
|
database: config[:database])
|
|
286
258
|
end
|
|
@@ -288,21 +260,44 @@ module Moose
|
|
|
288
260
|
#--------------------
|
|
289
261
|
def self.init_postgresql
|
|
290
262
|
require 'pg'
|
|
263
|
+
init_exceptions
|
|
291
264
|
|
|
292
|
-
|
|
293
|
-
config
|
|
294
|
-
|
|
295
|
-
if config[key].nil?
|
|
296
|
-
fail @exceptions[:moose],
|
|
297
|
-
"Expected key #{key} missing in postgresql configuration"
|
|
298
|
-
end
|
|
299
|
-
end
|
|
265
|
+
config = config_db_settings
|
|
266
|
+
ensure_required_config_keys!(config, %i[host database user], 'postgresql')
|
|
267
|
+
password = db_password(config, 'postgresql')
|
|
300
268
|
|
|
301
269
|
@db = Sequel.postgres(user: config[:user],
|
|
302
|
-
password:
|
|
270
|
+
password: password,
|
|
303
271
|
host: config[:host],
|
|
304
272
|
database: config[:database])
|
|
305
273
|
end
|
|
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
|
+
|
|
284
|
+
#--------------------
|
|
285
|
+
def self.db_password(config, adapter)
|
|
286
|
+
return config[:password] unless config[:password].nil?
|
|
287
|
+
|
|
288
|
+
if config[:password_env].nil?
|
|
289
|
+
raise @exceptions[:moose],
|
|
290
|
+
"Expected key password or password_env missing in #{adapter} configuration"
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
password = ENV.fetch(config[:password_env].to_s, nil)
|
|
294
|
+
if password.nil? || password.empty?
|
|
295
|
+
raise @exceptions[:moose],
|
|
296
|
+
"Environment variable #{config[:password_env]} is not set for #{adapter} password"
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
password
|
|
300
|
+
end
|
|
306
301
|
end
|
|
307
302
|
end
|
|
308
303
|
end
|
|
@@ -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
|