pgai 0.2.4 → 1.0.0.alpha2
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/README.md +22 -18
- data/lib/pgai/cli/base.rb +13 -10
- data/lib/pgai/cli/env.rb +27 -7
- data/lib/pgai/cli/main.rb +20 -30
- data/lib/pgai/client.rb +69 -0
- data/lib/pgai/clone_manager.rb +43 -113
- data/lib/pgai/commander.rb +74 -0
- data/lib/pgai/create_clone_service.rb +75 -0
- data/lib/pgai/external_command_manager.rb +41 -0
- data/lib/pgai/port/allocator.rb +43 -0
- data/lib/pgai/port/forwarder.rb +83 -0
- data/lib/pgai/port/manager.rb +27 -0
- data/lib/pgai/psql_manager.rb +37 -0
- data/lib/pgai/resources/attributes.rb +109 -0
- data/lib/pgai/resources/local/base_record.rb +65 -0
- data/lib/pgai/resources/local/clone.rb +39 -0
- data/lib/pgai/resources/local/configuration.rb +17 -0
- data/lib/pgai/resources/local/environment.rb +55 -0
- data/lib/pgai/resources/remote/base_record.rb +37 -0
- data/lib/pgai/resources/remote/clone.rb +114 -0
- data/lib/pgai/resources/remote/clone_metadata.rb +14 -0
- data/lib/pgai/resources/remote/clone_status.rb +12 -0
- data/lib/pgai/resources/remote/db_object.rb +23 -0
- data/lib/pgai/resources/remote/snapshot.rb +31 -0
- data/lib/pgai/store.rb +49 -0
- data/lib/pgai/version.rb +1 -1
- data/lib/pgai.rb +5 -9
- metadata +181 -8
- data/lib/pgai/config.rb +0 -114
- data/lib/pgai/dblab.rb +0 -91
- data/lib/pgai/port_forward.rb +0 -66
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8a9ded7a8c272d713343af77a8fa74c139d47f0bb478ce90bb8154205f9bed00
|
4
|
+
data.tar.gz: 5a2d6c82be207fce031a814bebe10e0015b08e79be471e7a526ee0272fb3f7e8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 71ec50b2d9622e2c65d50ceca0f6405133409b0aac40537084f97f86f10a88ddbce2696c775671d62ac08910d0c7d5695941510b22bae9413e0f5ec8281b2020
|
7
|
+
data.tar.gz: 78f9e1794a40b34ea0d2da5c892d4cbfb5e5883cfb42851b370e3e23f7ebab49cb23da2eea140966be4983d1cad780513eb98ba163125df16beabc06f1ba2fe5
|
data/README.md
CHANGED
@@ -25,35 +25,31 @@ An access token will be required and it can be obtained from: https://console.po
|
|
25
25
|
Example:
|
26
26
|
|
27
27
|
```shell
|
28
|
-
pgai config --
|
28
|
+
pgai config --prefix=<gitlab handle>
|
29
29
|
```
|
30
30
|
|
31
31
|
To configure environments:
|
32
32
|
|
33
33
|
```shell
|
34
|
-
pgai env add --alias ci --id ci
|
34
|
+
pgai env add --alias ci --id ci.lab.internal --port 2345 -n gitlabhq_dblab
|
35
35
|
```
|
36
36
|
|
37
|
-
The environment id
|
37
|
+
The environment id and port can be found by clicking on the `Connect` button on a database instance page.
|
38
38
|
|
39
|
-
|
39
|
+
The the id attributes must be configured for SSH port forwarding via the `~/.ssh/config` file. Example:
|
40
40
|
|
41
|
-
```
|
42
|
-
Host <
|
43
|
-
HostName <domain.postgres.ai>
|
41
|
+
```shell
|
42
|
+
Host <bastion>
|
44
43
|
User <username>
|
45
44
|
IdentityFile ~/.ssh/id_ed25519
|
46
|
-
```
|
47
|
-
|
48
|
-
## dblab binary file
|
49
|
-
|
50
|
-
This CLI is built around around the [`dblab`](https://postgres.ai/docs/how-to-guides/cli/cli-install-init) CLI and it looks for it at the following locations:
|
51
45
|
|
52
|
-
- at the path specified in the config file if specified when running the `pgai config` command
|
53
|
-
- in `$PATH`
|
54
|
-
- at `~/.dblab/dblab`
|
55
46
|
|
56
|
-
|
47
|
+
Host *.lab.internal
|
48
|
+
User <username>
|
49
|
+
PreferredAuthentications publickey
|
50
|
+
IdentityFile ~/.ssh/id_ed25519
|
51
|
+
ProxyCommand ssh <bastion> -W %h:%p
|
52
|
+
```
|
57
53
|
|
58
54
|
## Usage
|
59
55
|
|
@@ -63,10 +59,10 @@ pgai connect <env alias>
|
|
63
59
|
|
64
60
|
Multiple `connect` commands for an environment will connect to the same clone, it won't start a new one.
|
65
61
|
|
66
|
-
`pgai
|
62
|
+
`pgai use --only <env alias> -- command` can be used to run commands with access to a clone. Example for connecting a Rails console to the `CI` database:
|
67
63
|
|
68
64
|
```shell
|
69
|
-
|
65
|
+
pgai use -o ci -v -- bin/rails c -e test
|
70
66
|
```
|
71
67
|
|
72
68
|
### Features
|
@@ -76,6 +72,14 @@ CI_DATABASE_URL="postgresql://foo:bar@localhost:9000/foo_test" bin/rails c -e te
|
|
76
72
|
- automatic port forward management
|
77
73
|
- prevents system sleep while psql sessions are active via `caffeinate`
|
78
74
|
|
75
|
+
## Upgrading from version 0
|
76
|
+
|
77
|
+
- Update and test the ssh config file
|
78
|
+
- `~/.dblab/` and its contents can be removed
|
79
|
+
- `~/.config/pgai/config.pstore` should be removed and recreated with `pgai config`
|
80
|
+
- The environment ID serves as the proxy host and the database name can be configured per environment.
|
81
|
+
- Environments can share the same remote port value.
|
82
|
+
|
79
83
|
## Contributing
|
80
84
|
|
81
85
|
Bug reports and pull requests are welcome on GitLab at https://gitlab.com/mbobin/pgai.
|
data/lib/pgai/cli/base.rb
CHANGED
@@ -2,20 +2,23 @@ require "thor"
|
|
2
2
|
|
3
3
|
module Pgai::Cli
|
4
4
|
class Base < Thor
|
5
|
-
def self.exit_on_failure?
|
6
|
-
true
|
7
|
-
end
|
5
|
+
def self.exit_on_failure? = true
|
8
6
|
|
9
|
-
|
7
|
+
check_unknown_options!
|
8
|
+
|
9
|
+
class_option :verbose, type: :boolean, aliases: "-v", desc: "Detailed logging"
|
10
|
+
class_option :quiet, type: :boolean, aliases: "-q", desc: "Minimal logging"
|
10
11
|
|
11
|
-
def
|
12
|
-
|
12
|
+
def initialize(*)
|
13
|
+
super
|
14
|
+
|
15
|
+
Pgai::Commander.configure(options_with_subcommand_class_options)
|
13
16
|
end
|
14
17
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
18
|
+
private
|
19
|
+
|
20
|
+
def options_with_subcommand_class_options
|
21
|
+
options.merge(@_initializer.last[:class_options] || {})
|
19
22
|
end
|
20
23
|
end
|
21
24
|
end
|
data/lib/pgai/cli/env.rb
CHANGED
@@ -1,22 +1,42 @@
|
|
1
1
|
module Pgai::Cli
|
2
2
|
class Env < Base
|
3
3
|
desc "add", "Add new environment"
|
4
|
-
method_option :alias, aliases: "-a",
|
5
|
-
|
6
|
-
|
4
|
+
method_option :alias, aliases: "-a",
|
5
|
+
desc: "This will be used internally for the connect command",
|
6
|
+
required: true
|
7
|
+
method_option :id,
|
8
|
+
aliases: "-i",
|
9
|
+
desc: "Environment id from postgres.ai, example: lab-ci.db-lab.internal",
|
10
|
+
required: true
|
11
|
+
method_option :port,
|
12
|
+
aliases: "-p",
|
13
|
+
desc: "Environment port from postgres.ai",
|
14
|
+
required: true
|
15
|
+
method_option :dbname,
|
16
|
+
aliases: "-n",
|
17
|
+
desc: "Specify database name to connect to by default",
|
18
|
+
required: true,
|
19
|
+
default: "postgres"
|
7
20
|
def add
|
8
|
-
|
21
|
+
Pgai::Resources::Local::Environment.new(options).save
|
9
22
|
end
|
10
23
|
|
11
24
|
desc "remove", "Remove the specified environment from the config"
|
12
|
-
method_option :alias,
|
25
|
+
method_option :alias,
|
26
|
+
aliases: "-a",
|
27
|
+
desc: "The alias used for registering the environment",
|
28
|
+
required: true
|
13
29
|
def remove
|
14
|
-
|
30
|
+
Pgai::Resources::Local::Environment.delete(options[:alias])
|
15
31
|
end
|
16
32
|
|
17
33
|
desc "list", "List all configured environments"
|
18
34
|
def list
|
19
|
-
|
35
|
+
data = Pgai::Resources::Local::Environment.all.map do |env|
|
36
|
+
env.attributes.slice(:id, :alias, :port, :dbname)
|
37
|
+
end
|
38
|
+
|
39
|
+
say JSON.pretty_generate(data)
|
20
40
|
end
|
21
41
|
end
|
22
42
|
end
|
data/lib/pgai/cli/main.rb
CHANGED
@@ -2,60 +2,50 @@ module Pgai::Cli
|
|
2
2
|
class Main < Base
|
3
3
|
desc "config", "Configure CLI options"
|
4
4
|
method_option :prefix, aliases: "-p", desc: "Specify prefix name to be used for clones", required: true
|
5
|
-
method_option :dbname, aliases: "-n", desc: "Specify database name to connect to by default", required: true, default: "postgres"
|
6
|
-
method_option :proxy, aliases: "-x", desc: "Specify proxy host name", required: true
|
7
|
-
method_option :exe, aliases: "-e", desc: "dblab binary path"
|
8
5
|
def config
|
9
6
|
token = ask("Access token:", echo: false)
|
10
7
|
|
11
|
-
Pgai::
|
8
|
+
Pgai::Resources::Local::Configuration
|
9
|
+
.new(clone_prefix: options[:prefix], access_token: token)
|
10
|
+
.save
|
12
11
|
end
|
13
12
|
|
14
13
|
desc "connect database-name", "Create and connect to a thin clone database"
|
15
14
|
def connect(name)
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
with_env(name) do |env|
|
16
|
+
Pgai::CloneManager.new(env).connect
|
17
|
+
end
|
19
18
|
end
|
20
19
|
|
21
20
|
desc "destroy database-name", "Remove the thin clone and all port forwards"
|
22
21
|
def destroy(name)
|
23
|
-
|
24
|
-
|
25
|
-
|
22
|
+
with_env(name) do |env|
|
23
|
+
Pgai::CloneManager.new(env).cleanup
|
24
|
+
end
|
26
25
|
end
|
27
26
|
|
28
27
|
desc "reset database-name", "Reset clone's state"
|
29
28
|
def reset(name)
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
end
|
34
|
-
|
35
|
-
desc "info database-name", "Show clone details"
|
36
|
-
def info(name)
|
37
|
-
env = configuration.enviroments.fetch(name)
|
38
|
-
|
39
|
-
data = Pgai::CloneManager.new(env, config: configuration).info
|
40
|
-
print_table data
|
29
|
+
with_env(name) do |env|
|
30
|
+
Pgai::CloneManager.new(env).reset
|
31
|
+
end
|
41
32
|
end
|
42
33
|
|
43
34
|
desc "use", "Execute the given command with DATABASE_URLs"
|
44
35
|
method_option :only, aliases: "-o", desc: "Export only this database connection", repeatable: true
|
45
36
|
def use(*command)
|
46
|
-
envs = options.fetch(:only) {
|
47
|
-
|
48
|
-
vars = envs.each_with_object({}) do |env, acc|
|
49
|
-
environment = configuration.enviroments.fetch(env)
|
50
|
-
clone = Pgai::CloneManager.new(environment, config: configuration).clone
|
51
|
-
|
52
|
-
acc["#{env.to_s.upcase}_DATABASE_URL"] = clone.database_url
|
53
|
-
end
|
37
|
+
envs = options.fetch(:only) { Pgai::Resources::Local::Environment.all.map(&:alias) }
|
54
38
|
|
55
|
-
|
39
|
+
Pgai::ExternalCommandManager.new(envs, command).run
|
56
40
|
end
|
57
41
|
|
58
42
|
desc "env", "Manage environments"
|
59
43
|
subcommand "env", Pgai::Cli::Env
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def with_env(name, &block)
|
48
|
+
Pgai::Commander.instance.with_env(name, &block)
|
49
|
+
end
|
60
50
|
end
|
61
51
|
end
|
data/lib/pgai/client.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "excon"
|
4
|
+
|
5
|
+
module Pgai
|
6
|
+
class Client
|
7
|
+
def initialize(token:, host: "http://127.0.0.1:2355")
|
8
|
+
@client = Excon.new(host, headers: {"Verification-Token" => token})
|
9
|
+
end
|
10
|
+
|
11
|
+
def version
|
12
|
+
get("healthz").fetch(:version)
|
13
|
+
end
|
14
|
+
|
15
|
+
def expected_cloning_time
|
16
|
+
get("status").dig(:cloning, :expected_cloning_time)
|
17
|
+
end
|
18
|
+
|
19
|
+
def get(path)
|
20
|
+
response = client.get(path: path)
|
21
|
+
parse_reponse(response.body)
|
22
|
+
end
|
23
|
+
|
24
|
+
def post(path, body = nil)
|
25
|
+
response = if body
|
26
|
+
client.post(path: path, body: JSON.dump(body))
|
27
|
+
else
|
28
|
+
client.post(path: path)
|
29
|
+
end
|
30
|
+
return {} if response.body.empty?
|
31
|
+
|
32
|
+
parse_reponse(response.body)
|
33
|
+
end
|
34
|
+
|
35
|
+
def delete(path)
|
36
|
+
response = client.delete(path: path)
|
37
|
+
return {} if response.body.empty?
|
38
|
+
|
39
|
+
parse_reponse(response.body)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
attr_reader :client
|
45
|
+
|
46
|
+
def parse_reponse(data)
|
47
|
+
data = JSON.parse(data)
|
48
|
+
transform!(data)
|
49
|
+
data
|
50
|
+
end
|
51
|
+
|
52
|
+
def transform_hash!(data)
|
53
|
+
data.transform_keys! { |key| key.to_s.gsub(/(?<=[A-Z])(?=[A-Z][a-z])|(?<=[a-z\d])(?=[A-Z])/, "_").downcase.to_sym }
|
54
|
+
data.each_value { |value| transform!(value) }
|
55
|
+
end
|
56
|
+
|
57
|
+
def transform_array!(data)
|
58
|
+
data.each { transform!(_1) }
|
59
|
+
end
|
60
|
+
|
61
|
+
def transform!(data)
|
62
|
+
if data.is_a?(Hash)
|
63
|
+
transform_hash!(data)
|
64
|
+
elsif data.is_a?(Array)
|
65
|
+
transform_array!(data)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/pgai/clone_manager.rb
CHANGED
@@ -1,151 +1,81 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "securerandom"
|
4
|
-
|
5
3
|
module Pgai
|
6
4
|
class CloneManager
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
@environment = environment
|
11
|
-
@config = config
|
12
|
-
@port_forward = PortForward.new(config: config, hostname: HOSTNAME)
|
13
|
-
@dblab = Dblab.new(config: config, hostname: HOSTNAME)
|
5
|
+
def initialize(env)
|
6
|
+
@env = env
|
7
|
+
@logger = Pgai::Commander.instance.logger
|
14
8
|
end
|
15
9
|
|
16
10
|
def connect
|
17
|
-
|
11
|
+
prepare do |cached_clone|
|
12
|
+
PsqlManager.new(
|
13
|
+
cached_clone,
|
14
|
+
logger: logger
|
15
|
+
).run
|
16
|
+
end
|
17
|
+
end
|
18
18
|
|
19
|
-
|
19
|
+
def prepare
|
20
|
+
find_or_create_clone.with_port_forward do |cached_clone|
|
21
|
+
yield cached_clone
|
22
|
+
end
|
20
23
|
end
|
21
24
|
|
22
25
|
def cleanup
|
23
|
-
|
24
|
-
return unless find_raw_clone
|
26
|
+
return unless clone_already_created?
|
25
27
|
|
26
|
-
|
27
|
-
|
28
|
-
|
28
|
+
Resources::Local::Clone.delete(clone_id)
|
29
|
+
Resources::Remote::Clone.find(clone_id).tap do |resource|
|
30
|
+
resource.delete
|
31
|
+
resource.refresh
|
32
|
+
logger.info resource.status_message
|
33
|
+
end
|
29
34
|
end
|
30
35
|
|
31
36
|
def reset
|
32
|
-
|
33
|
-
return unless find_raw_clone
|
34
|
-
|
35
|
-
dblab.reset_clone(id: clone_id)
|
36
|
-
end
|
37
|
-
|
38
|
-
def info
|
39
|
-
configure_enviroment
|
40
|
-
|
41
|
-
clone = find_clone
|
42
|
-
return {} unless clone
|
37
|
+
return unless clone_already_created?
|
43
38
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
rails_database_url: database_url,
|
49
|
-
created_at: clone.created_at,
|
50
|
-
data_state_at: clone.data_state_at
|
51
|
-
}
|
52
|
-
end
|
53
|
-
|
54
|
-
def clone
|
55
|
-
configure_enviroment
|
56
|
-
clone = find_or_create_clone
|
57
|
-
port_forward.start(clone.port)
|
58
|
-
clone
|
39
|
+
Resources::Remote::Clone.find(clone_id).tap do |resource|
|
40
|
+
resource.reset
|
41
|
+
logger.info resource.status_message
|
42
|
+
end
|
59
43
|
end
|
60
44
|
|
61
45
|
private
|
62
46
|
|
63
|
-
attr_reader :
|
47
|
+
attr_reader :env, :logger
|
64
48
|
|
65
|
-
def
|
66
|
-
|
67
|
-
dblab.configure_env(port: enviroment_port, token: config.access_token, id: environment_id)
|
49
|
+
def clone_id
|
50
|
+
env.clone_id
|
68
51
|
end
|
69
52
|
|
70
53
|
def find_or_create_clone
|
71
|
-
|
54
|
+
find_cached_clone || create_clone
|
72
55
|
end
|
73
56
|
|
74
|
-
def
|
75
|
-
|
76
|
-
|
77
|
-
if raw_clone
|
78
|
-
config.find_clone(clone_id) || raise(Pgai::CloneNotFount, "clone not tracked")
|
57
|
+
def find_cached_clone
|
58
|
+
if clone_already_created?
|
59
|
+
Resources::Local::Clone.find(clone_id) || raise_unknown_clone
|
79
60
|
else
|
80
|
-
|
61
|
+
Resources::Local::Clone.delete(clone_id) && nil
|
81
62
|
end
|
82
63
|
end
|
83
64
|
|
84
|
-
def find_raw_clone
|
85
|
-
dblab.list_clones.find { |clone| clone["id"] == clone_id }
|
86
|
-
end
|
87
|
-
|
88
65
|
def create_clone
|
89
|
-
|
90
|
-
|
91
|
-
attributes = {
|
92
|
-
port: raw_clone.dig("db", "port"),
|
93
|
-
password: clone_password,
|
94
|
-
user: clone_user,
|
95
|
-
host: HOSTNAME,
|
96
|
-
dbname: config.dbname,
|
97
|
-
created_at: raw_clone["createdAt"],
|
98
|
-
data_state_at: raw_clone.dig("snapshot", "dataStateAt")
|
99
|
-
}
|
100
|
-
|
101
|
-
config.persist_clone(clone_id, attributes)
|
102
|
-
end
|
103
|
-
|
104
|
-
def psql(clone)
|
105
|
-
puts "Data state at: #{clone.data_state_at}"
|
106
|
-
port_forward.start(clone.port)
|
107
|
-
|
108
|
-
psql_pid = fork do
|
109
|
-
exec("psql #{clone.connection_string}")
|
110
|
-
end
|
111
|
-
|
112
|
-
Signal.trap("INT") {}
|
113
|
-
start_caffeinate(psql_pid)
|
114
|
-
Process.wait(psql_pid)
|
115
|
-
ensure
|
116
|
-
port_forward.conditionally_stop(clone.port, [psql_pid])
|
117
|
-
port_forward.conditionally_stop(enviroment_port)
|
118
|
-
end
|
119
|
-
|
120
|
-
def environment_id
|
121
|
-
environment.fetch(:id)
|
122
|
-
end
|
123
|
-
|
124
|
-
def enviroment_port
|
125
|
-
environment.fetch(:port)
|
66
|
+
CreateCloneService.new(clone_id, env: env, logger: logger).execute
|
126
67
|
end
|
127
68
|
|
128
|
-
def
|
129
|
-
|
69
|
+
def raise_unknown_clone
|
70
|
+
raise(Pgai::ResourceNotFound, <<~MESSAGE)
|
71
|
+
Unknown clone. Remove it and retry.
|
72
|
+
MESSAGE
|
130
73
|
end
|
131
74
|
|
132
|
-
def
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
def clone_user
|
137
|
-
@clone_user ||= SecureRandom.hex(8)
|
138
|
-
end
|
139
|
-
|
140
|
-
def clone_password
|
141
|
-
@clone_password ||= SecureRandom.hex(16)
|
142
|
-
end
|
143
|
-
|
144
|
-
def start_caffeinate(pid)
|
145
|
-
return if `which caffeinate`.to_s.empty?
|
146
|
-
|
147
|
-
caffeinate_pid = Process.spawn("caffeinate -is -w #{pid}")
|
148
|
-
Process.detach(caffeinate_pid)
|
75
|
+
def clone_already_created?
|
76
|
+
!!Resources::Remote::Clone.find(clone_id)
|
77
|
+
rescue Pgai::ResourceNotFound
|
78
|
+
false
|
149
79
|
end
|
150
80
|
end
|
151
81
|
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "tty-logger"
|
4
|
+
require "singleton"
|
5
|
+
require "forwardable"
|
6
|
+
|
7
|
+
module Pgai
|
8
|
+
class Commander
|
9
|
+
include Singleton
|
10
|
+
extend Forwardable
|
11
|
+
|
12
|
+
attr_reader :verbosity
|
13
|
+
def_delegators :logger, *TTY::Logger::LOG_TYPES.keys
|
14
|
+
|
15
|
+
def self.configure(options)
|
16
|
+
instance.tap do |commander|
|
17
|
+
if options[:verbose]
|
18
|
+
commander.verbosity = :debug
|
19
|
+
end
|
20
|
+
|
21
|
+
if options[:quiet]
|
22
|
+
commander.verbosity = :error
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize
|
28
|
+
self.verbosity = :info
|
29
|
+
end
|
30
|
+
|
31
|
+
def store
|
32
|
+
@store ||= Pgai::Store.new
|
33
|
+
end
|
34
|
+
|
35
|
+
def config
|
36
|
+
@config ||= Pgai::Resources::Local::Configuration.default
|
37
|
+
end
|
38
|
+
|
39
|
+
def configure
|
40
|
+
yield self
|
41
|
+
end
|
42
|
+
|
43
|
+
def verbosity=(verbosity)
|
44
|
+
@verbosity = verbosity
|
45
|
+
|
46
|
+
TTY::Logger.configure do |config|
|
47
|
+
config.level = verbosity
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def logger
|
52
|
+
@logger ||= TTY::Logger.new
|
53
|
+
end
|
54
|
+
|
55
|
+
def port_manager
|
56
|
+
@port_manager ||= Port::Manager.new(logger: logger)
|
57
|
+
end
|
58
|
+
|
59
|
+
def check_access_token_presence!
|
60
|
+
raise "Access token is not set" unless config&.access_token
|
61
|
+
end
|
62
|
+
|
63
|
+
def with_env(env_name, &block)
|
64
|
+
check_access_token_presence!
|
65
|
+
|
66
|
+
env = Resources::Local::Environment.find(env_name) do |env|
|
67
|
+
env.access_token = config.access_token
|
68
|
+
env.clone_prefix = config.clone_prefix
|
69
|
+
end
|
70
|
+
|
71
|
+
env.prepare(&block)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "tty-progressbar"
|
4
|
+
|
5
|
+
module Pgai
|
6
|
+
class CreateCloneService
|
7
|
+
HOSTNAME = "127.0.0.1"
|
8
|
+
CLONE_REFRESH_INTERVAL = 0.2
|
9
|
+
|
10
|
+
attr_reader :id, :env, :logger
|
11
|
+
|
12
|
+
def initialize(id, env:, logger:)
|
13
|
+
@id = id
|
14
|
+
@env = env
|
15
|
+
@logger = logger
|
16
|
+
end
|
17
|
+
|
18
|
+
def execute
|
19
|
+
create_clone_resource
|
20
|
+
logger.debug(clone_resource.status_message)
|
21
|
+
progress_bar.advance
|
22
|
+
wait_for_clone_to_be_ready
|
23
|
+
logger.debug(clone_resource.status_message)
|
24
|
+
|
25
|
+
Resources::Local::Clone.new(cached_clone_attributes).tap(&:save)
|
26
|
+
end
|
27
|
+
|
28
|
+
def create_clone_resource
|
29
|
+
@clone_resource = Resources::Remote::Clone.new(id: id).tap do |resource|
|
30
|
+
resource.db_object = Resources::Remote::DbObject.new(db_name: env.dbname)
|
31
|
+
resource.snapshot = Resources::Remote::Snapshot.latest
|
32
|
+
resource.save
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def cached_clone_attributes
|
37
|
+
{
|
38
|
+
id: id,
|
39
|
+
port: clone_resource.db_object.port,
|
40
|
+
password: clone_resource.db_object.password,
|
41
|
+
user: clone_resource.db_object.username,
|
42
|
+
host: HOSTNAME,
|
43
|
+
remote_host: env.id,
|
44
|
+
dbname: env.dbname,
|
45
|
+
created_at: clone_resource.created_at,
|
46
|
+
data_state_at: clone_resource.snapshot.data_state_at
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
def clone_resource
|
51
|
+
@clone_resource ||= create_clone_resource
|
52
|
+
end
|
53
|
+
|
54
|
+
def wait_for_clone_to_be_ready
|
55
|
+
loop do
|
56
|
+
clone_resource.refresh
|
57
|
+
progress_bar.advance
|
58
|
+
break if clone_resource.ready?
|
59
|
+
sleep(CLONE_REFRESH_INTERVAL)
|
60
|
+
end
|
61
|
+
progress_bar.finish
|
62
|
+
end
|
63
|
+
|
64
|
+
def progress_bar
|
65
|
+
@progress_bar ||= TTY::ProgressBar.new(
|
66
|
+
"Preparing clone ... [:bar]",
|
67
|
+
total: progress_bar_steps
|
68
|
+
)
|
69
|
+
end
|
70
|
+
|
71
|
+
def progress_bar_steps
|
72
|
+
(env.expected_cloning_time / CLONE_REFRESH_INTERVAL).ceil + 1
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|