From 283b75f7b67291867ba1718096894aa6875a3e69 Mon Sep 17 00:00:00 2001 From: janis Date: Fri, 23 Aug 2024 16:33:24 +0200 Subject: [PATCH 01/17] Add specs for the added functionality --- .../ee/projects/update_pages_service.rb | 31 ++++++++++++++- .../ee/projects/update_pages_service_spec.rb | 38 ++++++++++++++++++- 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/ee/app/services/ee/projects/update_pages_service.rb b/ee/app/services/ee/projects/update_pages_service.rb index 146361ee67ebb8..00965738847e51 100644 --- a/ee/app/services/ee/projects/update_pages_service.rb +++ b/ee/app/services/ee/projects/update_pages_service.rb @@ -15,11 +15,38 @@ def pages_deployment_attributes(file, build) private + def fallback_expiry + value = ::Gitlab::CurrentSettings.pages_extra_deployments_default_expiry_seconds + + # A value of 0 means deployments should not expire, in this case return nil + value.seconds.from_now if value.nonzero? + end + + def custom_expire_in + build.pages&.fetch(:expire_in, nil) + end + + def expiry_customised? + custom_expire_in.present? + end + + def should_not_expire? + # If expire_in is set to '0' or 0 (the integer), the deployment should not expire + Integer(custom_expire_in) == 0 + rescue ArgumentError + # If expire_in is set to a duration the above will raise an ArgumentError. In this + # case it does have an expiry. + false + end + def expires_at - return unless ::Gitlab::CurrentSettings.pages_extra_deployments_default_expiry_seconds&.nonzero? return unless extra_deployment? - ::Gitlab::CurrentSettings.pages_extra_deployments_default_expiry_seconds.seconds.from_now + return fallback_expiry unless expiry_customised? + + return if should_not_expire? + + ::Gitlab::Ci::Build::DurationParser.new(custom_expire_in).seconds_from_now end end end diff --git a/ee/spec/services/ee/projects/update_pages_service_spec.rb b/ee/spec/services/ee/projects/update_pages_service_spec.rb index 065c4d19977ea9..26f421896dc377 100644 --- a/ee/spec/services/ee/projects/update_pages_service_spec.rb +++ b/ee/spec/services/ee/projects/update_pages_service_spec.rb @@ -7,7 +7,8 @@ let_it_be(:user) { create(:user) } let(:path_prefix) { nil } - let(:build_options) { { pages: { path_prefix: path_prefix } } } + let(:expire_in) { nil } + let(:build_options) { { pages: { path_prefix: path_prefix, expire_in: expire_in } } } let(:build) { create(:ci_build, :pages, project: project, user: user, options: build_options) } subject(:service) { described_class.new(project, build) } @@ -66,6 +67,41 @@ expect(project.pages_deployments.last.expires_at).to eq(1.hour.from_now) end + context 'and a custom expiry date is given' do + let(:expire_in) { '4 weeks' } + + it 'sets the expiry date to the specified setting', :freeze_time do + expect { expect(service.execute).to include(status: :success) } + .to change { project.pages_deployments.count }.by(1) + + expect(project.pages_deployments.last.expires_at).to eq(4.weeks.from_now) + end + end + + context 'and expiry is disabled' do + context 'with a string' do + let(:expire_in) { '0' } + + it 'sets the expiry date to nil', :freeze_time do + expect { expect(service.execute).to include(status: :success) } + .to change { project.pages_deployments.count }.by(1) + + expect(project.pages_deployments.last.expires_at).to eq(nil) + end + end + + context 'with an integer' do + let(:expire_in) { 0 } + + it 'sets the expiry date to nil', :freeze_time do + expect { expect(service.execute).to include(status: :success) } + .to change { project.pages_deployments.count }.by(1) + + expect(project.pages_deployments.last.expires_at).to eq(nil) + end + end + end + it_behaves_like 'internal event tracking' do let(:event) { 'create_pages_extra_deployment' } let(:category) { 'Projects::UpdatePagesService' } -- GitLab From 3a85a46bd5e4d654f63efa5496cd0c28d02ea469 Mon Sep 17 00:00:00 2001 From: janis Date: Thu, 29 Aug 2024 10:05:54 +0200 Subject: [PATCH 02/17] Add expire_in property to pages CI definition Signed-off-by: janis --- lib/gitlab/ci/config/entry/pages.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/ci/config/entry/pages.rb b/lib/gitlab/ci/config/entry/pages.rb index 57d9e944f51d2d..e61bdb9d69389e 100644 --- a/lib/gitlab/ci/config/entry/pages.rb +++ b/lib/gitlab/ci/config/entry/pages.rb @@ -9,7 +9,7 @@ module Entry # Entry that represents the pages attributes # class Pages < ::Gitlab::Config::Entry::Node - ALLOWED_KEYS = %i[path_prefix].freeze + ALLOWED_KEYS = %i[path_prefix expire_in].freeze include ::Gitlab::Config::Entry::Attributable include ::Gitlab::Config::Entry::Validatable @@ -22,6 +22,7 @@ class Pages < ::Gitlab::Config::Entry::Node with_options allow_nil: true do validates :path_prefix, type: String + validates :expire_in, duration: { parser: ::Gitlab::Ci::Build::DurationParser } end end end -- GitLab From 33b18bf8902ad24c538332243f15599602632be1 Mon Sep 17 00:00:00 2001 From: janis Date: Thu, 29 Aug 2024 14:53:50 +0200 Subject: [PATCH 03/17] Update tests to include all variations --- .../ee/projects/update_pages_service.rb | 20 ++-- .../ee/projects/update_pages_service_spec.rb | 112 +++++++----------- 2 files changed, 59 insertions(+), 73 deletions(-) diff --git a/ee/app/services/ee/projects/update_pages_service.rb b/ee/app/services/ee/projects/update_pages_service.rb index 00965738847e51..511790436c33cf 100644 --- a/ee/app/services/ee/projects/update_pages_service.rb +++ b/ee/app/services/ee/projects/update_pages_service.rb @@ -15,7 +15,7 @@ def pages_deployment_attributes(file, build) private - def fallback_expiry + def fallback_expiry_date value = ::Gitlab::CurrentSettings.pages_extra_deployments_default_expiry_seconds # A value of 0 means deployments should not expire, in this case return nil @@ -26,6 +26,10 @@ def custom_expire_in build.pages&.fetch(:expire_in, nil) end + def custom_expiry_date + ::Gitlab::Ci::Build::DurationParser.new(custom_expire_in).seconds_from_now + end + def expiry_customised? custom_expire_in.present? end @@ -34,19 +38,21 @@ def should_not_expire? # If expire_in is set to '0' or 0 (the integer), the deployment should not expire Integer(custom_expire_in) == 0 rescue ArgumentError - # If expire_in is set to a duration the above will raise an ArgumentError. In this + # If expire_in is set to nil or a duration the above will raise an ArgumentError. In this # case it does have an expiry. false + rescue TypeError + # If expire_in is nil this will raise a TypeError. This too would be false + false end + # returns a datetime for the expiry or may return nil if the deployment should not expire def expires_at - return unless extra_deployment? - - return fallback_expiry unless expiry_customised? - return if should_not_expire? - ::Gitlab::Ci::Build::DurationParser.new(custom_expire_in).seconds_from_now + return custom_expiry_date if expiry_customised? + + fallback_expiry_date if extra_deployment? end end end diff --git a/ee/spec/services/ee/projects/update_pages_service_spec.rb b/ee/spec/services/ee/projects/update_pages_service_spec.rb index 26f421896dc377..7a3ba4d4759972 100644 --- a/ee/spec/services/ee/projects/update_pages_service_spec.rb +++ b/ee/spec/services/ee/projects/update_pages_service_spec.rb @@ -10,6 +10,7 @@ let(:expire_in) { nil } let(:build_options) { { pages: { path_prefix: path_prefix, expire_in: expire_in } } } let(:build) { create(:ci_build, :pages, project: project, user: user, options: build_options) } + let(:multiple_versions_enabled) { true } subject(:service) { described_class.new(project, build) } @@ -19,18 +20,55 @@ before do stub_pages_setting(enabled: true) + allow(::Gitlab::Pages) + .to receive(:multiple_versions_enabled_for?) + .with(build.project) + .and_return(multiple_versions_enabled) + end + + describe 'expiry' do + # Specify a fixed date as now, because we want to reference it in the examples + # and freeze_time does not apply during spec setup + now = Time.utc(2024, 8, 29, 13, 20, 0) + + before do + travel_to now + end + + [ + # prefix default ci_value expected + ['/path_prefix/', 3600, '1 week', now + 1.week], # use the value from ci over the default + ['/path_prefix/', 3600, 0, nil], # a value of 0 prevents the deployment from expiring + ['/path_prefix/', 3600, '0', nil], # 0 can also be a string + ['/path_prefix/', 3600, nil, now + 1.hour], # fall back to the system setting + ['/path_prefix/', 0, nil, nil], # System setting can also be set to 0 (no expiry) + ['/path_prefix/', 0, '1 week', now + 1.week], # but make sure to still use the value from ci + ['', 3600, '1 week', now + 1.week], # main deployments can also be set to expire + ['', 3600, nil, nil] # but they should not do so by default + ].each do |path_prefix, default, ci_value, expected| + context "when path_prefix=\"#{path_prefix}\" and default=#{default.inspect} and expire_in=#{ci_value.inspect}" do + let(:path_prefix) { path_prefix } + let(:expire_in) { ci_value } + + before do + stub_application_setting(pages_extra_deployments_default_expiry_seconds: expire_in_default) + end + + it "sets the expiry date to #{expected.inspect}" do + expect { expect(service.execute).to include(status: :success) } + .to change { project.pages_deployments.count }.by(1) + + expect(project.pages_deployments.last.expires_at).to eq(expected) + end + end + end end context 'when path_prefix is not blank' do let(:path_prefix) { '/path_prefix/' } context 'and pages_multiple_versions is disabled for project' do - before do - allow(::Gitlab::Pages) - .to receive(:multiple_versions_enabled_for?) - .with(build.project) - .and_return(false) - end + let(:multiple_versions_enabled) { false } it 'does not create a new pages_deployment' do expect { expect(service.execute).to include(status: :error) } @@ -45,11 +83,9 @@ end context 'and pages_multiple_versions is enabled for project' do + let(:multiple_versions_enabled) { true } + before do - allow(::Gitlab::Pages) - .to receive(:multiple_versions_enabled_for?) - .with(build.project) - .and_return(true) stub_application_setting(pages_extra_deployments_default_expiry_seconds: 3600) end @@ -67,41 +103,6 @@ expect(project.pages_deployments.last.expires_at).to eq(1.hour.from_now) end - context 'and a custom expiry date is given' do - let(:expire_in) { '4 weeks' } - - it 'sets the expiry date to the specified setting', :freeze_time do - expect { expect(service.execute).to include(status: :success) } - .to change { project.pages_deployments.count }.by(1) - - expect(project.pages_deployments.last.expires_at).to eq(4.weeks.from_now) - end - end - - context 'and expiry is disabled' do - context 'with a string' do - let(:expire_in) { '0' } - - it 'sets the expiry date to nil', :freeze_time do - expect { expect(service.execute).to include(status: :success) } - .to change { project.pages_deployments.count }.by(1) - - expect(project.pages_deployments.last.expires_at).to eq(nil) - end - end - - context 'with an integer' do - let(:expire_in) { 0 } - - it 'sets the expiry date to nil', :freeze_time do - expect { expect(service.execute).to include(status: :success) } - .to change { project.pages_deployments.count }.by(1) - - expect(project.pages_deployments.last.expires_at).to eq(nil) - end - end - end - it_behaves_like 'internal event tracking' do let(:event) { 'create_pages_extra_deployment' } let(:category) { 'Projects::UpdatePagesService' } @@ -111,25 +112,4 @@ end end end - - context 'when path_prefix is blank' do - let(:path_prefix) { '' } - - context 'and pages_multiple_versions is enabled for project' do - before do - allow(::Gitlab::Pages) - .to receive(:multiple_versions_enabled_for?) - .with(build.project) - .and_return(true) - stub_application_setting(pages_extra_deployments_default_expiry_seconds: 3600) - end - - it 'does not set an expiry date', :freeze_time do - expect { expect(service.execute).to include(status: :success) } - .to change { project.pages_deployments.count }.by(1) - - expect(project.pages_deployments.last.expires_at).to be_nil - end - end - end end -- GitLab From 68e9abb49205e87259dab2ef98a450fc93214212 Mon Sep 17 00:00:00 2001 From: janis Date: Fri, 30 Aug 2024 10:06:00 +0200 Subject: [PATCH 04/17] Update the documentation Signed-off-by: janis --- doc/user/project/pages/index.md | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md index 0e9a459607880c..da950bcc998373 100644 --- a/doc/user/project/pages/index.md +++ b/doc/user/project/pages/index.md @@ -175,6 +175,32 @@ The project maintainer can disable this feature on: 1. Deselect the **Use unique domain** checkbox. 1. Select **Save changes**. +## Expiring deployments + +DETAILS: +**Tier:** Premium, Ultimate +**Offering:** GitLab.com, Self-managed, GitLab Dedicated + +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/129534) in GitLab 17.4 + +You can configure your Pages deployments to be automatically deleted after +a period of time has passed by specifying a duration at `pages.expire_in`: + +```yaml +pages: + stage: deploy + script: + - ... + pages: + expire_in: 1 week + artifacts: + paths: + - public +``` + +By default, [extra deployments](#create-multiple-deployments) expire automatically after 24 hours. +To disable this behavior, set `pages.expire_in` to `0`. + ## Create multiple deployments DETAILS: @@ -236,7 +262,11 @@ The number of extra deployments is limited by the root-level namespace. For spec By default, extra deployments expire after 24 hours, after which they are deleted. If you're using a self-hosted instance, your instance admin can -[configure a different duration](../../../administration/pages/index.md#configure-the-default-expiry-for-extra-deployments). +[configure a different default duration](../../../administration/pages/index.md#configure-the-default-expiry-for-extra-deployments). + +To customize the expiry time, [configure `pages.expire_in`](#expiring-deployments) + +To prevent deployments from automatically expiring, set `pages.expire_in` to `0`. ### Path clash -- GitLab From 39543d502f2e3b361ad06a3b81c04a75c7feb747 Mon Sep 17 00:00:00 2001 From: janis Date: Fri, 30 Aug 2024 10:09:12 +0200 Subject: [PATCH 05/17] Fix a failing test after rewording Signed-off-by: janis --- ee/spec/services/ee/projects/update_pages_service_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/spec/services/ee/projects/update_pages_service_spec.rb b/ee/spec/services/ee/projects/update_pages_service_spec.rb index 7a3ba4d4759972..394a87d38a042a 100644 --- a/ee/spec/services/ee/projects/update_pages_service_spec.rb +++ b/ee/spec/services/ee/projects/update_pages_service_spec.rb @@ -51,7 +51,7 @@ let(:expire_in) { ci_value } before do - stub_application_setting(pages_extra_deployments_default_expiry_seconds: expire_in_default) + stub_application_setting(pages_extra_deployments_default_expiry_seconds: default) end it "sets the expiry date to #{expected.inspect}" do -- GitLab From f5a8ee1c138f56cc50f6de9b2527dae000d7031d Mon Sep 17 00:00:00 2001 From: janis Date: Fri, 30 Aug 2024 11:52:13 +0200 Subject: [PATCH 06/17] Improve the documentation Signed-off-by: janis --- app/assets/javascripts/editor/schema/ci.json | 4 ++ doc/ci/yaml/index.md | 44 ++++++++++++++++++++ doc/user/project/pages/index.md | 9 +++- 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/editor/schema/ci.json b/app/assets/javascripts/editor/schema/ci.json index 23fc89a4d6c847..06f72cddcb5aa8 100644 --- a/app/assets/javascripts/editor/schema/ci.json +++ b/app/assets/javascripts/editor/schema/ci.json @@ -2389,6 +2389,10 @@ "path_prefix": { "type": "string", "markdownDescription": "The GitLab Pages URL path prefix used in this version of pages. The given value is converted to lowercase, shortened to 63 bytes, and everything except alphanumeric characters is replaced with a hyphen. Leading and trailing hyphens are not permitted." + }, + "expire_in": { + "type": "string", + "markdownDescription": "How long the deployment should be active. Deployments that have expired are no longer available on the web. Supports a wide variety of formats, e.g. '1 week', '3 mins 4 sec', '2 hrs 20 min', '2h20min', '6 mos 1 day', '47 yrs 6 mos and 4d', '3 weeks and 2 days'. Set to '0' to prevent extra deployments from expiring. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#pagesexpire_in).", } } } diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md index f65cdcacdd4a73..14d5c8cf05df49 100644 --- a/doc/ci/yaml/index.md +++ b/doc/ci/yaml/index.md @@ -3493,6 +3493,50 @@ pages: In this example, a different pages deployment is created for each branch. +### `pages:pages.expire_in` + +DETAILS: +**Tier:** Premium, Ultimate +**Offering:** Self-managed + +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/162826) in GitLab 17.4 + +Use `expire_in` to specify how long a deployment should be available before +it expires. Once the deployment is expired, it will be deactivated by a cron +job running every 10 minutes. + +For extra deployments that expire by default, set the value to `0` to +prevent them from expiring. + +**Keyword type**: Job keyword. You can use it only as part of a `pages` job. + +**Possible inputs**: The expiry time. If no unit is provided, the time is in seconds. +Valid values include: + +- `'42'` +- `42 seconds` +- `3 mins 4 sec` +- `2 hrs 20 min` +- `2h20min` +- `6 mos 1 day` +- `47 yrs 6 mos and 4d` +- `3 weeks and 2 days` +- `never` + +**Example of `pages:pages.expire_in`**: + +```yaml +pages: + stage: deploy + script: + - echo "Pages accessible through ${CI_PAGES_URL}" + pages: + expire_in: 1 week + artifacts: + paths: + - public +``` + ### `parallel` > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/336576) in GitLab 15.9, the maximum value for `parallel` is increased from 50 to 200. diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md index da950bcc998373..a72bbe9f0dfcbf 100644 --- a/doc/user/project/pages/index.md +++ b/doc/user/project/pages/index.md @@ -181,7 +181,7 @@ DETAILS: **Tier:** Premium, Ultimate **Offering:** GitLab.com, Self-managed, GitLab Dedicated -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/129534) in GitLab 17.4 +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/162826) in GitLab 17.4 You can configure your Pages deployments to be automatically deleted after a period of time has passed by specifying a duration at `pages.expire_in`: @@ -201,6 +201,13 @@ pages: By default, [extra deployments](#create-multiple-deployments) expire automatically after 24 hours. To disable this behavior, set `pages.expire_in` to `0`. +Expired deployments are stopped by a cron job that runs every 10 minutes. +Stopped deployments are subsequently deleted by another cron job that also +runs every 10 minutes. To recover stopped deployments that have not yet been +deleted, visit **Deploy > Pages**, toggle "include stopped deployments". If it +has not been deleted yet, it should be included in the deployments below. +Click on the deployment you want to recover to expand and click "restore". + ## Create multiple deployments DETAILS: -- GitLab From d4f1be2101192782857437649d7a8b6eadf1d9d8 Mon Sep 17 00:00:00 2001 From: janis Date: Fri, 30 Aug 2024 12:10:05 +0200 Subject: [PATCH 07/17] Add expire_in to build spec Signed-off-by: janis --- ee/spec/models/ci/build_spec.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ee/spec/models/ci/build_spec.rb b/ee/spec/models/ci/build_spec.rb index c006341eb64946..d093712cc07ef7 100644 --- a/ee/spec/models/ci/build_spec.rb +++ b/ee/spec/models/ci/build_spec.rb @@ -1090,6 +1090,8 @@ true | { pages: { path_prefix: nil } } | { path_prefix: '' } true | { pages: { path_prefix: 'foo' } } | { path_prefix: 'foo' } true | { pages: { path_prefix: '$CI_COMMIT_BRANCH' } } | { path_prefix: 'master' } + true | { pages: { path_prefix: 'foo', expire_in: '1d' } } | { path_prefix: 'master', expire_in: '1d' } + true | { pages: { path_prefix: 'foo', expire_in: 0 } } | { path_prefix: 'master', expire_in: 0 } end with_them do -- GitLab From 1b3e5eff9b79f143db78aa5981b4e21569c3d45f Mon Sep 17 00:00:00 2001 From: janis Date: Fri, 30 Aug 2024 12:39:39 +0200 Subject: [PATCH 08/17] Fix dangling comma in JSON Signed-off-by: janis --- app/assets/javascripts/editor/schema/ci.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/editor/schema/ci.json b/app/assets/javascripts/editor/schema/ci.json index 06f72cddcb5aa8..3c301be815ea2a 100644 --- a/app/assets/javascripts/editor/schema/ci.json +++ b/app/assets/javascripts/editor/schema/ci.json @@ -2392,7 +2392,7 @@ }, "expire_in": { "type": "string", - "markdownDescription": "How long the deployment should be active. Deployments that have expired are no longer available on the web. Supports a wide variety of formats, e.g. '1 week', '3 mins 4 sec', '2 hrs 20 min', '2h20min', '6 mos 1 day', '47 yrs 6 mos and 4d', '3 weeks and 2 days'. Set to '0' to prevent extra deployments from expiring. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#pagesexpire_in).", + "markdownDescription": "How long the deployment should be active. Deployments that have expired are no longer available on the web. Supports a wide variety of formats, e.g. '1 week', '3 mins 4 sec', '2 hrs 20 min', '2h20min', '6 mos 1 day', '47 yrs 6 mos and 4d', '3 weeks and 2 days'. Set to '0' to prevent extra deployments from expiring. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#pagesexpire_in)." } } } -- GitLab From c66c6b1fba993c03447b69a255992971927445ca Mon Sep 17 00:00:00 2001 From: janis Date: Fri, 30 Aug 2024 14:38:31 +0200 Subject: [PATCH 09/17] Use table layout for expiry spec --- .../ee/projects/update_pages_service_spec.rb | 46 +++++++++---------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/ee/spec/services/ee/projects/update_pages_service_spec.rb b/ee/spec/services/ee/projects/update_pages_service_spec.rb index 394a87d38a042a..a17debe375ed81 100644 --- a/ee/spec/services/ee/projects/update_pages_service_spec.rb +++ b/ee/spec/services/ee/projects/update_pages_service_spec.rb @@ -3,11 +3,14 @@ require 'spec_helper' RSpec.describe Projects::UpdatePagesService, feature_category: :pages do + using RSpec::Parameterized::TableSyntax + let_it_be(:project) { create(:project) } let_it_be(:user) { create(:user) } let(:path_prefix) { nil } let(:expire_in) { nil } + let(:system_default_expiry) { 86400 } let(:build_options) { { pages: { path_prefix: path_prefix, expire_in: expire_in } } } let(:build) { create(:ci_build, :pages, project: project, user: user, options: build_options) } let(:multiple_versions_enabled) { true } @@ -19,6 +22,7 @@ end before do + stub_application_setting(pages_extra_deployments_default_expiry_seconds: system_default_expiry) stub_pages_setting(enabled: true) allow(::Gitlab::Pages) .to receive(:multiple_versions_enabled_for?) @@ -35,31 +39,23 @@ travel_to now end - [ - # prefix default ci_value expected - ['/path_prefix/', 3600, '1 week', now + 1.week], # use the value from ci over the default - ['/path_prefix/', 3600, 0, nil], # a value of 0 prevents the deployment from expiring - ['/path_prefix/', 3600, '0', nil], # 0 can also be a string - ['/path_prefix/', 3600, nil, now + 1.hour], # fall back to the system setting - ['/path_prefix/', 0, nil, nil], # System setting can also be set to 0 (no expiry) - ['/path_prefix/', 0, '1 week', now + 1.week], # but make sure to still use the value from ci - ['', 3600, '1 week', now + 1.week], # main deployments can also be set to expire - ['', 3600, nil, nil] # but they should not do so by default - ].each do |path_prefix, default, ci_value, expected| - context "when path_prefix=\"#{path_prefix}\" and default=#{default.inspect} and expire_in=#{ci_value.inspect}" do - let(:path_prefix) { path_prefix } - let(:expire_in) { ci_value } - - before do - stub_application_setting(pages_extra_deployments_default_expiry_seconds: default) - end - - it "sets the expiry date to #{expected.inspect}" do - expect { expect(service.execute).to include(status: :success) } - .to change { project.pages_deployments.count }.by(1) - - expect(project.pages_deployments.last.expires_at).to eq(expected) - end + where(:path_prefix, :system_default_expiry, :expire_in, :result) do + '/path_prefix/' | 3600 | '1 week' | (now + 1.week) # use the value from ci over the default + '/path_prefix/' | 3600 | 0 | nil # a value of 0 prevents the deployment from expiring + '/path_prefix/' | 3600 | '0' | nil # 0 can also be a string + '/path_prefix/' | 3600 | nil | (now + 1.hour) # fall back to the system setting + '/path_prefix/' | 0 | nil | nil # System setting can also be set to 0 (no expiry) + '/path_prefix/' | 0 | '1 week' | (now + 1.week) # but make sure to still use the value from ci + '' | 3600 | '1 week' | (now + 1.week) # main deployments can also be set to expire + '' | 3600 | nil | nil # but they should not do so by default + end + + with_them do + it "sets the expiry date to the expected value" do + expect { expect(service.execute).to include(status: :success) } + .to change { project.pages_deployments.count }.by(1) + + expect(project.pages_deployments.last.expires_at).to eq(result) end end end -- GitLab From c0f0e58d386c1575ef77736710d69f09d61f7fc6 Mon Sep 17 00:00:00 2001 From: janis Date: Fri, 30 Aug 2024 14:42:29 +0200 Subject: [PATCH 10/17] Fix path_prefix for other examples --- ee/spec/models/ci/build_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ee/spec/models/ci/build_spec.rb b/ee/spec/models/ci/build_spec.rb index d093712cc07ef7..06cb3dfafbcd84 100644 --- a/ee/spec/models/ci/build_spec.rb +++ b/ee/spec/models/ci/build_spec.rb @@ -1090,8 +1090,8 @@ true | { pages: { path_prefix: nil } } | { path_prefix: '' } true | { pages: { path_prefix: 'foo' } } | { path_prefix: 'foo' } true | { pages: { path_prefix: '$CI_COMMIT_BRANCH' } } | { path_prefix: 'master' } - true | { pages: { path_prefix: 'foo', expire_in: '1d' } } | { path_prefix: 'master', expire_in: '1d' } - true | { pages: { path_prefix: 'foo', expire_in: 0 } } | { path_prefix: 'master', expire_in: 0 } + true | { pages: { path_prefix: 'foo', expire_in: '1d' } } | { path_prefix: 'foo', expire_in: '1d' } + true | { pages: { path_prefix: 'foo', expire_in: 0 } } | { path_prefix: 'foo', expire_in: 0 } end with_them do -- GitLab From c7944d016caf0c86834c945745687c8ce9b03b48 Mon Sep 17 00:00:00 2001 From: janis Date: Fri, 30 Aug 2024 15:13:32 +0200 Subject: [PATCH 11/17] Fix offering levels in docs Signed-off-by: janis --- doc/ci/yaml/index.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md index 14d5c8cf05df49..1e929e2bc440a0 100644 --- a/doc/ci/yaml/index.md +++ b/doc/ci/yaml/index.md @@ -3461,7 +3461,7 @@ as an artifact and published with GitLab Pages. DETAILS: **Tier:** Premium, Ultimate -**Offering:** Self-managed +**Offering:** GitLab.com, Self-managed, GitLab Dedicated **Status:** Experiment > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/129534) in GitLab 16.7 as an [experiment](../../policy/experiment-beta-support.md) [with a flag](../../user/feature_flags.md) named `pages_multiple_versions_setting`, disabled by default. @@ -3497,9 +3497,9 @@ In this example, a different pages deployment is created for each branch. DETAILS: **Tier:** Premium, Ultimate -**Offering:** Self-managed +**Offering:** GitLab.com, Self-managed, GitLab Dedicated -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/162826) in GitLab 17.4 +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/456478) in GitLab 17.4 Use `expire_in` to specify how long a deployment should be available before it expires. Once the deployment is expired, it will be deactivated by a cron -- GitLab From cb6ffb616ba8d41141cacfbec3d1f526ead4e068 Mon Sep 17 00:00:00 2001 From: janis Date: Fri, 30 Aug 2024 15:21:48 +0200 Subject: [PATCH 12/17] Apply reviewer suggestions Signed-off-by: janis --- doc/user/project/pages/index.md | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md index a72bbe9f0dfcbf..761cc7e53ad34a 100644 --- a/doc/user/project/pages/index.md +++ b/doc/user/project/pages/index.md @@ -203,10 +203,23 @@ To disable this behavior, set `pages.expire_in` to `0`. Expired deployments are stopped by a cron job that runs every 10 minutes. Stopped deployments are subsequently deleted by another cron job that also -runs every 10 minutes. To recover stopped deployments that have not yet been -deleted, visit **Deploy > Pages**, toggle "include stopped deployments". If it -has not been deleted yet, it should be included in the deployments below. -Click on the deployment you want to recover to expand and click "restore". +runs every 10 minutes. To recover it, follow the steps described in +[Recover a stopped deployment](#recover-a-stopped-deployment) + +### Recover a stopped deployment + +Prerequisites: + +- You must have at least the Maintainer role for the project. + +To recover a stopped deployment that has not yet been deleted: + +1. On the left sidebar, select **Search or go to** and find your project. +1. Select **Deploy > Pages**. +1. Near **Deployments** toggle **Include stopped deployments**. + If your deployment has not been deleted yet, it should be included in the + list. +1. Expand the deployment you want to recover and select **Restore**. ## Create multiple deployments -- GitLab From 99772c0afa63bb00e7608a5e5b7d3c28fc15202c Mon Sep 17 00:00:00 2001 From: janis Date: Fri, 30 Aug 2024 15:47:50 +0200 Subject: [PATCH 13/17] Apply reviewer suggestions Signed-off-by: janis --- doc/ci/yaml/index.md | 2 +- doc/user/project/pages/index.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md index 1e929e2bc440a0..02bc90f645310e 100644 --- a/doc/ci/yaml/index.md +++ b/doc/ci/yaml/index.md @@ -3502,7 +3502,7 @@ DETAILS: > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/456478) in GitLab 17.4 Use `expire_in` to specify how long a deployment should be available before -it expires. Once the deployment is expired, it will be deactivated by a cron +it expires. After the deployment is expired, it's deactivated by a cron job running every 10 minutes. For extra deployments that expire by default, set the value to `0` to diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md index 761cc7e53ad34a..e00fd06a6651f5 100644 --- a/doc/user/project/pages/index.md +++ b/doc/user/project/pages/index.md @@ -181,7 +181,7 @@ DETAILS: **Tier:** Premium, Ultimate **Offering:** GitLab.com, Self-managed, GitLab Dedicated -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/162826) in GitLab 17.4 +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/162826) in GitLab 17.4. You can configure your Pages deployments to be automatically deleted after a period of time has passed by specifying a duration at `pages.expire_in`: @@ -284,7 +284,7 @@ By default, extra deployments expire after 24 hours, after which they are delete If you're using a self-hosted instance, your instance admin can [configure a different default duration](../../../administration/pages/index.md#configure-the-default-expiry-for-extra-deployments). -To customize the expiry time, [configure `pages.expire_in`](#expiring-deployments) +To customize the expiry time, [configure `pages.expire_in`](#expiring-deployments). To prevent deployments from automatically expiring, set `pages.expire_in` to `0`. -- GitLab From e094a77d3692286e34201d09b8c7f3c1d823d7ec Mon Sep 17 00:00:00 2001 From: Marcin Sedlak-Jakubowski Date: Fri, 30 Aug 2024 14:50:44 +0000 Subject: [PATCH 14/17] Apply more small style fixes --- doc/ci/yaml/index.md | 3 +-- doc/user/project/pages/index.md | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md index 02bc90f645310e..ff14916892151c 100644 --- a/doc/ci/yaml/index.md +++ b/doc/ci/yaml/index.md @@ -3505,8 +3505,7 @@ Use `expire_in` to specify how long a deployment should be available before it expires. After the deployment is expired, it's deactivated by a cron job running every 10 minutes. -For extra deployments that expire by default, set the value to `0` to -prevent them from expiring. +Extra deployments expire by default. To prevent them from expiring, set the value to `0`. **Keyword type**: Job keyword. You can use it only as part of a `pages` job. diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md index e00fd06a6651f5..ce5757abddc87c 100644 --- a/doc/user/project/pages/index.md +++ b/doc/user/project/pages/index.md @@ -204,7 +204,7 @@ To disable this behavior, set `pages.expire_in` to `0`. Expired deployments are stopped by a cron job that runs every 10 minutes. Stopped deployments are subsequently deleted by another cron job that also runs every 10 minutes. To recover it, follow the steps described in -[Recover a stopped deployment](#recover-a-stopped-deployment) +[Recover a stopped deployment](#recover-a-stopped-deployment). ### Recover a stopped deployment @@ -216,7 +216,7 @@ To recover a stopped deployment that has not yet been deleted: 1. On the left sidebar, select **Search or go to** and find your project. 1. Select **Deploy > Pages**. -1. Near **Deployments** toggle **Include stopped deployments**. +1. Near **Deployments** turn on the **Include stopped deployments** toggle. If your deployment has not been deleted yet, it should be included in the list. 1. Expand the deployment you want to recover and select **Restore**. -- GitLab From 761fbd21cd2bfc515d802f1dd96f6481f53ce634 Mon Sep 17 00:00:00 2001 From: janis Date: Mon, 2 Sep 2024 15:59:32 +0200 Subject: [PATCH 15/17] Use "never" instead of 0 to disable expiry This commit uses "never" to replace the need to set expiry to 0 Signed-off-by: janis --- app/assets/javascripts/editor/schema/ci.json | 2 +- doc/ci/yaml/index.md | 3 ++- doc/user/project/pages/index.md | 5 +++-- .../services/ee/projects/update_pages_service.rb | 16 +--------------- ee/spec/models/ci/build_spec.rb | 2 +- .../ee/projects/update_pages_service_spec.rb | 3 +-- 6 files changed, 9 insertions(+), 22 deletions(-) diff --git a/app/assets/javascripts/editor/schema/ci.json b/app/assets/javascripts/editor/schema/ci.json index 3c301be815ea2a..1c2613ba724d8e 100644 --- a/app/assets/javascripts/editor/schema/ci.json +++ b/app/assets/javascripts/editor/schema/ci.json @@ -2392,7 +2392,7 @@ }, "expire_in": { "type": "string", - "markdownDescription": "How long the deployment should be active. Deployments that have expired are no longer available on the web. Supports a wide variety of formats, e.g. '1 week', '3 mins 4 sec', '2 hrs 20 min', '2h20min', '6 mos 1 day', '47 yrs 6 mos and 4d', '3 weeks and 2 days'. Set to '0' to prevent extra deployments from expiring. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#pagesexpire_in)." + "markdownDescription": "How long the deployment should be active. Deployments that have expired are no longer available on the web. Supports a wide variety of formats, e.g. '1 week', '3 mins 4 sec', '2 hrs 20 min', '2h20min', '6 mos 1 day', '47 yrs 6 mos and 4d', '3 weeks and 2 days'. Set to 'never' to prevent extra deployments from expiring. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#pagesexpire_in)." } } } diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md index ff14916892151c..bcf7a4ecbed907 100644 --- a/doc/ci/yaml/index.md +++ b/doc/ci/yaml/index.md @@ -3505,7 +3505,8 @@ Use `expire_in` to specify how long a deployment should be available before it expires. After the deployment is expired, it's deactivated by a cron job running every 10 minutes. -Extra deployments expire by default. To prevent them from expiring, set the value to `0`. +Extra deployments expire by default. To prevent them from expiring, set the +value to `never`. **Keyword type**: Job keyword. You can use it only as part of a `pages` job. diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md index ce5757abddc87c..7f76363f11c52b 100644 --- a/doc/user/project/pages/index.md +++ b/doc/user/project/pages/index.md @@ -199,7 +199,7 @@ pages: ``` By default, [extra deployments](#create-multiple-deployments) expire automatically after 24 hours. -To disable this behavior, set `pages.expire_in` to `0`. +To disable this behavior, set `pages.expire_in` to `never`. Expired deployments are stopped by a cron job that runs every 10 minutes. Stopped deployments are subsequently deleted by another cron job that also @@ -286,7 +286,8 @@ If you're using a self-hosted instance, your instance admin can To customize the expiry time, [configure `pages.expire_in`](#expiring-deployments). -To prevent deployments from automatically expiring, set `pages.expire_in` to `0`. +To prevent deployments from automatically expiring, set `pages.expire_in` to +`never`. ### Path clash diff --git a/ee/app/services/ee/projects/update_pages_service.rb b/ee/app/services/ee/projects/update_pages_service.rb index 511790436c33cf..b9c5c826a8e939 100644 --- a/ee/app/services/ee/projects/update_pages_service.rb +++ b/ee/app/services/ee/projects/update_pages_service.rb @@ -34,22 +34,8 @@ def expiry_customised? custom_expire_in.present? end - def should_not_expire? - # If expire_in is set to '0' or 0 (the integer), the deployment should not expire - Integer(custom_expire_in) == 0 - rescue ArgumentError - # If expire_in is set to nil or a duration the above will raise an ArgumentError. In this - # case it does have an expiry. - false - rescue TypeError - # If expire_in is nil this will raise a TypeError. This too would be false - false - end - - # returns a datetime for the expiry or may return nil if the deployment should not expire + # returns a datetime for the expiry or may return nil if expire_in='never' def expires_at - return if should_not_expire? - return custom_expiry_date if expiry_customised? fallback_expiry_date if extra_deployment? diff --git a/ee/spec/models/ci/build_spec.rb b/ee/spec/models/ci/build_spec.rb index 06cb3dfafbcd84..c8d7a2b6159b98 100644 --- a/ee/spec/models/ci/build_spec.rb +++ b/ee/spec/models/ci/build_spec.rb @@ -1091,7 +1091,7 @@ true | { pages: { path_prefix: 'foo' } } | { path_prefix: 'foo' } true | { pages: { path_prefix: '$CI_COMMIT_BRANCH' } } | { path_prefix: 'master' } true | { pages: { path_prefix: 'foo', expire_in: '1d' } } | { path_prefix: 'foo', expire_in: '1d' } - true | { pages: { path_prefix: 'foo', expire_in: 0 } } | { path_prefix: 'foo', expire_in: 0 } + true | { pages: { path_prefix: 'foo', expire_in: 'never' } } | { path_prefix: 'foo', expire_in: 'never' } end with_them do diff --git a/ee/spec/services/ee/projects/update_pages_service_spec.rb b/ee/spec/services/ee/projects/update_pages_service_spec.rb index a17debe375ed81..1afb4c7b2cd931 100644 --- a/ee/spec/services/ee/projects/update_pages_service_spec.rb +++ b/ee/spec/services/ee/projects/update_pages_service_spec.rb @@ -41,8 +41,7 @@ where(:path_prefix, :system_default_expiry, :expire_in, :result) do '/path_prefix/' | 3600 | '1 week' | (now + 1.week) # use the value from ci over the default - '/path_prefix/' | 3600 | 0 | nil # a value of 0 prevents the deployment from expiring - '/path_prefix/' | 3600 | '0' | nil # 0 can also be a string + '/path_prefix/' | 3600 | 'never' | nil # a value of 'never' prevents the deployment from expiring '/path_prefix/' | 3600 | nil | (now + 1.hour) # fall back to the system setting '/path_prefix/' | 0 | nil | nil # System setting can also be set to 0 (no expiry) '/path_prefix/' | 0 | '1 week' | (now + 1.week) # but make sure to still use the value from ci -- GitLab From 1e039cd767b8b0a5d2201241ca513da9772edae9 Mon Sep 17 00:00:00 2001 From: janis Date: Mon, 2 Sep 2024 16:00:00 +0200 Subject: [PATCH 16/17] Add unit tests Signed-off-by: janis --- spec/lib/gitlab/ci/config/entry/pages_spec.rb | 67 ++++++++++++++++--- 1 file changed, 57 insertions(+), 10 deletions(-) diff --git a/spec/lib/gitlab/ci/config/entry/pages_spec.rb b/spec/lib/gitlab/ci/config/entry/pages_spec.rb index 0ee692a443e6ec..be2cbc23b3b037 100644 --- a/spec/lib/gitlab/ci/config/entry/pages_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/pages_spec.rb @@ -17,31 +17,78 @@ context 'when value is a hash' do context 'when the hash is valid' do - let(:config) { { path_prefix: 'prefix' } } + let(:config) { { path_prefix: 'prefix', expire_in: '1 day' } } it 'is valid' do expect(entry).to be_valid expect(entry.value).to eq({ - path_prefix: 'prefix' + path_prefix: 'prefix', + expire_in: '1 day' }) end end - context 'when path_prefix key is not a string' do - let(:config) { { path_prefix: 1 } } + context 'when hash contains not allowed keys' do + let(:config) { { unknown: 'echo' } } it 'is invalid' do expect(entry).not_to be_valid - expect(entry.errors).to include('pages path prefix should be a string') + expect(entry.errors).to include('pages config contains unknown keys: unknown') end end - context 'when hash contains not allowed keys' do - let(:config) { { unknown: 'echo' } } + context 'when it specifies path_prefix' do + context 'and it is not a string' do + let(:config) { { path_prefix: 1 } } - it 'is invalid' do - expect(entry).not_to be_valid - expect(entry.errors).to include('pages config contains unknown keys: unknown') + it 'is invalid' do + expect(entry).not_to be_valid + expect(entry.errors).to include('pages path prefix should be a string') + end + end + end + + context 'when it specifies expire_in' do + context 'and it is a duration string' do + let(:config) { { expire_in: '1 day' } } + + it 'is valid' do + expect(entry).to be_valid + expect(entry.value).to eq({ + expire_in: '1 day' + }) + end + end + + context 'and it is never' do + let(:config) { { expire_in: 'never' } } + + it 'is valid' do + expect(entry).to be_valid + expect(entry.value).to eq({ + expire_in: 'never' + }) + end + end + + context 'and it is nil' do + let(:config) { { expire_in: nil } } + + it 'is valid' do + expect(entry).to be_valid + expect(entry.value).to eq({ + expire_in: nil + }) + end + end + + context 'and it is an invalid duration' do + let(:config) { { expire_in: 'some string that cant be parsed' } } + + it 'is valid' do + expect(entry).not_to be_valid + expect(entry.errors).to include('pages expire in should be a duration') + end end end end -- GitLab From 0fe006fe2bd925839aa6cb1616730050f20e5df4 Mon Sep 17 00:00:00 2001 From: janis Date: Tue, 3 Sep 2024 14:49:31 +0200 Subject: [PATCH 17/17] Add paragraph about stopped deployments Signed-off-by: janis --- doc/user/project/pages/index.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md index 7f76363f11c52b..84d546ca920498 100644 --- a/doc/user/project/pages/index.md +++ b/doc/user/project/pages/index.md @@ -206,6 +206,10 @@ Stopped deployments are subsequently deleted by another cron job that also runs every 10 minutes. To recover it, follow the steps described in [Recover a stopped deployment](#recover-a-stopped-deployment). +A stopped or deleted deployment is no longer available on the web. Users will +see a 404 Not found error page at its URL, until another deployment is created +with the same URL configuration. + ### Recover a stopped deployment Prerequisites: -- GitLab