aptible-cli 0.16.4 → 0.16.9

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 67535773b4563abf1e922214aac67f15ea7678dc60e00d56b741cd88c0af3e90
4
- data.tar.gz: df21627d7393072178aab1842a55e1a4ef405ce0dabd426745d709c3061fc60b
3
+ metadata.gz: 0c9170855dbd5cf2000e91c545d019cc2e3b2ed5115f1a0c5baa7ae9961d1479
4
+ data.tar.gz: '07528b128af3da4c9b21e8bc17e07ed3184f0e58cb367804435bc48600dbc6dd'
5
5
  SHA512:
6
- metadata.gz: c99d5c69e0e0ef0561a590395a87a8eba2fcbd36fcc8be8137b9ad0e460f963d2abe2044e90533781a9d6a3470c4cf8ed713a29efea854f77d1809e32d0e37d7
7
- data.tar.gz: 1ec9225e2092a68ec40552b5602254498e4ec1f50ab602c25cbb2a6ac91691fe711af1d2580dcb178c73b2117f415ed936a4d5c8889943cf1d8b0b2153e5112c
6
+ metadata.gz: 2c145b617a27c9eb6fbda60b4953e12252c4d64b3ecc6d32493ad3f87f5a146567424f812cb495064974bdda7ece202bdbfbadc338a1f2252ace2e93451231f5
7
+ data.tar.gz: 5c200a84cdcfaebb37b45959d6d90177aec02218f3f53bff08f7d60127975c1eed5355b60340f91474bef3fb2df5de9bcf82c0ca930b6fb8d18775c225475fb1
@@ -1,3 +1,4 @@
1
+ dist: xenial
1
2
  sudo: false
2
3
 
3
4
  rvm:
data/Gemfile CHANGED
@@ -6,7 +6,6 @@ gem 'rack', '~> 1.0'
6
6
 
7
7
  group :test do
8
8
  gem 'webmock'
9
- gem 'codecov', require: false
10
9
  end
11
10
 
12
11
  # Specify your gem's dependencies in aptible-cli.gemspec
data/README.md CHANGED
@@ -3,7 +3,6 @@
3
3
  [![Gem Version](https://badge.fury.io/rb/aptible-cli.png)](https://rubygems.org/gems/aptible-cli)
4
4
  [![Build Status](https://travis-ci.org/aptible/aptible-cli.png?branch=master)](https://travis-ci.org/aptible/aptible-cli)
5
5
  [![Dependency Status](https://gemnasium.com/aptible/aptible-cli.png)](https://gemnasium.com/aptible/aptible-cli)
6
- [![codecov](https://codecov.io/gh/aptible/aptible-cli/branch/master/graph/badge.svg)](https://codecov.io/gh/aptible/aptible-cli)
7
6
  [![Roadmap](https://badge.waffle.io/aptible/aptible-cli.svg?label=ready&title=roadmap)](http://waffle.io/aptible/aptible-cli)
8
7
 
9
8
  Command-line interface for Aptible services.
@@ -29,53 +28,54 @@ From `aptible help`:
29
28
  <!-- BEGIN USAGE -->
30
29
  ```
31
30
  Commands:
32
- aptible apps # List all applications
33
- aptible apps:create HANDLE # Create a new application
34
- aptible apps:deprovision # Deprovision an app
35
- aptible apps:scale SERVICE [--container-count COUNT] [--container-size SIZE_MB] # Scale a service
36
- aptible backup:list DB_HANDLE # List backups for a database
37
- aptible backup:purge BACKUP_ID # Restore a backup
38
- aptible backup:restore BACKUP_ID [--environment ENVIRONMENT_HANDLE] [--handle HANDLE] [--container-size SIZE_MB] [--disk-size SIZE_GB] # Restore a backup
39
- aptible config # Print an app's current configuration
40
- aptible config:add [VAR1=VAL1] [VAR2=VAL2] [...] # Add an ENV variable to an app
41
- aptible config:rm [VAR1] [VAR2] [...] # Remove an ENV variable from an app
42
- aptible config:set [VAR1=VAL1] [VAR2=VAL2] [...] # Add an ENV variable to an app
43
- aptible config:unset [VAR1] [VAR2] [...] # Remove an ENV variable from an app
44
- aptible db:backup HANDLE # Backup a database
45
- aptible db:clone SOURCE DEST # Clone a database to create a new one
46
- aptible db:create HANDLE [--type TYPE] [--version VERSION] [--container-size SIZE_MB] [--disk-size SIZE_GB] # Create a new database
47
- aptible db:deprovision HANDLE # Deprovision a database
48
- aptible db:dump HANDLE [pg_dump options] # Dump a remote database to file
49
- aptible db:execute HANDLE SQL_FILE [--on-error-stop] # Executes sql against a database
50
- aptible db:list # List all databases
51
- aptible db:reload HANDLE # Reload a database
52
- aptible db:replicate HANDLE REPLICA_HANDLE [--container-size SIZE_MB] [--disk-size SIZE_GB] # Create a replica/follower of a database
53
- aptible db:restart HANDLE [--container-size SIZE_MB] [--disk-size SIZE_GB] # Restart a database
54
- aptible db:tunnel HANDLE # Create a local tunnel to a database
55
- aptible db:url HANDLE # Display a database URL
56
- aptible db:versions # List available database versions
57
- aptible deploy [OPTIONS] [VAR1=VAL1] [VAR2=VAL2] [...] # Deploy an app
58
- aptible domains # Print an app's current virtual domains - DEPRECATED
59
- aptible endpoints:database:create DATABASE # Create a Database Endpoint
60
- aptible endpoints:deprovision [--app APP | --database DATABASE] ENDPOINT_HOSTNAME # Deprovision an App or Database Endpoint
61
- aptible endpoints:https:create [--app APP] SERVICE # Create an App HTTPS Endpoint
62
- aptible endpoints:https:modify [--app APP] ENDPOINT_HOSTNAME # Modify an App HTTPS Endpoint
63
- aptible endpoints:list [--app APP | --database DATABASE] # List Endpoints for an App or Database
64
- aptible endpoints:renew [--app APP] ENDPOINT_HOSTNAME # Renew an App Managed TLS Endpoint
65
- aptible endpoints:tcp:create [--app APP] SERVICE # Create an App TCP Endpoint
66
- aptible endpoints:tcp:modify [--app APP] ENDPOINT_HOSTNAME # Modify an App TCP Endpoint
67
- aptible endpoints:tls:create [--app APP] SERVICE # Create an App TLS Endpoint
68
- aptible endpoints:tls:modify [--app APP] ENDPOINT_HOSTNAME # Modify an App TLS Endpoint
69
- aptible help [COMMAND] # Describe available commands or one specific command
70
- aptible login # Log in to Aptible
71
- aptible logs [--app APP | --database DATABASE] # Follows logs from a running app or database
72
- aptible operation:cancel OPERATION_ID # Cancel a running operation
73
- aptible ps # Display running processes for an app - DEPRECATED
74
- aptible rebuild # Rebuild an app, and restart its services
75
- aptible restart # Restart all services associated with an app
76
- aptible services # List Services for an App
77
- aptible ssh [COMMAND] # Run a command against an app
78
- aptible version # Print Aptible CLI version
31
+ aptible apps # List all applications
32
+ aptible apps:create HANDLE # Create a new application
33
+ aptible apps:deprovision # Deprovision an app
34
+ aptible apps:scale SERVICE [--container-count COUNT] [--container-size SIZE_MB] # Scale a service
35
+ aptible backup:list DB_HANDLE # List backups for a database
36
+ aptible backup:orphaned # List backups associated with deprovisioned databases
37
+ aptible backup:purge BACKUP_ID # Permanently delete a backup and any copies of it
38
+ aptible backup:restore BACKUP_ID [--environment ENVIRONMENT_HANDLE] [--handle HANDLE] [--container-size SIZE_MB] [--disk-size SIZE_GB] [--key-arn KEY_ARN] # Restore a backup
39
+ aptible config # Print an app's current configuration
40
+ aptible config:add [VAR1=VAL1] [VAR2=VAL2] [...] # Add an ENV variable to an app
41
+ aptible config:rm [VAR1] [VAR2] [...] # Remove an ENV variable from an app
42
+ aptible config:set [VAR1=VAL1] [VAR2=VAL2] [...] # Add an ENV variable to an app
43
+ aptible config:unset [VAR1] [VAR2] [...] # Remove an ENV variable from an app
44
+ aptible db:backup HANDLE # Backup a database
45
+ aptible db:clone SOURCE DEST # Clone a database to create a new one
46
+ aptible db:create HANDLE [--type TYPE] [--version VERSION] [--container-size SIZE_MB] [--disk-size SIZE_GB] [--key-arn KEY_ARN] # Create a new database
47
+ aptible db:deprovision HANDLE # Deprovision a database
48
+ aptible db:dump HANDLE [pg_dump options] # Dump a remote database to file
49
+ aptible db:execute HANDLE SQL_FILE [--on-error-stop] # Executes sql against a database
50
+ aptible db:list # List all databases
51
+ aptible db:reload HANDLE # Reload a database
52
+ aptible db:replicate HANDLE REPLICA_HANDLE [--container-size SIZE_MB] [--disk-size SIZE_GB] [--logical --version VERSION] [--key-arn KEY_ARN] # Create a replica/follower of a database
53
+ aptible db:restart HANDLE [--container-size SIZE_MB] [--disk-size SIZE_GB] # Restart a database
54
+ aptible db:tunnel HANDLE # Create a local tunnel to a database
55
+ aptible db:url HANDLE # Display a database URL
56
+ aptible db:versions # List available database versions
57
+ aptible deploy [OPTIONS] [VAR1=VAL1] [VAR2=VAL2] [...] # Deploy an app
58
+ aptible domains # Print an app's current virtual domains - DEPRECATED
59
+ aptible endpoints:database:create DATABASE # Create a Database Endpoint
60
+ aptible endpoints:deprovision [--app APP | --database DATABASE] ENDPOINT_HOSTNAME # Deprovision an App or Database Endpoint
61
+ aptible endpoints:https:create [--app APP] SERVICE # Create an App HTTPS Endpoint
62
+ aptible endpoints:https:modify [--app APP] ENDPOINT_HOSTNAME # Modify an App HTTPS Endpoint
63
+ aptible endpoints:list [--app APP | --database DATABASE] # List Endpoints for an App or Database
64
+ aptible endpoints:renew [--app APP] ENDPOINT_HOSTNAME # Renew an App Managed TLS Endpoint
65
+ aptible endpoints:tcp:create [--app APP] SERVICE # Create an App TCP Endpoint
66
+ aptible endpoints:tcp:modify [--app APP] ENDPOINT_HOSTNAME # Modify an App TCP Endpoint
67
+ aptible endpoints:tls:create [--app APP] SERVICE # Create an App TLS Endpoint
68
+ aptible endpoints:tls:modify [--app APP] ENDPOINT_HOSTNAME # Modify an App TLS Endpoint
69
+ aptible help [COMMAND] # Describe available commands or one specific command
70
+ aptible login # Log in to Aptible
71
+ aptible logs [--app APP | --database DATABASE] # Follows logs from a running app or database
72
+ aptible operation:cancel OPERATION_ID # Cancel a running operation
73
+ aptible ps # Display running processes for an app - DEPRECATED
74
+ aptible rebuild # Rebuild an app, and restart its services
75
+ aptible restart # Restart all services associated with an app
76
+ aptible services # List Services for an App
77
+ aptible ssh [COMMAND] # Run a command against an app
78
+ aptible version # Print Aptible CLI version
79
79
  ```
80
80
  <!-- END USAGE -->
81
81
 
@@ -8,12 +8,6 @@ environment:
8
8
  - RUBY_VERSION: 23
9
9
 
10
10
  install:
11
- # The SSL_CERT_* environment variables are here since otherwise calls to
12
- # codecov.io wtill not work. These variables do have to be set in order for
13
- # the gem to make calls to the Aptible API, since otherwise Ruby will fail
14
- # with a certificate verification error.
15
- - set SSL_CERT_DIR=%PROGRAMFILES%\Git\mingw64\ssl\certs
16
- - set SSL_CERT_FILE=%PROGRAMFILES%\Git\mingw64\ssl\cert.pem
17
11
  # Override PATHEXT so our ssh bat file has a higher precedence.
18
12
  - set PATHEXT=.BAT;.COM;.EXE;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC
19
13
  - set PATH=C:\Ruby%RUBY_VERSION%-x64\bin;%PATH%
@@ -28,4 +22,4 @@ before_test:
28
22
  - bundle -v
29
23
 
30
24
  test_script:
31
- - bundle exec rake ci
25
+ - bundle exec rake ci
@@ -21,8 +21,8 @@ Gem::Specification.new do |spec|
21
21
  spec.require_paths = ['lib']
22
22
 
23
23
  spec.add_dependency 'aptible-resource', '~> 1.1'
24
- spec.add_dependency 'aptible-api', '~> 1.0'
25
- spec.add_dependency 'aptible-auth', '~> 1.0'
24
+ spec.add_dependency 'aptible-api', '~> 1.2'
25
+ spec.add_dependency 'aptible-auth', '~> 1.2.3'
26
26
  spec.add_dependency 'aptible-billing', '~> 1.0'
27
27
  spec.add_dependency 'thor', '~> 0.20.0'
28
28
  spec.add_dependency 'git'
@@ -10,7 +10,7 @@ end
10
10
  begin
11
11
  Aptible::CLI::Agent.start
12
12
  rescue HyperResource::ClientError => e
13
- m = if e.body['error'] == 'invalid_token'
13
+ m = if %w(invalid_token expired_token).include? e.body['error']
14
14
  'API authentication error: please run aptible login'
15
15
  else
16
16
  "An error occurred: #{e.body['message']}"
@@ -67,6 +67,7 @@ module Aptible
67
67
  def initialize(*)
68
68
  nag_toolbelt unless toolbelt?
69
69
  Aptible::Resource.configure { |conf| conf.user_agent = version_string }
70
+ warn_sso_enforcement
70
71
  super
71
72
  end
72
73
 
@@ -232,6 +233,19 @@ module Aptible
232
233
  end
233
234
  end
234
235
 
236
+ def warn_sso_enforcement
237
+ # If the user is also a member of
238
+ token = fetch_token
239
+ reauth = Aptible::Auth::ReauthenticateOrganization.all(token: token)
240
+ return if reauth.empty?
241
+
242
+ CLI.logger.warn(['WARNING: You will need to use the appropriate',
243
+ 'login method (SSO or Aptible credentials) to access',
244
+ 'these organizations:',
245
+ reauth.map(&:name)].join(' '))
246
+ rescue StandardError
247
+ end
248
+
235
249
  def version_string
236
250
  bits = [
237
251
  'aptible-cli',
@@ -49,11 +49,20 @@ module Aptible
49
49
 
50
50
  def replicate_database(source, dest_handle, options)
51
51
  replication_params = {
52
- type: 'replicate',
53
52
  handle: dest_handle,
54
53
  container_size: options[:container_size],
55
- disk_size: options[:size]
54
+ disk_size: options[:size],
55
+ key_arn: options[:key_arn]
56
56
  }.reject { |_, v| v.nil? }
57
+
58
+ if options[:logical]
59
+ replication_params[:type] = 'replicate_logical'
60
+ replication_params[:docker_ref] =
61
+ options[:database_image].docker_repo
62
+ else
63
+ replication_params[:type] = 'replicate'
64
+ end
65
+
57
66
  op = source.create_operation!(replication_params)
58
67
  attach_to_operation_logs(op)
59
68
 
@@ -96,10 +105,11 @@ module Aptible
96
105
 
97
106
  def local_url(credential, local_port)
98
107
  remote_url = credential.connection_url
99
- uri = URI.parse(remote_url)
100
108
 
109
+ uri = URI.parse(remote_url)
110
+ domain = credential.database.account.stack.internal_domain
101
111
  "#{uri.scheme}://#{uri.user}:#{uri.password}@" \
102
- "localhost.aptible.in:#{local_port}#{uri.path}"
112
+ "localhost.#{domain}:#{local_port}#{uri.path}"
103
113
  end
104
114
 
105
115
  def find_credential(database, type = nil)
@@ -4,6 +4,41 @@ module Aptible
4
4
  class << self
5
5
  NO_NESTING = Object.new.freeze
6
6
 
7
+ def inject_backup(node, backup, include_db: false)
8
+ description = "#{backup.id}: #{backup.created_at}, " \
9
+ "#{backup.aws_region}"
10
+
11
+ if include_db
12
+ db = backup.database_with_deleted
13
+ node.keyed_object('database', 'id') do |n|
14
+ inject_deleted_database(n, db, backup.account)
15
+ end
16
+
17
+ description = "#{description}, " \
18
+ "#{db.handle} deleted at #{db.deleted_at}"
19
+ end
20
+
21
+ node.value('id', backup.id)
22
+ node.value('description', description)
23
+ node.value('created_at', backup.created_at)
24
+ node.value('region', backup.aws_region)
25
+ node.value('size', backup.size)
26
+
27
+ if backup.copied_from
28
+ node.keyed_object('copied_from', 'description') do |n|
29
+ inject_backup(n, backup.copied_from)
30
+ end
31
+ end
32
+ end
33
+
34
+ def inject_deleted_database(node, database, account)
35
+ node.value('id', database.id)
36
+ node.value('handle', database.handle)
37
+ node.value('type', database.type)
38
+ node.value('deleted_at', database.deleted_at)
39
+ attach_account(node, account)
40
+ end
41
+
7
42
  def inject_account(node, account)
8
43
  node.value('id', account.id)
9
44
  node.value('handle', account.handle)
@@ -47,6 +82,7 @@ module Aptible
47
82
 
48
83
  node.value('type', database.type)
49
84
  node.value('status', database.status)
85
+
50
86
  node.value('connection_url', database.connection_url)
51
87
 
52
88
  node.list('credentials') do |creds_list|
@@ -54,7 +90,6 @@ module Aptible
54
90
  creds_list.object { |n| inject_credential(n, cred) }
55
91
  end
56
92
  end
57
-
58
93
  attach_account(node, account)
59
94
  end
60
95
 
@@ -9,13 +9,15 @@ module Aptible
9
9
 
10
10
  desc 'backup:restore BACKUP_ID ' \
11
11
  '[--environment ENVIRONMENT_HANDLE] [--handle HANDLE] ' \
12
- '[--container-size SIZE_MB] [--disk-size SIZE_GB]',
12
+ '[--container-size SIZE_MB] [--disk-size SIZE_GB] ' \
13
+ '[--key-arn KEY_ARN]',
13
14
  'Restore a backup'
14
15
  option :handle, desc: 'a name to use for the new database'
15
16
  option :environment, desc: 'a different environment to restore to'
16
17
  option :container_size, type: :numeric
17
18
  option :size, type: :numeric
18
19
  option :disk_size, type: :numeric
20
+ option :key_arn, type: :string
19
21
  define_method 'backup:restore' do |backup_id|
20
22
  backup = Aptible::Api::Backup.find(backup_id, token: fetch_token)
21
23
  raise Thor::Error, "Backup ##{backup_id} not found" if backup.nil?
@@ -23,7 +25,8 @@ module Aptible
23
25
  handle = options[:handle]
24
26
  unless handle
25
27
  ts_suffix = backup.created_at.getgm.strftime '%Y-%m-%d-%H-%M-%S'
26
- handle = "#{backup.database.handle}-at-#{ts_suffix}"
28
+ handle =
29
+ "#{backup.database_with_deleted.handle}-at-#{ts_suffix}"
27
30
  end
28
31
 
29
32
  destination_account = if options[:environment]
@@ -37,7 +40,8 @@ module Aptible
37
40
  handle: handle,
38
41
  container_size: options[:container_size],
39
42
  disk_size: options[:disk_size] || options[:size],
40
- destination_account: destination_account
43
+ destination_account: destination_account,
44
+ key_arn: options[:key_arn]
41
45
  }.delete_if { |_, v| v.nil? }
42
46
 
43
47
  CLI.logger.warn([
@@ -69,24 +73,50 @@ module Aptible
69
73
  database = ensure_database(options.merge(db: handle))
70
74
 
71
75
  Formatter.render(Renderer.current) do |root|
72
- root.keyed_list('description') do |l|
76
+ root.keyed_list('description') do |node|
73
77
  database.each_backup do |backup|
74
- break if backup.created_at < min_created_at
75
- description = "#{backup.id}: #{backup.created_at}, " \
76
- "#{backup.aws_region}"
78
+ if backup.created_at < min_created_at && !backup.copied_from
79
+ break
80
+ end
81
+ node.object do |n|
82
+ ResourceFormatter.inject_backup(n, backup)
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
77
88
 
78
- l.object do |o|
79
- o.value('id', backup.id)
80
- o.value('description', description)
81
- o.value('created_at', backup.created_at)
82
- o.value('region', backup.aws_region)
89
+ desc 'backup:orphaned', 'List backups associated with ' \
90
+ 'deprovisioned databases'
91
+ option :environment
92
+ option :max_age, default: '1y',
93
+ desc: 'Limit backups returned '\
94
+ '(example usage: 1w, 1y, etc.)'
95
+ define_method 'backup:orphaned' do
96
+ age = ChronicDuration.parse(options[:max_age])
97
+ raise Thor::Error, "Invalid age: #{options[:max_age]}" if age.nil?
98
+ min_created_at = Time.now - age
99
+
100
+ Formatter.render(Renderer.current) do |root|
101
+ root.keyed_list('description') do |node|
102
+ scoped_environments(options).each do |account|
103
+ account.each_orphaned_backup do |backup|
104
+ created_at = backup.created_at
105
+ copied_from = backup.copied_from
106
+ break if created_at < min_created_at && !copied_from
107
+ node.object do |n|
108
+ ResourceFormatter.inject_backup(
109
+ n, backup, include_db: true
110
+ )
111
+ end
83
112
  end
84
113
  end
85
114
  end
86
115
  end
87
116
  end
88
117
 
89
- desc 'backup:purge BACKUP_ID', 'Restore a backup'
118
+ desc 'backup:purge BACKUP_ID',
119
+ 'Permanently delete a backup and any copies of it'
90
120
  define_method 'backup:purge' do |backup_id|
91
121
  backup = Aptible::Api::Backup.find(backup_id, token: fetch_token)
92
122
  raise Thor::Error, "Backup ##{backup_id} not found" if backup.nil?
@@ -53,13 +53,15 @@ module Aptible
53
53
 
54
54
  desc 'db:create HANDLE ' \
55
55
  '[--type TYPE] [--version VERSION] ' \
56
- '[--container-size SIZE_MB] [--disk-size SIZE_GB]',
56
+ '[--container-size SIZE_MB] [--disk-size SIZE_GB] ' \
57
+ '[--key-arn KEY_ARN]',
57
58
  'Create a new database'
58
59
  option :type, type: :string
59
60
  option :version, type: :string
60
61
  option :container_size, type: :numeric
61
62
  option :size, type: :numeric
62
63
  option :disk_size, default: 10, type: :numeric
64
+ option :key_arn, type: :string
63
65
  option :environment
64
66
  define_method 'db:create' do |handle|
65
67
  account = ensure_environment(options)
@@ -67,7 +69,8 @@ module Aptible
67
69
  db_opts = {
68
70
  handle: handle,
69
71
  initial_container_size: options[:container_size],
70
- initial_disk_size: options[:disk_size] || options[:size]
72
+ initial_disk_size: options[:disk_size] || options[:size],
73
+ current_kms_arn: options[:key_arn]
71
74
  }.delete_if { |_, v| v.nil? }
72
75
 
73
76
  CLI.logger.warn([
@@ -122,20 +125,41 @@ module Aptible
122
125
  end
123
126
 
124
127
  desc 'db:replicate HANDLE REPLICA_HANDLE ' \
125
- '[--container-size SIZE_MB] [--disk-size SIZE_GB]',
128
+ '[--container-size SIZE_MB] [--disk-size SIZE_GB] ' \
129
+ '[--logical --version VERSION] [--key-arn KEY_ARN]',
126
130
  'Create a replica/follower of a database'
127
131
  option :environment
128
132
  option :container_size, type: :numeric
129
133
  option :size, type: :numeric
130
134
  option :disk_size, type: :numeric
135
+ option :logical, type: :boolean
136
+ option :version, type: :string
137
+ option :key_arn, type: :string
131
138
  define_method 'db:replicate' do |source_handle, dest_handle|
132
139
  source = ensure_database(options.merge(db: source_handle))
140
+
141
+ if options[:logical]
142
+ if source.type != 'postgresql'
143
+ raise Thor::Error, 'Logical replication only works for ' \
144
+ 'PostgreSQL'
145
+ end
146
+ if options[:version]
147
+ image = find_database_image(source.type, options[:version])
148
+ else
149
+ raise Thor::Error, '--version is required for logical ' \
150
+ 'replication'
151
+ end
152
+ end
153
+
133
154
  CLI.logger.info "Replicating #{source_handle}..."
134
155
 
135
156
  opts = {
136
157
  environment: options[:environment],
137
158
  container_size: options[:container_size],
138
- size: options[:disk_size] || options[:size]
159
+ size: options[:disk_size] || options[:size],
160
+ logical: options[:logical],
161
+ database_image: image || nil,
162
+ key_arn: options[:key_arn]
139
163
  }.delete_if { |_, v| v.nil? }
140
164
 
141
165
  CLI.logger.warn([
@@ -1,5 +1,5 @@
1
1
  module Aptible
2
2
  module CLI
3
- VERSION = '0.16.4'.freeze
3
+ VERSION = '0.16.9'.freeze
4
4
  end
5
5
  end
@@ -2,14 +2,17 @@ require 'spec_helper'
2
2
 
3
3
  describe Aptible::CLI::Agent do
4
4
  let(:token) { 'some-token' }
5
- let(:account) { Fabricate(:account, handle: 'test') }
5
+ let(:account) { Fabricate(:account, handle: 'test', id: 1) }
6
6
  let(:alt_account) { Fabricate(:account, handle: 'alt') }
7
7
  let(:database) { Fabricate(:database, account: account, handle: 'some-db') }
8
8
  let!(:backup) do
9
9
  # created_at: 2016-06-14 13:24:11 +0000
10
10
  Fabricate(
11
11
  :backup,
12
- database: database, created_at: Time.at(1465910651), account: account
12
+ database_with_deleted: database,
13
+ created_at: Time.at(1465910651),
14
+ account: account,
15
+ id: 1
13
16
  )
14
17
  end
15
18
 
@@ -179,6 +182,50 @@ describe Aptible::CLI::Agent do
179
182
  end
180
183
  end
181
184
 
185
+ describe '#backup:orphaned' do
186
+ before { allow(Aptible::Api::Account).to receive(:all) { [account] } }
187
+ before do
188
+ m = allow(account).to receive(:each_orphaned_backup)
189
+ ages = [
190
+ 1.day, 2.days, 3.days, 4.days,
191
+ 5.days, 2.weeks, 3.weeks, 1.month,
192
+ 1.year
193
+ ]
194
+ ages.each do |age|
195
+ b = Fabricate(:backup, database: database, created_at: age.ago,
196
+ account: account)
197
+ allow(b).to receive(:database_with_deleted).and_return(database)
198
+ m.and_yield(b)
199
+ b
200
+ end
201
+ end
202
+ before { subject.options = { max_age: '1w' } }
203
+
204
+ it 'can show a subset of backups' do
205
+ subject.send('backup:orphaned')
206
+ puts captured_output_text
207
+ expect(captured_output_text.split("\n").size).to eq(5)
208
+ end
209
+
210
+ it 'allows scoping via environment' do
211
+ subject.options = { max_age: '1w', environment: database.account.handle }
212
+ subject.send('backup:orphaned')
213
+ expect(captured_output_text.split("\n").size).to eq(5)
214
+ end
215
+
216
+ it 'shows more backups if requested' do
217
+ subject.options = { max_age: '2y' }
218
+ subject.send('backup:orphaned')
219
+ expect(captured_output_text.split("\n").size).to eq(9)
220
+ end
221
+
222
+ it 'errors out if max_age is invalid' do
223
+ subject.options = { max_age: 'foobar' }
224
+ expect { subject.send('backup:orphaned') }
225
+ .to raise_error(Thor::Error, 'Invalid age: foobar')
226
+ end
227
+ end
228
+
182
229
  describe '#backup:purge' do
183
230
  it 'fails if the backup cannot be found' do
184
231
  expect(Aptible::Api::Backup).to receive(:find)
@@ -13,7 +13,9 @@ describe Aptible::CLI::Agent do
13
13
  end
14
14
 
15
15
  let(:handle) { 'foobar' }
16
- let(:database) { Fabricate(:database, handle: handle) }
16
+ let(:stack) { Fabricate(:stack, internal_domain: 'aptible.in') }
17
+ let(:account) { Fabricate(:account, stack: stack) }
18
+ let(:database) { Fabricate(:database, handle: handle, account: account) }
17
19
  let(:socat_helper) { SocatHelperMock.new(port: 4242) }
18
20
 
19
21
  describe '#db:create' do
@@ -519,6 +521,69 @@ describe Aptible::CLI::Agent do
519
521
  expect { subject.send('db:replicate', 'nope', 'replica') }
520
522
  .to raise_error(Thor::Error, 'Could not find database nope')
521
523
  end
524
+
525
+ it 'allows logical replication of a database with --version set' do
526
+ master = Fabricate(:database, handle: 'master')
527
+ databases << master
528
+ replica = Fabricate(:database,
529
+ account: master.account,
530
+ handle: 'replica')
531
+
532
+ dbimg = Fabricate(:database_image,
533
+ type: 'postgresql',
534
+ version: 10,
535
+ docker_repo: 'aptible/postgresql:10')
536
+
537
+ expect(subject).to receive(:find_database_image).with('postgresql', 10)
538
+ .and_return(dbimg)
539
+
540
+ op = Fabricate(:operation)
541
+
542
+ params = { type: 'replicate_logical', handle: 'replica',
543
+ docker_ref: dbimg.docker_repo }
544
+ expect(master).to receive(:create_operation!)
545
+ .with(**params).and_return(op)
546
+
547
+ expect(subject).to receive(:attach_to_operation_logs).with(op) do
548
+ databases << replica
549
+ replica
550
+ end
551
+
552
+ provision = Fabricate(:operation)
553
+
554
+ expect(replica).to receive_message_chain(:operations, :last)
555
+ .and_return(provision)
556
+
557
+ expect(subject).to receive(:attach_to_operation_logs).with(provision)
558
+
559
+ expect(replica).to receive(:reload).and_return(replica)
560
+
561
+ subject.options = { logical: true, version: 10 }
562
+ subject.send('db:replicate', 'master', 'replica')
563
+
564
+ expect(captured_logs).to match(/replicating master/i)
565
+ end
566
+
567
+ it 'fails if logical replication requested without --version' do
568
+ master = Fabricate(:database, handle: 'master', type: 'postgresql')
569
+ databases << master
570
+
571
+ subject.options = { type: 'replicate', handle: 'replica', logical: true }
572
+ expect { subject.send('db:replicate', 'master', 'replica') }
573
+ .to raise_error(Thor::Error, '--version is required for logical ' \
574
+ 'replication')
575
+ end
576
+
577
+ it 'fails if logical replication requested for non-postgres db' do
578
+ master = Fabricate(:database, handle: 'master', type: 'mysql')
579
+ databases << master
580
+
581
+ subject.options = { type: 'replicate', handle: 'replica',
582
+ logical: true, version: 10 }
583
+ expect { subject.send('db:replicate', 'master', 'replica') }
584
+ .to raise_error(Thor::Error, 'Logical replication only works for ' \
585
+ 'PostgreSQL')
586
+ end
522
587
  end
523
588
 
524
589
  describe '#db:dump' do
@@ -3,14 +3,6 @@ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
3
 
4
4
  Bundler.require :development
5
5
 
6
- require 'simplecov'
7
- SimpleCov.start
8
-
9
- if ENV['CI']
10
- require 'codecov'
11
- SimpleCov.formatter = SimpleCov::Formatter::Codecov
12
- end
13
-
14
6
  # Load shared spec files
15
7
  Dir["#{File.dirname(__FILE__)}/shared/**/*.rb"].each do |file|
16
8
  require file
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aptible-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.16.4
4
+ version: 0.16.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Frank Macreery
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-07 00:00:00.000000000 Z
11
+ date: 2020-11-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aptible-resource
@@ -30,28 +30,28 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '1.0'
33
+ version: '1.2'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '1.0'
40
+ version: '1.2'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: aptible-auth
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '1.0'
47
+ version: 1.2.3
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '1.0'
54
+ version: 1.2.3
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: aptible-billing
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -359,7 +359,7 @@ homepage: https://github.com/aptible/aptible-cli
359
359
  licenses:
360
360
  - MIT
361
361
  metadata: {}
362
- post_install_message:
362
+ post_install_message:
363
363
  rdoc_options: []
364
364
  require_paths:
365
365
  - lib
@@ -375,7 +375,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
375
375
  version: '0'
376
376
  requirements: []
377
377
  rubygems_version: 3.0.3
378
- signing_key:
378
+ signing_key:
379
379
  specification_version: 4
380
380
  summary: Command-line interface for Aptible services
381
381
  test_files: