pgchief 0.3.1 → 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: 627e9af2dc140bd7210c9f4e999f5be13ba0bf514e6397619d42bef8a6d8b838
4
- data.tar.gz: 78a5456432854cebd9b6d6fb68f9f02256a9a37c626ac0d78ef35b3bf79f4f21
3
+ metadata.gz: df59b4bbe15540e5d3b587f98c98ef71f2724f028299e88961766e2b6225ac2c
4
+ data.tar.gz: f5b0f578ac0c0610e2f020f740f207826a1f4cd05de16e27e5da7c1af860a408
5
5
  SHA512:
6
- metadata.gz: 26a19391751adbd9846cd70532becfa91c4f2e6a704405764c207e817f388b5ad900a5993eb2e20b574e09e78a089658b9f1f14591e9ea5f0349476fa7b49c13
7
- data.tar.gz: e96db718dd3e594b4f612d47b2d63bfe0792e94b4ba622d9093db6b2d8138c02cf8dc45885557a8048f767d280f597103a410468914496dd937e25b759eb2c18
6
+ metadata.gz: f2e9611feb5db3d76d8e13ca3b153d1a0a387c90faadd249590c115b94d696da5d0b4d5b712db6d7a739b8f9b933fea1f40f9e6dc102fb064098b6ff3b568f58
7
+ data.tar.gz: 863b4f71e694fb3e8ebde4d4b020d446d96a582a24b4e457d0d8cd42bc474780956d874a02944ea959ac689a3d33870374565871c19deea2bc31c750349aff8a
data/CHANGELOG.md CHANGED
@@ -13,6 +13,22 @@ 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
+
16
32
  ## [0.3.1]
17
33
 
18
34
  ### Changes
@@ -75,7 +91,9 @@ and this project will try its best to adhere to [Semantic Versioning](https://se
75
91
  - Drop user ✅
76
92
  - List databases ✅
77
93
 
78
- [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
79
97
  [0.3.0]: https://github.com/jayroh/pgchief/releases/tag/v0.3.0
80
98
  [0.2.0]: https://github.com/jayroh/pgchief/releases/tag/v0.2.0
81
99
  [0.1.0]: https://github.com/jayroh/pgchief/releases/tag/v0.1.0
data/README.md CHANGED
@@ -41,7 +41,7 @@ pgchief
41
41
 
42
42
  ## Config
43
43
 
44
- Format of `~/.pgchief.toml`
44
+ Format of `~/.config/pgchief/config.toml`
45
45
 
46
46
  ```toml
47
47
  # Connection string to superuser account at your PG instance
@@ -130,5 +130,9 @@ Give "rando-username" access to database(s):
130
130
  * [x] Give user permissions to use database
131
131
  * [x] Initialize toml file
132
132
  * [x] Display connection information
133
- * [ ] Back up database
134
- * [ ] 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/"
@@ -13,6 +13,8 @@ module Pgchief
13
13
  def initialize(*params)
14
14
  @params = params
15
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
@@ -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
@@ -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,30 +7,63 @@ module Pgchief
7
7
  class Config
8
8
  class << self
9
9
  attr_accessor \
10
- :backup_dir,
11
- :credentials_file
10
+ :s3_key,
11
+ :s3_secret,
12
+ :s3_region
12
13
 
13
14
  attr_writer :pgurl
14
15
 
15
- def load_config!(toml_file = "#{Dir.home}/.config/pgchief/config.toml")
16
- config = TomlRB.load_file(toml_file, symbolize_keys: true)
16
+ attr_reader \
17
+ :s3_path_prefix,
18
+ :backup_dir,
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
17
33
 
18
- @backup_dir = config[:backup_dir].gsub("~", Dir.home)
19
- @credentials_file = config[:credentials_file]&.gsub("~", Dir.home)
20
- @pgurl = config[:pgurl]
34
+ def s3
35
+ @s3 ||= Pgchief::Config::S3.new(self)
21
36
  end
22
37
 
23
38
  def pgurl
24
39
  ENV.fetch("DATABASE_URL", @pgurl)
25
40
  end
26
41
 
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)
52
+ end
53
+
27
54
  def set_up_file_structure!
28
55
  FileUtils.mkdir_p(backup_dir)
29
-
30
56
  return unless credentials_file && !File.exist?(credentials_file)
31
57
 
32
58
  FileUtils.touch(credentials_file)
33
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
34
67
  end
35
68
  end
36
69
  end
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pgchief
4
- VERSION = "0.3.1"
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,11 +28,13 @@ 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"
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.1
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-12 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
@@ -89,19 +103,23 @@ files:
89
103
  - lib/pgchief/command.rb
90
104
  - lib/pgchief/command/base.rb
91
105
  - lib/pgchief/command/config_create.rb
106
+ - lib/pgchief/command/database_backup.rb
92
107
  - lib/pgchief/command/database_create.rb
93
108
  - lib/pgchief/command/database_drop.rb
94
109
  - lib/pgchief/command/database_list.rb
95
110
  - lib/pgchief/command/database_privileges_grant.rb
96
111
  - lib/pgchief/command/retrieve_connection_string.rb
112
+ - lib/pgchief/command/s3_upload.rb
97
113
  - lib/pgchief/command/store_connection_string.rb
98
114
  - lib/pgchief/command/user_create.rb
99
115
  - lib/pgchief/command/user_drop.rb
100
116
  - lib/pgchief/command/user_list.rb
101
117
  - lib/pgchief/config.rb
118
+ - lib/pgchief/config/s3.rb
102
119
  - lib/pgchief/connection_string.rb
103
120
  - lib/pgchief/database.rb
104
121
  - lib/pgchief/prompt.rb
122
+ - lib/pgchief/prompt/backup_database.rb
105
123
  - lib/pgchief/prompt/base.rb
106
124
  - lib/pgchief/prompt/create_database.rb
107
125
  - lib/pgchief/prompt/create_user.rb
@@ -139,7 +157,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
139
157
  - !ruby/object:Gem::Version
140
158
  version: '0'
141
159
  requirements: []
142
- rubygems_version: 3.2.33
160
+ rubygems_version: 3.5.21
143
161
  signing_key:
144
162
  specification_version: 4
145
163
  summary: A simple ruby script to manage postgresql databases and users