diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 42c9481828c16055b21d6879034f8fad7df18b4f..a7694c5858856729a286c9f1d578e5fc377f6570 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -484,7 +484,8 @@ def visible_attributes :user_defaults_to_private_profile, :deactivation_email_additional_text, :projects_api_rate_limit_unauthenticated, - :gitlab_dedicated_instance + :gitlab_dedicated_instance, + :ci_max_includes ].tap do |settings| next if Gitlab.com? diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 02b8cf9ee28b9cb0127f8e065da3a4aed1fd7f3b..6a34a7dd14845dcf9e3cf736620c5cbbe03c3512 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -383,6 +383,8 @@ def self.kroki_formats_attributes validates :max_yaml_size_bytes, numericality: { only_integer: true, greater_than: 0 }, presence: true validates :max_yaml_depth, numericality: { only_integer: true, greater_than: 0 }, presence: true + validates :ci_max_includes, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, presence: true + validates :email_restrictions, untrusted_regexp: true validates :hashed_storage_enabled, inclusion: { in: [true], message: N_("Hashed storage can't be disabled anymore for new projects") } diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb index 010c88179df0424fa38d8f8a4aba9c95acd18a3a..845d402f550474e0ab3f9bd6e6a7fe97e75afa08 100644 --- a/app/models/application_setting_implementation.rb +++ b/app/models/application_setting_implementation.rb @@ -252,7 +252,8 @@ def defaults # rubocop:disable Metrics/AbcSize allow_runner_registration_token: true, user_defaults_to_private_profile: false, projects_api_rate_limit_unauthenticated: 400, - gitlab_dedicated_instance: false + gitlab_dedicated_instance: false, + ci_max_includes: 150 }.tap do |hsh| hsh.merge!(non_production_defaults) unless Rails.env.production? end diff --git a/app/views/admin/application_settings/_ci_cd.html.haml b/app/views/admin/application_settings/_ci_cd.html.haml index f162f5be74644286a0e181b20c34c48cb7b54fdf..0c9d5a5a8dff5ffa3b7ec133091975910b2ca433 100644 --- a/app/views/admin/application_settings/_ci_cd.html.haml +++ b/app/views/admin/application_settings/_ci_cd.html.haml @@ -45,6 +45,11 @@ = link_to _('Learn more.'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'archive-jobs') .form-group = f.gitlab_ui_checkbox_component :protected_ci_variables, s_('AdminSettings|Protect CI/CD variables by default'), help_text: s_('AdminSettings|New CI/CD variables in projects and groups default to protected.') + .form-group + = f.label :ci_max_includes, s_('AdminSettings|Maximum includes'), class: 'label-bold' + = f.number_field :ci_max_includes, class: 'form-control gl-form-input' + .form-text.text-muted + = s_('AdminSettings|The maximum number of included files per pipeline.') .form-group = f.label :ci_config_path, _('Default CI/CD configuration file'), class: 'label-bold' = f.text_field :default_ci_config_path, class: 'form-control gl-form-input', placeholder: '.gitlab-ci.yml' diff --git a/db/migrate/20230421165020_add_ci_max_includes_to_application_settings.rb b/db/migrate/20230421165020_add_ci_max_includes_to_application_settings.rb new file mode 100644 index 0000000000000000000000000000000000000000..9996d3b1654d2087d17a784a51b0e33e3745dcb1 --- /dev/null +++ b/db/migrate/20230421165020_add_ci_max_includes_to_application_settings.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddCiMaxIncludesToApplicationSettings < Gitlab::Database::Migration[2.1] + def change + add_column :application_settings, :ci_max_includes, :integer, default: 150, null: false + end +end diff --git a/db/schema_migrations/20230421165020 b/db/schema_migrations/20230421165020 new file mode 100644 index 0000000000000000000000000000000000000000..a1121e3f83df1b39464ba1a8b8a780531838b838 --- /dev/null +++ b/db/schema_migrations/20230421165020 @@ -0,0 +1 @@ +1e33e8da1f2f10f976e19bcf234f7ac5bdcc0eed7d60288ead2eaf87dfd678c9 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index c366c1e3bb64e1f57cd88f3a4088b365b737ef75..b9ea2dcbaa3d1d8fa07b84d156346a21ca262c42 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -11804,6 +11804,7 @@ CREATE TABLE application_settings ( encrypted_product_analytics_configurator_connection_string_iv bytea, silent_mode_enabled boolean DEFAULT false NOT NULL, package_metadata_purl_types smallint[] DEFAULT '{}'::smallint[], + ci_max_includes integer DEFAULT 150 NOT NULL, CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)), CONSTRAINT app_settings_container_registry_pre_import_tags_rate_positive CHECK ((container_registry_pre_import_tags_rate >= (0)::numeric)), CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)), diff --git a/doc/api/settings.md b/doc/api/settings.md index 22e21ba19b4cdc0dd671bfbebee0b7c56ae2c111..9c8c62ed2230d58bfee7f4e7688dfaec99e3cbfd 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -296,6 +296,7 @@ listed in the descriptions of the relevant settings. | `bulk_import_enabled` | boolean | no | Enable migrating GitLab groups by direct transfer. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/383268) in GitLab 15.8. Setting also [available](../user/admin_area/settings/visibility_and_access_controls.md#enable-migration-of-groups-and-projects-by-direct-transfer) in the Admin Area. | | `can_create_group` | boolean | no | Indicates whether users can create top-level groups. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/367754) in GitLab 15.5. Defaults to `true`. | | `check_namespace_plan` **(PREMIUM)** | boolean | no | Enabling this makes only licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public. | +| `ci_max_includes` | integer | no | The maximum number of [includes](../ci/yaml/includes.md) per pipeline. Default is `150`. | | `commit_email_hostname` | string | no | Custom hostname (for private commit emails). | | `container_expiration_policies_enable_historic_entries` | boolean | no | Enable [cleanup policies](../user/packages/container_registry/reduce_container_registry_storage.md#enable-the-cleanup-policy) for all projects. | | `container_registry_cleanup_tags_service_max_list_size` | integer | no | The maximum number of tags that can be deleted in a single execution of [cleanup policies](../user/packages/container_registry/reduce_container_registry_storage.md#set-cleanup-limits-to-conserve-resources). | diff --git a/doc/ci/yaml/includes.md b/doc/ci/yaml/includes.md index cd2cb3c96e1d92f1c264286386bd29a4b4ca2d99..35b302d03734d4554e4ff2f917f54e0c18a60634 100644 --- a/doc/ci/yaml/includes.md +++ b/doc/ci/yaml/includes.md @@ -603,3 +603,6 @@ To help reduce the risk of this happening, edit the pipeline configuration file with the [pipeline editor](../pipeline_editor/index.md), which validates if the limit is reached. You can remove one included file at a time to try to narrow down which configuration file is the source of the loop or excessive included files. + +In [GitLab 16.0 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/207270) self-managed users can +change the [maximum includes](../../user/admin_area/settings/continuous_integration.md#maximum-includes) value. diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md index 5090e016cb01b4bafd48846c5a8fe6d7f3e456da..39f979d98d5828e60a2fada11df9938d885a87c0 100644 --- a/doc/ci/yaml/index.md +++ b/doc/ci/yaml/index.md @@ -136,16 +136,6 @@ The `include` files are: - Always evaluated first and then merged with the content of the `.gitlab-ci.yml` file, regardless of the position of the `include` keyword. -You can have up to 150 includes per pipeline, including [nested](includes.md#use-nested-includes) includes: - -- In [GitLab 15.10 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/367150) you can have up to 150 includes. - In nested includes, the same file can be included multiple times, but duplicated includes - count towards the limit. -- From [GitLab 14.9 to GitLab 15.9](https://gitlab.com/gitlab-org/gitlab/-/issues/28987), you can have up to 100 includes. - The same file can be included multiple times in nested includes, but duplicates are ignored. -- In GitLab 14.9 and earlier you can have up to 100 includes, but the same file can not - be included multiple times. - The time limit to resolve all files is 30 seconds. **Keyword type**: Global keyword. @@ -171,6 +161,16 @@ The time limit to resolve all files is 30 seconds. do not affect job reruns. - Pipeline, the `include` files are fetched again. If they changed after the last pipeline run, the new pipeline uses the changed configuration. +- You can have up to 150 includes per pipeline by default, including [nested](includes.md#use-nested-includes). Additionally: + - In [GitLab 16.0 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/207270) self-managed users can + change the [maximum includes](../../user/admin_area/settings/continuous_integration.md#maximum-includes) value. + - In [GitLab 15.10 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/367150) you can have up to 150 includes. + In nested includes, the same file can be included multiple times, but duplicated includes + count towards the limit. + - From [GitLab 14.9 to GitLab 15.9](https://gitlab.com/gitlab-org/gitlab/-/issues/28987), you can have up to 100 includes. + The same file can be included multiple times in nested includes, but duplicates are ignored. + - In GitLab 14.9 and earlier you can have up to 100 includes, but the same file can not + be included multiple times. **Related topics**: diff --git a/doc/user/admin_area/settings/continuous_integration.md b/doc/user/admin_area/settings/continuous_integration.md index a0a0dc6509972732d9978df652de5a4cb96b3c81..d6b1d243d823b0e24f19050007eef2ba480bf5ec 100644 --- a/doc/user/admin_area/settings/continuous_integration.md +++ b/doc/user/admin_area/settings/continuous_integration.md @@ -194,6 +194,16 @@ To set all new [CI/CD variables](../../../ci/variables/index.md) as 1. On the left sidebar, select **Settings > CI/CD**. 1. Select **Protect CI/CD variables by default**. +## Maximum includes + +The maximum number of [includes](../../../ci/yaml/includes.md) per pipeline can be set at the instance level. +The default is `150`. + +1. On the top bar, select **Main menu > Admin**. +1. On the left sidebar, select **Settings > CI/CD**. +1. Change the value of **Maximum includes**. +1. Select **Save changes** for the changes to take effect. + ## Default CI/CD configuration file > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/18073) in GitLab 12.5. diff --git a/lib/api/settings.rb b/lib/api/settings.rb index 9562af225a7efacee1e619125a2e2612850118ed..f3204104b9f41c5ea1c9b48206ccd720e001fdfd 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -196,6 +196,7 @@ def filter_attributes_using_license(attrs) optional :jira_connect_proxy_url, type: String, desc: "URL of the GitLab instance that should be used as a proxy for the GitLab for Jira Cloud app" optional :bulk_import_enabled, type: Boolean, desc: 'Enable migrating GitLab groups and projects by direct transfer' optional :allow_runner_registration_token, type: Boolean, desc: 'Allow registering runners using a registration token' + optional :ci_max_includes, type: Integer, desc: 'Maximum number of includes per pipeline' Gitlab::SSHPublicKey.supported_types.each do |type| optional :"#{type}_key_restriction", diff --git a/lib/gitlab/ci/config/external/context.rb b/lib/gitlab/ci/config/external/context.rb index 156109a084d69bd9f0f575473536e2de1c084182..b8e012ec8515ceac8221cffcf86ca77766c87cee 100644 --- a/lib/gitlab/ci/config/external/context.rb +++ b/lib/gitlab/ci/config/external/context.rb @@ -9,8 +9,7 @@ class Context TimeoutError = Class.new(StandardError) - MAX_INCLUDES = 150 - TEMP_MAX_INCLUDES = 100 # For logging; to be removed in https://gitlab.com/gitlab-org/gitlab/-/issues/367150 + TEMP_MAX_INCLUDES = 100 # For logging; to be removed in https://gitlab.com/gitlab-org/gitlab/-/issues/396776 include ::Gitlab::Utils::StrongMemoize @@ -32,7 +31,7 @@ def initialize( @expandset = [] @execution_deadline = 0 @logger = logger || Gitlab::Ci::Pipeline::Logger.new(project: project) - @max_includes = MAX_INCLUDES + @max_includes = Gitlab::CurrentSettings.current_application_settings.ci_max_includes yield self if block_given? end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index e6b93cf41ac2e42ab77725cd08b6034297d1b1ad..66003ce06512f960f30b2b901a99302a4218b7e1 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -3075,6 +3075,9 @@ msgstr "" msgid "AdminSettings|Maximum duration of a session for Git operations when 2FA is enabled." msgstr "" +msgid "AdminSettings|Maximum includes" +msgstr "" + msgid "AdminSettings|Maximum number of DAG dependencies that a job can have" msgstr "" @@ -3228,6 +3231,9 @@ msgstr "" msgid "AdminSettings|The latest artifacts for all jobs in the most recent successful pipelines in each project are stored and do not expire." msgstr "" +msgid "AdminSettings|The maximum number of included files per pipeline." +msgstr "" + msgid "AdminSettings|The template for the required pipeline configuration can be one of the GitLab-provided templates, or a custom template added to an instance template repository. %{link_start}How do I create an instance template repository?%{link_end}" msgstr "" diff --git a/spec/controllers/admin/application_settings_controller_spec.rb b/spec/controllers/admin/application_settings_controller_spec.rb index 253f66579f3c6283fe14af75f41eeeb4b86d4c27..4b6274c2d2631392041a8957ab96a2b531db5f5c 100644 --- a/spec/controllers/admin/application_settings_controller_spec.rb +++ b/spec/controllers/admin/application_settings_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Admin::ApplicationSettingsController, :do_not_mock_admin_mode_setting do +RSpec.describe Admin::ApplicationSettingsController, :do_not_mock_admin_mode_setting, feature_category: :shared do include StubENV include UsageDataHelpers @@ -398,6 +398,17 @@ expect(application_settings.reload.invitation_flow_enforcement).to eq(true) end end + + context 'maximum includes' do + let(:application_settings) { ApplicationSetting.current } + + it 'updates ci_max_includes setting' do + put :update, params: { application_setting: { ci_max_includes: 200 } } + + expect(response).to redirect_to(general_admin_application_settings_path) + expect(application_settings.reload.ci_max_includes).to eq(200) + end + end end describe 'PUT #reset_registration_token', feature_category: :user_management do diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index 524fcb01644eec342b0c7bb2325e56a7c2a82747..4dee97880a5a553bba3ceaa6bf91dc88e23a5d45 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -426,6 +426,7 @@ fill_in 'application_setting_auto_devops_domain', with: 'domain.com' uncheck 'Keep the latest artifacts for all jobs in the latest successful pipelines' uncheck 'Enable pipeline suggestion banner' + fill_in 'application_setting_ci_max_includes', with: 200 click_button 'Save changes' end @@ -433,6 +434,7 @@ expect(current_settings.auto_devops_domain).to eq('domain.com') expect(current_settings.keep_latest_artifact).to be false expect(current_settings.suggest_pipeline_enabled).to be false + expect(current_settings.ci_max_includes).to be 200 expect(page).to have_content "Application settings saved successfully" end diff --git a/spec/lib/gitlab/ci/config/external/context_spec.rb b/spec/lib/gitlab/ci/config/external/context_spec.rb index fc68d3d62a25f584650e10e7e36472416723d15c..d917924f2577932875e611cad86072a45d1d22cf 100644 --- a/spec/lib/gitlab/ci/config/external/context_spec.rb +++ b/spec/lib/gitlab/ci/config/external/context_spec.rb @@ -24,7 +24,6 @@ context 'with values' do it { is_expected.to have_attributes(**attributes) } it { expect(subject.expandset).to eq([]) } - it { expect(subject.max_includes).to eq(Gitlab::Ci::Config::External::Context::MAX_INCLUDES) } it { expect(subject.execution_deadline).to eq(0) } it { expect(subject.variables).to be_instance_of(Gitlab::Ci::Variables::Collection) } it { expect(subject.variables_hash).to be_instance_of(ActiveSupport::HashWithIndifferentAccess) } @@ -37,12 +36,27 @@ it { is_expected.to have_attributes(**attributes) } it { expect(subject.expandset).to eq([]) } - it { expect(subject.max_includes).to eq(Gitlab::Ci::Config::External::Context::MAX_INCLUDES) } it { expect(subject.execution_deadline).to eq(0) } it { expect(subject.variables).to be_instance_of(Gitlab::Ci::Variables::Collection) } it { expect(subject.variables_hash).to be_instance_of(ActiveSupport::HashWithIndifferentAccess) } it { expect(subject.pipeline_config).to be_nil } end + + describe 'max_includes' do + it 'returns the default value of application setting `ci_max_includes`' do + expect(subject.max_includes).to eq(150) + end + + context 'when application setting `ci_max_includes` is changed' do + before do + stub_application_setting(ci_max_includes: 200) + end + + it 'returns the new value of application setting `ci_max_includes`' do + expect(subject.max_includes).to eq(200) + end + end + end end describe '#set_deadline' do diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index 64411c9fe928be0bc4139118024b90e2e9ba0c07..7b5c3c82b7f180f83f8bf57c56cdb48745f12f28 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -277,6 +277,13 @@ def many_usernames(num = 100) it { is_expected.to allow_value([true, false]).for(:silent_mode_enabled) } it { is_expected.not_to allow_value(nil).for(:silent_mode_enabled) } + it { is_expected.to allow_value(0).for(:ci_max_includes) } + it { is_expected.to allow_value(200).for(:ci_max_includes) } + it { is_expected.not_to allow_value('abc').for(:ci_max_includes) } + it { is_expected.not_to allow_value(nil).for(:ci_max_includes) } + it { is_expected.not_to allow_value(10.5).for(:ci_max_includes) } + it { is_expected.not_to allow_value(-1).for(:ci_max_includes) } + context 'when deactivate_dormant_users is enabled' do before do stub_application_setting(deactivate_dormant_users: true) diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index ff86859a8f4a8abcf36ea1c23aabc959a4378ce2..8decb8cdf9035ec484481374f40e8a19722c2fe9 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -70,6 +70,7 @@ expect(json_response['projects_api_rate_limit_unauthenticated']).to eq(400) expect(json_response['silent_mode_enabled']).to be(false) expect(json_response['valid_runner_registrars']).to match_array(%w(project group)) + expect(json_response['ci_max_includes']).to eq(150) end end @@ -819,6 +820,40 @@ end end + context 'with ci_max_includes' do + it 'updates the settings' do + put api("/application/settings", admin), params: { + ci_max_includes: 200 + } + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to include( + 'ci_max_includes' => 200 + ) + end + + it 'allows a zero value' do + put api("/application/settings", admin), params: { + ci_max_includes: 0 + } + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to include( + 'ci_max_includes' => 0 + ) + end + + it 'does not allow a nil value' do + put api("/application/settings", admin), params: { + ci_max_includes: nil + } + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['message']['ci_max_includes']) + .to include(a_string_matching('is not a number')) + end + end + context 'with housekeeping enabled' do it 'at least one of housekeeping_incremental_repack_period or housekeeping_optimize_repository_period is required' do put api("/application/settings", admin), params: {