Module: Moose::Inventory::DB

Extended by:
DB, SchemaMigrations
Included in:
DB
Defined in:
lib/moose_inventory/db/db.rb,
lib/moose_inventory/db/models.rb,
lib/moose_inventory/db/exceptions.rb,
lib/moose_inventory/db/schema_migrations.rb

Overview

Module for DB-related functionality

Defined Under Namespace

Modules: SchemaMigrations Classes: AuditEvent, Group, Groupvar, Host, Hostvar, MooseDBException, Tag

Constant Summary collapse

SCHEMA_VERSION =
SchemaMigrations::SCHEMA_VERSION
TABLE_DEFINITIONS =
SchemaMigrations::TABLE_DEFINITIONS
SCHEMA_MIGRATIONS =
SchemaMigrations::SCHEMA_MIGRATIONS
INDEX_DEFINITIONS =
SchemaMigrations::INDEX_DEFINITIONS
MODEL_KEYS =
{
  host: :Host,
  hostvar: :Hostvar,
  group: :Group,
  groupvar: :Groupvar,
  audit_event: :AuditEvent,
  tag: :Tag
}.freeze
BUSY_RETRY_LIMIT =
10
BUSY_RETRY_BASE_DELAY_SECONDS =
0.05
BUSY_RETRY_MAX_DELAY_SECONDS =
1.0

Instance Attribute Summary collapse

Class Method Summary collapse

Methods included from SchemaMigrations

add_index, apply_schema_indexes!, apply_schema_migration!, clean_duplicate_index_rows!, create_table, create_tables, dedupe_duplicate_rows!, duplicate_keys, index_exists?, migrate_schema!, migration_versions, record_schema_version!, reject_conflicting_duplicates!, reject_future_schema!, schema_indexes_missing?, schema_migration_artifacts_missing?, schema_migration_tables_missing?, schema_version

Instance Attribute Details

#dbObject (readonly)

Returns the value of attribute db.



22
23
24
# File 'lib/moose_inventory/db/db.rb', line 22

def db
  @db
end

#exceptionsObject (readonly)

Returns the value of attribute exceptions.



22
23
24
# File 'lib/moose_inventory/db/db.rb', line 22

def exceptions
  @exceptions
end

#modelsObject (readonly)

Returns the value of attribute models.



22
23
24
# File 'lib/moose_inventory/db/db.rb', line 22

def models
  @models
end

Class Method Details

.backup(path) ⇒ Object

Raises:

  • ()


155
156
157
158
159
160
161
162
163
164
165
# File 'lib/moose_inventory/db/db.rb', line 155

def self.backup(path)
  raise @exceptions[:moose], 'Database backup is currently supported for sqlite3 only.' unless sqlite_adapter?

  source = sqlite_file
  raise @exceptions[:moose], "SQLite database file #{source} does not exist." unless File.exist?(source)

  destination = File.expand_path(path)
  FileUtils.mkdir_p(File.dirname(destination))
  FileUtils.cp(source, destination)
  destination
end

.bind_models!Object



102
103
104
105
106
107
108
# File 'lib/moose_inventory/db/db.rb', line 102

def self.bind_models!
  Sequel::DATABASES[0] = @db
  require_relative 'models'
  @models = MODEL_KEYS.transform_values do |name|
    Moose::Inventory::DB.const_get(name)
  end
end

.busy_database_error?(error) ⇒ Boolean

Returns:

  • (Boolean)


110
111
112
# File 'lib/moose_inventory/db/db.rb', line 110

def self.busy_database_error?(error)
  error.message.include?('BusyException')
end

.busy_retry_delay(tries) ⇒ Object



114
115
116
117
# File 'lib/moose_inventory/db/db.rb', line 114

def self.busy_retry_delay(tries)
  delay = BUSY_RETRY_BASE_DELAY_SECONDS * (2**(tries - 1))
  [delay, BUSY_RETRY_MAX_DELAY_SECONDS].min
end

.config_db_settingsObject



220
221
222
# File 'lib/moose_inventory/db/db.rb', line 220

def self.config_db_settings
  Moose::Inventory::Config.db_settings
end

.connectObject




204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/moose_inventory/db/db.rb', line 204

def self.connect
  return unless @db.nil?

  case normalized_adapter
  when 'sqlite3'
    init_sqlite3
  when 'mysql'
    init_mysql
  when 'postgresql'
    init_postgresql
  else
    raise @exceptions[:moose],
          "database adapter #{normalized_adapter} is not yet supported."
  end
end

.db_password(config, adapter) ⇒ Object




285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'lib/moose_inventory/db/db.rb', line 285

def self.db_password(config, adapter)
  return config[:password] unless config[:password].nil?

  if config[:password_env].nil?
    raise @exceptions[:moose],
          "Expected key password or password_env missing in #{adapter} configuration"
  end

  password = ENV.fetch(config[:password_env].to_s, nil)
  if password.nil? || password.empty?
    raise @exceptions[:moose],
          "Environment variable #{config[:password_env]} is not set for #{adapter} password"
  end

  password
end

.ensure_required_config_keys!(config, keys, adapter) ⇒ Object



275
276
277
278
279
280
281
282
# File 'lib/moose_inventory/db/db.rb', line 275

def self.ensure_required_config_keys!(config, keys, adapter)
  keys.each do |key|
    next unless config[key].nil?

    raise @exceptions[:moose],
          "Expected key #{key} missing in #{adapter} configuration"
  end
end

.initObject




45
46
47
48
49
50
51
52
53
# File 'lib/moose_inventory/db/db.rb', line 45

def self.init
  init_exceptions
  return unless @db.nil?

  Sequel::Model.plugin :json_serializer
  connect
  migrate_schema!
  bind_models!
end

.init_exceptionsObject




62
63
64
65
# File 'lib/moose_inventory/db/db.rb', line 62

def self.init_exceptions
  @exceptions ||= {}
  @exceptions[:moose] ||= Moose::Inventory::DB::MooseDBException
end

.init_mysqlObject




246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/moose_inventory/db/db.rb', line 246

def self.init_mysql
  require 'mysql2'
  init_exceptions

  config = config_db_settings
  ensure_required_config_keys!(config, %i[host database user], 'mysql')
  password = db_password(config, 'mysql')

  @db = Sequel.mysql2(user: config[:user],
                      password: password,
                      host: config[:host],
                      database: config[:database])
end

.init_postgresqlObject




261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/moose_inventory/db/db.rb', line 261

def self.init_postgresql
  require 'pg'
  init_exceptions

  config = config_db_settings
  ensure_required_config_keys!(config, %i[host database user], 'postgresql')
  password = db_password(config, 'postgresql')

  @db = Sequel.postgres(user: config[:user],
                        password: password,
                        host: config[:host],
                        database: config[:database])
end

.init_sqlite3Object




229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/moose_inventory/db/db.rb', line 229

def self.init_sqlite3
  require 'sqlite3'
  require 'fileutils'
  init_exceptions

  config = config_db_settings
  ensure_required_config_keys!(config, [:file], 'sqlite3')
  raise("SQLite3 DB 'file' cannot be empty") if config[:file].empty?

  dbfile = File.expand_path(config[:file])
  dbdir = File.dirname(dbfile)
  FileUtils.mkdir_p(dbdir)

  @db = Sequel.sqlite(dbfile)
end

.migrate!Object



150
151
152
153
# File 'lib/moose_inventory/db/db.rb', line 150

def self.migrate!
  migrate_schema!
  status
end

.normalized_adapterObject



224
225
226
# File 'lib/moose_inventory/db/db.rb', line 224

def self.normalized_adapter
  config_db_settings[:adapter].downcase
end

.purgeObject




132
133
134
135
136
137
138
# File 'lib/moose_inventory/db/db.rb', line 132

def self.purge
  return purge_sqlite_associations if sqlite_adapter?

  @db.drop_table(:hosts, :hostvars,
                 :groups, :groupvars, :group_hosts,
                 if_exists: true, cascade: true)
end

.purge_sqlite_associationsObject



175
176
177
178
179
180
181
182
# File 'lib/moose_inventory/db/db.rb', line 175

def self.purge_sqlite_associations
  purge_sqlite_groups
  purge_sqlite_hosts
  Groupvar.all.each(&:destroy)
  Hostvar.all.each(&:destroy)
  AuditEvent.all.each(&:destroy) if @db.table_exists?(:audit_events)
  Tag.all.each(&:destroy) if @db.table_exists?(:tags)
end

.purge_sqlite_groupsObject



184
185
186
187
188
189
190
191
192
# File 'lib/moose_inventory/db/db.rb', line 184

def self.purge_sqlite_groups
  Group.all.each do |group|
    group.remove_all_hosts
    group.remove_all_groupvars
    group.remove_all_children
    group.remove_all_tags if @db.table_exists?(:groups_tags)
    group.destroy
  end
end

.purge_sqlite_hostsObject



194
195
196
197
198
199
200
201
# File 'lib/moose_inventory/db/db.rb', line 194

def self.purge_sqlite_hosts
  Host.all.each do |host|
    host.remove_all_groups
    host.remove_all_hostvars
    host.remove_all_tags if @db.table_exists?(:hosts_tags)
    host.destroy
  end
end

.resetObject




93
94
95
96
97
98
# File 'lib/moose_inventory/db/db.rb', line 93

def self.reset
  raise('Database connection has not been established') if @db.nil?

  purge
  migrate_schema!
end

.reset_runtime_stateObject



55
56
57
58
59
# File 'lib/moose_inventory/db/db.rb', line 55

def self.reset_runtime_state
  @db = nil
  @models = nil
  @exceptions = nil
end

.retry_busy_transaction(error, tries, sleeper: method(:sleep)) ⇒ Object



119
120
121
122
123
124
125
126
127
128
129
# File 'lib/moose_inventory/db/db.rb', line 119

def self.retry_busy_transaction(error, tries, sleeper: method(:sleep))
  if tries <= BUSY_RETRY_LIMIT
    warn error.message if Moose::Inventory::Config.trace_enabled?
    sleeper.call(busy_retry_delay(tries))
    return
  end

  warn('The database appears to be locked by another process, and ' \
       "did not become free after #{tries} tries. Giving up. ")
  raise error
end

.sqlite_adapter?Boolean

Returns:

  • (Boolean)


167
168
169
# File 'lib/moose_inventory/db/db.rb', line 167

def self.sqlite_adapter?
  normalized_adapter == 'sqlite3'
end

.sqlite_fileObject



171
172
173
# File 'lib/moose_inventory/db/db.rb', line 171

def self.sqlite_file
  File.expand_path(config_db_settings[:file])
end

.statusObject



140
141
142
143
144
145
146
147
148
# File 'lib/moose_inventory/db/db.rb', line 140

def self.status
  {
    adapter: normalized_adapter,
    schema_version: schema_version,
    expected_schema_version: SCHEMA_VERSION,
    tables: TABLE_DEFINITIONS.keys.to_h { |name| [name, @db.table_exists?(name)] },
    sqlite_file: sqlite_adapter? ? sqlite_file : nil
  }
end

.transactionObject




68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/moose_inventory/db/db.rb', line 68

def self.transaction(&)
  raise('Database connection has not been established') if @db.nil?

  tries = 0

  begin
    @db.transaction(savepoint: true, &)
  rescue Sequel::DatabaseError => e
    raise unless busy_database_error?(e)

    tries += 1
    retry_busy_transaction(e, tries)
    retry
  rescue @exceptions[:moose] => e
    warn 'An error occurred during a transaction, any changes have been rolled back.'

    warn e.full_message(highlight: false, order: :top) if Moose::Inventory::Config.trace_enabled?
    abort("ERROR: #{e.message}")
  rescue SystemExit, StandardError
    warn 'An error occurred during a transaction, any changes have been rolled back.'
    raise
  end
end