Skip to content

Add basic Credentials wrapper + ManagedNative mixin #20

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Oct 12, 2020
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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
strategy:
fail-fast: false
matrix:
ruby: [2.5, 2.6, 2.7, jruby, truffleruby]
ruby: [2.5, 2.6, 2.7, jruby]
runs-on: ubuntu-latest
steps:
- uses: ruby/setup-ruby@v1
Expand Down Expand Up @@ -72,7 +72,7 @@ jobs:
fail-fast: false
matrix:
os: [macos]
ruby: [2.5, 2.6, 2.7, jruby, truffleruby]
ruby: [2.5, 2.6, 2.7, jruby]
runs-on: macos-latest
steps:
- uses: ruby/setup-ruby@v1
Expand Down
6 changes: 5 additions & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,16 @@ Metrics/BlockLength:
Exclude:
- 'gems/aws-crt/Rakefile'
- '**/*.rake'
- 'gems/aws-crt/spec/**/*.rb'
- 'gems/**/spec/**/*.rb'

Metrics/MethodLength:
Max: 25
Exclude:
- 'gems/aws-crt/ext/compile.rb'

Metrics/AbcSize:
Max: 20

Naming/FileName:
Exclude:
- 'gems/aws-crt/lib/aws-crt.rb'
Expand Down
2 changes: 1 addition & 1 deletion format-check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ if NOT type $CLANG_FORMAT 2> /dev/null ; then
fi

FAIL=0
SOURCE_FILES=`find aws-crt/native/src -type f \( -name '*.h' -o -name '*.c' \)`
SOURCE_FILES=`find gems/aws-crt/native/src -type f \( -name '*.h' -o -name '*.c' \)`
for i in $SOURCE_FILES
do
$CLANG_FORMAT -output-replacements-xml $i | grep -c "<replacement " > /dev/null
Expand Down
1 change: 1 addition & 0 deletions gems/aws-crt-auth/lib/aws-crt-auth.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require 'aws-crt'
require_relative 'aws-crt-auth/credentials'

module Aws
module Crt
Expand Down
71 changes: 71 additions & 0 deletions gems/aws-crt-auth/lib/aws-crt-auth/credentials.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# frozen_string_literal: true

module Aws
module Crt
module Auth
# Utility class for Credentials.
class Credentials
include Aws::Crt::ManagedNative
native_destroy Aws::Crt::Native.method(:credentials_release)

UINT64_MAX = 18_446_744_073_709_551_615

# @param [String] access_key_id
# @param [String] secret_access_key
# @param [String] session_token (nil)
# @param [Time|int] expiration (nil) - Either a Time or an int
# seconds since unix epoch
def initialize(access_key_id, secret_access_key,
session_token = nil, expiration = nil)
if !access_key_id || access_key_id.empty?
raise ArgumentError, 'access_key_id must be set'
end

if !secret_access_key || secret_access_key.empty?
raise ArgumentError, 'secret_access_key must be set'
end

manage_native do
Aws::Crt::Native.credentials_new(
access_key_id,
secret_access_key,
session_token,
expiration&.to_i || UINT64_MAX
)
end
end

# @return [String]
def access_key_id
Aws::Crt::Native.credentials_get_access_key_id(native).to_s
end

# @return [String]
def secret_access_key
Aws::Crt::Native.credentials_get_secret_access_key(native).to_s
end

# @return [String, nil]
def session_token
Aws::Crt::Native.credentials_get_session_token(native).to_s
end

# @return [Time,nil]
def expiration
exp = Aws::Crt::Native.credentials_get_expiration_timepoint_seconds!(
native
)
return if exp == UINT64_MAX

Time.at(exp)
end

# Removing the secret access key from the default inspect string.
# @api private
def inspect
"#<#{self.class.name} access_key_id=#{access_key_id.inspect}>"
end
end
end
end
end
98 changes: 98 additions & 0 deletions gems/aws-crt-auth/spec/credentials_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# frozen_string_literal: true

require_relative 'spec_helper'
require 'weakref'

module Aws
module Crt
module Auth #:nodoc:
describe Credentials do
describe '#initilize' do
it 'raises an ArgumentError when missing access_key_id' do
expect { Credentials.new(nil, 'secret') }
.to raise_error(ArgumentError)
end

it 'raises an ArgumentError when missing secret_access_key' do
expect { Credentials.new('akid', nil) }
.to raise_error(ArgumentError)
end

it 'defaults the session token to nil' do
expect(Credentials.new('akid', 'secret').session_token).to be nil
end

it 'defaults the expiration to nil' do
expect(Credentials.new('akid', 'secret').expiration)
.to be_nil
end

it 'accepts a Time for expiration' do
exp = Time.now
creds = Credentials.new('akid', 'secret', 'token', exp)
expect(creds.expiration.to_i).to eq exp.to_i
end

it 'accepts an epoch (integer) for expiration' do
exp = Time.now
creds = Credentials.new('akid', 'secret', 'token', exp.to_i)
expect(creds.expiration.to_i).to eq exp.to_i
end
end

describe 'accessors' do
let(:exp) { Time.now }
let(:creds) { Credentials.new('akid', 'secret', 'token', exp) }

it 'provides access to the access key id' do
expect(creds.access_key_id).to eq('akid')
end

it 'provides access to the secret access key' do
expect(creds.secret_access_key).to eq('secret')
end

it 'provides access to the session token' do
expect(creds.session_token).to eq('token')
end

it 'provides access to the expiration' do
expect(creds.expiration.to_i).to eq exp.to_i
end
end

describe '#inspect' do
let(:creds) { Credentials.new('akid', 'secret', 'token') }

it 'does not include the secret_access_key' do
expect(creds.inspect).not_to include 'secret'
end
end

describe '.on_release' do
it 'cleans up with release' do
creds = Credentials.new('akid', 'secret')
expect(creds).to_not be_nil

creds.release
check_for_clean_shutdown
end

if garbage_collect_is_immediate?
it 'cleans up with GC' do
creds = Credentials.new('akid', 'secret', 'session')
weakref = WeakRef.new(creds)
expect(weakref.weakref_alive?).to be true

# force cleanup via GC
creds = nil # rubocop:disable Lint/UselessAssignment
ObjectSpace.garbage_collect
expect(weakref.weakref_alive?).to be_falsey
check_for_clean_shutdown
end
end
end
end
end
end
end
1 change: 1 addition & 0 deletions gems/aws-crt-auth/spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@
# use the local version of aws-crt
$LOAD_PATH.unshift File.expand_path('../../aws-crt/lib', __dir__)

require_relative '../../aws-crt/spec/spec_helper'
require 'aws-crt-auth'
15 changes: 1 addition & 14 deletions gems/aws-crt/lib/aws-crt.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
require_relative 'aws-crt/platforms'
require_relative 'aws-crt/native'
require_relative 'aws-crt/errors'
require_relative 'aws-crt/managed_native'
require_relative 'aws-crt/io'

# Top level Amazon Web Services (AWS) namespace
Expand All @@ -11,19 +12,5 @@ module Aws
module Crt
# Ensure native init() is called when gem loads
Aws::Crt::Native.init

# Invoke native call, and raise exception if it failed
def self.call
res = yield
# functions that return void cannot fail
return unless res

# for functions that return int, non-zero indicates failure
Errors.raise_last_error if res.is_a?(Integer) && res != 0

# for functions that return pointer, NULL indicates failure
Errors.raise_last_error if res.is_a?(FFI::Pointer) && res.null?
res
end
end
end
22 changes: 4 additions & 18 deletions gems/aws-crt/lib/aws-crt/io.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ module IO
# Classes that need to do async work will ask the EventLoopGroup
# for an event-loop to use.
class EventLoopGroup
include Aws::Crt::ManagedNative
native_destroy Aws::Crt::Native.method(:event_loop_group_release)

def initialize(max_threads = nil)
unless max_threads.nil? ||
(max_threads.is_a?(Integer) && max_threads.positive?)
Expand All @@ -18,26 +21,9 @@ def initialize(max_threads = nil)
# Ruby uses nil to request default values, native code uses 0
max_threads = 0 if max_threads.nil?

native = Aws::Crt.call do
manage_native do
Aws::Crt::Native.event_loop_group_new(max_threads)
end

@native = FFI::AutoPointer.new(native, self.class.method(:on_release))
end

# Immediately release this instance's attachment to the underlying
# resources, without waiting for the garbage collector.
# Note that underlying resources will remain alive until nothing
# else is using them.
def release
return unless @native

@native.free
@native = nil
end

def self.on_release(native)
Aws::Crt::Native.event_loop_group_release(native)
end
end
end
Expand Down
86 changes: 86 additions & 0 deletions gems/aws-crt/lib/aws-crt/managed_native.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# frozen_string_literal: true

module Aws
module Crt
# A mixin module for generic managed native functionality
# Example:
#
# class C
# include Aws::Crt::ManagedNative
# native_destroy Aws::Crt::Native.method(:test_struct_destroy)
#
# def initialize
# manage_native { Aws::Crt::Native::test_struct_new() }
# end
#
# def use_native
# Aws::Crt::Native::test_method(native) #use that getter for native
# end
# end
module ManagedNative
def self.included(sub_class)
sub_class.extend(ClassMethods)
end

# expects a block that returns a :pointer to the native resource
# that this class manages
def manage_native(&block)
# check that a destructor has been registered
unless self.class.instance_variable_get('@destructor')
raise 'No native destructor registered. use native_destroy to ' \
'set the method used to cleanup the native object this ' \
'class manages.'
end
native = block.call
@native = FFI::AutoPointer.new(native, self.class.method(:on_release))
end

# @param [Boolean] safe (true) - raise an exception if the native object
# is not set (has been freed or never created)
# @return [FFI:Pointer]
def native(safe: true)
raise '@native is unset or has been freed.' if safe && !@native

@native
end

# @return [Boolean]
def native_set?
!!@native
end

# Immediately release this instance's attachment to the underlying
# resources, without waiting for the garbage collector.
# Note that underlying resources will remain alive until nothing
# else is using them.
def release
return unless @native

@native.free
@native = nil
end

# ClassMethods for ManagedNative
module ClassMethods
# Register the method used to cleanup the native object this class
# manages. Must be a method, use object.method(:method_name).
#
# Example:
# native_destroy Aws::Crt::Native.method(:test_release)
def native_destroy(destructor)
unless destructor.is_a?(Method)
raise ArgumentError, 'destructor must be a Method. ' \
'Use object.method(:method_name)'
end
@destructor = destructor
end

# Do not call directly
# method passed to FFI Autopointer to call the destructor
def on_release(native)
@destructor.call(native)
end
end
end
end
end
Loading