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.
Files changed (169) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/release.yml +2 -0
  3. data/.gitignore +2 -1
  4. data/.rubocop.yml +21 -0
  5. data/BACKLOG.md +630 -8
  6. data/Gemfile +2 -0
  7. data/Gemfile.lock +1 -1
  8. data/README.md +315 -39
  9. data/Rakefile +2 -0
  10. data/bin/moose-inventory +2 -1
  11. data/docs/architecture/architecture-and-trust-boundaries.md +444 -0
  12. data/docs/compatibility/cli-output-compatibility.md +76 -0
  13. data/docs/governance/approval-register.md +37 -0
  14. data/docs/maintenance/database-backup-restore-guidance.md +162 -0
  15. data/docs/maintenance/package-maintenance-and-agent-boundaries.md +260 -0
  16. data/docs/process/conformance-gap-analysis-2026-05-28.md +192 -0
  17. data/docs/product/product-brief.md +161 -0
  18. data/docs/product/requirements-baseline.md +477 -0
  19. data/docs/qa/qa-documentation-and-release-gates.md +283 -0
  20. data/docs/release/package-provenance-hardening.md +126 -0
  21. data/docs/release/publishing.md +11 -3
  22. data/docs/release/release-environment-protection.md +70 -0
  23. data/docs/release/release-readiness.md +23 -4
  24. data/docs/security/accepted-risk-register.md +84 -0
  25. data/docs/security/security-privacy-process.md +287 -0
  26. data/docs/security-audit-2026-05-26-rerun.md +2 -2
  27. data/docs/ux/cli-workflow-notes.md +287 -0
  28. data/examples/ansible/ansible.cfg +3 -0
  29. data/examples/ansible/inventory/moose_inventory.yml +5 -0
  30. data/examples/ansible/inventory_plugins/moose_inventory.py +100 -0
  31. data/examples/ci/README.md +16 -0
  32. data/examples/ci/github-actions/inventory-review.yml +38 -0
  33. data/examples/ci/inventory/example-snapshot.yml +19 -0
  34. data/examples/ci/scripts/validate-inventory-snapshot.sh +30 -0
  35. data/lib/moose_inventory/cli/application.rb +133 -5
  36. data/lib/moose_inventory/cli/association_rendering.rb +74 -0
  37. data/lib/moose_inventory/cli/association_rendering_support.rb +89 -0
  38. data/lib/moose_inventory/cli/audit.rb +62 -0
  39. data/lib/moose_inventory/cli/audit_recording.rb +40 -0
  40. data/lib/moose_inventory/cli/child_relation_rendering.rb +110 -0
  41. data/lib/moose_inventory/cli/console.rb +135 -0
  42. data/lib/moose_inventory/cli/db.rb +64 -0
  43. data/lib/moose_inventory/cli/factory.rb +28 -0
  44. data/lib/moose_inventory/cli/formatter.rb +8 -12
  45. data/lib/moose_inventory/cli/group.rb +5 -2
  46. data/lib/moose_inventory/cli/group_add.rb +11 -9
  47. data/lib/moose_inventory/cli/group_addchild.rb +23 -65
  48. data/lib/moose_inventory/cli/group_addhost.rb +16 -67
  49. data/lib/moose_inventory/cli/group_addvar.rb +27 -47
  50. data/lib/moose_inventory/cli/group_get.rb +8 -42
  51. data/lib/moose_inventory/cli/group_list.rb +7 -40
  52. data/lib/moose_inventory/cli/group_listvars.rb +9 -55
  53. data/lib/moose_inventory/cli/group_rm.rb +12 -10
  54. data/lib/moose_inventory/cli/group_rmchild.rb +26 -82
  55. data/lib/moose_inventory/cli/group_rmhost.rb +18 -53
  56. data/lib/moose_inventory/cli/group_rmvar.rb +30 -41
  57. data/lib/moose_inventory/cli/group_tags.rb +33 -0
  58. data/lib/moose_inventory/cli/helpers.rb +68 -1
  59. data/lib/moose_inventory/cli/host.rb +6 -3
  60. data/lib/moose_inventory/cli/host_add.rb +69 -29
  61. data/lib/moose_inventory/cli/host_addgroup.rb +22 -58
  62. data/lib/moose_inventory/cli/host_addvar.rb +28 -52
  63. data/lib/moose_inventory/cli/host_get.rb +9 -37
  64. data/lib/moose_inventory/cli/host_list.rb +24 -21
  65. data/lib/moose_inventory/cli/host_listvars.rb +9 -62
  66. data/lib/moose_inventory/cli/host_rm.rb +60 -42
  67. data/lib/moose_inventory/cli/host_rmgroup.rb +25 -44
  68. data/lib/moose_inventory/cli/host_rmvar.rb +31 -45
  69. data/lib/moose_inventory/cli/host_tags.rb +33 -0
  70. data/lib/moose_inventory/cli/listvars_support.rb +55 -0
  71. data/lib/moose_inventory/cli/plan_rendering.rb +50 -0
  72. data/lib/moose_inventory/cli/relation_transaction_support.rb +51 -0
  73. data/lib/moose_inventory/cli/tag_support.rb +97 -0
  74. data/lib/moose_inventory/cli/variable_rendering.rb +67 -0
  75. data/lib/moose_inventory/config/config.rb +185 -108
  76. data/lib/moose_inventory/db/db.rb +170 -195
  77. data/lib/moose_inventory/db/exceptions.rb +6 -3
  78. data/lib/moose_inventory/db/models.rb +16 -0
  79. data/lib/moose_inventory/db/schema_migrations.rb +248 -0
  80. data/lib/moose_inventory/inventory_context.rb +68 -2
  81. data/lib/moose_inventory/operations/add_associations.rb +20 -16
  82. data/lib/moose_inventory/operations/add_groups.rb +21 -13
  83. data/lib/moose_inventory/operations/add_hosts.rb +30 -17
  84. data/lib/moose_inventory/operations/add_variables.rb +77 -0
  85. data/lib/moose_inventory/operations/entity_variable_operation_support.rb +46 -0
  86. data/lib/moose_inventory/operations/group_child_relations.rb +23 -16
  87. data/lib/moose_inventory/operations/group_cleanup.rb +23 -8
  88. data/lib/moose_inventory/operations/import_inventory_snapshot.rb +41 -0
  89. data/lib/moose_inventory/operations/inventory_doctor.rb +172 -0
  90. data/lib/moose_inventory/operations/inventory_snapshot.rb +60 -0
  91. data/lib/moose_inventory/operations/inventory_snapshot_applier.rb +112 -0
  92. data/lib/moose_inventory/operations/inventory_snapshot_preview.rb +174 -0
  93. data/lib/moose_inventory/operations/inventory_snapshot_validator.rb +134 -0
  94. data/lib/moose_inventory/operations/operation_event_support.rb +27 -0
  95. data/lib/moose_inventory/operations/query_inventory/base_query.rb +24 -0
  96. data/lib/moose_inventory/operations/query_inventory/group_queries.rb +86 -0
  97. data/lib/moose_inventory/operations/query_inventory/host_queries.rb +106 -0
  98. data/lib/moose_inventory/operations/query_inventory.rb +47 -0
  99. data/lib/moose_inventory/operations/remove_associations.rb +30 -18
  100. data/lib/moose_inventory/operations/remove_groups.rb +12 -12
  101. data/lib/moose_inventory/operations/remove_hosts.rb +68 -0
  102. data/lib/moose_inventory/operations/remove_variables.rb +67 -0
  103. data/lib/moose_inventory/runtime_options.rb +31 -0
  104. data/lib/moose_inventory/version.rb +3 -1
  105. data/lib/moose_inventory.rb +10 -7
  106. data/moose-inventory.gemspec +19 -35
  107. data/scripts/check.sh +1 -0
  108. data/scripts/ci/check_generated_artifacts.sh +41 -0
  109. data/scripts/ci/check_permissions.sh +2 -0
  110. data/scripts/ci/check_rubocop.sh +30 -25
  111. data/scripts/files.rb +5 -4
  112. data/spec/examples/ci_examples_spec.rb +37 -0
  113. data/spec/lib/moose_inventory/ansible_plugin_examples_spec.rb +29 -0
  114. data/spec/lib/moose_inventory/cli/application_doctor_spec.rb +50 -0
  115. data/spec/lib/moose_inventory/cli/application_import_export_spec.rb +100 -0
  116. data/spec/lib/moose_inventory/cli/application_spec.rb +25 -15
  117. data/spec/lib/moose_inventory/cli/audit_spec.rb +56 -0
  118. data/spec/lib/moose_inventory/cli/cli_spec.rb +15 -19
  119. data/spec/lib/moose_inventory/cli/console_spec.rb +98 -0
  120. data/spec/lib/moose_inventory/cli/factory_spec.rb +27 -0
  121. data/spec/lib/moose_inventory/cli/formatter_spec.rb +95 -3
  122. data/spec/lib/moose_inventory/cli/group_add_spec.rb +140 -116
  123. data/spec/lib/moose_inventory/cli/group_addchild_spec.rb +89 -35
  124. data/spec/lib/moose_inventory/cli/group_addhost_spec.rb +81 -84
  125. data/spec/lib/moose_inventory/cli/group_addvar_spec.rb +65 -68
  126. data/spec/lib/moose_inventory/cli/group_get_spec.rb +17 -33
  127. data/spec/lib/moose_inventory/cli/group_list_spec.rb +16 -38
  128. data/spec/lib/moose_inventory/cli/group_listvar_spec.rb +33 -40
  129. data/spec/lib/moose_inventory/cli/group_rm_spec.rb +136 -96
  130. data/spec/lib/moose_inventory/cli/group_rmchild_spec.rb +66 -41
  131. data/spec/lib/moose_inventory/cli/group_rmhost_spec.rb +76 -78
  132. data/spec/lib/moose_inventory/cli/group_rmvar_spec.rb +57 -63
  133. data/spec/lib/moose_inventory/cli/group_spec.rb +2 -0
  134. data/spec/lib/moose_inventory/cli/helpers_spec.rb +146 -0
  135. data/spec/lib/moose_inventory/cli/host_add_spec.rb +170 -116
  136. data/spec/lib/moose_inventory/cli/host_addgroup_spec.rb +100 -83
  137. data/spec/lib/moose_inventory/cli/host_addvar_spec.rb +92 -74
  138. data/spec/lib/moose_inventory/cli/host_get_spec.rb +14 -33
  139. data/spec/lib/moose_inventory/cli/host_list_spec.rb +41 -33
  140. data/spec/lib/moose_inventory/cli/host_listvar_spec.rb +45 -53
  141. data/spec/lib/moose_inventory/cli/host_rm_spec.rb +66 -48
  142. data/spec/lib/moose_inventory/cli/host_rmgroup_spec.rb +73 -83
  143. data/spec/lib/moose_inventory/cli/host_rmvar_spec.rb +56 -63
  144. data/spec/lib/moose_inventory/cli/host_spec.rb +2 -0
  145. data/spec/lib/moose_inventory/cli/tags_spec.rb +81 -0
  146. data/spec/lib/moose_inventory/config/config_spec.rb +41 -3
  147. data/spec/lib/moose_inventory/db/db_spec.rb +396 -36
  148. data/spec/lib/moose_inventory/db/exceptions_spec.rb +18 -0
  149. data/spec/lib/moose_inventory/db/models_spec.rb +7 -3
  150. data/spec/lib/moose_inventory/db_lifecycle_spec.rb +73 -0
  151. data/spec/lib/moose_inventory/inventory_context_spec.rb +10 -0
  152. data/spec/lib/moose_inventory/operations/add_associations_spec.rb +34 -0
  153. data/spec/lib/moose_inventory/operations/add_groups_spec.rb +15 -0
  154. data/spec/lib/moose_inventory/operations/add_hosts_spec.rb +13 -0
  155. data/spec/lib/moose_inventory/operations/add_variables_spec.rb +103 -0
  156. data/spec/lib/moose_inventory/operations/group_child_relations_spec.rb +46 -0
  157. data/spec/lib/moose_inventory/operations/import_inventory_snapshot_spec.rb +226 -0
  158. data/spec/lib/moose_inventory/operations/inventory_doctor_spec.rb +77 -0
  159. data/spec/lib/moose_inventory/operations/inventory_snapshot_spec.rb +50 -0
  160. data/spec/lib/moose_inventory/operations/operation_event_support_spec.rb +78 -0
  161. data/spec/lib/moose_inventory/operations/query_inventory_spec.rb +146 -0
  162. data/spec/lib/moose_inventory/operations/remove_associations_spec.rb +35 -0
  163. data/spec/lib/moose_inventory/operations/remove_groups_spec.rb +21 -0
  164. data/spec/lib/moose_inventory/operations/remove_hosts_spec.rb +55 -0
  165. data/spec/lib/moose_inventory/operations/remove_variables_spec.rb +83 -0
  166. data/spec/shared/shared_config_setup.rb +4 -3
  167. data/spec/spec_helper.rb +50 -40
  168. data/spec/support/cli_harness.rb +33 -0
  169. 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 './exceptions.rb'
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 = nil
16
- @models = nil
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
- attr_reader :db
20
- attr_reader :models
21
- attr_reader :exceptions
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
- create_tables
49
-
50
- # Make our models work
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
- fail('Database connection has not been established') if @db.nil?
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) do
78
- yield
79
- end
80
-
74
+ @db.transaction(savepoint: true, &)
81
75
  rescue Sequel::DatabaseError => e
82
- # We want to rescue Sqlite3::BusyException. But, sequel catches that
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
- case tries
93
- when 1..10
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._confopts[:trace] == true
111
- $stderr.puts e.full_message(highlight: false, order: :top)
112
- abort("ERROR: #{e.message}")
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 e
88
+ raise
120
89
  end
121
90
  end
122
91
 
123
92
  #--------------------
124
93
  def self.reset
125
- fail('Database connection has not been established') if @db.nil?
126
- # @debug << 'reset'
94
+ raise('Database connection has not been established') if @db.nil?
95
+
127
96
  purge
128
- create_tables
97
+ migrate_schema!
129
98
  end
130
99
 
131
100
  #===============================
132
101
 
133
- private
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
- def self.purge # rubocop:disable Metrics/AbcSize
137
- adapter = Moose::Inventory::Config._settings[:config][:db][:adapter]
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
- else
165
- @db.drop_table(:hosts, :hostvars,
166
- :groups, :groupvars, :group_hosts,
167
- if_exists: true, cascade: true)
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.create_tables # rubocop:disable Metrics/AbcSize
173
- unless @db.table_exists? :hosts
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
- unless @db.table_exists? :hostvars
181
- @db.create_table(:hostvars) do
182
- primary_key :id
183
- foreign_key :host_id
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
- unless @db.table_exists? :groups
190
- @db.create_table(:groups) do
191
- primary_key :id
192
- column :name, :text, unique: true
193
- end
194
- end
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
- unless @db.table_exists? :groups_groups
197
- @db.create_table(:groups_groups) do
198
- primary_key :id
199
- foreign_key :parent_id, :groups
200
- foreign_key :child_id, :groups
201
- end
202
- end
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
- unless @db.table_exists? :groupvars
205
- @db.create_table(:groupvars) do
206
- primary_key :id
207
- foreign_key :group_id
208
- column :name, :text
209
- column :value, :text
210
- end
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
- unless @db.table_exists? :groups_hosts
214
- @db.create_table(:groups_hosts) do
215
- primary_key :id
216
- foreign_key :host_id, :hosts
217
- foreign_key :group_id, :groups
218
- end
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
- adapter = Moose::Inventory::Config._settings[:config][:db][:adapter]
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
- fail @exceptions[:moose],
241
- "database adapter #{adapter} is not yet supported."
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 # rubocop:disable Metrics/AbcSize
229
+ def self.init_sqlite3
247
230
  require 'sqlite3'
248
231
  require 'fileutils'
232
+ init_exceptions
249
233
 
250
- # Quick check that expected keys are at least present & sensible
251
- config = Moose::Inventory::Config._settings[:config][:db]
252
- [:file].each do |key|
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) unless Dir.exist?(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
- # Quick check that expected keys are at least present
274
- config = Moose::Inventory::Config._settings[:config][:db]
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
- # Quick check that expected keys are at least present
294
- config = Moose::Inventory::Config._settings[:config][:db]
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
- fail @exceptions[:moose],
315
- "Expected key password or password_env missing in #{adapter} configuration"
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
- fail @exceptions[:moose],
321
- "Environment variable #{config[:password_env]} is not set for #{adapter} password"
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
- attr_reader :message
8
- def initialize(message)
9
- @message = message || 'An undefined Moose exception occurred'
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