aptible-cli 0.8.6 → 0.9.0

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
  SHA1:
3
- metadata.gz: 710c981d4ee3393bbe00fb09cabb6a45e964aeb7
4
- data.tar.gz: 2705459f3698db56a010edce891f49f832dc584c
3
+ metadata.gz: 5e3e03cae11a2a3bab84c225df322c9fe8c75ab6
4
+ data.tar.gz: aafef516b5c3bc4996317cf977e3cb403d3f7496
5
5
  SHA512:
6
- metadata.gz: 67a7478f09bdfcf92839064523ee231e8b84d8b7455a8e0826ab47a5fa1a09dcb7eaea968a7981694921738bf9733b23b90518a7697427a067b4db921e6440d7
7
- data.tar.gz: dc845bf331a6644e89352af644eacd95b871b12fdc443feb49ca543594fd2868fb735b4be358ece0753ffe125b546ec08991b7ced562b72d8c1bc2e5dc860703
6
+ metadata.gz: 0ef871ee863e4f5e49a82acd02b7062e20c93c11138e3aa3eae2918c82090b0991a198c5166f765b8d75b576a71ac5508ec014774bded52518d9008448dc4597
7
+ data.tar.gz: 6f40a10270be6929ad61c11a42fb11bc3d68dc3a5d62a3d4713a7101fb4eb065005816ed93f7ad2bc561dc0285fadd4f5ca0991b01efde892da59cde25a5da16
data/Gemfile CHANGED
@@ -2,6 +2,7 @@ source 'https://rubygems.org'
2
2
 
3
3
  gem 'pry', github: 'fancyremarker/pry', branch: 'aptible'
4
4
  gem 'activesupport', '~> 4.0'
5
+ gem 'rack', '~> 1.0'
5
6
 
6
7
  group :test do
7
8
  gem 'webmock'
data/README.md CHANGED
@@ -28,36 +28,36 @@ From `aptible help`:
28
28
 
29
29
  ```
30
30
  Commands:
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 TYPE NUMBER # Scale app to NUMBER of instances
35
- aptible backup:list DB_HANDLE # List backups for a database
36
- aptible backup:restore [--handle HANDLE] [--size SIZE_GB] # Restore a backup
37
- aptible config # Print an app's current configuration
38
- aptible config:add # Add an ENV variable to an app
39
- aptible config:rm # Remove an ENV variable from an app
40
- aptible config:set # Alias for config:add
41
- aptible config:unset # Alias for config:rm
42
- aptible db:backup HANDLE # Backup a database
43
- aptible db:clone SOURCE DEST # Clone a database to create a new one
44
- aptible db:create HANDLE # Create a new database
45
- aptible db:deprovision HANDLE # Deprovision a database
46
- aptible db:dump HANDLE # Dump a remote database to file
47
- aptible db:execute HANDLE SQL_FILE # Executes sql against a database
48
- aptible db:list # List all databases
49
- aptible db:reload HANDLE # Reload a database
50
- aptible db:tunnel HANDLE # Create a local tunnel to a database
51
- aptible domains # Print an app's current virtual domains
52
- aptible help [COMMAND] # Describe available commands or one specific command
53
- aptible login # Log in to Aptible
54
- aptible logs # Follows logs from a running app or database
55
- aptible operation:cancel OPERATION_ID # Cancel a running operation
56
- aptible ps # Display running processes for an app - DEPRECATED
57
- aptible rebuild # Rebuild an app, and restart its services
58
- aptible restart # Restart all services associated with an app
59
- aptible ssh [COMMAND] # Run a command against an app
60
- 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] # Scale a service
35
+ aptible backup:list DB_HANDLE # List backups for a database
36
+ aptible backup:restore [--handle HANDLE] [--size SIZE_GB] # Restore a backup
37
+ aptible config # Print an app's current configuration
38
+ aptible config:add # Add an ENV variable to an app
39
+ aptible config:rm # Remove an ENV variable from an app
40
+ aptible config:set # Alias for config:add
41
+ aptible config:unset # Alias for config:rm
42
+ aptible db:backup HANDLE # Backup a database
43
+ aptible db:clone SOURCE DEST # Clone a database to create a new one
44
+ aptible db:create HANDLE # Create a new database
45
+ aptible db:deprovision HANDLE # Deprovision a database
46
+ aptible db:dump HANDLE # Dump a remote database to file
47
+ aptible db:execute HANDLE SQL_FILE # Executes sql against a database
48
+ aptible db:list # List all databases
49
+ aptible db:reload HANDLE # Reload a database
50
+ aptible db:tunnel HANDLE # Create a local tunnel to a database
51
+ aptible domains # Print an app's current virtual domains
52
+ aptible help [COMMAND] # Describe available commands or one specific command
53
+ aptible login # Log in to Aptible
54
+ aptible logs # Follows logs from a running app or database
55
+ aptible operation:cancel OPERATION_ID # Cancel a running operation
56
+ aptible ps # Display running processes for an app - DEPRECATED
57
+ aptible rebuild # Rebuild an app, and restart its services
58
+ aptible restart # Restart all services associated with an app
59
+ aptible ssh [COMMAND] # Run a command against an app
60
+ aptible version # Print Aptible CLI version
61
61
  ```
62
62
 
63
63
  ## Contributing
@@ -1,3 +1,5 @@
1
+ require 'uri'
2
+
1
3
  require 'aptible/auth'
2
4
  require 'thor'
3
5
  require 'json'
@@ -22,6 +24,7 @@ require_relative 'subcommands/restart'
22
24
  require_relative 'subcommands/ssh'
23
25
  require_relative 'subcommands/backup'
24
26
  require_relative 'subcommands/operation'
27
+ require_relative 'subcommands/inspect'
25
28
 
26
29
  module Aptible
27
30
  module CLI
@@ -41,6 +44,7 @@ module Aptible
41
44
  include Subcommands::SSH
42
45
  include Subcommands::Backup
43
46
  include Subcommands::Operation
47
+ include Subcommands::Inspect
44
48
 
45
49
  # Forward return codes on failures.
46
50
  def self.exit_on_failure?
@@ -34,20 +34,66 @@ module Aptible
34
34
  end
35
35
  end
36
36
 
37
- desc 'apps:scale TYPE NUMBER', 'Scale app to NUMBER of instances'
37
+ desc 'apps:scale SERVICE ' \
38
+ '[--container-count COUNT] [--container-size SIZE]',
39
+ 'Scale a service'
38
40
  app_options
39
- option :size, type: :numeric, enum: [512,
40
- 1024,
41
- 2048,
42
- 4096,
43
- 8192,
44
- 16384,
45
- 32768,
46
- 65536]
47
- define_method 'apps:scale' do |type, n|
48
- num = Integer(n)
41
+ option :container_count, type: :numeric
42
+ option :container_size, type: :numeric
43
+ option :size, type: :numeric,
44
+ desc: 'DEPRECATED, use --container-size'
45
+ define_method 'apps:scale' do |type, *more|
49
46
  app = ensure_app(options)
50
47
  service = app.services.find { |s| s.process_type == type }
48
+
49
+ container_count = options[:container_count]
50
+ container_size = options[:container_size]
51
+
52
+ # There are two legacy options we have to process here:
53
+ # - We used to accept apps:scale SERVICE COUNT
54
+ # - We used to accept --size
55
+ case more.size
56
+ when 0
57
+ # Noop
58
+ when 1
59
+ if container_count.nil?
60
+ m = yellow('Passing container count as a positional ' \
61
+ 'argument is deprecated, use --container-count')
62
+ $stderr.puts m
63
+ container_count = Integer(more.first)
64
+ else
65
+ raise Thor::Error, 'Container count was passed via both ' \
66
+ 'the --container-count keyword argument ' \
67
+ 'and a positional argument. ' \
68
+ 'Use only --container-count to proceed.'
69
+ end
70
+ else
71
+ # Unfortunately, Thor does not want to let us easily hook into
72
+ # its usage formatting, so we have to work around it here.
73
+ command = thor.commands.fetch('apps:scale')
74
+ error = ArgumentError.new
75
+ args = [type] + more
76
+ thor.handle_argument_error(command, error, args, 1)
77
+ end
78
+
79
+ if options[:size]
80
+ if container_size.nil?
81
+ m = yellow('Passing container size via the --size keyword ' \
82
+ 'argument is deprecated, use --container-size')
83
+ $stderr.puts m
84
+ container_size = options[:size]
85
+ else
86
+ raise Thor::Error, 'Container size was passed via both ' \
87
+ '--container-size and --size. ' \
88
+ 'Use only --container-size to proceed.'
89
+ end
90
+ end
91
+
92
+ if container_count.nil? && container_size.nil?
93
+ raise Thor::Error,
94
+ 'Provide at least --container-count or --container-size'
95
+ end
96
+
51
97
  if service.nil?
52
98
  valid_types = if app.services.empty?
53
99
  'NONE (deploy the app first)'
@@ -58,9 +104,13 @@ module Aptible
58
104
  "exist for app #{app.handle}. Valid " \
59
105
  "types: #{valid_types}."
60
106
  end
61
- op = service.create_operation!(type: 'scale',
62
- container_count: num,
63
- container_size: options[:size])
107
+
108
+ # We don't validate any parameters here: API will do that for us.
109
+ opts = { type: 'scale' }
110
+ opts[:container_count] = container_count if container_count
111
+ opts[:container_size] = container_size if container_size
112
+
113
+ op = service.create_operation!(opts)
64
114
  attach_to_operation_logs(op)
65
115
  end
66
116
 
@@ -0,0 +1,51 @@
1
+ module Aptible
2
+ module CLI
3
+ module Subcommands
4
+ module Inspect
5
+ class InspectResourceCommand < Thor::HiddenCommand
6
+ def run(instance, args = [])
7
+ instance.inspect_resource(*args)
8
+ end
9
+ end
10
+
11
+ def inspect_resource(raw)
12
+ begin
13
+ uri = URI(raw)
14
+ rescue URI::InvalidURIError
15
+ raise Thor::Error, "Invalid URI: #{raw}"
16
+ end
17
+
18
+ if uri.scheme != 'https'
19
+ raise "Invalid scheme: #{uri.scheme} (use https)"
20
+ end
21
+
22
+ apis = [Aptible::Auth, Aptible::Api, Aptible::Billing]
23
+
24
+ api = apis.find do |klass|
25
+ uri.host == URI(klass.configuration.root_url).host
26
+ end
27
+
28
+ if api.nil?
29
+ hosts = apis.map(&:configuration).map(&:root_url).map do |u|
30
+ URI(u).host
31
+ end
32
+ m = "Invalid API: #{uri.host} (valid APIs: #{hosts.join(', ')})"
33
+ raise Thor::Error, m
34
+ end
35
+
36
+ res = api::Resource.new(token: fetch_token).find_by_url(uri.to_s)
37
+ puts JSON.pretty_generate(res.body)
38
+ end
39
+
40
+ def self.included(thor)
41
+ # We have to manually register a command here since we can't override
42
+ # the inspect method!
43
+ desc = 'Inspect a resource as JSON by URL'
44
+ thor.commands['inspect'] = InspectResourceCommand.new(
45
+ :inspect, desc, desc, 'inspect URL'
46
+ )
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -1,5 +1,5 @@
1
1
  module Aptible
2
2
  module CLI
3
- VERSION = '0.8.6'.freeze
3
+ VERSION = '0.9.0'.freeze
4
4
  end
5
5
  end
@@ -1,3 +1,5 @@
1
+ require 'spec_helper'
2
+
1
3
  def dummy_strategy_factory(app_handle, env_handle, usable,
2
4
  options_receiver = [])
3
5
  Class.new do
@@ -31,51 +33,129 @@ describe Aptible::CLI::Agent do
31
33
  allow(Aptible::Api::Account).to receive(:all) { [account] }
32
34
  end
33
35
 
34
- it 'should pass given correct parameters' do
35
- allow(subject).to receive(:options) do
36
- { app: 'hello', environment: 'foobar' }
37
- end
38
- expect(service).to receive(:create_operation!) { op }
39
- expect(subject).to receive(:environment_from_handle)
40
- .with('foobar')
41
- .and_return(account)
42
- expect(subject).to receive(:apps_from_handle).and_return([app])
43
- subject.send('apps:scale', 'web', 3)
44
- end
36
+ context 'with environment and app' do
37
+ let(:base_options) { { app: 'hello', environment: 'foobar' } }
45
38
 
46
- it 'should pass container size param to operation if given' do
47
- allow(subject).to receive(:options) do
48
- { app: 'hello', size: 90210, environment: 'foobar' }
49
- end
50
- expect(service).to receive(:create_operation!)
51
- .with(type: 'scale', container_count: 3, container_size: 90210)
52
- .and_return(op)
53
- expect(subject).to receive(:environment_from_handle)
54
- .with('foobar')
55
- .and_return(account)
56
- expect(subject).to receive(:apps_from_handle).and_return([app])
57
- subject.send('apps:scale', 'web', 3)
39
+ before do
40
+ expect(subject).to receive(:environment_from_handle)
41
+ .with('foobar')
42
+ .and_return(account)
43
+
44
+ expect(subject).to receive(:apps_from_handle)
45
+ .with('hello', account)
46
+ .and_return([app])
47
+ end
48
+
49
+ def stub_options(**opts)
50
+ allow(subject).to receive(:options).and_return(base_options.merge(opts))
51
+ end
52
+
53
+ it 'should scale container size and count together' do
54
+ stub_options(container_count: 3, container_size: 1024)
55
+ expect($stderr).not_to receive(:puts)
56
+ expect(service).to receive(:create_operation!)
57
+ .with(type: 'scale', container_count: 3, container_size: 1024)
58
+ .and_return(op)
59
+ subject.send('apps:scale', 'web')
60
+ end
61
+
62
+ it 'should scale container count alone' do
63
+ stub_options(container_count: 3)
64
+ expect($stderr).not_to receive(:puts)
65
+ expect(service).to receive(:create_operation!)
66
+ .with(type: 'scale', container_count: 3)
67
+ .and_return(op)
68
+ subject.send('apps:scale', 'web')
69
+ end
70
+
71
+ it 'should scale container size alone' do
72
+ stub_options(container_size: 1024)
73
+ expect($stderr).not_to receive(:puts)
74
+ expect(service).to receive(:create_operation!)
75
+ .with(type: 'scale', container_size: 1024)
76
+ .and_return(op)
77
+ subject.send('apps:scale', 'web')
78
+ end
79
+
80
+ it 'should fail if neither container_count nor container_size is set' do
81
+ stub_options
82
+ expect { subject.send('apps:scale', 'web') }
83
+ .to raise_error(/provide at least/im)
84
+ end
85
+
86
+ it 'should scale container count (legacy)' do
87
+ stub_options
88
+ expect($stderr).to receive(:puts).once
89
+ expect(service).to receive(:create_operation!)
90
+ .with(type: 'scale', container_count: 3)
91
+ .and_return(op)
92
+ subject.send('apps:scale', 'web', '3')
93
+ end
94
+
95
+ it 'should scale container size (legacy)' do
96
+ stub_options(size: 90210)
97
+ expect($stderr).to receive(:puts).once
98
+ expect(service).to receive(:create_operation!)
99
+ .with(type: 'scale', container_size: 90210)
100
+ .and_return(op)
101
+ subject.send('apps:scale', 'web')
102
+ end
103
+
104
+ it 'should fail when using both current and legacy count' do
105
+ stub_options(container_count: 2)
106
+ expect { subject.send('apps:scale', 'web', '3') }
107
+ .to raise_error(/count was passed via both/im)
108
+ end
109
+
110
+ it 'should fail when using both current and legacy size' do
111
+ stub_options(container_size: 1024, size: 512)
112
+ expect { subject.send('apps:scale', 'web') }
113
+ .to raise_error(/size was passed via both/im)
114
+ end
115
+
116
+ it 'should fail when using too many arguments' do
117
+ stub_options
118
+ expect { subject.send('apps:scale', 'web', '3', '4') }
119
+ .to raise_error(/usage:.*apps:scale/im)
120
+ end
121
+
122
+ it 'should fail if the service does not exist' do
123
+ stub_options(container_count: 2)
124
+
125
+ expect { subject.send('apps:scale', 'potato') }
126
+ .to raise_error(Thor::Error, /Service.* potato.* does not exist/)
127
+ end
128
+
129
+ it 'should fail if the app has no services' do
130
+ app.services = []
131
+ stub_options(container_count: 2)
132
+
133
+ expect { subject.send('apps:scale', 'web') }
134
+ .to raise_error(Thor::Error, /deploy the app first/)
135
+ end
58
136
  end
59
137
 
60
138
  it 'should fail if environment is non-existent' do
61
139
  allow(subject).to receive(:options) do
62
- { environment: 'foo', app: 'web' }
140
+ { environment: 'foo', app: 'web', container_count: 2 }
63
141
  end
64
142
  allow(Aptible::Api::Account).to receive(:all) { [] }
65
143
  allow(service).to receive(:create_operation!) { op }
66
144
 
67
145
  expect do
68
- subject.send('apps:scale', 'web', 3)
146
+ subject.send('apps:scale', 'web')
69
147
  end.to raise_error(Thor::Error)
70
148
  end
71
149
 
72
150
  it 'should fail if app is non-existent' do
151
+ allow(subject).to receive(:options) { { container_count: 2 } }
73
152
  expect do
74
- subject.send('apps:scale', 'web', 3)
153
+ subject.send('apps:scale', 'web')
75
154
  end.to raise_error(Thor::Error)
76
155
  end
77
156
 
78
- it 'should fail if number is not a valid number' do
157
+ it 'should fail if number is not a valid number (legacy)' do
158
+ expect($stderr).to receive(:puts).once
79
159
  allow(subject).to receive(:options) { { app: 'hello' } }
80
160
  allow(service).to receive(:create_operation) { op }
81
161
 
@@ -83,38 +163,6 @@ describe Aptible::CLI::Agent do
83
163
  subject.send('apps:scale', 'web', 'potato')
84
164
  end.to raise_error(ArgumentError)
85
165
  end
86
-
87
- it 'should fail if the service does not exist' do
88
- allow(subject).to receive(:options) do
89
- { app: 'hello', environment: 'foobar' }
90
- end
91
- expect(subject).to receive(:environment_from_handle)
92
- .with('foobar')
93
- .and_return(account)
94
- expect(subject).to receive(:apps_from_handle).and_return([app])
95
-
96
- expect do
97
- subject.send('apps:scale', 'potato', 1)
98
- end.to raise_error(Thor::Error, /Service.* potato.* does not exist/)
99
- end
100
-
101
- context 'no service' do
102
- before { app.services = [] }
103
-
104
- it 'should fail if the app has no services' do
105
- allow(subject).to receive(:options) do
106
- { app: 'hello', environment: 'foobar' }
107
- end
108
- expect(subject).to receive(:environment_from_handle)
109
- .with('foobar')
110
- .and_return(account)
111
- expect(subject).to receive(:apps_from_handle).and_return([app])
112
-
113
- expect do
114
- subject.send('apps:scale', 'web', 1)
115
- end.to raise_error(Thor::Error, /deploy the app first/)
116
- end
117
- end
118
166
  end
119
167
 
120
168
  describe '#ensure_app' do
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe Aptible::CLI::Agent do
4
+ describe '#inspect_resource' do
5
+ let(:token) { 'foo token' }
6
+ before { allow(subject).to receive(:fetch_token).and_return(token) }
7
+
8
+ it 'should fail if the URI is invalid' do
9
+ expect { subject.inspect_resource('^^') }
10
+ .to raise_error(/invalid uri/im)
11
+ end
12
+
13
+ it 'should fail if the URI is not for a valid host' do
14
+ expect { subject.inspect_resource('https://foo.com') }
15
+ .to raise_error(/invalid api/im)
16
+ end
17
+
18
+ it 'should fail if the scheme is invalid' do
19
+ # Not necessarily a feature per-se, but the URI will be parsed improperly
20
+ # if we don't have a scheme.
21
+ expect { subject.inspect_resource('api.aptible.com') }
22
+ .to raise_error(/invalid scheme/im)
23
+ end
24
+
25
+ it 'should succeed if the URI is complete' do
26
+ api = double('api')
27
+ expect(Aptible::Api::Resource).to receive(:new).with(token: token)
28
+ .and_return(api)
29
+
30
+ res = double('resource', body: { foo: 'bar' })
31
+ expect(api).to receive(:find_by_url).with('https://api.aptible.com/foo')
32
+ .and_return(res)
33
+
34
+ expect(subject).to receive(:puts) do |body|
35
+ expect(JSON.parse(body)).to eq('foo' => 'bar')
36
+ end
37
+
38
+ subject.inspect_resource('https://api.aptible.com/foo')
39
+ end
40
+ end
41
+ end
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.8.6
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Frank Macreery
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-04-21 00:00:00.000000000 Z
11
+ date: 2017-05-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aptible-api
@@ -239,6 +239,7 @@ files:
239
239
  - lib/aptible/cli/subcommands/config.rb
240
240
  - lib/aptible/cli/subcommands/db.rb
241
241
  - lib/aptible/cli/subcommands/domains.rb
242
+ - lib/aptible/cli/subcommands/inspect.rb
242
243
  - lib/aptible/cli/subcommands/logs.rb
243
244
  - lib/aptible/cli/subcommands/operation.rb
244
245
  - lib/aptible/cli/subcommands/ps.rb
@@ -257,6 +258,7 @@ files:
257
258
  - spec/aptible/cli/subcommands/backup_spec.rb
258
259
  - spec/aptible/cli/subcommands/db_spec.rb
259
260
  - spec/aptible/cli/subcommands/domains_spec.rb
261
+ - spec/aptible/cli/subcommands/inspect_spec.rb
260
262
  - spec/aptible/cli/subcommands/logs_spec.rb
261
263
  - spec/aptible/cli/subcommands/operation_spec.rb
262
264
  - spec/aptible/cli/subcommands/restart_spec.rb
@@ -312,6 +314,7 @@ test_files:
312
314
  - spec/aptible/cli/subcommands/backup_spec.rb
313
315
  - spec/aptible/cli/subcommands/db_spec.rb
314
316
  - spec/aptible/cli/subcommands/domains_spec.rb
317
+ - spec/aptible/cli/subcommands/inspect_spec.rb
315
318
  - spec/aptible/cli/subcommands/logs_spec.rb
316
319
  - spec/aptible/cli/subcommands/operation_spec.rb
317
320
  - spec/aptible/cli/subcommands/restart_spec.rb