├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── ci.yml ├── .gitignore ├── .gitmodules ├── .rspec ├── .rubocop.yml ├── .yardopts ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── VERSION ├── aws-sdk-rails.gemspec ├── doc-src └── templates │ └── default │ └── layout │ └── html │ ├── footer.erb │ └── layout.erb ├── lib ├── aws-sdk-rails.rb └── aws │ └── rails │ ├── middleware │ └── elastic_beanstalk_sqsd.rb │ ├── notifications.rb │ └── railtie.rb ├── sample-app ├── .dockerignore ├── .ebextensions │ └── ruby.config ├── .github │ ├── dependabot.yml │ └── workflows │ │ └── ci.yml ├── .gitignore ├── .rubocop.yml ├── .ruby-version ├── Dockerfile ├── Gemfile ├── README.md ├── Rakefile ├── app │ ├── assets │ │ ├── config │ │ │ └── manifest.js │ │ ├── images │ │ │ └── .keep │ │ └── stylesheets │ │ │ └── application.css │ ├── controllers │ │ ├── application_controller.rb │ │ ├── concerns │ │ │ └── .keep │ │ ├── job_controller.rb │ │ ├── mailer_controller.rb │ │ └── users_controller.rb │ ├── helpers │ │ ├── application_helper.rb │ │ ├── job_helper.rb │ │ ├── mailer_helper.rb │ │ └── users_helper.rb │ ├── jobs │ │ ├── application_job.rb │ │ ├── test_async_job.rb │ │ └── test_job.rb │ ├── mailboxes │ │ ├── application_mailbox.rb │ │ └── test_mailbox.rb │ ├── mailers │ │ ├── application_mailer.rb │ │ └── test_mailer.rb │ ├── models │ │ ├── application_record.rb │ │ ├── concerns │ │ │ └── .keep │ │ └── user.rb │ └── views │ │ ├── layouts │ │ ├── application.html.erb │ │ ├── mailer.html.erb │ │ └── mailer.text.erb │ │ ├── pwa │ │ ├── manifest.json.erb │ │ └── service-worker.js │ │ └── users │ │ ├── _form.html.erb │ │ ├── _user.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── new.html.erb │ │ └── show.html.erb ├── bin │ ├── brakeman │ ├── bundle │ ├── docker-entrypoint │ ├── rails │ ├── rake │ ├── rubocop │ └── setup ├── config.ru ├── config │ ├── application.rb │ ├── aws_dynamo_db_session_store.yml │ ├── aws_sqs_active_job.yml │ ├── boot.rb │ ├── credentials.yml.enc │ ├── database.yml │ ├── environment.rb │ ├── environments │ │ ├── development.rb │ │ ├── production.rb │ │ └── test.rb │ ├── initializers │ │ ├── assets.rb │ │ ├── content_security_policy.rb │ │ ├── filter_parameter_logging.rb │ │ ├── inflections.rb │ │ ├── notifications.rb │ │ ├── permissions_policy.rb │ │ └── session_store.rb │ ├── locales │ │ └── en.yml │ ├── master.key │ ├── puma.rb │ ├── routes.rb │ └── storage.yml ├── db │ ├── migrate │ │ ├── 20241028193146_create_users.rb │ │ ├── 20241106152613_create_dynamo_db_sessions_table.rb │ │ ├── 20241113202339_create_active_storage_tables.active_storage.rb │ │ └── 20241113202340_create_action_mailbox_tables.action_mailbox.rb │ ├── schema.rb │ └── seeds.rb ├── lib │ ├── assets │ │ └── .keep │ └── tasks │ │ └── .keep ├── log │ └── .keep ├── public │ ├── 404.html │ ├── 406-unsupported-browser.html │ ├── 422.html │ ├── 500.html │ ├── icon.png │ ├── icon.svg │ └── robots.txt ├── storage │ ├── .keep │ └── development.sqlite3 ├── test │ ├── controllers │ │ ├── .keep │ │ ├── job_controller_test.rb │ │ ├── mailer_controller_test.rb │ │ └── users_controller_test.rb │ ├── fixtures │ │ ├── files │ │ │ └── .keep │ │ └── users.yml │ ├── helpers │ │ └── .keep │ ├── integration │ │ └── .keep │ ├── jobs │ │ └── test_job_test.rb │ ├── mailboxes │ │ └── test_mailbox_test.rb │ ├── mailers │ │ ├── previews │ │ │ └── test_mailer_preview.rb │ │ └── test_mailer_test.rb │ ├── models │ │ ├── .keep │ │ └── user_test.rb │ └── test_helper.rb ├── tmp │ ├── .keep │ ├── local_secret.txt │ ├── pids │ │ └── .keep │ ├── restart.txt │ └── storage │ │ └── .keep └── vendor │ └── .keep └── spec ├── aws └── rails │ ├── middleware │ └── elastic_beanstalk_sqsd_spec.rb │ ├── notifications_spec.rb │ └── railtie_spec.rb ├── dummy ├── Rakefile ├── app │ └── jobs │ │ ├── application_job.rb │ │ ├── elastic_beanstalk_job.rb │ │ └── elastic_beanstalk_periodic_task.rb ├── bin │ └── rails ├── config.ru └── config │ ├── application.rb │ ├── boot.rb │ ├── credentials.yml.enc │ ├── environment.rb │ └── master.key └── spec_helper.rb /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | By submitting this pull request, I confirm that my contribution is made under 6 | the terms of the Apache 2.0 license. 7 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | pull_request: 9 | branches: 10 | - main 11 | 12 | env: 13 | ruby_version: 3.4 14 | 15 | jobs: 16 | test: 17 | runs-on: ubuntu-latest 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | ruby: [2.7, '3.0', 3.1, 3.2, 3.3, 3.4, jruby-9.4] 22 | rails: [7.1, 7.2, '8.0', main] 23 | 24 | exclude: 25 | # Rails 7.2 is Ruby >= 3.1 26 | - rails: 7.2 27 | ruby: 2.7 28 | - rails: 7.2 29 | ruby: 3.0 30 | # Rails 8.0 is Ruby >= 3.2 31 | - rails: '8.0' 32 | ruby: 2.7 33 | - rails: '8.0' 34 | ruby: 3.0 35 | - rails: '8.0' 36 | ruby: 3.1 37 | - rails: '8.0' 38 | ruby: jruby-9.4 39 | # Rails main is Ruby >= 3.2 40 | - rails: main 41 | ruby: 2.7 42 | - rails: main 43 | ruby: 3.0 44 | - rails: main 45 | ruby: 3.1 46 | - rails: main 47 | ruby: jruby-9.4 48 | 49 | steps: 50 | - uses: actions/checkout@v4 51 | 52 | - name: Set RAILS_VERSION 53 | run: echo "RAILS_VERSION=${{ matrix.rails }}" >> $GITHUB_ENV 54 | 55 | - name: Setup Ruby 56 | uses: ruby/setup-ruby@v1 57 | with: 58 | ruby-version: ${{ matrix.ruby }} 59 | bundler-cache: true 60 | 61 | - name: Test 62 | run: bundle exec rake spec 63 | 64 | rubocop: 65 | runs-on: ubuntu-latest 66 | 67 | steps: 68 | - uses: actions/checkout@v4 69 | 70 | - name: Setup Ruby 71 | uses: ruby/setup-ruby@v1 72 | with: 73 | ruby-version: ${{ env.ruby_version }} 74 | 75 | - name: Install gems 76 | run: | 77 | bundle config set --local with 'development' 78 | bundle install 79 | 80 | - name: Rubocop 81 | run: bundle exec rake rubocop 82 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .byebug_history 3 | 4 | Gemfile.lock 5 | *.gem 6 | 7 | coverage/ 8 | .yardoc/ 9 | doc/ 10 | docs.zip 11 | .release/ 12 | 13 | spec/dummy/log/ 14 | spec/dummy/tmp/ 15 | spec/dummy/storage/ 16 | 17 | sample-app/Gemfile.lock 18 | sample-app/log/ 19 | sample-app/tmp/ 20 | sample-app.zip -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "tasks/release"] 2 | path = tasks/release 3 | url = git@github.com:aws/aws-sdk-ruby-release-tools.git 4 | branch = main 5 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | --format documentation -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | NewCops: enable 3 | TargetRubyVersion: 2.7 4 | SuggestExtensions: false 5 | Exclude: 6 | - 'sample-app/**/*' 7 | 8 | Gemspec/RequireMFA: 9 | Enabled: false 10 | 11 | Metrics/AbcSize: 12 | Exclude: 13 | - 'spec/**/*.rb' 14 | 15 | Metrics/BlockLength: 16 | Exclude: 17 | - 'spec/**/*.rb' 18 | 19 | Metrics/MethodLength: 20 | Max: 15 21 | Exclude: 22 | - 'spec/**/*.rb' 23 | 24 | Metrics/ModuleLength: 25 | Exclude: 26 | - 'spec/**/*.rb' 27 | 28 | Metrics/ClassLength: 29 | Max: 150 30 | 31 | Naming/FileName: 32 | Exclude: 33 | - 'lib/aws-sdk-rails.rb' 34 | 35 | Style/BlockComments: 36 | Exclude: 37 | - 'spec/spec_helper.rb' -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --title 'AWS SDK Rails' 2 | --template-path doc-src/templates 3 | --plugin sitemap 4 | --hide-api private 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Unreleased Changes 2 | ------------------ 3 | 4 | 5.1.0 (2024-12-05) 5 | ------------------ 6 | 7 | * Feature - Support async job processing in Elastic Beanstalk middleware. (#167) 8 | 9 | 5.0.0 (2024-11-21) 10 | ------------------ 11 | 12 | * 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`. 13 | 14 | * 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`. 15 | 16 | * Issue - Remove require of `aws/rails/action_mailbox/rspec` in favor of `aws/action_mailbox/ses/rspec`. 17 | 18 | * Issue - Remove symlinked namespaces from previous major versions. 19 | 20 | * Feature - `ActiveSupport::Notifications` are enabled by default and removes `Aws::Rails.instrument_sdk_operations`. 21 | 22 | * Feature - Moved railtie initializations to their appropriate spots. 23 | 24 | * Issue - Do not execute `ActiveJob` from EB cron without the root path. 25 | 26 | 4.2.0 (2024-11-20) 27 | ------------------ 28 | 29 | * Feature - DynamoDB Session Storage features now live in the `aws-actiondispatch-dynamodb` gem. This gem depends on `aws-sessionstore-dynamodb ~> 3` which depends on `rack ~> 3`. 30 | 31 | * Feature - Add session store config generation with `rails generate dynamo_db:session_store_config`. Config generation is no longer tied to the DynamoDB SessionStore ActiveRecord migration generator. 32 | 33 | * Issue - `ActionDispatch::Session::DynamoDbStore` now inherits `ActionDispatch::Session::AbstractStore` by wrapping `Aws::SessionStore::DynamoDB::RackMiddleware`. 34 | 35 | * Issue - `DynamoDbStore` is now configured with the `:dynamo_db_store` configuration instead of `:dynamodb_store`. 36 | 37 | * Feature - Session Store configuration passed into `:dynamo_db_store` in an initializer will now be considered when using the ActiveRecord migrations or rake tasks that create, delete, or clean session tables. 38 | 39 | * Feature - `AWS_DYNAMO_DB_SESSION_CONFIG_FILE` is now searched and with precedence over the default Rails configuration YAML file locations. 40 | 41 | * Feature - Prepare modularization of `aws-record`. 42 | 43 | * Issue - Do not skip autoload modules for `Aws::Rails.instrument_sdk_operations`. 44 | 45 | * Feature - ActionMailer SES and SESV2 mailers now live in the `aws-actionmailer-ses` gem. 46 | 47 | * Feature - New namespace and class names for SES and SESV2 mailers. `Aws::Rails::SesMailer` has been moved to `Aws::ActionMailer::SES::Mailer` and `Aws::Rails::Sesv2Mailer` has been moved to `Aws::ActionMailer::SESV2::Mailer`. The classes have been symlinked for backwards compatibility in this major version. 48 | 49 | * Issue - Add deprecation warning to `Aws::Rails.add_action_mailer_delivery_method` to instead use `ActionMailer::Base.add_delivery_method`. This method will be removed in aws-sdk-rails ~> 5. 50 | 51 | * Feature - ActionMailbox SES ingress now lives in the `aws-actionmailbox-ses` gem. 52 | 53 | * Issue - The `Aws::Rails::ActionMailbox::RSpec` module has been moved to `Aws::ActionMailbox::SES::RSpec` and will be removed in aws-sdk-rails ~> 5. 54 | 55 | * Feature - ActiveJob SQS now lives in the `aws-activejob-sqs` gem. 56 | 57 | * Feature - New namespace and class names for SQS ActiveJob. Existing namespace has temporarily been kept for backward compatibility and will be removed in aws-sdk-rails ~> 5. 58 | 59 | * Issue - Correctly determine if SQSD is running in a Docker container. 60 | 61 | * Feature - Aws::Record scaffold generators now lives in the `aws-record-rails` gem. 62 | 63 | 4.1.0 (2024-09-27) 64 | ------------------ 65 | 66 | * Feature - Add SDK eager loading to optimize load times. See: https://github.com/aws/aws-sdk-ruby/pull/3105. 67 | 68 | 4.0.3 (2024-07-31) 69 | ------------------ 70 | 71 | * Issue - Revert validating `:ses` or `:sesv2` as ActionMailer configuration. (#136) 72 | 73 | 4.0.2 (2024-07-22) 74 | ------------------ 75 | 76 | * Issue - Do not require `action_mailbox/engine` in `Aws::Rails::ActionMailbox::Engine` and instead check for its existence. 77 | 78 | * Issue - Refactor the loading of the SQS ActiveJob adapter to be in `aws/rails/sqs_active_job`. 79 | 80 | 4.0.1 (2024-07-18) 81 | ------------------ 82 | 83 | * Issue - Require `action_mailbox/engine` from `Aws::Rails::ActionMailbox::Engine`. 84 | 85 | 4.0.0 (2024-07-18) 86 | ------------------ 87 | 88 | * Feature - Add support for Action Mailbox with SES (#127). 89 | 90 | * Issue - Ensure `:ses` or `:sesv2` as ActionMailer configuration. 91 | 92 | * Issue - Do not allow `:amazon`, `amazon_sqs`, or `amazon_sqs_async` for SQS active job configuration. Instead use `:sqs` and `:sqs_async`. 93 | 94 | 3.13.0 (2024-06-06) 95 | ------------------ 96 | 97 | * Feature - Use `Concurrent.available_processor_count` to set default thread pool max threads (#125). 98 | 99 | * Issue - No longer rely on `caller_runs` for backpressure in sqs active job executor (#123). 100 | 101 | 3.12.0 (2024-04-02) 102 | ------------------ 103 | * Feature - Drop support for Ruby 2.3 and Ruby 2.4 (#117). 104 | * Issue - Fix `EbsSqsActiveJobMiddleware` to detect Docker container with cgroup2. (#116). 105 | 106 | 3.11.0 (2024-03-01) 107 | ------------------ 108 | 109 | * Feature - Add `retry_standard_errors` (default `true`) in SQS ActiveJob and improve retry logic (#114). 110 | 111 | 3.10.0 (2024-01-19) 112 | ------------------ 113 | 114 | * Feature - Support `enqueue_all` in the SQS ActiveJob adapter. 115 | 116 | * Issue - Improve `to_h` method's performance of `Aws::Rails::SqsActiveJob::Configuration`. 117 | 118 | 3.9.1 (2023-12-19) 119 | ------------------ 120 | 121 | * Issue - Fix negative `delay_seconds` being passed to parameter in the SQS adapter. 122 | 123 | 3.9.0 (2023-09-28) 124 | ------------------ 125 | 126 | * Feature - Add support for selectively choosing deduplication keys. 127 | 128 | * Feature - Set required Ruby version to >= 2.3 (#104) 129 | 130 | * Issue - Run `rubocop` on all files. (#104) 131 | 132 | 3.8.0 (2023-06-02) 133 | ------------------ 134 | 135 | * Feature - Improve User-Agent tracking and bump minimum SQS and SES versions. 136 | 137 | 3.7.1 (2023-02-15) 138 | ------------------ 139 | 140 | * Issue - Fix detecting docker host in `EbsSqsActiveJobMiddleware`. 141 | 142 | 3.7.0 (2023-01-24) 143 | ------------------ 144 | 145 | * Feature - Add SES v2 Mailer. 146 | 147 | * Feature - Support smtp_envelope_from and _to in SES Mailer. 148 | 149 | * Issue - Fix Ruby 3.1 usage by handling Psych 4 BadAlias error. 150 | 151 | 3.6.4 (2022-10-13) 152 | ------------------ 153 | 154 | * Issue - Use `request.ip` in `sent_from_docker_host?`. 155 | 156 | 3.6.3 (2022-09-06) 157 | ------------------ 158 | 159 | * Issue - Remove defaults for `visibility_timeout`: fallback to value configured on queue. 160 | * Issue - Fix I18n localization bug in SQS adapters. 161 | 162 | 3.6.2 (2022-06-16) 163 | ------------------ 164 | 165 | * Issue - Fix DynamoDB session store to work with Rails 7. 166 | * Issue - Allow for dynamic message group ids in FIFO Queues. 167 | 168 | 3.6.1 (2021-06-08) 169 | ------------------ 170 | 171 | * Issue - Fix credential loading to work with Rails 7. 172 | 173 | 3.6.0 (2021-01-20) 174 | ------------------ 175 | 176 | * Feature - Support for forwarding Elastic Beanstalk SQS Daemon requests to Active Job. 177 | 178 | 3.5.0 (2021-01-06) 179 | ------------------ 180 | 181 | * Feature - Add support for FIFO Queues to AWS SQS ActiveJob. 182 | 183 | 3.4.0 (2020-12-07) 184 | ------------------ 185 | 186 | * Feature - Add a non-blocking async ActiveJob adapter: `:amazon_sqs_async`. 187 | 188 | * Feature - Add a lambda handler for processing active jobs from an SQS trigger. 189 | 190 | * Issue - Fix bug in default for backpressure config. 191 | 192 | 3.3.0 (2020-12-01) 193 | ------------------ 194 | 195 | * Feature - Add `aws-record` as a dependency, a rails generator for `aws-record` models, and a rake task for table migrations. 196 | 197 | * Feature - Add AWS SQS ActiveJob - A lightweight, SQS backend for ActiveJob. 198 | 199 | 3.2.1 (2020-11-13) 200 | ------------------ 201 | 202 | * Issue - Include missing files into the gemspec. 203 | 204 | 3.2.0 (2020-11-13) 205 | ------------------ 206 | 207 | * Feature - Add support for `ActiveSupport::Notifications` for instrumenting AWS SDK service calls. 208 | 209 | * Feature - Add support for DynamoDB as an `ActiveDispatch::Session`. 210 | 211 | 3.1.0 (2020-04-06) 212 | ------------------ 213 | * Issue - Merge only credential related keys from Rails encrypted credentials into `Aws.config`. 214 | 215 | 3.0.5 (2019-10-17) 216 | ------------------ 217 | 218 | * Upgrading - Adds support for Rails Encrypted Credentials, requiring Rails 5.2+ 219 | and thus needed a new major version. Consequently drops support for Ruby < 2.3 220 | and for Rails < 5.2. Delivery method configuration changed from `:aws_sdk` to 221 | `:ses`, to allow for future delivery methods. Adds rubocop to the package and 222 | fixed many violations. This test framework now includes a dummy application for 223 | testing future features. 224 | 225 | 2.1.0 (2019-02-14) 226 | ------------------ 227 | 228 | * Feature - Aws::Rails::Mailer - Adds the Amazon SES message ID as a header to 229 | raw emails after sending, for tracking purposes. See 230 | [related GitHub pull request #25](https://github.com/aws/aws-sdk-rails/pull/25). 231 | 232 | 2.0.1 (2017-10-03) 233 | ------------------ 234 | 235 | * Issue - Ensure `aws-sdk-rails.initialize` executes before 236 | `load_config_initializers` 237 | 238 | 2.0.0 (2017-08-29) 239 | ------------------ 240 | 241 | * Upgrading - Support version 3 of the AWS SDK for Ruby. This is being released 242 | as major version 2 of `aws-sdk-rails`, though the APIs remain the same. Do note, 243 | however, that we've changed our SDK dependency to only depend on `aws-sdk-ses`. 244 | This means that if you were depending on other service clients transitively via 245 | `aws-sdk-rails`, you will need to add dependencies on the appropriate service 246 | gems when upgrading. Logger integration will work for other service gems you 247 | depend on, since it is wired up against `aws-sdk-core` which is included in 248 | the `aws-sdk-ses` dependency. 249 | 250 | 1.0.1 (2016-02-01) 251 | ------------------ 252 | 253 | * Feature - Gemfile - Replaced `rails` gem dependency with `railties` 254 | dependency. With this change, applications that bring their own dependencies 255 | in place of, for example, ActiveRecord, can do so with reduced bloat. 256 | 257 | See [related GitHub pull request 258 | #4](https://github.com/aws/aws-sdk-rails/pull/4). 259 | 260 | 1.0.0 (2015-03-17) 261 | ------------------ 262 | 263 | * Initial Release: Support for Amazon Simple Email Service and Rails Logger 264 | integration. 265 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | 3 | This project has adopted the [Amazon Open Source Code of 4 | Conduct](https://aws.github.io/code-of-conduct). For more information see the 5 | [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 6 | opensource-codeofconduct@amazon.com with any additional questions or comments. 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug 4 | report, new feature, correction, or additional documentation, we greatly value 5 | feedback and contributions from our community. 6 | 7 | Please read through this document before submitting any issues or pull requests 8 | to ensure we have all the necessary information to effectively respond to your 9 | bug report or contribution. 10 | 11 | ## Reporting Bugs/Feature Requests 12 | 13 | We welcome you to use the GitHub issue tracker to report bugs or suggest 14 | features. 15 | 16 | When filing an issue, please check [existing 17 | open](https://github.com/aws/aws-sdk-rails/issues), or [recently 18 | closed](https://github.com/aws/aws-sdk-rails/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), 19 | issues to make sure somebody else hasn't already reported the issue. 20 | 21 | Please try to include as much information as you can. Details like these are 22 | incredibly useful: 23 | 24 | * A reproducible test case or series of steps 25 | * The version of our code being used 26 | * Any modifications you've made relevant to the bug 27 | * Anything unusual about your environment or deployment 28 | 29 | ## Contributing via Pull Requests 30 | 31 | Contributions via pull requests are much appreciated. Before sending us a pull 32 | request, please ensure that: 33 | 34 | 1. You are working against the latest source on the *main* branch. 35 | 2. You check existing open, and recently merged, pull requests to make sure 36 | someone else hasn't addressed the problem already. 37 | 3. You open an issue to discuss any significant work - we would hate for your 38 | time to be wasted. 39 | 40 | To send us a pull request, please: 41 | 42 | 1. Fork the repository. 43 | 2. Modify the source; please focus on the specific change you are contributing. 44 | If you also reformat all the code, it will be hard for us to focus on your 45 | change. 46 | 3. Ensure local tests pass. 47 | 4. Commit to your fork using clear commit messages. 48 | 5. Send us a pull request, answering any default questions in the pull request 49 | interface. 50 | 6. Pay attention to any automated CI failures reported in the pull request, and 51 | stay involved in the conversation. 52 | 53 | GitHub provides additional document on [forking a 54 | repository](https://help.github.com/articles/fork-a-repo/) and [creating a pull 55 | request](https://help.github.com/articles/creating-a-pull-request/). 56 | 57 | ### Setup 58 | 59 | Prefix all commands with `BUNDLE_GEMFILE=gemfiles/rails-7.1.gemfile` or whatever version you want to work with. 60 | 61 | To setup the repository: 62 | 63 | 1. `BUNDLE_GEMFILE=gemfiles/rails-7.1.gemfile bundle install` 64 | 1. `cd spec/dummy` 65 | 1. `RAILS_ENV=test BUNDLE_GEMFILE=../../gemfiles/rails-7.1.gemfile bin/rails db:migrate` (note `../../` before the gemfile of choice). 66 | 67 | All tests should pass when running: 68 | 69 | `BUNDLE_GEMFILE=gemfiles/rails-7.1.gemfile bundle exec rspec` 70 | 71 | ### Updating AWS Fixtures for SES Action Mailbox Ingestion 72 | 73 | `BUNDLE_GEMFILE=gemfiles/rails-7.1.gemfile bundle exec rake sign_aws_fixtures` 74 | 75 | ## Finding contributions to work on 76 | 77 | Looking at the existing issues is a great way to find something to contribute 78 | on. As our projects, by default, use the default GitHub issue labels 79 | ((enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at 80 | any ['help wanted'](https://github.com/aws/aws-sdk-rails/labels/help%20wanted) 81 | issues is a great place to start. 82 | 83 | ## Code of Conduct 84 | 85 | This project has adopted the [Amazon Open Source Code of 86 | Conduct](https://aws.github.io/code-of-conduct). For more information see the 87 | [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 88 | opensource-codeofconduct@amazon.com with any additional questions or comments. 89 | 90 | ## Security issue notifications 91 | 92 | If you discover a potential security issue in this project we ask that you 93 | notify AWS/Amazon Security via our [vulnerability reporting 94 | page](http://aws.amazon.com/security/vulnerability-reporting/). Please do 95 | **not** create a public github issue. 96 | 97 | ## Licensing 98 | 99 | See the [LICENSE](https://github.com/aws/aws-sdk-rails/blob/main/LICENSE.txt) file 100 | for our project's licensing. We will ask you to confirm the licensing of your 101 | contribution. 102 | 103 | We may ask you to sign a [Contributor License Agreement 104 | (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger 105 | changes. 106 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | gemspec 6 | 7 | gem 'rake', require: false 8 | 9 | case ENV.fetch('RAILS_VERSION', nil) 10 | when '7.1' 11 | gem 'rails', '~> 7.1.0' 12 | when '7.2' 13 | gem 'rails', '~> 7.2.0' 14 | when '8.0' 15 | gem 'rails', '~> 8.0.0' 16 | else 17 | gem 'rails', github: 'rails/rails' 18 | end 19 | 20 | group :development do 21 | gem 'byebug', platforms: :ruby 22 | gem 'pry' 23 | gem 'rubocop' 24 | end 25 | 26 | group :test do 27 | gem 'rspec' 28 | end 29 | 30 | group :docs do 31 | gem 'yard' 32 | gem 'yard-sitemap', '~> 1.0' 33 | end 34 | 35 | group :release do 36 | gem 'octokit' 37 | end 38 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"). You 4 | may not use this file except in compliance with the License. A copy of 5 | the License is located at 6 | 7 | http://aws.amazon.com/apache2.0/ 8 | 9 | or in the "license" file accompanying this file. This file is 10 | distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 11 | ANY KIND, either express or implied. See the License for the specific 12 | language governing permissions and limitations under the License. 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AWS SDK for Ruby Rails Plugin 2 | 3 | [![Gem Version](https://badge.fury.io/rb/aws-sdk-rails.svg)](https://badge.fury.io/rb/aws-sdk-rails) 4 | [![Build Status](https://github.com/aws/aws-sdk-rails/workflows/CI/badge.svg)](https://github.com/aws/aws-sdk-rails/actions) 5 | [![Github forks](https://img.shields.io/github/forks/aws/aws-sdk-rails.svg)](https://github.com/aws/aws-sdk-rails/network) 6 | [![Github stars](https://img.shields.io/github/stars/aws/aws-sdk-rails.svg)](https://github.com/aws/aws-sdk-rails/stargazers) 7 | 8 | A Ruby on Rails plugin that integrates AWS services with your application using 9 | the latest version of [AWS SDK For Ruby](https://github.com/aws/aws-sdk-ruby). 10 | 11 | ## Installation 12 | 13 | Add this gem to your Rails project's Gemfile: 14 | 15 | ```ruby 16 | gem 'aws-sdk-rails', '~> 5' 17 | ``` 18 | 19 | This gem also brings in the following AWS gems: 20 | 21 | * `aws-sdk-core` 22 | 23 | You will have to ensure that you provide credentials for the SDK to use. See the 24 | latest [AWS SDK for Ruby Docs](https://docs.aws.amazon.com/sdk-for-ruby/v3/api/index.html#Configuration) 25 | for details. 26 | 27 | If you're running your Rails application on Amazon EC2, the AWS SDK will 28 | check Amazon EC2 instance metadata for credentials to load. Learn more: 29 | [IAM Roles for Amazon EC2](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html) 30 | 31 | # Features 32 | 33 | ## ActionDispatch DynamoDB Session Storage 34 | 35 | See https://github.com/aws/aws-actiondispatch-dynamodb-ruby 36 | 37 | ## ActionMailer delivery with Amazon Simple Email Service 38 | 39 | See https://github.com/aws/aws-actionmailer-ses-ruby 40 | 41 | ## ActionMailbox ingress with Amazon Simple Email Service 42 | 43 | See https://github.com/aws/aws-actionmailbox-ses-ruby 44 | 45 | ## ActiveJob SQS adapter 46 | 47 | See https://github.com/aws/aws-activejob-sqs-ruby 48 | 49 | ## AWS Record Generators 50 | 51 | See https://github.com/aws/aws-record-rails 52 | 53 | ## AWS SDK uses the Rails logger 54 | 55 | The AWS SDK is configured to use the built-in Rails logger for any 56 | SDK log output. The logger is configured to use the `:info` log level. You can 57 | change the log level by setting `:log_level` in the 58 | [Aws.config](https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws.html) hash. 59 | 60 | ```ruby 61 | Aws.config.update(log_level: :debug) 62 | ``` 63 | 64 | ## Rails 5.2+ Encrypted Credentials 65 | 66 | If you are using [Encrypted Credentials](http://guides.rubyonrails.org/security.html#custom-credentials), 67 | the credentials will be decrypted and loaded under the `:aws` top level key: 68 | 69 | ```yml 70 | # config/credentials.yml.enc 71 | # viewable with: `bundle exec rails credentials:edit` 72 | aws: 73 | access_key_id: YOUR_KEY_ID 74 | secret_access_key: YOUR_ACCESS_KEY 75 | session_token: YOUR_SESSION_TOKEN 76 | account_id: YOUR_ACCOUNT_ID 77 | ``` 78 | 79 | Encrypted Credentials will take precedence over any other AWS Credentials that 80 | may exist in your environment (e.g. credentials from profiles set in `~/.aws/credentials`). 81 | 82 | If you are using [ActiveStorage](https://edgeguides.rubyonrails.org/active_storage_overview.html) 83 | with `S3`, then you do not need to specify your credentials in your `storage.yml` 84 | configuration because they will be loaded automatically. 85 | 86 | ## AWS SDK eager loading 87 | 88 | An initializer will eager load the AWS SDK for you. To enable eager loading, 89 | add the following to your `config/application.rb`: 90 | 91 | ```ruby 92 | config.eager_load = true 93 | ``` 94 | 95 | ## ActiveSupport Notifications for AWS SDK calls 96 | 97 | [ActiveSupport::Notifications](https://api.rubyonrails.org/classes/ActiveSupport/Notifications.html) 98 | instrumentation is enabled by default for all AWS SDK calls. Events are 99 | published for each client operation call with the following event name: 100 | `..aws`. For example, S3's `:put_object` has an event name 101 | of: `put_object.S3.aws`. The service name will always match the namespace of the 102 | service client (e.g. Aws::S3::Client => 'S3'). The payload of the event is the 103 | [request context](https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Seahorse/Client/RequestContext.html). 104 | 105 | You can subscribe to these events as you would for other 106 | `ActiveSupport::Notifications`: 107 | 108 | ```ruby 109 | ActiveSupport::Notifications.subscribe('put_object.S3.aws') do |name, start, finish, id, payload| 110 | # process event 111 | end 112 | 113 | # Or use a regex to subscribe to all service notifications 114 | ActiveSupport::Notifications.subscribe(/S3[.]aws/) do |name, start, finish, id, payload| 115 | # process event 116 | end 117 | ``` 118 | 119 | ### Elastic Beanstalk ActiveJob processing 120 | 121 | [Elastic Beanstalk worker environments](https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/using-features-managing-env-tiers.html) 122 | can be used to run ActiveJob without managing a worker process. To do this, 123 | [configure the worker](https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/using-features-managing-env-tiers.html#using-features-managing-env-tiers-worker-settings) 124 | to read from the correct SQS queue that you want to process jobs from and set 125 | the `AWS_PROCESS_BEANSTALK_WORKER_REQUESTS` environment variable to `true` in 126 | the worker environment configuration. The 127 | [SQS Daemon](https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/using-features-managing-env-tiers.html#worker-daemon) 128 | running on the worker sends messages as a POST request to `http://localhost/`. 129 | The ElasticBeanstalkSQSD middleware will forward each request and parameters to their 130 | appropriate jobs. The middleware will only process requests from the SQS daemon 131 | and will pass on others and so will not interfere with other routes in your 132 | application. 133 | 134 | To protect against forgeries, daemon requests will only be processed if they 135 | originate from localhost or the Docker host. 136 | 137 | #### Running Jobs Async 138 | By default the ElasticBeanstalkSQSD middleware will process jobs synchronously 139 | and will not complete the request until the job has finished executing. For 140 | long running jobs (exceeding the configured nginix timeout on the worker) this 141 | may cause timeouts and incomplete executions. 142 | 143 | To run jobs asynchronously, set the `AWS_PROCESS_BEANSTALK_WORKER_JOBS_ASYNC` 144 | environment variable to `true` in your worker environment. Jobs will be queued 145 | in a ThreadPoolExecutor and the request will return a 200 OK immediately and the 146 | SQS message will be deleted and the job will be executed in the background. 147 | 148 | By default the executor will use the available processor count as the the 149 | max_threads. You can configure the max threads for the executor by setting 150 | the `AWS_PROCESS_BEANSTALK_WORKER_THREADS` environment variable. 151 | 152 | When there is no additional capacity to execute a task, the middleware 153 | returns a 429 (too many requests) response which will result in the 154 | sqsd NOT deleting the message. The message will be retried again once its 155 | visibility timeout is reached. 156 | 157 | Periodic (scheduled) tasks will also be run asynchronously in the same way. 158 | Elastic beanstalk queues a message for the periodic task and if there is 159 | no capacity to execute the task, it will be retried again once the message's 160 | visibility timeout is reached. 161 | 162 | #### Periodic (scheduled) jobs 163 | [Periodic (scheduled) tasks](https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/using-features-managing-env-tiers.html#worker-periodictasks) 164 | are also supported with this approach. Elastic 165 | Beanstalk workers support the addition of a `cron.yaml` file in the application 166 | root to configure this. You can call your jobs from your controller actions 167 | or if you name your cron job the same as your job class and set the URL to 168 | `/`, the middleware will automatically call the job. 169 | 170 | Example: 171 | ```yml 172 | version: 1 173 | cron: 174 | - name: "do some task" 175 | url: "/scheduled" 176 | schedule: "0 */12 * * *" 177 | - name: "SomeJob" 178 | url: "/" 179 | schedule: "* * * * *" 180 | ``` 181 | 182 | and in your controller: 183 | 184 | ```ruby 185 | class SomeController < ApplicationController 186 | def scheduled 187 | SomeJob.perform_later 188 | end 189 | end 190 | ``` 191 | 192 | Will execute cron `SomeJob` every minute and `SomeJob` every 12 hours via the 193 | `/scheduled` endpoint. 194 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rspec/core/rake_task' 4 | require 'rubocop/rake_task' 5 | 6 | Dir.glob('tasks/**/*.rake').each do |task_file| 7 | load task_file 8 | end 9 | 10 | RuboCop::RakeTask.new 11 | 12 | RSpec::Core::RakeTask.new 13 | 14 | task 'release:test' => :spec 15 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 5.1.0 2 | -------------------------------------------------------------------------------- /aws-sdk-rails.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | version = File.read(File.expand_path('VERSION', __dir__)).strip 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = 'aws-sdk-rails' 7 | spec.version = version 8 | spec.author = 'Amazon Web Services' 9 | spec.email = ['aws-dr-rubygems@amazon.com'] 10 | spec.summary = 'AWS SDK for Ruby on Rails Railtie' 11 | spec.description = 'Integrates the AWS SDK for Ruby with Ruby on Rails' 12 | spec.homepage = 'https://github.com/aws/aws-sdk-rails' 13 | spec.license = 'Apache-2.0' 14 | spec.files = Dir['LICENSE.txt', 'CHANGELOG.md', 'VERSION', 'lib/**/*'] 15 | 16 | spec.add_dependency('aws-sdk-core', '~> 3') 17 | 18 | spec.add_dependency('railties', '>= 7.1.0') 19 | 20 | spec.required_ruby_version = '>= 2.7' 21 | end 22 | -------------------------------------------------------------------------------- /doc-src/templates/default/layout/html/footer.erb: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /doc-src/templates/default/layout/html/layout.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= erb(:headers) %> 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 |
17 | 22 | 23 |
24 | 25 | <%= yieldall %> 26 |
27 | 28 | <%= erb(:footer) %> 29 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /lib/aws-sdk-rails.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'aws/rails/middleware/elastic_beanstalk_sqsd' 4 | require_relative 'aws/rails/railtie' 5 | require_relative 'aws/rails/notifications' 6 | 7 | module Aws 8 | module Rails 9 | VERSION = File.read(File.expand_path('../VERSION', __dir__)).strip 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/aws/rails/middleware/elastic_beanstalk_sqsd.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Aws 4 | module Rails 5 | module Middleware 6 | # Middleware to handle requests from the SQS Daemon present on Elastic Beanstalk worker environments. 7 | class ElasticBeanstalkSQSD 8 | def initialize(app) 9 | @app = app 10 | @logger = ::Rails.logger 11 | 12 | return unless ENV['AWS_PROCESS_BEANSTALK_WORKER_JOBS_ASYNC'] 13 | 14 | @executor = init_executor 15 | end 16 | 17 | def call(env) 18 | request = ::ActionDispatch::Request.new(env) 19 | 20 | # Pass through unless user agent is the SQS Daemon 21 | return @app.call(env) unless from_sqs_daemon?(request) 22 | 23 | @logger.debug('aws-sdk-rails middleware detected call from Elastic Beanstalk SQS Daemon.') 24 | 25 | # Only accept requests from this user agent if it is from localhost or a docker host in case of forgery. 26 | unless request.local? || sent_from_docker_host?(request) 27 | @logger.warn('SQSD request detected from untrusted address; returning 403 forbidden.') 28 | return forbidden_response 29 | end 30 | 31 | # Execute job or periodic task based on HTTP request context 32 | execute(request) 33 | end 34 | 35 | def shutdown(timeout = nil) 36 | return unless @executor 37 | 38 | @logger.info("Shutting down SQS EBS background job executor. Timeout: #{timeout}") 39 | @executor.shutdown 40 | clean_shutdown = @executor.wait_for_termination(timeout) 41 | @logger.info("SQS EBS background executor shutdown complete. Clean: #{clean_shutdown}") 42 | end 43 | 44 | private 45 | 46 | def init_executor 47 | threads = Integer(ENV.fetch('AWS_PROCESS_BEANSTALK_WORKER_THREADS', 48 | Concurrent.available_processor_count || Concurrent.processor_count)) 49 | options = { 50 | max_threads: threads, 51 | max_queue: 1, 52 | auto_terminate: false, # register our own at_exit to gracefully shutdown 53 | fallback_policy: :abort # Concurrent::RejectedExecutionError must be handled 54 | } 55 | at_exit { shutdown } 56 | 57 | Concurrent::ThreadPoolExecutor.new(options) 58 | end 59 | 60 | def execute(request) 61 | if periodic_task?(request) 62 | execute_periodic_task(request) 63 | else 64 | execute_job(request) 65 | end 66 | end 67 | 68 | def execute_job(request) 69 | if @executor 70 | _execute_job_background(request) 71 | else 72 | _execute_job_now(request) 73 | end 74 | end 75 | 76 | # Execute a job in the current thread 77 | def _execute_job_now(request) 78 | # Jobs queued from the SQS adapter contain the JSON message in the request body. 79 | job = ::ActiveSupport::JSON.decode(request.body.string) 80 | job_name = job['job_class'] 81 | @logger.debug("Executing job: #{job_name}") 82 | ::ActiveJob::Base.execute(job) 83 | [200, { 'Content-Type' => 'text/plain' }, ["Successfully ran job #{job_name}."]] 84 | rescue NameError => e 85 | @logger.error("Job #{job_name} could not resolve to a class that inherits from Active Job.") 86 | @logger.error("Error: #{e}") 87 | internal_error_response 88 | end 89 | 90 | # Execute a job using the thread pool executor 91 | def _execute_job_background(request) 92 | job_data = ::ActiveSupport::JSON.decode(request.body.string) 93 | @logger.debug("Queuing background job: #{job_data['job_class']}") 94 | @executor.post(job_data) do |job| 95 | ::ActiveJob::Base.execute(job) 96 | end 97 | [200, { 'Content-Type' => 'text/plain' }, ["Successfully queued job #{job_data['job_class']}"]] 98 | rescue Concurrent::RejectedExecutionError 99 | msg = 'No capacity to execute job.' 100 | @logger.info(msg) 101 | [429, { 'Content-Type' => 'text/plain' }, [msg]] 102 | end 103 | 104 | def execute_periodic_task(request) 105 | # The beanstalk worker SQS Daemon will add the 'X-Aws-Sqsd-Taskname' for periodic tasks set in cron.yaml. 106 | job_name = request.headers['X-Aws-Sqsd-Taskname'] 107 | job = job_name.constantize.new 108 | if @executor 109 | _execute_periodic_task_background(job) 110 | else 111 | _execute_periodic_task_now(job) 112 | end 113 | rescue NameError => e 114 | @logger.error("Periodic task #{job_name} could not resolve to an Active Job class " \ 115 | '- check the cron name spelling and set the path as / in cron.yaml.') 116 | @logger.error("Error: #{e}.") 117 | internal_error_response 118 | end 119 | 120 | def _execute_periodic_task_now(job) 121 | @logger.debug("Executing periodic task: #{job.class}") 122 | job.perform_now 123 | [200, { 'Content-Type' => 'text/plain' }, ["Successfully ran periodic task #{job.class}."]] 124 | end 125 | 126 | def _execute_periodic_task_background(job) 127 | @logger.debug("Queuing bakground periodic task: #{job.class}") 128 | @executor.post(job, &:perform_now) 129 | [200, { 'Content-Type' => 'text/plain' }, ["Successfully queued periodic task #{job.class}"]] 130 | rescue Concurrent::RejectedExecutionError 131 | msg = 'No capacity to execute periodic task.' 132 | @logger.info(msg) 133 | [429, { 'Content-Type' => 'text/plain' }, [msg]] 134 | end 135 | 136 | def internal_error_response 137 | message = 'Failed to execute job - see Rails log for more details.' 138 | [500, { 'Content-Type' => 'text/plain' }, [message]] 139 | end 140 | 141 | def forbidden_response 142 | message = 'Request with aws-sqsd user agent was made from untrusted address.' 143 | [403, { 'Content-Type' => 'text/plain' }, [message]] 144 | end 145 | 146 | # The beanstalk worker SQS Daemon sets a specific User-Agent headers that begins with 'aws-sqsd'. 147 | def from_sqs_daemon?(request) 148 | current_user_agent = request.headers['User-Agent'] 149 | 150 | !current_user_agent.nil? && current_user_agent.start_with?('aws-sqsd') 151 | end 152 | 153 | # The beanstalk worker SQS Daemon will add the custom 'X-Aws-Sqsd-Taskname' header 154 | # for periodic tasks set in cron.yaml. 155 | def periodic_task?(request) 156 | request.headers['X-Aws-Sqsd-Taskname'].present? && request.fullpath == '/' 157 | end 158 | 159 | def sent_from_docker_host?(request) 160 | app_runs_in_docker_container? && ip_originates_from_docker_host?(request) 161 | end 162 | 163 | def app_runs_in_docker_container? 164 | @app_runs_in_docker_container ||= in_docker_container_with_cgroup1? || in_docker_container_with_cgroup2? 165 | end 166 | 167 | def in_docker_container_with_cgroup1? 168 | File.exist?('/proc/1/cgroup') && File.read('/proc/1/cgroup') =~ %r{/docker/} 169 | end 170 | 171 | def in_docker_container_with_cgroup2? 172 | File.exist?('/proc/self/mountinfo') && File.read('/proc/self/mountinfo') =~ %r{/docker/containers/} 173 | end 174 | 175 | def ip_originates_from_docker_host?(request) 176 | default_docker_ips.include?(request.remote_ip) || 177 | default_docker_ips.include?(request.remote_addr) 178 | end 179 | 180 | def default_docker_ips 181 | @default_docker_ips ||= build_default_docker_ips 182 | end 183 | 184 | # rubocop:disable Metrics/AbcSize 185 | def build_default_docker_ips 186 | default_gw_ips = ['172.17.0.1'] 187 | 188 | if File.exist?('/proc/net/route') 189 | File.open('/proc/net/route').each_line do |line| 190 | fields = line.strip.split 191 | next if fields.size != 11 192 | # Destination == 0.0.0.0 and Flags & RTF_GATEWAY != 0 193 | next unless fields[1] == '00000000' && fields[3].hex.anybits?(0x2) 194 | 195 | default_gw_ips << IPAddr.new_ntoh([fields[2].hex].pack('L')).to_s 196 | end 197 | end 198 | 199 | default_gw_ips 200 | end 201 | # rubocop:enable Metrics/AbcSize 202 | end 203 | end 204 | end 205 | end 206 | -------------------------------------------------------------------------------- /lib/aws/rails/notifications.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'active_support/notifications' 4 | 5 | require 'aws-sdk-core' 6 | 7 | module Aws 8 | module Rails 9 | # @api private 10 | class Notifications < Seahorse::Client::Plugin 11 | # This plugin needs to be first, which means it is called first in the stack, 12 | # to start recording time, and returns last 13 | def add_handlers(handlers, _config) 14 | handlers.add(Handler, step: :initialize, priority: 99) 15 | end 16 | 17 | # @api private 18 | class Handler < Seahorse::Client::Handler 19 | def call(context) 20 | event_name = "#{context.operation_name}.#{context.config.api.metadata['serviceId']}.aws" 21 | ActiveSupport::Notifications.instrument(event_name, context: context) do 22 | @handler.call(context) 23 | end 24 | end 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/aws/rails/railtie.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Aws 4 | # Use the Rails namespace. 5 | module Rails 6 | # See https://guides.rubyonrails.org/configuring.html#initializers 7 | # @api private 8 | class Railtie < ::Rails::Railtie 9 | # Set the logger for the AWS SDK to Rails.logger. 10 | initializer 'aws-sdk-rails.log-to-rails-logger', after: :initialize_logger do 11 | Aws.config[:logger] = ::Rails.logger 12 | end 13 | 14 | # Configures the AWS SDK with credentials from Rails encrypted credentials. 15 | initializer 'aws-sdk-rails.use-rails-encrypted-credentials', after: :load_environment_config do 16 | # limit the config keys we merge to credentials only 17 | aws_credential_keys = %i[access_key_id secret_access_key session_token account_id] 18 | creds = ::Rails.application.credentials[:aws].to_h.slice(*aws_credential_keys) 19 | Aws.config.merge!(creds) 20 | end 21 | 22 | # Eager load the AWS SDK Clients. 23 | initializer 'aws-sdk-rails.eager-load-sdk', before: :eager_load! do 24 | Aws.define_singleton_method(:eager_load!) do 25 | Aws.constants.each do |c| 26 | m = Aws.const_get(c) 27 | next unless m.is_a?(Module) 28 | 29 | m.constants.each do |constant| 30 | m.const_get(constant) 31 | end 32 | end 33 | end 34 | 35 | config.before_eager_load do 36 | config.eager_load_namespaces << Aws 37 | end 38 | end 39 | 40 | # Add ActiveSupport Notifications instrumentation to AWS SDK client operations. 41 | # Each operation will produce an event with a name `..aws`. 42 | # For example, S3's put_object has an event name of: put_object.S3.aws 43 | initializer 'aws-sdk-rails.instrument-sdk-operations', after: :load_active_support do 44 | Aws.constants.each do |c| 45 | m = Aws.const_get(c) 46 | if m.is_a?(Module) && m.const_defined?(:Client) && 47 | (client = m.const_get(:Client)) && client.superclass == Seahorse::Client::Base 48 | m.const_get(:Client).add_plugin(Aws::Rails::Notifications) 49 | end 50 | end 51 | end 52 | 53 | # Register a middleware that will handle requests from the Elastic Beanstalk worker SQS Daemon. 54 | initializer 'aws-sdk-rails.add-sqsd-middleware', before: :build_middleware_stack do |app| 55 | Aws::Rails.add_sqsd_middleware(app) 56 | end 57 | end 58 | 59 | class << self 60 | # @api private 61 | def add_sqsd_middleware(app) 62 | return unless ENV['AWS_PROCESS_BEANSTALK_WORKER_REQUESTS'] 63 | 64 | if app.config.force_ssl 65 | # SQS Daemon sends requests over HTTP - allow and process them before enforcing SSL. 66 | app.config.middleware.insert_before(::ActionDispatch::SSL, Aws::Rails::Middleware::ElasticBeanstalkSQSD) 67 | else 68 | app.config.middleware.use(Aws::Rails::Middleware::ElasticBeanstalkSQSD) 69 | end 70 | end 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /sample-app/.dockerignore: -------------------------------------------------------------------------------- 1 | # See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files. 2 | 3 | # Ignore git directory. 4 | /.git/ 5 | /.gitignore 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore all environment files (except templates). 11 | /.env* 12 | !/.env*.erb 13 | 14 | # Ignore all default key files. 15 | /config/master.key 16 | /config/credentials/*.key 17 | 18 | # Ignore all logfiles and tempfiles. 19 | /log/* 20 | /tmp/* 21 | !/log/.keep 22 | !/tmp/.keep 23 | 24 | # Ignore pidfiles, but keep the directory. 25 | /tmp/pids/* 26 | !/tmp/pids/.keep 27 | 28 | # Ignore storage (uploaded files in development and any SQLite databases). 29 | /storage/* 30 | !/storage/.keep 31 | /tmp/storage/* 32 | !/tmp/storage/.keep 33 | 34 | # Ignore assets. 35 | /node_modules/ 36 | /app/assets/builds/* 37 | !/app/assets/builds/.keep 38 | /public/assets 39 | 40 | # Ignore CI service files. 41 | /.github 42 | 43 | # Ignore development files 44 | /.devcontainer 45 | 46 | # Ignore Docker-related files 47 | /.dockerignore 48 | /Dockerfile* 49 | -------------------------------------------------------------------------------- /sample-app/.ebextensions/ruby.config: -------------------------------------------------------------------------------- 1 | packages: 2 | yum: 3 | git: [] 4 | -------------------------------------------------------------------------------- /sample-app/.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: bundler 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | - package-ecosystem: github-actions 9 | directory: "/" 10 | schedule: 11 | interval: daily 12 | open-pull-requests-limit: 10 13 | -------------------------------------------------------------------------------- /sample-app/.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: [ main ] 7 | 8 | jobs: 9 | scan_ruby: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v4 15 | 16 | - name: Set up Ruby 17 | uses: ruby/setup-ruby@v1 18 | with: 19 | ruby-version: .ruby-version 20 | bundler-cache: true 21 | 22 | - name: Scan for common Rails security vulnerabilities using static analysis 23 | run: bin/brakeman --no-pager 24 | 25 | scan_js: 26 | runs-on: ubuntu-latest 27 | 28 | steps: 29 | - name: Checkout code 30 | uses: actions/checkout@v4 31 | 32 | - name: Set up Ruby 33 | uses: ruby/setup-ruby@v1 34 | with: 35 | ruby-version: .ruby-version 36 | bundler-cache: true 37 | 38 | - name: Scan for security vulnerabilities in JavaScript dependencies 39 | run: bin/importmap audit 40 | 41 | lint: 42 | runs-on: ubuntu-latest 43 | steps: 44 | - name: Checkout code 45 | uses: actions/checkout@v4 46 | 47 | - name: Set up Ruby 48 | uses: ruby/setup-ruby@v1 49 | with: 50 | ruby-version: .ruby-version 51 | bundler-cache: true 52 | 53 | - name: Lint code for consistent style 54 | run: bin/rubocop -f github 55 | 56 | test: 57 | runs-on: ubuntu-latest 58 | 59 | # services: 60 | # redis: 61 | # image: redis 62 | # ports: 63 | # - 6379:6379 64 | # options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 65 | steps: 66 | - name: Install packages 67 | run: sudo apt-get update && sudo apt-get install --no-install-recommends -y google-chrome-stable curl libjemalloc2 sqlite3 68 | 69 | - name: Checkout code 70 | uses: actions/checkout@v4 71 | 72 | - name: Set up Ruby 73 | uses: ruby/setup-ruby@v1 74 | with: 75 | ruby-version: .ruby-version 76 | bundler-cache: true 77 | 78 | - name: Run tests 79 | env: 80 | RAILS_ENV: test 81 | # REDIS_URL: redis://localhost:6379/0 82 | run: bin/rails db:test:prepare test test:system 83 | 84 | - name: Keep screenshots from failed system tests 85 | uses: actions/upload-artifact@v4 86 | if: failure() 87 | with: 88 | name: screenshots 89 | path: ${{ github.workspace }}/tmp/screenshots 90 | if-no-files-found: ignore 91 | -------------------------------------------------------------------------------- /sample-app/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Elastic Beanstalk Files 3 | .elasticbeanstalk/* 4 | !.elasticbeanstalk/*.cfg.yml 5 | !.elasticbeanstalk/*.global.yml 6 | -------------------------------------------------------------------------------- /sample-app/.rubocop.yml: -------------------------------------------------------------------------------- 1 | # Omakase Ruby styling for Rails 2 | inherit_gem: { rubocop-rails-omakase: rubocop.yml } 3 | 4 | # Overwrite or add rules to create your own house style 5 | # 6 | # # Use `[a, [b, c]]` not `[ a, [ b, c ] ]` 7 | # Layout/SpaceInsideArrayLiteralBrackets: 8 | # Enabled: false 9 | -------------------------------------------------------------------------------- /sample-app/.ruby-version: -------------------------------------------------------------------------------- 1 | ruby-3.3.4 2 | -------------------------------------------------------------------------------- /sample-app/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax = docker/dockerfile:1 2 | 3 | # This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand: 4 | # docker build -t my-app . 5 | # docker run -d -p 80:80 -p 443:443 --name my-app -e RAILS_MASTER_KEY= my-app 6 | 7 | # Make sure RUBY_VERSION matches the Ruby version in .ruby-version 8 | FROM docker.io/library/ruby:3.3.4-slim AS base 9 | 10 | # Rails app lives here 11 | WORKDIR /rails 12 | 13 | # Install base packages 14 | RUN apt-get update -qq && \ 15 | apt-get install --no-install-recommends -y curl libjemalloc2 sqlite3 && \ 16 | rm -rf /var/lib/apt/lists /var/cache/apt/archives 17 | 18 | # Set production environment 19 | ENV RAILS_ENV="production" \ 20 | BUNDLE_DEPLOYMENT="1" \ 21 | BUNDLE_PATH="/usr/local/bundle" \ 22 | BUNDLE_WITHOUT="development" \ 23 | AWS_PROCESS_BEANSTALK_WORKER_REQUESTS="true" \ 24 | SECRET_KEY_BASE="SECRET" \ 25 | AWS_REGION="us-west-2" 26 | 27 | 28 | # Throw-away build stage to reduce size of final image 29 | FROM base AS build 30 | 31 | # Install packages needed to build gems 32 | RUN apt-get update -qq && \ 33 | apt-get install --no-install-recommends -y build-essential git pkg-config && \ 34 | rm -rf /var/lib/apt/lists /var/cache/apt/archives 35 | 36 | # Install application gems 37 | COPY Gemfile Gemfile.lock ./ 38 | RUN bundle install && \ 39 | rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git 40 | 41 | # Copy application code 42 | COPY . . 43 | 44 | # Precompiling assets for production without requiring secret RAILS_MASTER_KEY 45 | RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile 46 | 47 | 48 | 49 | 50 | # Final stage for app image 51 | FROM base 52 | 53 | # Copy built artifacts: gems, application 54 | COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}" 55 | COPY --from=build /rails /rails 56 | 57 | # Run and own only the runtime files as a non-root user for security 58 | RUN groupadd --system --gid 1000 rails && \ 59 | useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \ 60 | chown -R rails:rails db log storage tmp 61 | USER 1000:1000 62 | 63 | # Entrypoint prepares the database. 64 | ENTRYPOINT ["/rails/bin/docker-entrypoint"] 65 | 66 | # Start the server by default, this can be overwritten at runtime 67 | EXPOSE 3000 68 | CMD ["./bin/rails", "server"] 69 | -------------------------------------------------------------------------------- /sample-app/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | # Our gems 4 | # bundle config set local.aws-sdk-rails ../ 5 | gem 'aws-sdk-rails', git: 'https://github.com/aws/aws-sdk-rails', branch: 'main' 6 | gem 'aws-actiondispatch-dynamodb', git: 'https://github.com/aws/aws-actiondispatch-dynamodb-ruby', branch: 'main' 7 | gem 'aws-actionmailbox-ses', git: 'https://github.com/aws/aws-actionmailbox-ses-ruby', branch: 'main' 8 | gem 'aws-actionmailer-ses', git: 'https://github.com/aws/aws-actionmailer-ses-ruby', branch: 'main' 9 | gem 'aws-activejob-sqs', git: 'https://github.com/aws/aws-activejob-sqs-ruby', branch: 'main' 10 | gem 'aws-record-rails', git: 'https://github.com/aws/aws-record-rails', branch: 'main' 11 | 12 | # Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword] 13 | gem 'bcrypt' 14 | 15 | gem 'byebug', platforms: :ruby 16 | 17 | ### Created by generator 18 | 19 | # Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main" 20 | gem "rails", "~> 7.2.1", ">= 7.2.1.2" 21 | # The original asset pipeline for Rails [https://github.com/rails/sprockets-rails] 22 | gem "sprockets-rails" 23 | # Use sqlite3 as the database for Active Record 24 | gem "sqlite3", ">= 1.4" 25 | # Use the Puma web server [https://github.com/puma/puma] 26 | gem "puma", ">= 5.0" 27 | 28 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 29 | gem "tzinfo-data", platforms: %i[ windows jruby ] 30 | 31 | group :development, :test do 32 | # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem 33 | gem "debug", platforms: %i[ mri windows ], require: "debug/prelude" 34 | 35 | # Static analysis for security vulnerabilities [https://brakemanscanner.org/] 36 | gem "brakeman", require: false 37 | 38 | # Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/] 39 | gem "rubocop-rails-omakase", require: false 40 | end 41 | 42 | 43 | -------------------------------------------------------------------------------- /sample-app/README.md: -------------------------------------------------------------------------------- 1 | # aws-sdk-rails Sample App 2 | 3 | This is a minimal app created with `rails new --minimal --skip-git sample-app` 4 | 5 | An additional user scaffold was created with: `bundle exec rails generate scaffold user email:uniq password:digest`. Gem `bcrypt` was added to the Gemfile. 6 | 7 | The database was migrated with: `bundle exec rails db:migrate`. 8 | 9 | The `database.yml` for production was setup to use `storage/production.sqlite3`. 10 | 11 | Our gems (`aws-sdk-rails` + feature gems) were added to the Gemfile. 12 | 13 | Gem `byebug` is added to help with development. 14 | 15 | In `config/environments/production.rb` the following configuration has been changed for Elastic Beanstalk: 16 | 17 | ```ruby 18 | config.assume_ssl = false 19 | config.force_ssl = false 20 | config.ssl_options = { redirect: false, secure_cookies: false, hsts: false } 21 | ``` 22 | 23 | The following extension was added to `.ebextensions/ruby.config` to allow to fetch github branch changes in Elastic Beanstalk: 24 | 25 | ```yaml 26 | packages: 27 | yum: 28 | git: [] 29 | ``` 30 | 31 | Rails and AWS environment variables were added to the Dockerfile. 32 | 33 | ## Pre-requisite: Deploying an Elastic Beanstalk Web Server and Worker 34 | 35 | Some of the features require a web server and worker with Elastic Beanstalk. To deploy the sample app to Elastic Beanstalk, follow these steps: 36 | 37 | Create a EB application with a **web server environment** using the **Ruby platform**. 38 | Use the default settings (including using the default/sample app initially) except: 39 | 1. (Optional) Set an EC2 key pair. 40 | 2. Choose the default VPC and enable all of the subnets. Enable a public IP address. 41 | 3. Set the root volume to General Purpose 3. 42 | 4. Select a bigger instance than the micro default, such as m7.large. 43 | 5. Set `SECRET_KEY_BASE` to `SECRET` in the environment configuration. 44 | 6. Set `AWS_REGION` to your region in the environment configuration. 45 | 46 | In SQS, create two queues called `active-job-worker` and `active-job-worker-docker`. 47 | 48 | Create a EB application with a **worker environment** using the **Ruby platform**. 49 | Use the default settings (including using the default/sample app initially) except: 50 | 1. (Optional) Set an EC2 key pair. 51 | 2. Choose the default VPC and enable all of the subnets. Enable a public IP address. 52 | 3. Set the root volume to General Purpose 3. 53 | 4. Select a bigger instance than the micro default, such as m7.large. 54 | 5. Set the worker queue to your personal `active-job-worker` queue. 55 | 6. Set `AWS_PROCESS_BEANSTALK_WORKER_REQUESTS` to `true` in the environment configuration 56 | 7. Set `SECRET_KEY_BASE` to `SECRET` in the environment configuration. 57 | 8. Set `AWS_REGION` to your region in the environment configuration. 58 | 59 | Create a EB application with a **worker environment** using the **Docker platform**. 60 | Use the default settings (including using the default/sample app initially) except: 61 | 1. (Optional) Set an EC2 key pair. 62 | 2. Choose the default VPC and enable all of the subnets. Enable a public IP address. 63 | 3. Set the root volume to General Purpose 3. 64 | 4. Select a bigger instance than the micro default, such as m7.large. 65 | 5. Set the worker queue to your personal `active-job-worker-docker` queue. 66 | 67 | Navigate to IAM and for the new role (`aws-elasticbeanstalk-ec2-role`) add the `AmazonDynamoDBFullAccess` policy. 68 | 69 | After initial deployment of the sample app and workers: 70 | 1. Ensure `path` is not used in the Gemfile - GitHub and branch may be used. 71 | 2. Run `rm Gemfile.lock && bundle install && bundle lock --add-platform ruby` 72 | 3. Create a zip of the sample-app: `rm -rf ../sample-app.zip && zip ../sample-app.zip -r * .[^.]*`. 73 | 4. Upload the zip file to your EB web environments. 74 | 75 | You can find web logs under `/var/log/puma/puma.log` 76 | 77 | ## AWS Rails Logger 78 | 79 | ### Setup 80 | 81 | The Railtie is already configured to setup the Rails logger as the global `Aws.config[:logger]`. 82 | 83 | ### Testing 84 | 85 | Run `bundle exec rails console` to start the console. 86 | 87 | Inspect the output of `Aws.config[:logger]` and ensure it is an `ActiveSupport` logger. 88 | 89 | ## Encrypted Credentials 90 | 91 | ### Setup 92 | 93 | Run `EDITOR=nano bundle exec rails credentials:edit` to edit credentials. 94 | 95 | Commented credentials are defined under the `:aws` key. Uncomment the credentials, which should look like: 96 | 97 | ```yaml 98 | aws: 99 | access_key_id: secret 100 | secret_access_key: akid 101 | session_token: token 102 | account_id: account 103 | ``` 104 | 105 | ### Testing 106 | 107 | Run `bundle exec rails console` to start the console. 108 | 109 | Inspect the output of `Aws.config` and ensure the credentials are set. 110 | 111 | ## ActiveSupport Notifications 112 | 113 | ### Setup 114 | 115 | This is configured in `config/initializers/notifications.rb`. See the `aws-sdk-rails` README. 116 | 117 | ```ruby 118 | ActiveSupport::Notifications.subscribe(/[.]aws/) do |name, start, finish, id, _payload| 119 | Rails.logger.info "Got notification: #{name} #{start} #{finish} #{id}" 120 | end 121 | ``` 122 | 123 | ### Testing 124 | 125 | Start the service with `bundle exec rails server` and visit `http://127.0.0.1:3000/users`. 126 | 127 | In the logs, you should at least see a notification for DynamoDB `update_item` from the session store. 128 | It should look like: 129 | 130 | ``` 131 | Got notification: update_item.DynamoDB.aws ... 132 | ``` 133 | 134 | ## ActionDispatch DynamoDB Session 135 | 136 | ### Setup 137 | 138 | This is configured in `config/initializers/session_store.rb`. See [this guide](https://guides.rubyonrails.org/configuring.html#config-session-store) and the `aws-sdk-rails` README. 139 | 140 | The default configuration file was generated with `bundle exec rails generate dynamo_db:session_store_config`. 141 | 142 | The ActiveRecord session table migration was created with `bundle exec rails generate dynamo_db:session_store_migration`. 143 | 144 | To create the table if it doesn't already exist, run `bundle exec rake dynamo_db:session_store:create_table`. 145 | 146 | To override changes, change this app's Gemfile to use the local path. 147 | 148 | ### Testing 149 | 150 | Start the service with `bundle exec rails server` and visit `http://127.0.0.1:3000/users`. 151 | 152 | In the logs, you should see a notification for DynamoDB `update_item` with a `session_id`. This key should exist in your DynamoDB `sessions` table. Refreshing the page should update the session `updated_at` and/or `expired_at` and not create a new session. 153 | 154 | ## ActionMailer mailers 155 | 156 | ### Setup 157 | 158 | The mailer was generated with `bundle exec rails generate mailer Test`. 159 | 160 | An empty controller scaffold was generated with `bundle exec rails generate controller Mailer`. 161 | 162 | `ApplicationMailer` was set to use `ENV['ACTION_MAILER_EMAIL']`. 163 | 164 | `TestMailer` implemented SES and SESv2 mailer methods. 165 | 166 | `MailerController` (and routes) were added to send mail. 167 | 168 | `config/application.rb` added `require "action_mailer/railtie"`. 169 | 170 | Delivery methods are configured in `config/initializers/action_mailer.rb`. 171 | 172 | ### Testing 173 | 174 | Start the service with `ACTION_MAILER_EMAIL= bundle exec rails server`. 175 | 176 | > **Important**: The email address in SES must be verified. 177 | 178 | Visit `http://127.0.0.1:3000/send_ses_email` or `http://127.0.0.1:3000/send_ses_v2_email` and check your email. 179 | 180 | ## ActionMailbox ingress 181 | 182 | ### Setup 183 | 184 | Following [this guide](https://guides.rubyonrails.org/action_mailbox_basics.html), ActionMailbox was setup with `bundle exec bin/rails action_mailbox:install`. 185 | 186 | The database was migrated with: `bundle exec rails db:migrate`. 187 | 188 | The ingress and ActiveStorage was configured in `config/environments/development.rb` with: 189 | 190 | ```ruby 191 | config.active_storage.service = :local 192 | config.action_mailbox.ingress = :ses 193 | ``` 194 | 195 | A default route was added to `app/mailboxes/application_mailbox.rb`. 196 | 197 | The test mailbox was created with `bundle exec rails generate mailbox test`. 198 | 199 | ### Testing 200 | 201 | This feature can't fully be tested end to end unless the rails application is hosted on a domain. The SNS topic would have to notify a route such as `https://example.com/rails/action_mailbox/ses/inbound_emails`. 202 | 203 | Future work could deploy this sample-app behind a domain to fully test it. 204 | 205 | Start the service with `bundle exec rails server` and visit `http://127.0.0.1:3000/rails/conductor/action_mailbox/inbound_emails`. 206 | 207 | Click "New inbound email by source". 208 | 209 | Use the following message (other messages can be created and signed in aws-actionmailbox-ses): 210 | 211 | ``` 212 | Return-Path: 213 | Received: from example.com (example.com [127.0.0.1]) 214 | by inbound-smtp.us-east-1.amazonaws.com with SMTP id 17at0jiq08p0449huhf16qsmdi6sa1ltm069t801 215 | for test@test.example.com; 216 | Wed, 02 Sep 2020 01:30:50 +0000 (UTC) 217 | X-SES-Spam-Verdict: PASS 218 | X-SES-Virus-Verdict: PASS 219 | X-SES-RECEIPT: AEFBQUFBQUFBQUFHMWlxem9Gb1ZOemNkamlTeFlYdlZUSmUwVVZhYndjK213dHFIM0dVRTYwUlk1UlpBQVVVTXhQRUd1MTN6YTFJalp0TFdMZjhOOUZGSlJCYkxEV2craXhpOG02d2xDc2FtY2dNdVMvRE9QWWpNVkxBWVZzMyt5MHBTUXV5KzM5aDY1Vng5UnZsZTdTK2dGVDF5RVc1QndOd0xvbndNRlR3TDZjd2cxT2c2UVFQbVN2andMS09VM2R5elFrTGk3RnF0WXI3WDZ1alhkUzJxdzhzU1dwT3FPZEFsU0VNc3RpTWM0QStFZDB5RFd5SnpRelBJWnJjelZPRytudEVpNTc5dVZRUXMra2lrby9wOExhR3JqTi9xNkZnNHREN3BmSmVYS25Jeis2NDRyaEE9PQ== 220 | X-SES-DKIM-SIGNATURE: a=rsa-sha256; q=dns/txt; b=WGBoUguIq9047YXpCaubVCtm/ISR3JEVkvm/yAfL2MrAryQcYsTdUM6zzStPyvOm0QsonOKsWJ0O2YyuQDX1dvBmggdeUqZq08laD+Xuy1L6ODm0O/EQE9wDitj0KqXxOgMr3oM7tpcTTGLcCgXERFZbmI+1ACeeA7fbylMasIM=; c=relaxed/simple; s=224i4yxa5dv7c2xz3womw6peuasteono; d=amazonses.com; t=1599010250; v=1; bh=FUugtX/z1FFtLvVfaVhPhqhi4Gvo1Aam67iRPZYKfTo=; h=From:To:Cc:Bcc:Subject:Date:Message-ID:MIME-Version:Content-Type:X-SES-RECEIPT; 221 | From: "Smith, Bob E" 222 | To: "test@test.example.com" 223 | 224 | Subject: Test 500 225 | Thread-Topic: Test 500 226 | Thread-Index: AQHWgMisvDz2gn/lKEK/giPayBxk7g== 227 | Date: Wed, 2 Sep 2020 01:30:43 +0000 228 | Message-ID: <1344C740-07D3-476E-BEE7-6EB162294DF6@example.com> 229 | Accept-Language: en-US 230 | Content-Language: en-US 231 | Content-Type: text/plain; charset="us-ascii" 232 | Content-ID: 233 | Content-Transfer-Encoding: quoted-printable 234 | MIME-Version: 1.0 235 | 236 | Aaaron 237 | ``` 238 | 239 | You should see the message say delivered and not bounced. 240 | 241 | ## ActiveJob SQS 242 | 243 | ### Setup 244 | 245 | The jobs were generated with `bundle exec rails generate job Test` and `bundle exec rails generate job TestAsync`. 246 | 247 | An empty controller scaffold was generated with `bundle exec rails generate controller Job`. 248 | 249 | `TestJob` and `TestAsyncJob` was implemented to print the job's args. 250 | 251 | `JobController` and routes were added to queue the job. 252 | 253 | `config/application.rb` added `require "active_job/railtie"`. 254 | 255 | > **Important**: Create an SQS queue and retrieve the queue URL. 256 | 257 | ### Testing 258 | 259 | Start rails with `AWS_ACTIVE_JOB_QUEUE_URL=https://my_sqs_queue_url bundle exec rails server` 260 | 261 | Poll for and process jobs with: `AWS_ACTIVE_JOB_QUEUE_URL=https://my_sqs_queue_url bundle exec aws_sqs_active_job --queue default` 262 | 263 | Visit `http://127.0.0.1:3000/queue_sqs_job` and `http://127.0.0.1:3000/queue_sqs_async_job` to queue jobs. The output of both jobs should be printed in the logs. 264 | 265 | ### Testing with Elastic Beanstalk workers 266 | 267 | Run the sample-app locally with `AWS_ACTIVE_JOB_QUEUE_URL=https://my_sqs_queue_url rails console`. 268 | 269 | Send a test job: `TestJob.perform_later('elastic beanstalk worker test')`. 270 | 271 | You can then request the logs and should see processing of the job in `/var/log/puma/puma.log`. 272 | 273 | For Docker workers, the logs are in `/var/log/eb-docker/containers/eb-current-app/eb--stdouterr.log` . 274 | 275 | Repeat this for either the worker or docker worker queues. 276 | 277 | ### Testing with Lambda 278 | 279 | TODO 280 | 281 | ## Aws::Record Generators 282 | 283 | Run `bundle exec rails generate aws_record:scaffold aws_record_test --table-config primary:10-5`. 284 | 285 | An entire scaffold should be created. 286 | -------------------------------------------------------------------------------- /sample-app/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative "config/application" 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /sample-app/app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../stylesheets .css 3 | -------------------------------------------------------------------------------- /sample-app/app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-sdk-rails/dec6705b457e1523bdc20caf642330c6b65eef90/sample-app/app/assets/images/.keep -------------------------------------------------------------------------------- /sample-app/app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's 6 | * vendor/assets/stylesheets directory can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS 10 | * files in this directory. Styles in this file should be added after the last require_* statement. 11 | * It is generally better to create a new file per style scope. 12 | * 13 | *= require_tree . 14 | *= require_self 15 | */ 16 | -------------------------------------------------------------------------------- /sample-app/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | # Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has. 3 | allow_browser versions: :modern 4 | end 5 | -------------------------------------------------------------------------------- /sample-app/app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-sdk-rails/dec6705b457e1523bdc20caf642330c6b65eef90/sample-app/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /sample-app/app/controllers/job_controller.rb: -------------------------------------------------------------------------------- 1 | class JobController < ApplicationController 2 | def queue_sqs_job 3 | TestJob.perform_later('a1', 'a2') 4 | render plain: 'Job enqueued' 5 | end 6 | 7 | def queue_sqs_async_job 8 | TestAsyncJob.perform_later('a1', 'a2') 9 | render plain: 'Job enqueued' 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /sample-app/app/controllers/mailer_controller.rb: -------------------------------------------------------------------------------- 1 | class MailerController < ApplicationController 2 | def send_ses_email 3 | TestMailer.send_ses_email.deliver_now 4 | render plain: 'Email sent using SES' 5 | end 6 | 7 | def send_ses_v2_email 8 | TestMailer.send_ses_v2_email.deliver_now 9 | render plain: 'Email sent using SES V2' 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /sample-app/app/controllers/users_controller.rb: -------------------------------------------------------------------------------- 1 | class UsersController < ApplicationController 2 | before_action :set_user, only: %i[ show edit update destroy ] 3 | 4 | # GET /users 5 | def index 6 | @users = User.all 7 | end 8 | 9 | # GET /users/1 10 | def show 11 | end 12 | 13 | # GET /users/new 14 | def new 15 | @user = User.new 16 | end 17 | 18 | # GET /users/1/edit 19 | def edit 20 | end 21 | 22 | # POST /users 23 | def create 24 | @user = User.new(user_params) 25 | 26 | if @user.save 27 | redirect_to @user, notice: "User was successfully created." 28 | else 29 | render :new, status: :unprocessable_entity 30 | end 31 | end 32 | 33 | # PATCH/PUT /users/1 34 | def update 35 | if @user.update(user_params) 36 | redirect_to @user, notice: "User was successfully updated.", status: :see_other 37 | else 38 | render :edit, status: :unprocessable_entity 39 | end 40 | end 41 | 42 | # DELETE /users/1 43 | def destroy 44 | @user.destroy! 45 | redirect_to users_url, notice: "User was successfully destroyed.", status: :see_other 46 | end 47 | 48 | private 49 | # Use callbacks to share common setup or constraints between actions. 50 | def set_user 51 | @user = User.find(params[:id]) 52 | end 53 | 54 | # Only allow a list of trusted parameters through. 55 | def user_params 56 | params.require(:user).permit(:email, :password, :password_confirmation) 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /sample-app/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /sample-app/app/helpers/job_helper.rb: -------------------------------------------------------------------------------- 1 | module JobHelper 2 | end 3 | -------------------------------------------------------------------------------- /sample-app/app/helpers/mailer_helper.rb: -------------------------------------------------------------------------------- 1 | module MailerHelper 2 | end 3 | -------------------------------------------------------------------------------- /sample-app/app/helpers/users_helper.rb: -------------------------------------------------------------------------------- 1 | module UsersHelper 2 | end 3 | -------------------------------------------------------------------------------- /sample-app/app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | # Automatically retry jobs that encountered a deadlock 3 | # retry_on ActiveRecord::Deadlocked 4 | 5 | # Most jobs are safe to ignore if the underlying records are no longer available 6 | # discard_on ActiveJob::DeserializationError 7 | end 8 | -------------------------------------------------------------------------------- /sample-app/app/jobs/test_async_job.rb: -------------------------------------------------------------------------------- 1 | class TestAsyncJob < ApplicationJob 2 | self.queue_adapter = :sqs_async 3 | queue_as :default 4 | 5 | def perform(*args) 6 | puts "AsyncJob performed with args: #{args}" 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /sample-app/app/jobs/test_job.rb: -------------------------------------------------------------------------------- 1 | class TestJob < ApplicationJob 2 | self.queue_adapter = :sqs 3 | queue_as :default 4 | 5 | def perform(*args) 6 | puts "Job performed with args: #{args}" 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /sample-app/app/mailboxes/application_mailbox.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailbox < ActionMailbox::Base 2 | routing /.*/ => :test 3 | end 4 | -------------------------------------------------------------------------------- /sample-app/app/mailboxes/test_mailbox.rb: -------------------------------------------------------------------------------- 1 | class TestMailbox < ApplicationMailbox 2 | def process 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /sample-app/app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: ENV['ACTION_MAILER_EMAIL'] 3 | layout 'mailer' 4 | end 5 | 6 | -------------------------------------------------------------------------------- /sample-app/app/mailers/test_mailer.rb: -------------------------------------------------------------------------------- 1 | class TestMailer < ApplicationMailer 2 | def send_ses_email 3 | mail( 4 | to: ENV['ACTION_MAILER_EMAIL'], 5 | subject: 'Amazon SES Email', 6 | body: 'This is a test email from Amazon SES', 7 | delivery_method: :ses 8 | ) 9 | end 10 | 11 | def send_ses_v2_email 12 | mail( 13 | to: ENV['ACTION_MAILER_EMAIL'], 14 | subject: 'Amazon SES V2 Email', 15 | body: 'This is a test email from Amazon SES V2', 16 | delivery_method: :ses_v2 17 | ) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /sample-app/app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | primary_abstract_class 3 | end 4 | -------------------------------------------------------------------------------- /sample-app/app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-sdk-rails/dec6705b457e1523bdc20caf642330c6b65eef90/sample-app/app/models/concerns/.keep -------------------------------------------------------------------------------- /sample-app/app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ApplicationRecord 2 | has_secure_password 3 | end 4 | -------------------------------------------------------------------------------- /sample-app/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= content_for(:title) || "Sample App" %> 5 | 6 | 7 | <%= csrf_meta_tags %> 8 | <%= csp_meta_tag %> 9 | 10 | <%= yield :head %> 11 | 12 | 13 | 14 | 15 | 16 | <%= stylesheet_link_tag "application" %> 17 | 18 | 19 | 20 | <%= yield %> 21 | 22 | 23 | -------------------------------------------------------------------------------- /sample-app/app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /sample-app/app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /sample-app/app/views/pwa/manifest.json.erb: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SampleApp", 3 | "icons": [ 4 | { 5 | "src": "/icon.png", 6 | "type": "image/png", 7 | "sizes": "512x512" 8 | }, 9 | { 10 | "src": "/icon.png", 11 | "type": "image/png", 12 | "sizes": "512x512", 13 | "purpose": "maskable" 14 | } 15 | ], 16 | "start_url": "/", 17 | "display": "standalone", 18 | "scope": "/", 19 | "description": "SampleApp.", 20 | "theme_color": "red", 21 | "background_color": "red" 22 | } 23 | -------------------------------------------------------------------------------- /sample-app/app/views/pwa/service-worker.js: -------------------------------------------------------------------------------- 1 | // Add a service worker for processing Web Push notifications: 2 | // 3 | // self.addEventListener("push", async (event) => { 4 | // const { title, options } = await event.data.json() 5 | // event.waitUntil(self.registration.showNotification(title, options)) 6 | // }) 7 | // 8 | // self.addEventListener("notificationclick", function(event) { 9 | // event.notification.close() 10 | // event.waitUntil( 11 | // clients.matchAll({ type: "window" }).then((clientList) => { 12 | // for (let i = 0; i < clientList.length; i++) { 13 | // let client = clientList[i] 14 | // let clientPath = (new URL(client.url)).pathname 15 | // 16 | // if (clientPath == event.notification.data.path && "focus" in client) { 17 | // return client.focus() 18 | // } 19 | // } 20 | // 21 | // if (clients.openWindow) { 22 | // return clients.openWindow(event.notification.data.path) 23 | // } 24 | // }) 25 | // ) 26 | // }) 27 | -------------------------------------------------------------------------------- /sample-app/app/views/users/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with(model: user) do |form| %> 2 | <% if user.errors.any? %> 3 |
4 |

<%= pluralize(user.errors.count, "error") %> prohibited this user from being saved:

5 | 6 |
    7 | <% user.errors.each do |error| %> 8 |
  • <%= error.full_message %>
  • 9 | <% end %> 10 |
11 |
12 | <% end %> 13 | 14 |
15 | <%= form.label :email, style: "display: block" %> 16 | <%= form.text_field :email %> 17 |
18 | 19 |
20 | <%= form.label :password, style: "display: block" %> 21 | <%= form.password_field :password %> 22 |
23 | 24 |
25 | <%= form.label :password_confirmation, style: "display: block" %> 26 | <%= form.password_field :password_confirmation %> 27 |
28 | 29 |
30 | <%= form.submit %> 31 |
32 | <% end %> 33 | -------------------------------------------------------------------------------- /sample-app/app/views/users/_user.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

3 | Email: 4 | <%= user.email %> 5 |

6 | 7 |
8 | -------------------------------------------------------------------------------- /sample-app/app/views/users/edit.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title, "Editing user" %> 2 | 3 |

Editing user

4 | 5 | <%= render "form", user: @user %> 6 | 7 |
8 | 9 |
10 | <%= link_to "Show this user", @user %> | 11 | <%= link_to "Back to users", users_path %> 12 |
13 | -------------------------------------------------------------------------------- /sample-app/app/views/users/index.html.erb: -------------------------------------------------------------------------------- 1 |

<%= notice %>

2 | 3 | <% content_for :title, "Users" %> 4 | 5 |

Users

6 | 7 |
8 | <% @users.each do |user| %> 9 | <%= render user %> 10 |

11 | <%= link_to "Show this user", user %> 12 |

13 | <% end %> 14 |
15 | 16 | <%= link_to "New user", new_user_path %> 17 | -------------------------------------------------------------------------------- /sample-app/app/views/users/new.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title, "New user" %> 2 | 3 |

New user

4 | 5 | <%= render "form", user: @user %> 6 | 7 |
8 | 9 |
10 | <%= link_to "Back to users", users_path %> 11 |
12 | -------------------------------------------------------------------------------- /sample-app/app/views/users/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= notice %>

2 | 3 | <%= render @user %> 4 | 5 |
6 | <%= link_to "Edit this user", edit_user_path(@user) %> | 7 | <%= link_to "Back to users", users_path %> 8 | 9 | <%= button_to "Destroy this user", @user, method: :delete %> 10 |
11 | -------------------------------------------------------------------------------- /sample-app/bin/brakeman: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "rubygems" 3 | require "bundler/setup" 4 | 5 | ARGV.unshift("--ensure-latest") 6 | 7 | load Gem.bin_path("brakeman", "brakeman") 8 | -------------------------------------------------------------------------------- /sample-app/bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'bundle' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "rubygems" 12 | 13 | m = Module.new do 14 | module_function 15 | 16 | def invoked_as_script? 17 | File.expand_path($0) == File.expand_path(__FILE__) 18 | end 19 | 20 | def env_var_version 21 | ENV["BUNDLER_VERSION"] 22 | end 23 | 24 | def cli_arg_version 25 | return unless invoked_as_script? # don't want to hijack other binstubs 26 | return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` 27 | bundler_version = nil 28 | update_index = nil 29 | ARGV.each_with_index do |a, i| 30 | if update_index && update_index.succ == i && a.match?(Gem::Version::ANCHORED_VERSION_PATTERN) 31 | bundler_version = a 32 | end 33 | next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ 34 | bundler_version = $1 35 | update_index = i 36 | end 37 | bundler_version 38 | end 39 | 40 | def gemfile 41 | gemfile = ENV["BUNDLE_GEMFILE"] 42 | return gemfile if gemfile && !gemfile.empty? 43 | 44 | File.expand_path("../Gemfile", __dir__) 45 | end 46 | 47 | def lockfile 48 | lockfile = 49 | case File.basename(gemfile) 50 | when "gems.rb" then gemfile.sub(/\.rb$/, ".locked") 51 | else "#{gemfile}.lock" 52 | end 53 | File.expand_path(lockfile) 54 | end 55 | 56 | def lockfile_version 57 | return unless File.file?(lockfile) 58 | lockfile_contents = File.read(lockfile) 59 | return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ 60 | Regexp.last_match(1) 61 | end 62 | 63 | def bundler_requirement 64 | @bundler_requirement ||= 65 | env_var_version || 66 | cli_arg_version || 67 | bundler_requirement_for(lockfile_version) 68 | end 69 | 70 | def bundler_requirement_for(version) 71 | return "#{Gem::Requirement.default}.a" unless version 72 | 73 | bundler_gem_version = Gem::Version.new(version) 74 | 75 | bundler_gem_version.approximate_recommendation 76 | end 77 | 78 | def load_bundler! 79 | ENV["BUNDLE_GEMFILE"] ||= gemfile 80 | 81 | activate_bundler 82 | end 83 | 84 | def activate_bundler 85 | gem_error = activation_error_handling do 86 | gem "bundler", bundler_requirement 87 | end 88 | return if gem_error.nil? 89 | require_error = activation_error_handling do 90 | require "bundler/version" 91 | end 92 | return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) 93 | warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" 94 | exit 42 95 | end 96 | 97 | def activation_error_handling 98 | yield 99 | nil 100 | rescue StandardError, LoadError => e 101 | e 102 | end 103 | end 104 | 105 | m.load_bundler! 106 | 107 | if m.invoked_as_script? 108 | load Gem.bin_path("bundler", "bundle") 109 | end 110 | -------------------------------------------------------------------------------- /sample-app/bin/docker-entrypoint: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # Enable jemalloc for reduced memory usage and latency. 4 | if [ -z "${LD_PRELOAD+x}" ] && [ -f /usr/lib/*/libjemalloc.so.2 ]; then 5 | export LD_PRELOAD="$(echo /usr/lib/*/libjemalloc.so.2)" 6 | fi 7 | 8 | # If running the rails server then create or migrate existing database 9 | if [ "${1}" == "./bin/rails" ] && [ "${2}" == "server" ]; then 10 | ./bin/rails db:prepare 11 | fi 12 | 13 | exec "${@}" 14 | -------------------------------------------------------------------------------- /sample-app/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path("../config/application", __dir__) 3 | require_relative "../config/boot" 4 | require "rails/commands" 5 | -------------------------------------------------------------------------------- /sample-app/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative "../config/boot" 3 | require "rake" 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /sample-app/bin/rubocop: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "rubygems" 3 | require "bundler/setup" 4 | 5 | # explicit rubocop config increases performance slightly while avoiding config confusion. 6 | ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__)) 7 | 8 | load Gem.bin_path("rubocop", "rubocop") 9 | -------------------------------------------------------------------------------- /sample-app/bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "fileutils" 3 | 4 | APP_ROOT = File.expand_path("..", __dir__) 5 | APP_NAME = "sample-app" 6 | 7 | def system!(*args) 8 | system(*args, exception: true) 9 | end 10 | 11 | FileUtils.chdir APP_ROOT do 12 | # This script is a way to set up or update your development environment automatically. 13 | # This script is idempotent, so that you can run it at any time and get an expectable outcome. 14 | # Add necessary setup steps to this file. 15 | 16 | puts "== Installing dependencies ==" 17 | system! "gem install bundler --conservative" 18 | system("bundle check") || system!("bundle install") 19 | 20 | # puts "\n== Copying sample files ==" 21 | # unless File.exist?("config/database.yml") 22 | # FileUtils.cp "config/database.yml.sample", "config/database.yml" 23 | # end 24 | 25 | puts "\n== Preparing database ==" 26 | system! "bin/rails db:prepare" 27 | 28 | puts "\n== Removing old logs and tempfiles ==" 29 | system! "bin/rails log:clear tmp:clear" 30 | 31 | puts "\n== Restarting application server ==" 32 | system! "bin/rails restart" 33 | 34 | # puts "\n== Configuring puma-dev ==" 35 | # system "ln -nfs #{APP_ROOT} ~/.puma-dev/#{APP_NAME}" 36 | # system "curl -Is https://#{APP_NAME}.test/up | head -n 1" 37 | end 38 | -------------------------------------------------------------------------------- /sample-app/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative "config/environment" 4 | 5 | run Rails.application 6 | Rails.application.load_server 7 | -------------------------------------------------------------------------------- /sample-app/config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative "boot" 2 | 3 | require "rails" 4 | # Pick the frameworks you want: 5 | require "active_model/railtie" 6 | require "active_job/railtie" 7 | require "active_record/railtie" 8 | # require "active_storage/engine" 9 | require "action_controller/railtie" 10 | require "action_mailer/railtie" 11 | # require "action_mailbox/engine" 12 | # require "action_text/engine" 13 | require "action_view/railtie" 14 | # require "action_cable/engine" 15 | require "rails/test_unit/railtie" 16 | 17 | # Require the gems listed in Gemfile, including any gems 18 | # you've limited to :test, :development, or :production. 19 | Bundler.require(*Rails.groups) 20 | 21 | module SampleApp 22 | class Application < Rails::Application 23 | # Initialize configuration defaults for originally generated Rails version. 24 | config.load_defaults 7.2 25 | 26 | # Please, add to the `ignore` list any other `lib` subdirectories that do 27 | # not contain `.rb` files, or that should not be reloaded or eager loaded. 28 | # Common ones are `templates`, `generators`, or `middleware`, for example. 29 | config.autoload_lib(ignore: %w[assets tasks]) 30 | 31 | # Configuration for the application, engines, and railties goes here. 32 | # 33 | # These settings can be overridden in specific environments using the files 34 | # in config/environments, which are processed later. 35 | # 36 | # config.time_zone = "Central Time (US & Canada)" 37 | # config.eager_load_paths << Rails.root.join("extras") 38 | 39 | # Don't generate system test files. 40 | config.generators.system_tests = nil 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /sample-app/config/aws_dynamo_db_session_store.yml: -------------------------------------------------------------------------------- 1 | # Uncomment and manipulate the key value pairs below 2 | # in order to configure the DynamoDB Session Store Application. 3 | 4 | # [String] Session table name. 5 | # 6 | # table_name: Sessions 7 | 8 | # [String] Session table hash key name. 9 | # 10 | # table_key: session_id 11 | 12 | # [String] The secret key for HMAC encryption. This defaults to 13 | # `Rails.application.secret_key_base`. You can use a different key if desired. 14 | # 15 | # secret_key: SECRET_KEY 16 | 17 | # [Boolean] Define as true or false depending on if you want a strongly 18 | # consistent read. 19 | # See AWS DynamoDB documentation for table consistent_read for more 20 | # information on this setting. 21 | # 22 | # consistent_read: true 23 | 24 | # [Integer] Maximum number of reads consumed per second before 25 | # DynamoDB returns a ThrottlingException. See AWS DynamoDB documentation 26 | # or table read_capacity for more information on this setting. 27 | # 28 | # read_capacity: 10 29 | 30 | # [Integer] Maximum number of writes consumed per second before 31 | # DynamoDB returns a ThrottlingException. See AWS DynamoDB documentation 32 | # or table write_capacity for more information on this setting. 33 | # 34 | # write_capacity: 5 35 | 36 | # [Boolean] Define as true or false depending on whether you want all errors to be 37 | # raised up the stack. 38 | # 39 | # raise_errors: false 40 | 41 | # [Integer] Maximum number of seconds earlier 42 | # from the current time that a session was created. 43 | # By default this is 7 days. 44 | # 45 | # max_age: 604800 46 | 47 | # [Integer] Maximum number of seconds 48 | # before the current time that the session was last accessed. 49 | # By default this is 5 hours. 50 | # 51 | # max_stale: 18000 52 | 53 | # [Boolean] Define as true or false for whether you want to enable locking 54 | # for all accesses to session data. 55 | # 56 | # enable_locking: false 57 | 58 | # [Integer] Time in milleseconds after which lock will expire. 59 | # 60 | # lock_expiry_time: 500 61 | 62 | # [Integer] Time in milleseconds to wait before retrying to obtain 63 | # lock once an attempt to obtain lock has been made and has failed. 64 | # 65 | # lock_retry_delay: 500 66 | 67 | # [Integer] Maximum time in seconds to wait to acquire lock 68 | # before giving up. 69 | # 70 | # lock_max_wait_time: 1 71 | -------------------------------------------------------------------------------- /sample-app/config/aws_sqs_active_job.yml: -------------------------------------------------------------------------------- 1 | queues: 2 | default: <%= ENV['AWS_ACTIVE_JOB_QUEUE_URL'] %> -------------------------------------------------------------------------------- /sample-app/config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 2 | 3 | require "bundler/setup" # Set up gems listed in the Gemfile. 4 | -------------------------------------------------------------------------------- /sample-app/config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | zL8sHwUnJeo0dd2+65lrC/MSuzGI9vjTZ79XSBD+8p6An51I3JwyxFi1IzYn5dOoTYLbPaZSfQNz0IuvIbEKE3hRoI19SA877J4ap9Cppm7L1QyW46lO9kHo6UfII6UCgzuPAtMn5T8IgL6FoybZUGlpdqZL1KQrwWQt34V6d6InHGPD3twyWcxMFKHzrtw97BP0LPzxqP9YHFzj+gvu40CrKr+Ai0PsqERU/XnrmvIq2RkJ27vTn7RayUvxndZe3JCRzO/X19BLemjvUh8TmaaWHSiOd4geiRt6kDzSfKYAhPpVvr3CYWjDGOetF1/7CSHXgzgV2xpRX6TtGSJzpCirJmEFGBek3UKaJhuwdy7Fz/vt3gZeTPXT2uDv0HjHP79yFcE+df9saAOSVi/ccGw31gj7XzJw7zmnlcagSWdPc1UrtECKuLD02vehMt6t+zSn+AGh1bd3EZOP9VKSYBLQGmx3J0AGRqE=--ntBqTaCxzRu+86Vz--aRDU88hyEf9xyoUqyIl1fA== -------------------------------------------------------------------------------- /sample-app/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite. Versions 3.8.0 and up are supported. 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem "sqlite3" 6 | # 7 | default: &default 8 | adapter: sqlite3 9 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 10 | timeout: 5000 11 | 12 | development: 13 | <<: *default 14 | database: storage/development.sqlite3 15 | 16 | # Warning: The database defined as "test" will be erased and 17 | # re-generated from your development database when you run "rake". 18 | # Do not set this db to the same as development or production. 19 | test: 20 | <<: *default 21 | database: storage/test.sqlite3 22 | 23 | 24 | # SQLite3 write its data on the local filesystem, as such it requires 25 | # persistent disks. If you are deploying to a managed service, you should 26 | # make sure it provides disk persistence, as many don't. 27 | # 28 | # Similarly, if you deploy your application as a Docker container, you must 29 | # ensure the database is located in a persisted volume. 30 | production: 31 | <<: *default 32 | database: storage/production.sqlite3 33 | -------------------------------------------------------------------------------- /sample-app/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative "application" 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /sample-app/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | Rails.application.configure do 4 | config.active_storage.service = :local 5 | config.action_mailbox.ingress = :ses 6 | 7 | # Settings specified here will take precedence over those in config/application.rb. 8 | 9 | # In the development environment your application's code is reloaded any time 10 | # it changes. This slows down response time but is perfect for development 11 | # since you don't have to restart the web server when you make code changes. 12 | config.enable_reloading = true 13 | 14 | # Do not eager load code on boot. 15 | config.eager_load = false 16 | 17 | # Show full error reports. 18 | config.consider_all_requests_local = true 19 | 20 | # Enable server timing. 21 | config.server_timing = true 22 | 23 | # Enable/disable caching. By default caching is disabled. 24 | # Run rails dev:cache to toggle caching. 25 | if Rails.root.join("tmp/caching-dev.txt").exist? 26 | config.action_controller.perform_caching = true 27 | config.action_controller.enable_fragment_cache_logging = true 28 | 29 | config.cache_store = :memory_store 30 | config.public_file_server.headers = { "Cache-Control" => "public, max-age=#{2.days.to_i}" } 31 | else 32 | config.action_controller.perform_caching = false 33 | 34 | config.cache_store = :null_store 35 | end 36 | 37 | # Print deprecation notices to the Rails logger. 38 | config.active_support.deprecation = :log 39 | 40 | # Raise exceptions for disallowed deprecations. 41 | config.active_support.disallowed_deprecation = :raise 42 | 43 | # Tell Active Support which deprecation messages to disallow. 44 | config.active_support.disallowed_deprecation_warnings = [] 45 | 46 | # Raise an error on page load if there are pending migrations. 47 | config.active_record.migration_error = :page_load 48 | 49 | # Highlight code that triggered database queries in logs. 50 | config.active_record.verbose_query_logs = true 51 | 52 | # Suppress logger output for asset requests. 53 | config.assets.quiet = true 54 | 55 | # Raises error for missing translations. 56 | # config.i18n.raise_on_missing_translations = true 57 | 58 | # Annotate rendered view with file names. 59 | config.action_view.annotate_rendered_view_with_filenames = true 60 | 61 | # Raise error when a before_action's only/except options reference missing actions. 62 | config.action_controller.raise_on_missing_callback_actions = true 63 | 64 | # Apply autocorrection by RuboCop to files generated by `bin/rails generate`. 65 | # config.generators.apply_rubocop_autocorrect_after_generate! 66 | end 67 | -------------------------------------------------------------------------------- /sample-app/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | Rails.application.configure do 4 | config.assume_ssl = false 5 | config.force_ssl = false 6 | config.ssl_options = { redirect: false, secure_cookies: false, hsts: false } 7 | 8 | # Prepare the ingress controller used to receive mail 9 | # config.action_mailbox.ingress = :relay 10 | 11 | # Settings specified here will take precedence over those in config/application.rb. 12 | 13 | # Code is not reloaded between requests. 14 | config.enable_reloading = false 15 | 16 | # Eager load code on boot. This eager loads most of Rails and 17 | # your application in memory, allowing both threaded web servers 18 | # and those relying on copy on write to perform better. 19 | # Rake tasks automatically ignore this option for performance. 20 | config.eager_load = true 21 | 22 | # Full error reports are disabled and caching is turned on. 23 | config.consider_all_requests_local = false 24 | config.action_controller.perform_caching = true 25 | 26 | # Ensures that a master key has been made available in ENV["RAILS_MASTER_KEY"], config/master.key, or an environment 27 | # key such as config/credentials/production.key. This key is used to decrypt credentials (and other encrypted files). 28 | # config.require_master_key = true 29 | 30 | # Disable serving static files from `public/`, relying on NGINX/Apache to do so instead. 31 | # config.public_file_server.enabled = false 32 | 33 | # Compress CSS using a preprocessor. 34 | # config.assets.css_compressor = :sass 35 | 36 | # Do not fall back to assets pipeline if a precompiled asset is missed. 37 | config.assets.compile = false 38 | 39 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 40 | # config.asset_host = "http://assets.example.com" 41 | 42 | # Specifies the header that your server uses for sending files. 43 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache 44 | # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX 45 | 46 | # Skip http-to-https redirect for the default health check endpoint. 47 | # config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } } 48 | 49 | # Log to STDOUT by default 50 | config.logger = ActiveSupport::Logger.new(STDOUT) 51 | .tap { |logger| logger.formatter = ::Logger::Formatter.new } 52 | .then { |logger| ActiveSupport::TaggedLogging.new(logger) } 53 | 54 | # Prepend all log lines with the following tags. 55 | config.log_tags = [ :request_id ] 56 | 57 | # "info" includes generic and useful information about system operation, but avoids logging too much 58 | # information to avoid inadvertent exposure of personally identifiable information (PII). If you 59 | # want to log everything, set the level to "debug". 60 | config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info") 61 | 62 | # Use a different cache store in production. 63 | # config.cache_store = :mem_cache_store 64 | 65 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 66 | # the I18n.default_locale when a translation cannot be found). 67 | config.i18n.fallbacks = true 68 | 69 | # Don't log any deprecations. 70 | config.active_support.report_deprecations = false 71 | 72 | # Do not dump schema after migrations. 73 | config.active_record.dump_schema_after_migration = false 74 | 75 | # Enable DNS rebinding protection and other `Host` header attacks. 76 | # config.hosts = [ 77 | # "example.com", # Allow requests from example.com 78 | # /.*\.example\.com/ # Allow requests from subdomains like `www.example.com` 79 | # ] 80 | # Skip DNS rebinding protection for the default health check endpoint. 81 | # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } 82 | end 83 | -------------------------------------------------------------------------------- /sample-app/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | # The test environment is used exclusively to run your application's 4 | # test suite. You never need to work with it otherwise. Remember that 5 | # your test database is "scratch space" for the test suite and is wiped 6 | # and recreated between test runs. Don't rely on the data there! 7 | 8 | Rails.application.configure do 9 | # Settings specified here will take precedence over those in config/application.rb. 10 | 11 | # While tests run files are not watched, reloading is not necessary. 12 | config.enable_reloading = false 13 | 14 | # Eager loading loads your entire application. When running a single test locally, 15 | # this is usually not necessary, and can slow down your test suite. However, it's 16 | # recommended that you enable it in continuous integration systems to ensure eager 17 | # loading is working properly before deploying your code. 18 | config.eager_load = ENV["CI"].present? 19 | 20 | # Configure public file server for tests with Cache-Control for performance. 21 | config.public_file_server.headers = { "Cache-Control" => "public, max-age=#{1.hour.to_i}" } 22 | 23 | # Show full error reports and disable caching. 24 | config.consider_all_requests_local = true 25 | config.action_controller.perform_caching = false 26 | config.cache_store = :null_store 27 | 28 | # Render exception templates for rescuable exceptions and raise for other exceptions. 29 | config.action_dispatch.show_exceptions = :rescuable 30 | 31 | # Disable request forgery protection in test environment. 32 | config.action_controller.allow_forgery_protection = false 33 | 34 | # Print deprecation notices to the stderr. 35 | config.active_support.deprecation = :stderr 36 | 37 | # Raise exceptions for disallowed deprecations. 38 | config.active_support.disallowed_deprecation = :raise 39 | 40 | # Tell Active Support which deprecation messages to disallow. 41 | config.active_support.disallowed_deprecation_warnings = [] 42 | 43 | # Raises error for missing translations. 44 | # config.i18n.raise_on_missing_translations = true 45 | 46 | # Annotate rendered view with file names. 47 | # config.action_view.annotate_rendered_view_with_filenames = true 48 | 49 | # Raise error when a before_action's only/except options reference missing actions. 50 | config.action_controller.raise_on_missing_callback_actions = true 51 | end 52 | -------------------------------------------------------------------------------- /sample-app/config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = "1.0" 5 | 6 | # Add additional assets to the asset load path. 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | 9 | # Precompile additional assets. 10 | # application.js, application.css, and all non-JS/CSS in the app/assets 11 | # folder are already added. 12 | # Rails.application.config.assets.precompile += %w[ admin.js admin.css ] 13 | -------------------------------------------------------------------------------- /sample-app/config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Define an application-wide content security policy. 4 | # See the Securing Rails Applications Guide for more information: 5 | # https://guides.rubyonrails.org/security.html#content-security-policy-header 6 | 7 | # Rails.application.configure do 8 | # config.content_security_policy do |policy| 9 | # policy.default_src :self, :https 10 | # policy.font_src :self, :https, :data 11 | # policy.img_src :self, :https, :data 12 | # policy.object_src :none 13 | # policy.script_src :self, :https 14 | # policy.style_src :self, :https 15 | # # Specify URI for violation reports 16 | # # policy.report_uri "/csp-violation-report-endpoint" 17 | # end 18 | # 19 | # # Generate session nonces for permitted importmap, inline scripts, and inline styles. 20 | # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } 21 | # config.content_security_policy_nonce_directives = %w(script-src style-src) 22 | # 23 | # # Report violations without enforcing the policy. 24 | # # config.content_security_policy_report_only = true 25 | # end 26 | -------------------------------------------------------------------------------- /sample-app/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. 4 | # Use this to limit dissemination of sensitive information. 5 | # See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. 6 | Rails.application.config.filter_parameters += [ 7 | :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn 8 | ] 9 | -------------------------------------------------------------------------------- /sample-app/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, "\\1en" 8 | # inflect.singular /^(ox)en/i, "\\1" 9 | # inflect.irregular "person", "people" 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym "RESTful" 16 | # end 17 | -------------------------------------------------------------------------------- /sample-app/config/initializers/notifications.rb: -------------------------------------------------------------------------------- 1 | ActiveSupport::Notifications.subscribe(/[.]aws/) do |name, start, finish, id, _payload| 2 | Rails.logger.info "Got notification: #{name} #{start} #{finish} #{id}\n" 3 | end -------------------------------------------------------------------------------- /sample-app/config/initializers/permissions_policy.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Define an application-wide HTTP permissions policy. For further 4 | # information see: https://developers.google.com/web/updates/2018/06/feature-policy 5 | 6 | # Rails.application.config.permissions_policy do |policy| 7 | # policy.camera :none 8 | # policy.gyroscope :none 9 | # policy.microphone :none 10 | # policy.usb :none 11 | # policy.fullscreen :self 12 | # policy.payment :self, "https://secure.example.com" 13 | # end 14 | -------------------------------------------------------------------------------- /sample-app/config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | options = { key: '_sample_app_session' } 2 | Rails.application.config.session_store :dynamo_db_store, **options 3 | -------------------------------------------------------------------------------- /sample-app/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization and 2 | # are automatically loaded by Rails. If you want to use locales other than 3 | # English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t "hello" 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t("hello") %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more about the API, please read the Rails Internationalization guide 20 | # at https://guides.rubyonrails.org/i18n.html. 21 | # 22 | # Be aware that YAML interprets the following case-insensitive strings as 23 | # booleans: `true`, `false`, `on`, `off`, `yes`, `no`. Therefore, these strings 24 | # must be quoted to be interpreted as strings. For example: 25 | # 26 | # en: 27 | # "yes": yup 28 | # enabled: "ON" 29 | 30 | en: 31 | hello: "Hello world" 32 | -------------------------------------------------------------------------------- /sample-app/config/master.key: -------------------------------------------------------------------------------- 1 | 96114083fb4ae16aa696239a11cd7057 -------------------------------------------------------------------------------- /sample-app/config/puma.rb: -------------------------------------------------------------------------------- 1 | # This configuration file will be evaluated by Puma. The top-level methods that 2 | # are invoked here are part of Puma's configuration DSL. For more information 3 | # about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html. 4 | 5 | # Puma starts a configurable number of processes (workers) and each process 6 | # serves each request in a thread from an internal thread pool. 7 | # 8 | # The ideal number of threads per worker depends both on how much time the 9 | # application spends waiting for IO operations and on how much you wish to 10 | # to prioritize throughput over latency. 11 | # 12 | # As a rule of thumb, increasing the number of threads will increase how much 13 | # traffic a given process can handle (throughput), but due to CRuby's 14 | # Global VM Lock (GVL) it has diminishing returns and will degrade the 15 | # response time (latency) of the application. 16 | # 17 | # The default is set to 3 threads as it's deemed a decent compromise between 18 | # throughput and latency for the average Rails application. 19 | # 20 | # Any libraries that use a connection pool or another resource pool should 21 | # be configured to provide at least as many connections as the number of 22 | # threads. This includes Active Record's `pool` parameter in `database.yml`. 23 | threads_count = ENV.fetch("RAILS_MAX_THREADS", 3) 24 | threads threads_count, threads_count 25 | 26 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 27 | port ENV.fetch("PORT", 3000) 28 | 29 | # Allow puma to be restarted by `bin/rails restart` command. 30 | plugin :tmp_restart 31 | 32 | # Specify the PID file. Defaults to tmp/pids/server.pid in development. 33 | # In other environments, only set the PID file if requested. 34 | pidfile ENV["PIDFILE"] if ENV["PIDFILE"] 35 | -------------------------------------------------------------------------------- /sample-app/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | resources :users 3 | # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html 4 | 5 | # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500. 6 | # Can be used by load balancers and uptime monitors to verify that the app is live. 7 | get "up" => "rails/health#show", as: :rails_health_check 8 | 9 | # Render dynamic PWA files from app/views/pwa/* 10 | get "service-worker" => "rails/pwa#service_worker", as: :pwa_service_worker 11 | get "manifest" => "rails/pwa#manifest", as: :pwa_manifest 12 | 13 | # Defines the root path route ("/") 14 | # root "posts#index" 15 | 16 | # Active Job routes 17 | get '/queue_sqs_job', to: 'job#queue_sqs_job' 18 | get '/queue_sqs_async_job', to: 'job#queue_sqs_async_job' 19 | 20 | # Action Mailer routes 21 | get '/send_ses_email', to: 'mailer#send_ses_email' 22 | get '/send_ses_v2_email', to: 'mailer#send_ses_v2_email' 23 | end 24 | -------------------------------------------------------------------------------- /sample-app/config/storage.yml: -------------------------------------------------------------------------------- 1 | local: 2 | service: Disk 3 | root: <%= Rails.root.join("tmp/storage") %> 4 | -------------------------------------------------------------------------------- /sample-app/db/migrate/20241028193146_create_users.rb: -------------------------------------------------------------------------------- 1 | class CreateUsers < ActiveRecord::Migration[7.2] 2 | def change 3 | create_table :users do |t| 4 | t.string :email 5 | t.string :password_digest 6 | 7 | t.timestamps 8 | end 9 | add_index :users, :email, unique: true 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /sample-app/db/migrate/20241106152613_create_dynamo_db_sessions_table.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreateDynamoDbSessionsTable < ActiveRecord::Migration[7.2] 4 | def up 5 | options = Rails.application.config.session_options 6 | Aws::SessionStore::DynamoDB::Table.create_table(options) 7 | end 8 | 9 | def down 10 | options = Rails.application.config.session_options 11 | Aws::SessionStore::DynamoDB::Table.delete_table(options) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /sample-app/db/migrate/20241113202339_create_active_storage_tables.active_storage.rb: -------------------------------------------------------------------------------- 1 | # This migration comes from active_storage (originally 20170806125915) 2 | class CreateActiveStorageTables < ActiveRecord::Migration[7.0] 3 | def change 4 | # Use Active Record's configured type for primary and foreign keys 5 | primary_key_type, foreign_key_type = primary_and_foreign_key_types 6 | 7 | create_table :active_storage_blobs, id: primary_key_type do |t| 8 | t.string :key, null: false 9 | t.string :filename, null: false 10 | t.string :content_type 11 | t.text :metadata 12 | t.string :service_name, null: false 13 | t.bigint :byte_size, null: false 14 | t.string :checksum 15 | 16 | if connection.supports_datetime_with_precision? 17 | t.datetime :created_at, precision: 6, null: false 18 | else 19 | t.datetime :created_at, null: false 20 | end 21 | 22 | t.index [ :key ], unique: true 23 | end 24 | 25 | create_table :active_storage_attachments, id: primary_key_type do |t| 26 | t.string :name, null: false 27 | t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type 28 | t.references :blob, null: false, type: foreign_key_type 29 | 30 | if connection.supports_datetime_with_precision? 31 | t.datetime :created_at, precision: 6, null: false 32 | else 33 | t.datetime :created_at, null: false 34 | end 35 | 36 | t.index [ :record_type, :record_id, :name, :blob_id ], name: :index_active_storage_attachments_uniqueness, unique: true 37 | t.foreign_key :active_storage_blobs, column: :blob_id 38 | end 39 | 40 | create_table :active_storage_variant_records, id: primary_key_type do |t| 41 | t.belongs_to :blob, null: false, index: false, type: foreign_key_type 42 | t.string :variation_digest, null: false 43 | 44 | t.index [ :blob_id, :variation_digest ], name: :index_active_storage_variant_records_uniqueness, unique: true 45 | t.foreign_key :active_storage_blobs, column: :blob_id 46 | end 47 | end 48 | 49 | private 50 | def primary_and_foreign_key_types 51 | config = Rails.configuration.generators 52 | setting = config.options[config.orm][:primary_key_type] 53 | primary_key_type = setting || :primary_key 54 | foreign_key_type = setting || :bigint 55 | [ primary_key_type, foreign_key_type ] 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /sample-app/db/migrate/20241113202340_create_action_mailbox_tables.action_mailbox.rb: -------------------------------------------------------------------------------- 1 | # This migration comes from action_mailbox (originally 20180917164000) 2 | class CreateActionMailboxTables < ActiveRecord::Migration[6.0] 3 | def change 4 | create_table :action_mailbox_inbound_emails, id: primary_key_type do |t| 5 | t.integer :status, default: 0, null: false 6 | t.string :message_id, null: false 7 | t.string :message_checksum, null: false 8 | 9 | t.timestamps 10 | 11 | t.index [ :message_id, :message_checksum ], name: "index_action_mailbox_inbound_emails_uniqueness", unique: true 12 | end 13 | end 14 | 15 | private 16 | def primary_key_type 17 | config = Rails.configuration.generators 18 | config.options[config.orm][:primary_key_type] || :primary_key 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /sample-app/db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # This file is the source Rails uses to define your schema when running `bin/rails 6 | # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to 7 | # be faster and is potentially less error prone than running all of your 8 | # migrations from scratch. Old migrations may fail to apply correctly if those 9 | # migrations use external dependencies or application code. 10 | # 11 | # It's strongly recommended that you check this file into your version control system. 12 | 13 | ActiveRecord::Schema[7.2].define(version: 2024_11_13_202340) do 14 | create_table "action_mailbox_inbound_emails", force: :cascade do |t| 15 | t.integer "status", default: 0, null: false 16 | t.string "message_id", null: false 17 | t.string "message_checksum", null: false 18 | t.datetime "created_at", null: false 19 | t.datetime "updated_at", null: false 20 | t.index ["message_id", "message_checksum"], name: "index_action_mailbox_inbound_emails_uniqueness", unique: true 21 | end 22 | 23 | create_table "active_storage_attachments", force: :cascade do |t| 24 | t.string "name", null: false 25 | t.string "record_type", null: false 26 | t.bigint "record_id", null: false 27 | t.bigint "blob_id", null: false 28 | t.datetime "created_at", null: false 29 | t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" 30 | t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true 31 | end 32 | 33 | create_table "active_storage_blobs", force: :cascade do |t| 34 | t.string "key", null: false 35 | t.string "filename", null: false 36 | t.string "content_type" 37 | t.text "metadata" 38 | t.string "service_name", null: false 39 | t.bigint "byte_size", null: false 40 | t.string "checksum" 41 | t.datetime "created_at", null: false 42 | t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true 43 | end 44 | 45 | create_table "active_storage_variant_records", force: :cascade do |t| 46 | t.bigint "blob_id", null: false 47 | t.string "variation_digest", null: false 48 | t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true 49 | end 50 | 51 | create_table "users", force: :cascade do |t| 52 | t.string "email" 53 | t.string "password_digest" 54 | t.datetime "created_at", null: false 55 | t.datetime "updated_at", null: false 56 | t.index ["email"], name: "index_users_on_email", unique: true 57 | end 58 | 59 | add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" 60 | add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" 61 | end 62 | -------------------------------------------------------------------------------- /sample-app/db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should ensure the existence of records required to run the application in every environment (production, 2 | # development, test). The code here should be idempotent so that it can be executed at any point in every environment. 3 | # The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup). 4 | # 5 | # Example: 6 | # 7 | # ["Action", "Comedy", "Drama", "Horror"].each do |genre_name| 8 | # MovieGenre.find_or_create_by!(name: genre_name) 9 | # end 10 | -------------------------------------------------------------------------------- /sample-app/lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-sdk-rails/dec6705b457e1523bdc20caf642330c6b65eef90/sample-app/lib/assets/.keep -------------------------------------------------------------------------------- /sample-app/lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-sdk-rails/dec6705b457e1523bdc20caf642330c6b65eef90/sample-app/lib/tasks/.keep -------------------------------------------------------------------------------- /sample-app/log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-sdk-rails/dec6705b457e1523bdc20caf642330c6b65eef90/sample-app/log/.keep -------------------------------------------------------------------------------- /sample-app/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

You may have mistyped the address or the page may have moved.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /sample-app/public/406-unsupported-browser.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Your browser is not supported (406) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

Your browser is not supported.

62 |

Please upgrade your browser to continue.

63 |
64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /sample-app/public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

Maybe you tried to change something you didn't have access to.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /sample-app/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /sample-app/public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-sdk-rails/dec6705b457e1523bdc20caf642330c6b65eef90/sample-app/public/icon.png -------------------------------------------------------------------------------- /sample-app/public/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /sample-app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /sample-app/storage/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-sdk-rails/dec6705b457e1523bdc20caf642330c6b65eef90/sample-app/storage/.keep -------------------------------------------------------------------------------- /sample-app/storage/development.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-sdk-rails/dec6705b457e1523bdc20caf642330c6b65eef90/sample-app/storage/development.sqlite3 -------------------------------------------------------------------------------- /sample-app/test/controllers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-sdk-rails/dec6705b457e1523bdc20caf642330c6b65eef90/sample-app/test/controllers/.keep -------------------------------------------------------------------------------- /sample-app/test/controllers/job_controller_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class JobControllerTest < ActionDispatch::IntegrationTest 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /sample-app/test/controllers/mailer_controller_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class MailerControllerTest < ActionDispatch::IntegrationTest 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /sample-app/test/controllers/users_controller_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class UsersControllerTest < ActionDispatch::IntegrationTest 4 | setup do 5 | @user = users(:one) 6 | end 7 | 8 | test "should get index" do 9 | get users_url 10 | assert_response :success 11 | end 12 | 13 | test "should get new" do 14 | get new_user_url 15 | assert_response :success 16 | end 17 | 18 | test "should create user" do 19 | assert_difference("User.count") do 20 | post users_url, params: { user: { email: @user.email, password: "secret", password_confirmation: "secret" } } 21 | end 22 | 23 | assert_redirected_to user_url(User.last) 24 | end 25 | 26 | test "should show user" do 27 | get user_url(@user) 28 | assert_response :success 29 | end 30 | 31 | test "should get edit" do 32 | get edit_user_url(@user) 33 | assert_response :success 34 | end 35 | 36 | test "should update user" do 37 | patch user_url(@user), params: { user: { email: @user.email, password: "secret", password_confirmation: "secret" } } 38 | assert_redirected_to user_url(@user) 39 | end 40 | 41 | test "should destroy user" do 42 | assert_difference("User.count", -1) do 43 | delete user_url(@user) 44 | end 45 | 46 | assert_redirected_to users_url 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /sample-app/test/fixtures/files/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-sdk-rails/dec6705b457e1523bdc20caf642330c6b65eef90/sample-app/test/fixtures/files/.keep -------------------------------------------------------------------------------- /sample-app/test/fixtures/users.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | email: MyString 5 | password_digest: <%= BCrypt::Password.create("secret") %> 6 | 7 | two: 8 | email: MyString 9 | password_digest: <%= BCrypt::Password.create("secret") %> 10 | -------------------------------------------------------------------------------- /sample-app/test/helpers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-sdk-rails/dec6705b457e1523bdc20caf642330c6b65eef90/sample-app/test/helpers/.keep -------------------------------------------------------------------------------- /sample-app/test/integration/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-sdk-rails/dec6705b457e1523bdc20caf642330c6b65eef90/sample-app/test/integration/.keep -------------------------------------------------------------------------------- /sample-app/test/jobs/test_job_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class TestJobTest < ActiveJob::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /sample-app/test/mailboxes/test_mailbox_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class TestMailboxTest < ActionMailbox::TestCase 4 | # test "receive mail" do 5 | # receive_inbound_email_from_mail \ 6 | # to: '"someone" ', 7 | # from: '"else" ', 8 | # subject: "Hello world!", 9 | # body: "Hello?" 10 | # end 11 | end 12 | -------------------------------------------------------------------------------- /sample-app/test/mailers/previews/test_mailer_preview.rb: -------------------------------------------------------------------------------- 1 | # Preview all emails at http://localhost:3000/rails/mailers/test_mailer 2 | class TestMailerPreview < ActionMailer::Preview 3 | end 4 | -------------------------------------------------------------------------------- /sample-app/test/mailers/test_mailer_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class TestMailerTest < ActionMailer::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /sample-app/test/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-sdk-rails/dec6705b457e1523bdc20caf642330c6b65eef90/sample-app/test/models/.keep -------------------------------------------------------------------------------- /sample-app/test/models/user_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class UserTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /sample-app/test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV["RAILS_ENV"] ||= "test" 2 | require_relative "../config/environment" 3 | require "rails/test_help" 4 | 5 | module ActiveSupport 6 | class TestCase 7 | # Run tests in parallel with specified workers 8 | parallelize(workers: :number_of_processors) 9 | 10 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. 11 | fixtures :all 12 | 13 | # Add more helper methods to be used by all tests here... 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /sample-app/tmp/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-sdk-rails/dec6705b457e1523bdc20caf642330c6b65eef90/sample-app/tmp/.keep -------------------------------------------------------------------------------- /sample-app/tmp/local_secret.txt: -------------------------------------------------------------------------------- 1 | 802c30bf4569dd8acc933d0e9a12bcceef0a46ccfd8227435958b094fe9ea8834f7770c751252d21717995e3fb0611e1db18b91894770771532e54620383ab14 -------------------------------------------------------------------------------- /sample-app/tmp/pids/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-sdk-rails/dec6705b457e1523bdc20caf642330c6b65eef90/sample-app/tmp/pids/.keep -------------------------------------------------------------------------------- /sample-app/tmp/restart.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-sdk-rails/dec6705b457e1523bdc20caf642330c6b65eef90/sample-app/tmp/restart.txt -------------------------------------------------------------------------------- /sample-app/tmp/storage/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-sdk-rails/dec6705b457e1523bdc20caf642330c6b65eef90/sample-app/tmp/storage/.keep -------------------------------------------------------------------------------- /sample-app/vendor/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-sdk-rails/dec6705b457e1523bdc20caf642330c6b65eef90/sample-app/vendor/.keep -------------------------------------------------------------------------------- /spec/aws/rails/middleware/elastic_beanstalk_sqsd_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Aws 4 | module Rails 5 | module Middleware 6 | describe ElasticBeanstalkSQSD do 7 | subject(:response) do 8 | mock_rack_env = create_mock_env 9 | test_middleware = described_class.new(mock_rack_app) 10 | test_middleware.call(mock_rack_env) 11 | end 12 | 13 | # Simple mock Rack app that always returns 200 14 | let(:mock_rack_app) { ->(_) { [200, { 'Content-Type' => 'text/plain' }, ['OK']] } } 15 | 16 | let(:logger) { double(error: nil, debug: nil, warn: nil) } 17 | let(:user_agent) { 'aws-sqsd/1.1' } 18 | let(:remote_ip) { '127.0.0.1' } 19 | let(:remote_addr) { nil } 20 | let(:is_periodic_task) { nil } 21 | let(:period_task_name) { 'ElasticBeanstalkPeriodicTask' } 22 | 23 | before do 24 | allow(File).to receive(:exist?).and_call_original 25 | allow(File).to receive(:open).and_call_original 26 | end 27 | 28 | shared_examples_for 'passes request through' do 29 | it 'passes request' do 30 | expect(response[0]).to eq(200) 31 | expect(response[2]).to eq(['OK']) 32 | end 33 | end 34 | 35 | shared_examples_for 'runs job' do 36 | it 'invokes job' do 37 | expect(response[0]).to eq(200) 38 | expect(response[2]).to eq(['Successfully ran job ElasticBeanstalkJob.']) 39 | end 40 | 41 | it 'returns internal server error if job name cannot be resolved' do 42 | # Stub execute call to avoid invoking Active Job callbacks 43 | # Local testing indicates this failure results in a NameError 44 | allow(::ActiveJob::Base).to receive(:execute).and_raise(NameError) 45 | 46 | expect(response[0]).to eq(500) 47 | end 48 | 49 | context 'when user-agent is not sqs daemon' do 50 | let(:user_agent) { 'not-aws-sqsd' } 51 | 52 | include_examples 'passes request through' 53 | end 54 | 55 | context 'when periodic task' do 56 | let(:is_periodic_task) { true } 57 | 58 | it 'successfully invokes periodic task when passed through custom header' do 59 | expect(response[0]).to eq(200) 60 | expect(response[1]['Content-Type']).to eq('text/plain') 61 | expect(response[2]).to eq(['Successfully ran periodic task ElasticBeanstalkPeriodicTask.']) 62 | end 63 | 64 | context 'when unknown periodic task name' do 65 | let(:period_task_name) { 'NonExistentTask' } 66 | 67 | it 'returns internal server error' do 68 | expect(response[0]).to eq(500) 69 | end 70 | end 71 | end 72 | end 73 | 74 | shared_examples_for 'is forbidden' do 75 | it 'passes request' do 76 | expect(response[0]).to eq(403) 77 | end 78 | 79 | context 'when user-agent is not sqs daemon' do 80 | let(:user_agent) { 'not-aws-sqsd' } 81 | 82 | include_examples 'passes request through' 83 | end 84 | end 85 | 86 | context 'when local IP' do 87 | let(:remote_ip) { '127.0.0.1' } 88 | 89 | include_examples 'runs job' 90 | end 91 | 92 | context 'when ::1 IP' do 93 | let(:remote_ip) { '::1' } 94 | 95 | include_examples 'runs job' 96 | end 97 | 98 | context 'when non-local IP' do 99 | let(:remote_ip) { '1.2.3.4' } 100 | 101 | include_examples 'is forbidden' 102 | end 103 | 104 | shared_examples_for 'is valid in either cgroup1 or cgroup2' do 105 | context 'when not in a docker container' do 106 | before { stub_runs_in_neither_docker_container } 107 | 108 | include_examples 'is forbidden' 109 | end 110 | 111 | context 'when docker container cgroup1' do 112 | before { stub_runs_in_docker_container_cgroup1 } 113 | 114 | include_examples 'runs job' 115 | end 116 | 117 | context 'when docker container cgroup2' do 118 | before { stub_runs_in_docker_container_cgroup2 } 119 | 120 | include_examples 'runs job' 121 | end 122 | end 123 | 124 | shared_examples_for 'is invalid in either cgroup1 or cgroup2' do 125 | context 'when not in a docker container' do 126 | before { stub_runs_in_neither_docker_container } 127 | 128 | include_examples 'is forbidden' 129 | end 130 | 131 | context 'when docker container cgroup1' do 132 | before { stub_runs_in_docker_container_cgroup1 } 133 | 134 | include_examples 'is forbidden' 135 | end 136 | 137 | context 'when docker container cgroup2' do 138 | before { stub_runs_in_docker_container_cgroup2 } 139 | 140 | include_examples 'is forbidden' 141 | end 142 | end 143 | 144 | context 'when remote ip is invalid, but remote_addr is docker gw' do 145 | let(:remote_addr) { '172.17.0.1' } 146 | let(:remote_ip) { '192.168.176.1' } 147 | 148 | include_examples 'is valid in either cgroup1 or cgroup2' 149 | 150 | it 'successfully invokes job when /proc/net/route does not exist' do 151 | expect(File).to receive(:exist?).with('/proc/net/route').and_return(false) 152 | 153 | stub_runs_in_docker_container_cgroup2 154 | 155 | expect(response[0]).to eq(200) 156 | expect(response[1]['Content-Type']).to eq('text/plain') 157 | expect(response[2]).to eq(['Successfully ran job ElasticBeanstalkJob.']) 158 | end 159 | end 160 | 161 | context 'when remote addr is non-standard ip but in /proc/net/route' do 162 | let(:remote_addr) { '192.168.176.1' } 163 | 164 | before do 165 | proc_net_route = <<~CONTENT 166 | Iface\tDestination\tGateway\tFlags\tRefCnt\tUse\tMetric\tMask\tMTU\tWindow\tIRTT 167 | eth0\t00000000\t01B0A8C0\t0003\t0\t0\t0\t00000000\t0\t0\t0 168 | eth0\t00B0A8C0\t00000000\t0001\t0\t0\t0\t00F0FFFF\t0\t0\t0 169 | CONTENT 170 | 171 | allow(File).to receive(:exist?).with('/proc/net/route').and_return(true) 172 | allow(File).to receive(:open).with('/proc/net/route').and_return(StringIO.new(proc_net_route)) 173 | end 174 | 175 | include_examples 'is valid in either cgroup1 or cgroup2' 176 | end 177 | 178 | context 'when remote ip is non-standard ip but in /proc/net/route' do 179 | let(:remote_ip) { '192.168.176.1' } 180 | 181 | before do 182 | proc_net_route = <<~CONTENT 183 | Iface\tDestination\tGateway\tFlags\tRefCnt\tUse\tMetric\tMask\tMTU\tWindow\tIRTT 184 | eth0\t00000000\t01B0A8C0\t0003\t0\t0\t0\t00000000\t0\t0\t0 185 | eth0\t00B0A8C0\t00000000\t0001\t0\t0\t0\t00F0FFFF\t0\t0\t0 186 | CONTENT 187 | 188 | allow(File).to receive(:exist?).with('/proc/net/route').and_return(true) 189 | allow(File).to receive(:open).with('/proc/net/route').and_return(StringIO.new(proc_net_route)) 190 | end 191 | 192 | include_examples 'is valid in either cgroup1 or cgroup2' 193 | end 194 | 195 | context 'when remote addr is non-standard ip but not in /proc/net/route' do 196 | let(:remote_addr) { '192.168.176.1' } 197 | 198 | before do 199 | proc_net_route = <<~CONTENT 200 | Iface\tDestination\tGateway\tFlags\tRefCnt\tUse\tMetric\tMask\tMTU\tWindow\tIRTT 201 | CONTENT 202 | 203 | allow(File).to receive(:exist?).with('/proc/net/route').and_return(true) 204 | allow(File).to receive(:open).with('/proc/net/route').and_return(StringIO.new(proc_net_route)) 205 | end 206 | 207 | include_examples 'is invalid in either cgroup1 or cgroup2' 208 | end 209 | 210 | context 'when remote ip is default docker gw' do 211 | let(:remote_ip) { '172.17.0.1' } 212 | 213 | include_examples 'is valid in either cgroup1 or cgroup2' 214 | end 215 | 216 | context 'when remote addr is default docker gw' do 217 | let(:remote_addr) { '172.17.0.1' } 218 | 219 | include_examples 'is valid in either cgroup1 or cgroup2' 220 | end 221 | 222 | context 'when AWS_PROCESS_BEANSTALK_WORKER_JOBS_ASYNC' do 223 | before(:each) do 224 | ENV['AWS_PROCESS_BEANSTALK_WORKER_JOBS_ASYNC'] = 'true' 225 | end 226 | 227 | after(:each) do 228 | ENV.delete('AWS_PROCESS_BEANSTALK_WORKER_JOBS_ASYNC') 229 | end 230 | 231 | it 'queues job' do 232 | expect_any_instance_of(Concurrent::ThreadPoolExecutor).to receive(:post) 233 | expect(response[0]).to eq(200) 234 | expect(response[2]).to eq(['Successfully queued job ElasticBeanstalkJob']) 235 | end 236 | 237 | context 'no capacity' do 238 | it 'returns too many requests error' do 239 | allow_any_instance_of(Concurrent::ThreadPoolExecutor).to receive(:post) 240 | .and_raise Concurrent::RejectedExecutionError 241 | 242 | expect(response[0]).to eq(429) 243 | end 244 | end 245 | 246 | context 'periodic task' do 247 | let(:is_periodic_task) { true } 248 | 249 | it 'queues job' do 250 | expect_any_instance_of(Concurrent::ThreadPoolExecutor).to receive(:post) 251 | expect(response[0]).to eq(200) 252 | expect(response[2]).to eq(['Successfully queued periodic task ElasticBeanstalkPeriodicTask']) 253 | end 254 | 255 | context 'no capacity' do 256 | it 'returns too many requests error' do 257 | allow_any_instance_of(Concurrent::ThreadPoolExecutor).to receive(:post) 258 | .and_raise Concurrent::RejectedExecutionError 259 | 260 | expect(response[0]).to eq(429) 261 | end 262 | end 263 | end 264 | end 265 | 266 | def stub_runs_in_neither_docker_container 267 | proc_1_cgroup = <<~CONTENT 268 | 0::/ 269 | CONTENT 270 | 271 | proc_self_mountinfo = <<~CONTENT 272 | 355 354 0:21 / /sys/fs/cgroup ro,nosuid,nodev,noexec,relatime - cgroup2 cgroup rw,nsdelegate 273 | 356 352 0:74 / /dev/mqueue rw,nosuid,nodev,noexec,relatime - mqueue mqueue rw 274 | 357 352 0:79 / /dev/shm rw,nosuid,nodev,noexec,relatime - tmpfs shm rw,size=65536k 275 | 316 352 0:77 /0 /dev/console rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666 276 | CONTENT 277 | 278 | allow(File).to receive(:exist?).with('/proc/1/cgroup').and_return(true) 279 | allow(File).to receive(:read).with('/proc/1/cgroup').and_return(proc_1_cgroup) 280 | allow(File).to receive(:exist?).with('/proc/self/mountinfo').and_return(true) 281 | allow(File).to receive(:read).with('/proc/self/mountinfo').and_return(proc_self_mountinfo) 282 | end 283 | 284 | def stub_runs_in_docker_container_cgroup1 285 | proc_1_cgroup = <<~CONTENT 286 | 13:rdma:/docker/d59538e9b3d3aa6012f08587c13199cbad3f882ecaa9637905971df18ab89757 287 | 12:hugetlb:/docker/d59538e9b3d3aa6012f08587c13199cbad3f882ecaa9637905971df18ab89757 288 | 11:memory:/docker/d59538e9b3d3aa6012f08587c13199cbad3f882ecaa9637905971df18ab89757 289 | 10:devices:/docker/d59538e9b3d3aa6012f08587c13199cbad3f882ecaa9637905971df18ab89757 290 | 9:blkio:/docker/d59538e9b3d3aa6012f08587c13199cbad3f882ecaa9637905971df18ab89757 291 | CONTENT 292 | allow(File).to receive(:exist?).with('/proc/1/cgroup').and_return(true) 293 | allow(File).to receive(:read).with('/proc/1/cgroup').and_return(proc_1_cgroup) 294 | end 295 | 296 | def stub_runs_in_docker_container_cgroup2 297 | proc_1_cgroup = <<~CONTENT 298 | 0::/ 299 | CONTENT 300 | 301 | proc_self_mountinfo = <<~CONTENT 302 | 355 354 0:21 / /sys/fs/cgroup ro,nosuid,nodev,noexec,relatime - cgroup2 cgroup rw,nsdelegate 303 | 356 352 0:74 / /dev/mqueue rw,nosuid,nodev,noexec,relatime - mqueue mqueue rw 304 | 357 352 0:79 / /dev/shm rw,nosuid,nodev,noexec,relatime - tmpfs shm rw,size=65536k 305 | 358 350 8:16 /var/lib/docker/containers/69e3febd00ac4720d2ea58c935574776285f6a0016d2aa30b0c280a81c385e69/resolv.conf /etc/resolv.conf rw,relatime - ext4 /dev/sdb rw,discard,errors=remount-ro,data=ordered 306 | 359 350 8:16 /var/lib/docker/containers/69e3febd00ac4720d2ea58c935574776285f6a0016d2aa30b0c280a81c385e69/hostname /etc/hostname rw,relatime - ext4 /dev/sdb rw,discard,errors=remount-ro,data=ordered 307 | 360 350 8:16 /var/lib/docker/containers/69e3febd00ac4720d2ea58c935574776285f6a0016d2aa30b0c280a81c385e69/hosts /etc/hosts rw,relatime - ext4 /dev/sdb rw,discard,errors=remount-ro,data=ordered 308 | 316 352 0:77 /0 /dev/console rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666 309 | CONTENT 310 | 311 | allow(File).to receive(:exist?).with('/proc/1/cgroup').and_return(true) 312 | allow(File).to receive(:read).with('/proc/1/cgroup').and_return(proc_1_cgroup) 313 | allow(File).to receive(:exist?).with('/proc/self/mountinfo').and_return(true) 314 | allow(File).to receive(:read).with('/proc/self/mountinfo').and_return(proc_self_mountinfo) 315 | end 316 | 317 | # Create a minimal mock Rack environment hash to test just what we need 318 | def create_mock_env 319 | mock_env = { 320 | 'HTTP_X_FORWARDED_FOR' => remote_ip, 321 | 'REMOTE_ADDR' => remote_addr || remote_ip, 322 | 'HTTP_USER_AGENT' => user_agent 323 | } 324 | 325 | if is_periodic_task 326 | mock_env['PATH_INFO'] = '/' 327 | mock_env['HTTP_X_AWS_SQSD_TASKNAME'] = period_task_name 328 | else 329 | mock_env['rack.input'] = StringIO.new('{"job_class": "ElasticBeanstalkJob"}') 330 | end 331 | 332 | mock_env 333 | end 334 | end 335 | end 336 | end 337 | end 338 | -------------------------------------------------------------------------------- /spec/aws/rails/notifications_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Aws 4 | module Rails 5 | describe Notifications do 6 | let(:client) do 7 | Aws::STS::Client.new(stub_responses: true) 8 | end 9 | 10 | it 'adds instrumentation on each call' do 11 | out = {} 12 | ActiveSupport::Notifications.subscribe(/aws/) do |name, _start, _finish, _id, payload| 13 | out[:name] = name 14 | out[:payload] = payload 15 | end 16 | client.get_caller_identity 17 | expect(out[:name]).to eq('get_caller_identity.STS.aws') 18 | expect(out[:payload][:context]).to be_a(Seahorse::Client::RequestContext) 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/aws/rails/railtie_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Aws 4 | module Rails 5 | describe 'Railtie' do 6 | it 'uses aws credentials from rails encrypted credentials' do 7 | rails_creds = ::Rails.application.credentials.aws 8 | expect(Aws.config[:access_key_id]).to eq rails_creds[:access_key_id] 9 | expect(Aws.config[:secret_access_key]).to eq rails_creds[:secret_access_key] 10 | expect(Aws.config[:session_token]).to eq rails_creds[:session_token] 11 | expect(Aws.config[:account_id]).to eq rails_creds[:account_id] 12 | 13 | expect(rails_creds[:something]).not_to be_nil 14 | expect(Aws.config[:something]).to be_nil 15 | end 16 | 17 | it 'sets the Rails logger to Aws global config' do 18 | expect(Aws.config[:logger]).to eq ::Rails.logger 19 | end 20 | 21 | it 'sets up eager loading for sdk services' do 22 | expect(Aws.methods).to include(:eager_load!) 23 | expect(::Rails.application.config.eager_load_namespaces).to include(Aws) 24 | end 25 | 26 | it 'adds the Notifications plugin to sdk clients' do 27 | expect(Aws::STS::Client.plugins).to include(Aws::Rails::Notifications) 28 | expect(Aws::NotService::Client.plugins).not_to include(Aws::Rails::Notifications) 29 | expect(Aws::Client.plugins).not_to include(Aws::Rails::Notifications) 30 | end 31 | 32 | context 'sqsd middleware' do 33 | describe 'AWS_PROCESS_BEANSTALK_WORKER_REQUESTS is set' do 34 | before do 35 | ENV['AWS_PROCESS_BEANSTALK_WORKER_REQUESTS'] = 'true' 36 | end 37 | 38 | after do 39 | ENV.delete('AWS_PROCESS_BEANSTALK_WORKER_REQUESTS') 40 | end 41 | 42 | it 'adds the middleware' do 43 | mock_rails_app = double 44 | mock_middleware_stack = [] 45 | 46 | allow(mock_rails_app).to receive_message_chain(:config, :middleware, :use) do |middleware| 47 | mock_middleware_stack.push(middleware) 48 | end 49 | allow(mock_rails_app).to receive_message_chain(:config, :force_ssl).and_return(false) 50 | 51 | Aws::Rails.add_sqsd_middleware(mock_rails_app) 52 | 53 | expect(mock_middleware_stack.count).to eq(1) 54 | expect(mock_middleware_stack[0].inspect).to eq('Aws::Rails::Middleware::ElasticBeanstalkSQSD') 55 | end 56 | 57 | it 'adds the middleware before SSL when force_ssl is true' do 58 | mock_rails_app = double 59 | mock_middleware_stack = [] 60 | 61 | allow(mock_rails_app).to receive_message_chain(:config, :middleware, 62 | :insert_before) do |_before, middleware| 63 | mock_middleware_stack.push(middleware) 64 | end 65 | allow(mock_rails_app).to receive_message_chain(:config, :force_ssl).and_return(true) 66 | 67 | Aws::Rails.add_sqsd_middleware(mock_rails_app) 68 | 69 | expect(mock_middleware_stack.count).to eq(1) 70 | expect(mock_middleware_stack[0].inspect).to eq('Aws::Rails::Middleware::ElasticBeanstalkSQSD') 71 | end 72 | end 73 | 74 | describe 'AWS_PROCESS_BEANSTALK_WORKER_REQUESTS is not set' do 75 | it 'does not add the middleware' do 76 | mock_rails_app = double 77 | mock_middleware_stack = [] 78 | 79 | allow(mock_rails_app).to receive_message_chain(:config, :middleware, :use) do |middleware| 80 | mock_middleware_stack.push(middleware) 81 | end 82 | allow(mock_rails_app).to receive_message_chain(:config, :force_ssl).and_return(false) 83 | 84 | Aws::Rails.add_sqsd_middleware(mock_rails_app) 85 | 86 | expect(mock_middleware_stack.count).to eq(0) 87 | end 88 | end 89 | end 90 | end 91 | end 92 | end 93 | -------------------------------------------------------------------------------- /spec/dummy/Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'config/application' 4 | 5 | Rails.application.load_tasks 6 | -------------------------------------------------------------------------------- /spec/dummy/app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationJob < ActiveJob::Base 4 | end 5 | -------------------------------------------------------------------------------- /spec/dummy/app/jobs/elastic_beanstalk_job.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ElasticBeanstalkJob < ApplicationJob 4 | queue_as :default 5 | 6 | def perform(); end 7 | end 8 | -------------------------------------------------------------------------------- /spec/dummy/app/jobs/elastic_beanstalk_periodic_task.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ElasticBeanstalkPeriodicTask < ApplicationJob 4 | queue_as :default 5 | 6 | def perform(); end 7 | end 8 | -------------------------------------------------------------------------------- /spec/dummy/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | APP_PATH = File.expand_path('../config/application', __dir__) 5 | require_relative '../config/boot' 6 | require 'rails/commands' 7 | -------------------------------------------------------------------------------- /spec/dummy/config.ru: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This file indicates the Rails root directory 4 | -------------------------------------------------------------------------------- /spec/dummy/config/application.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails' 4 | require 'action_mailer/railtie' 5 | require 'action_controller/railtie' 6 | 7 | require 'aws-sdk-rails' 8 | 9 | module Dummy 10 | class Application < Rails::Application 11 | config.load_defaults Rails::VERSION::STRING.to_f 12 | config.eager_load = true 13 | config.require_master_key = true 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/dummy/config/boot.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler/setup' 4 | -------------------------------------------------------------------------------- /spec/dummy/config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | pkQaZPRp3yxY1rWeVzGsqykFx7a/5dW1hrwfcX0koxO2r3ptyVyyhweXLe/422aay01kw8U109//PSig093Mwak8I7ucC3vBfKWeFpHwAcVmS8Mz8sWRC4AcBnh1S/VzmVXkr9eRb827VRVeLWoig9CmsrL5TAaIDC5n7BOtUYrYyHac6myGALYbLx8Kw93pmA3ty0HZ1ScV7gSDSfmWT0ggQv41m0upFBAIv6q94FkCe9oqmHALl9ZEEx8jsBirrZj3vrAexoeJwB6+5FHgFXAVPTafzqTD/V9wHN7BHqmnJXJv1088KnwYlqs5wzWokQ3ZW0hcHtriJIHC1sFTjUA0nuqOfbo4vI9hETq2A0audMmcpcydzzmN0JOQq5gy9zIh20t3+cB1B0hl4yXeWkg79qirbvMNSUFiXlReJ65FaeYu8wMgYJy9PvLfUkJncqY9CE8IWfjDDTT6NmVHtQaYmXGtBhcGH20lK8ioniVYE7wy+RJ8v6jQPXk=--g5eHeIjSoliyk0oZ--Z4JLfrw59cQg0Ms0jth9lQ== -------------------------------------------------------------------------------- /spec/dummy/config/environment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Load the Rails application. 4 | require_relative 'application' 5 | 6 | # Initialize the Rails application. 7 | Rails.application.initialize! 8 | -------------------------------------------------------------------------------- /spec/dummy/config/master.key: -------------------------------------------------------------------------------- 1 | d05a9aac81dfc7b1a70c29d6f417afca -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | ENV['RAILS_ENV'] = 'test' 4 | 5 | # Test services for Notifications 6 | # Must exist before initializing the Rails application 7 | module Aws 8 | module NotService 9 | class Client 10 | def self.plugins 11 | [] 12 | end 13 | end 14 | end 15 | 16 | class Client 17 | def self.plugins 18 | [] 19 | end 20 | end 21 | end 22 | 23 | require_relative 'dummy/config/application' 24 | 25 | Rails.application.initialize! 26 | 27 | require 'rspec' 28 | 29 | # This file was generated by the `rspec --init` command. Conventionally, all 30 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 31 | # The generated `.rspec` file contains `--require spec_helper` which will cause 32 | # this file to always be loaded, without a need to explicitly require it in any 33 | # files. 34 | # 35 | # Given that it is always loaded, you are encouraged to keep this file as 36 | # light-weight as possible. Requiring heavyweight dependencies from this file 37 | # will add to the boot time of your test suite on EVERY test run, even for an 38 | # individual file that may not need all of that loaded. Instead, consider making 39 | # a separate helper file that requires the additional dependencies and performs 40 | # the additional setup, and require it from the spec files that actually need 41 | # it. 42 | # 43 | # See https://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 44 | RSpec.configure do |config| 45 | # rspec-expectations config goes here. You can use an alternate 46 | # assertion/expectation library such as wrong or the stdlib/minitest 47 | # assertions if you prefer. 48 | config.expect_with :rspec do |expectations| 49 | # This option will default to `true` in RSpec 4. It makes the `description` 50 | # and `failure_message` of custom matchers include text for helper methods 51 | # defined using `chain`, e.g.: 52 | # be_bigger_than(2).and_smaller_than(4).description 53 | # # => "be bigger than 2 and smaller than 4" 54 | # ...rather than: 55 | # # => "be bigger than 2" 56 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 57 | end 58 | 59 | # rspec-mocks config goes here. You can use an alternate test double 60 | # library (such as bogus or mocha) by changing the `mock_with` option here. 61 | config.mock_with :rspec do |mocks| 62 | # Prevents you from mocking or stubbing a method that does not exist on 63 | # a real object. This is generally recommended, and will default to 64 | # `true` in RSpec 4. 65 | mocks.verify_partial_doubles = true 66 | end 67 | 68 | # This option will default to `:apply_to_host_groups` in RSpec 4 (and will 69 | # have no way to turn it off -- the option exists only for backwards 70 | # compatibility in RSpec 3). It causes shared context metadata to be 71 | # inherited by the metadata hash of host groups and examples, rather than 72 | # triggering implicit auto-inclusion in groups with matching metadata. 73 | config.shared_context_metadata_behavior = :apply_to_host_groups 74 | 75 | # The settings below are suggested to provide a good initial experience 76 | # with RSpec, but feel free to customize to your heart's content. 77 | =begin 78 | # This allows you to limit a spec run to individual examples or groups 79 | # you care about by tagging them with `:focus` metadata. When nothing 80 | # is tagged with `:focus`, all examples get run. RSpec also provides 81 | # aliases for `it`, `describe`, and `context` that include `:focus` 82 | # metadata: `fit`, `fdescribe` and `fcontext`, respectively. 83 | config.filter_run_when_matching :focus 84 | 85 | # Allows RSpec to persist some state between runs in order to support 86 | # the `--only-failures` and `--next-failure` CLI options. We recommend 87 | # you configure your source control system to ignore this file. 88 | config.example_status_persistence_file_path = "spec/examples.txt" 89 | 90 | # Limits the available syntax to the non-monkey patched syntax that is 91 | # recommended. For more details, see: 92 | # https://rspec.info/features/3-12/rspec-core/configuration/zero-monkey-patching-mode/ 93 | config.disable_monkey_patching! 94 | 95 | # This setting enables warnings. It's recommended, but in some cases may 96 | # be too noisy due to issues in dependencies. 97 | config.warnings = true 98 | 99 | # Many RSpec users commonly either run the entire suite or an individual 100 | # file, and it's useful to allow more verbose output when running an 101 | # individual spec file. 102 | if config.files_to_run.one? 103 | # Use the documentation formatter for detailed output, 104 | # unless a formatter has already been configured 105 | # (e.g. via a command-line flag). 106 | config.default_formatter = "doc" 107 | end 108 | 109 | # Print the 10 slowest examples and example groups at the 110 | # end of the spec run, to help surface which specs are running 111 | # particularly slow. 112 | config.profile_examples = 10 113 | 114 | # Run specs in random order to surface order dependencies. If you find an 115 | # order dependency and want to debug it, you can fix the order by providing 116 | # the seed, which is printed after each run. 117 | # --seed 1234 118 | config.order = :random 119 | 120 | # Seed global randomization in this process using the `--seed` CLI option. 121 | # Setting this allows you to use `--seed` to deterministically reproduce 122 | # test failures related to randomization by passing the same `--seed` value 123 | # as the one that triggered the failure. 124 | Kernel.srand config.seed 125 | =end 126 | end 127 | --------------------------------------------------------------------------------