pgchief 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 756ccf173c3d0cbe56bb969e2f7d20e9032e1a7b1c600155e212548fa81f93bb
4
- data.tar.gz: c00f369d5e6013bedb955daba82da8c54ad2a77c978b2ead411cae3ef0ca5f0f
3
+ metadata.gz: df59b4bbe15540e5d3b587f98c98ef71f2724f028299e88961766e2b6225ac2c
4
+ data.tar.gz: f5b0f578ac0c0610e2f020f740f207826a1f4cd05de16e27e5da7c1af860a408
5
5
  SHA512:
6
- metadata.gz: 5ec3b0a81250bd7c8b479780ec658adf5a51f7f19db08c0447f1470ba4477cee24c7a00affec84ba5db0cf136a18e02de636e1d2f6701f0b6aa13d3ffee88afa
7
- data.tar.gz: 828bf9aebb8235af34a0462f2d2890e5d51245e98841c97644a1d4411ba9147d5f9a1b594c2367f0dd34a96dff47f45201d0d312e8b862e90f27f83b5aad4514
6
+ metadata.gz: f2e9611feb5db3d76d8e13ca3b153d1a0a387c90faadd249590c115b94d696da5d0b4d5b712db6d7a739b8f9b933fea1f40f9e6dc102fb064098b6ff3b568f58
7
+ data.tar.gz: 863b4f71e694fb3e8ebde4d4b020d446d96a582a24b4e457d0d8cd42bc474780956d874a02944ea959ac689a3d33870374565871c19deea2bc31c750349aff8a
data/CHANGELOG.md CHANGED
@@ -13,6 +13,35 @@ and this project will try its best to adhere to [Semantic Versioning](https://se
13
13
 
14
14
  ### Fixes
15
15
 
16
+ ## [0.4.0]
17
+
18
+ ### Changes
19
+
20
+ * Clean up the config object
21
+
22
+ ### Additions
23
+
24
+ * Back up option for databases: save to local filesystem or S3.
25
+
26
+ ### Fixes
27
+
28
+ * Capture error where the config file does not exist and provide some guidance.
29
+ * Make a `PG::ConnectionBad` error a little less scary(?)
30
+ * Do not inherit the base `Command` class in `ConfigCreate`. It doesn't need to connect to the DB.
31
+
32
+ ## [0.3.1]
33
+
34
+ ### Changes
35
+
36
+ - The main database url will be accessed via the config class and will prioritize
37
+ the ENV over the config file, if it is set.
38
+
39
+ ### Fixes
40
+
41
+ - Do not use the ENV only. Until now that was the only method and it was ignoring
42
+ what was set in the config file. This fixes the issue where nothing will work
43
+ if `ENV["DATABASE_URL"]` is not set.
44
+
16
45
  ## [0.3.0]
17
46
 
18
47
  ### Additions
@@ -62,7 +91,9 @@ and this project will try its best to adhere to [Semantic Versioning](https://se
62
91
  - Drop user ✅
63
92
  - List databases ✅
64
93
 
65
- [Unreleased]: https://github.com/jayroh/pgchief/compare/v0.3.0...HEAD
94
+ [Unreleased]: https://github.com/jayroh/pgchief/compare/v0.4.0...HEAD
95
+ [0.4.0]: https://github.com/jayroh/pgchief/releases/tag/v0.4.0
96
+ [0.3.1]: https://github.com/jayroh/pgchief/releases/tag/v0.3.1
66
97
  [0.3.0]: https://github.com/jayroh/pgchief/releases/tag/v0.3.0
67
98
  [0.2.0]: https://github.com/jayroh/pgchief/releases/tag/v0.2.0
68
99
  [0.1.0]: https://github.com/jayroh/pgchief/releases/tag/v0.1.0
data/README.md CHANGED
@@ -26,19 +26,22 @@ below for the feature check-list and current progress.
26
26
  ```sh
27
27
  gem install pgchief
28
28
 
29
- # To initialize the config file at `~/.pgchief.toml`:
29
+ # To initialize the config file at `~/.config/pgchief/config.toml`:
30
30
 
31
31
  pgchief --init
32
32
 
33
- # make sure the DATABASE_URL is set to the connection string for a pg server's superuser
34
- export DATABASE_URL=postgresql://postgres:password@postgres.local:5432
33
+ # edit the config file and set your main administrative connection string
34
+ # (vim|nano|pico|ed) ~/.config/pgchief/config.toml
35
+
36
+ # OR ... make sure the DATABASE_URL env is set to the connection string
37
+ # export DATABASE_URL=postgresql://postgres:password@postgres.local:5432
35
38
 
36
39
  pgchief
37
40
  ```
38
41
 
39
42
  ## Config
40
43
 
41
- Format of `~/.pgchief.toml`
44
+ Format of `~/.config/pgchief/config.toml`
42
45
 
43
46
  ```toml
44
47
  # Connection string to superuser account at your PG instance
@@ -127,5 +130,9 @@ Give "rando-username" access to database(s):
127
130
  * [x] Give user permissions to use database
128
131
  * [x] Initialize toml file
129
132
  * [x] Display connection information
130
- * [ ] Back up database
131
- * [ ] Restore database
133
+ * [x] Back up database locally
134
+ * [x] Back up database to S3
135
+ * [ ] Restore local database
136
+ * [ ] Restore remote database @ S3
137
+ * [ ] Quickly back up via command line option
138
+ * [ ] Quickly restore via command line option
data/config/pgchief.toml CHANGED
@@ -8,3 +8,9 @@ backup_dir = "~/.config/pgchief/backups"
8
8
 
9
9
  # Location of saved database connection strings
10
10
  # credentials_file = "~/.config/pgchief/credentials"
11
+
12
+ # S3 config - if present, will back up to S3 instead of local filesystem
13
+ # s3_key = ""
14
+ # s3_secret = ""
15
+ # s3_region = "us-east-1"
16
+ # s3_path_prefix = "s3://bucket-name/database-backups/"
@@ -12,7 +12,9 @@ module Pgchief
12
12
 
13
13
  def initialize(*params)
14
14
  @params = params
15
- @conn = PG.connect(Pgchief::DATABASE_URL)
15
+ @conn = PG.connect(Pgchief::Config.pgurl)
16
+ rescue PG::ConnectionBad => e
17
+ puts "Cannot connect to database. #{e.message}"
16
18
  end
17
19
 
18
20
  def call
@@ -5,7 +5,11 @@ require "fileutils"
5
5
  module Pgchief
6
6
  module Command
7
7
  # Create a configuration file at $HOME
8
- class ConfigCreate < Base
8
+ class ConfigCreate
9
+ def self.call(dir: "#{Dir.home}/.config/pgchief")
10
+ new.call(dir: dir)
11
+ end
12
+
9
13
  def call(dir: "#{Dir.home}/.config/pgchief")
10
14
  return if File.exist?("#{dir}/config.toml")
11
15
 
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ module Pgchief
6
+ module Command
7
+ # Command object to drop a database
8
+ class DatabaseBackup < Base
9
+ extend Forwardable
10
+
11
+ def_delegators :@uploader, :configured?, :upload!, :s3_location
12
+
13
+ attr_reader :database
14
+
15
+ def call
16
+ @database = params.first
17
+ @uploader = Pgchief::Command::S3Upload.new(local_location)
18
+ raise Pgchief::Errors::DatabaseMissingError unless db_exists?
19
+
20
+ backup!
21
+ upload! if configured?
22
+
23
+ "Database '#{database}' backed up to #{location}"
24
+ rescue PG::Error => e
25
+ "Error: #{e.message}"
26
+ ensure
27
+ conn.close
28
+ end
29
+
30
+ private
31
+
32
+ def backup!
33
+ `pg_dump -Fc #{database} -f #{local_location}`
34
+ end
35
+
36
+ def db_exists?
37
+ query = "SELECT 1 FROM pg_database WHERE datname = '#{database}'"
38
+ conn.exec(query).any?
39
+ end
40
+
41
+ def location
42
+ configured? ? s3_location : local_location
43
+ end
44
+
45
+ def local_location
46
+ @local_location ||= begin
47
+ timestamp = Time.now.strftime("%Y%m%d%H%M%S")
48
+ "#{Pgchief::Config.backup_dir}#{database}-#{timestamp}.dump"
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -25,7 +25,7 @@ module Pgchief
25
25
  private
26
26
 
27
27
  def grant_privs! # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
28
- conn = PG.connect("#{Pgchief::DATABASE_URL}/#{database}")
28
+ conn = PG.connect("#{Pgchief::Config.pgurl}/#{database}")
29
29
  conn.exec("GRANT CONNECT ON DATABASE #{database} TO #{username};")
30
30
  conn.exec("GRANT CREATE ON SCHEMA public TO #{username};")
31
31
  conn.exec("GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO #{username};")
@@ -50,7 +50,7 @@ module Pgchief
50
50
 
51
51
  def connection_string
52
52
  ConnectionString.new(
53
- Pgchief::DATABASE_URL,
53
+ Pgchief::Config.pgurl,
54
54
  username: username,
55
55
  password: password,
56
56
  database: database
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pgchief
4
+ module Command
5
+ # Class to upload a file to S3
6
+ class S3Upload
7
+ attr_reader :local_location, :file_name, :bucket, :path
8
+
9
+ def initialize(local_location)
10
+ @local_location = local_location
11
+ @file_name = File.basename(local_location)
12
+ end
13
+
14
+ def upload!
15
+ s3.client.put_object(
16
+ bucket: s3.bucket,
17
+ key: "#{s3.path}#{file_name}",
18
+ body: File.open(local_location, "rb"),
19
+ acl: "private",
20
+ content_type: "application/octet-stream"
21
+ )
22
+ FileUtils.rm(local_location)
23
+ end
24
+
25
+ def s3_location
26
+ "s3://#{s3.bucket}/#{s3.path}#{file_name}"
27
+ end
28
+
29
+ def configured?
30
+ s3.configured?
31
+ end
32
+
33
+ private
34
+
35
+ def s3
36
+ Pgchief::Config.s3
37
+ end
38
+ end
39
+ end
40
+ end
@@ -49,7 +49,7 @@ module Pgchief
49
49
 
50
50
  def connection_string
51
51
  ConnectionString.new(
52
- Pgchief::DATABASE_URL,
52
+ Pgchief::Config.pgurl,
53
53
  username: username,
54
54
  password: password
55
55
  ).to_s
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ module Pgchief
6
+ class Config
7
+ # Class to store s3 configuration settings
8
+ class S3
9
+ extend Forwardable
10
+
11
+ def_delegators \
12
+ :config,
13
+ :s3_key,
14
+ :s3_secret,
15
+ :s3_region,
16
+ :s3_path_prefix
17
+
18
+ PREFIX_REGEX = %r{\As3://(?<bucket>(\w|-)*)/(?<path>(\w|/)*/)\z}
19
+
20
+ attr_reader :config
21
+
22
+ def initialize(config)
23
+ @config = config
24
+ end
25
+
26
+ def client
27
+ @client ||= Aws::S3::Client.new(
28
+ access_key_id: s3_key,
29
+ secret_access_key: s3_secret,
30
+ region: s3_region
31
+ )
32
+ end
33
+
34
+ def bucket
35
+ s3_match[:bucket]
36
+ end
37
+
38
+ def path
39
+ s3_match[:path]
40
+ end
41
+
42
+ def configured?
43
+ [
44
+ s3_key,
45
+ s3_secret,
46
+ s3_region,
47
+ s3_path_prefix
48
+ ].none?(&:nil?)
49
+ end
50
+
51
+ private
52
+
53
+ def s3_match
54
+ @s3_match ||= s3_path_prefix.match(PREFIX_REGEX)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -7,25 +7,63 @@ module Pgchief
7
7
  class Config
8
8
  class << self
9
9
  attr_accessor \
10
+ :s3_key,
11
+ :s3_secret,
12
+ :s3_region
13
+
14
+ attr_writer :pgurl
15
+
16
+ attr_reader \
17
+ :s3_path_prefix,
10
18
  :backup_dir,
11
- :credentials_file,
12
- :pgurl
19
+ :credentials_file
20
+
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)
23
+ self.backup_dir = config[:backup_dir]
24
+ self.credentials_file = config[:credentials_file]
25
+ self.pgurl = config[:pgurl]
26
+ self.s3_key = config[:s3_key]
27
+ self.s3_secret = config[:s3_secret]
28
+ self.s3_region = config[:s3_region]
29
+ self.s3_path_prefix = config[:s3_path_prefix]
30
+ rescue Errno::ENOENT
31
+ puts config_missing_error(toml_file)
32
+ end
13
33
 
14
- def load_config!(toml_file = "#{Dir.home}/.config/pgchief/config.toml")
15
- config = TomlRB.load_file(toml_file, symbolize_keys: true)
34
+ def s3
35
+ @s3 ||= Pgchief::Config::S3.new(self)
36
+ end
37
+
38
+ def pgurl
39
+ ENV.fetch("DATABASE_URL", @pgurl)
40
+ end
16
41
 
17
- @backup_dir = config[:backup_dir].gsub("~", Dir.home)
18
- @credentials_file = config[:credentials_file]&.gsub("~", Dir.home)
19
- @pgurl = config[:pgurl]
42
+ def backup_dir=(value)
43
+ @backup_dir = value ? "#{value.chomp("/")}/".gsub("~", Dir.home) : "/tmp/"
44
+ end
45
+
46
+ def s3_path_prefix=(value)
47
+ @s3_path_prefix = value ? "#{value.chomp("/")}/" : nil
48
+ end
49
+
50
+ def credentials_file=(value)
51
+ @credentials_file = value&.gsub("~", Dir.home)
20
52
  end
21
53
 
22
54
  def set_up_file_structure!
23
55
  FileUtils.mkdir_p(backup_dir)
24
-
25
56
  return unless credentials_file && !File.exist?(credentials_file)
26
57
 
27
58
  FileUtils.touch(credentials_file)
28
59
  end
60
+
61
+ private
62
+
63
+ def config_missing_error(toml_file)
64
+ "You must create a config file at #{toml_file}.\n" \
65
+ "run `pgchief --init` to create it."
66
+ end
29
67
  end
30
68
  end
31
69
  end
@@ -6,7 +6,7 @@ module Pgchief
6
6
  # Database information and operations
7
7
  class Database
8
8
  def self.all
9
- conn = PG.connect(Pgchief::DATABASE_URL)
9
+ conn = PG.connect(Pgchief::Config.pgurl)
10
10
  result = conn.exec("SELECT datname FROM pg_database WHERE datistemplate = false")
11
11
  result
12
12
  .map { |row| row["datname"] }
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pgchief
4
+ module Prompt
5
+ # Class to prompt for which database to drop
6
+ class BackupDatabase < Base
7
+ def call
8
+ database = prompt.select("Which database needs backing up?", Pgchief::Database.all)
9
+ result = Pgchief::Command::DatabaseBackup.call(database)
10
+
11
+ prompt.say result
12
+ end
13
+ end
14
+ end
15
+ end
@@ -6,8 +6,13 @@ module Pgchief
6
6
  class DatabaseManagement < Base
7
7
  def call
8
8
  prompt = TTY::Prompt.new
9
- result = prompt.select("Database management", ["Create database", "Drop database", "Database List"])
10
- scope = result == "Database List" ? "command" : "prompt"
9
+ result = prompt.select("Database management", [
10
+ "Create database",
11
+ "Drop database",
12
+ "Database List",
13
+ "Backup database"
14
+ ])
15
+ scope = result == "Database List" ? "command" : "prompt"
11
16
 
12
17
  klassify(scope, result).call
13
18
  end
data/lib/pgchief/user.rb CHANGED
@@ -6,7 +6,7 @@ module Pgchief
6
6
  # Database information and operations
7
7
  class User
8
8
  def self.all
9
- conn = PG.connect(Pgchief::DATABASE_URL)
9
+ conn = PG.connect(Pgchief::Config.pgurl)
10
10
  result = conn.exec("SELECT usename FROM pg_user")
11
11
 
12
12
  result.map { |row| row["usename"] }.reject { |name| name == "postgres" }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pgchief
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
5
5
  end
data/lib/pgchief.rb CHANGED
@@ -3,9 +3,11 @@
3
3
  require "pg"
4
4
  require "tty-prompt"
5
5
  require "tty-option"
6
+ require "aws-sdk-s3"
6
7
 
7
8
  require "pgchief/cli"
8
9
  require "pgchief/config"
10
+ require "pgchief/config/s3"
9
11
  require "pgchief/connection_string"
10
12
  require "pgchief/version"
11
13
  require "pgchief/database"
@@ -13,6 +15,7 @@ require "pgchief/user"
13
15
 
14
16
  require "pgchief/prompt/base"
15
17
  require "pgchief/prompt/start"
18
+ require "pgchief/prompt/backup_database"
16
19
  require "pgchief/prompt/create_database"
17
20
  require "pgchief/prompt/create_user"
18
21
  require "pgchief/prompt/database_management"
@@ -25,19 +28,19 @@ require "pgchief/prompt/view_database_connection_string"
25
28
  require "pgchief/command"
26
29
  require "pgchief/command/base"
27
30
  require "pgchief/command/config_create"
31
+ require "pgchief/command/database_backup"
28
32
  require "pgchief/command/database_create"
29
33
  require "pgchief/command/database_drop"
30
34
  require "pgchief/command/database_list"
31
35
  require "pgchief/command/database_privileges_grant"
32
36
  require "pgchief/command/retrieve_connection_string"
37
+ require "pgchief/command/s3_upload"
33
38
  require "pgchief/command/store_connection_string"
34
39
  require "pgchief/command/user_create"
35
40
  require "pgchief/command/user_drop"
36
41
  require "pgchief/command/user_list"
37
42
 
38
43
  module Pgchief
39
- DATABASE_URL = ENV.fetch("DATABASE_URL")
40
-
41
44
  class Error < StandardError; end
42
45
 
43
46
  module Errors
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pgchief
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel Oliveira
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-11-11 00:00:00.000000000 Z
11
+ date: 2024-11-27 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aws-sdk-s3
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: pg
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -79,7 +93,6 @@ files:
79
93
  - ".rubocop.yml"
80
94
  - CHANGELOG.md
81
95
  - CODE_OF_CONDUCT.md
82
- - LICENSE
83
96
  - LICENSE.txt
84
97
  - README.md
85
98
  - Rakefile
@@ -90,19 +103,23 @@ files:
90
103
  - lib/pgchief/command.rb
91
104
  - lib/pgchief/command/base.rb
92
105
  - lib/pgchief/command/config_create.rb
106
+ - lib/pgchief/command/database_backup.rb
93
107
  - lib/pgchief/command/database_create.rb
94
108
  - lib/pgchief/command/database_drop.rb
95
109
  - lib/pgchief/command/database_list.rb
96
110
  - lib/pgchief/command/database_privileges_grant.rb
97
111
  - lib/pgchief/command/retrieve_connection_string.rb
112
+ - lib/pgchief/command/s3_upload.rb
98
113
  - lib/pgchief/command/store_connection_string.rb
99
114
  - lib/pgchief/command/user_create.rb
100
115
  - lib/pgchief/command/user_drop.rb
101
116
  - lib/pgchief/command/user_list.rb
102
117
  - lib/pgchief/config.rb
118
+ - lib/pgchief/config/s3.rb
103
119
  - lib/pgchief/connection_string.rb
104
120
  - lib/pgchief/database.rb
105
121
  - lib/pgchief/prompt.rb
122
+ - lib/pgchief/prompt/backup_database.rb
106
123
  - lib/pgchief/prompt/base.rb
107
124
  - lib/pgchief/prompt/create_database.rb
108
125
  - lib/pgchief/prompt/create_user.rb
data/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2024 Joel Oliveira
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.