aptible-cli 0.16.3 → 0.16.8

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: 3eb92fffdc685a61fd06c0d5b38b485395eccb0e1762d8efeab0514e74c61b6b
4
- data.tar.gz: 2237aacb034a1f7f7b1ae41328f0c05ee9393b7d48d6bdca93ca72072e867183
3
+ metadata.gz: 7f9abbc0bef99a8ca0f69c0b3d9751e529d9b37b5528315b03481acc84b68868
4
+ data.tar.gz: 916d97a558b654865ae9cbcf4532058a9070d19dbe6d984be7a5f233eb2884e6
5
5
  SHA512:
6
- metadata.gz: c3ad2d2c88fa090eb8f65a9f4b91b1c3bccf180764a69ef6d60c3f068e37b6fafbbea515f9a6451b2983aa6e44dcd82bee1020003069e644c3389a2011207764
7
- data.tar.gz: 63cc2f56d9f14f9c5981cae0df45d1de16bf03ee714ddb8635641176ad8f6315d1a00ca154b8ad377274a7494d8ca268e9873ea0b51eb1aa8186a1094f444ad6
6
+ metadata.gz: 6ef0c0c9dc25afdf19b680fcabcfd589cd74f246d7442361e79ef5f01d73382bd10ee21c9ca63a31a3f6a003ef0405c651b03e251c6e36525ddfebc86df7aeee
7
+ data.tar.gz: 3457904ef7ab8e5ff0a5a63fd856633fea48460e8dd88b0ae5ba8181494d893bfe45027927e57c7b217cc89fb39f09eb3fe27abdcb929d1b6fd8d6ca7a6a5b18
@@ -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,52 +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:restore BACKUP_ID [--environment ENVIRONMENT_HANDLE] [--handle HANDLE] [--container-size SIZE_MB] [--size SIZE_GB] # Restore a backup
38
- aptible config # Print an app's current configuration
39
- aptible config:add [VAR1=VAL1] [VAR2=VAL2] [...] # Add an ENV variable to an app
40
- aptible config:rm [VAR1] [VAR2] [...] # Remove an ENV variable from an app
41
- aptible config:set [VAR1=VAL1] [VAR2=VAL2] [...] # Add an ENV variable to an app
42
- aptible config:unset [VAR1] [VAR2] [...] # Remove an ENV variable from an app
43
- aptible db:backup HANDLE # Backup a database
44
- aptible db:clone SOURCE DEST # Clone a database to create a new one
45
- aptible db:create HANDLE [--type TYPE] [--version VERSION] [--container-size SIZE_MB] [--size SIZE_GB] # Create a new database
46
- aptible db:deprovision HANDLE # Deprovision a database
47
- aptible db:dump HANDLE [pg_dump options] # Dump a remote database to file
48
- aptible db:execute HANDLE SQL_FILE [--on-error-stop] # Executes sql against a database
49
- aptible db:list # List all databases
50
- aptible db:reload HANDLE # Reload a database
51
- aptible db:replicate HANDLE REPLICA_HANDLE [--container-size SIZE_MB] [--size SIZE_GB] # Create a replica/follower of a database
52
- aptible db:restart HANDLE [--container-size SIZE_MB] [--size SIZE_GB] # Restart a database
53
- aptible db:tunnel HANDLE # Create a local tunnel to a database
54
- aptible db:url HANDLE # Display a database URL
55
- aptible db:versions # List available database versions
56
- aptible deploy [OPTIONS] [VAR1=VAL1] [VAR2=VAL2] [...] # Deploy an app
57
- aptible domains # Print an app's current virtual domains - DEPRECATED
58
- aptible endpoints:database:create DATABASE # Create a Database Endpoint
59
- aptible endpoints:deprovision [--app APP | --database DATABASE] ENDPOINT_HOSTNAME # Deprovision an App or Database Endpoint
60
- aptible endpoints:https:create [--app APP] SERVICE # Create an App HTTPS Endpoint
61
- aptible endpoints:https:modify [--app APP] ENDPOINT_HOSTNAME # Modify an App HTTPS Endpoint
62
- aptible endpoints:list [--app APP | --database DATABASE] # List Endpoints for an App or Database
63
- aptible endpoints:renew [--app APP] ENDPOINT_HOSTNAME # Renew an App Managed TLS Endpoint
64
- aptible endpoints:tcp:create [--app APP] SERVICE # Create an App TCP Endpoint
65
- aptible endpoints:tcp:modify [--app APP] ENDPOINT_HOSTNAME # Modify an App TCP Endpoint
66
- aptible endpoints:tls:create [--app APP] SERVICE # Create an App TLS Endpoint
67
- aptible endpoints:tls:modify [--app APP] ENDPOINT_HOSTNAME # Modify an App TLS Endpoint
68
- aptible help [COMMAND] # Describe available commands or one specific command
69
- aptible login # Log in to Aptible
70
- aptible logs [--app APP | --database DATABASE] # Follows logs from a running app or database
71
- aptible operation:cancel OPERATION_ID # Cancel a running operation
72
- aptible ps # Display running processes for an app - DEPRECATED
73
- aptible rebuild # Rebuild an app, and restart its services
74
- aptible restart # Restart all services associated with an app
75
- aptible services # List Services for an App
76
- aptible ssh [COMMAND] # Run a command against an app
77
- 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
78
79
  ```
79
80
  <!-- END USAGE -->
80
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'
@@ -1,3 +1,4 @@
1
+ require 'base64'
1
2
  require 'uri'
2
3
 
3
4
  require 'aptible/auth'
@@ -66,6 +67,7 @@ module Aptible
66
67
  def initialize(*)
67
68
  nag_toolbelt unless toolbelt?
68
69
  Aptible::Resource.configure { |conf| conf.user_agent = version_string }
70
+ warn_sso_enforcement
69
71
  super
70
72
  end
71
73
 
@@ -84,7 +86,22 @@ module Aptible
84
86
  option :lifetime, desc: 'The duration the token should be valid for ' \
85
87
  '(example usage: 24h, 1d, 600s, etc.)'
86
88
  option :otp_token, desc: 'A token generated by your second-factor app'
89
+ option :sso, desc: 'Use a token from a Single Sign On login on the ' \
90
+ 'dashboard'
87
91
  def login
92
+ if options[:sso]
93
+ begin
94
+ token = options[:sso]
95
+ token = ask('Paste token copied from Dashboard:') if token == 'sso'
96
+ Base64.urlsafe_decode64(token.split('.').first)
97
+ save_token(token)
98
+ CLI.logger.info "Token written to #{token_file}"
99
+ return
100
+ rescue StandardError
101
+ raise Thor::Error, 'Invalid token provided for SSO'
102
+ end
103
+ end
104
+
88
105
  email = options[:email] || ask('Email: ')
89
106
  password = options[:password] || ask_then_line(
90
107
  'Password: ', echo: false
@@ -216,6 +233,22 @@ module Aptible
216
233
  end
217
234
  end
218
235
 
236
+ def warn_sso_enforcement
237
+ # If the user is also a member of
238
+ begin
239
+ token = fetch_token
240
+ rescue StandardError
241
+ return
242
+ end
243
+ reauth = Aptible::Auth::ReauthenticateOrganization.all(token: token)
244
+ return if reauth.empty?
245
+
246
+ CLI.logger.warn(['WARNING: You will need to use the appropriate',
247
+ 'login method (SSO or Aptible credentials) to access',
248
+ 'these organizations:',
249
+ reauth.map(&:name)].join(' '))
250
+ end
251
+
219
252
  def version_string
220
253
  bits = [
221
254
  '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,12 +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] [--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
19
+ option :disk_size, type: :numeric
20
+ option :key_arn, type: :string
18
21
  define_method 'backup:restore' do |backup_id|
19
22
  backup = Aptible::Api::Backup.find(backup_id, token: fetch_token)
20
23
  raise Thor::Error, "Backup ##{backup_id} not found" if backup.nil?
@@ -22,7 +25,8 @@ module Aptible
22
25
  handle = options[:handle]
23
26
  unless handle
24
27
  ts_suffix = backup.created_at.getgm.strftime '%Y-%m-%d-%H-%M-%S'
25
- handle = "#{backup.database.handle}-at-#{ts_suffix}"
28
+ handle =
29
+ "#{backup.database_with_deleted.handle}-at-#{ts_suffix}"
26
30
  end
27
31
 
28
32
  destination_account = if options[:environment]
@@ -35,10 +39,17 @@ module Aptible
35
39
  type: 'restore',
36
40
  handle: handle,
37
41
  container_size: options[:container_size],
38
- disk_size: options[:size],
39
- destination_account: destination_account
42
+ disk_size: options[:disk_size] || options[:size],
43
+ destination_account: destination_account,
44
+ key_arn: options[:key_arn]
40
45
  }.delete_if { |_, v| v.nil? }
41
46
 
47
+ CLI.logger.warn([
48
+ 'You have used the "--size" option to specify a disk size.',
49
+ 'This option which be deprecated in a future version.',
50
+ 'Please use the "--disk-size" option, instead.'
51
+ ].join("\n")) if options[:size]
52
+
42
53
  operation = backup.create_operation!(opts)
43
54
  CLI.logger.info "Restoring backup into #{handle}"
44
55
  attach_to_operation_logs(operation)
@@ -62,22 +73,58 @@ module Aptible
62
73
  database = ensure_database(options.merge(db: handle))
63
74
 
64
75
  Formatter.render(Renderer.current) do |root|
65
- root.keyed_list('description') do |l|
76
+ root.keyed_list('description') do |node|
66
77
  database.each_backup do |backup|
67
- break if backup.created_at < min_created_at
68
- description = "#{backup.id}: #{backup.created_at}, " \
69
- "#{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
70
88
 
71
- l.object do |o|
72
- o.value('id', backup.id)
73
- o.value('description', description)
74
- o.value('created_at', backup.created_at)
75
- 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
76
112
  end
77
113
  end
78
114
  end
79
115
  end
80
116
  end
117
+
118
+ desc 'backup:purge BACKUP_ID',
119
+ 'Permanently delete a backup and any copies of it'
120
+ define_method 'backup:purge' do |backup_id|
121
+ backup = Aptible::Api::Backup.find(backup_id, token: fetch_token)
122
+ raise Thor::Error, "Backup ##{backup_id} not found" if backup.nil?
123
+
124
+ operation = backup.create_operation!(type: 'purge')
125
+ CLI.logger.info "Purging backup #{backup_id}"
126
+ attach_to_operation_logs(operation)
127
+ end
81
128
  end
82
129
  end
83
130
  end
@@ -53,12 +53,15 @@ module Aptible
53
53
 
54
54
  desc 'db:create HANDLE ' \
55
55
  '[--type TYPE] [--version VERSION] ' \
56
- '[--container-size SIZE_MB] [--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
- option :size, default: 10, type: :numeric
62
+ option :size, type: :numeric
63
+ option :disk_size, default: 10, type: :numeric
64
+ option :key_arn, type: :string
62
65
  option :environment
63
66
  define_method 'db:create' do |handle|
64
67
  account = ensure_environment(options)
@@ -66,9 +69,16 @@ module Aptible
66
69
  db_opts = {
67
70
  handle: handle,
68
71
  initial_container_size: options[:container_size],
69
- initial_disk_size: options[:size]
72
+ initial_disk_size: options[:disk_size] || options[:size],
73
+ current_kms_arn: options[:key_arn]
70
74
  }.delete_if { |_, v| v.nil? }
71
75
 
76
+ CLI.logger.warn([
77
+ 'You have used the "--size" option to specify a disk size.',
78
+ 'This option which be deprecated in a future version.',
79
+ 'Please use the "--disk-size" option, instead.'
80
+ ].join("\n")) if options[:size]
81
+
72
82
  type = options[:type]
73
83
  version = options[:version]
74
84
 
@@ -87,7 +97,7 @@ module Aptible
87
97
  op_opts = {
88
98
  type: 'provision',
89
99
  container_size: options[:container_size],
90
- disk_size: options[:size]
100
+ disk_size: options[:disk_size] || options[:size]
91
101
  }.delete_if { |_, v| v.nil? }
92
102
  op = database.create_operation(op_opts)
93
103
 
@@ -115,15 +125,50 @@ module Aptible
115
125
  end
116
126
 
117
127
  desc 'db:replicate HANDLE REPLICA_HANDLE ' \
118
- '[--container-size SIZE_MB] [--size SIZE_GB]',
128
+ '[--container-size SIZE_MB] [--disk-size SIZE_GB] ' \
129
+ '[--logical --version VERSION] [--key-arn KEY_ARN]',
119
130
  'Create a replica/follower of a database'
120
131
  option :environment
121
132
  option :container_size, type: :numeric
122
133
  option :size, type: :numeric
134
+ option :disk_size, type: :numeric
135
+ option :logical, type: :boolean
136
+ option :version, type: :string
137
+ option :key_arn, type: :string
123
138
  define_method 'db:replicate' do |source_handle, dest_handle|
124
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
+
125
154
  CLI.logger.info "Replicating #{source_handle}..."
126
- database = replicate_database(source, dest_handle, options)
155
+
156
+ opts = {
157
+ environment: options[:environment],
158
+ container_size: options[:container_size],
159
+ size: options[:disk_size] || options[:size],
160
+ logical: options[:logical],
161
+ database_image: image || nil,
162
+ key_arn: options[:key_arn]
163
+ }.delete_if { |_, v| v.nil? }
164
+
165
+ CLI.logger.warn([
166
+ 'You have used the "--size" option to specify a disk size.',
167
+ 'This option which be deprecated in a future version.',
168
+ 'Please use the "--disk-size" option, instead.'
169
+ ].join("\n")) if options[:size]
170
+
171
+ database = replicate_database(source, dest_handle, opts)
127
172
  render_database(database.reload, database.account)
128
173
  end
129
174
 
@@ -234,10 +279,11 @@ module Aptible
234
279
  end
235
280
 
236
281
  desc 'db:restart HANDLE ' \
237
- '[--container-size SIZE_MB] [--size SIZE_GB]',
282
+ '[--container-size SIZE_MB] [--disk-size SIZE_GB]',
238
283
  'Restart a database'
239
284
  option :environment
240
285
  option :container_size, type: :numeric
286
+ option :disk_size, type: :numeric
241
287
  option :size, type: :numeric
242
288
  define_method 'db:restart' do |handle|
243
289
  database = ensure_database(options.merge(db: handle))
@@ -245,9 +291,15 @@ module Aptible
245
291
  opts = {
246
292
  type: 'restart',
247
293
  container_size: options[:container_size],
248
- disk_size: options[:size]
294
+ disk_size: options[:disk_size] || options[:size]
249
295
  }.delete_if { |_, v| v.nil? }
250
296
 
297
+ CLI.logger.warn([
298
+ 'You have used the "--size" option to specify a disk size.',
299
+ 'This option which be deprecated in a future version.',
300
+ 'Please use the "--disk-size" option, instead.'
301
+ ].join("\n")) if options[:size]
302
+
251
303
  CLI.logger.info "Restarting #{database.handle}..."
252
304
  op = database.create_operation!(opts)
253
305
  attach_to_operation_logs(op)
@@ -19,7 +19,7 @@ module Aptible
19
19
  raise "Invalid scheme: #{uri.scheme} (use https)"
20
20
  end
21
21
 
22
- apis = [Aptible::Auth, Aptible::Api, Aptible::Billing]
22
+ apis = [Aptible::Auth, Aptible::Api]
23
23
 
24
24
  api = apis.find do |klass|
25
25
  uri.host == URI(klass.configuration.root_url).host
@@ -1,5 +1,5 @@
1
1
  module Aptible
2
2
  module CLI
3
- VERSION = '0.16.3'.freeze
3
+ VERSION = '0.16.8'.freeze
4
4
  end
5
5
  end
@@ -258,6 +258,36 @@ describe Aptible::CLI::Agent do
258
258
  subject.login
259
259
  end
260
260
  end
261
+
262
+ context 'SSO logins' do
263
+ let(:token) { 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzUxMiJ9.eyJpZCI6I' }
264
+
265
+ it 'accepts a token as an argument' do
266
+ options = { sso: token }
267
+ allow(subject).to receive(:options).and_return options
268
+
269
+ expect(subject).to receive(:save_token).with(token)
270
+
271
+ subject.login
272
+ end
273
+
274
+ it 'rejects clearly invalid tokens' do
275
+ options = { sso: 'blarg' }
276
+ allow(subject).to receive(:options).and_return options
277
+
278
+ expect { subject.login }.to raise_error Thor::Error
279
+ end
280
+
281
+ it 'prompts for a token if none provided' do
282
+ options = { sso: 'sso' }
283
+ allow(subject).to receive(:options).and_return options
284
+
285
+ expect(subject).to receive(:ask).once.and_return(token)
286
+ expect(subject).to receive(:save_token).with(token)
287
+
288
+ subject.login
289
+ end
290
+ end
261
291
  end
262
292
  end
263
293
 
@@ -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
 
@@ -178,4 +181,78 @@ describe Aptible::CLI::Agent do
178
181
  .to raise_error(Thor::Error, 'Could not find database nope')
179
182
  end
180
183
  end
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
+
229
+ describe '#backup:purge' do
230
+ it 'fails if the backup cannot be found' do
231
+ expect(Aptible::Api::Backup).to receive(:find)
232
+ .with(1, token: token).and_return(nil)
233
+
234
+ expect { subject.send('backup:purge', 1) }
235
+ .to raise_error('Backup #1 not found')
236
+ end
237
+
238
+ context 'successful purge' do
239
+ let(:op) { Fabricate(:operation, resource: backup) }
240
+
241
+ before do
242
+ expect(Aptible::Api::Backup).to receive(:find)
243
+ .with(1, token: token).and_return(backup)
244
+ end
245
+
246
+ it 'creates a purge operation on the backup' do
247
+ expect(backup).to receive(:create_operation!) do |options|
248
+ expect(options[:type]).to eq('purge')
249
+ op
250
+ end
251
+
252
+ expect(subject).to receive(:attach_to_operation_logs).with(op)
253
+
254
+ subject.send('backup:purge', 1)
255
+ end
256
+ end
257
+ end
181
258
  end
@@ -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
@@ -55,7 +57,7 @@ describe Aptible::CLI::Agent do
55
57
  subject.send('db:create', 'foo')
56
58
  end
57
59
 
58
- it 'creates a new DB with a disk size' do
60
+ it 'creates a new DB with a (implicitly) disk size' do
59
61
  expect_provision_database(
60
62
  { handle: 'foo', type: 'postgresql', initial_disk_size: 200 },
61
63
  { disk_size: 200 }
@@ -65,6 +67,16 @@ describe Aptible::CLI::Agent do
65
67
  subject.send('db:create', 'foo')
66
68
  end
67
69
 
70
+ it 'creates a new DB with a disk-size' do
71
+ expect_provision_database(
72
+ { handle: 'foo', type: 'postgresql', initial_disk_size: 200 },
73
+ { disk_size: 200 }
74
+ )
75
+
76
+ subject.options = { type: 'postgresql', disk_size: 200 }
77
+ subject.send('db:create', 'foo')
78
+ end
79
+
68
80
  it 'deprovisions the database if the operation cannot be created' do
69
81
  db = Fabricate(:database)
70
82
 
@@ -377,7 +389,7 @@ describe Aptible::CLI::Agent do
377
389
  expect(captured_logs).to match(/restarting foobar/i)
378
390
  end
379
391
 
380
- it 'allows restarting a database with a disk size' do
392
+ it 'allows restarting a database with (implicitly disk) size' do
381
393
  expect(database).to receive(:create_operation!)
382
394
  .with(type: 'restart', disk_size: 40).and_return(op)
383
395
 
@@ -389,6 +401,18 @@ describe Aptible::CLI::Agent do
389
401
  expect(captured_logs).to match(/restarting foobar/i)
390
402
  end
391
403
 
404
+ it 'allows restarting a database with a disk-size' do
405
+ expect(database).to receive(:create_operation!)
406
+ .with(type: 'restart', disk_size: 40).and_return(op)
407
+
408
+ expect(subject).to receive(:attach_to_operation_logs).with(op)
409
+
410
+ subject.options = { disk_size: 40 }
411
+ subject.send('db:restart', handle)
412
+
413
+ expect(captured_logs).to match(/restarting foobar/i)
414
+ end
415
+
392
416
  it 'fails if the DB is not found' do
393
417
  expect { subject.send('db:restart', 'nope') }
394
418
  .to raise_error(Thor::Error, 'Could not find database nope')
@@ -485,14 +509,81 @@ describe Aptible::CLI::Agent do
485
509
  expect_replicate_database(container_size: 40)
486
510
  end
487
511
 
488
- it 'allows replicating a database with a disk size' do
512
+ it 'allows replicating a database with an (implicitly) disk size option' do
489
513
  expect_replicate_database(size: 40)
490
514
  end
491
515
 
516
+ it 'allows replicating a database with a disk-size option' do
517
+ expect_replicate_database(disk_size: 40)
518
+ end
519
+
492
520
  it 'fails if the DB is not found' do
493
521
  expect { subject.send('db:replicate', 'nope', 'replica') }
494
522
  .to raise_error(Thor::Error, 'Could not find database nope')
495
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
496
587
  end
497
588
 
498
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.3
4
+ version: 0.16.8
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: 2019-10-24 00:00:00.000000000 Z
11
+ date: 2020-10-23 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
@@ -374,9 +374,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
374
374
  - !ruby/object:Gem::Version
375
375
  version: '0'
376
376
  requirements: []
377
- rubyforge_project:
378
- rubygems_version: 2.7.6.2
379
- signing_key:
377
+ rubygems_version: 3.0.3
378
+ signing_key:
380
379
  specification_version: 4
381
380
  summary: Command-line interface for Aptible services
382
381
  test_files: