diff --git a/Gemfile b/Gemfile index 4e9736347a1..5cac6fb405e 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,7 @@ FOREMAN_GEMFILE = __FILE__ unless defined? FOREMAN_GEMFILE source 'https://rubygems.org' -gem 'rails', '~> 6.1.6' +gem 'rails', '~> 7.0.3' gem 'rest-client', '>= 2.0.0', '< 3', :require => 'rest_client' gem 'audited', '~> 5.0', '!= 5.1.0' gem 'will_paginate', '~> 3.3' diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 70732daf073..dab973f19f6 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -333,10 +333,6 @@ def process_ajax_error(exception, action = nil) render :partial => "common/ajax_error", :status => :internal_server_error, :locals => { :message => message } end - def redirect_back_or_to(url) - redirect_back(fallback_location: url, allow_other_host: false) - end - def saved_redirect_url_or(default) session["redirect_to_url_#{controller_name}"] || default end diff --git a/app/controllers/links_controller.rb b/app/controllers/links_controller.rb index d9e2a940d13..41a6d3047c0 100644 --- a/app/controllers/links_controller.rb +++ b/app/controllers/links_controller.rb @@ -4,7 +4,7 @@ class LinksController < ApplicationController def show url = external_url(type: params[:type], options: params) - redirect_to(url) + redirect_to(url, allow_other_host: true) end def external_url(type:, options: {}) diff --git a/app/controllers/usergroups_controller.rb b/app/controllers/usergroups_controller.rb index b633f906739..4079b29ffe8 100644 --- a/app/controllers/usergroups_controller.rb +++ b/app/controllers/usergroups_controller.rb @@ -37,7 +37,7 @@ def update process_error end rescue Foreman::CyclicGraphException => e - @usergroup.errors[:usergroups] << e.record.errors[:base].join(' ') + @usergroup.errors.add(:usergroups, e.record.errors[:base].join(' ')) process_error rescue => e external_usergroups_error(@usergroup, e) diff --git a/app/graphql/collection_loader.rb b/app/graphql/collection_loader.rb index 07217ce4617..004e3134e11 100644 --- a/app/graphql/collection_loader.rb +++ b/app/graphql/collection_loader.rb @@ -41,7 +41,7 @@ def validate end def preloader_for_association(records) - ::ActiveRecord::Associations::Preloader.new.preload(records, association_name, base_scope).first + ::ActiveRecord::Associations::Preloader.new(records: records, associations: association_name, scope: base_scope).call.first end def read_association(preloader, record) diff --git a/app/helpers/layout_helper.rb b/app/helpers/layout_helper.rb index 50779a1f7bd..ee51e8d8a6a 100644 --- a/app/helpers/layout_helper.rb +++ b/app/helpers/layout_helper.rb @@ -159,7 +159,7 @@ def popover(title, msg, options = {}) def icon_text(i, text = "", opts = {}) opts[:kind] ||= "glyphicon" - (content_tag(:span, "", :class => "#{opts[:kind] + ' ' + opts[:kind]}-#{i} #{opts[:class]}", :title => opts[:title]) + " " + text).html_safe + (content_tag(:span, "", :class => "#{opts[:kind] + ' ' + opts[:kind]}-#{i} #{opts[:class]}", :title => opts[:title]) + " " + text.to_s).html_safe end def alert(opts = {}) diff --git a/app/models/compute_resources/foreman/model/ec2.rb b/app/models/compute_resources/foreman/model/ec2.rb index 930b3316b60..d801542190a 100644 --- a/app/models/compute_resources/foreman/model/ec2.rb +++ b/app/models/compute_resources/foreman/model/ec2.rb @@ -122,9 +122,9 @@ def test_connection(options = {}) super errors[:user].empty? && errors[:password].empty? && regions rescue Fog::AWS::Compute::Error => e - errors[:base] << e.message + errors.add(:base, e.message) rescue Excon::Error::Socket => e - errors[:base] << e.message + errors.add(:base, e.message) end def console(uuid) diff --git a/app/models/compute_resources/foreman/model/libvirt.rb b/app/models/compute_resources/foreman/model/libvirt.rb index 23d367de42b..f5360fbd61f 100644 --- a/app/models/compute_resources/foreman/model/libvirt.rb +++ b/app/models/compute_resources/foreman/model/libvirt.rb @@ -78,7 +78,7 @@ def test_connection(options = {}) errors[:url].empty? && hypervisor rescue => e disconnect rescue nil - errors[:base] << e.message + errors.add(:base, e.message) end def new_nic(attr = {}) diff --git a/app/models/compute_resources/foreman/model/openstack.rb b/app/models/compute_resources/foreman/model/openstack.rb index 3a414cfa4a3..44154ace9f8 100644 --- a/app/models/compute_resources/foreman/model/openstack.rb +++ b/app/models/compute_resources/foreman/model/openstack.rb @@ -91,7 +91,7 @@ def test_connection(options = {}) super errors[:user].empty? && errors[:password] && tenants rescue => e - errors[:base] << e.message + errors.add(:base, e.message) end def available_images diff --git a/app/models/compute_resources/foreman/model/vmware.rb b/app/models/compute_resources/foreman/model/vmware.rb index 76c7ea8e9ee..1a5284f454e 100644 --- a/app/models/compute_resources/foreman/model/vmware.rb +++ b/app/models/compute_resources/foreman/model/vmware.rb @@ -470,7 +470,7 @@ def test_connection(options = {}) errors.delete(:datacenter) end rescue => e - errors[:base] << e.message + errors.add(:base, e.message) end def parse_args(args) diff --git a/app/models/concerns/foreman/sti.rb b/app/models/concerns/foreman/sti.rb index 1ef85bf61ad..e13affaedf1 100644 --- a/app/models/concerns/foreman/sti.rb +++ b/app/models/concerns/foreman/sti.rb @@ -13,13 +13,5 @@ def new(attributes = nil, &block) super end end - - def save(*args, **kwargs) - type_changed = type_changed? - self.class.instance_variable_set("@finder_needs_type_condition", :false) if type_changed - super - ensure - self.class.instance_variable_set("@finder_needs_type_condition", :true) if type_changed - end end end diff --git a/app/models/concerns/hidden_value.rb b/app/models/concerns/hidden_value.rb index be8cca69f7e..5dd951224b4 100644 --- a/app/models/concerns/hidden_value.rb +++ b/app/models/concerns/hidden_value.rb @@ -10,4 +10,8 @@ def safe_value def hidden_value HIDDEN_VALUE end + + def hidden_value? + attributes['hidden_value'] + end end diff --git a/app/models/concerns/hostext/token.rb b/app/models/concerns/hostext/token.rb index 910aa651222..59ebba381f6 100644 --- a/app/models/concerns/hostext/token.rb +++ b/app/models/concerns/hostext/token.rb @@ -5,7 +5,7 @@ module Token included do has_one :token, :foreign_key => :host_id, :dependent => :destroy, :inverse_of => :host, :class_name => 'Token::Build' - scope :for_token, ->(token) { joins(:token).where(:tokens => { :value => token }).where("expires >= ?", Time.now.utc.to_s(:db)).select('hosts.*') } + scope :for_token, ->(token) { joins(:token).where(:tokens => { :value => token }).where("expires >= ?", Time.now.utc.to_formatted_s(:db)).select('hosts.*') } scope :for_token_when_built, ->(token) { joins(:token).where(:tokens => { :value => token }).select('hosts.*') } before_validation :refresh_token_on_build diff --git a/app/models/host.rb b/app/models/host.rb index 9713158cbe3..b134fa25b61 100644 --- a/app/models/host.rb +++ b/app/models/host.rb @@ -58,4 +58,9 @@ def self.respond_to_missing?(method, include_private = false) method.to_s =~ /\Afind_by_(.*)\Z/ || method.to_s.include?('create') || [:reorder, :new].include?(method) || super end + + # This is a workaround for https://github.com/rails/rails/blob/v7.0.4/activerecord/lib/active_record/reflection.rb#L420-L443 + def self.<(other) + other == ActiveRecord::Base || super + end end diff --git a/app/services/foreman/preload_scopes_builder.rb b/app/services/foreman/preload_scopes_builder.rb index 2fed836d0d3..0d8a1e3b9f5 100644 --- a/app/services/foreman/preload_scopes_builder.rb +++ b/app/services/foreman/preload_scopes_builder.rb @@ -26,7 +26,7 @@ def preload_scopes end def build_scopes(model, ignore: [], assoc_name: nil) - scopes = dependent_associations(model).map do |assoc| + scopes = dependent_associations(model, ignore: ignore).map do |assoc| next if ignore.include?(assoc.name) dep_associations = dependent_associations(assoc.klass) @@ -34,8 +34,14 @@ def build_scopes(model, ignore: [], assoc_name: nil) ignore += dep_associations.select { |to_ignore| to_ignore.options.key?(:through) }.map(&:name) ignore << assoc.name dep_scopes = build_scopes(assoc.klass, ignore: ignore, assoc_name: assoc.name) - if assoc.options.key?(:through) && dep_scopes.is_a?(Hash) - next { assoc.options[:through] => assoc.source_reflection_name }.merge(dep_scopes) + if assoc.options.key?(:through) + deps = dependent_associations(assoc.source_reflection.klass, ignore: ignore) + if deps.any? + dep_scopes = build_scopes(assoc.source_reflection.klass, ignore: ignore, assoc_name: assoc.source_reflection_name) + next { assoc.options[:through] => dep_scopes } + else + next { assoc.options[:through] => assoc.source_reflection_name } + end end next dep_scopes end @@ -46,9 +52,9 @@ def build_scopes(model, ignore: [], assoc_name: nil) scopes.empty? ? assoc_name : { assoc_name => scopes } end - def dependent_associations(model) + def dependent_associations(model, ignore: []) model.reflect_on_all_associations.select do |assoc| - (assoc.options.values & [:destroy, :delete_all, :destroy_async]).any? + !ignore.include?(assoc.name) && (assoc.options.values & [:destroy, :delete_all, :destroy_async]).any? end end end diff --git a/bin/spring b/bin/spring index 7fe232c3aae..c2ee6ba19c1 100755 --- a/bin/spring +++ b/bin/spring @@ -2,8 +2,7 @@ # This file loads spring without using Bundler, in order to be fast. # It gets overwritten when you run the `spring binstub` command. - -unless defined?(Spring) +if !defined?(Spring) && ARGV.grep(/test\/integration/).empty? # Disable spring for integration test require 'rubygems' require 'bundler' diff --git a/bundler.d/development.rb b/bundler.d/development.rb index f17b5a2d91c..54723b87557 100644 --- a/bundler.d/development.rb +++ b/bundler.d/development.rb @@ -20,7 +20,7 @@ gem 'bullet', '>= 6.1.0' gem "parallel_tests" - gem 'spring', '>= 1.0', '< 3' + gem 'spring', '~> 4.0' gem 'benchmark-ips', '>= 2.8.2' gem 'foreman' gem('bootsnap', :require => false) diff --git a/config/application.rb b/config/application.rb index 11344ec4841..b6e2cf4f783 100644 --- a/config/application.rb +++ b/config/application.rb @@ -80,7 +80,7 @@ class Foreman::Consoletie < Rails::Railtie module Foreman class Application < Rails::Application - config.load_defaults '6.1' + config.load_defaults '7.0' # Rails 5.0 changed this to true, but a lot of code depends on this config.active_record.belongs_to_required_by_default = false @@ -99,11 +99,17 @@ class Application < Rails::Application config.active_support.use_authenticated_message_encryption = false config.action_dispatch.use_authenticated_cookie_encryption = false - # Rails 6.0 changed this to :zeitwerk - config.autoloader = :zeitwerk - # Rails 6.1 changed this to true, but apparently our codebase is not ready for bidirectional associations config.active_record.has_many_inversing = false + + # Rails 7.0 changed this to true + config.active_record.verify_foreign_keys_for_fixtures = false + config.active_record.automatic_scope_inversing = false + # Rails 7.0 changed this to OpenSSL::Digest::SHA256 + # We'd probably need a rotator: + # https://guides.rubyonrails.org/v7.0/upgrading_ruby_on_rails.html#key-generator-digest-class-changing-to-use-sha256 + config.active_support.hash_digest_class = OpenSSL::Digest::SHA1 + # Setup additional routes by loading all routes file from routes directory Dir["#{Rails.root}/config/routes/**/*.rb"].each do |route_file| config.paths['config/routes.rb'] << route_file @@ -113,6 +119,8 @@ class Application < Rails::Application # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. + # Recommended by Rails: https://guides.rubyonrails.org/v7.0/configuring.html#config-add-autoload-paths-to-load-path + config.add_autoload_paths_to_load_path = false # Autoloading config.autoload_paths += %W(#{config.root}/app/models/auth_sources) config.autoload_paths += %W(#{config.root}/app/models/compute_resources) diff --git a/config/boot.rb b/config/boot.rb index 77d8aa1d777..37ccc74b87b 100644 --- a/config/boot.rb +++ b/config/boot.rb @@ -5,5 +5,5 @@ # Set up boootsnap on Ruby 2.3+ in development env with Bundler enabled and development group early_env = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development" require 'active_support/dependencies' - require('bootsnap/setup') if early_env == "development" && File.exist?(ENV['BUNDLE_GEMFILE']) && !Gem::Specification.stubs_for("bootsnap").empty? + require('bootsnap/setup') if %w[development test].include?(early_env) && File.exist?(ENV['BUNDLE_GEMFILE']) && !Gem::Specification.stubs_for("bootsnap").empty? end diff --git a/config/environments/test.rb b/config/environments/test.rb index ae8cf0bd621..c3d6dafaad4 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -10,7 +10,8 @@ # test suite. You never need to work with it otherwise. Remember that # your test database is "scratch space" for the test suite and is wiped # and recreated between test runs. Don't rely on the data there! - config.cache_classes = true + # Disable reloading for integration tests + config.cache_classes = ARGV.grep(/test\/integration/).any? config.eager_load = true diff --git a/config/initializers/core_extensions.rb b/config/initializers/core_extensions.rb index 3ae98c399e6..833a78b8710 100644 --- a/config/initializers/core_extensions.rb +++ b/config/initializers/core_extensions.rb @@ -14,7 +14,7 @@ def self.per_page # Foreman.in_rake? prevents the failure of db:migrate for postgresql # don't query settings table if in rake return 20 unless Foreman.settings.ready? - Setting[:entries_per_page] rescue 20 + Foreman.settings[:entries_per_page] rescue 20 end def self.audited(*args) diff --git a/lib/tasks/webpack_compile.rake b/lib/tasks/webpack_compile.rake index 4aaa37d538c..175d6b1cd1e 100644 --- a/lib/tasks/webpack_compile.rake +++ b/lib/tasks/webpack_compile.rake @@ -16,5 +16,6 @@ namespace :webpack do end sh "npx --max_old_space_size=#{max_old_space_size} webpack --config #{config_file} --bail" + ActiveRecord::Base.clear_all_connections! end end diff --git a/test/factories/audit.rb b/test/factories/audit.rb index 8f1f525df63..98674cd0708 100644 --- a/test/factories/audit.rb +++ b/test/factories/audit.rb @@ -1,7 +1,7 @@ FactoryBot.define do factory :audit do sequence(:version) { |n| n.to_s } - auditable_type { "test" } + auditable_type { ApplicationRecord } action { "update" } trait :with_diff do diff --git a/test/integration/hostgroup_js_test.rb b/test/integration/hostgroup_js_test.rb index 7873e90fba7..1ef4dca91ac 100644 --- a/test/integration/hostgroup_js_test.rb +++ b/test/integration/hostgroup_js_test.rb @@ -23,8 +23,9 @@ class HostgroupJSTest < IntegrationTestWithJavascript select2 os.ptables.first.name, :from => 'hostgroup_ptable_id' fill_in 'hostgroup_root_pass', :with => '12345678' click_button 'Submit' - - hostgroup = Hostgroup.where(:name => "myhostgroup1").first + hostgroup = wait_for do + Hostgroup.where(:name => "myhostgroup1").first + end refute_nil hostgroup assert_equal os.id, hostgroup.operatingsystem_id assert page.has_current_path? hostgroups_path diff --git a/test/integration/org_admin_js_test.rb b/test/integration/org_admin_js_test.rb index f6794fa6ea9..224f2c3063a 100644 --- a/test/integration/org_admin_js_test.rb +++ b/test/integration/org_admin_js_test.rb @@ -95,7 +95,9 @@ def test_org_admin_tries_to_create_domain_when_unselect_the_organization ensure_selected_option_of_multiselect(@org1.name, select_id: 'domain_organization_ids') page.click_button 'Submit' - created_domain = Domain.unscoped.find_by_name(domain.name) + created_domain = wait_for do + Domain.unscoped.find_by_name(domain.name) + end # sets the only organization anyway assert_equal [@org1], created_domain.organizations end diff --git a/test/unit/foreman/access_control_test.rb b/test/unit/foreman/access_control_test.rb index f8738c02a28..7676c153a4c 100644 --- a/test/unit/foreman/access_control_test.rb +++ b/test/unit/foreman/access_control_test.rb @@ -1,5 +1,4 @@ require 'test_helper' -require 'foreman/access_control' class AccessControlTest < ActiveSupport::TestCase test '#path_hash_to_string reads controller and action' do diff --git a/test/unit/foreman/cast_test.rb b/test/unit/foreman/cast_test.rb index 64242f3ad92..2faed02b8e9 100644 --- a/test/unit/foreman/cast_test.rb +++ b/test/unit/foreman/cast_test.rb @@ -1,5 +1,4 @@ require 'test_helper' -require 'foreman/cast' class CastTest < ActiveSupport::TestCase include Foreman::Cast diff --git a/test/unit/has_many_common_test.rb b/test/unit/has_many_common_test.rb index ba79aaed563..8947e46739f 100644 --- a/test/unit/has_many_common_test.rb +++ b/test/unit/has_many_common_test.rb @@ -92,7 +92,7 @@ class HasManyCommonTest < ActiveSupport::TestCase ## Test name methods resolve for Plugin AR objects class ::FakePlugin; end - class ::FakePlugin::FakeModel; end + class ::FakePlugin::FakeModel < ApplicationRecord; end test "should return plugin associations" do Host::Managed.class_eval do diff --git a/test/unit/shared/access_permissions_test_base.rb b/test/unit/shared/access_permissions_test_base.rb index 5f697389ae8..d4cfd0ecbbd 100644 --- a/test/unit/shared/access_permissions_test_base.rb +++ b/test/unit/shared/access_permissions_test_base.rb @@ -1,5 +1,3 @@ -require 'foreman/access_control' - module AccessPermissionsTestBase extend ActiveSupport::Concern