Skip to content

Commit 10311e1

Browse files
authored
Add basic Credentials wrapper + ManagedNative mixin (#20)
1 parent 9b6aebf commit 10311e1

File tree

17 files changed

+435
-71
lines changed

17 files changed

+435
-71
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
strategy:
2222
fail-fast: false
2323
matrix:
24-
ruby: [2.5, 2.6, 2.7, jruby, truffleruby]
24+
ruby: [2.5, 2.6, 2.7, jruby]
2525
runs-on: ubuntu-latest
2626
steps:
2727
- uses: ruby/setup-ruby@v1
@@ -72,7 +72,7 @@ jobs:
7272
fail-fast: false
7373
matrix:
7474
os: [macos]
75-
ruby: [2.5, 2.6, 2.7, jruby, truffleruby]
75+
ruby: [2.5, 2.6, 2.7, jruby]
7676
runs-on: macos-latest
7777
steps:
7878
- uses: ruby/setup-ruby@v1

.rubocop.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,16 @@ Metrics/BlockLength:
1919
Exclude:
2020
- 'gems/aws-crt/Rakefile'
2121
- '**/*.rake'
22-
- 'gems/aws-crt/spec/**/*.rb'
22+
- 'gems/**/spec/**/*.rb'
2323

2424
Metrics/MethodLength:
25+
Max: 25
2526
Exclude:
2627
- 'gems/aws-crt/ext/compile.rb'
2728

29+
Metrics/AbcSize:
30+
Max: 20
31+
2832
Naming/FileName:
2933
Exclude:
3034
- 'gems/aws-crt/lib/aws-crt.rb'

format-check.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ if NOT type $CLANG_FORMAT 2> /dev/null ; then
1010
fi
1111

1212
FAIL=0
13-
SOURCE_FILES=`find aws-crt/native/src -type f \( -name '*.h' -o -name '*.c' \)`
13+
SOURCE_FILES=`find gems/aws-crt/native/src -type f \( -name '*.h' -o -name '*.c' \)`
1414
for i in $SOURCE_FILES
1515
do
1616
$CLANG_FORMAT -output-replacements-xml $i | grep -c "<replacement " > /dev/null

gems/aws-crt-auth/lib/aws-crt-auth.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# frozen_string_literal: true
22

33
require 'aws-crt'
4+
require_relative 'aws-crt-auth/credentials'
45

56
module Aws
67
module Crt
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# frozen_string_literal: true
2+
3+
module Aws
4+
module Crt
5+
module Auth
6+
# Utility class for Credentials.
7+
class Credentials
8+
include Aws::Crt::ManagedNative
9+
native_destroy Aws::Crt::Native.method(:credentials_release)
10+
11+
UINT64_MAX = 18_446_744_073_709_551_615
12+
13+
# @param [String] access_key_id
14+
# @param [String] secret_access_key
15+
# @param [String] session_token (nil)
16+
# @param [Time|int] expiration (nil) - Either a Time or an int
17+
# seconds since unix epoch
18+
def initialize(access_key_id, secret_access_key,
19+
session_token = nil, expiration = nil)
20+
if !access_key_id || access_key_id.empty?
21+
raise ArgumentError, 'access_key_id must be set'
22+
end
23+
24+
if !secret_access_key || secret_access_key.empty?
25+
raise ArgumentError, 'secret_access_key must be set'
26+
end
27+
28+
manage_native do
29+
Aws::Crt::Native.credentials_new(
30+
access_key_id,
31+
secret_access_key,
32+
session_token,
33+
expiration&.to_i || UINT64_MAX
34+
)
35+
end
36+
end
37+
38+
# @return [String]
39+
def access_key_id
40+
Aws::Crt::Native.credentials_get_access_key_id(native).to_s
41+
end
42+
43+
# @return [String]
44+
def secret_access_key
45+
Aws::Crt::Native.credentials_get_secret_access_key(native).to_s
46+
end
47+
48+
# @return [String, nil]
49+
def session_token
50+
Aws::Crt::Native.credentials_get_session_token(native).to_s
51+
end
52+
53+
# @return [Time,nil]
54+
def expiration
55+
exp = Aws::Crt::Native.credentials_get_expiration_timepoint_seconds!(
56+
native
57+
)
58+
return if exp == UINT64_MAX
59+
60+
Time.at(exp)
61+
end
62+
63+
# Removing the secret access key from the default inspect string.
64+
# @api private
65+
def inspect
66+
"#<#{self.class.name} access_key_id=#{access_key_id.inspect}>"
67+
end
68+
end
69+
end
70+
end
71+
end
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# frozen_string_literal: true
2+
3+
require_relative 'spec_helper'
4+
require 'weakref'
5+
6+
module Aws
7+
module Crt
8+
module Auth #:nodoc:
9+
describe Credentials do
10+
describe '#initilize' do
11+
it 'raises an ArgumentError when missing access_key_id' do
12+
expect { Credentials.new(nil, 'secret') }
13+
.to raise_error(ArgumentError)
14+
end
15+
16+
it 'raises an ArgumentError when missing secret_access_key' do
17+
expect { Credentials.new('akid', nil) }
18+
.to raise_error(ArgumentError)
19+
end
20+
21+
it 'defaults the session token to nil' do
22+
expect(Credentials.new('akid', 'secret').session_token).to be nil
23+
end
24+
25+
it 'defaults the expiration to nil' do
26+
expect(Credentials.new('akid', 'secret').expiration)
27+
.to be_nil
28+
end
29+
30+
it 'accepts a Time for expiration' do
31+
exp = Time.now
32+
creds = Credentials.new('akid', 'secret', 'token', exp)
33+
expect(creds.expiration.to_i).to eq exp.to_i
34+
end
35+
36+
it 'accepts an epoch (integer) for expiration' do
37+
exp = Time.now
38+
creds = Credentials.new('akid', 'secret', 'token', exp.to_i)
39+
expect(creds.expiration.to_i).to eq exp.to_i
40+
end
41+
end
42+
43+
describe 'accessors' do
44+
let(:exp) { Time.now }
45+
let(:creds) { Credentials.new('akid', 'secret', 'token', exp) }
46+
47+
it 'provides access to the access key id' do
48+
expect(creds.access_key_id).to eq('akid')
49+
end
50+
51+
it 'provides access to the secret access key' do
52+
expect(creds.secret_access_key).to eq('secret')
53+
end
54+
55+
it 'provides access to the session token' do
56+
expect(creds.session_token).to eq('token')
57+
end
58+
59+
it 'provides access to the expiration' do
60+
expect(creds.expiration.to_i).to eq exp.to_i
61+
end
62+
end
63+
64+
describe '#inspect' do
65+
let(:creds) { Credentials.new('akid', 'secret', 'token') }
66+
67+
it 'does not include the secret_access_key' do
68+
expect(creds.inspect).not_to include 'secret'
69+
end
70+
end
71+
72+
describe '.on_release' do
73+
it 'cleans up with release' do
74+
creds = Credentials.new('akid', 'secret')
75+
expect(creds).to_not be_nil
76+
77+
creds.release
78+
check_for_clean_shutdown
79+
end
80+
81+
if garbage_collect_is_immediate?
82+
it 'cleans up with GC' do
83+
creds = Credentials.new('akid', 'secret', 'session')
84+
weakref = WeakRef.new(creds)
85+
expect(weakref.weakref_alive?).to be true
86+
87+
# force cleanup via GC
88+
creds = nil # rubocop:disable Lint/UselessAssignment
89+
ObjectSpace.garbage_collect
90+
expect(weakref.weakref_alive?).to be_falsey
91+
check_for_clean_shutdown
92+
end
93+
end
94+
end
95+
end
96+
end
97+
end
98+
end

gems/aws-crt-auth/spec/spec_helper.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@
55
# use the local version of aws-crt
66
$LOAD_PATH.unshift File.expand_path('../../aws-crt/lib', __dir__)
77

8+
require_relative '../../aws-crt/spec/spec_helper'
89
require 'aws-crt-auth'

gems/aws-crt/lib/aws-crt.rb

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
require_relative 'aws-crt/platforms'
44
require_relative 'aws-crt/native'
55
require_relative 'aws-crt/errors'
6+
require_relative 'aws-crt/managed_native'
67
require_relative 'aws-crt/io'
78

89
# Top level Amazon Web Services (AWS) namespace
@@ -11,19 +12,5 @@ module Aws
1112
module Crt
1213
# Ensure native init() is called when gem loads
1314
Aws::Crt::Native.init
14-
15-
# Invoke native call, and raise exception if it failed
16-
def self.call
17-
res = yield
18-
# functions that return void cannot fail
19-
return unless res
20-
21-
# for functions that return int, non-zero indicates failure
22-
Errors.raise_last_error if res.is_a?(Integer) && res != 0
23-
24-
# for functions that return pointer, NULL indicates failure
25-
Errors.raise_last_error if res.is_a?(FFI::Pointer) && res.null?
26-
res
27-
end
2815
end
2916
end

gems/aws-crt/lib/aws-crt/io.rb

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ module IO
99
# Classes that need to do async work will ask the EventLoopGroup
1010
# for an event-loop to use.
1111
class EventLoopGroup
12+
include Aws::Crt::ManagedNative
13+
native_destroy Aws::Crt::Native.method(:event_loop_group_release)
14+
1215
def initialize(max_threads = nil)
1316
unless max_threads.nil? ||
1417
(max_threads.is_a?(Integer) && max_threads.positive?)
@@ -18,26 +21,9 @@ def initialize(max_threads = nil)
1821
# Ruby uses nil to request default values, native code uses 0
1922
max_threads = 0 if max_threads.nil?
2023

21-
native = Aws::Crt.call do
24+
manage_native do
2225
Aws::Crt::Native.event_loop_group_new(max_threads)
2326
end
24-
25-
@native = FFI::AutoPointer.new(native, self.class.method(:on_release))
26-
end
27-
28-
# Immediately release this instance's attachment to the underlying
29-
# resources, without waiting for the garbage collector.
30-
# Note that underlying resources will remain alive until nothing
31-
# else is using them.
32-
def release
33-
return unless @native
34-
35-
@native.free
36-
@native = nil
37-
end
38-
39-
def self.on_release(native)
40-
Aws::Crt::Native.event_loop_group_release(native)
4127
end
4228
end
4329
end
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# frozen_string_literal: true
2+
3+
module Aws
4+
module Crt
5+
# A mixin module for generic managed native functionality
6+
# Example:
7+
#
8+
# class C
9+
# include Aws::Crt::ManagedNative
10+
# native_destroy Aws::Crt::Native.method(:test_struct_destroy)
11+
#
12+
# def initialize
13+
# manage_native { Aws::Crt::Native::test_struct_new() }
14+
# end
15+
#
16+
# def use_native
17+
# Aws::Crt::Native::test_method(native) #use that getter for native
18+
# end
19+
# end
20+
module ManagedNative
21+
def self.included(sub_class)
22+
sub_class.extend(ClassMethods)
23+
end
24+
25+
# expects a block that returns a :pointer to the native resource
26+
# that this class manages
27+
def manage_native(&block)
28+
# check that a destructor has been registered
29+
unless self.class.instance_variable_get('@destructor')
30+
raise 'No native destructor registered. use native_destroy to ' \
31+
'set the method used to cleanup the native object this ' \
32+
'class manages.'
33+
end
34+
native = block.call
35+
@native = FFI::AutoPointer.new(native, self.class.method(:on_release))
36+
end
37+
38+
# @param [Boolean] safe (true) - raise an exception if the native object
39+
# is not set (has been freed or never created)
40+
# @return [FFI:Pointer]
41+
def native(safe: true)
42+
raise '@native is unset or has been freed.' if safe && !@native
43+
44+
@native
45+
end
46+
47+
# @return [Boolean]
48+
def native_set?
49+
!!@native
50+
end
51+
52+
# Immediately release this instance's attachment to the underlying
53+
# resources, without waiting for the garbage collector.
54+
# Note that underlying resources will remain alive until nothing
55+
# else is using them.
56+
def release
57+
return unless @native
58+
59+
@native.free
60+
@native = nil
61+
end
62+
63+
# ClassMethods for ManagedNative
64+
module ClassMethods
65+
# Register the method used to cleanup the native object this class
66+
# manages. Must be a method, use object.method(:method_name).
67+
#
68+
# Example:
69+
# native_destroy Aws::Crt::Native.method(:test_release)
70+
def native_destroy(destructor)
71+
unless destructor.is_a?(Method)
72+
raise ArgumentError, 'destructor must be a Method. ' \
73+
'Use object.method(:method_name)'
74+
end
75+
@destructor = destructor
76+
end
77+
78+
# Do not call directly
79+
# method passed to FFI Autopointer to call the destructor
80+
def on_release(native)
81+
@destructor.call(native)
82+
end
83+
end
84+
end
85+
end
86+
end

0 commit comments

Comments
 (0)