aptible-cli 0.16.6 → 0.17.1

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: 00beffb5298954baab427a850fca6f30be69ab59f6c5ff098a2d65ee64546094
4
- data.tar.gz: 5c56645ed8df1aeb780248bdbf68658410d8aba3589f8daa84b6b89498464539
3
+ metadata.gz: 2b10127dfc01ec4af08021f37da130738584bd5468298e7519ba4ec49b87d6dd
4
+ data.tar.gz: 785eb00b9009419d7e952c344c642cb831fbd96c10d68b680b3bda68231d35f4
5
5
  SHA512:
6
- metadata.gz: 01f8a8d9f7370c9ffbdcb83f9c17f83668b5e7c42cd7a35f5a397156f72a279f394777fd92feebf93395520a267027b04c12f5b5a557000b50c3cb67c6f64583
7
- data.tar.gz: b1182433b759c81561d79488d6937f5badc84b3b58f75c2ed1387cb71f4102a2dc15a304810c3db5b25f3c39fd9f1a3a8e2d290a20f97a97e114c8b5efe0dfc6
6
+ metadata.gz: 749b640447638c67ff246dcf8f1fcff049248d08870d2012165b9c7694fd65038533cb365f03b734bac593f9a953ce7ba5e5ce074ef4165ce2132961e9baecea
7
+ data.tar.gz: 4ebfa45ebe3cfebbaa8abd6306414539c4e32dee645f7146b93ba081fe61c2b8a8aba5a5e540cc607a7d8b7fcb65bee04b4700ddd2698fe211458103fc1ec9bc
data/.gitignore CHANGED
@@ -17,4 +17,4 @@ test/tmp
17
17
  test/version_tmp
18
18
  tmp
19
19
  /.idea
20
-
20
+ /.vscode
data/.travis.yml CHANGED
@@ -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,55 @@ 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 # Permanently delete 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 environment:ca_cert # Retrieve the CA certificate associated with the environment
70
+ aptible environment:list # List all environments
71
+ aptible help [COMMAND] # Describe available commands or one specific command
72
+ aptible login # Log in to Aptible
73
+ aptible logs [--app APP | --database DATABASE] # Follows logs from a running app or database
74
+ aptible operation:cancel OPERATION_ID # Cancel a running operation
75
+ aptible rebuild # Rebuild an app, and restart its services
76
+ aptible restart # Restart all services associated with an app
77
+ aptible services # List Services for an App
78
+ aptible ssh [COMMAND] # Run a command against an app
79
+ aptible version # Print Aptible CLI version
79
80
  ```
80
81
  <!-- END USAGE -->
81
82
 
data/appveyor.yml CHANGED
@@ -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
data/aptible-cli.gemspec CHANGED
@@ -21,14 +21,18 @@ 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.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'
29
29
  spec.add_dependency 'term-ansicolor'
30
30
  spec.add_dependency 'chronic_duration', '~> 0.10.6'
31
+
32
+ # Temporarily pin ffi until https://github.com/ffi/ffi/issues/868 is fixed
33
+ spec.add_dependency 'ffi', '<= 1.14.1' if Gem.win_platform?
31
34
  spec.add_dependency 'win32-process' if Gem.win_platform?
35
+
32
36
  spec.add_dependency 'activesupport', '>= 4.0', '< 6.0'
33
37
  spec.add_development_dependency 'bundler', '~> 1.3'
34
38
  spec.add_development_dependency 'aptible-tasks', '~> 0.5.8'
data/bin/aptible CHANGED
@@ -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']}"
@@ -23,8 +23,8 @@ require_relative 'subcommands/apps'
23
23
  require_relative 'subcommands/config'
24
24
  require_relative 'subcommands/db'
25
25
  require_relative 'subcommands/domains'
26
+ require_relative 'subcommands/environment'
26
27
  require_relative 'subcommands/logs'
27
- require_relative 'subcommands/ps'
28
28
  require_relative 'subcommands/rebuild'
29
29
  require_relative 'subcommands/deploy'
30
30
  require_relative 'subcommands/restart'
@@ -47,8 +47,8 @@ module Aptible
47
47
  include Subcommands::Config
48
48
  include Subcommands::DB
49
49
  include Subcommands::Domains
50
+ include Subcommands::Environment
50
51
  include Subcommands::Logs
51
- include Subcommands::Ps
52
52
  include Subcommands::Rebuild
53
53
  include Subcommands::Deploy
54
54
  include Subcommands::Restart
@@ -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,42 @@ 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
+ node.value('manual', backup.manual)
27
+
28
+ if backup.copied_from
29
+ node.keyed_object('copied_from', 'description') do |n|
30
+ inject_backup(n, backup.copied_from)
31
+ end
32
+ end
33
+ end
34
+
35
+ def inject_deleted_database(node, database, account)
36
+ node.value('id', database.id)
37
+ node.value('handle', database.handle)
38
+ node.value('type', database.type)
39
+ node.value('deleted_at', database.deleted_at)
40
+ attach_account(node, account)
41
+ end
42
+
7
43
  def inject_account(node, account)
8
44
  node.value('id', account.id)
9
45
  node.value('handle', account.handle)
@@ -46,7 +82,9 @@ module Aptible
46
82
  node.value('handle', database.handle)
47
83
 
48
84
  node.value('type', database.type)
85
+ node.value('version', database.database_image.version)
49
86
  node.value('status', database.status)
87
+
50
88
  node.value('connection_url', database.connection_url)
51
89
 
52
90
  node.list('credentials') do |creds_list|
@@ -54,8 +92,12 @@ module Aptible
54
92
  creds_list.object { |n| inject_credential(n, cred) }
55
93
  end
56
94
  end
57
-
58
95
  attach_account(node, account)
96
+
97
+ node.value('disk_type', database.disk.ebs_volume_type)
98
+ node.value('disk_size', database.disk.size)
99
+ node.value('container_size', \
100
+ database.service.container_memory_limit_mb)
59
101
  end
60
102
 
61
103
  def inject_credential(node, credential)
@@ -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', 'Permanently delete 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([
@@ -0,0 +1,49 @@
1
+ module Aptible
2
+ module CLI
3
+ module Subcommands
4
+ module Environment
5
+ def self.included(thor)
6
+ thor.class_eval do
7
+ include Helpers::Environment
8
+ include Helpers::Token
9
+
10
+ desc 'environment:list', 'List all environments'
11
+ option :environment
12
+ define_method 'environment:list' do
13
+ Formatter.render(Renderer.current) do |root|
14
+ root.keyed_list(
15
+ 'handle'
16
+ ) do |node|
17
+ scoped_environments(options).each do |account|
18
+ node.object do |n|
19
+ ResourceFormatter.inject_account(n, account)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ desc 'environment:ca_cert',
27
+ 'Retrieve the CA certificate associated with the environment'
28
+ option :environment
29
+ define_method 'environment:ca_cert' do
30
+ Formatter.render(Renderer.current) do |root|
31
+ root.grouped_keyed_list(
32
+ 'handle',
33
+ 'ca_body'
34
+ ) do |node|
35
+ scoped_environments(options).each do |account|
36
+ node.object do |n|
37
+ n.value('ca_body', account.ca_body)
38
+ ResourceFormatter.inject_account(n, account)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -1,5 +1,5 @@
1
1
  module Aptible
2
2
  module CLI
3
- VERSION = '0.16.6'.freeze
3
+ VERSION = '0.17.1'.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
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+
3
+ describe Aptible::CLI::Agent do
4
+ let!(:a1) do
5
+ Fabricate(:account, handle: 'foo', ca_body: 'account 1 cert')
6
+ end
7
+ let!(:a2) do
8
+ Fabricate(:account, handle: 'bar', ca_body: '--account 2 cert--')
9
+ end
10
+
11
+ let(:token) { double 'token' }
12
+
13
+ before do
14
+ allow(subject).to receive(:fetch_token) { token }
15
+ allow(Aptible::Api::Account).to receive(:all).with(token: token)
16
+ .and_return([a1, a2])
17
+ end
18
+
19
+ it 'lists avaliable environments' do
20
+ subject.send('environment:list')
21
+
22
+ expect(captured_output_text.split("\n")).to include('foo')
23
+ expect(captured_output_text.split("\n")).to include('bar')
24
+ end
25
+
26
+ it 'fetches certs for all avaliable environments' do
27
+ subject.send('environment:ca_cert')
28
+
29
+ expect(captured_output_text.split("\n")).to include('account 1 cert')
30
+ expect(captured_output_text.split("\n")).to include('--account 2 cert--')
31
+
32
+ expected_accounts = [
33
+ {
34
+ 'handle' => 'foo',
35
+ 'ca_body' => 'account 1 cert'
36
+ },
37
+ {
38
+ 'handle' => 'bar',
39
+ 'ca_body' => '--account 2 cert--'
40
+ }
41
+ ]
42
+ expect(captured_output_json.map! { |account| account.except('id') })
43
+ .to eq(expected_accounts)
44
+ end
45
+
46
+ it 'fetches certs for specified environment' do
47
+ subject.options = { environment: 'foo' }
48
+ subject.send('environment:ca_cert')
49
+
50
+ expect(captured_output_text.split("\n")).to include('account 1 cert')
51
+ expect(captured_output_text.split("\n"))
52
+ .to_not include('--account 2 cert--')
53
+ end
54
+ end
@@ -20,6 +20,7 @@ Fabricator(:account, from: :stub_account) do
20
20
  bastion_host 'localhost'
21
21
  dumptruck_port 1234
22
22
  handle 'aptible'
23
+ ca_body '--BEGIN FAKE CERT-- test --END FAKE CERT--'
23
24
  stack
24
25
 
25
26
  apps { [] }
@@ -0,0 +1,7 @@
1
+ class StubDatabaseDisk < OpenStruct
2
+ end
3
+
4
+ Fabricator(:database_disk, from: :stub_database_disk) do
5
+ size 100
6
+ ebs_volume_type { 'gb2' }
7
+ end
@@ -16,6 +16,8 @@ Fabricator(:database, from: :stub_database) do
16
16
  status 'provisioned'
17
17
  connection_url 'postgresql://aptible:password@10.252.1.125:49158/db'
18
18
  account
19
+ database_image
20
+ disk { Fabricate(:database_disk) }
19
21
  service { nil }
20
22
 
21
23
  backups { [] }
data/spec/spec_helper.rb CHANGED
@@ -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.6
4
+ version: 0.17.1
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-14 00:00:00.000000000 Z
11
+ date: 2021-03-16 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.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.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
@@ -296,10 +296,10 @@ files:
296
296
  - lib/aptible/cli/subcommands/deploy.rb
297
297
  - lib/aptible/cli/subcommands/domains.rb
298
298
  - lib/aptible/cli/subcommands/endpoints.rb
299
+ - lib/aptible/cli/subcommands/environment.rb
299
300
  - lib/aptible/cli/subcommands/inspect.rb
300
301
  - lib/aptible/cli/subcommands/logs.rb
301
302
  - lib/aptible/cli/subcommands/operation.rb
302
- - lib/aptible/cli/subcommands/ps.rb
303
303
  - lib/aptible/cli/subcommands/rebuild.rb
304
304
  - lib/aptible/cli/subcommands/restart.rb
305
305
  - lib/aptible/cli/subcommands/services.rb
@@ -325,6 +325,7 @@ files:
325
325
  - spec/aptible/cli/subcommands/deploy_spec.rb
326
326
  - spec/aptible/cli/subcommands/domains_spec.rb
327
327
  - spec/aptible/cli/subcommands/endpoints_spec.rb
328
+ - spec/aptible/cli/subcommands/environment_spec.rb
328
329
  - spec/aptible/cli/subcommands/inspect_spec.rb
329
330
  - spec/aptible/cli/subcommands/logs_spec.rb
330
331
  - spec/aptible/cli/subcommands/operation_spec.rb
@@ -339,6 +340,7 @@ files:
339
340
  - spec/fabricators/certificate_fabricator.rb
340
341
  - spec/fabricators/configuration_fabricator.rb
341
342
  - spec/fabricators/database_credential_fabricator.rb
343
+ - spec/fabricators/database_disk_fabricator.rb
342
344
  - spec/fabricators/database_fabricator.rb
343
345
  - spec/fabricators/database_image_fabricator.rb
344
346
  - spec/fabricators/operation_fabricator.rb
@@ -359,7 +361,7 @@ homepage: https://github.com/aptible/aptible-cli
359
361
  licenses:
360
362
  - MIT
361
363
  metadata: {}
362
- post_install_message:
364
+ post_install_message:
363
365
  rdoc_options: []
364
366
  require_paths:
365
367
  - lib
@@ -375,7 +377,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
375
377
  version: '0'
376
378
  requirements: []
377
379
  rubygems_version: 3.0.3
378
- signing_key:
380
+ signing_key:
379
381
  specification_version: 4
380
382
  summary: Command-line interface for Aptible services
381
383
  test_files:
@@ -398,6 +400,7 @@ test_files:
398
400
  - spec/aptible/cli/subcommands/deploy_spec.rb
399
401
  - spec/aptible/cli/subcommands/domains_spec.rb
400
402
  - spec/aptible/cli/subcommands/endpoints_spec.rb
403
+ - spec/aptible/cli/subcommands/environment_spec.rb
401
404
  - spec/aptible/cli/subcommands/inspect_spec.rb
402
405
  - spec/aptible/cli/subcommands/logs_spec.rb
403
406
  - spec/aptible/cli/subcommands/operation_spec.rb
@@ -412,6 +415,7 @@ test_files:
412
415
  - spec/fabricators/certificate_fabricator.rb
413
416
  - spec/fabricators/configuration_fabricator.rb
414
417
  - spec/fabricators/database_credential_fabricator.rb
418
+ - spec/fabricators/database_disk_fabricator.rb
415
419
  - spec/fabricators/database_fabricator.rb
416
420
  - spec/fabricators/database_image_fabricator.rb
417
421
  - spec/fabricators/operation_fabricator.rb
@@ -1,30 +0,0 @@
1
- require 'shellwords'
2
-
3
- module Aptible
4
- module CLI
5
- module Subcommands
6
- module Ps
7
- def self.included(thor)
8
- thor.class_eval do
9
- include Helpers::Operation
10
- include Helpers::App
11
-
12
- desc 'ps', 'Display running processes for an app - DEPRECATED'
13
- app_options
14
- def ps
15
- deprecated('This command is deprecated on Aptible v2 stacks.')
16
-
17
- app = ensure_app(options)
18
-
19
- op = app.create_operation!(type: 'ps', status: 'succeeded')
20
-
21
- ENV['ACCESS_TOKEN'] = fetch_token
22
- opts = ['-o', 'SendEnv=ACCESS_TOKEN']
23
- exit_with_ssh_portal(op, *opts)
24
- end
25
- end
26
- end
27
- end
28
- end
29
- end
30
- end