aptible-cli 0.8.6 → 0.9.0

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
  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