pgchief 0.5.3 → 0.6.0

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +17 -26
  3. data/.rubocop_todo.yml +39 -0
  4. data/CHANGELOG.md +33 -0
  5. data/README.md +55 -10
  6. data/Rakefile +8 -8
  7. data/config/pgchief.toml +6 -1
  8. data/exe/pgchief +1 -1
  9. data/lib/pgchief/cli.rb +54 -6
  10. data/lib/pgchief/command/config_create.rb +2 -2
  11. data/lib/pgchief/command/database_backup.rb +8 -4
  12. data/lib/pgchief/command/database_drop.rb +2 -2
  13. data/lib/pgchief/command/database_privileges_grant.rb +1 -1
  14. data/lib/pgchief/command/database_restore.rb +3 -3
  15. data/lib/pgchief/command/quick_backup.rb +15 -0
  16. data/lib/pgchief/command/quick_restore.rb +20 -0
  17. data/lib/pgchief/command/retrieve_connection_string.rb +1 -1
  18. data/lib/pgchief/command/s3_upload.rb +3 -3
  19. data/lib/pgchief/command/store_connection_string.rb +1 -1
  20. data/lib/pgchief/command/user_create.rb +1 -1
  21. data/lib/pgchief/command/user_drop.rb +1 -1
  22. data/lib/pgchief/config/s3.rb +1 -1
  23. data/lib/pgchief/config.rb +48 -15
  24. data/lib/pgchief/connection_string.rb +5 -5
  25. data/lib/pgchief/database/backups.rb +4 -4
  26. data/lib/pgchief/database.rb +4 -4
  27. data/lib/pgchief/prompt/backup_database.rb +1 -1
  28. data/lib/pgchief/prompt/base.rb +3 -3
  29. data/lib/pgchief/prompt/create_database.rb +1 -1
  30. data/lib/pgchief/prompt/create_user.rb +2 -2
  31. data/lib/pgchief/prompt/database_management.rb +7 -7
  32. data/lib/pgchief/prompt/drop_database.rb +1 -1
  33. data/lib/pgchief/prompt/drop_user.rb +1 -1
  34. data/lib/pgchief/prompt/grant_database_privileges.rb +2 -2
  35. data/lib/pgchief/prompt/restore_database.rb +9 -4
  36. data/lib/pgchief/prompt/start.rb +5 -5
  37. data/lib/pgchief/prompt/user_management.rb +7 -7
  38. data/lib/pgchief/prompt/view_database_connection_string.rb +3 -3
  39. data/lib/pgchief/user.rb +3 -3
  40. data/lib/pgchief/version.rb +1 -1
  41. data/lib/pgchief.rb +59 -44
  42. metadata +6 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8c695989f5431e21f2c33b016b34a7197fb5cca9ea62592391fab10fcdbfe385
4
- data.tar.gz: 195ab95c85943e54d41455ae25b1458aa538d6bc4b6319685b609de7549504f9
3
+ metadata.gz: 142a11a0be4792a8b0c428c25119c964fb899564db0f6ace3464fef0409017c6
4
+ data.tar.gz: d62cd7fd0b58525300e25065b3fff56b1e9bd157af9058f335c5436c88e0090f
5
5
  SHA512:
6
- metadata.gz: 352d1edab7569b64483b3abf178297cf2a213ff1c24dfdc40f39ecd8548a06acfffd650ae30a7284fba34368c15e7d58c09e9f3153e9cfc8657613fa01fe4da0
7
- data.tar.gz: e6e5f42192398383a2b8bc481c6a2b5f37722eef72111a04e2250a84bd1ac4125a9e783123ea1b3d4d64d2c3721a177482b3c83fd1bc656cc4d9b564ac2af4fc
6
+ metadata.gz: c02ad41656562151ac067e33a8a61bf8c3c39a915d32b93eda8ed76df0b601bbc4b3bfd8ab5c9979492ae0bb3b543c1e806fbcc9dbfe6b5e9452111662415336
7
+ data.tar.gz: 9cc987456a024dd1abaa656b2a725152a3e16ec7f345ab8723e3d912bcae6b213b3329e383b73bf23491826f9b8f9769d1e36d67d5688d5ad53f1bc829b5a907
data/.rubocop.yml CHANGED
@@ -1,34 +1,25 @@
1
- require:
2
- - rubocop-performance
3
- - rubocop-rake
4
- - rubocop-rspec
1
+ inherit_from: .rubocop_todo.yml
2
+
3
+ plugins:
4
+ - rubocop-rake
5
+ - rubocop-rspec
5
6
 
6
7
  AllCops:
7
- TargetRubyVersion: 3.0
8
8
  NewCops: enable
9
9
 
10
- Style/StringLiterals:
11
- EnforcedStyle: double_quotes
12
-
13
- Style/StringLiteralsInInterpolation:
14
- EnforcedStyle: double_quotes
15
-
16
- Metrics/BlockLength:
10
+ Style/Documentation:
17
11
  Exclude:
18
- - 'spec/**/*'
19
- - '*.gemspec'
20
-
21
- Lint/MixedRegexpCaptureTypes:
22
- Enabled: false
12
+ - 'lib/pgchief.rb'
13
+ - 'spec/**/*.rb'
23
14
 
24
- RSpec/MultipleExpectations:
25
- Max: 5
26
-
27
- RSpec/ExampleLength:
28
- Max: 20
15
+ Metrics/AbcSize:
16
+ Exclude:
17
+ - 'lib/pgchief/config.rb'
29
18
 
30
- Lint/MissingSuper:
31
- Enabled: false
19
+ Metrics/MethodLength:
20
+ Exclude:
21
+ - 'lib/pgchief/config.rb'
32
22
 
33
- Metrics/ParameterLists:
34
- Enabled: false
23
+ Metrics/CyclomaticComplexity:
24
+ Exclude:
25
+ - 'lib/pgchief/config.rb'
data/.rubocop_todo.yml ADDED
@@ -0,0 +1,39 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2025-03-25 01:44:14 UTC using RuboCop version 1.74.0.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 2
10
+ # Configuration parameters: AllowedParentClasses.
11
+ Lint/MissingSuper:
12
+ Exclude:
13
+ - 'lib/pgchief/command/database_privileges_grant.rb'
14
+ - 'lib/pgchief/command/retrieve_connection_string.rb'
15
+
16
+ # Offense count: 2
17
+ Lint/MixedRegexpCaptureTypes:
18
+ Exclude:
19
+ - 'lib/pgchief/config/s3.rb'
20
+ - 'lib/pgchief/connection_string.rb'
21
+
22
+ # Offense count: 1
23
+ # Configuration parameters: CountKeywordArgs, MaxOptionalParameters.
24
+ Metrics/ParameterLists:
25
+ Max: 6
26
+
27
+ # Offense count: 12
28
+ # Configuration parameters: CountAsOne.
29
+ RSpec/ExampleLength:
30
+ Max: 9
31
+
32
+ # Offense count: 15
33
+ RSpec/MultipleExpectations:
34
+ Max: 5
35
+
36
+ # Offense count: 4
37
+ # Configuration parameters: AllowSubject.
38
+ RSpec/MultipleMemoizedHelpers:
39
+ Max: 6
data/CHANGELOG.md CHANGED
@@ -13,6 +13,39 @@ and this project will try its best to adhere to [Semantic Versioning](https://se
13
13
 
14
14
  ### Fixes
15
15
 
16
+ ## [0.6.0]
17
+
18
+ ### Additions
19
+
20
+ * Adds `--backup database_name` option to cli to quickly backup a provided database.
21
+
22
+ ### Changes
23
+
24
+ * Update restore command to only act on objects `--if-exists` (if they exist)
25
+ * Allow for config to be set via ENV variables
26
+ * Don't raise error when a database doesn't exist when a drop is attempted - just notify
27
+
28
+ ### Fixes
29
+
30
+ * Fix method signature in `Database::Backups` - `remote` should be a kwarg and should
31
+ pass it along as such.
32
+ * When backing up a database, we add the db name to the end of the pg connection string.
33
+ In cases where that DATABASE_URL already has the database name tacked on at the end,
34
+ make sure it's only there once.
35
+
36
+ ## [0.5.4]
37
+
38
+ ### Additions
39
+
40
+ * Improve CLI usability with `--version` and `--help` flags.
41
+ * Adds `--remote-backup` and `--remote-restore` CLI options.
42
+ * Modifies `DatabaseRestore` and prompt flows to conditionally operate on remote backups.
43
+ * Adds `--restore database_name` option to cli to quickly restore latest backup for a provided database.
44
+
45
+ ### Changes
46
+
47
+ * Updates the config loader to respect CLI flags and TOML values for remote_backup and remote_restore.
48
+
16
49
  ## [0.5.3]
17
50
 
18
51
  ### Additions
data/README.md CHANGED
@@ -24,7 +24,7 @@ below for the feature check-list and current progress.
24
24
  ## Usage
25
25
 
26
26
  ```sh
27
- # System dependency `libpq-dev` must be installed
27
+ # System dependencies `build-essential` and `libpq-dev` must be installed
28
28
  gem install pgchief
29
29
 
30
30
  # To initialize the config file at `~/.config/pgchief/config.toml`:
@@ -38,9 +38,26 @@ pgchief --init
38
38
  # export DATABASE_URL=postgresql://postgres:password@postgres.local:5432
39
39
 
40
40
  pgchief
41
+
42
+ # Full usage:
43
+
44
+ $ pgchief --help
45
+ Options:
46
+ -b, --backup string Quickly backup specific database. Pass name of db.
47
+ -h, --help Print usage.
48
+ -i, --init Initialize the TOML configuration file.
49
+ --remote-backup Backup a database to a remote location.
50
+ --remote-restore Restore a database from a remote backup.
51
+ -r, --restore string Quickly restore specific database. Pass name of db.
52
+ -v, --version Show the version.
41
53
  ```
42
54
 
43
- ## Config
55
+ > [!Note]
56
+ > Prompts accept both `↑` and `↓` arrows, as well as `j` and `k`.
57
+ > Pressing the `esc` key at any point amidst a prompt will exit out of the
58
+ > program.
59
+
60
+ ## Config via File
44
61
 
45
62
  Format of `~/.config/pgchief/config.toml`
46
63
 
@@ -53,14 +70,42 @@ backup_dir = "~/.pgchief/backups"
53
70
 
54
71
  # ** OPTIONAL **
55
72
 
56
- # Location of encrypted database connection strings
57
- # credentials_file = "~/.pgchief/credentials"
73
+ # Location of saved database connection strings
74
+ # credentials_file = "~/.config/pgchief/credentials"
75
+
76
+ # S3 config
77
+ # ---------
78
+ # s3_key = ""
79
+ # s3_secret = ""
80
+ # s3_region = "us-east-1"
81
+ # s3_objects_path = "s3://bucket-name/database-backups/"
82
+
83
+ # Backup and restore from remote locations?
84
+ # remote_restore = false # default: false
85
+ # remote_backup = false # default: false
58
86
  ```
59
87
 
60
- Note:
88
+ ### OR ... Config via Environment Variables
89
+
90
+ The following environment variables will be picked up in the absence of a config
91
+ file.
92
+
93
+ ```env
94
+ # Required
95
+ DATABASE_URL
96
+
97
+ # Optional
98
+ AWS_ACCESS_KEY_ID or AWS_ACCESS_KEY
99
+ AWS_SECRET_ACCESS_KEY or AWS_SECRET_KEY
100
+ AWS_DEFAULT_REGION or AWS_REGION
101
+ S3_BACKUPS_PATH
102
+ ```
61
103
 
62
- 1. Prompts accept both `↑` and `↓` arrows, as well as `j` and `k`.
63
- 2. Pressing the `esc` key at any point amidst a prompt will exit out of the program.
104
+ > [!IMPORTANT]
105
+ > Backup files must be named starting with `[Database Name]-` in order for the
106
+ > file(s) to be found for restoration. Eg: A database named
107
+ > `project_production` that has backups named `project_production-20250325.tar`
108
+ > will be picked up and given the option to restore.
64
109
 
65
110
  ## Development of the gem
66
111
 
@@ -135,7 +180,7 @@ Give "rando-username" access to database(s):
135
180
  * [x] Back up database to S3
136
181
  * [x] Restore local database
137
182
  * [x] Restore remote database @ S3
138
- * [ ] Quickly back up via command line option
139
- * [ ] Quickly restore via command line option
183
+ * [x] Quickly back up via command line option
184
+ * [x] Quickly restore via command line option
140
185
  * [ ] Task for inclusion in a Rakefile
141
- * [ ] Support environment variables in config
186
+ * [x] Support environment variables in config
data/Rakefile CHANGED
@@ -1,19 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "bundler/gem_tasks"
4
- require "rspec/core/rake_task"
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
5
 
6
6
  RSpec::Core::RakeTask.new(:spec)
7
7
 
8
8
  # Set up Rubocop tasks
9
- require "rubocop/rake_task"
9
+ require 'rubocop/rake_task'
10
10
  RuboCop::RakeTask.new(:rubocop) do |t|
11
11
  t.options = [
12
- "--autocorrect-all",
13
- "--cache=true",
14
- "--display-cop-names",
15
- "--display-time",
16
- "--parallel"
12
+ '--autocorrect-all',
13
+ '--cache=true',
14
+ '--display-cop-names',
15
+ '--display-time',
16
+ '--parallel'
17
17
  ]
18
18
  end
19
19
 
data/config/pgchief.toml CHANGED
@@ -9,8 +9,13 @@ backup_dir = "~/.config/pgchief/backups"
9
9
  # Location of saved database connection strings
10
10
  # credentials_file = "~/.config/pgchief/credentials"
11
11
 
12
- # S3 config - if present, will back up to S3 instead of local filesystem
12
+ # S3 config
13
+ # ---------
13
14
  # s3_key = ""
14
15
  # s3_secret = ""
15
16
  # s3_region = "us-east-1"
16
17
  # s3_objects_path = "s3://bucket-name/database-backups/"
18
+
19
+ # Backup and restore from remote locations?
20
+ # remote_restore = false # default: false
21
+ # remote_backup = false # default: false
data/exe/pgchief CHANGED
@@ -5,7 +5,7 @@
5
5
  libdir = "#{__dir__}/../lib"
6
6
  $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
7
7
 
8
- require "pgchief"
8
+ require 'pgchief'
9
9
 
10
10
  cli = Pgchief::Cli.new
11
11
  cli.parse(ARGV)
data/lib/pgchief/cli.rb CHANGED
@@ -5,18 +5,66 @@ module Pgchief
5
5
  class Cli
6
6
  include TTY::Option
7
7
 
8
+ banner 'Usage: pgchief [OPTIONS]'
9
+
8
10
  option :init do
9
- short "-i"
10
- long "--init"
11
- desc "Initialize the TOML configuration file"
11
+ short '-i'
12
+ long '--init'
13
+ desc 'Initialize the TOML configuration file.'
14
+ end
15
+
16
+ option :version do
17
+ short '-v'
18
+ long '--version'
19
+ desc 'Show the version.'
20
+ end
21
+
22
+ option :'remote-restore' do
23
+ long '--remote-restore'
24
+ desc 'Restore a database from a remote backup.'
25
+ end
26
+
27
+ option :'remote-backup' do
28
+ long '--remote-backup'
29
+ desc 'Backup a database to a remote location.'
30
+ end
31
+
32
+ option :restore do
33
+ short '-r'
34
+ long '--restore string'
35
+ arity 1
36
+ desc 'Quickly restore specific database. Pass name of db.'
37
+ end
38
+
39
+ option :backup do
40
+ short '-b'
41
+ long '--backup string'
42
+ arity 1
43
+ desc 'Quickly backup specific database. Pass name of db.'
44
+ end
45
+
46
+ flag :help do
47
+ short '-h'
48
+ long '--help'
49
+ desc 'Print usage.'
12
50
  end
13
51
 
14
- def run
15
- if params[:init]
52
+ def run # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
53
+ if params[:help]
54
+ print help
55
+ elsif params[:init]
16
56
  Pgchief::Command::ConfigCreate.call
57
+ elsif params[:version]
58
+ puts Pgchief::VERSION
59
+ elsif params[:restore]
60
+ puts Pgchief::Command::QuickRestore.call(params, params[:restore])
61
+ elsif params[:backup]
62
+ puts Pgchief::Command::QuickBackup.call(params, params[:backup])
17
63
  else
18
- Pgchief::Prompt::Start.call
64
+ Pgchief::Prompt::Start.call(params)
19
65
  end
66
+ rescue Pgchief::Error => e
67
+ puts e.message
20
68
  end
21
69
  end
22
70
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "fileutils"
3
+ require 'fileutils'
4
4
 
5
5
  module Pgchief
6
6
  module Command
@@ -13,7 +13,7 @@ module Pgchief
13
13
  def call(dir: "#{Dir.home}/.config/pgchief")
14
14
  return if File.exist?("#{dir}/config.toml")
15
15
 
16
- template = File.join(__dir__, "..", "..", "..", "config", "pgchief.toml")
16
+ template = File.join(__dir__, '..', '..', '..', 'config', 'pgchief.toml')
17
17
  FileUtils.mkdir_p(dir)
18
18
  FileUtils.cp(template, "#{dir}/config.toml")
19
19
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "forwardable"
3
+ require 'forwardable'
4
4
 
5
5
  module Pgchief
6
6
  module Command
@@ -35,11 +35,15 @@ module Pgchief
35
35
  private
36
36
 
37
37
  def backup!
38
- `pg_dump -Fc #{Pgchief::Config.pgurl}/#{database} -f #{local_location}`
38
+ # if the provided `pgurl` already has a database at the tail
39
+ # end of the string, remove it. We'll explicitly add it below.
40
+ pgurl = Pgchief::Config.pgurl.gsub(%r{/[a-zA-Z\-_]*$}, '')
41
+
42
+ `pg_dump -Fc #{pgurl}/#{database} -f #{local_location}`
39
43
  end
40
44
 
41
45
  def check_backup!
42
- raise Pgchief::Errors::BackupError, "Backup file has 0 bytes" unless File.size?(local_location)
46
+ raise Pgchief::Errors::BackupError, 'Backup file has 0 bytes' unless File.size?(local_location)
43
47
  end
44
48
 
45
49
  def db_exists?
@@ -53,7 +57,7 @@ module Pgchief
53
57
 
54
58
  def local_location
55
59
  @local_location ||= begin
56
- timestamp = Time.now.strftime("%Y%m%d%H%M%S")
60
+ timestamp = Time.now.strftime('%Y%m%d%H%M%S')
57
61
  "#{Pgchief::Config.backup_dir}#{database}-#{timestamp}.dump"
58
62
  end
59
63
  end
@@ -8,10 +8,10 @@ module Pgchief
8
8
 
9
9
  def call
10
10
  @database = params.first
11
- raise Pgchief::Errors::DatabaseMissingError unless db_exists?
12
11
 
13
- conn.exec("DROP DATABASE #{database}")
12
+ return "Database '#{database}' does not exist." unless db_exists?
14
13
 
14
+ conn.exec("DROP DATABASE #{database}")
15
15
  "Database '#{database}' dropped successfully!"
16
16
  rescue PG::Error => e
17
17
  "Error: #{e.message}"
@@ -19,7 +19,7 @@ module Pgchief
19
19
  store_credentials!
20
20
  end
21
21
 
22
- "Privileges granted to #{username} on #{@databases.join(", ")}"
22
+ "Privileges granted to #{username} on #{@databases.join(', ')}"
23
23
  end
24
24
 
25
25
  private
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "forwardable"
3
+ require 'forwardable'
4
4
 
5
5
  module Pgchief
6
6
  module Command
@@ -17,7 +17,7 @@ module Pgchief
17
17
  @filename = params.last
18
18
  raise Pgchief::Errors::DatabaseMissingError unless db_exists?
19
19
 
20
- download! if configured?
20
+ download! if Pgchief::Config.remote_restore
21
21
  restore!
22
22
 
23
23
  "Database '#{database}' restored from #{filename}"
@@ -38,7 +38,7 @@ module Pgchief
38
38
  end
39
39
 
40
40
  def restore!
41
- `pg_restore --clean --no-owner --dbname=#{Pgchief::Config.pgurl}/#{database} #{local_location}`
41
+ `pg_restore --clean --if-exists --no-owner --dbname=#{Pgchief::Config.pgurl}/#{database} #{local_location}`
42
42
  end
43
43
 
44
44
  def db_exists?
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pgchief
4
+ module Command
5
+ # Command object to quickly restore the latest backup for a database
6
+ class QuickBackup < Base
7
+ def call
8
+ database = params.last
9
+ Pgchief::Config.load_config!(params.first)
10
+ Pgchief::Config.set_up_file_structure!
11
+ Pgchief::Command::DatabaseBackup.call(database)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pgchief
4
+ module Command
5
+ # Command object to quickly restore the latest backup for a database
6
+ class QuickRestore < Base
7
+ def call
8
+ database = params.last
9
+ Pgchief::Config.load_config!(params.first)
10
+ Pgchief::Config.set_up_file_structure!
11
+
12
+ filename = Pgchief::Database
13
+ .backups_for(database, remote: Pgchief::Config.remote_restore)
14
+ .first
15
+
16
+ Pgchief::Command::DatabaseRestore.call(database, filename)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -17,7 +17,7 @@ module Pgchief
17
17
  @connection_string = line if regex.match?(line)
18
18
  end
19
19
 
20
- @connection_string.nil? ? "No connection string found" : @connection_string
20
+ @connection_string.nil? ? 'No connection string found' : @connection_string
21
21
  end
22
22
 
23
23
  private
@@ -15,9 +15,9 @@ module Pgchief
15
15
  s3.client.put_object(
16
16
  bucket: s3.bucket,
17
17
  key: "#{s3.path}#{file_name}",
18
- body: File.open(local_location, "rb"),
19
- acl: "private",
20
- content_type: "application/octet-stream"
18
+ body: File.open(local_location, 'rb'),
19
+ acl: 'private',
20
+ content_type: 'application/octet-stream'
21
21
  )
22
22
  end
23
23
 
@@ -15,7 +15,7 @@ module Pgchief
15
15
  end
16
16
 
17
17
  def call
18
- File.open(Config.credentials_file, "a") do |file|
18
+ File.open(Config.credentials_file, 'a') do |file|
19
19
  file.puts @connection_string
20
20
  end
21
21
  end
@@ -44,7 +44,7 @@ module Pgchief
44
44
  end
45
45
 
46
46
  def user_options
47
- USER_OPTIONS.join(" ")
47
+ USER_OPTIONS.join(' ')
48
48
  end
49
49
 
50
50
  def connection_string
@@ -48,7 +48,7 @@ module Pgchief
48
48
  AND datname NOT IN ('postgres', 'template1', 'template0')
49
49
  SQL
50
50
 
51
- results.map { |row| row["datname"] }
51
+ results.map { |row| row['datname'] }
52
52
  end
53
53
  end
54
54
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "forwardable"
3
+ require 'forwardable'
4
4
 
5
5
  module Pgchief
6
6
  class Config
@@ -1,25 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "toml-rb"
3
+ require 'toml-rb'
4
4
 
5
5
  module Pgchief
6
6
  # Class to store configuration settings
7
7
  class Config
8
+ DEFAULT_CONFIG = "#{Dir.home}/.config/pgchief/config.toml".freeze
9
+
8
10
  class << self
9
11
  attr_accessor \
10
12
  :s3_key,
11
13
  :s3_secret,
12
- :s3_region
14
+ :s3_region,
15
+ :remote_restore,
16
+ :remote_backup
13
17
 
14
18
  attr_writer :pgurl
15
19
 
16
20
  attr_reader \
17
21
  :s3_objects_path,
18
22
  :backup_dir,
19
- :credentials_file
23
+ :credentials_file,
24
+ :toml_file
25
+
26
+ # explicitly define getter so that pgurl is always available if
27
+ # load_config! is not run.
28
+ def pgurl
29
+ ENV.fetch('DATABASE_URL', ENV.fetch('DB_URL', @pgurl))
30
+ end
20
31
 
21
- def load_config!(toml_file = "#{Dir.home}/.config/pgchief/config.toml") # rubocop:disable Metrics/AbcSize
22
- config = TomlRB.load_file(toml_file, symbolize_keys: true)
32
+ def load_config!(params, toml_file = DEFAULT_CONFIG)
33
+ @toml_file = toml_file
34
+ config = toml_config || env_config
23
35
  self.backup_dir = config[:backup_dir]
24
36
  self.credentials_file = config[:credentials_file]
25
37
  self.pgurl = config[:pgurl]
@@ -27,7 +39,13 @@ module Pgchief
27
39
  self.s3_secret = config[:s3_secret]
28
40
  self.s3_region = config[:s3_region]
29
41
  self.s3_objects_path = config[:s3_objects_path] || config[:s3_path_prefix]
30
- rescue Errno::ENOENT
42
+ self.remote_restore = params[:'remote-restore'] == true ||
43
+ params[:'local-restore'] == false ||
44
+ config[:remote_restore]
45
+ self.remote_backup = params[:'remote-backup'] == true ||
46
+ params[:'local-backup'] == false ||
47
+ config[:remote_backup]
48
+ rescue Pgchief::Errors::ConfigMissingError
31
49
  puts config_missing_error(toml_file)
32
50
  end
33
51
 
@@ -35,20 +53,16 @@ module Pgchief
35
53
  @s3 ||= Pgchief::Config::S3.new(self)
36
54
  end
37
55
 
38
- def pgurl
39
- ENV.fetch("DATABASE_URL", @pgurl)
40
- end
41
-
42
56
  def backup_dir=(value)
43
- @backup_dir = value ? "#{value.chomp("/")}/".gsub("~", Dir.home) : "/tmp/"
57
+ @backup_dir = value ? "#{value.chomp('/')}/".gsub('~', Dir.home) : '/tmp/'
44
58
  end
45
59
 
46
60
  def s3_objects_path=(value)
47
- @s3_objects_path = value ? "#{value.chomp("/")}/" : nil
61
+ @s3_objects_path = value ? "#{value.chomp('/')}/" : nil
48
62
  end
49
63
 
50
64
  def credentials_file=(value)
51
- @credentials_file = value&.gsub("~", Dir.home)
65
+ @credentials_file = value&.gsub('~', Dir.home)
52
66
  end
53
67
 
54
68
  def set_up_file_structure!
@@ -60,9 +74,28 @@ module Pgchief
60
74
 
61
75
  private
62
76
 
77
+ def toml_config
78
+ return unless File.exist?(toml_file.to_s)
79
+
80
+ TomlRB.load_file(toml_file, symbolize_keys: true)
81
+ end
82
+
83
+ def env_config
84
+ pgurl = ENV.fetch('DATABASE_URL', ENV.fetch('DB_URL', nil))
85
+ raise Pgchief::Errors::ConfigMissingError if pgurl.nil?
86
+
87
+ {
88
+ pgurl: pgurl,
89
+ s3_key: ENV.fetch('AWS_ACCESS_KEY_ID', ENV.fetch('AWS_ACCESS_KEY', nil)),
90
+ s3_secret: ENV.fetch('AWS_SECRET_ACCESS_KEY', ENV.fetch('AWS_SECRET_KEY', nil)),
91
+ s3_region: ENV.fetch('AWS_DEFAULT_REGION', ENV.fetch('AWS_REGION', nil)),
92
+ s3_objects_path: ENV.fetch('S3_BACKUPS_PATH', nil)
93
+ }
94
+ end
95
+
63
96
  def config_missing_error(toml_file)
64
- "You must create a config file at #{toml_file}.\n" \
65
- "run `pgchief --init` to create it."
97
+ "You must create a config file at #{toml_file || DEFAULT_CONFIG}.\n" \
98
+ 'run `pgchief --init` to create it.'
66
99
  end
67
100
  end
68
101
  end
@@ -9,7 +9,7 @@ module Pgchief
9
9
  :?(?<password>[^@]*)?
10
10
  @?(?<host>[^:]*)?
11
11
  :?(?<port>\d+)?
12
- /?(?<database>[^\?]*)?
12
+ /?(?<database>[^?]*)?
13
13
  \z}
14
14
 
15
15
  attr_reader :database_url
@@ -39,19 +39,19 @@ module Pgchief
39
39
  end
40
40
 
41
41
  def username
42
- @username || ("" if matched[:host].empty? && !matched[:username].empty?) || matched[:username] || ""
42
+ @username || ('' if matched[:host].empty? && !matched[:username].empty?) || matched[:username] || ''
43
43
  end
44
44
 
45
45
  def password
46
- @password || matched[:password] || ""
46
+ @password || matched[:password] || ''
47
47
  end
48
48
 
49
49
  def port
50
- @port || matched[:port] || "5432"
50
+ @port || matched[:port] || '5432'
51
51
  end
52
52
 
53
53
  def database
54
- @database || matched[:database] || ""
54
+ @database || matched[:database] || ''
55
55
  end
56
56
 
57
57
  private
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "forwardable"
3
+ require 'forwardable'
4
4
 
5
5
  module Pgchief
6
6
  class Database
@@ -10,13 +10,13 @@ module Pgchief
10
10
 
11
11
  def_delegators :s3, :bucket, :path, :client
12
12
 
13
- def self.for(database, remote)
14
- new(database, remote).for
13
+ def self.for(database, remote:)
14
+ new(database, remote: remote).for
15
15
  end
16
16
 
17
17
  attr_reader :database, :remote
18
18
 
19
- def initialize(database, remote)
19
+ def initialize(database, remote:)
20
20
  @database = database
21
21
  @remote = remote
22
22
  end
@@ -1,16 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "pg"
3
+ require 'pg'
4
4
 
5
5
  module Pgchief
6
6
  # Database information and operations
7
7
  class Database
8
8
  def self.all
9
9
  conn = PG.connect(Pgchief::Config.pgurl)
10
- result = conn.exec("SELECT datname FROM pg_database WHERE datistemplate = false")
10
+ result = conn.exec('SELECT datname FROM pg_database WHERE datistemplate = false')
11
11
  result
12
- .map { |row| row["datname"] }
13
- .reject { |name| name == "postgres" }
12
+ .map { |row| row['datname'] }
13
+ .reject { |name| name == 'postgres' }
14
14
  ensure
15
15
  conn.close
16
16
  end
@@ -5,7 +5,7 @@ module Pgchief
5
5
  # Class to prompt for which database to backup
6
6
  class BackupDatabase < Base
7
7
  def call
8
- database = prompt.select("Which database needs backing up?", Pgchief::Database.all)
8
+ database = prompt.select('Which database needs backing up?', Pgchief::Database.all)
9
9
  result = Pgchief::Command::DatabaseBackup.call(database)
10
10
 
11
11
  prompt.say result
@@ -20,7 +20,7 @@ module Pgchief
20
20
 
21
21
  def klassify(scope, words)
22
22
  Object.const_get([
23
- "Pgchief", "::", scope.capitalize, "::",
23
+ 'Pgchief', '::', scope.capitalize, '::',
24
24
  words.split.map(&:capitalize)
25
25
  ].flatten.join)
26
26
  end
@@ -33,8 +33,8 @@ module Pgchief
33
33
  def prompt
34
34
  @prompt ||= TTY::Prompt.new.tap do |p|
35
35
  p.on(:keypress) do |event|
36
- p.trigger(:keydown) if event.value == "j"
37
- p.trigger(:keyup) if event.value == "k"
36
+ p.trigger(:keydown) if event.value == 'j'
37
+ p.trigger(:keyup) if event.value == 'k'
38
38
  end
39
39
 
40
40
  p.on(:keyescape) do
@@ -5,7 +5,7 @@ module Pgchief
5
5
  # Class to ask for database name, in order to create it
6
6
  class CreateDatabase < Base
7
7
  def call
8
- database = prompt.ask("Database name:")
8
+ database = prompt.ask('Database name:')
9
9
  result = Pgchief::Command::DatabaseCreate.call(database)
10
10
 
11
11
  prompt.say result
@@ -5,8 +5,8 @@ module Pgchief
5
5
  # Class to prompt for user creation details
6
6
  class CreateUser < Base
7
7
  def call
8
- username = prompt.ask("Username:")
9
- password = prompt.mask("Password:")
8
+ username = prompt.ask('Username:')
9
+ password = prompt.mask('Password:')
10
10
  result = Pgchief::Command::UserCreate.call(username, password)
11
11
 
12
12
  prompt.say result
@@ -6,14 +6,14 @@ module Pgchief
6
6
  class DatabaseManagement < Base
7
7
  def call
8
8
  prompt = TTY::Prompt.new
9
- result = prompt.select("Database management", [
10
- "Create database",
11
- "Drop database",
12
- "Database List",
13
- "Backup database",
14
- "Restore database"
9
+ result = prompt.select('Database management', [
10
+ 'Create database',
11
+ 'Drop database',
12
+ 'Database List',
13
+ 'Backup database',
14
+ 'Restore database'
15
15
  ])
16
- scope = result == "Database List" ? "command" : "prompt"
16
+ scope = result == 'Database List' ? 'command' : 'prompt'
17
17
 
18
18
  klassify(scope, result).call
19
19
  end
@@ -5,7 +5,7 @@ module Pgchief
5
5
  # Class to prompt for which database to drop
6
6
  class DropDatabase < Base
7
7
  def call
8
- database = prompt.select("Which database needs to be dropped?", Pgchief::Database.all)
8
+ database = prompt.select('Which database needs to be dropped?', Pgchief::Database.all)
9
9
  result = Pgchief::Command::DatabaseDrop.call(database)
10
10
 
11
11
  prompt.say result
@@ -5,7 +5,7 @@ module Pgchief
5
5
  # Class to prompt for which user to drop
6
6
  class DropUser < Base
7
7
  def call
8
- user = prompt.select("Which user needs to be deleted?", Pgchief::User.all)
8
+ user = prompt.select('Which user needs to be deleted?', Pgchief::User.all)
9
9
  result = Pgchief::Command::UserDrop.call(user)
10
10
 
11
11
  prompt.say result
@@ -15,11 +15,11 @@ module Pgchief
15
15
  end
16
16
 
17
17
  def select_user
18
- prompt.select("Select user to update:", Pgchief::User.all)
18
+ prompt.select('Select user to update:', Pgchief::User.all)
19
19
  end
20
20
 
21
21
  def ask_for_password
22
- prompt.mask("Password:")
22
+ prompt.mask('Password:')
23
23
  end
24
24
  end
25
25
  end
@@ -5,11 +5,16 @@ module Pgchief
5
5
  # Class to prompt for which database to restore
6
6
  class RestoreDatabase < Base
7
7
  def call
8
- database = prompt.select("Which database needs restoring?", Pgchief::Database.all)
9
- local_file = prompt.select("Which backup file do you want to restore?", Pgchief::Database.backups_for(database))
10
- result = Pgchief::Command::DatabaseRestore.call(database, local_file)
8
+ database = prompt.select('Which database needs restoring?', Pgchief::Database.all)
9
+ backups = Pgchief::Database.backups_for(database, remote: Pgchief::Config.remote_restore)
11
10
 
12
- prompt.say result
11
+ if backups.empty?
12
+ prompt.warn "No backup files found for database '#{database}'"
13
+ else
14
+ local_file = prompt.select('Which backup file do you want to restore?', backups)
15
+ result = Pgchief::Command::DatabaseRestore.call(database, local_file)
16
+ prompt.say result
17
+ end
13
18
  end
14
19
  end
15
20
  end
@@ -8,20 +8,20 @@ module Pgchief
8
8
  manage_config!
9
9
 
10
10
  result = prompt.select(
11
- "Welcome! How can I help?",
11
+ 'Welcome! How can I help?',
12
12
  [
13
- "Database management",
14
- "User management"
13
+ 'Database management',
14
+ 'User management'
15
15
  ]
16
16
  )
17
17
 
18
- klassify("prompt", result).call
18
+ klassify('prompt', result).call
19
19
  end
20
20
 
21
21
  private
22
22
 
23
23
  def manage_config!
24
- Pgchief::Config.load_config!
24
+ Pgchief::Config.load_config!(params.first)
25
25
  Pgchief::Config.set_up_file_structure!
26
26
  end
27
27
  end
@@ -5,15 +5,15 @@ module Pgchief
5
5
  # Class to manage users
6
6
  class UserManagement < Base
7
7
  def call
8
- result = prompt.select("User management", [
9
- "Create user",
10
- "Drop user",
11
- "User list",
12
- "Grant database privileges",
13
- "View database connection string"
8
+ result = prompt.select('User management', [
9
+ 'Create user',
10
+ 'Drop user',
11
+ 'User list',
12
+ 'Grant database privileges',
13
+ 'View database connection string'
14
14
  ])
15
15
 
16
- scope = result == "User list" ? "command" : "prompt"
16
+ scope = result == 'User list' ? 'command' : 'prompt'
17
17
  klassify(scope, result).call
18
18
  end
19
19
  end
@@ -6,15 +6,15 @@ module Pgchief
6
6
  class ViewDatabaseConnectionString < Base
7
7
  def call
8
8
  username = params.first || select_user
9
- database = prompt.select("Database you're connecting to:", Pgchief::Database.all + ["None"])
10
- database = nil if database == "None"
9
+ database = prompt.select("Database you're connecting to:", Pgchief::Database.all + ['None'])
10
+ database = nil if database == 'None'
11
11
  result = Pgchief::Command::RetrieveConnectionString.call(username, database)
12
12
 
13
13
  prompt.say result
14
14
  end
15
15
 
16
16
  def select_user
17
- prompt.select("Select user to update:", Pgchief::User.all)
17
+ prompt.select('Select user to update:', Pgchief::User.all)
18
18
  end
19
19
  end
20
20
  end
data/lib/pgchief/user.rb CHANGED
@@ -1,15 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "pg"
3
+ require 'pg'
4
4
 
5
5
  module Pgchief
6
6
  # Database information and operations
7
7
  class User
8
8
  def self.all
9
9
  conn = PG.connect(Pgchief::Config.pgurl)
10
- result = conn.exec("SELECT usename FROM pg_user")
10
+ result = conn.exec('SELECT usename FROM pg_user')
11
11
 
12
- result.map { |row| row["usename"] }.reject { |name| name == "postgres" }
12
+ result.map { |row| row['usename'] }.reject { |name| name == 'postgres' }
13
13
  ensure
14
14
  conn.close
15
15
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pgchief
4
- VERSION = "0.5.3"
4
+ VERSION = '0.6.0'
5
5
  end
data/lib/pgchief.rb CHANGED
@@ -1,55 +1,70 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "pg"
4
- require "tty-prompt"
5
- require "tty-option"
6
- require "aws-sdk-s3"
7
-
8
- require "pgchief/cli"
9
- require "pgchief/config"
10
- require "pgchief/config/s3"
11
- require "pgchief/connection_string"
12
- require "pgchief/version"
13
- require "pgchief/database"
14
- require "pgchief/database/backups"
15
- require "pgchief/user"
16
-
17
- require "pgchief/prompt/base"
18
- require "pgchief/prompt/backup_database"
19
- require "pgchief/prompt/create_database"
20
- require "pgchief/prompt/create_user"
21
- require "pgchief/prompt/database_management"
22
- require "pgchief/prompt/drop_database"
23
- require "pgchief/prompt/drop_user"
24
- require "pgchief/prompt/grant_database_privileges"
25
- require "pgchief/prompt/restore_database"
26
- require "pgchief/prompt/start"
27
- require "pgchief/prompt/user_management"
28
- require "pgchief/prompt/view_database_connection_string"
29
-
30
- require "pgchief/command"
31
- require "pgchief/command/base"
32
- require "pgchief/command/config_create"
33
- require "pgchief/command/database_backup"
34
- require "pgchief/command/database_create"
35
- require "pgchief/command/database_drop"
36
- require "pgchief/command/database_list"
37
- require "pgchief/command/database_privileges_grant"
38
- require "pgchief/command/database_restore"
39
- require "pgchief/command/retrieve_connection_string"
40
- require "pgchief/command/s3_upload"
41
- require "pgchief/command/store_connection_string"
42
- require "pgchief/command/user_create"
43
- require "pgchief/command/user_drop"
44
- require "pgchief/command/user_list"
3
+ require 'pg'
4
+ require 'tty-prompt'
5
+ require 'tty-option'
6
+ require 'aws-sdk-s3'
7
+
8
+ require 'pgchief/cli'
9
+ require 'pgchief/config'
10
+ require 'pgchief/config/s3'
11
+ require 'pgchief/connection_string'
12
+ require 'pgchief/version'
13
+ require 'pgchief/database'
14
+ require 'pgchief/database/backups'
15
+ require 'pgchief/user'
16
+
17
+ require 'pgchief/prompt/base'
18
+ require 'pgchief/prompt/backup_database'
19
+ require 'pgchief/prompt/create_database'
20
+ require 'pgchief/prompt/create_user'
21
+ require 'pgchief/prompt/database_management'
22
+ require 'pgchief/prompt/drop_database'
23
+ require 'pgchief/prompt/drop_user'
24
+ require 'pgchief/prompt/grant_database_privileges'
25
+ require 'pgchief/prompt/restore_database'
26
+ require 'pgchief/prompt/start'
27
+ require 'pgchief/prompt/user_management'
28
+ require 'pgchief/prompt/view_database_connection_string'
29
+
30
+ require 'pgchief/command'
31
+ require 'pgchief/command/base'
32
+ require 'pgchief/command/config_create'
33
+ require 'pgchief/command/database_backup'
34
+ require 'pgchief/command/database_create'
35
+ require 'pgchief/command/database_drop'
36
+ require 'pgchief/command/database_list'
37
+ require 'pgchief/command/database_privileges_grant'
38
+ require 'pgchief/command/database_restore'
39
+ require 'pgchief/command/quick_restore'
40
+ require 'pgchief/command/quick_backup'
41
+ require 'pgchief/command/retrieve_connection_string'
42
+ require 'pgchief/command/s3_upload'
43
+ require 'pgchief/command/store_connection_string'
44
+ require 'pgchief/command/user_create'
45
+ require 'pgchief/command/user_drop'
46
+ require 'pgchief/command/user_list'
45
47
 
46
48
  module Pgchief
47
49
  class Error < StandardError; end
48
50
 
49
51
  module Errors
50
52
  class UserExistsError < Error; end
51
- class DatabaseExistsError < Error; end
52
- class DatabaseMissingError < Error; end
53
+
53
54
  class BackupError < Error; end
55
+
56
+ class ConfigMissingError < Error; end
57
+
58
+ class DatabaseExistsError < Error
59
+ def message
60
+ 'The database already exists.'
61
+ end
62
+ end
63
+
64
+ class DatabaseMissingError < Error
65
+ def message
66
+ 'The database requested does not exist.'
67
+ end
68
+ end
54
69
  end
55
70
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pgchief
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.3
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel Oliveira
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-12-31 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: aws-sdk-s3
@@ -80,7 +79,6 @@ dependencies:
80
79
  - - ">="
81
80
  - !ruby/object:Gem::Version
82
81
  version: '0'
83
- description:
84
82
  email:
85
83
  - joel.oliveira@gmail.com
86
84
  executables:
@@ -91,6 +89,7 @@ files:
91
89
  - ".env.sample"
92
90
  - ".rspec"
93
91
  - ".rubocop.yml"
92
+ - ".rubocop_todo.yml"
94
93
  - CHANGELOG.md
95
94
  - CODE_OF_CONDUCT.md
96
95
  - LICENSE.txt
@@ -109,6 +108,8 @@ files:
109
108
  - lib/pgchief/command/database_list.rb
110
109
  - lib/pgchief/command/database_privileges_grant.rb
111
110
  - lib/pgchief/command/database_restore.rb
111
+ - lib/pgchief/command/quick_backup.rb
112
+ - lib/pgchief/command/quick_restore.rb
112
113
  - lib/pgchief/command/retrieve_connection_string.rb
113
114
  - lib/pgchief/command/s3_upload.rb
114
115
  - lib/pgchief/command/store_connection_string.rb
@@ -145,7 +146,6 @@ metadata:
145
146
  source_code_uri: https://github.com/jayroh/pgchief
146
147
  changelog_uri: https://github.com/jayroh/pgchief/blob/main/CHANGELOG.md
147
148
  rubygems_mfa_required: 'true'
148
- post_install_message:
149
149
  rdoc_options: []
150
150
  require_paths:
151
151
  - lib
@@ -160,8 +160,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
160
160
  - !ruby/object:Gem::Version
161
161
  version: '0'
162
162
  requirements: []
163
- rubygems_version: 3.5.23
164
- signing_key:
163
+ rubygems_version: 3.6.8
165
164
  specification_version: 4
166
165
  summary: A simple ruby script to manage postgresql databases and users
167
166
  test_files: []