diff --git a/.rubocop.yml b/.rubocop.yml index 7253302b..f6ab1c1c 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,5 +1,3 @@ -inherit_from: .rubocop_todo.yml - AllCops: NewCops: enable TargetRubyVersion: 2.7 @@ -10,6 +8,10 @@ AllCops: Gemspec/RequireMFA: Enabled: false +Metrics/AbcSize: + Exclude: + - 'spec/**/*.rb' + Metrics/BlockLength: Exclude: - 'spec/**/*.rb' @@ -18,6 +20,10 @@ Metrics/MethodLength: Exclude: - 'spec/**/*.rb' +Metrics/ModuleLength: + Exclude: + - 'spec/**/*.rb' + Naming/FileName: Exclude: - 'lib/aws-sdk-rails.rb' diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml deleted file mode 100644 index 056b5aee..00000000 --- a/.rubocop_todo.yml +++ /dev/null @@ -1,29 +0,0 @@ -# This configuration was generated by -# `rubocop --auto-gen-config` -# on 2024-11-17 00:20:44 UTC using RuboCop version 1.68.0. -# The point is for the user to remove these configuration records -# one by one as the offenses are removed from the code base. -# Note that changes in the inspected code, or installation of new -# versions of RuboCop, may require this file to be generated again. - -# Offense count: 3 -# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes. -Metrics/AbcSize: - Max: 21 - -# Offense count: 2 -# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns. -Metrics/MethodLength: - Max: 11 - -# Offense count: 1 -# Configuration parameters: CountComments, CountAsOne. -Metrics/ModuleLength: - Max: 215 - -# Offense count: 1 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns. -# URISchemes: http, https -Layout/LineLength: - Max: 130 diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b63562d..56a784ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,20 @@ Unreleased Changes ------------------ +* Feature - [Major Version] Remove dependencies on modular feature gems: `aws-actiondispatch-dynamodb`, `aws-actionmailer-ses`, `aws-actionmailbox-ses`, `aws-activejob-sqs`, and `aws-record-rails`. + +* Issue - Remove `Aws::Rails.add_action_mailer_delivery_method` in favor of `ActionMailer::Base.add_delivery_method` or the Railtie and configuration in `aws-actionmailer-ses ~> 1`. + +* Issue - Remove require of `aws/rails/action_mailbox/rspec` in favor of `aws/action_mailbox/ses/rspec`. + +* Issue - Remove symlinked namespaces from previous major versions. + +* Feature - `ActiveSupport::Notifications` are enabled by default and removes `Aws::Rails.instrument_sdk_operations`. + +* Feature - Moved railtie initializations to their appropriate spots. + +* Issue - Do not execute `ActiveJob` from EB cron without the root path. + 4.2.0 (2024-11-20) ------------------ diff --git a/README.md b/README.md index b9b88bf4..0b44af16 100644 --- a/README.md +++ b/README.md @@ -13,16 +13,12 @@ the latest version of [AWS SDK For Ruby](https://github.com/aws/aws-sdk-ruby). Add this gem to your Rails project's Gemfile: ```ruby -gem 'aws-sdk-rails', '~> 4' +gem 'aws-sdk-rails', '~> 5' ``` This gem also brings in the following AWS gems: -* `aws-sdk-s3` -* `aws-sdk-ses` -* `aws-sdk-sesv2` -* `aws-sdk-sqs` -* `aws-sdk-sns` +* `aws-sdk-core` You will have to ensure that you provide credentials for the SDK to use. See the latest [AWS SDK for Ruby Docs](https://docs.aws.amazon.com/sdk-for-ruby/v3/api/index.html#Configuration) @@ -34,6 +30,26 @@ check Amazon EC2 instance metadata for credentials to load. Learn more: # Features +## ActionDispatch DynamoDB Session Storage + +See https://github.com/aws/aws-actiondispatch-dynamodb-ruby + +## ActionMailer delivery with Amazon Simple Email Service + +See https://github.com/aws/aws-actionmailer-ses-ruby + +## ActionMailbox ingress with Amazon Simple Email Service + +See https://github.com/aws/aws-actionmailbox-ses-ruby + +## ActiveJob SQS adapter + +See https://github.com/aws/aws-activejob-sqs-ruby + +## AWS Record Generators + +See https://github.com/aws/aws-record-rails + ## AWS SDK uses the Rails logger The AWS SDK is configured to use the built-in Rails logger for any @@ -47,7 +63,7 @@ Aws.config.update(log_level: :debug) ## Rails 5.2+ Encrypted Credentials -If you are using Rails 5.2+ [Encrypted Credentials](http://guides.rubyonrails.org/security.html#custom-credentials), +If you are using [Encrypted Credentials](http://guides.rubyonrails.org/security.html#custom-credentials), the credentials will be decrypted and loaded under the `:aws` top level key: ```yml @@ -67,286 +83,27 @@ If you are using [ActiveStorage](https://edgeguides.rubyonrails.org/active_stora with `S3`, then you do not need to specify your credentials in your `storage.yml` configuration because they will be loaded automatically. -## DynamoDB Session Store - -You can configure session storage in Rails to use DynamoDB instead of cookies, -allowing access to sessions from other applications and devices. You will need -to have or create an existing Amazon DynamoDB session table to use this feature. - -To enable this feature, add the following to your Gemfile: - -```ruby -gem 'aws-sessionstore-dynamodb', '~> 3' -``` - -For more information about this feature and configuration options, see the -[API reference](https://docs.aws.amazon.com/sdk-for-ruby/aws-sessionstore-dynamodb/api/) -and the -[GitHub repository](https://github.com/aws/aws-sessionstore-dynamodb-ruby). - -### Configuration generator - -You can generate a configuration file for the session store using the following -command (--environment= is optional): - -```bash -rails generate dynamo_db:session_store_config --environment= -``` - -The session store configuration generator command will generate a YAML file -`config/dynamo_db_session_store.yml` with default options. If provided an -environment, the file will be named -`config/dynamo_db_session_store/.yml`, which takes precedence over -the default file. - -### ActiveRecord Migration generator - -You can generate a migration file for the session table using the following -command ( is optional): - -```bash -rails generate dynamo_db:session_store_migration -``` - -The session store migration generator command will generate a -migration file `db/migration/#{VERSION}_#{MIGRATION_NAME}.rb`. - -The migration file will create and delete a table with default options. These -options can be changed prior to running the migration either in code by editing -the migration file or in the config YAML file. These options are documented in -the -[Table](https://docs.aws.amazon.com/sdk-for-ruby/aws-sessionstore-dynamodb/api/Aws/SessionStore/DynamoDB/Table.html) -class. - -To create the table, run migrations as normal with: - -```bash -rails db:migrate -``` - -To delete the table and rollback, run the following command: - -```bash -rails db:migrate:down VERSION= -``` - -### Migration Rake tasks - -If you are not using ActiveRecord, you can manage the table using the provided -Rake tasks: - -```bash -rake dynamo_db:session_store:create_table -rake dynamo_db:session_store:delete_table -``` - -The rake tasks will create and delete a table with default options. These -options can be changed prior to running the task in the config YAML file. These -options are documented in the -[Table](https://docs.aws.amazon.com/sdk-for-ruby/aws-sessionstore-dynamodb/api/Aws/SessionStore/DynamoDB/Table.html) -class. - -### Usage - -To use the session store, add or edit your `config/initializers/session_store.rb` file: - -```ruby -options = { table_name: '_your_app_session' } # overrides from YAML or ENV -Rails.application.config.session_store :dynamo_db_store, **options -``` - -You can now start your Rails application with DynamoDB session support. - -### Configuration - -You can configure the session store with code, YAML files, or ENV, in this order -of precedence. To configure in code, you can directly pass options to your -initializer like so: - -```ruby -# config/initializers/session_store.rb -Rails.application.config.session_store :dynamo_db_store, - table_name: 'your-table-name', - table_key: 'your-session-key', - dynamo_db_client: my_ddb_client -``` - -Alternatively, you can use the generated YAML configuration files -`config/dynamo_db_session_store.yml` or -`config/dynamo_db_session_store/.yml`. - -For configuration options, see the [Configuration] -(https://docs.aws.amazon.com/sdk-for-ruby/aws-sessionstore-dynamodb/api/Aws/SessionStore/DynamoDB/Configuration.html) -class. - -The session store inherits from the -[`Rack::Session::Abstract::Persisted`](https://rubydoc.info/github/rack/rack-session/main/Rack/Session/Abstract/Persisted) -class, which also includes additional options (such as `:key`) that can be -passed into the Rails initializer. - -### Cleaning old sessions - -By default sessions do not expire. You can use `:max_age` and `:max_stale` to -configure the max age or stale period of a session. - -You can use the DynamoDB -[Time to Live (TTL) feature](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TTL.html) -on the `expire_at` attribute to automatically delete expired items, saving you -the trouble of manually deleting them and reducing costs. - -If you wish to delete old sessions based on creation age (invalidating valid -sessions) or if you want control over the garbage collection process, you can -use the provided Rake task: - -```bash -rake dynamo_db:session_store:clean -``` - -## ActionMailer delivery with Amazon Simple Email Service +## AWS SDK eager loading -This gem contains Mailer classes for Amazon SES and SESV2. To use these mailers -as a delivery method, you need to register them with ActionMailer. -You can create a Rails initializer `config/initializers/action_mailer.rb` -with contents similar to the following: +An initializer will eager load the AWS SDK for you. To enable eager loading, +add the following to your `config/application.rb`: ```ruby -options = { region: 'us-west-2' } -ActionMailer::Base.add_delivery_method :ses, Aws::ActionMailer::SESMailer, **options -ActionMailer::Base.add_delivery_method :ses_v2, Aws::ActionMailer::SESV2Mailer, **options +config.eager_load = true ``` -In your environment configuration, set the delivery method to -`:ses` or `:ses_v2`. - -```ruby -config.action_mailer.delivery_method = :ses # or :ses_v2 -``` - -### Using ARNs with SES - -This gem uses [\`Aws::SES::Client#send_raw_email\`](https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/SES/Client.html#send_raw_email-instance_method) -and [\`Aws::SESV2::Client#send_email\`](https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/SESV2/Client.html#send_email-instance_method) -to send emails. This operation allows you to specify a cross-account identity -for the email's Source, From, and Return-Path. To set these ARNs, use any of the -following headers on your `Mail::Message` object returned by your Mailer class: - -* X-SES-SOURCE-ARN - -* X-SES-FROM-ARN - -* X-SES-RETURN-PATH-ARN - -Example: - -``` -# in your Rails controller -message = MyMailer.send_email(options) -message['X-SES-FROM-ARN'] = 'arn:aws:ses:us-west-2:012345678910:identity/bigchungus@memes.com' -message.deliver -``` - -## Amazon Simple Email Service (SES) as an ActionMailbox Ingress - -### Configuration - -#### Amazon SES/SNS - -1. [Configure SES](https://docs.aws.amazon.com/ses/latest/DeveloperGuide/receiving-email-notifications.html) to (save emails to S3)(https://docs.aws.amazon.com/ses/latest/dg/receiving-email-action-s3.html) or to send them as raw messages. - -2. [Configure the SNS topic for SES or for the S3 action](https://docs.aws.amazon.com/ses/latest/DeveloperGuide/receiving-email-action-sns.html) to send notifications to +/rails/action_mailbox/ses/inbound_emails+. For example, if your website is hosted at https://www.example.com then configure _SNS_ to publish the _SES_ notification topic to this _HTTP_ endpoint: https://example.com/rails/action_mailbox/ses/inbound_emails - -#### Rails - -1. Configure _ActionMailbox_ to accept emails from Amazon SES: - -``` -# config/environments/production.rb -config.action_mailbox.ingress = :ses -``` - -2. Configure which _SNS_ topic will be accepted and what region (note: the region of the bucket need not match the topic region) the emails will be stored in when using S3 (plus any other desired options): - -``` -# config/environments/production.rb -config.action_mailbox.ses.subscribed_topic = 'arn:aws:sns:eu-west-1:012345678910:example-topic-1' -config.action_mailbox.ses.s3_client_options = { region: 'us-east-1' } -``` - -SNS Subscriptions will now be auto-confirmed and messages will be automatically handled via _ActionMailbox_. - -Note that even if you manually confirm subscriptions you will still need to provide a list of subscribed topics; messages from unrecognized topics will be ignored. - -See [ActionMailbox documentation](https://guides.rubyonrails.org/action_mailbox_basics.html) for full usage information. - -### Testing - -#### RSpec - -Two _RSpec_ _request spec_ helpers are provided to facilitate testing _Amazon SNS/SES_ notifications in your application: - -* `action_mailbox_ses_deliver_subscription_confirmation` -* `action_mailbox_ses_deliver_email` - -Include the `Aws::Rails::ActionMailbox::RSpec` extension in your tests: - -```ruby -# spec/rails_helper.rb - -require 'aws/rails/action_mailbox/rspec' - -RSpec.configure do |config| - config.include Aws::Rails::ActionMailbox::RSpec -end -``` - -Configure your _test_ environment to accept the default topic used by the provided helpers: - -```ruby -# config/environments/test.rb - -config.action_mailbox.ses.subscribed_topic = 'topic:arn:default' -``` - -##### Example Usage - -```ruby -# spec/requests/amazon_emails_spec.rb - -RSpec.describe 'amazon emails', type: :request do - it 'delivers a subscription notification' do - action_mailbox_ses_deliver_subscription_confirmation - expect(response).to have_http_status :ok - end - - it 'delivers an email notification' do - action_mailbox_ses_deliver_email(mail: Mail.new(to: 'user@example.com')) - expect(ActionMailbox::InboundEmail.last.mail.recipients).to eql ['user@example.com'] - end -end -``` - -You may also pass the following keyword arguments to both helpers: - -* `topic`: The _SNS_ topic used for each notification (default: `topic:arn:default`). -* `authentic`: The `Aws::SNS::MessageVerifier` class is stubbed by these helpers; set `authentic` to `true` or `false` to define how it will verify incoming notifications (default: `true`). - - -## Active Support Notifications for AWS SDK calls - -To add `ActiveSupport::Notifications` instrumentation to all AWS SDK client operations, -add or edit your `config/initializers/instrument_aws_sdk.rb` file: +## ActiveSupport Notifications for AWS SDK calls -```ruby -Aws::Rails.instrument_sdk_operations -``` - -Events are published for each client operation call with the following event name: +[ActiveSupport::Notifications](https://api.rubyonrails.org/classes/ActiveSupport/Notifications.html) +instrumentation is enabled by default for all AWS SDK calls. Events are +published for each client operation call with the following event name: `..aws`. For example, S3's `:put_object` has an event name of: `put_object.S3.aws`. The service name will always match the namespace of the service client (e.g. Aws::S3::Client => 'S3'). The payload of the event is the [request context](https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Seahorse/Client/RequestContext.html). -You can subscribe to these events as you would for other `ActiveSupport::Notifications`: +You can subscribe to these events as you would for other +`ActiveSupport::Notifications`: ```ruby ActiveSupport::Notifications.subscribe('put_object.S3.aws') do |name, start, finish, id, payload| @@ -359,450 +116,51 @@ ActiveSupport::Notifications.subscribe(/S3[.]aws/) do |name, start, finish, id, end ``` -## AWS SQS Active Job -This package provides a lightweight, high performance SQS backend -for [ActiveJob](https://guides.rubyonrails.org/active_job_basics.html). - -To use AWS SQS ActiveJob as your queuing backend, simply set the `active_job.queue_adapter` -to `:sqs` For details on setting the queuing backend see: [ActiveJob: Setting the Backend](https://guides.rubyonrails.org/active_job_basics.html#setting-the-backend). - -To use the non-blocking (async) adapter set `active_job.queue_adapter` to `:sqs_async`. If you have -a lot of jobs to queue or you need to avoid the extra latency from an SQS call in your request then consider -using the async adapter. However, you may also want to configure a `async_queue_error_handler` to -handle errors that may occur when queuing jobs. See the -[Aws::Rails::SqsActiveJob::Configuration](https://docs.aws.amazon.com/sdk-for-ruby/aws-sdk-rails/api/Aws/Rails/SqsActiveJob/Configuration.html) -for documentation. - - -```ruby -# config/application.rb -module YourApp - class Application < Rails::Application - config.active_job.queue_adapter = :sqs - # To use the non-blocking async adapter: - # config.active_job.queue_adapter = :sqs_async - end -end - -# Or to set the adapter for a single job: -class YourJob < ApplicationJob - self.queue_adapter = :sqs - #.... -end -``` - -You also need to configure a mapping of ActiveJob queue name to SQS Queue URL. For more details, see the configuration section below. +### Elastic Beanstalk ActiveJob processing -```ruby -# config/aws_sqs_active_job.yml -queues: - default: 'https://my-queue-url.amazon.aws' -``` - -To queue a job, you can just use standard ActiveJob methods: - -```ruby -# To queue for immediate processing -YourJob.perform_later(args) - -# or to schedule a job for a future time: -YourJob.set(wait: 1.minute).perform_later(args) -``` - -Note: Due to limitations in SQS, you cannot schedule jobs for -later than 15 minutes in the future. - -### Retry Behavior and Handling Errors -See the Rails ActiveJob Guide on -[Exceptions](https://guides.rubyonrails.org/active_job_basics.html#exceptions) -for background on how ActiveJob handles exceptions and retries. - -In general - you should configure retries for your jobs using -[retry_on](https://edgeapi.rubyonrails.org/classes/ActiveJob/Exceptions/ClassMethods.html#method-i-retry_on). -When configured, ActiveJob will catch the exception and reschedule the job for -re-execution after the configured delay. This will delete the original -message from the SQS queue and requeue a new message. - -By default SQS ActiveJob is configured with `retry_standard_error` set to `true` -and will not delete messages for jobs that raise a `StandardError` and that do -not handle that error via `retry_on` or `discard_on`. These job messages -will remain on the queue and will be re-read and retried following the -SQS Queue's configured -[retry and DLQ settings](https://docs.aws.amazon.com/lambda/latest/operatorguide/sqs-retries.html). -If you do not have a DLQ configured, the message will continue to be attempted -until it reaches the queues retention period. In general, it is a best practice -to configure a DLQ to store unprocessable jobs for troubleshooting and redrive. - -If you want failed jobs that do not have `retry_on` or `discard_on` configured -to be immediately discarded and not left on the queue, set `retry_standard_error` -to `false`. See the configuration section below for details. - - -### Running workers - polling for jobs -To start processing jobs, you need to start a separate process -(in additional to your Rails app) with `bin/aws_sqs_active_job` -(an executable script provided with this gem). You need to specify the queue to -process jobs from: -```sh -RAILS_ENV=development bundle exec aws_sqs_active_job --queue default -``` - -To see a complete list of arguments use `--help`. - -You can kill the process at any time with `CTRL+C` - the processor will attempt -to shutdown cleanly and will wait up to `:shutdown_timeout` seconds for all -actively running jobs to finish before killing them. - - -Note: When running in production, its recommended that use a process -supervisor such as [foreman](https://github.com/ddollar/foreman), systemd, -upstart, daemontools, launchd, runit, ect. - -### Performance -AWS SQS ActiveJob is a lightweight and performant queueing backend. Benchmark performed using: Ruby MRI 2.6.5, -shoryuken 5.0.5, aws-sdk-rails 3.3.1 and aws-sdk-sqs 1.34.0 on a 2015 Macbook Pro dual-core i7 with 16GB ram. - -*AWS SQS ActiveJob* (default settings): Throughput 119.1 jobs/sec -*Shoryuken* (default settings): Throughput 76.8 jobs/sec - -### Serverless workers: processing activejobs using AWS Lambda -Rather than managing the worker processes yourself, you can use Lambda with an SQS Trigger. -With [Lambda Container Image Support](https://aws.amazon.com/blogs/aws/new-for-aws-lambda-container-image-support/) -and the lambda handler provided with `aws-sdk-rails` its easy to use lambda to run ActiveJobs for your dockerized -rails app (see below for some tips). All you need to do is: -1. include the [aws_lambda_ric gem](https://github.com/aws/aws-lambda-ruby-runtime-interface-client) -2. Push your image to ecr -3. Create a lambda function from your image (see the lambda docs for details). -4. Add an SQS Trigger for the queue(s) you want to process jobs from. -5. Set the ENTRYPOINT to `/usr/local/bundle/bin/aws_lambda_ric` and the CMD -to `config/environment.Aws::Rails::SqsActiveJob.lambda_job_handler` - this will load Rails and -then use the lambda handler provided by `aws-sdk-rails.` You can do this either as function config -or in your Dockerfile. - -There are a few -[limitations/requirements](https://docs.aws.amazon.com/lambda/latest/dg/images-create.html#images-reqs) -for lambda container images: the default lambda user must be able -to read all the files and the image must be able to run on a read only file system. -You may need to disable bootsnap, set a HOME env variable and -set the logger to STDOUT (which lambda will record to cloudwatch for you). - -You can use the RAILS_ENV to control environment. If you need to execute -specific configuration in the lambda, you can create a ruby file and use it -as your entrypoint: - -```ruby -# app.rb -# some custom config - -require_relative 'config/environment' # load rails - -# Rails.config.custom.... -# Aws::Rails::SqsActiveJob.config.... - -# no need to write a handler yourself here, as long as -# aws-sdk-rails is loaded, you can still use the -# Aws::Rails::SqsActiveJob.lambda_job_handler - -# To use this file, set CMD: app.Aws::Rails::SqsActiveJob.lambda_job_handler -``` - -### Elastic Beanstalk workers: processing activejobs using worker environments - -Another option for processing jobs without managing the worker process is hosting the application in a scalable -[Elastic Beanstalk worker environment](https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/using-features-managing-env-tiers.html). -[Configure the worker](https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/using-features-managing-env-tiers.html#using-features-managing-env-tiers-worker-settings) -to read from the correct SQS queue that you want to process jobs from and set the -```AWS_PROCESS_BEANSTALK_WORKER_REQUESTS``` environment variable to `true` in the worker environment configuration. -Note that this will NOT start the poller. Instead the +[Elastic Beanstalk worker environments](https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/using-features-managing-env-tiers.html) +can be used to run ActiveJob without managing a worker process. To do this, +[configure the worker](https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/using-features-managing-env-tiers.html#using-features-managing-env-tiers-worker-settings) +to read from the correct SQS queue that you want to process jobs from and set +the `AWS_PROCESS_BEANSTALK_WORKER_REQUESTS` environment variable to `true` in +the worker environment configuration. The [SQS Daemon](https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/using-features-managing-env-tiers.html#worker-daemon) -running on the worker sends messages as a POST request to `http://localhost/`. The middleware will forward each -request and parameters to their appropriate jobs. The middleware will only process requests from the SQS daemon -and will pass on others and so will not interfere with other routes in your application. +running on the worker sends messages as a POST request to `http://localhost/`. +The aws-sdk-rails middleware will forward each request and parameters to their +appropriate jobs. The middleware will only process requests from the SQS daemon +and will pass on others and so will not interfere with other routes in your +application. -To add the middleware on application startup, set the ```AWS_PROCESS_BEANSTALK_WORKER_REQUESTS``` environment variable to true -in the worker environment configuration. +To protect against forgeries, daemon requests will only be processed if they +originate from localhost or the Docker host. -To protect against forgeries, daemon requests will only be processed if they originate from localhost or the Docker host. - -Periodic (scheduled) jobs are also supported with this approach without requiring any additional dependencies. -Elastic Beanstalk workers support the addition of a ```cron.yaml``` file in the application root to configure this. +Periodic (scheduled) jobs are also supported with this approach. Elastic +Beanstalk workers support the addition of a `cron.yaml` file in the application +root to configure this. You can call your jobs from your controller actions +or if you name your cron job the same as your job class and set the URL to +`/`, the middleware will automatically call the job. Example: ```yml version: 1 cron: - - name: "MyApplicationJob" - url: "/" + - name: "do some task" + url: "/scheduled" schedule: "0 */12 * * *" + - name: "SomeJob" + url: "/" + schedule: "* * * * *" ``` -Where 'name' must be the case-sensitive class name of the job. - -### Configuration - -For a complete list of configuration options see the -[Aws::Rails::SqsActiveJob::Configuration](https://docs.aws.amazon.com/sdk-for-ruby/aws-sdk-rails/api/Aws/Rails/SqsActiveJob/Configuration.html) -documentation. - -You can configure AWS SQS Active Job either through the yml file or -through code in your config/.rb or initializers. - -For file based configuration, you can use either: -1. config/aws_sqs_active_job/.yml -2. config/aws_sqs_active_job.yml - -The yml file supports ERB. - -To configure in code: -```ruby -Aws::Rails::SqsActiveJob.configure do |config| - config.logger = ActiveSupport::Logger.new(STDOUT) - config.max_messages = 5 - config.client = Aws::SQS::Client.new(region: 'us-east-1') -end -``` - -### Using FIFO queues - -If the order in which your jobs executes is important, consider using a -[FIFO Queue](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/FIFO-queues.html). -A FIFO queue ensures that messages are processed in the order they were sent -(First-In-First-Out) and exactly-once processing (ensuring duplicates are never -introduced into the queue). To use a fifo queue, simply set the queue url (which will end in ".fifo") -in your config. - -When using FIFO queues, jobs will NOT be processed concurrently by the poller -to ensure the correct ordering. Additionally, all jobs on a FIFO queue will be queued -synchronously, even if you have configured the `sqs_async` adapter. - -#### Message Deduplication ID - -FIFO queues support [Message deduplication ID](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/using-messagededuplicationid-property.html), which is the token used for deduplication of sent messages. -If a message with a particular message deduplication ID is sent successfully, any messages sent with the same message deduplication ID are accepted successfully but aren't delivered during the 5-minute deduplication interval. - -##### Customize Deduplication keys - -If necessary, the deduplication key used to create the message deduplication ID can be customized: - -```ruby -Aws::Rails::SqsActiveJob.configure do |config| - config.excluded_deduplication_keys = [:job_class, :arguments] -end - -# Or to set deduplication keys to exclude for a single job: -class YourJob < ApplicationJob - include Aws::Rails::SqsActiveJob - deduplicate_without :job_class, :arguments - #... -end -``` - -By default, the following keys are used for deduplication keys: - -``` -job_class, provider_job_id, queue_name, priority, arguments, executions, exception_executions, locale, timezone, enqueued_at -``` - -Note that `job_id` is NOT included in deduplication keys because it is unique for each initialization of the job, and the run-once behavior must be guaranteed for ActiveJob retries. -Even without setting job_id, it is implicitly excluded from deduplication keys. - -#### Message Group IDs - -FIFO queues require a message group id to be provided for the job. It is determined by: -1. Calling `message_group_id` on the job if it is defined -2. If `message_group_id` is not defined or the result is `nil`, the default value will be used. -You can optionally specify a custom value in your config as the default that will be used by all jobs. - -## AWS Record Model Generators - -This package provides generators for creating `Aws::Record` models and table -configurations that are backed by DynamoDB, and a rake task for performing -the migrations. - -To enable this feature, add the following to your Gemfile: - -```ruby -gem 'aws-record', '~> 2' -``` - -### Setup - -You can either invoke the generator by calling - -```bash -rails generate aws_record:model ... -``` - -If DynamoDB will be the only datastore you plan on using you can also set `Aws::Record` to be your project's default orm with: - -```ruby -config.generators do |g| - g.orm :aws_record -end -``` -Which will cause `aws_record:model` to be invoked by the Rails model generator. - -### Generating a model - -Generating a model can be as simple as: - -```bash -rails generate aws_record:model Forum --table-config primary:10-5 -``` - -The generator will automatically create a `uuid:hash_key` field for you, and a table config with the provided r/w units: +and in your controller: ```ruby -# app/models/forum.rb - -require 'aws-record' - -class Forum - include Aws::Record - - string_attr :uuid, hash_key: true -end - -# db/table_config/forum_config.rb - -require 'aws-record' - -module ModelTableConfig - def self.config - Aws::Record::TableConfig.define do |t| - t.model_class Forum - - t.read_capacity_units 10 - t.write_capacity_units 5 - end +class SomeController < ApplicationController + def scheduled + SomeJob.perform_later end end ``` -More complex models can be created by adding more fields to the model as well as other options: - -```bash -rails generate aws_record Forum post_id:rkey author_username post_title post_body tags:sset:default_value{Set.new} -``` - -```ruby -# app/models/forum.rb - -require 'aws-record' - -class Forum - include Aws::Record - - string_attr :uuid, hash_key: true - string_attr :post_id, range_key: true - string_attr :author_username - string_attr :post_title - string_attr :post_body - string_set_attr :tags, default_value: Set.new -end - -# db/table_config/forum_config.rb -# ... -``` - -Finally you can attach a variety of options to your fields, and even `ActiveModel` validations to the models: - -```bash -rails generate aws_record:model Forum forum_uuid:hkey post_id:rkey author_username post_title post_body tags:sset:default_value{Set.new} created_at:datetime:db_attr_name{PostCreatedAtTime} moderation:boolean:default_value{false} --table-config=primary:5-2 AuthorIndex:12-14 --required=post_title --length-validations=post_body:50-1000 --gsi=AuthorIndex:hkey{author_username} -``` - -Which results in the following files being generated: - -```ruby -# app/models/forum.rb - -require 'aws-record' -require 'active_model' - -class Forum - include Aws::Record - include ActiveModel::Validations - - string_attr :forum_uuid, hash_key: true - string_attr :post_id, range_key: true - string_attr :author_username - string_attr :post_title - string_attr :post_body - string_set_attr :tags, default_value: Set.new - datetime_attr :created_at, database_attribute_name: 'PostCreatedAtTime' - boolean_attr :moderation, default_value: false - - global_secondary_index( - :AuthorIndex, - hash_key: :author_username, - projection: { - projection_type: "ALL" - } - ) - validates_presence_of :post_title - validates_length_of :post_body, within: 50..1000 -end - -# db/table_config/forum_config.rb -# ... -``` - -### Running Table Config Migrations - -The included rake task `aws_record:migrate` will run all of the migrations in -`app/db/table_config`: - -```bash -rake aws_record:migrate -``` - -### Model generator attributes - -The syntax for creating an aws-record model follows: - -```bash -rails generate aws_record:model NAME [field[:type][:opts]...] [options] -``` - -The possible field types are: - -| Field Name | aws-record attribute type | -|----------------------------------|---------------------------| -| `bool \| boolean` | :boolean_attr | -| `date` | :date_attr | -| `datetime` | :datetime_attr | -| `float` | :float_attr | -| `int \| integer` | :integer_attr | -| `list` | :list_attr | -| `map` | :map_attr | -| `num_set \| numeric_set \| nset` | :numeric_set_attr | -| `string_set \| s_set \| sset` | :string_set_attr | -| `string` | :string_attr | - -If a type is not provided, it will assume the field is of type `:string_attr`. - -Additionally a number of options may be attached as a comma separated list to the field: - -| Field Option Name | aws-record option | -|---------------------------------------------|-------------------------------------------------------------------------------------| -| `hkey` | marks an attribute as a hash_key | -| `rkey` | marks an attribute as a range_key | -| `persist_nil` | will persist nil values in a attribute | -| `db_attr_name{NAME}` | sets a secondary name for an attribute, these must be unique across attribute names | -| `ddb_type{S\|N\|B\|BOOL\|SS\|NS\|BS\|M\|L}` | sets the dynamo_db_type for an attribute | -| `default_value{Object}` | sets the default value for an attribute | - -The standard rules apply for using options in a model. Additional reading can be found [here](#links-of-interest) - -| Command Option Names | Purpose | -|----------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------| -| [--skip-namespace], [--no-skip-namespace] | Skip namespace (affects only isolated applications) | -| [--disable-mutation-tracking], [--no-disable-mutation-tracking] | Disables dirty tracking | -| [--timestamps], [--no-timestamps] | Adds created, updated timestamps to the model | -| --table-config=primary:R-W [SecondaryIndex1:R-W]... | Declares the r/w units for the model as well as any secondary indexes | -| [--gsi=name:hkey{ field_name }[,rkey{ field_name },proj_type{ ALL\|KEYS_ONLY\|INCLUDE }]...] | Allows for the declaration of secondary indexes | -| [--required=field1...] | A list of attributes that are required for an instance of the model | -| [--length-validations=field1:MIN-MAX...] | Validations on the length of attributes in a model | -| [--table-name=name] | Sets the name of the table in DynamoDB, if different than the model name | -| [--skip-table-config] | Doesn't generate a table config for the model | -| [--password-digest] | Adds a password field (note that you must have bcrypt has a dependency) that automatically hashes and manages the model password | +Will execute cron `SomeJob` every minute and `SomeJob` every 12 hours via the +`/scheduled` endpoint. diff --git a/aws-sdk-rails.gemspec b/aws-sdk-rails.gemspec index da021fc6..5ee41e87 100644 --- a/aws-sdk-rails.gemspec +++ b/aws-sdk-rails.gemspec @@ -13,14 +13,9 @@ Gem::Specification.new do |spec| spec.license = 'Apache-2.0' spec.files = Dir['LICENSE.txt', 'CHANGELOG.md', 'VERSION', 'lib/**/*'] - # These will be removed in aws-sdk-rails ~> 5 - spec.add_dependency('aws-actiondispatch-dynamodb', '~> 0') - spec.add_dependency('aws-actionmailbox-ses', '~> 0') - spec.add_dependency('aws-actionmailer-ses', '~> 0') - spec.add_dependency('aws-activejob-sqs', '~> 0') - spec.add_dependency('aws-record-rails', '~> 0') + spec.add_dependency('aws-sdk-core', '~> 3') - spec.add_dependency('railties', '>= 7.1.0') # Minimum supported Rails version + spec.add_dependency('railties', '>= 7.1.0') spec.required_ruby_version = '>= 2.7' end diff --git a/lib/aws-sdk-rails.rb b/lib/aws-sdk-rails.rb index 237615be..ef6db089 100644 --- a/lib/aws-sdk-rails.rb +++ b/lib/aws-sdk-rails.rb @@ -4,24 +4,8 @@ require_relative 'aws/rails/railtie' require_relative 'aws/rails/notifications' -# remove these in aws-sdk-rails 5 -require 'aws-actiondispatch-dynamodb' -require 'aws-actionmailbox-ses' if defined?(ActionMailbox::Engine) -require 'aws-actionmailer-ses' -require 'aws-activejob-sqs' -require 'aws-record-rails' - module Aws module Rails VERSION = File.read(File.expand_path('../VERSION', __dir__)).strip end end - -# remove these in aws-sdk-rails 5 -Aws::Rails::SqsActiveJob = Aws::ActiveJob::SQS -Aws::Rails::EbsSqsActiveJobMiddleware = Aws::Rails::Middleware::ElasticBeanstalkSQSD -Aws::Rails::SesMailer = Aws::ActionMailer::SES::Mailer -Aws::Rails::Sesv2Mailer = Aws::ActionMailer::SESV2::Mailer -# This is for backwards compatibility after introducing support for SESv2. -# The old mailer is now replaced with the new SES (v1) mailer. -Aws::Rails::Mailer = Aws::Rails::SesMailer diff --git a/lib/aws/rails/action_mailbox/rspec.rb b/lib/aws/rails/action_mailbox/rspec.rb deleted file mode 100644 index bcf5e554..00000000 --- a/lib/aws/rails/action_mailbox/rspec.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -# This can be deleted in aws-sdk-rails ~> 5 - -require 'aws/action_mailbox/ses/rspec' -Aws::Rails::ActionMailbox::RSpec = Aws::ActionMailbox::SES::RSpec - -Kernel.warn('Aws::Rails::ActionMailbox::RSpec is deprecated in aws-sdk-rails ~> 5. ' \ - 'Use Aws::ActionMailbox::SES::RSpec instead.') -Kernel.warn('Please require "aws/action_mailbox/ses/rspec" instead of "aws/rails/action_mailbox/rspec"') diff --git a/lib/aws/rails/middleware/elastic_beanstalk_sqsd.rb b/lib/aws/rails/middleware/elastic_beanstalk_sqsd.rb index d0c09de5..5f75493e 100644 --- a/lib/aws/rails/middleware/elastic_beanstalk_sqsd.rb +++ b/lib/aws/rails/middleware/elastic_beanstalk_sqsd.rb @@ -31,37 +31,42 @@ def call(env) private def execute_job(request) - # Jobs queued from the Active Job SQS adapter contain the JSON message in the request body. - job = Aws::Json.load(request.body.string) + # Jobs queued from the SQS adapter contain the JSON message in the request body. + job = ::ActiveSupport::JSON.decode(request.body.string) job_name = job['job_class'] @logger.debug("Executing job: #{job_name}") - - begin - ::ActiveJob::Base.execute(job) - rescue NameError => e - @logger.error("Job #{job_name} could not resolve to a class that inherits from Active Job.") - @logger.error("Error: #{e}") - return internal_error_response - end - + _execute_job(job, job_name) [200, { 'Content-Type' => 'text/plain' }, ["Successfully ran job #{job_name}."]] + rescue NameError + internal_error_response + end + + def _execute_job(job, job_name) + ::ActiveJob::Base.execute(job) + rescue NameError => e + @logger.error("Job #{job_name} could not resolve to a class that inherits from Active Job.") + @logger.error("Error: #{e}") + raise e end def execute_periodic_task(request) # The beanstalk worker SQS Daemon will add the 'X-Aws-Sqsd-Taskname' for periodic tasks set in cron.yaml. job_name = request.headers['X-Aws-Sqsd-Taskname'] @logger.debug("Creating and executing periodic task: #{job_name}") - - begin - job = job_name.constantize.new - job.perform_now - rescue NameError => e - @logger.error("Periodic task #{job_name} could not resolve to an Active Job class - check the spelling in cron.yaml.") - @logger.error("Error: #{e}.") - return internal_error_response - end - + _execute_periodic_task(job_name) [200, { 'Content-Type' => 'text/plain' }, ["Successfully ran periodic task #{job_name}."]] + rescue NameError + internal_error_response + end + + def _execute_periodic_task(job_name) + job = job_name.constantize.new + job.perform_now + rescue NameError => e + @logger.error("Periodic task #{job_name} could not resolve to an Active Job class " \ + '- check the cron name spelling and set the path as / in cron.yaml.') + @logger.error("Error: #{e}.") + raise e end def internal_error_response @@ -84,7 +89,7 @@ def from_sqs_daemon?(request) # The beanstalk worker SQS Daemon will add the custom 'X-Aws-Sqsd-Taskname' header # for periodic tasks set in cron.yaml. def periodic_task?(request) - !request.headers['X-Aws-Sqsd-Taskname'].nil? && request.headers['X-Aws-Sqsd-Taskname'].present? + request.headers['X-Aws-Sqsd-Taskname'].present? && request.fullpath == '/' end def sent_from_docker_host?(request) @@ -112,6 +117,7 @@ def default_docker_ips @default_docker_ips ||= build_default_docker_ips end + # rubocop:disable Metrics/AbcSize def build_default_docker_ips default_gw_ips = ['172.17.0.1'] @@ -128,6 +134,7 @@ def build_default_docker_ips default_gw_ips end + # rubocop:enable Metrics/AbcSize end end end diff --git a/lib/aws/rails/notifications.rb b/lib/aws/rails/notifications.rb index 17ecd40e..8c8064cf 100644 --- a/lib/aws/rails/notifications.rb +++ b/lib/aws/rails/notifications.rb @@ -1,8 +1,9 @@ # frozen_string_literal: true -require 'aws-sdk-core' require 'active_support/notifications' +require 'aws-sdk-core' + module Aws module Rails # @api private diff --git a/lib/aws/rails/railtie.rb b/lib/aws/rails/railtie.rb index 4664f1f6..cb48db66 100644 --- a/lib/aws/rails/railtie.rb +++ b/lib/aws/rails/railtie.rb @@ -3,34 +3,24 @@ module Aws # Use the Rails namespace. module Rails + # See https://guides.rubyonrails.org/configuring.html#initializers # @api private class Railtie < ::Rails::Railtie - initializer 'aws-sdk-rails.initialize', - before: :load_config_initializers do - # Initialization Actions - Aws::Rails.log_to_rails_logger - Aws::Rails.use_rails_encrypted_credentials - Aws::Rails.add_action_mailer_delivery_method - Aws::Rails.add_action_mailer_delivery_method(:sesv2) - - if %i[ses sesv2].include?(::Rails.application.config.action_mailer.delivery_method) - ::Rails.logger.warn(<<~MSG) - ** Aws::Rails.add_action_mailer_delivery_method will be removed in aws-sdk-rails ~> 5. - In `aws-actionmailer-ses ~> 1`, configuration will be set using config settings: - - config.action_mailer.delivery_method = :ses_v2 - config.action_mailer.ses_v2_settings = { region: 'us-west-2' } - - Existing Mailer classes have moved namespaces but will continue to work in this major version. ** - MSG - end + # Set the logger for the AWS SDK to Rails.logger. + initializer 'aws-sdk-rails.log-to-rails-logger', after: :initialize_logger do + Aws.config[:logger] = ::Rails.logger end - initializer 'aws-sdk-rails.insert_middleware' do |app| - Aws::Rails.add_sqsd_middleware(app) + # Configures the AWS SDK with credentials from Rails encrypted credentials. + initializer 'aws-sdk-rails.use-rails-encrypted-credentials', after: :load_environment_config do + # limit the config keys we merge to credentials only + aws_credential_keys = %i[access_key_id secret_access_key session_token account_id] + creds = ::Rails.application.credentials[:aws].to_h.slice(*aws_credential_keys) + Aws.config.merge!(creds) end - initializer 'aws-sdk-rails.eager_load' do + # Eager load the AWS SDK Clients. + initializer 'aws-sdk-rails.eager-load-sdk', before: :eager_load! do Aws.define_singleton_method(:eager_load!) do Aws.constants.each do |c| m = Aws.const_get(c) @@ -46,65 +36,37 @@ class Railtie < ::Rails::Railtie config.eager_load_namespaces << Aws end end - end - - # Configures the AWS SDK for Ruby's logger to use the Rails logger. - def self.log_to_rails_logger - Aws.config[:logger] = ::Rails.logger - nil - end - # Configures the AWS SDK with credentials from Rails encrypted credentials. - def self.use_rails_encrypted_credentials - # limit the config keys we merge to credentials only - aws_credential_keys = %i[access_key_id secret_access_key session_token account_id] - creds = ::Rails.application.credentials[:aws].to_h.slice(*aws_credential_keys) - Aws.config.merge!(creds) - end - - # This is called automatically from the SDK's Railtie, but can be manually - # called if you want to specify options for building the Aws::SES::Client or - # Aws::SESV2::Client. - # - # @param [Symbol] name The name of the ActionMailer delivery method to - # register, either :ses or :sesv2. - # @param [Hash] client_options The options you wish to pass on to the - # Aws::SES[V2]::Client initialization method. - def self.add_action_mailer_delivery_method(name = :ses, client_options = {}) - # TODO: remove this method in aws-sdk-rails ~> 5 - ActiveSupport.on_load(:action_mailer) do - if name == :sesv2 - add_delivery_method(name, Aws::Rails::Sesv2Mailer, client_options) - else - add_delivery_method(name, Aws::Rails::SesMailer, client_options) + # Add ActiveSupport Notifications instrumentation to AWS SDK client operations. + # Each operation will produce an event with a name `..aws`. + # For example, S3's put_object has an event name of: put_object.S3.aws + initializer 'aws-sdk-rails.instrument-sdk-operations', after: :load_active_support do + Aws.constants.each do |c| + m = Aws.const_get(c) + if m.is_a?(Module) && m.const_defined?(:Client) && + (client = m.const_get(:Client)) && client.superclass == Seahorse::Client::Base + m.const_get(:Client).add_plugin(Aws::Rails::Notifications) + end end end - end - # Add ActiveSupport Notifications instrumentation to AWS SDK client operations. - # Each operation will produce an event with a name `..aws`. - # For example, S3's put_object has an event name of: put_object.S3.aws - def self.instrument_sdk_operations - Aws.constants.each do |c| - m = Aws.const_get(c) - if m.is_a?(Module) && m.const_defined?(:Client) && - (client = m.const_get(:Client)) && client.superclass == Seahorse::Client::Base - m.const_get(:Client).add_plugin(Aws::Rails::Notifications) - end + # Register a middleware that will handle requests from the Elastic Beanstalk worker SQS Daemon. + initializer 'aws-sdk-rails.add-sqsd-middleware', before: :build_middleware_stack do |app| + Aws::Rails.add_sqsd_middleware(app) end end - # Register a middleware that will handle requests from the Elastic Beanstalk worker SQS Daemon. - # This will only be added in the presence of the AWS_PROCESS_BEANSTALK_WORKER_REQUESTS environment variable. - # The expectation is this variable should only be set on EB worker environments. - def self.add_sqsd_middleware(app) - return unless ENV['AWS_PROCESS_BEANSTALK_WORKER_REQUESTS'] + class << self + # @api private + def add_sqsd_middleware(app) + return unless ENV['AWS_PROCESS_BEANSTALK_WORKER_REQUESTS'] - if app.config.force_ssl - # SQS Daemon sends requests over HTTP - allow and process them before enforcing SSL. - app.config.middleware.insert_before(::ActionDispatch::SSL, Aws::Rails::Middleware::ElasticBeanstalkSQSD) - else - app.config.middleware.use(Aws::Rails::Middleware::ElasticBeanstalkSQSD) + if app.config.force_ssl + # SQS Daemon sends requests over HTTP - allow and process them before enforcing SSL. + app.config.middleware.insert_before(::ActionDispatch::SSL, Aws::Rails::Middleware::ElasticBeanstalkSQSD) + else + app.config.middleware.use(Aws::Rails::Middleware::ElasticBeanstalkSQSD) + end end end end diff --git a/sample-app/Gemfile b/sample-app/Gemfile index 8e110924..e63af5d3 100644 --- a/sample-app/Gemfile +++ b/sample-app/Gemfile @@ -1,15 +1,13 @@ source "https://rubygems.org" # Our gems -gem 'aws-sdk-rails', - # path: '../' - git: 'https://github.com/aws/aws-sdk-rails', branch: '4.2' - -gem 'aws-actiondispatch-dynamodb' #, git: 'https://github.com/aws/aws-actiondispatch-dynamodb-ruby' -gem 'aws-actionmailbox-ses' #, git: 'https://github.com/aws/aws-actionmailbox-ses-ruby' -gem 'aws-actionmailer-ses' #, git: 'https://github.com/aws/aws-actionmailer-ses-ruby' -gem 'aws-activejob-sqs' #, git: 'https://github.com/aws/aws-activejob-sqs-ruby' -gem 'aws-record-rails' #, git: 'https://github.com/aws/aws-record-rails', branch: 'main' +# bundle config set local.aws-sdk-rails ../ +gem 'aws-sdk-rails', git: 'https://github.com/aws/aws-sdk-rails', branch: 'main' +gem 'aws-actiondispatch-dynamodb', git: 'https://github.com/aws/aws-actiondispatch-dynamodb-ruby', branch: 'main' +gem 'aws-actionmailbox-ses', git: 'https://github.com/aws/aws-actionmailbox-ses-ruby', branch: 'main' +gem 'aws-actionmailer-ses', git: 'https://github.com/aws/aws-actionmailer-ses-ruby', branch: 'main' +gem 'aws-activejob-sqs', git: 'https://github.com/aws/aws-activejob-sqs-ruby', branch: 'main' +gem 'aws-record-rails', git: 'https://github.com/aws/aws-record-rails', branch: 'main' # Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword] gem 'bcrypt' diff --git a/sample-app/README.md b/sample-app/README.md index 00a8cadb..c82da59c 100644 --- a/sample-app/README.md +++ b/sample-app/README.md @@ -112,9 +112,7 @@ Inspect the output of `Aws.config` and ensure the credentials are set. ### Setup -This is configured in `config/initializers/instrument_aws_sdk.rb`. See the `aws-sdk-rails` README. - -`UsersController#index` captures any AWS SDK notification with: +This is configured in `config/initializers/notifications.rb`. See the `aws-sdk-rails` README. ```ruby ActiveSupport::Notifications.subscribe(/[.]aws/) do |name, start, finish, id, _payload| diff --git a/sample-app/app/controllers/users_controller.rb b/sample-app/app/controllers/users_controller.rb index f2b492be..87ca2f98 100644 --- a/sample-app/app/controllers/users_controller.rb +++ b/sample-app/app/controllers/users_controller.rb @@ -3,9 +3,6 @@ class UsersController < ApplicationController # GET /users def index - ActiveSupport::Notifications.subscribe(/[.]aws/) do |name, start, finish, id, _payload| - Rails.logger.info "Got notification: #{name} #{start} #{finish} #{id}" - end @users = User.all end diff --git a/sample-app/config/dynamo_db_session_store.yml b/sample-app/config/aws_dynamo_db_session_store.yml similarity index 100% rename from sample-app/config/dynamo_db_session_store.yml rename to sample-app/config/aws_dynamo_db_session_store.yml diff --git a/sample-app/config/initializers/action_mailer.rb b/sample-app/config/initializers/action_mailer.rb deleted file mode 100644 index a6ccf50c..00000000 --- a/sample-app/config/initializers/action_mailer.rb +++ /dev/null @@ -1,3 +0,0 @@ -options = {} -ActionMailer::Base.add_delivery_method :ses, Aws::ActionMailer::SES::Mailer, **options -ActionMailer::Base.add_delivery_method :ses_v2, Aws::ActionMailer::SESV2::Mailer, **options diff --git a/sample-app/config/initializers/instrument_aws_sdk.rb b/sample-app/config/initializers/instrument_aws_sdk.rb deleted file mode 100644 index 86167af9..00000000 --- a/sample-app/config/initializers/instrument_aws_sdk.rb +++ /dev/null @@ -1 +0,0 @@ -Aws::Rails.instrument_sdk_operations diff --git a/sample-app/config/initializers/notifications.rb b/sample-app/config/initializers/notifications.rb new file mode 100644 index 00000000..60a5cdcb --- /dev/null +++ b/sample-app/config/initializers/notifications.rb @@ -0,0 +1,3 @@ +ActiveSupport::Notifications.subscribe(/[.]aws/) do |name, start, finish, id, _payload| + Rails.logger.info "Got notification: #{name} #{start} #{finish} #{id}\n" +end \ No newline at end of file diff --git a/sample-app/tmp/cache/assets/sprockets/v4.0.0/1H/1HyneLliGLAocm4KVeWzj5tKGWdomYfz7rWloxQlXRc.cache b/sample-app/tmp/cache/assets/sprockets/v4.0.0/1H/1HyneLliGLAocm4KVeWzj5tKGWdomYfz7rWloxQlXRc.cache deleted file mode 100644 index 8986d7e2..00000000 --- a/sample-app/tmp/cache/assets/sprockets/v4.0.0/1H/1HyneLliGLAocm4KVeWzj5tKGWdomYfz7rWloxQlXRc.cache +++ /dev/null @@ -1,2 +0,0 @@ -[o:Set: -@hash}I"environment-version:ETTI"environment-paths;TTI"rails-env;TTI"Lprocessors:type=application/javascript&file_type=application/javascript;TTI"0file-digest://app/assets/config/manifest.js;TTI"0processors:type=text/css&file_type=text/css;TTI"9file-digest://app/assets/stylesheets/application.css;TTI">processors:type=text/css&file_type=text/css&pipeline=self;TTI")file-digest://app/assets/stylesheets;TTI"Zprocessors:type=application/javascript&file_type=application/javascript&pipeline=self;TTI"$file-digest://app/assets/images;TTF \ No newline at end of file diff --git a/sample-app/tmp/cache/assets/sprockets/v4.0.0/2K/2Kqqs5CDZKeqQ8Zi3OcRThACYBsE09CB_YbQaG96080.cache b/sample-app/tmp/cache/assets/sprockets/v4.0.0/2K/2Kqqs5CDZKeqQ8Zi3OcRThACYBsE09CB_YbQaG96080.cache deleted file mode 100644 index 419da84a..00000000 --- a/sample-app/tmp/cache/assets/sprockets/v4.0.0/2K/2Kqqs5CDZKeqQ8Zi3OcRThACYBsE09CB_YbQaG96080.cache +++ /dev/null @@ -1,2 +0,0 @@ -[o:Set: -@hash} I"environment-version:ETTI"environment-paths;TTI"rails-env;TTI"0processors:type=text/css&file_type=text/css;TTI"9file-digest://app/assets/stylesheets/application.css;TTI">processors:type=text/css&file_type=text/css&pipeline=self;TTI")file-digest://app/assets/stylesheets;TTF \ No newline at end of file diff --git a/sample-app/tmp/cache/assets/sprockets/v4.0.0/2t/2tbemif0f3xGMuuz-ZwxBCJ6rke15DVu5SvpBjWgMD0.cache b/sample-app/tmp/cache/assets/sprockets/v4.0.0/2t/2tbemif0f3xGMuuz-ZwxBCJ6rke15DVu5SvpBjWgMD0.cache deleted file mode 100644 index ba8ba258..00000000 --- a/sample-app/tmp/cache/assets/sprockets/v4.0.0/2t/2tbemif0f3xGMuuz-ZwxBCJ6rke15DVu5SvpBjWgMD0.cache +++ /dev/null @@ -1 +0,0 @@ -I"}app/assets/config/manifest.js?type=application/javascript&id=ad249dc977a647023bfb525ee331795da2096a6f8071324ab40c5bb4a57f52a2:ET \ No newline at end of file diff --git a/sample-app/tmp/cache/assets/sprockets/v4.0.0/8J/8JOedZF9m0P656fHle8DWmmJffxhiciEo-B9Ta6NyG8.cache b/sample-app/tmp/cache/assets/sprockets/v4.0.0/8J/8JOedZF9m0P656fHle8DWmmJffxhiciEo-B9Ta6NyG8.cache deleted file mode 100644 index 39a773d1..00000000 --- a/sample-app/tmp/cache/assets/sprockets/v4.0.0/8J/8JOedZF9m0P656fHle8DWmmJffxhiciEo-B9Ta6NyG8.cache +++ /dev/null @@ -1,2 +0,0 @@ -[o:Set: -@hash} I"environment-version:ETTI"environment-paths;TTI"rails-env;TTI"Zprocessors:type=application/javascript&file_type=application/javascript&pipeline=self;TTI"0file-digest://app/assets/config/manifest.js;TTI"$file-digest://app/assets/images;TTI")file-digest://app/assets/stylesheets;TTI"9file-digest://app/assets/stylesheets/application.css;TTF \ No newline at end of file diff --git a/sample-app/tmp/cache/assets/sprockets/v4.0.0/C9/C9p4GYdNFr06GzIPpHyXDsirkmwyGNFLPe-xzQKXhdM.cache b/sample-app/tmp/cache/assets/sprockets/v4.0.0/C9/C9p4GYdNFr06GzIPpHyXDsirkmwyGNFLPe-xzQKXhdM.cache deleted file mode 100644 index 2800ad3b..00000000 --- a/sample-app/tmp/cache/assets/sprockets/v4.0.0/C9/C9p4GYdNFr06GzIPpHyXDsirkmwyGNFLPe-xzQKXhdM.cache +++ /dev/null @@ -1 +0,0 @@ -"%Bșo$'AdLxRU \ No newline at end of file diff --git a/sample-app/tmp/cache/assets/sprockets/v4.0.0/V6/V6wcLa96hkVkK7jM22NZ_VOKCuIjqho04O7rGTK9jUY.cache b/sample-app/tmp/cache/assets/sprockets/v4.0.0/V6/V6wcLa96hkVkK7jM22NZ_VOKCuIjqho04O7rGTK9jUY.cache deleted file mode 100644 index 27109d68..00000000 Binary files a/sample-app/tmp/cache/assets/sprockets/v4.0.0/V6/V6wcLa96hkVkK7jM22NZ_VOKCuIjqho04O7rGTK9jUY.cache and /dev/null differ diff --git a/sample-app/tmp/cache/assets/sprockets/v4.0.0/WO/WOu00REbbbbQp4LEtw4H9lC2rYftdS1IlTpDqvMzjes.cache b/sample-app/tmp/cache/assets/sprockets/v4.0.0/WO/WOu00REbbbbQp4LEtw4H9lC2rYftdS1IlTpDqvMzjes.cache deleted file mode 100644 index 51cc1214..00000000 Binary files a/sample-app/tmp/cache/assets/sprockets/v4.0.0/WO/WOu00REbbbbQp4LEtw4H9lC2rYftdS1IlTpDqvMzjes.cache and /dev/null differ diff --git a/sample-app/tmp/cache/assets/sprockets/v4.0.0/XD/XDQkeEf3MYVjut2bt1BiIV8Yj5CnTFeqtxUiBk8FJWo.cache b/sample-app/tmp/cache/assets/sprockets/v4.0.0/XD/XDQkeEf3MYVjut2bt1BiIV8Yj5CnTFeqtxUiBk8FJWo.cache deleted file mode 100644 index b5f4f87e..00000000 --- a/sample-app/tmp/cache/assets/sprockets/v4.0.0/XD/XDQkeEf3MYVjut2bt1BiIV8Yj5CnTFeqtxUiBk8FJWo.cache +++ /dev/null @@ -1 +0,0 @@ -I"}app/assets/stylesheets/application.css?type=text/css&id=5e9d9a39aa4fc209a940470303fd6419aacb7ecf4cd292086343e913cedcbcff:ET \ No newline at end of file diff --git a/sample-app/tmp/cache/assets/sprockets/v4.0.0/ZS/ZS8hRCtYuHOFgWfJoBYAxBmIwBtcsu3PYanlh_uAfCo.cache b/sample-app/tmp/cache/assets/sprockets/v4.0.0/ZS/ZS8hRCtYuHOFgWfJoBYAxBmIwBtcsu3PYanlh_uAfCo.cache deleted file mode 100644 index c514eec0..00000000 --- a/sample-app/tmp/cache/assets/sprockets/v4.0.0/ZS/ZS8hRCtYuHOFgWfJoBYAxBmIwBtcsu3PYanlh_uAfCo.cache +++ /dev/null @@ -1 +0,0 @@ -I"app/assets/stylesheets/application.css?type=text/css&pipeline=self&id=a254134ce8f9c7231c9ef31da9fced53ab73cac78552464a62c09d68134e9d6b:ET \ No newline at end of file diff --git a/sample-app/tmp/cache/assets/sprockets/v4.0.0/b3/b3gtJoyAIxjnY7i60X1CwqjKPcJTGGr833BJOljAKAM.cache b/sample-app/tmp/cache/assets/sprockets/v4.0.0/b3/b3gtJoyAIxjnY7i60X1CwqjKPcJTGGr833BJOljAKAM.cache deleted file mode 100644 index afa14733..00000000 Binary files a/sample-app/tmp/cache/assets/sprockets/v4.0.0/b3/b3gtJoyAIxjnY7i60X1CwqjKPcJTGGr833BJOljAKAM.cache and /dev/null differ diff --git a/sample-app/tmp/cache/assets/sprockets/v4.0.0/cU/cUjyj7PX2Ld62_yyPxyk3GlnE-BB-jYFByRAh4Hxfq0.cache b/sample-app/tmp/cache/assets/sprockets/v4.0.0/cU/cUjyj7PX2Ld62_yyPxyk3GlnE-BB-jYFByRAh4Hxfq0.cache deleted file mode 100644 index e7df5f33..00000000 Binary files a/sample-app/tmp/cache/assets/sprockets/v4.0.0/cU/cUjyj7PX2Ld62_yyPxyk3GlnE-BB-jYFByRAh4Hxfq0.cache and /dev/null differ diff --git a/sample-app/tmp/cache/assets/sprockets/v4.0.0/eY/eYYtGuRJFHF4Pjsabyz_ImIS9TV7NEuGCqVjlDujx6M.cache b/sample-app/tmp/cache/assets/sprockets/v4.0.0/eY/eYYtGuRJFHF4Pjsabyz_ImIS9TV7NEuGCqVjlDujx6M.cache deleted file mode 100644 index 8fa38f59..00000000 Binary files a/sample-app/tmp/cache/assets/sprockets/v4.0.0/eY/eYYtGuRJFHF4Pjsabyz_ImIS9TV7NEuGCqVjlDujx6M.cache and /dev/null differ diff --git a/sample-app/tmp/cache/assets/sprockets/v4.0.0/gz/gzNXvETQe4mNpgCBZsnjWajtDLKAb8PR0oOpoX0XN0Y.cache b/sample-app/tmp/cache/assets/sprockets/v4.0.0/gz/gzNXvETQe4mNpgCBZsnjWajtDLKAb8PR0oOpoX0XN0Y.cache deleted file mode 100644 index 7aaf37df..00000000 --- a/sample-app/tmp/cache/assets/sprockets/v4.0.0/gz/gzNXvETQe4mNpgCBZsnjWajtDLKAb8PR0oOpoX0XN0Y.cache +++ /dev/null @@ -1 +0,0 @@ -"%RmvS3.OLڹcƕq=_j \ No newline at end of file diff --git a/sample-app/tmp/cache/assets/sprockets/v4.0.0/hS/hSWEp-CCjUqk7Lu6U5eGyjYNPw8xQ925GtEd383FdrQ.cache b/sample-app/tmp/cache/assets/sprockets/v4.0.0/hS/hSWEp-CCjUqk7Lu6U5eGyjYNPw8xQ925GtEd383FdrQ.cache deleted file mode 100644 index 1da5a56a..00000000 --- a/sample-app/tmp/cache/assets/sprockets/v4.0.0/hS/hSWEp-CCjUqk7Lu6U5eGyjYNPw8xQ925GtEd383FdrQ.cache +++ /dev/null @@ -1 +0,0 @@ -I"app/assets/config/manifest.js?type=application/javascript&pipeline=self&id=e7511f57d8ab446599c939f596a18af344215d5b62d02a8fb492607975a6245e:ET \ No newline at end of file diff --git a/sample-app/tmp/cache/assets/sprockets/v4.0.0/jE/jE8naJQC8LBy-gbNT-g-ygvqSqjt69w9k6c6X04lbpM.cache b/sample-app/tmp/cache/assets/sprockets/v4.0.0/jE/jE8naJQC8LBy-gbNT-g-ygvqSqjt69w9k6c6X04lbpM.cache deleted file mode 100644 index 99c1ed4d..00000000 --- a/sample-app/tmp/cache/assets/sprockets/v4.0.0/jE/jE8naJQC8LBy-gbNT-g-ygvqSqjt69w9k6c6X04lbpM.cache +++ /dev/null @@ -1,2 +0,0 @@ -[o:Set: -@hash} I"environment-version:ETTI"environment-paths;TTI"rails-env;TTI">processors:type=text/css&file_type=text/css&pipeline=self;TTI"9file-digest://app/assets/stylesheets/application.css;TTI")file-digest://app/assets/stylesheets;TTF \ No newline at end of file diff --git a/sample-app/tmp/cache/assets/sprockets/v4.0.0/n7/n74mGv3B9Koe12KDWIP0abZgSWbUe0pOz-BebWbDsJo.cache b/sample-app/tmp/cache/assets/sprockets/v4.0.0/n7/n74mGv3B9Koe12KDWIP0abZgSWbUe0pOz-BebWbDsJo.cache deleted file mode 100644 index db9ab5a4..00000000 --- a/sample-app/tmp/cache/assets/sprockets/v4.0.0/n7/n74mGv3B9Koe12KDWIP0abZgSWbUe0pOz-BebWbDsJo.cache +++ /dev/null @@ -1,2 +0,0 @@ -"%8&ێ_ -28PL} \ No newline at end of file diff --git a/spec/aws/rails/middleware/elastic_beanstalk_sqsd_spec.rb b/spec/aws/rails/middleware/elastic_beanstalk_sqsd_spec.rb index c73ee6f1..e7861372 100644 --- a/spec/aws/rails/middleware/elastic_beanstalk_sqsd_spec.rb +++ b/spec/aws/rails/middleware/elastic_beanstalk_sqsd_spec.rb @@ -279,6 +279,7 @@ def create_mock_env } if is_periodic_task + mock_env['PATH_INFO'] = '/' mock_env['HTTP_X_AWS_SQSD_TASKNAME'] = period_task_name else mock_env['rack.input'] = StringIO.new('{"job_class": "ElasticBeanstalkJob"}') diff --git a/spec/aws/rails/notifications_spec.rb b/spec/aws/rails/notifications_spec.rb index c58e8880..f8fa88f2 100644 --- a/spec/aws/rails/notifications_spec.rb +++ b/spec/aws/rails/notifications_spec.rb @@ -4,9 +4,7 @@ module Aws module Rails describe Notifications do let(:client) do - Client = Aws::SES::Client # rubocop:disable Lint/ConstantDefinitionInBlock - Client.add_plugin(Aws::Rails::Notifications) - Client.new(stub_responses: true, logger: nil) + Aws::STS::Client.new(stub_responses: true) end it 'adds instrumentation on each call' do @@ -15,8 +13,8 @@ module Rails out[:name] = name out[:payload] = payload end - client.send_raw_email(raw_message: { data: 'test' }) - expect(out[:name]).to eq('send_raw_email.SES.aws') + client.get_caller_identity + expect(out[:name]).to eq('get_caller_identity.STS.aws') expect(out[:payload][:context]).to be_a(Seahorse::Client::RequestContext) end end diff --git a/spec/aws/rails/railtie_spec.rb b/spec/aws/rails/railtie_spec.rb index b1d30e6a..a56c6e56 100644 --- a/spec/aws/rails/railtie_spec.rb +++ b/spec/aws/rails/railtie_spec.rb @@ -1,21 +1,6 @@ # frozen_string_literal: true module Aws - # Test service for Notifications - module Service - class Client < Seahorse::Client::Base; end - end - - module NotService - class Client - def self.add_plugin(_plugin); end - end - end - - class Client - def self.add_plugin(_plugin); end - end - module Rails describe 'Railtie' do it 'uses aws credentials from rails encrypted credentials' do @@ -29,11 +14,6 @@ module Rails expect(Aws.config[:something]).to be_nil end - it 'adds action mailer delivery methods' do - expect(::ActionMailer::Base.delivery_methods[:ses]).to eq Aws::Rails::SesMailer - expect(::ActionMailer::Base.delivery_methods[:sesv2]).to eq Aws::Rails::Sesv2Mailer - end - it 'sets the Rails logger to Aws global config' do expect(Aws.config[:logger]).to eq ::Rails.logger end @@ -43,17 +23,13 @@ module Rails expect(::Rails.application.config.eager_load_namespaces).to include(Aws) end - describe '.instrument_sdk_operations' do - it 'adds the Notifications plugin to sdk clients' do - expect(Aws::Service::Client).to receive(:add_plugin).with(Aws::Rails::Notifications) - expect(Aws::NotService::Client).not_to receive(:add_plugin) - expect(Aws::Client).not_to receive(:add_plugin) - - Aws::Rails.instrument_sdk_operations - end + it 'adds the Notifications plugin to sdk clients' do + expect(Aws::STS::Client.plugins).to include(Aws::Rails::Notifications) + expect(Aws::NotService::Client.plugins).not_to include(Aws::Rails::Notifications) + expect(Aws::Client.plugins).not_to include(Aws::Rails::Notifications) end - describe '.add_sqsd_middleware' do + context 'sqsd middleware' do describe 'AWS_PROCESS_BEANSTALK_WORKER_REQUESTS is set' do before do ENV['AWS_PROCESS_BEANSTALK_WORKER_REQUESTS'] = 'true' diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index f75ccd14..44374f79 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,6 +2,24 @@ ENV['RAILS_ENV'] = 'test' +# Test services for Notifications +# Must exist before initializing the Rails application +module Aws + module NotService + class Client + def self.plugins + [] + end + end + end + + class Client + def self.plugins + [] + end + end +end + require_relative 'dummy/config/application' Rails.application.initialize!