Commit f368be61 authored by Fabio Pitino's avatar Fabio Pitino

Merge branch '276583-make-keep-latest-artifact-an-instance-level-configuration' into 'master'

Make "keep latest artifact" an instance-level configuration

See merge request gitlab-org/gitlab!50889
parents 2c6adeb8 af89c610
query getKeepLatestArtifactApplicationSetting {
ciApplicationSettings {
keepLatestArtifact
}
}
...@@ -2,12 +2,22 @@ ...@@ -2,12 +2,22 @@
import { GlAlert, GlFormCheckbox, GlLink } from '@gitlab/ui'; import { GlAlert, GlFormCheckbox, GlLink } from '@gitlab/ui';
import { __ } from '~/locale'; import { __ } from '~/locale';
import GetKeepLatestArtifactProjectSetting from './graphql/queries/get_keep_latest_artifact_project_setting.query.graphql'; import GetKeepLatestArtifactProjectSetting from './graphql/queries/get_keep_latest_artifact_project_setting.query.graphql';
import GetKeepLatestArtifactApplicationSetting from './graphql/queries/get_keep_latest_artifact_application_setting.query.graphql';
import UpdateKeepLatestArtifactProjectSetting from './graphql/mutations/update_keep_latest_artifact_project_setting.mutation.graphql'; import UpdateKeepLatestArtifactProjectSetting from './graphql/mutations/update_keep_latest_artifact_project_setting.mutation.graphql';
const FETCH_ERROR = __('There was a problem fetching the keep latest artifact setting.');
const UPDATE_ERROR = __('There was a problem updating the keep latest artifact setting.');
export default { export default {
errors: {
fetchError: __('There was a problem fetching the keep latest artifacts setting.'),
updateError: __('There was a problem updating the keep latest artifacts setting.'),
},
i18n: {
enabledHelpText: __(
'The latest artifacts created by jobs in the most recent successful pipeline will be stored.',
),
disabledHelpText: __('This feature is disabled at the instance level.'),
helpLinkText: __('More information'),
checkboxText: __('Keep artifacts from most recent successful jobs'),
},
components: { components: {
GlAlert, GlAlert,
GlFormCheckbox, GlFormCheckbox,
...@@ -33,21 +43,33 @@ export default { ...@@ -33,21 +43,33 @@ export default {
return data.project?.ciCdSettings?.keepLatestArtifact; return data.project?.ciCdSettings?.keepLatestArtifact;
}, },
error() { error() {
this.reportError(FETCH_ERROR); this.reportError(this.$options.errors.fetchError);
},
},
projectSettingDisabled: {
query: GetKeepLatestArtifactApplicationSetting,
update(data) {
return !data.ciApplicationSettings?.keepLatestArtifact;
}, },
}, },
}, },
data() { data() {
return { return {
keepLatestArtifact: true, keepLatestArtifact: null,
errorMessage: '', errorMessage: '',
isAlertDismissed: false, isAlertDismissed: false,
projectSettingDisabled: true,
}; };
}, },
computed: { computed: {
shouldShowAlert() { shouldShowAlert() {
return this.errorMessage && !this.isAlertDismissed; return this.errorMessage && !this.isAlertDismissed;
}, },
helpText() {
return this.projectSettingDisabled
? this.$options.i18n.disabledHelpText
: this.$options.i18n.enabledHelpText;
},
}, },
methods: { methods: {
reportError(error) { reportError(error) {
...@@ -65,10 +87,10 @@ export default { ...@@ -65,10 +87,10 @@ export default {
}); });
if (data.ciCdSettingsUpdate.errors.length) { if (data.ciCdSettingsUpdate.errors.length) {
this.reportError(UPDATE_ERROR); this.reportError(this.$options.errors.updateError);
} }
} catch (error) { } catch (error) {
this.reportError(UPDATE_ERROR); this.reportError(this.$options.errors.updateError);
} }
}, },
}, },
...@@ -84,16 +106,13 @@ export default { ...@@ -84,16 +106,13 @@ export default {
@dismiss="isAlertDismissed = true" @dismiss="isAlertDismissed = true"
>{{ errorMessage }}</gl-alert >{{ errorMessage }}</gl-alert
> >
<gl-form-checkbox v-model="keepLatestArtifact" @change="updateSetting" <gl-form-checkbox
><b class="gl-mr-3">{{ __('Keep artifacts from most recent successful jobs') }}</b> v-model="keepLatestArtifact"
<gl-link :href="helpPagePath">{{ __('More information') }}</gl-link></gl-form-checkbox :disabled="projectSettingDisabled"
> @change="updateSetting"
<p> ><strong class="gl-mr-3">{{ $options.i18n.checkboxText }}</strong>
{{ <gl-link :href="helpPagePath">{{ $options.i18n.helpLinkText }}</gl-link>
__( <template v-if="!$apollo.loading" #help>{{ helpText }}</template>
'The latest artifacts created by jobs in the most recent successful pipeline will be stored.', </gl-form-checkbox>
)
}}
</p>
</div> </div>
</template> </template>
# frozen_string_literal: true
module Types
module Ci
class ApplicationSettingType < BaseObject
graphql_name 'CiApplicationSettings'
authorize :read_application_setting
field :keep_latest_artifact, GraphQL::BOOLEAN_TYPE, null: true,
description: 'Whether to keep the latest jobs artifacts.'
end
end
end
...@@ -14,7 +14,8 @@ module Types ...@@ -14,7 +14,8 @@ module Types
description: 'Whether merge trains are enabled.', description: 'Whether merge trains are enabled.',
method: :merge_trains_enabled? method: :merge_trains_enabled?
field :keep_latest_artifact, GraphQL::BOOLEAN_TYPE, null: true, field :keep_latest_artifact, GraphQL::BOOLEAN_TYPE, null: true,
description: 'Whether to keep the latest builds artifacts.' description: 'Whether to keep the latest builds artifacts.',
method: :keep_latest_artifacts_available?
field :project, Types::ProjectType, null: true, field :project, Types::ProjectType, null: true,
description: 'Project the CI/CD settings belong to.' description: 'Project the CI/CD settings belong to.'
end end
......
...@@ -87,6 +87,10 @@ module Types ...@@ -87,6 +87,10 @@ module Types
description: 'Get statistics on the instance.', description: 'Get statistics on the instance.',
resolver: Resolvers::Admin::Analytics::InstanceStatistics::MeasurementsResolver resolver: Resolvers::Admin::Analytics::InstanceStatistics::MeasurementsResolver
field :ci_application_settings, Types::Ci::ApplicationSettingType,
null: true,
description: 'CI related settings that apply to the entire instance.'
field :runner_platforms, Types::Ci::RunnerPlatformType.connection_type, field :runner_platforms, Types::Ci::RunnerPlatformType.connection_type,
null: true, description: 'Supported runner platforms.', null: true, description: 'Supported runner platforms.',
resolver: Resolvers::Ci::RunnerPlatformsResolver resolver: Resolvers::Ci::RunnerPlatformsResolver
...@@ -128,6 +132,14 @@ module Types ...@@ -128,6 +132,14 @@ module Types
def current_user def current_user
context[:current_user] context[:current_user]
end end
def ci_application_settings
application_settings
end
def application_settings
Gitlab::CurrentSettings.current_application_settings
end
end end
end end
......
...@@ -339,7 +339,8 @@ module ApplicationSettingsHelper ...@@ -339,7 +339,8 @@ module ApplicationSettingsHelper
:container_registry_delete_tags_service_timeout, :container_registry_delete_tags_service_timeout,
:rate_limiting_response_text, :rate_limiting_response_text,
:container_registry_expiration_policies_worker_capacity, :container_registry_expiration_policies_worker_capacity,
:container_registry_cleanup_tags_service_max_list_size :container_registry_cleanup_tags_service_max_list_size,
:keep_latest_artifact
] ]
end end
......
...@@ -525,6 +525,10 @@ class ApplicationSetting < ApplicationRecord ...@@ -525,6 +525,10 @@ class ApplicationSetting < ApplicationRecord
current_without_cache current_without_cache
end end
def self.find_or_create_without_cache
current_without_cache || create_from_defaults
end
# Due to the frequency with which settings are accessed, it is # Due to the frequency with which settings are accessed, it is
# likely that during a backup restore a running GitLab process # likely that during a backup restore a running GitLab process
# will insert a new `application_settings` row before the # will insert a new `application_settings` row before the
......
...@@ -411,7 +411,7 @@ class Project < ApplicationRecord ...@@ -411,7 +411,7 @@ class Project < ApplicationRecord
delegate :dashboard_timezone, to: :metrics_setting, allow_nil: true, prefix: true delegate :dashboard_timezone, to: :metrics_setting, allow_nil: true, prefix: true
delegate :default_git_depth, :default_git_depth=, to: :ci_cd_settings, prefix: :ci delegate :default_git_depth, :default_git_depth=, to: :ci_cd_settings, prefix: :ci
delegate :forward_deployment_enabled, :forward_deployment_enabled=, :forward_deployment_enabled?, to: :ci_cd_settings, prefix: :ci delegate :forward_deployment_enabled, :forward_deployment_enabled=, :forward_deployment_enabled?, to: :ci_cd_settings, prefix: :ci
delegate :keep_latest_artifact, :keep_latest_artifact=, :keep_latest_artifact?, to: :ci_cd_settings, prefix: :ci delegate :keep_latest_artifact, :keep_latest_artifact=, :keep_latest_artifact?, :keep_latest_artifacts_available?, to: :ci_cd_settings
delegate :restrict_user_defined_variables, :restrict_user_defined_variables=, :restrict_user_defined_variables?, delegate :restrict_user_defined_variables, :restrict_user_defined_variables=, :restrict_user_defined_variables?,
to: :ci_cd_settings to: :ci_cd_settings
delegate :actual_limits, :actual_plan_name, to: :namespace, allow_nil: true delegate :actual_limits, :actual_plan_name, to: :namespace, allow_nil: true
...@@ -837,8 +837,12 @@ class Project < ApplicationRecord ...@@ -837,8 +837,12 @@ class Project < ApplicationRecord
webide_pipelines.running_or_pending.for_user(user) webide_pipelines.running_or_pending.for_user(user)
end end
def latest_pipeline_locked def default_pipeline_lock
ci_keep_latest_artifact? ? :artifacts_locked : :unlocked if keep_latest_artifacts_available?
return :artifacts_locked
end
:unlocked
end end
def autoclose_referenced_issues def autoclose_referenced_issues
......
...@@ -21,6 +21,11 @@ class ProjectCiCdSetting < ApplicationRecord ...@@ -21,6 +21,11 @@ class ProjectCiCdSetting < ApplicationRecord
super && ::Feature.enabled?(:forward_deployment_enabled, project, default_enabled: true) super && ::Feature.enabled?(:forward_deployment_enabled, project, default_enabled: true)
end end
def keep_latest_artifacts_available?
# The project level feature can only be enabled when the feature is enabled instance wide
Gitlab::CurrentSettings.current_application_settings.keep_latest_artifact? && keep_latest_artifact?
end
private private
def set_default_git_depth def set_default_git_depth
......
# frozen_string_literal: true
class ApplicationSettingPolicy < BasePolicy # rubocop:disable Gitlab/NamespacedClass
rule { admin }.enable :read_application_setting
end
...@@ -41,6 +41,14 @@ ...@@ -41,6 +41,14 @@
.form-text.text-muted .form-text.text-muted
= html_escape(_("Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: %{code_open}4 mins 2 sec%{code_close}, %{code_open}2h42min%{code_close}.")) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe } = html_escape(_("Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: %{code_open}4 mins 2 sec%{code_close}, %{code_open}2h42min%{code_close}.")) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
= link_to sprite_icon('question-o'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'default-artifacts-expiration') = link_to sprite_icon('question-o'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'default-artifacts-expiration')
.form-group
.form-check
= f.check_box :keep_latest_artifact, class: 'form-check-input'
= f.label :keep_latest_artifact, class: 'form-check-label' do
%strong
= s_('AdminSettings|Keep the latest artifacts for all jobs in the latest successful pipelines')
.form-text.text-muted
= s_('AdminSettings|The latest artifacts for all jobs in the most recent successful pipelines in each project are stored and do not expire.')
.form-group .form-group
= f.label :archive_builds_in_human_readable, _('Archive jobs'), class: 'label-bold' = f.label :archive_builds_in_human_readable, _('Archive jobs'), class: 'label-bold'
= f.text_field :archive_builds_in_human_readable, class: 'form-control gl-form-input', placeholder: 'never' = f.text_field :archive_builds_in_human_readable, class: 'form-control gl-form-input', placeholder: 'never'
......
---
title: Add keep latest artifact option for instances
merge_request: 50889
author:
type: added
# frozen_string_literal: true
class AddKeepLatestArtifactsToApplicationSettings < ActiveRecord::Migration[6.0]
DOWNTIME = false
def change
# This is named keep_latest_artifact for consistency with the project level setting but
# turning it on keeps all (multiple) artifacts on the latest pipeline per ref
add_column :application_settings, :keep_latest_artifact, :boolean, default: true, null: false
end
end
a0561e52982756aded22563e833ab8005b4f45b84c81e872dd8c7188aeb84434
\ No newline at end of file
...@@ -9412,6 +9412,7 @@ CREATE TABLE application_settings ( ...@@ -9412,6 +9412,7 @@ CREATE TABLE application_settings (
enforce_ssh_key_expiration boolean DEFAULT false NOT NULL, enforce_ssh_key_expiration boolean DEFAULT false NOT NULL,
git_two_factor_session_expiry integer DEFAULT 15 NOT NULL, git_two_factor_session_expiry integer DEFAULT 15 NOT NULL,
asset_proxy_allowlist text, asset_proxy_allowlist text,
keep_latest_artifact boolean DEFAULT true 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_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)),
CONSTRAINT app_settings_registry_exp_policies_worker_capacity_positive CHECK ((container_registry_expiration_policies_worker_capacity >= 0)), CONSTRAINT app_settings_registry_exp_policies_worker_capacity_positive CHECK ((container_registry_expiration_policies_worker_capacity >= 0)),
CONSTRAINT check_17d9558205 CHECK ((char_length((kroki_url)::text) <= 1024)), CONSTRAINT check_17d9558205 CHECK ((char_length((kroki_url)::text) <= 1024)),
......
...@@ -2533,6 +2533,13 @@ type BurnupChartDailyTotals { ...@@ -2533,6 +2533,13 @@ type BurnupChartDailyTotals {
scopeWeight: Int! scopeWeight: Int!
} }
type CiApplicationSettings {
"""
Whether to keep the latest jobs artifacts.
"""
keepLatestArtifact: Boolean
}
type CiBuildNeed { type CiBuildNeed {
""" """
Name of the job we need to complete. Name of the job we need to complete.
...@@ -20898,6 +20905,11 @@ type PromoteToEpicPayload { ...@@ -20898,6 +20905,11 @@ type PromoteToEpicPayload {
} }
type Query { type Query {
"""
CI related settings that apply to the entire instance.
"""
ciApplicationSettings: CiApplicationSettings
""" """
Get linted and processed contents of a CI config. Should not be requested more than once per request. Get linted and processed contents of a CI config. Should not be requested more than once per request.
""" """
......
...@@ -6681,6 +6681,33 @@ ...@@ -6681,6 +6681,33 @@
"enumValues": null, "enumValues": null,
"possibleTypes": null "possibleTypes": null
}, },
{
"kind": "OBJECT",
"name": "CiApplicationSettings",
"description": null,
"fields": [
{
"name": "keepLatestArtifact",
"description": "Whether to keep the latest jobs artifacts.",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{ {
"kind": "OBJECT", "kind": "OBJECT",
"name": "CiBuildNeed", "name": "CiBuildNeed",
...@@ -60807,6 +60834,20 @@ ...@@ -60807,6 +60834,20 @@
"name": "Query", "name": "Query",
"description": null, "description": null,
"fields": [ "fields": [
{
"name": "ciApplicationSettings",
"description": "CI related settings that apply to the entire instance.",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "CiApplicationSettings",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "ciConfig", "name": "ciConfig",
"description": "Get linted and processed contents of a CI config. Should not be requested more than once per request.", "description": "Get linted and processed contents of a CI config. Should not be requested more than once per request.",
...@@ -407,6 +407,12 @@ Represents the total number of issues and their weights for a particular day. ...@@ -407,6 +407,12 @@ Represents the total number of issues and their weights for a particular day.
| `scopeCount` | Int! | Number of issues as of this day | | `scopeCount` | Int! | Number of issues as of this day |
| `scopeWeight` | Int! | Total weight of issues as of this day | | `scopeWeight` | Int! | Total weight of issues as of this day |
### CiApplicationSettings
| Field | Type | Description |
| ----- | ---- | ----------- |
| `keepLatestArtifact` | Boolean | Whether to keep the latest jobs artifacts. |
### CiBuildNeed ### CiBuildNeed
| Field | Type | Description | | Field | Type | Description |
......
...@@ -85,7 +85,8 @@ Example response: ...@@ -85,7 +85,8 @@ Example response:
"wiki_page_max_content_bytes": 52428800, "wiki_page_max_content_bytes": 52428800,
"require_admin_approval_after_user_signup": false, "require_admin_approval_after_user_signup": false,
"personal_access_token_prefix": "GL-", "personal_access_token_prefix": "GL-",
"rate_limiting_response_text": null "rate_limiting_response_text": null,
"keep_latest_artifact": true
} }
``` ```
...@@ -179,7 +180,8 @@ Example response: ...@@ -179,7 +180,8 @@ Example response:
"wiki_page_max_content_bytes": 52428800, "wiki_page_max_content_bytes": 52428800,
"require_admin_approval_after_user_signup": false, "require_admin_approval_after_user_signup": false,
"personal_access_token_prefix": "GL-", "personal_access_token_prefix": "GL-",
"rate_limiting_response_text": null "rate_limiting_response_text": null,
"keep_latest_artifact": true
} }
``` ```
......
...@@ -484,6 +484,9 @@ a project, you can disable this behavior to save space: ...@@ -484,6 +484,9 @@ a project, you can disable this behavior to save space:
1. Navigate to **Settings > CI/CD > Artifacts**. 1. Navigate to **Settings > CI/CD > Artifacts**.
1. Uncheck **Keep artifacts from most recent successful jobs**. 1. Uncheck **Keep artifacts from most recent successful jobs**.
You can disable this behavior for all projects on a self-managed instance in the
[instance's CI/CD settings](../../user/admin_area/settings/continuous_integration.md#keep-the-latest-artifacts-for-all-jobs-in-the-latest-successful-pipelines). **(CORE ONLY)**
When you disable the feature, the latest artifacts do not immediately expire. When you disable the feature, the latest artifacts do not immediately expire.
A new pipeline must run before the latest artifacts can expire and be deleted. A new pipeline must run before the latest artifacts can expire and be deleted.
......
...@@ -3331,7 +3331,9 @@ job: ...@@ -3331,7 +3331,9 @@ job:
The latest artifacts for refs are locked against deletion, and kept regardless of The latest artifacts for refs are locked against deletion, and kept regardless of
the expiry time. [Introduced in](https://gitlab.com/gitlab-org/gitlab/-/issues/16267) the expiry time. [Introduced in](https://gitlab.com/gitlab-org/gitlab/-/issues/16267)
GitLab 13.0 behind a disabled feature flag, and [made the default behavior](https://gitlab.com/gitlab-org/gitlab/-/issues/229936) GitLab 13.0 behind a disabled feature flag, and [made the default behavior](https://gitlab.com/gitlab-org/gitlab/-/issues/229936)
in GitLab 13.4. In [GitLab 13.8 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/241026), you can [disable this behavior in the CI/CD settings](../pipelines/job_artifacts.md#keep-artifacts-from-most-recent-successful-jobs). in GitLab 13.4.
In [GitLab 13.8 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/241026), you can [disable this behavior at the project level in the CI/CD settings](../pipelines/job_artifacts.md#keep-artifacts-from-most-recent-successful-jobs). In [GitLab 13.9 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/276583), you can [disable this behavior instance-wide](../../user/admin_area/settings/continuous_integration.md#keep-the-latest-artifacts-for-all-jobs-in-the-latest-successful-pipelines).
#### `artifacts:reports` #### `artifacts:reports`
......
...@@ -86,6 +86,25 @@ be updated for artifacts created before this setting was changed. ...@@ -86,6 +86,25 @@ be updated for artifacts created before this setting was changed.
The administrator may need to manually search for and expire previously-created The administrator may need to manually search for and expire previously-created
artifacts, as described in the [troubleshooting documentation](../../../administration/troubleshooting/gitlab_rails_cheat_sheet.md#remove-artifacts-more-than-a-week-old). artifacts, as described in the [troubleshooting documentation](../../../administration/troubleshooting/gitlab_rails_cheat_sheet.md#remove-artifacts-more-than-a-week-old).
## Keep the latest artifacts for all jobs in the latest successful pipelines **(CORE ONLY)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/50889) in GitLab Core 13.9.
When enabled (default), the artifacts for the most recent pipeline for a ref are
locked against deletion and kept regardless of the expiry time.
When disabled, the latest artifacts for any **new** successful or fixed pipelines
are allowed to expire.
This setting takes precedence over the [project level setting](../../../ci/pipelines/job_artifacts.md#keep-artifacts-from-most-recent-successful-jobs).
If disabled at the instance level, you cannot enable this per-project.
When you disable the feature, the latest artifacts do not immediately expire.
A new pipeline must run before the latest artifacts can expire and be deleted.
NOTE:
All application settings have a [customizable cache expiry interval](../../../administration/application_settings_cache.md) which can delay the settings affect.
## Shared runners pipeline minutes quota **(PREMIUM SELF)** ## Shared runners pipeline minutes quota **(PREMIUM SELF)**
> [Moved](https://about.gitlab.com/blog/2021/01/26/new-gitlab-product-subscription-model/) to GitLab Premium in 13.9. > [Moved](https://about.gitlab.com/blog/2021/01/26/new-gitlab-product-subscription-model/) to GitLab Premium in 13.9.
......
...@@ -10,8 +10,7 @@ module API ...@@ -10,8 +10,7 @@ module API
helpers do helpers do
def current_settings def current_settings
@current_setting ||= @current_setting ||= ApplicationSetting.find_or_create_without_cache
(ApplicationSetting.current_without_cache || ApplicationSetting.create_from_defaults)
end end
def filter_attributes_using_license(attrs) def filter_attributes_using_license(attrs)
......
...@@ -23,7 +23,7 @@ module Gitlab ...@@ -23,7 +23,7 @@ module Gitlab
pipeline_schedule: @command.schedule, pipeline_schedule: @command.schedule,
merge_request: @command.merge_request, merge_request: @command.merge_request,
external_pull_request: @command.external_pull_request, external_pull_request: @command.external_pull_request,
locked: @command.project.latest_pipeline_locked, locked: @command.project.default_pipeline_lock,
variables_attributes: variables_attributes variables_attributes: variables_attributes
) )
end end
......
...@@ -2070,6 +2070,9 @@ msgstr "" ...@@ -2070,6 +2070,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance." msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr "" msgstr ""
msgid "AdminSettings|Keep the latest artifacts for all jobs in the latest successful pipelines"
msgstr ""
msgid "AdminSettings|Maximum duration of a session for Git operations when 2FA is enabled." msgid "AdminSettings|Maximum duration of a session for Git operations when 2FA is enabled."
msgstr "" msgstr ""
...@@ -2109,6 +2112,9 @@ msgstr "" ...@@ -2109,6 +2112,9 @@ msgstr ""
msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages." msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages."
msgstr "" 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 required pipeline configuration can be selected from the %{code_start}gitlab-ci%{code_end} directory inside of the configured %{link_start}instance template repository%{link_end} or from GitLab provided configurations." msgid "AdminSettings|The required pipeline configuration can be selected from the %{code_start}gitlab-ci%{code_end} directory inside of the configured %{link_start}instance template repository%{link_end} or from GitLab provided configurations."
msgstr "" msgstr ""
...@@ -29320,7 +29326,7 @@ msgstr "" ...@@ -29320,7 +29326,7 @@ msgstr ""
msgid "There was a problem fetching project users." msgid "There was a problem fetching project users."
msgstr "" msgstr ""
msgid "There was a problem fetching the keep latest artifact setting." msgid "There was a problem fetching the keep latest artifacts setting."
msgstr "" msgstr ""
msgid "There was a problem fetching users." msgid "There was a problem fetching users."
...@@ -29335,7 +29341,7 @@ msgstr "" ...@@ -29335,7 +29341,7 @@ msgstr ""
msgid "There was a problem sending the confirmation email" msgid "There was a problem sending the confirmation email"
msgstr "" msgstr ""
msgid "There was a problem updating the keep latest artifact setting." msgid "There was a problem updating the keep latest artifacts setting."
msgstr "" msgstr ""
msgid "There was an error %{message} todo." msgid "There was an error %{message} todo."
...@@ -29689,6 +29695,9 @@ msgstr "" ...@@ -29689,6 +29695,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission." msgid "This epic does not exist or you don't have sufficient permission."
msgstr "" msgstr ""
msgid "This feature is disabled at the instance level."
msgstr ""
msgid "This feature requires local storage to be enabled" msgid "This feature requires local storage to be enabled"
msgstr "" msgstr ""
......
...@@ -39,7 +39,7 @@ FactoryBot.define do ...@@ -39,7 +39,7 @@ FactoryBot.define do
group_runners_enabled { nil } group_runners_enabled { nil }
merge_pipelines_enabled { nil } merge_pipelines_enabled { nil }
merge_trains_enabled { nil } merge_trains_enabled { nil }
ci_keep_latest_artifact { nil } keep_latest_artifact { nil }
import_status { nil } import_status { nil }
import_jid { nil } import_jid { nil }
import_correlation_id { nil } import_correlation_id { nil }
...@@ -84,7 +84,7 @@ FactoryBot.define do ...@@ -84,7 +84,7 @@ FactoryBot.define do
project.group_runners_enabled = evaluator.group_runners_enabled unless evaluator.group_runners_enabled.nil? project.group_runners_enabled = evaluator.group_runners_enabled unless evaluator.group_runners_enabled.nil?
project.merge_pipelines_enabled = evaluator.merge_pipelines_enabled unless evaluator.merge_pipelines_enabled.nil? project.merge_pipelines_enabled = evaluator.merge_pipelines_enabled unless evaluator.merge_pipelines_enabled.nil?
project.merge_trains_enabled = evaluator.merge_trains_enabled unless evaluator.merge_trains_enabled.nil? project.merge_trains_enabled = evaluator.merge_trains_enabled unless evaluator.merge_trains_enabled.nil?
project.ci_keep_latest_artifact = evaluator.ci_keep_latest_artifact unless evaluator.ci_keep_latest_artifact.nil? project.keep_latest_artifact = evaluator.keep_latest_artifact unless evaluator.keep_latest_artifact.nil?
project.restrict_user_defined_variables = evaluator.restrict_user_defined_variables unless evaluator.restrict_user_defined_variables.nil? project.restrict_user_defined_variables = evaluator.restrict_user_defined_variables unless evaluator.restrict_user_defined_variables.nil?
if evaluator.import_status if evaluator.import_status
......
...@@ -306,11 +306,13 @@ RSpec.describe 'Admin updates settings' do ...@@ -306,11 +306,13 @@ RSpec.describe 'Admin updates settings' do
page.within('.as-ci-cd') do page.within('.as-ci-cd') do
check 'Default to Auto DevOps pipeline for all projects' check 'Default to Auto DevOps pipeline for all projects'
fill_in 'application_setting_auto_devops_domain', with: 'domain.com' fill_in 'application_setting_auto_devops_domain', with: 'domain.com'
uncheck 'Keep the latest artifacts for all jobs in the latest successful pipelines'
click_button 'Save changes' click_button 'Save changes'
end end
expect(current_settings.auto_devops_enabled?).to be true expect(current_settings.auto_devops_enabled?).to be true
expect(current_settings.auto_devops_domain).to eq('domain.com') expect(current_settings.auto_devops_domain).to eq('domain.com')
expect(current_settings.keep_latest_artifact).to be false
expect(page).to have_content "Application settings saved successfully" expect(page).to have_content "Application settings saved successfully"
end end
......
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Keep latest artifact checkbox sets correct setting value in checkbox with query result 1`] = ` exports[`Keep latest artifact checkbox when application keep latest artifact setting is disabled checkbox is disabled when application setting is disabled 1`] = `
<div> <div>
<!----> <!---->
<gl-form-checkbox-stub <b-form-checkbox-stub
checked="true" checked="true"
class="gl-form-checkbox"
disabled="true"
plain="true"
value="true"
> >
<b <strong
class="gl-mr-3" class="gl-mr-3"
> >
Keep artifacts from most recent successful jobs Keep artifacts from most recent successful jobs
</b> </strong>
<gl-link-stub <gl-link-stub
href="/help/ci/pipelines/job_artifacts" href="/help/ci/pipelines/job_artifacts"
> >
More information More information
</gl-link-stub> </gl-link-stub>
</gl-form-checkbox-stub>
<p> <p
class="help-text"
>
This feature is disabled at the instance level.
</p>
</b-form-checkbox-stub>
</div>
`;
The latest artifacts created by jobs in the most recent successful pipeline will be stored. exports[`Keep latest artifact checkbox when application keep latest artifact setting is enabled sets correct setting value in checkbox with query result 1`] = `
<div>
<!---->
<b-form-checkbox-stub
checked="true"
class="gl-form-checkbox"
plain="true"
value="true"
>
<strong
class="gl-mr-3"
>
Keep artifacts from most recent successful jobs
</strong>
<gl-link-stub
href="/help/ci/pipelines/job_artifacts"
>
More information
</gl-link-stub>
<p
class="help-text"
>
The latest artifacts created by jobs in the most recent successful pipeline will be stored.
</p> </p>
</b-form-checkbox-stub>
</div> </div>
`; `;
...@@ -4,12 +4,13 @@ import VueApollo from 'vue-apollo'; ...@@ -4,12 +4,13 @@ import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
import KeepLatestArtifactCheckbox from '~/artifacts_settings/keep_latest_artifact_checkbox.vue'; import KeepLatestArtifactCheckbox from '~/artifacts_settings/keep_latest_artifact_checkbox.vue';
import GetKeepLatestArtifactProjectSetting from '~/artifacts_settings/graphql/queries/get_keep_latest_artifact_project_setting.query.graphql'; import GetKeepLatestArtifactProjectSetting from '~/artifacts_settings/graphql/queries/get_keep_latest_artifact_project_setting.query.graphql';
import GetKeepLatestArtifactApplicationSetting from '~/artifacts_settings/graphql/queries/get_keep_latest_artifact_application_setting.query.graphql';
import UpdateKeepLatestArtifactProjectSetting from '~/artifacts_settings/graphql/mutations/update_keep_latest_artifact_project_setting.mutation.graphql'; import UpdateKeepLatestArtifactProjectSetting from '~/artifacts_settings/graphql/mutations/update_keep_latest_artifact_project_setting.mutation.graphql';
const localVue = createLocalVue(); const localVue = createLocalVue();
localVue.use(VueApollo); localVue.use(VueApollo);
const keepLatestArtifactMock = { const keepLatestArtifactProjectMock = {
data: { data: {
project: { project: {
ciCdSettings: { keepLatestArtifact: true }, ciCdSettings: { keepLatestArtifact: true },
...@@ -17,6 +18,14 @@ const keepLatestArtifactMock = { ...@@ -17,6 +18,14 @@ const keepLatestArtifactMock = {
}, },
}; };
const keepLatestArtifactApplicationMock = {
data: {
ciApplicationSettings: {
keepLatestArtifact: true,
},
},
};
const keepLatestArtifactMockResponse = { const keepLatestArtifactMockResponse = {
data: { ciCdSettingsUpdate: { errors: [], __typename: 'CiCdSettingsUpdatePayload' } }, data: { ciCdSettingsUpdate: { errors: [], __typename: 'CiCdSettingsUpdatePayload' } },
}; };
...@@ -34,7 +43,12 @@ describe('Keep latest artifact checkbox', () => { ...@@ -34,7 +43,12 @@ describe('Keep latest artifact checkbox', () => {
const createComponent = (handlers) => { const createComponent = (handlers) => {
requestHandlers = { requestHandlers = {
keepLatestArtifactQueryHandler: jest.fn().mockResolvedValue(keepLatestArtifactMock), keepLatestArtifactProjectQueryHandler: jest
.fn()
.mockResolvedValue(keepLatestArtifactProjectMock),
keepLatestArtifactApplicationQueryHandler: jest
.fn()
.mockResolvedValue(keepLatestArtifactApplicationMock),
keepLatestArtifactMutationHandler: jest keepLatestArtifactMutationHandler: jest
.fn() .fn()
.mockResolvedValue(keepLatestArtifactMockResponse), .mockResolvedValue(keepLatestArtifactMockResponse),
...@@ -42,7 +56,11 @@ describe('Keep latest artifact checkbox', () => { ...@@ -42,7 +56,11 @@ describe('Keep latest artifact checkbox', () => {
}; };
apolloProvider = createMockApollo([ apolloProvider = createMockApollo([
[GetKeepLatestArtifactProjectSetting, requestHandlers.keepLatestArtifactQueryHandler], [GetKeepLatestArtifactProjectSetting, requestHandlers.keepLatestArtifactProjectQueryHandler],
[
GetKeepLatestArtifactApplicationSetting,
requestHandlers.keepLatestArtifactApplicationQueryHandler,
],
[UpdateKeepLatestArtifactProjectSetting, requestHandlers.keepLatestArtifactMutationHandler], [UpdateKeepLatestArtifactProjectSetting, requestHandlers.keepLatestArtifactMutationHandler],
]); ]);
...@@ -51,38 +69,74 @@ describe('Keep latest artifact checkbox', () => { ...@@ -51,38 +69,74 @@ describe('Keep latest artifact checkbox', () => {
fullPath, fullPath,
helpPagePath, helpPagePath,
}, },
stubs: {
GlFormCheckbox,
},
localVue, localVue,
apolloProvider, apolloProvider,
}); });
}; };
beforeEach(() => {
createComponent();
});
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
wrapper = null; wrapper = null;
apolloProvider = null; apolloProvider = null;
}); });
describe('default', () => {
beforeEach(() => {
createComponent();
});
it('displays the checkbox and the help link', () => { it('displays the checkbox and the help link', () => {
expect(findCheckbox().exists()).toBe(true); expect(findCheckbox().exists()).toBe(true);
expect(findHelpLink().exists()).toBe(true); expect(findHelpLink().exists()).toBe(true);
}); });
it('calls mutation on artifact setting change with correct payload', () => {
findCheckbox().vm.$emit('change', false);
expect(requestHandlers.keepLatestArtifactMutationHandler).toHaveBeenCalledWith({
fullPath,
keepLatestArtifact: false,
});
});
});
describe('when application keep latest artifact setting is enabled', () => {
beforeEach(() => {
createComponent();
});
it('sets correct setting value in checkbox with query result', async () => { it('sets correct setting value in checkbox with query result', async () => {
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
expect(wrapper.element).toMatchSnapshot(); expect(wrapper.element).toMatchSnapshot();
}); });
it('calls mutation on artifact setting change with correct payload', () => { it('checkbox is enabled when application setting is enabled', async () => {
findCheckbox().vm.$emit('change', false); await wrapper.vm.$nextTick();
expect(requestHandlers.keepLatestArtifactMutationHandler).toHaveBeenCalledWith({ expect(findCheckbox().attributes('disabled')).toBeUndefined();
fullPath, });
});
describe('when application keep latest artifact setting is disabled', () => {
it('checkbox is disabled when application setting is disabled', async () => {
createComponent({
keepLatestArtifactApplicationQueryHandler: jest.fn().mockResolvedValue({
data: {
ciApplicationSettings: {
keepLatestArtifact: false, keepLatestArtifact: false,
},
},
}),
});
await wrapper.vm.$nextTick();
expect(wrapper.element).toMatchSnapshot();
expect(findCheckbox().attributes('disabled')).toBe('true');
}); });
}); });
}); });
...@@ -235,7 +235,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Build do ...@@ -235,7 +235,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Build do
with_them do with_them do
before do before do
project.update!(ci_keep_latest_artifact: keep_latest_artifact) project.update!(keep_latest_artifact: keep_latest_artifact)
end end
it 'builds a pipeline with appropriate locked value' do it 'builds a pipeline with appropriate locked value' do
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe ProjectCiCdSetting do RSpec.describe ProjectCiCdSetting do
using RSpec::Parameterized::TableSyntax
describe 'validations' do describe 'validations' do
it 'validates default_git_depth is between 0 and 1000 or nil' do it 'validates default_git_depth is between 0 and 1000 or nil' do
expect(subject).to validate_numericality_of(:default_git_depth) expect(subject).to validate_numericality_of(:default_git_depth)
...@@ -36,4 +38,39 @@ RSpec.describe ProjectCiCdSetting do ...@@ -36,4 +38,39 @@ RSpec.describe ProjectCiCdSetting do
expect(project.reload.ci_cd_settings.default_git_depth).to eq(0) expect(project.reload.ci_cd_settings.default_git_depth).to eq(0)
end end
end end
describe '#keep_latest_artifacts_available?' do
let(:attrs) { { keep_latest_artifact: project_enabled } }
let(:project_settings) { described_class.new(attrs) }
subject { project_settings.keep_latest_artifacts_available? }
context 'without application setting record' do
where(:project_enabled, :result_keep_latest_artifact) do
false | false
true | true
end
with_them do
it { expect(subject).to eq(result_keep_latest_artifact) }
end
end
context 'with application setting record' do
where(:instance_enabled, :project_enabled, :result_keep_latest_artifact) do
false | false | false
false | true | false
true | false | false
true | true | true
end
before do
Gitlab::CurrentSettings.current_application_settings.update!(keep_latest_artifact: instance_enabled)
end
with_them do
it { expect(subject).to eq(result_keep_latest_artifact) }
end
end
end
end end
...@@ -559,6 +559,25 @@ RSpec.describe Project, factory_default: :keep do ...@@ -559,6 +559,25 @@ RSpec.describe Project, factory_default: :keep do
end end
end end
describe '#default_pipeline_lock' do
let(:project) { build_stubbed(:project) }
subject { project.default_pipeline_lock }
where(:keep_latest_artifact_enabled, :result_pipeline_locked) do
false | :unlocked
true | :artifacts_locked
end
before do
allow(project).to receive(:keep_latest_artifacts_available?).and_return(keep_latest_artifact_enabled)
end
with_them do
it { expect(subject).to eq(result_pipeline_locked) }
end
end
describe '#autoclose_referenced_issues' do describe '#autoclose_referenced_issues' do
context 'when DB entry is nil' do context 'when DB entry is nil' do
let(:project) { build(:project, autoclose_referenced_issues: nil) } let(:project) { build(:project, autoclose_referenced_issues: nil) }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'getting Application Settings' do
include GraphqlHelpers
let(:fields) do
<<~QUERY
#{all_graphql_fields_for('CiApplicationSettings', max_depth: 1)}
QUERY
end
let(:query) do
graphql_query_for(
'ciApplicationSettings',
fields
)
end
let(:settings_data) { graphql_data['ciApplicationSettings'] }
context 'without admin permissions' do
let(:user) { create(:user) }
before do
post_graphql(query, current_user: user)
end
it_behaves_like 'a working graphql query'
specify { expect(settings_data).to be nil }
end
context 'with admin permissions' do
let(:user) { create(:user, :admin) }
before do
post_graphql(query, current_user: user)
end
it_behaves_like 'a working graphql query'
it 'fetches the settings data' do
# assert against hash to ensure no additional fields are exposed
expect(settings_data).to match({ 'keepLatestArtifact' => Gitlab::CurrentSettings.current_application_settings.keep_latest_artifact })
end
end
end
...@@ -46,7 +46,7 @@ RSpec.describe 'Getting Ci Cd Setting' do ...@@ -46,7 +46,7 @@ RSpec.describe 'Getting Ci Cd Setting' do
it 'fetches the settings data' do it 'fetches the settings data' do
expect(settings_data['mergePipelinesEnabled']).to eql project.ci_cd_settings.merge_pipelines_enabled? expect(settings_data['mergePipelinesEnabled']).to eql project.ci_cd_settings.merge_pipelines_enabled?
expect(settings_data['mergeTrainsEnabled']).to eql project.ci_cd_settings.merge_trains_enabled? expect(settings_data['mergeTrainsEnabled']).to eql project.ci_cd_settings.merge_trains_enabled?
expect(settings_data['keepLatestArtifact']).to eql project.ci_keep_latest_artifact? expect(settings_data['keepLatestArtifact']).to eql project.keep_latest_artifacts_available?
end end
end end
end end
...@@ -5,7 +5,7 @@ require 'spec_helper' ...@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe 'CiCdSettingsUpdate' do RSpec.describe 'CiCdSettingsUpdate' do
include GraphqlHelpers include GraphqlHelpers
let_it_be(:project) { create(:project, ci_keep_latest_artifact: true) } let_it_be(:project) { create(:project, keep_latest_artifact: true) }
let(:variables) { { full_path: project.full_path, keep_latest_artifact: false } } let(:variables) { { full_path: project.full_path, keep_latest_artifact: false } }
let(:mutation) { graphql_mutation(:ci_cd_settings_update, variables) } let(:mutation) { graphql_mutation(:ci_cd_settings_update, variables) }
...@@ -42,7 +42,7 @@ RSpec.describe 'CiCdSettingsUpdate' do ...@@ -42,7 +42,7 @@ RSpec.describe 'CiCdSettingsUpdate' do
project.reload project.reload
expect(response).to have_gitlab_http_status(:success) expect(response).to have_gitlab_http_status(:success)
expect(project.ci_keep_latest_artifact).to eq(false) expect(project.keep_latest_artifact).to eq(false)
end end
context 'when bad arguments are provided' do context 'when bad arguments are provided' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment