potluck-postgres 0.0.1 → 0.0.5

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 (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/potluck/postgres.rb +120 -42
  3. metadata +19 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5350781163dc218410b42e790bedb5c2ca246572e1334672ea9471c81efb432c
4
- data.tar.gz: fdd55c1e5b645f6f0c99df93b9761e8830776b52b32e5310cd0def002e5c4f51
3
+ metadata.gz: 667d6b357e4b021bba60a415b90f8623d9b72683ed3b5869ce87f2e43e033325
4
+ data.tar.gz: 656fd69e5541b414c9e67f750daabaf072c94fbf1237042424a3a5848cfedfa8
5
5
  SHA512:
6
- metadata.gz: 9e9475b41d894b36972aa8481eb06dfc6d1d0497d88a6841dfe92b5179bbb205db7f1cf48995df077aed00dbbc49371c553ccc5914154e15ca9c5684ed4bb9fe
7
- data.tar.gz: d6df0284807374eafa9d9e48bebed6f79f00cbeb1398f6564831c2e1be703720ca059fbc5a09e0d84819f4f63f9fa5e8ff4519fc3f113a54f6696fd069c8fe86
6
+ metadata.gz: 03b852e0ce06ed33d0c7010779eb5bd6d1804e0151df4814a3ad218a50ffcaa27daebf47e7b7288ac7755c75ef4c11b003a82516f6a383e724dbbcdc4269759c
7
+ data.tar.gz: c3cfc79e9c846ca36e2da1015871cac23928bca1291bb7ee7c05ec8b459c71c4918bf057f70516da5dec9a6ab6942a4fc9d1428af970ab79bbfb20936039f5d9
@@ -1,17 +1,68 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require('potluck')
4
+ require('sequel')
4
5
 
5
6
  module Potluck
6
- class Postgres < Dish
7
+ ##
8
+ # Error class used to wrap errors encountered while connecting to or setting up a database.
9
+ #
10
+ class PostgresError < ServiceError
11
+ attr_reader(:wrapped_error)
12
+
13
+ ##
14
+ # Creates a new instance.
15
+ #
16
+ # * +message+ - Error message.
17
+ # * +wrapped_error+ - Original error that was rescued and is being wrapped by this one (optional).
18
+ #
19
+ def initialize(message, wrapped_error = nil)
20
+ super(message)
21
+
22
+ @wrapped_error = wrapped_error
23
+ end
24
+ end
25
+
26
+ ##
27
+ # A Ruby interface for controlling and connecting to Postgres. Uses
28
+ # [Sequel](https://github.com/jeremyevans/sequel) to connect and perform automatic role and database
29
+ # creation, as well as for utility methods such as database schema migration.
30
+ #
31
+ class Postgres < Service
32
+ ROLE_NOT_FOUND_REGEX = /role .* does not exist/.freeze
33
+ DATABASE_NOT_FOUND_REGEX = /database .* does not exist/.freeze
34
+
35
+ STARTING_UP_STRING = 'the database system is starting up'
36
+ STARTING_UP_TIMEOUT = 30
37
+
38
+ CONNECTION_REFUSED_STRING = 'connection refused'
39
+ CONNECTION_REFUSED_TIMEOUT = 3
40
+
7
41
  attr_reader(:database)
8
42
 
43
+ ##
44
+ # Creates a new instance.
45
+ #
46
+ # * +config+ - Configuration hash to pass to <tt>Sequel.connect</tt>.
47
+ # * +args+ - Arguments to pass to Potluck::Service.new (optional).
48
+ #
9
49
  def initialize(config, **args)
10
50
  super(**args)
11
51
 
12
52
  @config = config
13
53
  end
14
54
 
55
+ ##
56
+ # Disconnects and stops the Postgres process.
57
+ #
58
+ def stop
59
+ disconnect
60
+ super
61
+ end
62
+
63
+ ##
64
+ # Connects to the configured Postgres database.
65
+ #
15
66
  def connect
16
67
  (tries ||= 0) && (tries += 1)
17
68
  @database = Sequel.connect(@config, logger: @logger)
@@ -21,61 +72,43 @@ module Potluck
21
72
  Sequel.synchronize { Sequel::DATABASES.delete(dud) }
22
73
  end
23
74
 
24
- if e.message =~ /role .* does not exist/ && tries == 1
75
+ message = e.message.downcase
76
+
77
+ if message =~ ROLE_NOT_FOUND_REGEX && tries == 1
25
78
  create_database_role
26
79
  create_database
27
80
  retry
28
- elsif e.message =~ /database .* does not exist/ && tries == 1
81
+ elsif message =~ DATABASE_NOT_FOUND_REGEX && tries == 1
29
82
  create_database
30
83
  retry
31
- elsif (@is_local && tries < 3) && (e.message.include?('could not connect') ||
32
- e.message.include?('the database system is starting up'))
84
+ elsif message.include?(STARTING_UP_STRING) && tries < STARTING_UP_TIMEOUT
33
85
  sleep(1)
34
86
  retry
35
- elsif e.message.include?('could not connect')
36
- abort("#{e.class}: #{e.message.strip}")
87
+ elsif message.include?(CONNECTION_REFUSED_STRING) && tries < CONNECTION_REFUSED_TIMEOUT && manage?
88
+ sleep(1)
89
+ retry
90
+ elsif message.include?(CONNECTION_REFUSED_STRING)
91
+ raise(PostgresError.new(e.message.strip, e))
37
92
  else
38
- abort("#{e.class}: #{e.message.strip}\n #{e.backtrace.join("\n ")}")
93
+ raise
39
94
  end
40
95
  end
41
96
 
97
+ ##
98
+ # Disconnects from the database if a connection was made.
99
+ #
42
100
  def disconnect
43
101
  @database&.disconnect
44
102
  end
45
103
 
46
- def create_database_role
47
- tmp_config = @config.dup
48
- tmp_config[:database] = 'postgres'
49
- tmp_config[:username] = ENV['USER']
50
- tmp_config[:password] = nil
51
-
52
- begin
53
- Sequel.connect(tmp_config, logger: @logger) do |database|
54
- database.execute("CREATE ROLE #{@config[:username]} WITH LOGIN CREATEDB REPLICATION PASSWORD "\
55
- "'#{@config[:password]}'")
56
- end
57
- rescue => e
58
- @logger.error("#{e.class}: #{e.message.strip}\n #{e.backtrace.join("\n ")}\n")
59
- abort("Could not create role '#{@config[:username]}'. Make sure database user '#{ENV['USER']}' "\
60
- 'has permission to do so, or create it manually.')
61
- end
62
- end
63
-
64
- def create_database
65
- tmp_config = @config.dup
66
- tmp_config[:database] = 'postgres'
67
-
68
- begin
69
- Sequel.connect(tmp_config, logger: @logger) do |database|
70
- database.execute("CREATE DATABASE #{@config[:database]}")
71
- end
72
- rescue => e
73
- @logger.error("#{e.class}: #{e.message.strip}\n #{e.backtrace.join("\n ")}\n")
74
- abort("Could not create database '#{@config[:database]}'. Make sure database user "\
75
- "'#{@config[:username]}' has permission to do so, or create it manually.")
76
- end
77
- end
78
-
104
+ ##
105
+ # Runs database migrations by way of Sequel's migration extension. Migration files must use the
106
+ # timestamp naming strategy as opposed to integers.
107
+ #
108
+ # * +dir+ - Directory where migration files are located.
109
+ # * +steps+ - Number of steps forward or backward to migrate from the current migration, otherwise will
110
+ # migrate to latest (optional).
111
+ #
79
112
  def migrate(dir, steps = nil)
80
113
  return unless File.directory?(dir)
81
114
 
@@ -83,7 +116,7 @@ module Potluck
83
116
 
84
117
  # Suppress Sequel schema migration table queries.
85
118
  original_level = @logger.level
86
- @logger.level = Logger::WARN
119
+ @logger.level = Logger::WARN if @logger.level == Logger::INFO
87
120
 
88
121
  args = [Sequel::Model.db, dir, {allow_missing_migration_files: true}]
89
122
  migrator = Sequel::TimestampMigrator.new(*args)
@@ -106,10 +139,55 @@ module Potluck
106
139
  migrator = Sequel::TimestampMigrator.new(*args)
107
140
  @logger.level = original_level
108
141
  migrator.run
142
+ ensure
143
+ @logger.level = original_level if original_level
109
144
  end
110
145
 
111
146
  private
112
147
 
148
+ ##
149
+ # Attempts to connect to the 'postgres' database as the system user with no password and create the
150
+ # configured role. Useful in development.
151
+ #
152
+ def create_database_role
153
+ tmp_config = @config.dup
154
+ tmp_config[:database] = 'postgres'
155
+ tmp_config[:username] = ENV['USER']
156
+ tmp_config[:password] = nil
157
+
158
+ begin
159
+ Sequel.connect(tmp_config, logger: @logger) do |database|
160
+ database.execute("CREATE ROLE #{@config[:username]} WITH LOGIN CREATEDB REPLICATION PASSWORD "\
161
+ "'#{@config[:password]}'")
162
+ end
163
+ rescue => e
164
+ raise(PostgresError.new("Database role #{@config[:username].inspect} could not be created using "\
165
+ "system user #{tmp_config[:username].inspect}. Please create the role manually.", e))
166
+ end
167
+ end
168
+
169
+ ##
170
+ # Attempts to connect to the 'postgres' database with the configured user and password and create the
171
+ # configured database. Useful in development.
172
+ #
173
+ def create_database
174
+ tmp_config = @config.dup
175
+ tmp_config[:database] = 'postgres'
176
+
177
+ begin
178
+ Sequel.connect(tmp_config, logger: @logger) do |database|
179
+ database.execute("CREATE DATABASE #{@config[:database]}")
180
+ end
181
+ rescue => e
182
+ raise(PostgresError.new("Database #{@config[:database].inspect} could not be created by "\
183
+ "connecting to system database #{tmp_config[:database].inspect}. Please create the database "\
184
+ 'manually.', e))
185
+ end
186
+ end
187
+
188
+ ##
189
+ # Content of the launchctl plist file.
190
+ #
113
191
  def self.plist
114
192
  super(
115
193
  <<~EOS
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: potluck-postgres
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nate Pickens
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-03-27 00:00:00.000000000 Z
11
+ date: 2021-12-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: potluck
@@ -16,14 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 0.0.1
19
+ version: 0.0.5
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 0.0.1
26
+ version: 0.0.5
27
+ - !ruby/object:Gem::Dependency
28
+ name: pg
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.2'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: sequel
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -105,7 +119,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
105
119
  - !ruby/object:Gem::Version
106
120
  version: '0'
107
121
  requirements: []
108
- rubygems_version: 3.2.3
122
+ rubygems_version: 3.2.32
109
123
  signing_key:
110
124
  specification_version: 4
111
125
  summary: A Ruby manager for Postgres.