8000 OPER-5336 Update AWS SDK library by alanbrent · Pull Request #55 · Tapjoy/chore · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

OPER-5336 Update AWS SDK library #55

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 5 commits into from
Sep 14, 2020
Merged

OPER-5336 Update AWS SDK library #55

merged 5 commits into from
Sep 14, 2020

Conversation

alanbrent
Copy link
Member
@alanbrent alanbrent commented Aug 27, 2020

This PR updates the AWS SDK to a modern version with support for authentication via Web Federated Identity. While is designed to be a drop-in replacement for applications that already use Chore, there are various API changes in the AWS SDK, some of which have necessitated API changes in Chore::Queues. Hence I have incremented the major version of the gem.

Along the way I have made a bunch of QOL updates to the documentation. Hopefully people will find them useful :)

Chore API Changes

Minor Changes

  1. Chore::Consumer#consume signature has been unified between interface and implementations
  2. Chore::Consumer#handle_jobs renamed to Chore::Consumer::#handle_messages to unify names between publisher and consumer interfaces
  3. Chore::Consumer#handle_messages private method used in all consumers has been added to the interface definition
  4. Chore::Publisher#reset_connection! added to interface definition
  5. Chore::Consumer-s and Chore::Publisher-s unified to use message_id argument name rather than id where applicable

Major Changes

  1. UnitOfWork now takes :receipt_handle argument to accommodate AWS SDK requirements
    • this is passed in as nil in the filesystem handler, which doesn't need it
  2. SQS client object is now allocated in the parent Chore::Queues::SQS module rather than being duplicated in the publisher and consumer classes
  3. Chore::Queues::Consumer#complete signature now takes an options hash to accommodate changes needed by the SQS consumer
  4. Chore::Queues::SQS::Consumer#complete now requires the SQS message receipt handle to be passed in the options hash
  5. API interactions with SQS queues have been refactored to accommodate changes in the AWS SDK APIs

Spec Changes

  1. We're now sharing fake objects between all the SQS specs
  2. We're now double-ing the actual AWS API objects rather than generically named doubles
    • Perhaps this is incorrect? I'm not well-versed on this stuff so definitely let me know!
  3. Incorrect usage of expect(thing).to receive(:action).and_return(expected_value) has been fixed
    • This was confusing for me, and I learned that and_return is actually a mock being set rather than an expected value to be compared against
  4. A couple of seemingly useless specs that weren't actually testing anything have been disabled and may yet be removed in this PR, pending feedback.

Quality Of Life Changes

  1. Many more YARD docs
  2. Documented the release process

Copy link
Member Author
@alanbrent alanbrent left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To dos Completed

@alanbrent alanbrent force-pushed the update_aws_gem branch 12 times, most recently from 79b6439 to 89530e0 Compare August 28, 2020 13:40
Copy link
Member Author
@alanbrent alanbrent left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Notes for reviewers

@@ -19,7 +20,7 @@ def initialize(*) #:nodoc:

# The current attempt number for the worker processing this message.
def current_attempt
previous_attempts + 1
previous_attempts.to_i + 1
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I came across this and it seemed like a subtle bug. Can anyone think of a reason not to do this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Under what circumstances is this not an integer? As far as I can tell, both the filesystem and sqs consumer pass an integer through here. If the specs are passing a string, then I'd say the specs are wrong.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In reality we never pass in anything but an integer as far as I can tell. I noticed this because by looking at UnitOfWork it appears that :previous_attempts could at the very least be nil. But yeah, specs should cover that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in that case we should remove this change as I see that as both a Ruby language design problem and a failed contract for the consumer. I think better documentation could make it clearer what is expected to be nil vs. not nil.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nod I will back out the change as part of addressing forthcoming feedback :)

Comment on lines 68 to 73
# TODO: This seems like it's no longer necessary. If it is, fix and reenable it, otherwise delete
xit 'should look up the queue based on the queue url' do
expect(sqs).to receive(:get_queue_url).with(:queue_name=>queue_name).and_return(queue_url)
expect(queue).to receive(:[]).with(queue_url).and_return(queue)
consume
end
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking for opinions

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You've replaced this with a call to Aws::SQS::Queue.new(url: @queue_url), so I think you can get rid of this unless you want to replace it with a spec that validates Aws::SQS::Queue is built properly and passed the correct options.

Comment on lines 56 to 60
# TODO: AThe reset_connection! method is setup so every call resets it, so this spec shouldn't even exist right?
# Also, the test itself seems like basic identity (i.e. not even a real test)
xit 'should not reset the connection between calls' do
sqs = publisher.send(:queue, queue_name)
expect(sqs).to be publisher.send(:queue, queue_name)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking for opinions

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is still a spec we'll want

@alanbrent alanbrent requested a review from a team August 28, 2020 13:41
@alanbrent
Copy link
Member Author

Submitted to ops team for a first pass review. Will expand out to more people when this has passed through that filter.

@felipeTaps felipeTaps self-assigned this Aug 28, 2020
@alanbrent alanbrent force-pushed the update_aws_gem branch 4 times, most recently from 3de07db to 6aaa5e7 Compare August 31, 2020 19:45
README.md Outdated

For producers, you must do all of your Chore configuration in an intializer.
**IS THIS TRUE?** => (the consumer is also able to produce messages if need be, but is running as its own isolated instance of your application).
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem correct to me, should I remove it?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume this is just saying one could call the perform of another job from within the consumer which seems plausible?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A worker can do that, but I don't see any logic in the consumer that would actually publish a message. @obrie do you happen to be able to figure out what was meant by this language?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's just talking about the consuming application, not the consumer queue implementation.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes a lot more sense, thank you. I will update this language.

@obrie
Copy link
Member
obrie commented Aug 31, 2020

@alanbrent Could you provide a more comprehensive overview of what changes were made and why they were made? It would be helpful to know what were QOL changes, what were bug fixes, what changes were made to explicitly support the AWS upgrade, etc. etc.

It seems like there's a lot of stuff in this PR that's not specifically for the AWS upgrade, but I'm not certain.

As a side note, is it possible to split out the changes into logical commits if they are unrelated to each other?

@alanbrent alanbrent force-pushed the update_aws_gem branch 3 times, most recently from 6418591 to 140010c Compare August 31, 2020 20:22
@alanbrent alanbrent force-pushed the update_aws_gem branch 2 times, most recently from 4c238c9 to bafe85b Compare September 1, 2020 17:03
Copy link
Member
@obrie obrie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't reviewed specs or docs yet, but wanted to get some feedback in for you to look through. Nothing major -- this is all looking pretty good 👍

Thanks for splitting up the commits and providing the PR overview!

lib/chore.rb Outdated
@@ -63,6 +63,24 @@ def self.logger
end
end

def self.log_level_to_sym
return self.config[:log_level] if self.config[:log_level].kind_of?Symbol
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style nit -- missing parens with kind_of? (also I typically see is_a? be used, but they're synonymous either way)

raise NotImplementedError
end

# Perform any shutdown behavior and stop consuming messages
#
# @return [FalseClass]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this method does return false, I don't think that's a behavior we want to document or an interface we would want to guarantee. It just happens to be the last line executed in this method.

Chore.prefixed_queue_names
end

# Collect a list of queues that already exist
#
# @return [TrueClass, FalseClass]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method returns an array of strings (the prefixed_queue_names that exist)

def self.reset_connection!
@@reset_at = Time.now
Aws.empty_connection_pools!
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic shouldn't be in here -- this is just responsible for setting the timestamp

AWS::Core::Http::ConnectionPool.pools.each do |p|
p.empty!
end
self.class.reset_connection!
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Careful here -- we'll never get here because reset_connection! is the one that sets @@reset_at. We're safer leaving the previous logic as-is and simply switching the AWS::Core::Http::ConnectionPool.pools.each do |p| block to call Aws.empty_connection_pools!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nod I plan to do the first part (revert to previous flow), but the call defining that loop is no longer a thing. AFAICT the Aws.reset_connection_pools! does the same conceptual work.

[1] pry(main)> show-source Aws.empty_connection_pools!

From: /Users/brent.stephens/Documents/development/src/github.com/tapjoy/chore/vendor/bundle/ruby/2.3.0/gems/aws-sdk-core-3.105.0/lib/aws-sdk-core.rb:153:
Owner: #<Class:Aws>
Visibility: public
Signature: empty_connection_pools!()
Number of lines: 5

def empty_connection_pools!
  Seahorse::Client::NetHttp::ConnectionPool.pools.each do |pool|
    pool.empty!
  end
end

Chore.logger.debug "Completing (deleting): #{id}"
queue.batch_delete([id])
raise Chore::Consumer::CouldNotComplete, 'Required param "receipt_handle" missing!' unless opts[:receipt_handle]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is just to enforce an interface rather than an edge case error condition, I think we'd be better off defining it in the method, e.g.

def complete(message_id, receipt_handle:)
  ...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On second thought, maybe we should just make it a standard second parameter. That's probably simpler and doesn't give the impression that you can just easily pass through any generic options through here. It also reflects the fact that this data is always passed in.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I document anything around the signature being different than the one defined in the interface, or is that a common thing that won't surprise developers?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about just updating the interface? Implementations can then decide whether they need/respect the argument.

def self.reset_connection!
@@reset_next = true
Aws.empty_connection_pools!
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just noting that this is also impacted by the same comment on where the logic should live

# single queue which can be kept up with at a relatively low volume. If you have more than a single queue
# configured, it will raise an exception.
#
# @return [TrueClass, FalseClass]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No return doc needed here (I'll stop repeating myself -- just need to apply this across the doc changes you've made)

Chore.run_hooks_for(:consumed_from_source, work)
@queue.push(work) if running?
Chore.run_hooks_for(:added_to_queue, work)
end
end
end # ThrottledConsumerStrategyyeah
end # ThrottledConsumerStrategy
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😆 I do like the idea of a Lil' Jon language where the classes all end in yeah

https://www.youtube.com/watch?v=Wx3-cfE-6is

F438
@@ -75,7 +75,7 @@ def ipc_help

private

# TODO: do we need this as a optional param
# TODO: Decide if we should make this customizable via an optional param
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the answer is probably "no" since we haven't needed to do it in years 😆

CHANGELOG.md Outdated
@@ -1,15 +1,22 @@
# Change Log

## [master](https://github.com/Tapjoy/chore/tree/master)
## [v4.0.0](https://github.com/Tapjoy/chore/tree/v4.0.0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you create a new [master] section as part of this?


All of this should be taken into consideration when using this feature. In general, any queue requiring this will likely
need to run as its own Facet and not share workers with other queues.
All of this should be taken into consideration when using this feature. In general, any queue requiring this will likely need to run as its own Facet and not share workers with other queues.
Copy link
Member
@obrie obrie Sep 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you go back to limiting these lines to 80 / 100 / 120-ish characters? It becomes pretty difficult to read these files when I have to horizontally scroll each line. I'm sure you can set up the editor to do this for you, but I'd prefer to not assume what editor settings people have, particularly for open-source projects. Wrapped lines in Markdown is fairly common because Markdown will render it the same either way.

Examples:

Copy link
Member
@obrie obrie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Few more files to read through tomorrow 😄

Chore.logger.debug "Completing (deleting): #{id}"
queue.batch_delete([id])
raise Chore::Consumer::CouldNotComplete, 'Required param "receipt_handle" missing!' unless opts[:receipt_handle]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On second thought, maybe we should just make it a standard second parameter. That's probably simpler and doesn't give the impression that you can just easily pass through any generic options through here. It also reflects the fact that this data is always passed in.

let(:queue_name) { 'test_queue' }
let(:queue_url) { "http://amazon.sqs.url/queues/#{queue_name}" }

let(:queue) do double(Aws::SQS::Queue,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing a newline between do and double

consumer.consume
require_relative '../spec_helper'

module Chore
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit unusual. I don't think I've ever seen modules used for specs. Curious what led to this change?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The goal was to share AWS API object doubles in an effort to reduce the cognitive overhead of reasoning about all the SQS-related specs together. I just cargo-culted module Chore because that's the context the specs were all running in. I will finish reading through your interrelated comments and either make updates or ask more questions :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if I follow here. What's the benefit in putting specs in a module? As far as I'm aware, we don't do this in any of our codebases and I'm not sure I've ever seen that as a practice in rspec docs. The describe blocks themselves create anonymous modules, so there isn't typically value in surrounding them with another module.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same-functionality-but-different-implementation fake objects between the publisher and consumer was confusing to me, and it seemed to make sense to unify the objects to reduce that confusion as well as prevent drift. As far as the enclosing module declaration in the specs themselves, that was pre-existing. Did you want me to remove them?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update: it was pre-existing in the publisher, and I guess I updated the other spec to match.

let(:consumer) { Chore::Queues::SQS::Consumer.new(queue_name) }
let(:backoff_func) { Proc.new { 2 + 2 } }
let(:dupe_detector) { double(Chore::DuplicateDetector) }
let(:empty_message_collection) { double(Aws::SQS::Message::Collection.new([])) }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neither of the above 2 seem to be used

require 'spec_helper'

module Chore
RSpec.shared_context 'fake objects' do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is going to cause problems in the long-run. The helpers here are specific to s3 and the shared context name will get clobbered when you have non-s3 queue implementations being spec'd. The pattern I typically see is to put this as something like spec/support/contexts/s3_queue_context.rb.

It then just gets loaded automatically in spec_helper, e.g.

Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}

This also avoids having to use require_relative and confusion with which spec_helper is being referred to.


module Chore
RSpec.shared_context 'fake objects' do
let(:job) { {'class' => 'TestJob', 'args'=>[1,2,'3']}}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like an outlier that doesn't need to be in the shared context. I can see an argument for the more complex s3 class creation being shared because you're effectively creating a fake_sqs, though.

body: job.to_json,
data: job,
queue: queue,
queue_url: sqs.get_queue_url.queue_url,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like we're taking advantage of the fact that the get_queue_url method doesn't actually require any arguments which I think isn't actually the case and is a bit confusing. I think it'd be clearer to reference the queue_url variable here.

@queue ||= sqs.queues[@queue_url]

@queue_url ||= sqs.get_queue_url(queue_name: @queue_name).queue_url
@queue ||= Aws::SQS::Queue.new(url: @queue_url)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good eye, thank you!

Comment on lines 68 to 73
# TODO: This seems like it's no longer necessary. If it is, fix and reenable it, otherwise delete
xit 'should look up the queue based on the queue url' do
expect(sqs).to receive(:get_queue_url).with(:queue_name=>queue_name).and_return(queue_url)
expect(queue).to receive(:[]).with(queue_url).and_return(queue)
consume
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You've replaced this with a call to Aws::SQS::Queue.new(url: @queue_url), so I think you can get rid of this unless you want to replace it with a spec that validates Aws::SQS::Queue is built properly and passed the correct options.

context "should receive a message from the queue" do
before do
allow(consumer).to receive(:queue).and_return(queue)
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this required if you've previously stubbed it with allow(Aws::SQS::Queue).to receive(:new).and_return(queue)?

it 'should create a new SQS client before every publish' do
expect(Aws::SQS::Client).to receive(:new).twice
publisher.send(:queue, queue_name)
publisher.send(:queue, queue_name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be 2.times { publisher.send(:queue, queue_name) }

Comment on lines 56 to 60
# TODO: AThe reset_connection! method is setup so every call resets it, so this spec shouldn't even exist right?
# Also, the test itself seems like basic identity (i.e. not even a real test)
xit 'should not reset the connection between calls' do
sqs = publisher.send(:queue, queue_name)
expect(sqs).to be publisher.send(:queue, queue_name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is still a spec we'll want

@alanbrent
Copy link
Member Author

Alright I think we're ready for the next round. Thanks for all the feedback :)

Copy link
Member
@obrie obrie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The question on rspec modules is the main outstanding comment I think on my end.

end

# Sets a flag that instructs the publisher to reset the connection the next time it's used
#
# @return [Array<Seahorse::Client::NetHttp::ConnectionPool>]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out-of-date doc (No need to document a return value here)

consumer.consume
require_relative '../spec_helper'

module Chore
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if I follow here. What's the benefit in putting specs in a module? As far as I'm aware, we don't do this in any of our codebases and I'm not sure I've ever seen that as a practice in rspec docs. The describe blocks themselves create anonymous modules, so there isn't typically value in surrounding them with another module.

obrie
obrie previously approved these changes Sep 3, 2020
Copy link
Member
@obrie obrie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll take one more pass through it, but this seems ready for a production test!

obrie
obrie previously approved these changes Sep 3, 2020
Copy link
Member
@obrie obrie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🥳 🍾 To version infinity and beyond!

@alanbrent
Copy link
Member Author

Thanks @ehealy for catching a bug in the consumer whose fix is captured in the diff below. I have updated the PR with a fix as well as an additional spec to more fully test the complete method.

diff --git a/lib/chore/queues/sqs/consumer.rb b/lib/chore/queues/sqs/consumer.rb
index 7a76d49..4ca8ed5 100644
--- a/lib/chore/queues/sqs/consumer.rb
+++ b/lib/chore/queues/sqs/consumer.rb
@@ -58,8 +58,8 @@ module Chore
         # @param [String] message_id Unique ID of the SQS message
         # @param [Hash] receipt_handle Receipt handle (unique per consume request) of the SQS message
         def complete(message_id, receipt_handle)
-          Chore.logger.debug "Completing (deleting): #{id}"
-          queue.delete_messages(entries: [{ id: id, receipt_handle: receipt_handle }])
+          Chore.logger.debug "Completing (deleting): #{message_id}"
+          queue.delete_messages(entries: [{ id: message_id, receipt_handle: receipt_handle }])
         end

ehealy
ehealy previously approved these changes Sep 14, 2020
Copy link
@ehealy ehealy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using https://github.com/Tapjoy/opsbot/pull/84 as a testbed, I have validated the following:

  • SQS Producer
  • Initializes without issue
  • Successfully submits messages to SQS
  • SQS Consumer utilizing the Threaded Consumer Strategy
  • Initializes without issue
  • Successfully polls SQS for batches of messages for the Chore jobs specified
  • Assigns messages to workers
  • Workers complete Chore jobs
  • Chore jobs are successfully removed form the appropriate SQS queues.

Any feedback/bugfixes were addressed via facewords and re-validated during testing cycle. Minor non-blocking YARD documentation suggestion.

Outstanding work @alanbrent.

@alanbrent alanbrent force-pushed the update_aws_gem branch 2 times, most recently from 2f80ef7 to 03bd447 Compare September 14, 2020 15:04
@alanbrent alanbrent removed the request for review from riteshnoronha September 14, 2020 15:16
@alanbrent alanbrent merged commit 44844c5 into master Sep 14, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants
0