pgchief 0.1.0 → 0.3.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: 72ba9fb5a28f3128a49fa7944b30154be2de8b785a24295bae9c3b4544c29c3d
4
- data.tar.gz: b049ed99e5365e1fd78d727947950aa99e3e5426d9aa76bb4fd77ffdea091d73
3
+ metadata.gz: 756ccf173c3d0cbe56bb969e2f7d20e9032e1a7b1c600155e212548fa81f93bb
4
+ data.tar.gz: c00f369d5e6013bedb955daba82da8c54ad2a77c978b2ead411cae3ef0ca5f0f
5
5
  SHA512:
6
- metadata.gz: b8cfe8f92033c986892e551b8cdc6ff1f7847ed91010ec38ffdd73d2283000b02ae70b9669a79eb4509d5c69c2449fd8f8891d2a5d7a75ccf2372ab141f83087
7
- data.tar.gz: d8ee3dadb057da4338ab8467040f286c4de2f4383a62a162204de8a686da42734aab4ccfe7e768dc53ef94962b432c3e4be8269129f2ce10bde727bd31129318
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
- ## [0.1.0] - 2024-07-20
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
- * `DATABASE_URL` to point to your main pg instance's superuser account with a connection string.
42
- * `TEST_DATABASE_URL` to point to your local pg instance where tests can be run against.
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
- - [x] Create database
105
- - [x] Create user
106
- - [x] Drop database
107
- - [x] Drop user
108
- - [x] List databases
109
- - [ ] Give user permissions to use database
110
- - [ ] Back up database
111
- - [ ] Restore database
112
- - [ ] Initialize toml file
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
@@ -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
@@ -7,4 +7,6 @@ $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
7
7
 
8
8
  require "pgchief"
9
9
 
10
- Pgchief::Prompt::Start.call
10
+ cli = Pgchief::Cli.new
11
+ cli.parse(ARGV)
12
+ cli.run
@@ -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
@@ -12,7 +12,7 @@ module Pgchief
12
12
 
13
13
  def initialize(*params)
14
14
  @params = params
15
- @conn = PG.connect(ENV.fetch("DATABASE_URL"))
15
+ @conn = PG.connect(Pgchief::DATABASE_URL)
16
16
  end
17
17
 
18
18
  def call
@@ -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
- conn.exec("CREATE USER #{username} WITH #{user_options} PASSWORD '#{password}'")
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
- conn.exec("DROP USER #{username}")
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
@@ -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(ENV.fetch("DATABASE_URL"))
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"] }
@@ -4,16 +4,45 @@ module Pgchief
4
4
  module Prompt
5
5
  # Base class for prompt classes
6
6
  class Base
7
- def self.class
8
- raise "Method not defined"
7
+ def self.call(*params)
8
+ new(*params).call
9
9
  end
10
10
 
11
- def self.klassify(scope, words)
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 self.call
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 self.call
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 self.call
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 self.call
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 self.call
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
@@ -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 self.call
8
- prompt = TTY::Prompt.new
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 self.call
8
- prompt = TTY::Prompt.new
9
- result = prompt.select("User management", ["Create user", "Drop user", "User list"])
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(ENV.fetch("DATABASE_URL"))
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" }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pgchief
4
- VERSION = "0.1.0"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/pgchief.rb CHANGED
@@ -2,10 +2,15 @@
2
2
 
3
3
  require "pg"
4
4
  require "tty-prompt"
5
- require "pgchief/version"
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.1.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-08-30 00:00:00.000000000 Z
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/grant_database_priveleges.rb
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.11
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