Module: Omen::Inquirer

Defined in:
lib/omen/inquirer.rb

Overview

The Postgres role a reading's own SELECT runs as, blind to the tables a reading is kept in. It needs no database.yml entry of its own: NOLOGIN, it is a privilege container reached with SET LOCAL ROLE and never connected as.

Constant Summary collapse

WHOEVER =

Whom to ask, since the role a host reads through is one this gem has no name for.

'SELECT current_user'
UNGRANTED =

Said on the way through installation step one, where a host has yet to declare the role.

'No %{role} connection is configured, so nothing was granted to whatever ' \
'reads through it. Run this again once config/database.yml names one.'
UNMADE =

Said where the role could not be made: a managed database never grants CREATEROLE.

'Could not make %{role}, so every reading will say this app is misconfigured. Ask ' \
'for that role, NOLOGIN, granted SELECT on every table but %{tables}.'
REFUSED =

Said per statement, since the grants, the revocations and the functions need nothing from one another and one refusal should not discard the rest.

'Skipped, refused by the database: %{statement} (%{error})'
DANGEROUS =

Said where a role that already existed is one a reading should not be able to reach through.

'%{role} holds %{held}. This gem cannot take that away without being a superuser ' \
'itself, so ask for it to be taken away.'

Class Method Summary collapse

Class Method Details

.attempted(connection, statement) ⇒ void

This method returns an undefined value.

One statement at a time, so a database that refuses one still runs the others -- and each inside a savepoint of its own, since a refusal inside a transaction refuses everything after it too, and this task is as likely to be run from a console as from a deploy.

Parameters:

  • connection (ActiveRecord::ConnectionAdapters::AbstractAdapter)

    a writing one.

  • statement (String)

    one of the statements Omen::Grants builds.



65
66
67
68
69
# File 'lib/omen/inquirer.rb', line 65

def self.attempted(connection, statement)
  connection.transaction(requires_new: true) { connection.execute statement }
rescue ActiveRecord::StatementInvalid => error
  warn REFUSED % { statement: statement.squish, error: error.message.lines.first.strip }
end

.environmentsArray<String>

Returns the environments whose databases this run should cover.

Returns:

  • (Array<String>)

    the environments whose databases this run should cover.



39
# File 'lib/omen/inquirer.rb', line 39

def self.environments = Rails.env.development? ? %w[ development test ] : [Rails.env.to_s]

.grantvoid

This method returns an undefined value.

Creates the role and the function, on every database this environment prepares.



27
28
29
30
31
32
33
34
35
36
# File 'lib/omen/inquirer.rb', line 27

def self.grant
  environments.each do |environment|
    config = ActiveRecord::Base.configurations.configs_for env_name: environment,
      name: 'primary'
    next unless config
    ActiveRecord::Tasks::DatabaseTasks.with_temporary_connection config do |connection|
      grant_on connection
    end
  end
end

.grant_on(connection) ⇒ void

This method returns an undefined value.

Warns rather than raises: a managed database never grants CREATEROLE, and a deploy that cannot make the role must still finish, having said what has to be made by hand.

Parameters:

  • connection (ActiveRecord::ConnectionAdapters::AbstractAdapter)

    a writing one.



45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/omen/inquirer.rb', line 45

def self.grant_on(connection)
  read_by = reader
  warn UNGRANTED % { role: Omen.config.reading_role } unless read_by
  members = [ read_by, connection.select_value(WHOEVER) ].compact
  Grants.statements(connection, members).each { |statement| attempted connection, statement }
  role = Omen.config.narrow_role
  return warn UNMADE % { role: role, tables: Omen.tables.to_sentence } unless
    Attributes.exists? connection

  held = Attributes.dangerous connection
  warn DANGEROUS % { role: role, held: held.to_sentence } if held.any?
  puts "Granted SELECT on #{connection.current_database} to #{role}"
end

.readerString?

Discovered rather than named: SET LOCAL ROLE needs the connecting role to be a member of this one, and the role a host's reading connection logs in as is the host's own business.

Returns:

  • (String, nil)

    the Postgres user a reading is read through, where there is one.



74
75
76
77
78
79
80
# File 'lib/omen/inquirer.rb', line 74

def self.reader
  Omen.config.record.connected_to role: Omen.config.reading_role do
    Omen.config.record.with_connection { |connection| connection.select_value WHOEVER }
  end
rescue ActiveRecord::ConnectionNotDefined, ActiveRecord::ConnectionNotEstablished
  nil
end