Upgrade to Pro — share decks privately, control downloads, hide ads and more …

RailsConf 2024: Ruby & Rails Versioning at Scale

RailsConf 2024: Ruby & Rails Versioning at Scale

In this talk we will dive into how Shopify automated Rails upgrades as well as leveraged Dependabot, Rubocop, and Bundler to easily upgrade and maintain 300+ Ruby Services at Shopify to be on the latest Ruby & Rails versions. You'll learn how you can do the same!

Avatar for George Ma

George Ma

May 13, 2024
Tweet

More Decks by George Ma

Other Decks in Programming

Transcript

  1. Overview Context & Motivations Rails upgrade process, version system 1

    2 Upgrade Schedule Benefits of a planned upgrade calendar 3 Rails Upgrades at Scale Streamlining Rails upgrades and how you can too 4 Ruby Upgrades at Scale Contributions to OSS and utilizing .ruby-version
  2. Everyone will say they’re from Toronto out of convenience Being

    “from” Toronto 2 Don’t pronounce the “T”! “Toron-o” Pronunciation 1 2 Tips Visiting Toronto
  3. Rails X.Y.Z Major Minor Patch 6.1.4 7.1.3.1 7.0.5 🔐CVE Common

    Vulnerabilities Exposures ex. CVE-2024-26143 Context (1)
  4. # Gemfile gem 'simplecov', require: false, group: :test # test/test_helper.rb

    (or spec_helper.rb) require "simplecov" SimpleCov.start "rails" Prerequisite: Have a test suite with good coverage
  5. Rails Upgrade Process Rails X.Y.Z Major Minor Patch Pre-req: Have

    a test suite with good coverage 1. Upgrade to the latest patch version 6.1.7 Start: 6.1.5 2. Fix deprecations, get CI passing ✓ 6.1.7 3. Upgrade to the next major/minor version 7.0.8 4a. Run bin/rails app:update task
  6. 544 >25% 110 Apps at Shopify that use Rails Apps

    that were pinged to upgrade to Rails 7.0 of the upgrades forgot a step/detail
  7. Usually an update notification comes out of the blue and

    disrupts your flow Unexpectedness Lack of Perceived Benefits 2 1 Psychological Blockers Most of the time it’s unclear what benefits there are to gain: “why bother?”
  8. 1. Upgrade to the latest patch version 2. Fix deprecations,

    get CI passing ✓ 3. Upgrade to the next major/minor version 4. Run bin/rails app:update task 5. Bump the load_defaults value Rails Upgrade Process Pre-req: Have a test suite with good coverage
  9. 544 >25% 110 Apps at Shopify that use Rails Apps

    that were pinged to upgrade to Rails 7.0 of the upgrades forgot a step/detail
  10. Unsustainable at Scale 1 Upgrade Speed Difficult to time box

    upgrades with 20+ versions in production 2 Security Old versions of Rails are unsupported 3 Developer Happiness Better to have a up-to-date throughout the company
  11. Simplifying the Upgrade Process Would… Allow for timely upgrades Take

    advantage of performance improvements / new features Remove the learning curve Prevent forgotten details / missed steps
  12. Usually an update notification comes out of the blue and

    disrupts your flow Unexpectedness Lack of Perceived Benefits 2 1 Psychological Blockers Most of the time it’s unclear what benefits there are to gain: “why bother?”
  13. Usually an update notification comes out of the blue and

    disrupts your flow Unexpectedness 1 Predictability
  14. What happens to apps that are not upgraded? Apps central

    to the business Escalation to the app teams’ VP to ensure prioritization Tier 1-2 Tier 3-4 Internal Tools / Experimental Apps Expiration process is initiated, results in app deletion after a period of time (8 weeks)
  15. A regular upgrade cadence prompts app owners to continuously evaluate

    the necessity of their apps 🌅 upgrade vs
  16. This repository has been archived by the owner on July

    11, 2022. It is now read-only. 447 Rails apps Today 2 544 Rails apps Rails 7.0 Upgrade Cycle 1
  17. A regular upgrade schedule is easier to plan around and

    provides opportunity for reflection Predictability 1 Lack of Perceived Benefits 2 Most of the time it’s unclear what benefits there are to gain: “why bother?”
  18. Addressing Perceived Value Support Staying up-to-date with Rails versions ensures

    access to latest bug fixes and patches, reducing time spent on debugging resolved issues. Stability Keeping your Rails version current ensures a reliable environment; reducing compatibility issues and unexpected behaviour 2 Security Keeping Rails versions up-to-date ensures protection from CVEs and reduces security risks. 3 1
  19. If you're running on Rails 3.2, your version is 10

    years old and so are your problems. -Eileen Uchitelle RailsConf 2022 - Keynote: The Success of Ruby on Rails
  20. Addressing Perceived Value Support Staying up-to-date with Rails versions ensures

    access to latest bug fixes and patches, reducing time spent on debugging resolved issues. Stability Keeping your Rails version current ensures a reliable environment; reducing compatibility issues and unexpected behaviour 2 Security Keeping Rails versions up-to-date ensures protection from CVEs and reduces security risks. 3 1
  21. Security Keeping Rails versions up-to-date ensures protection from CVEs and

    reduces security risks. 3 Currently, any apps using Rails 6.0 or older is unsupported from CVEs.
  22. Security Keeping Rails versions up-to-date ensures protection from CVEs and

    reduces security risks. 3 1 CVE-2023-28362 (actionpack) Cross-Site Scripting (XSS) vulnerability 2 CVE-2022-44566 (activerecord) Denial of Service (DoS) vulnerability 3 CVE-2023-38037 (activesupport) Locally encrypted files vulnerability Examples
  23. But by staying up-to-date and off of unsupported versions of

    Rails, you give your apps the best chance of staying secure. Vulnerabilities are Inevitable
  24. Addressing Perceived Value Developer Happiness For both new and established

    team members alike 4 7.1.3.2 7.1.3.2 7.1.3.2 7.1.3.2 7.1.3.2 7.1.3.2
  25. A regular upgrade schedule is easier to plan around and

    provides opportunity for reflection Predictability 1 Perceived Benefits 2 Support, stability, security, & developer happiness
  26. 544 >25% 110 Apps at Shopify that use Rails Apps

    that were pinged to upgrade to Rails 7.0 of the upgrades forgot a step/detail
  27. Context (2) 6.1.7 6.1.5 7.0.8 7.1.3 1.Upgrade to the latest

    patch version 2. Fix deprecations, get CI passing ✓ 1. Upgrade to the next minor version 2. Fix deprecations, get CI passing ✓ 3. Run bin/rails app:update task 4. Bump the load_defaults value 1. Upgrade to the next minor version 2. Fix deprecations, get CI passing ✓ 3. Run bin/rails app:update task 4. Bump the load_defaults value
  28. 1. Upgrade to the latest patch version 2. Fix deprecations,

    get CI passing ✓ 3. Upgrade to the next major/minor version 4. Run bin/rails app:update task 5. Bump the load_defaults value Rails Upgrade Process Pre-req: Have a test suite with good coverage
  29. steps/update_to_latest_patch.rb (snippet) @version_upgrader.upgrade("rails", latest_patch_version) title("Bump Rails from #{@version_detection.current_version} to #{latest_patch_version}")

    commit("Upgrade to Rails #{latest_patch_version}") STEPS = [ UpdateToLatestPatch, AutocorrectDeprecations, DisallowDeprecations, UpdateToNextMinor, RunAppUpdateTask, EnsureNewDefaults, ]
  30. rails_upgrade (gem) Idempotent CLI Script Semi-automates the upgrade process Ensures

    steps are not missed / proper upgrade flow Creates a title, commit, guidance, and task list message Enhanced with RBI updaters, deprecation helpers, autocorrectors
  31. def load_plugin(argv) require_flag = argv.index("--require") || argv.index("-r") if require_flag argv.delete_at(require_flag)

    plugin = argv.delete_at(require_flag) require(plugin) if plugin end end Loading a custom plugin Calling the plugin
  32. def run_tapioca_gem_command unless execute("bundle", "exec", "tapioca", "gem", allow_failure: true) warn

    "Tapioca gem command failed. Please run `bundle exec tapioca gem` to generate RBI files for gems." execute("git", "checkout", "--", ".") execute("git", "clean", "-d", "-f") end end run_tapioca_gem_command commit("Generate RBI files for gems") ? :advance_with_changes : :advance_without_changes shopify/tapioca RailsUpgrade.plugin_step(RailsUpgrade::UpdateRBI, after: :UpdateToLatestPatch) RailsUpgrade.plugin_step(RailsUpgrade::UpdateRBI, after: :UpdateToLatestMinor Injecting the RBI step into the upgrade steps Updating RBI step
  33. Disallow Deprecations step STEPS = [ UpdateToLatestPatch, AutocorrectDeprecations, DisallowDeprecations, UpdateToNextMinor,

    RunAppUpdateTask, EnsureNewDefaults, ] config.active_support.deprecation = :raise config/test.rb & config/development.rb bugsnag-ruby config/production.rb <<~RUBY config.active_support.disallowed_deprecation = ->(message, callstack, _deprecation_horizon, _gem_name) do exception = ActiveSupport::DeprecationException.new(message) exception.set_backtrace(callstack.map(&:to_s)) Bugsnag.notify(exception) do |event| event.severity = "warning" end end RUBY
  34. Autocorrector Step correctors = Autocorrection::Registry.correctors Autocorrection::Runner.run_for(correctors) do |corrector| commit("Autocorrect deprecation:

    #{corrector.description}") end def self.description "`config.active_record.legacy_connection_handling = false` is deprecated and will be removed.” end remove_legacy_connection_handling.rb return unless node.target.is_a?(SyntaxTree::Field) return unless node.target.name.value == "legacy_connection_handling" return unless node.value.value.value == "false" @visited << node
  35. rails_upgrade (gem) title("Run Update task”) guidance(<<~MESSAGE) ### Running Update task

    The `rails app:update` command has been run. An initializer named `#{framework_defaults_file(@version_detection)}` has been created, containing every new default commented out. Don’t change these before deploying. The previous Rails minor defaults are necessary to allow forward and backward compatibility during zero-downtime deploys. You will be able to use this file for testing out the new defaults, as described in the commented-out instructions within the file. MESSAGE todo(<<~MESSAGE) **Restore any unwanted changes from the `app:update` command.** Verify the contents of all files modified by `app:update` to ensure that nothing important was overwritten by the command. For example, changes to the `content_security_policy.rb` file may have been applied, but you may want to keep the previous content in place. MESSAGE after_merge_todo(<<~MESSAGE) **Test new defaults.** This PR created a `#{framework_defaults_file(@version_detection)}` file, containing every new configuration default (currently commented out). Once the PR is merged and the app is fully deployed, enable the new defaults by uncommenting them and making sure the application is ready to run with them. This can be done gradually over several deployments. MESSAGE Title, Guidance & Task List for the App:Update Step
  36. 🚃 / Solid Track / Github Actions • Solid Track

    = Web app for all things Rails upgrade related • Triggers Github Action workflows for owners that have “onboarded” their app • These workflows call the rails_upgrade gem • Regularly creates Rails upgrades in place of Dependabot
  37. Standardizing on the .ruby-version file simplifies Ruby Upgrades for App

    owners today and makes future upgrades very straightforward
  38. # Install Ruby from official Docker image # When bumping

    Ruby minor, need to also add the previous version to `bundler/helpers/v{1,2}/ monkey_patches/definition_ruby_version_patch.rb` COPY --from=docker.io/library/ruby:3.1.4-bookworm --chown=dependabot:dependabot /usr/local /usr/local dependabot-core/Dockerfile.updater-core Ruby 3.1.4 (by default) Dependabot runs
  39. Ruby 3.1.4 Your Repo Ruby 3.3.0 (by default) Defined in

    .ruby-version only But ‘protobuf’ resolved on
  40. .rubocop.yml require: rubocop-rails Style/StringLiterals: Enabled: false After Removing TargetRubyVersion Still

    OK SOURCES = [ RuboCopConfig, RubyVersionFile, ToolVersionsFile, BundlerLockFile, GemspecFile, Default ] lib/rubocop/target_ruby.rb
  41. Going forward, developers only need to change one line to

    upgrade their Ruby version. Improving upgrade ease, version consistency, and reducing onboarding friction for new developers.
  42. Rails Upgrades Are Unique… And require more work than regular

    dependency upgrades. Possible to make mistakes if not careful. But not scary! Nothing but a couple of steps that as we’ve shown, can be automated! With the insights you’ve gained, upgrades will be a piece of cake. 🍰
  43. Ruby Upgrades Use the .ruby-version file Your future self will

    thank you! It is the ecosystem standard and makes future Ruby upgrades super easy. Apps & Gems both work! We’ve fixed the most pressing issues within Dependabot and Rubocop and have standardized most Ruby repos at Shopify. Have confidence in standardizing today!