pgchief 0.1.0 → 0.3.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +12 -0
- data/CHANGELOG.md +59 -1
- data/README.md +44 -25
- data/config/pgchief.toml +10 -0
- data/exe/pgchief +3 -1
- data/lib/pgchief/cli.rb +22 -0
- data/lib/pgchief/command/base.rb +1 -1
- data/lib/pgchief/command/config_create.rb +20 -0
- data/lib/pgchief/command/database_create.rb +1 -0
- data/lib/pgchief/command/database_privileges_grant.rb +61 -0
- data/lib/pgchief/command/retrieve_connection_string.rb +34 -0
- data/lib/pgchief/command/store_connection_string.rb +24 -0
- data/lib/pgchief/command/user_create.rb +18 -1
- data/lib/pgchief/command/user_drop.rb +29 -1
- data/lib/pgchief/config.rb +31 -0
- data/lib/pgchief/connection_string.rb +63 -0
- data/lib/pgchief/database.rb +1 -1
- data/lib/pgchief/prompt/base.rb +32 -3
- data/lib/pgchief/prompt/create_database.rb +1 -2
- data/lib/pgchief/prompt/create_user.rb +6 -2
- data/lib/pgchief/prompt/database_management.rb +1 -1
- data/lib/pgchief/prompt/drop_database.rb +1 -2
- data/lib/pgchief/prompt/drop_user.rb +1 -2
- data/lib/pgchief/prompt/grant_database_privileges.rb +26 -0
- data/lib/pgchief/prompt/start.rb +10 -2
- data/lib/pgchief/prompt/user_management.rb +8 -3
- data/lib/pgchief/prompt/view_database_connection_string.rb +21 -0
- data/lib/pgchief/user.rb +1 -1
- data/lib/pgchief/version.rb +1 -1
- data/lib/pgchief.rb +15 -1
- data/tmp/.gitkeep +0 -0
- metadata +14 -4
- data/lib/pgchief/prompt/grant_database_priveleges.rb +0 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 756ccf173c3d0cbe56bb969e2f7d20e9032e1a7b1c600155e212548fa81f93bb
|
4
|
+
data.tar.gz: c00f369d5e6013bedb955daba82da8c54ad2a77c978b2ead411cae3ef0ca5f0f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5ec3b0a81250bd7c8b479780ec658adf5a51f7f19db08c0447f1470ba4477cee24c7a00affec84ba5db0cf136a18e02de636e1d2f6701f0b6aa13d3ffee88afa
|
7
|
+
data.tar.gz: 828bf9aebb8235af34a0462f2d2890e5d51245e98841c97644a1d4411ba9147d5f9a1b594c2367f0dd34a96dff47f45201d0d312e8b862e90f27f83b5aad4514
|
data/.rubocop.yml
CHANGED
@@ -18,5 +18,17 @@ Metrics/BlockLength:
|
|
18
18
|
- 'spec/**/*'
|
19
19
|
- '*.gemspec'
|
20
20
|
|
21
|
+
Lint/MixedRegexpCaptureTypes:
|
22
|
+
Enabled: false
|
23
|
+
|
21
24
|
RSpec/MultipleExpectations:
|
22
25
|
Max: 5
|
26
|
+
|
27
|
+
RSpec/ExampleLength:
|
28
|
+
Max: 20
|
29
|
+
|
30
|
+
Lint/MissingSuper:
|
31
|
+
Enabled: false
|
32
|
+
|
33
|
+
Metrics/ParameterLists:
|
34
|
+
Enabled: false
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,59 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
6
|
+
and this project will try its best to adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
|
+
|
1
8
|
## [Unreleased]
|
2
9
|
|
3
|
-
|
10
|
+
### Additions
|
11
|
+
|
12
|
+
### Changes
|
13
|
+
|
14
|
+
### Fixes
|
15
|
+
|
16
|
+
## [0.3.0]
|
17
|
+
|
18
|
+
### Additions
|
19
|
+
|
20
|
+
- Refactor `exe/pgchief` to utilize `TTY::Option` for kicking off config initialization
|
21
|
+
- `pgchief --init` now creates a toml config file in your `$HOME`
|
22
|
+
- Added ability to store credentials if your config sets `credentials_file`
|
23
|
+
when db's and users are created
|
24
|
+
- Added `ConnectionString` class that abstracts the base db connection,
|
25
|
+
allowing for additions of users and db's
|
26
|
+
- Load everything in the config file to the Config attributes
|
27
|
+
|
28
|
+
### Changes
|
29
|
+
|
30
|
+
- Default location of config changed from `~/.pgchief.toml` to `~/.config/pgchief/config.toml`
|
31
|
+
- Automatically require 'pry' in the test suite
|
32
|
+
|
33
|
+
### Fixes
|
34
|
+
|
35
|
+
- When dropping user, ignore whenever a database has no privileges for the
|
36
|
+
selected user
|
37
|
+
- Retroactive addition of tests to cover any regressions
|
38
|
+
|
39
|
+
## [0.2.0] - 2024-08-30
|
40
|
+
|
41
|
+
### Additions
|
42
|
+
|
43
|
+
- Add `j` and `k` keys as substitutes for `↑` and `↓`.
|
44
|
+
- Allow exiting the program with the `esc` key.
|
45
|
+
- Add ability to grant access privileges for newly created users.
|
46
|
+
- Or grant privileges for existing users to database(s).
|
47
|
+
|
48
|
+
### Fixes
|
49
|
+
|
50
|
+
- GitHub now running CI successfully.
|
51
|
+
- Newly created databases are no longer open for connection by default.
|
52
|
+
`CONNECT` is revoked by default for them.
|
53
|
+
- When dropping users, loop through all the databases they have access to and
|
54
|
+
revoke access before dropping them.
|
55
|
+
|
56
|
+
## [0.1.0] - 2024-08-30
|
4
57
|
|
5
58
|
- Initial release
|
6
59
|
- Create database ✅
|
@@ -8,3 +61,8 @@
|
|
8
61
|
- Drop database ✅
|
9
62
|
- Drop user ✅
|
10
63
|
- List databases ✅
|
64
|
+
|
65
|
+
[Unreleased]: https://github.com/jayroh/pgchief/compare/v0.3.0...HEAD
|
66
|
+
[0.3.0]: https://github.com/jayroh/pgchief/releases/tag/v0.3.0
|
67
|
+
[0.2.0]: https://github.com/jayroh/pgchief/releases/tag/v0.2.0
|
68
|
+
[0.1.0]: https://github.com/jayroh/pgchief/releases/tag/v0.1.0
|
data/README.md
CHANGED
@@ -21,30 +21,58 @@ below for the feature check-list and current progress.
|
|
21
21
|
|
22
22
|
***
|
23
23
|
|
24
|
-
## Usage
|
24
|
+
## Usage
|
25
25
|
|
26
|
-
```
|
26
|
+
```sh
|
27
27
|
gem install pgchief
|
28
28
|
|
29
|
+
# To initialize the config file at `~/.pgchief.toml`:
|
30
|
+
|
31
|
+
pgchief --init
|
32
|
+
|
29
33
|
# make sure the DATABASE_URL is set to the connection string for a pg server's superuser
|
30
34
|
export DATABASE_URL=postgresql://postgres:password@postgres.local:5432
|
31
35
|
|
32
36
|
pgchief
|
33
37
|
```
|
34
38
|
|
39
|
+
## Config
|
40
|
+
|
41
|
+
Format of `~/.pgchief.toml`
|
42
|
+
|
43
|
+
```toml
|
44
|
+
# Connection string to superuser account at your PG instance
|
45
|
+
pgurl = "postgresql://username:password@host:5432"
|
46
|
+
|
47
|
+
# Directory where db backups will be placed
|
48
|
+
backup_dir = "~/.pgchief/backups"
|
49
|
+
|
50
|
+
# ** OPTIONAL **
|
51
|
+
|
52
|
+
# Location of encrypted database connection strings
|
53
|
+
# credentials_file = "~/.pgchief/credentials"
|
54
|
+
```
|
55
|
+
|
56
|
+
Note:
|
57
|
+
|
58
|
+
1. Prompts accept both `↑` and `↓` arrows, as well as `j` and `k`.
|
59
|
+
2. Pressing the `esc` key at any point amidst a prompt will exit out of the program.
|
60
|
+
|
35
61
|
## Development of the gem
|
36
62
|
|
37
63
|
1. Clone this repo.
|
38
64
|
2. `bundle install`
|
39
65
|
3. `cp .env.sample .env`
|
40
66
|
4. Edit `.env` and change:
|
41
|
-
|
42
|
-
|
67
|
+
* `DATABASE_URL` to point to your main pg instance's superuser account with a
|
68
|
+
connection string.
|
69
|
+
* `TEST_DATABASE_URL` to point to your local pg instance where tests can be
|
70
|
+
run against.
|
43
71
|
5. `bundle exec rake` to run test suite & rubocop.
|
44
72
|
|
45
|
-
## The ideal, aspirational, DX
|
73
|
+
## The ideal, aspirational, DX
|
46
74
|
|
47
|
-
```
|
75
|
+
```sh
|
48
76
|
$ pgchief --init # create the TOML file in your home dir (w/600 permissions)
|
49
77
|
$ pgchief
|
50
78
|
|
@@ -87,26 +115,17 @@ Give "rando-username" access to database(s):
|
|
87
115
|
# ... etc.
|
88
116
|
```
|
89
117
|
|
90
|
-
Format of `~/.pgchief.toml`
|
91
|
-
|
92
|
-
```toml
|
93
|
-
pgurl = "postgres://username:password@host:5432"
|
94
|
-
backup_dir = "~/.pg_backups"
|
95
|
-
|
96
|
-
# [optional] encryption key (to display hashed passwords)
|
97
|
-
# encryption_key = "my-password"
|
98
|
-
```
|
99
|
-
|
100
118
|
***
|
101
119
|
|
102
120
|
## Feature Roadmap
|
103
121
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
122
|
+
* [x] Create database
|
123
|
+
* [x] Create user
|
124
|
+
* [x] Drop database
|
125
|
+
* [x] Drop user
|
126
|
+
* [x] List databases
|
127
|
+
* [x] Give user permissions to use database
|
128
|
+
* [x] Initialize toml file
|
129
|
+
* [x] Display connection information
|
130
|
+
* [ ] Back up database
|
131
|
+
* [ ] Restore database
|
data/config/pgchief.toml
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# Connection string to superuser account at your PG instance
|
2
|
+
pgurl = "postgresql://localhost:5432"
|
3
|
+
|
4
|
+
# Directory where db backups will be placed
|
5
|
+
backup_dir = "~/.config/pgchief/backups"
|
6
|
+
|
7
|
+
# ** OPTIONAL **
|
8
|
+
|
9
|
+
# Location of saved database connection strings
|
10
|
+
# credentials_file = "~/.config/pgchief/credentials"
|
data/exe/pgchief
CHANGED
data/lib/pgchief/cli.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pgchief
|
4
|
+
# Command line interface and option parsing
|
5
|
+
class Cli
|
6
|
+
include TTY::Option
|
7
|
+
|
8
|
+
option :init do
|
9
|
+
short "-i"
|
10
|
+
long "--init"
|
11
|
+
desc "Initialize the TOML configuration file"
|
12
|
+
end
|
13
|
+
|
14
|
+
def run
|
15
|
+
if params[:init]
|
16
|
+
Pgchief::Command::ConfigCreate.call
|
17
|
+
else
|
18
|
+
Pgchief::Prompt::Start.call
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/pgchief/command/base.rb
CHANGED
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "fileutils"
|
4
|
+
|
5
|
+
module Pgchief
|
6
|
+
module Command
|
7
|
+
# Create a configuration file at $HOME
|
8
|
+
class ConfigCreate < Base
|
9
|
+
def call(dir: "#{Dir.home}/.config/pgchief")
|
10
|
+
return if File.exist?("#{dir}/config.toml")
|
11
|
+
|
12
|
+
template = File.join(__dir__, "..", "..", "..", "config", "pgchief.toml")
|
13
|
+
FileUtils.mkdir_p(dir)
|
14
|
+
FileUtils.cp(template, "#{dir}/config.toml")
|
15
|
+
|
16
|
+
puts "Configuration file created at #{dir}/config.toml"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -11,6 +11,7 @@ module Pgchief
|
|
11
11
|
raise Pgchief::Errors::DatabaseExistsError if db_exists?
|
12
12
|
|
13
13
|
conn.exec("CREATE DATABASE #{database}")
|
14
|
+
conn.exec("REVOKE CONNECT ON DATABASE #{database} FROM PUBLIC")
|
14
15
|
|
15
16
|
"Database '#{database}' created successfully!"
|
16
17
|
rescue PG::Error => e
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pgchief
|
4
|
+
module Command
|
5
|
+
# Class to grant database privileges
|
6
|
+
class DatabasePrivilegesGrant < Base
|
7
|
+
attr_reader :username, :password, :database
|
8
|
+
|
9
|
+
def initialize(*params)
|
10
|
+
@username = params[0]
|
11
|
+
@password = params[1]
|
12
|
+
@databases = params[2]
|
13
|
+
end
|
14
|
+
|
15
|
+
def call
|
16
|
+
@databases.each do |database|
|
17
|
+
@database = database
|
18
|
+
grant_privs!
|
19
|
+
store_credentials!
|
20
|
+
end
|
21
|
+
|
22
|
+
"Privileges granted to #{username} on #{@databases.join(", ")}"
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def grant_privs! # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
28
|
+
conn = PG.connect("#{Pgchief::DATABASE_URL}/#{database}")
|
29
|
+
conn.exec("GRANT CONNECT ON DATABASE #{database} TO #{username};")
|
30
|
+
conn.exec("GRANT CREATE ON SCHEMA public TO #{username};")
|
31
|
+
conn.exec("GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO #{username};")
|
32
|
+
conn.exec("GRANT USAGE ON SCHEMA public TO #{username};")
|
33
|
+
conn.exec(
|
34
|
+
<<~SQL
|
35
|
+
ALTER DEFAULT PRIVILEGES IN SCHEMA public
|
36
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES
|
37
|
+
TO #{username};
|
38
|
+
SQL
|
39
|
+
)
|
40
|
+
conn.close
|
41
|
+
rescue PG::Error => e
|
42
|
+
"Error: #{e.message}"
|
43
|
+
ensure
|
44
|
+
conn.finished? || conn.close
|
45
|
+
end
|
46
|
+
|
47
|
+
def store_credentials!
|
48
|
+
Pgchief::Command::StoreConnectionString.call(connection_string)
|
49
|
+
end
|
50
|
+
|
51
|
+
def connection_string
|
52
|
+
ConnectionString.new(
|
53
|
+
Pgchief::DATABASE_URL,
|
54
|
+
username: username,
|
55
|
+
password: password,
|
56
|
+
database: database
|
57
|
+
).to_s
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pgchief
|
4
|
+
module Command
|
5
|
+
# Class to view database connection string
|
6
|
+
class RetrieveConnectionString < Base
|
7
|
+
attr_reader :username, :database
|
8
|
+
|
9
|
+
def initialize(username, database = nil)
|
10
|
+
@username = username
|
11
|
+
@database = database
|
12
|
+
@connection_string = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def call
|
16
|
+
File.foreach(Config.credentials_file) do |line|
|
17
|
+
@connection_string = line if regex.match?(line)
|
18
|
+
end
|
19
|
+
|
20
|
+
@connection_string.nil? ? "No connection string found" : @connection_string
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def regex
|
26
|
+
if database
|
27
|
+
/#{username}.*#{database}$/
|
28
|
+
else
|
29
|
+
/#{username}.*\d$/
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pgchief
|
4
|
+
module Command
|
5
|
+
# Class to store connection string
|
6
|
+
class StoreConnectionString
|
7
|
+
def self.call(connection_string)
|
8
|
+
new(connection_string).call
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :connection_string
|
12
|
+
|
13
|
+
def initialize(connection_string)
|
14
|
+
@connection_string = connection_string
|
15
|
+
end
|
16
|
+
|
17
|
+
def call
|
18
|
+
File.open(Config.credentials_file, "a") do |file|
|
19
|
+
file.puts @connection_string
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -18,7 +18,8 @@ module Pgchief
|
|
18
18
|
@username, @password = params
|
19
19
|
raise Pgchief::Errors::UserExistsError if user_exists?
|
20
20
|
|
21
|
-
|
21
|
+
create_user!
|
22
|
+
save_credentials!
|
22
23
|
|
23
24
|
"User '#{username}' created successfully!"
|
24
25
|
rescue PG::Error => e
|
@@ -34,9 +35,25 @@ module Pgchief
|
|
34
35
|
conn.exec(query).any?
|
35
36
|
end
|
36
37
|
|
38
|
+
def create_user!
|
39
|
+
conn.exec("CREATE USER #{username} WITH #{user_options} PASSWORD '#{password}'")
|
40
|
+
end
|
41
|
+
|
42
|
+
def save_credentials!
|
43
|
+
Pgchief::Command::StoreConnectionString.call(connection_string)
|
44
|
+
end
|
45
|
+
|
37
46
|
def user_options
|
38
47
|
USER_OPTIONS.join(" ")
|
39
48
|
end
|
49
|
+
|
50
|
+
def connection_string
|
51
|
+
ConnectionString.new(
|
52
|
+
Pgchief::DATABASE_URL,
|
53
|
+
username: username,
|
54
|
+
password: password
|
55
|
+
).to_s
|
56
|
+
end
|
40
57
|
end
|
41
58
|
end
|
42
59
|
end
|
@@ -10,7 +10,8 @@ module Pgchief
|
|
10
10
|
@username = params.first
|
11
11
|
raise Pgchief::Errors::UserExistsError unless user_exists?
|
12
12
|
|
13
|
-
|
13
|
+
revoke_all_privileges
|
14
|
+
drop_user
|
14
15
|
|
15
16
|
"User '#{username}' dropped successfully!"
|
16
17
|
rescue PG::Error => e
|
@@ -19,10 +20,37 @@ module Pgchief
|
|
19
20
|
conn.close
|
20
21
|
end
|
21
22
|
|
23
|
+
private
|
24
|
+
|
22
25
|
def user_exists?
|
23
26
|
query = "SELECT 1 FROM pg_user WHERE usename = '#{username}'"
|
24
27
|
conn.exec(query).any?
|
25
28
|
end
|
29
|
+
|
30
|
+
def revoke_all_privileges
|
31
|
+
databases_with_access.each do |database|
|
32
|
+
conn.exec("REVOKE ALL PRIVILEGES ON DATABASE #{database} FROM #{username};")
|
33
|
+
rescue PG::Error
|
34
|
+
next
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def drop_user
|
39
|
+
conn.exec("DROP USER #{username}")
|
40
|
+
end
|
41
|
+
|
42
|
+
def databases_with_access
|
43
|
+
@databases_with_access ||= begin
|
44
|
+
results = conn.exec <<~SQL
|
45
|
+
SELECT datname
|
46
|
+
FROM pg_database
|
47
|
+
WHERE has_database_privilege('#{username}', datname, 'CONNECT')
|
48
|
+
AND datname NOT IN ('postgres', 'template1', 'template0')
|
49
|
+
SQL
|
50
|
+
|
51
|
+
results.map { |row| row["datname"] }
|
52
|
+
end
|
53
|
+
end
|
26
54
|
end
|
27
55
|
end
|
28
56
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "toml-rb"
|
4
|
+
|
5
|
+
module Pgchief
|
6
|
+
# Class to store configuration settings
|
7
|
+
class Config
|
8
|
+
class << self
|
9
|
+
attr_accessor \
|
10
|
+
:backup_dir,
|
11
|
+
:credentials_file,
|
12
|
+
:pgurl
|
13
|
+
|
14
|
+
def load_config!(toml_file = "#{Dir.home}/.config/pgchief/config.toml")
|
15
|
+
config = TomlRB.load_file(toml_file, symbolize_keys: true)
|
16
|
+
|
17
|
+
@backup_dir = config[:backup_dir].gsub("~", Dir.home)
|
18
|
+
@credentials_file = config[:credentials_file]&.gsub("~", Dir.home)
|
19
|
+
@pgurl = config[:pgurl]
|
20
|
+
end
|
21
|
+
|
22
|
+
def set_up_file_structure!
|
23
|
+
FileUtils.mkdir_p(backup_dir)
|
24
|
+
|
25
|
+
return unless credentials_file && !File.exist?(credentials_file)
|
26
|
+
|
27
|
+
FileUtils.touch(credentials_file)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pgchief
|
4
|
+
# Class to parse and manipulate connection strings
|
5
|
+
class ConnectionString
|
6
|
+
URL_REGEX = %r{(?x)\A
|
7
|
+
postgres(ql)?://
|
8
|
+
(?<username>[^:@]*)?
|
9
|
+
:?(?<password>[^@]*)?
|
10
|
+
@?(?<host>[^:]*)?
|
11
|
+
:?(?<port>\d+)?
|
12
|
+
/?(?<database>[^\?]*)?
|
13
|
+
\z}
|
14
|
+
|
15
|
+
attr_reader :database_url
|
16
|
+
|
17
|
+
def initialize(
|
18
|
+
database_url,
|
19
|
+
username: nil,
|
20
|
+
password: nil,
|
21
|
+
host: nil,
|
22
|
+
port: nil,
|
23
|
+
database: nil
|
24
|
+
)
|
25
|
+
@database_url = database_url
|
26
|
+
@host = host
|
27
|
+
@username = username
|
28
|
+
@password = password
|
29
|
+
@port = port
|
30
|
+
@database = database
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_s
|
34
|
+
"postgresql://#{username}:#{password}@#{host}:#{port}/#{database}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def host
|
38
|
+
@host || (matched[:username] if matched[:host].empty?) || matched[:host]
|
39
|
+
end
|
40
|
+
|
41
|
+
def username
|
42
|
+
@username || ("" if matched[:host].empty? && !matched[:username].empty?) || matched[:username] || ""
|
43
|
+
end
|
44
|
+
|
45
|
+
def password
|
46
|
+
@password || matched[:password] || ""
|
47
|
+
end
|
48
|
+
|
49
|
+
def port
|
50
|
+
@port || matched[:port] || "5432"
|
51
|
+
end
|
52
|
+
|
53
|
+
def database
|
54
|
+
@database || matched[:database] || ""
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def matched
|
60
|
+
@matched ||= database_url.match(URL_REGEX) || {}
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/pgchief/database.rb
CHANGED
@@ -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(
|
9
|
+
conn = PG.connect(Pgchief::DATABASE_URL)
|
10
10
|
result = conn.exec("SELECT datname FROM pg_database WHERE datistemplate = false")
|
11
11
|
result
|
12
12
|
.map { |row| row["datname"] }
|
data/lib/pgchief/prompt/base.rb
CHANGED
@@ -4,16 +4,45 @@ module Pgchief
|
|
4
4
|
module Prompt
|
5
5
|
# Base class for prompt classes
|
6
6
|
class Base
|
7
|
-
def self.
|
8
|
-
|
7
|
+
def self.call(*params)
|
8
|
+
new(*params).call
|
9
9
|
end
|
10
10
|
|
11
|
-
|
11
|
+
attr_reader :params
|
12
|
+
|
13
|
+
def initialize(*params)
|
14
|
+
@params = params
|
15
|
+
end
|
16
|
+
|
17
|
+
def call
|
18
|
+
raise NotImplementedError
|
19
|
+
end
|
20
|
+
|
21
|
+
def klassify(scope, words)
|
12
22
|
Object.const_get([
|
13
23
|
"Pgchief", "::", scope.capitalize, "::",
|
14
24
|
words.split.map(&:capitalize)
|
15
25
|
].flatten.join)
|
16
26
|
end
|
27
|
+
|
28
|
+
def yes_or_no(question, yes: nil, no: nil) # rubocop:disable Naming/MethodParameterName
|
29
|
+
response = prompt.yes?(question)
|
30
|
+
response ? yes&.call : no&.call
|
31
|
+
end
|
32
|
+
|
33
|
+
def prompt
|
34
|
+
@prompt ||= TTY::Prompt.new.tap do |p|
|
35
|
+
p.on(:keypress) do |event|
|
36
|
+
p.trigger(:keydown) if event.value == "j"
|
37
|
+
p.trigger(:keyup) if event.value == "k"
|
38
|
+
end
|
39
|
+
|
40
|
+
p.on(:keyescape) do
|
41
|
+
p.say "\n\nExiting...bye-bye 👋\n\n"
|
42
|
+
exit
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
17
46
|
end
|
18
47
|
end
|
19
48
|
end
|
@@ -4,8 +4,7 @@ module Pgchief
|
|
4
4
|
module Prompt
|
5
5
|
# Class to ask for database name, in order to create it
|
6
6
|
class CreateDatabase < Base
|
7
|
-
def
|
8
|
-
prompt = TTY::Prompt.new
|
7
|
+
def call
|
9
8
|
database = prompt.ask("Database name:")
|
10
9
|
result = Pgchief::Command::DatabaseCreate.call(database)
|
11
10
|
|
@@ -4,13 +4,17 @@ module Pgchief
|
|
4
4
|
module Prompt
|
5
5
|
# Class to prompt for user creation details
|
6
6
|
class CreateUser < Base
|
7
|
-
def
|
8
|
-
prompt = TTY::Prompt.new
|
7
|
+
def call
|
9
8
|
username = prompt.ask("Username:")
|
10
9
|
password = prompt.mask("Password:")
|
11
10
|
result = Pgchief::Command::UserCreate.call(username, password)
|
12
11
|
|
13
12
|
prompt.say result
|
13
|
+
|
14
|
+
yes_or_no(
|
15
|
+
"Give \"#{username}\" access to database(s)?",
|
16
|
+
yes: -> { Pgchief::Prompt::GrantDatabasePrivileges.call(username, password) }
|
17
|
+
)
|
14
18
|
end
|
15
19
|
end
|
16
20
|
end
|
@@ -4,7 +4,7 @@ module Pgchief
|
|
4
4
|
module Prompt
|
5
5
|
# Class to manage database operations
|
6
6
|
class DatabaseManagement < Base
|
7
|
-
def
|
7
|
+
def call
|
8
8
|
prompt = TTY::Prompt.new
|
9
9
|
result = prompt.select("Database management", ["Create database", "Drop database", "Database List"])
|
10
10
|
scope = result == "Database List" ? "command" : "prompt"
|
@@ -4,8 +4,7 @@ module Pgchief
|
|
4
4
|
module Prompt
|
5
5
|
# Class to prompt for which database to drop
|
6
6
|
class DropDatabase < Base
|
7
|
-
def
|
8
|
-
prompt = TTY::Prompt.new
|
7
|
+
def call
|
9
8
|
database = prompt.select("Which database needs to be dropped?", Pgchief::Database.all)
|
10
9
|
result = Pgchief::Command::DatabaseDrop.call(database)
|
11
10
|
|
@@ -4,8 +4,7 @@ module Pgchief
|
|
4
4
|
module Prompt
|
5
5
|
# Class to prompt for which user to drop
|
6
6
|
class DropUser < Base
|
7
|
-
def
|
8
|
-
prompt = TTY::Prompt.new
|
7
|
+
def call
|
9
8
|
user = prompt.select("Which user needs to be deleted?", Pgchief::User.all)
|
10
9
|
result = Pgchief::Command::UserDrop.call(user)
|
11
10
|
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pgchief
|
4
|
+
module Prompt
|
5
|
+
# Class to ask for database names, in order to create it
|
6
|
+
class GrantDatabasePrivileges < Base
|
7
|
+
def call
|
8
|
+
username = params[0] || select_user
|
9
|
+
password = params[1] || ask_for_password
|
10
|
+
databases = params[2] || prompt.multi_select("Give \"#{username}\" access to database(s):",
|
11
|
+
Pgchief::Database.all)
|
12
|
+
|
13
|
+
result = Pgchief::Command::DatabasePrivilegesGrant.call(username, password, databases)
|
14
|
+
prompt.say result
|
15
|
+
end
|
16
|
+
|
17
|
+
def select_user
|
18
|
+
prompt.select("Select user to update:", Pgchief::User.all)
|
19
|
+
end
|
20
|
+
|
21
|
+
def ask_for_password
|
22
|
+
prompt.mask("Password:")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/pgchief/prompt/start.rb
CHANGED
@@ -4,8 +4,9 @@ module Pgchief
|
|
4
4
|
module Prompt
|
5
5
|
# Kicks off the CLI with an initial prompt
|
6
6
|
class Start < Base
|
7
|
-
def
|
8
|
-
|
7
|
+
def call
|
8
|
+
manage_config!
|
9
|
+
|
9
10
|
result = prompt.select(
|
10
11
|
"Welcome! How can I help?",
|
11
12
|
[
|
@@ -16,6 +17,13 @@ module Pgchief
|
|
16
17
|
|
17
18
|
klassify("prompt", result).call
|
18
19
|
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def manage_config!
|
24
|
+
Pgchief::Config.load_config!
|
25
|
+
Pgchief::Config.set_up_file_structure!
|
26
|
+
end
|
19
27
|
end
|
20
28
|
end
|
21
29
|
end
|
@@ -4,9 +4,14 @@ module Pgchief
|
|
4
4
|
module Prompt
|
5
5
|
# Class to manage users
|
6
6
|
class UserManagement < Base
|
7
|
-
def
|
8
|
-
|
9
|
-
|
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"
|
14
|
+
])
|
10
15
|
|
11
16
|
scope = result == "User list" ? "command" : "prompt"
|
12
17
|
klassify(scope, result).call
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pgchief
|
4
|
+
module Prompt
|
5
|
+
# Class to ask for database names, in order to create it
|
6
|
+
class ViewDatabaseConnectionString < Base
|
7
|
+
def call
|
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"
|
11
|
+
result = Pgchief::Command::RetrieveConnectionString.call(username, database)
|
12
|
+
|
13
|
+
prompt.say result
|
14
|
+
end
|
15
|
+
|
16
|
+
def select_user
|
17
|
+
prompt.select("Select user to update:", Pgchief::User.all)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
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(
|
9
|
+
conn = PG.connect(Pgchief::DATABASE_URL)
|
10
10
|
result = conn.exec("SELECT usename FROM pg_user")
|
11
11
|
|
12
12
|
result.map { |row| row["usename"] }.reject { |name| name == "postgres" }
|
data/lib/pgchief/version.rb
CHANGED
data/lib/pgchief.rb
CHANGED
@@ -2,10 +2,15 @@
|
|
2
2
|
|
3
3
|
require "pg"
|
4
4
|
require "tty-prompt"
|
5
|
-
require "
|
5
|
+
require "tty-option"
|
6
6
|
|
7
|
+
require "pgchief/cli"
|
8
|
+
require "pgchief/config"
|
9
|
+
require "pgchief/connection_string"
|
10
|
+
require "pgchief/version"
|
7
11
|
require "pgchief/database"
|
8
12
|
require "pgchief/user"
|
13
|
+
|
9
14
|
require "pgchief/prompt/base"
|
10
15
|
require "pgchief/prompt/start"
|
11
16
|
require "pgchief/prompt/create_database"
|
@@ -14,16 +19,25 @@ require "pgchief/prompt/database_management"
|
|
14
19
|
require "pgchief/prompt/drop_database"
|
15
20
|
require "pgchief/prompt/drop_user"
|
16
21
|
require "pgchief/prompt/user_management"
|
22
|
+
require "pgchief/prompt/grant_database_privileges"
|
23
|
+
require "pgchief/prompt/view_database_connection_string"
|
17
24
|
|
25
|
+
require "pgchief/command"
|
18
26
|
require "pgchief/command/base"
|
27
|
+
require "pgchief/command/config_create"
|
19
28
|
require "pgchief/command/database_create"
|
20
29
|
require "pgchief/command/database_drop"
|
21
30
|
require "pgchief/command/database_list"
|
31
|
+
require "pgchief/command/database_privileges_grant"
|
32
|
+
require "pgchief/command/retrieve_connection_string"
|
33
|
+
require "pgchief/command/store_connection_string"
|
22
34
|
require "pgchief/command/user_create"
|
23
35
|
require "pgchief/command/user_drop"
|
24
36
|
require "pgchief/command/user_list"
|
25
37
|
|
26
38
|
module Pgchief
|
39
|
+
DATABASE_URL = ENV.fetch("DATABASE_URL")
|
40
|
+
|
27
41
|
class Error < StandardError; end
|
28
42
|
|
29
43
|
module Errors
|
data/tmp/.gitkeep
ADDED
File without changes
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pgchief
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.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
|
+
date: 2024-11-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pg
|
@@ -83,16 +83,24 @@ files:
|
|
83
83
|
- LICENSE.txt
|
84
84
|
- README.md
|
85
85
|
- Rakefile
|
86
|
+
- config/pgchief.toml
|
86
87
|
- exe/pgchief
|
87
88
|
- lib/pgchief.rb
|
89
|
+
- lib/pgchief/cli.rb
|
88
90
|
- lib/pgchief/command.rb
|
89
91
|
- lib/pgchief/command/base.rb
|
92
|
+
- lib/pgchief/command/config_create.rb
|
90
93
|
- lib/pgchief/command/database_create.rb
|
91
94
|
- lib/pgchief/command/database_drop.rb
|
92
95
|
- lib/pgchief/command/database_list.rb
|
96
|
+
- lib/pgchief/command/database_privileges_grant.rb
|
97
|
+
- lib/pgchief/command/retrieve_connection_string.rb
|
98
|
+
- lib/pgchief/command/store_connection_string.rb
|
93
99
|
- lib/pgchief/command/user_create.rb
|
94
100
|
- lib/pgchief/command/user_drop.rb
|
95
101
|
- lib/pgchief/command/user_list.rb
|
102
|
+
- lib/pgchief/config.rb
|
103
|
+
- lib/pgchief/connection_string.rb
|
96
104
|
- lib/pgchief/database.rb
|
97
105
|
- lib/pgchief/prompt.rb
|
98
106
|
- lib/pgchief/prompt/base.rb
|
@@ -101,12 +109,14 @@ files:
|
|
101
109
|
- lib/pgchief/prompt/database_management.rb
|
102
110
|
- lib/pgchief/prompt/drop_database.rb
|
103
111
|
- lib/pgchief/prompt/drop_user.rb
|
104
|
-
- lib/pgchief/prompt/
|
112
|
+
- lib/pgchief/prompt/grant_database_privileges.rb
|
105
113
|
- lib/pgchief/prompt/start.rb
|
106
114
|
- lib/pgchief/prompt/user_management.rb
|
115
|
+
- lib/pgchief/prompt/view_database_connection_string.rb
|
107
116
|
- lib/pgchief/user.rb
|
108
117
|
- lib/pgchief/version.rb
|
109
118
|
- sig/pgchief.rbs
|
119
|
+
- tmp/.gitkeep
|
110
120
|
homepage: https://github.com/jayroh/pgchief
|
111
121
|
licenses:
|
112
122
|
- MIT
|
@@ -130,7 +140,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
130
140
|
- !ruby/object:Gem::Version
|
131
141
|
version: '0'
|
132
142
|
requirements: []
|
133
|
-
rubygems_version: 3.5.
|
143
|
+
rubygems_version: 3.5.21
|
134
144
|
signing_key:
|
135
145
|
specification_version: 4
|
136
146
|
summary: A simple ruby script to manage postgresql databases and users
|
@@ -1,15 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Pgchief
|
4
|
-
module Prompt
|
5
|
-
# Class to ask for database names, in order to create it
|
6
|
-
class GrantDatabasePrivileges
|
7
|
-
def self.call(username)
|
8
|
-
databases = Pgchief::Database.all
|
9
|
-
prompt = TTY::Prompt.new
|
10
|
-
databases = prompt.select("Select database:", databases, multiselect: true)
|
11
|
-
Pgchief::Command::GrantDatabasePrivileges.call(username, databases)
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|