Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ GEM
aptible-resource
gem_config
multipart-post (< 2.2.0)
aptible-auth (1.4.0)
aptible-auth (1.5.0)
aptible-resource (~> 1.0)
gem_config
multipart-post (= 2.1.1)
Expand All @@ -49,7 +49,7 @@ GEM
activesupport (>= 4.0, < 6.0)
aptible-resource (~> 1.0)
stripe (>= 1.13.0)
aptible-resource (1.1.3)
aptible-resource (1.1.4)
activesupport
fridge
gem_config (~> 0.3.1)
Expand Down Expand Up @@ -86,7 +86,7 @@ GEM
fabrication (2.15.2)
faraday (0.17.6)
multipart-post (>= 1.2, < 3)
fridge (1.0.0)
fridge (1.0.1)
gem_config
jwt (~> 2.3.0)
gem_config (0.3.2)
Expand All @@ -104,7 +104,7 @@ GEM
json (2.5.1)
jwt (2.3.0)
method_source (1.1.0)
minitest (5.12.0)
minitest (5.15.0)
multi_xml (0.6.0)
multipart-post (2.1.1)
net-http-persistent (3.1.0)
Expand Down Expand Up @@ -184,7 +184,9 @@ DEPENDENCIES
bundler (~> 1.3)
climate_control (= 0.0.3)
fabrication (~> 2.15.2)
hashie (< 5.1)
httplog (< 1.6)
minitest (< 5.16)
pry
rack (~> 1.0)
rake
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ Commands:
aptible operation:cancel OPERATION_ID # Cancel a running operation
aptible operation:follow OPERATION_ID # Follow logs of a running operation
aptible operation:logs OPERATION_ID # View logs for given operation
aptible organizations # List all organizations
aptible rebuild # Rebuild an app, and restart its services
aptible restart # Restart all services associated with an app
aptible services # List Services for an App
Expand Down
2 changes: 2 additions & 0 deletions aptible-cli.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,6 @@ Gem::Specification.new do |spec|
spec.add_development_dependency 'climate_control', '= 0.0.3'
spec.add_development_dependency 'fabrication', '~> 2.15.2'
spec.add_development_dependency 'httplog', '< 1.6'
spec.add_development_dependency 'minitest', '< 5.16'
spec.add_development_dependency 'hashie', '< 5.1'
end
2 changes: 2 additions & 0 deletions lib/aptible/cli/agent.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
require_relative 'subcommands/maintenance'
require_relative 'subcommands/backup_retention_policy'
require_relative 'subcommands/aws_accounts'
require_relative 'subcommands/organizations'

module Aptible
module CLI
Expand Down Expand Up @@ -77,6 +78,7 @@ class Agent < Thor
include Subcommands::Maintenance
include Subcommands::BackupRetentionPolicy
include Subcommands::AwsAccounts
include Subcommands::Organizations

# Forward return codes on failures.
def self.exit_on_failure?
Expand Down
14 changes: 14 additions & 0 deletions lib/aptible/cli/helpers/token.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,20 @@ def decode_token
tok = fetch_token
JWT.decode(tok, nil, false)
end

# Instance of Aptible::Auth::Token from current token
def current_token
Aptible::Auth::Token.current_token(token: fetch_token)
rescue HyperResource::ClientError => e
raise Thor::Error, e.message
end

# Instance of Aptible::Auth::User associated with current token
def whoami
current_token.user
rescue HyperResource::ClientError => e
raise Thor::Error, e.message
end
end
end
end
Expand Down
35 changes: 33 additions & 2 deletions lib/aptible/cli/renderer/text.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,14 @@ def visit(node, io)
# children are KeyedObject instances so they can render properly,
# but we need to warn in tests that this is required.
node.children.each_pair do |k, c|
io.print "#{format_key(k)}: "
visit(c, io)
io.print "#{format_key(k)}:"
if c.is_a?(Formatter::List)
io.puts
visit_indented(c, io, ' ')
else
io.print ' '
visit(c, io)
end
end
when Formatter::GroupedKeyedList
enum = spacer_enumerator
Expand Down Expand Up @@ -65,6 +71,31 @@ def render(node)

private

def visit_indented(node, io, indent)
return unless node.is_a?(Formatter::List)

node.children.each do |child|
case child
when Formatter::Object
child.children.each_pair do |k, c|
io.print "#{indent}#{format_key(k)}:"
if c.is_a?(Formatter::List)
io.puts
visit_indented(c, io, indent + ' ')
else
io.print ' '
visit(c, io)
end
end
io.puts unless child == node.children.last
when Formatter::Value
io.puts "#{indent}#{child.value}"
else
visit(child, io)
end
end
end

def output_list(nodes, io)
if nodes.all? { |v| v.is_a?(Formatter::Value) }
# All nodes are single values, so we render one per line.
Expand Down
36 changes: 14 additions & 22 deletions lib/aptible/cli/subcommands/aws_accounts.rb
Original file line number Diff line number Diff line change
Expand Up @@ -220,32 +220,24 @@ def aws_accounts

response = check_external_aws_account!(id)

if Renderer.format == 'json'
Formatter.render(Renderer.current) do |root|
root.object do |node|
node.value('state', response.state)
node.list('checks') do |check_list|
response.checks.each do |check|
check_list.object do |check_node|
check_node.value('name', check.check_name)
check_node.value('state', check.state)
check_node.value('details', check.details) \
unless check.details.nil?
end
fmt_state = lambda do |state|
Renderer.format == 'json' ? state : format_check_state(state)
end

Formatter.render(Renderer.current) do |root|
root.object do |node|
node.value('state', fmt_state.call(response.state))
node.list('checks') do |check_list|
response.checks.each do |check|
check_list.object do |check_node|
check_node.value('name', check.check_name)
check_node.value('state', fmt_state.call(check.state))
check_node.value('details', check.details) \
unless check.details.nil?
end
end
end
end
else
puts "State: #{format_check_state(response.state)}"
puts ''
puts 'Checks:'
response.checks.each do |check|
puts " Name: #{check.check_name}"
puts " State: #{format_check_state(check.state)}"
puts " Details: #{check.details}" unless check.details.nil?
puts ''
end
end

unless response.state == 'success'
Expand Down
55 changes: 55 additions & 0 deletions lib/aptible/cli/subcommands/organizations.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# frozen_string_literal: true

module Aptible
module CLI
module Subcommands
module Organizations
def self.included(thor)
thor.class_eval do
include Helpers::Token
include Helpers::Telemetry

desc 'organizations', 'List all organizations'
def organizations
telemetry(__method__, options)

user_orgs_and_roles = {}
begin
roles = whoami.roles_with_organizations
rescue HyperResource::ClientError => e
raise Thor::Error, e.message
end
roles.each do |role|
user_orgs_and_roles[role.organization.id] ||= {
'org' => role.organization,
'roles' => []
}
user_orgs_and_roles[role.organization.id]['roles'] << role
end
Formatter.render(Renderer.current) do |root|
root.list do |list|
user_orgs_and_roles.each do |org_id, org_and_role|
org = org_and_role['org']
roles = org_and_role['roles']
list.object do |node|
node.value('id', org_id)
node.value('name', org.name)
node.list('roles') do |roles_list|
roles.each do |role|
roles_list.object do |role_node|
role_node.value('id', role.id)
role_node.value('name', role.name)
end
end
end
end
end
end
end
end
end
end
end
end
end
end
70 changes: 70 additions & 0 deletions spec/aptible/cli/helpers/token_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@

subject { Class.new.send(:include, described_class).new }

let(:token) { 'test-token' }
let(:user) { double('user', id: 'user-id', email: 'test@example.com') }
let(:auth_token) { double('auth_token', user: user) }

describe '#save_token / #fetch_token' do
it 'reads back a token it saved' do
subject.save_token('foo')
Expand Down Expand Up @@ -38,4 +42,70 @@
end
end
end

describe '#current_token' do
before do
subject.save_token(token)
end

it 'returns the current auth token' do
expect(Aptible::Auth::Token).to receive(:current_token)
.with(token: token)
.and_return(auth_token)

expect(subject.current_token).to eq(auth_token)
end

it 'raises Thor::Error on 401 unauthorized' do
response = Faraday::Response.new(status: 401)
error = HyperResource::ClientError.new(
'401 (invalid_token) Invalid Token', response: response
)
expect(Aptible::Auth::Token).to receive(:current_token)
.with(token: token)
.and_raise(error)

expect { subject.current_token }
.to raise_error(Thor::Error, /Invalid Token/)
end

it 'raises Thor::Error on 403 forbidden' do
response = Faraday::Response.new(status: 403)
error = HyperResource::ClientError.new('403 (forbidden) Access denied',
response: response)
expect(Aptible::Auth::Token).to receive(:current_token)
.with(token: token)
.and_raise(error)

expect { subject.current_token }
.to raise_error(Thor::Error, /Access denied/)
end
end

describe '#whoami' do
before do
subject.save_token(token)
end

it 'returns the current user' do
expect(Aptible::Auth::Token).to receive(:current_token)
.with(token: token)
.and_return(auth_token)

expect(subject.whoami).to eq(user)
end

it 'raises Thor::Error on API error' do
response = Faraday::Response.new(status: 401)
error = HyperResource::ClientError.new(
'401 (invalid_token) Invalid Token', response: response
)
expect(Aptible::Auth::Token).to receive(:current_token)
.with(token: token)
.and_raise(error)

expect { subject.whoami }
.to raise_error(Thor::Error, /Invalid Token/)
end
end
end
7 changes: 3 additions & 4 deletions spec/aptible/cli/subcommands/external_aws_accounts_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -692,10 +692,9 @@
.with('42', token: token).and_return(ext)
expect(ext).to receive(:check!).and_return(check_result)

# check command uses puts directly (not Formatter) for non-JSON output
expect { subject.send('aws_accounts:check', '42') }.to output(
/State:.*success/m
).to_stdout
subject.send('aws_accounts:check', '42')

expect(captured_output_text).to match(/State:.*success/m)
end

it 'raises error on check failure' do
Expand Down
Loading