Skip to content

Commit

Permalink
Merge pull request #7 from alces-flight/develop
Browse files Browse the repository at this point in the history
Changes for version 1.0.0 release
  • Loading branch information
WilliamMcCumstie authored Sep 10, 2020
2 parents 8f63b4a + 64a5ece commit 3880e47
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 18 deletions.
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ gem 'commander-openflighthpc'
gem 'faraday'
gem 'faraday_middleware'
gem 'hashie'
gem 'jwt'
gem 'tty-table'
gem 'tty-prompt'

group :development do
gem 'pry'
Expand Down
29 changes: 21 additions & 8 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
GEM
remote: https://rubygems.org/
specs:
activesupport (6.0.2.1)
activesupport (6.0.3.2)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
tzinfo (~> 1.1)
zeitwerk (~> 2.2)
zeitwerk (~> 2.2, >= 2.2.2)
byebug (11.0.1)
coderay (1.1.2)
commander-openflighthpc (1.2.0)
highline (~> 1.7.2)
paint (~> 2.1.0)
concurrent-ruby (1.1.6)
concurrent-ruby (1.1.7)
equatable (0.6.1)
faraday (0.17.3)
multipart-post (>= 1.2, < 3)
faraday_middleware (0.14.0)
faraday (>= 0.7.4, < 1.0)
hashie (4.1.0)
highline (1.7.10)
i18n (1.8.2)
i18n (1.8.5)
concurrent-ruby (~> 1.0)
jwt (2.2.2)
method_source (0.9.2)
minitest (5.14.0)
minitest (5.14.1)
multipart-post (2.1.1)
necromancer (0.5.0)
paint (2.1.1)
Expand All @@ -43,18 +44,28 @@ GEM
strings-ansi (0.2.0)
thread_safe (0.3.6)
tty-color (0.5.0)
tty-cursor (0.7.1)
tty-prompt (0.20.0)
necromancer (~> 0.5.0)
pastel (~> 0.7.0)
tty-reader (~> 0.7.0)
tty-reader (0.7.0)
tty-cursor (~> 0.7)
tty-screen (~> 0.7)
wisper (~> 2.0.0)
tty-screen (0.7.0)
tty-table (0.11.0)
equatable (~> 0.6)
necromancer (~> 0.5)
pastel (~> 0.7.2)
strings (~> 0.1.5)
tty-screen (~> 0.7)
tzinfo (1.2.6)
tzinfo (1.2.7)
thread_safe (~> 0.1)
unicode-display_width (1.6.0)
unicode_utils (1.4.0)
zeitwerk (2.2.2)
wisper (2.0.1)
zeitwerk (2.4.0)

PLATFORMS
ruby
Expand All @@ -65,9 +76,11 @@ DEPENDENCIES
faraday
faraday_middleware
hashie
jwt
pry
pry-byebug
tty-prompt
tty-table

BUNDLED WITH
1.17.3
2.1.4
11 changes: 11 additions & 0 deletions docs/api_routes.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ All requests SHOULD set the following headers:
```
Authorization: Bearer <token>
Accepts: application/json
Content-Type: application/json
```

The `Accepts` header MAY be omitted if `.json` is appended onto the end of any of the URI documented below. The response MUST be `406 Not Acceptable` if the `Accepts` header has been omitted without appending `.json`.
Expand All @@ -53,6 +54,7 @@ Retrieve remaining number of compute units
GET :leader/compute-balance
Authorization: Bearer <token>
Accepts: application/json
Content-Type: application/json
```

#### Request Elements
Expand All @@ -74,6 +76,11 @@ Type: Integer
#### Example

```
GET /compute-balance
Authorization: Bearer <token>
Accepts: application/json
Content-Type: application/json
HTTP/2 200 OK
Content-Type: application/json
Expand All @@ -90,6 +97,7 @@ Consume a set number of compute units
POST :leader/compute-balance/consume
Authorization: Bearer <token>
Accepts: application/json
Content-Type: application/json
{
"consumption": {
Expand Down Expand Up @@ -172,6 +180,7 @@ The following is an example of case 1. This is a standard request with sufficien
POST :leader/compute-balance/consume
Authorization: Bearer <token>
Accepts: application/json
Content-Type: application/json
{
"consumption": {
Expand All @@ -197,6 +206,7 @@ The following is an example of case 2 where there are insufficient `compute unit
POST :leader/compute-balance/consume
Authorization: Bearer <token>
Accepts: application/json
Content-Type: application/json
{
"consumption": {
Expand All @@ -221,6 +231,7 @@ The following is an example of case 3 where there are insufficient `compute unit
POST :leader/compute-balance/consume
Authorization: Bearer <token>
Accepts: application/json
Content-Type: application/json
{
"consumption": {
Expand Down
73 changes: 63 additions & 10 deletions lib/charge_client/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,31 @@
require 'faraday'
require 'faraday_middleware'

require_relative 'configure'

module ChargeClient
VERSION = '0.1.4'
VERSION = '1.0.0'

class BaseError < StandardError; end
class ServerError < BaseError; end
class ClientError < BaseError; end

class HandleErrors < Faraday::Middleware
class CustomMiddleware < Faraday::Middleware
attr_reader :jwt

def initialize(app, jwt:)
@app = app
@jwt = jwt
end

def call(env)
raise ClientError, <<~ERROR.chomp if jwt.empty?
The API access token has not been set! Please set it with:
#{Paint["#{CLI.program(:name)} configure JWT", :yellow]}
ERROR
env.request_headers['Authorization'] = "Bearer #{jwt}"
@app.call(env).tap do |res|
self.check_token if res.status == 404
raise ServerError, <<~ERROR.chomp if res.status >= 500
Unrecoverable server-side error encountered (#{res.status})
ERROR
Expand All @@ -57,15 +72,37 @@ def call(env)
Bad response format received from server
ERROR
end
rescue Faraday::ConnectionFailed
raise ServerError, 'Unable to connect to the API server'
end

def check_token
expiry = begin
JWT.decode(jwt, nil, false).first['exp']
rescue
raise ClientError, <<~ERROR.chomp
Your access token appears to be malformed and needs to be regenerated.
Please take care when copying the token into the configure command:
#{Paint["#{CLI.program(:name)} configure JWT", :yellow]}
ERROR
end

if expiry && expiry < Time.now.to_i
raise ClientError, <<~ERROR.chomp
Your access token has expired! Please regenerate it and run:
#{Paint["#{CLI.program(:name)} configure JWT", :yellow]}
ERROR
end
end
end

class CLI
extend Commander::Delegates

program :name, 'flight-cu'
program :application, 'Flight Compute Units'
program :version, ChargeClient::VERSION
program :description, 'Charges compute units for work done'
program :description, 'Manage Alces Flight Center compute unit balance'
program :help_paging, false

silent_trace!
Expand Down Expand Up @@ -97,21 +134,37 @@ def self.action(command)
def self.connection
@connection ||= Faraday.new(
Config::Cache.base_url,
headers: {
'Authorization' => "Bearer #{Config::Cache.jwt_token}",
'Accept' => "application/json"
}
headers: { 'Accept' => "application/json" }
) do |conn|
conn.request :json
conn.response :json, content_type: 'application/json'
conn.use HandleErrors
conn.use CustomMiddleware, jwt: Config::Cache.jwt_token

# Log requests to STDERR in dev mode
# TODO: Make this more standard
if Config::Cache.debug
logger = Logger.new($stderr)
logger.level = Logger::DEBUG
conn.use Faraday::Response::Logger, logger, { bodies: true } do |l|
l.filter(/(Authorization:)(.*)/, '\1 [REDACTED]')
end
end
conn.adapter :net_http
end
end

command 'configure' do |c|
cli_syntax(c, '[JWT]')
c.summary = 'Set the API access token'
c.action do |args, _|
new_jwt = (args.length > 0 ? args.first : nil)
Configure.new(Config::Cache.jwt_token, new_jwt).run
end
end

command 'balance' do |c|
cli_syntax(c)
c.summary = 'View the available compute units'
c.summary = 'View available compute unit balance'
c.action do
puts connection.get('/compute-balance').body['computeUnitBalance']
end
Expand All @@ -122,7 +175,7 @@ def self.connection
c.summary = 'Debit compute units from the balance'
c.action do |args, _|
payload = { amount: args[0], reason: args[1] }
payload[:private_reason] = args[3] if args.length > 2
payload[:private_reason] = args[2] if args.length > 2
data = connection.post('/compute-balance/consume', consumption: payload).body

error = data['error']
Expand Down
67 changes: 67 additions & 0 deletions lib/charge_client/configure.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#==============================================================================
# Copyright (C) 2020-present Alces Flight Ltd.
#
# This file is part of Charge Client.
#
# This program and the accompanying materials are made available under
# the terms of the Eclipse Public License 2.0 which is available at
# <https://www.eclipse.org/legal/epl-2.0>, or alternative license
# terms made available by Alces Flight Ltd - please direct inquiries
# about licensing to [email protected].
#
# Charge Client is distributed in the hope that it will be useful, but
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
# IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS
# OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A
# PARTICULAR PURPOSE. See the Eclipse Public License 2.0 for more
# details.
#
# You should have received a copy of the Eclipse Public License 2.0
# along with Charge Client. If not, see:
#
# https://opensource.org/licenses/EPL-2.0
#
# For more information on Charge Client, please visit:
# https://github.com/alces-flight/charge-client
#==============================================================================

require 'tty-prompt'

module ChargeClient
Configure = Struct.new(:old_jwt, :new_jwt) do
def run
prompt_for_jwt if $stdout.tty? && !new_jwt
data = YAML.load File.read(Config::Cache.path), symbolize_names: true
data[:jwt_token] = new_jwt
begin
File.write(Config::Cache.path, YAML.dump(data))
rescue
raise BaseError, <<~ERROR.chomp
Failed to update the configuration file!
Please contact your system administrator for further assistance.
ERROR
end
end

def prompt_for_jwt
opts = { required: true }.tap { |o| o[:default] = old_jwt_mask if old_jwt_mask }
self.new_jwt = prompt.ask 'Alces Flight Center API token:', **opts
self.new_jwt = nil if new_jwt == old_jwt_mask
end

def prompt
@prompt ||= TTY::Prompt.new
end

def old_jwt_mask
@old_jwt_mask ||= if old_jwt.nil?
nil
elsif old_jwt[-8..-1].nil?
('*' * 24)
else
('*' * 24) + old_jwt[-8..-1]
end
end
end
end

0 comments on commit 3880e47

Please sign in to comment.