Commit 7c7f7eee authored by Steve Abrams's avatar Steve Abrams

Set default for cleanup policy expiration regex

Set default values for name_regex in
container_expiration_policies.

Add a validation to ensure new policies do not have
a blank name_regex when enabled.
parent 87e5db95
...@@ -117,8 +117,9 @@ export default { ...@@ -117,8 +117,9 @@ export default {
const errorMessage = data?.updateContainerExpirationPolicy?.errors[0]; const errorMessage = data?.updateContainerExpirationPolicy?.errors[0];
if (errorMessage) { if (errorMessage) {
this.$toast.show(errorMessage, { type: 'error' }); this.$toast.show(errorMessage, { type: 'error' });
} else {
this.$toast.show(UPDATE_SETTINGS_SUCCESS_MESSAGE, { type: 'success' });
} }
this.$toast.show(UPDATE_SETTINGS_SUCCESS_MESSAGE, { type: 'success' });
}) })
.catch(error => { .catch(error => {
this.setApiErrors(error); this.setApiErrors(error);
......
...@@ -32,7 +32,7 @@ export const KEEP_N_LABEL = s__('ContainerRegistry|Number of tags to retain:'); ...@@ -32,7 +32,7 @@ export const KEEP_N_LABEL = s__('ContainerRegistry|Number of tags to retain:');
export const NAME_REGEX_LABEL = s__( export const NAME_REGEX_LABEL = s__(
'ContainerRegistry|Tags with names matching this regex pattern will %{italicStart}expire:%{italicEnd}', 'ContainerRegistry|Tags with names matching this regex pattern will %{italicStart}expire:%{italicEnd}',
); );
export const NAME_REGEX_PLACEHOLDER = '.*'; export const NAME_REGEX_PLACEHOLDER = '';
export const NAME_REGEX_DESCRIPTION = s__( export const NAME_REGEX_DESCRIPTION = s__(
'ContainerRegistry|Wildcards such as %{codeStart}.*-test%{codeEnd} or %{codeStart}dev-.*%{codeEnd} are supported. To select all tags, use %{codeStart}.*%{codeEnd}', 'ContainerRegistry|Wildcards such as %{codeStart}.*-test%{codeEnd} or %{codeStart}dev-.*%{codeEnd} are supported. To select all tags, use %{codeStart}.*%{codeEnd}',
); );
......
...@@ -21,6 +21,7 @@ class ContainerExpirationPolicy < ApplicationRecord ...@@ -21,6 +21,7 @@ class ContainerExpirationPolicy < ApplicationRecord
validates :cadence, presence: true, inclusion: { in: ->(_) { self.cadence_options.stringify_keys } } validates :cadence, presence: true, inclusion: { in: ->(_) { self.cadence_options.stringify_keys } }
validates :older_than, inclusion: { in: ->(_) { self.older_than_options.stringify_keys } }, allow_nil: true validates :older_than, inclusion: { in: ->(_) { self.older_than_options.stringify_keys } }, allow_nil: true
validates :keep_n, inclusion: { in: ->(_) { self.keep_n_options.keys } }, allow_nil: true validates :keep_n, inclusion: { in: ->(_) { self.keep_n_options.keys } }, allow_nil: true
validates :name_regex, presence: true, if: :enabled?
validates :name_regex, untrusted_regexp: true, if: :enabled? validates :name_regex, untrusted_regexp: true, if: :enabled?
validates :name_regex_keep, untrusted_regexp: true, if: :enabled? validates :name_regex_keep, untrusted_regexp: true, if: :enabled?
......
---
title: Add default regexes and prevent blank regexes for container cleanup policies
merge_request: 44757
author:
type: changed
# frozen_string_literal: true
class SetRegexDefaultsOnContainerExpirationPolicies < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
with_lock_retries do
change_column_default :container_expiration_policies, :name_regex, '.*'
change_column_default :container_expiration_policies, :enabled, false
end
end
def down
with_lock_retries do
change_column_default :container_expiration_policies, :name_regex, nil
change_column_default :container_expiration_policies, :enabled, true
end
end
end
250785e18682cc10afb4f04546e5ff6dff9ab6c6673df84692c8221d6fe820ac
\ No newline at end of file
...@@ -11235,11 +11235,11 @@ CREATE TABLE container_expiration_policies ( ...@@ -11235,11 +11235,11 @@ CREATE TABLE container_expiration_policies (
created_at timestamp with time zone NOT NULL, created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL, updated_at timestamp with time zone NOT NULL,
next_run_at timestamp with time zone, next_run_at timestamp with time zone,
name_regex character varying(255), name_regex character varying(255) DEFAULT '.*'::character varying,
cadence character varying(12) DEFAULT '1d'::character varying NOT NULL, cadence character varying(12) DEFAULT '1d'::character varying NOT NULL,
older_than character varying(12) DEFAULT '90d'::character varying, older_than character varying(12) DEFAULT '90d'::character varying,
keep_n integer DEFAULT 10, keep_n integer DEFAULT 10,
enabled boolean DEFAULT true NOT NULL, enabled boolean DEFAULT false NOT NULL,
name_regex_keep text, name_regex_keep text,
CONSTRAINT container_expiration_policies_name_regex_keep CHECK ((char_length(name_regex_keep) <= 255)) CONSTRAINT container_expiration_policies_name_regex_keep CHECK ((char_length(name_regex_keep) <= 255))
); );
......
...@@ -522,7 +522,7 @@ To create a cleanup policy in the UI: ...@@ -522,7 +522,7 @@ To create a cleanup policy in the UI:
| **Expiration interval** | How long tags are exempt from being deleted. | | **Expiration interval** | How long tags are exempt from being deleted. |
| **Expiration schedule** | How often the policy should run. | | **Expiration schedule** | How often the policy should run. |
| **Number of tags to retain** | How many tags to _always_ keep for each image. | | **Number of tags to retain** | How many tags to _always_ keep for each image. |
| **Tags with names matching this regex pattern expire:** | The regex pattern that determines which tags to remove. For all tags, use `.*`. See other [regex pattern examples](#regex-pattern-examples). | | **Tags with names matching this regex pattern expire:** | The regex pattern that determines which tags to remove. This value cannot be blank. For all tags, use `.*`. See other [regex pattern examples](#regex-pattern-examples). |
| **Tags with names matching this regex pattern are preserved:** | The regex pattern that determines which tags to preserve. The `latest` tag is always preserved. For all tags, use `.*`. See other [regex pattern examples](#regex-pattern-examples). | | **Tags with names matching this regex pattern are preserved:** | The regex pattern that determines which tags to preserve. The `latest` tag is always preserved. For all tags, use `.*`. See other [regex pattern examples](#regex-pattern-examples). |
1. Click **Set cleanup policy**. 1. Click **Set cleanup policy**.
...@@ -544,6 +544,8 @@ Here are examples of regex patterns you may want to use: ...@@ -544,6 +544,8 @@ Here are examples of regex patterns you may want to use:
.* .*
``` ```
This is the default value for the expiration regex.
- Match tags that start with `v`: - Match tags that start with `v`:
```plaintext ```plaintext
......
...@@ -15,6 +15,7 @@ RSpec.describe 'Project > Settings > CI/CD > Container registry tag expiration p ...@@ -15,6 +15,7 @@ RSpec.describe 'Project > Settings > CI/CD > Container registry tag expiration p
before do before do
project.update!(container_registry_enabled: container_registry_enabled_on_project) project.update!(container_registry_enabled: container_registry_enabled_on_project)
project.container_expiration_policy.update!(enabled: true)
sign_in(user) sign_in(user)
stub_container_registry_config(enabled: container_registry_enabled) stub_container_registry_config(enabled: container_registry_enabled)
......
...@@ -123,7 +123,7 @@ exports[`Expiration Policy Form renders 1`] = ` ...@@ -123,7 +123,7 @@ exports[`Expiration Policy Form renders 1`] = `
disabled="true" disabled="true"
id="expiration-policy-name-matching" id="expiration-policy-name-matching"
noresize="true" noresize="true"
placeholder=".*" placeholder=""
trim="" trim=""
value="" value=""
/> />
......
...@@ -35,7 +35,7 @@ RSpec.describe Mutations::ContainerExpirationPolicies::Update do ...@@ -35,7 +35,7 @@ RSpec.describe Mutations::ContainerExpirationPolicies::Update do
it_behaves_like 'not creating the container expiration policy' it_behaves_like 'not creating the container expiration policy'
it "doesn't update the cadence" do it 'doesn\'t update the cadence' do
expect { subject } expect { subject }
.not_to change { container_expiration_policy.reload.cadence } .not_to change { container_expiration_policy.reload.cadence }
end end
...@@ -47,6 +47,24 @@ RSpec.describe Mutations::ContainerExpirationPolicies::Update do ...@@ -47,6 +47,24 @@ RSpec.describe Mutations::ContainerExpirationPolicies::Update do
) )
end end
end end
context 'with blank regex' do
let_it_be(:params) { { project_path: project.full_path, name_regex: '', enabled: true } }
it_behaves_like 'not creating the container expiration policy'
it "doesn't update the cadence" do
expect { subject }
.not_to change { container_expiration_policy.reload.cadence }
end
it 'returns an error' do
expect(subject).to eq(
container_expiration_policy: nil,
errors: ['Name regex can\'t be blank']
)
end
end
end end
RSpec.shared_examples 'denying access to container expiration policy' do RSpec.shared_examples 'denying access to container expiration policy' do
......
...@@ -546,13 +546,13 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do ...@@ -546,13 +546,13 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
subject { described_class.data[:counts] } subject { described_class.data[:counts] }
it 'gathers usage data' do it 'gathers usage data' do
expect(subject[:projects_with_expiration_policy_enabled]).to eq 22 expect(subject[:projects_with_expiration_policy_enabled]).to eq 18
expect(subject[:projects_with_expiration_policy_disabled]).to eq 1 expect(subject[:projects_with_expiration_policy_disabled]).to eq 5
expect(subject[:projects_with_expiration_policy_enabled_with_keep_n_unset]).to eq 1 expect(subject[:projects_with_expiration_policy_enabled_with_keep_n_unset]).to eq 1
expect(subject[:projects_with_expiration_policy_enabled_with_keep_n_set_to_1]).to eq 1 expect(subject[:projects_with_expiration_policy_enabled_with_keep_n_set_to_1]).to eq 1
expect(subject[:projects_with_expiration_policy_enabled_with_keep_n_set_to_5]).to eq 1 expect(subject[:projects_with_expiration_policy_enabled_with_keep_n_set_to_5]).to eq 1
expect(subject[:projects_with_expiration_policy_enabled_with_keep_n_set_to_10]).to eq 16 expect(subject[:projects_with_expiration_policy_enabled_with_keep_n_set_to_10]).to eq 12
expect(subject[:projects_with_expiration_policy_enabled_with_keep_n_set_to_25]).to eq 1 expect(subject[:projects_with_expiration_policy_enabled_with_keep_n_set_to_25]).to eq 1
expect(subject[:projects_with_expiration_policy_enabled_with_keep_n_set_to_50]).to eq 1 expect(subject[:projects_with_expiration_policy_enabled_with_keep_n_set_to_50]).to eq 1
...@@ -560,9 +560,9 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do ...@@ -560,9 +560,9 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
expect(subject[:projects_with_expiration_policy_enabled_with_older_than_set_to_7d]).to eq 1 expect(subject[:projects_with_expiration_policy_enabled_with_older_than_set_to_7d]).to eq 1
expect(subject[:projects_with_expiration_policy_enabled_with_older_than_set_to_14d]).to eq 1 expect(subject[:projects_with_expiration_policy_enabled_with_older_than_set_to_14d]).to eq 1
expect(subject[:projects_with_expiration_policy_enabled_with_older_than_set_to_30d]).to eq 1 expect(subject[:projects_with_expiration_policy_enabled_with_older_than_set_to_30d]).to eq 1
expect(subject[:projects_with_expiration_policy_enabled_with_older_than_set_to_90d]).to eq 18 expect(subject[:projects_with_expiration_policy_enabled_with_older_than_set_to_90d]).to eq 14
expect(subject[:projects_with_expiration_policy_enabled_with_cadence_set_to_1d]).to eq 18 expect(subject[:projects_with_expiration_policy_enabled_with_cadence_set_to_1d]).to eq 14
expect(subject[:projects_with_expiration_policy_enabled_with_cadence_set_to_7d]).to eq 1 expect(subject[:projects_with_expiration_policy_enabled_with_cadence_set_to_7d]).to eq 1
expect(subject[:projects_with_expiration_policy_enabled_with_cadence_set_to_14d]).to eq 1 expect(subject[:projects_with_expiration_policy_enabled_with_cadence_set_to_14d]).to eq 1
expect(subject[:projects_with_expiration_policy_enabled_with_cadence_set_to_1month]).to eq 1 expect(subject[:projects_with_expiration_policy_enabled_with_cadence_set_to_1month]).to eq 1
......
...@@ -66,9 +66,15 @@ RSpec.describe ContainerExpirationPolicy, type: :model do ...@@ -66,9 +66,15 @@ RSpec.describe ContainerExpirationPolicy, type: :model do
end end
context 'with a set of regexps' do context 'with a set of regexps' do
let_it_be(:container_expiration_policy) { create(:container_expiration_policy) }
subject { container_expiration_policy }
valid_regexps = %w[master .* v.+ v10.1.* (?:v.+|master|release)] valid_regexps = %w[master .* v.+ v10.1.* (?:v.+|master|release)]
invalid_regexps = ['[', '(?:v.+|master|release'] invalid_regexps = ['[', '(?:v.+|master|release']
it { is_expected.to validate_presence_of(:name_regex) }
valid_regexps.each do |valid_regexp| valid_regexps.each do |valid_regexp|
it { is_expected.to allow_value(valid_regexp).for(:name_regex) } it { is_expected.to allow_value(valid_regexp).for(:name_regex) }
it { is_expected.to allow_value(valid_regexp).for(:name_regex_keep) } it { is_expected.to allow_value(valid_regexp).for(:name_regex_keep) }
...@@ -84,6 +90,8 @@ RSpec.describe ContainerExpirationPolicy, type: :model do ...@@ -84,6 +90,8 @@ RSpec.describe ContainerExpirationPolicy, type: :model do
subject { container_expiration_policy } subject { container_expiration_policy }
it { is_expected.not_to validate_presence_of(:name_regex) }
valid_regexps.each do |valid_regexp| valid_regexps.each do |valid_regexp|
it { is_expected.to allow_value(valid_regexp).for(:name_regex) } it { is_expected.to allow_value(valid_regexp).for(:name_regex) }
it { is_expected.to allow_value(valid_regexp).for(:name_regex_keep) } it { is_expected.to allow_value(valid_regexp).for(:name_regex_keep) }
......
...@@ -73,6 +73,29 @@ RSpec.describe 'Updating the container expiration policy' do ...@@ -73,6 +73,29 @@ RSpec.describe 'Updating the container expiration policy' do
end end
end end
RSpec.shared_examples 'rejecting blank name_regex when enabled' do
context "for blank name_regex" do
let(:params) do
{
project_path: project.full_path,
name_regex: '',
enabled: true
}
end
it_behaves_like 'returning response status', :success
it_behaves_like 'not creating the container expiration policy'
it 'returns an error' do
subject
expect(graphql_data['updateContainerExpirationPolicy']['errors'].size).to eq(1)
expect(graphql_data['updateContainerExpirationPolicy']['errors']).to include("Name regex can't be blank")
end
end
end
RSpec.shared_examples 'accepting the mutation request updating the container expiration policy' do RSpec.shared_examples 'accepting the mutation request updating the container expiration policy' do
it_behaves_like 'updating the container expiration policy attributes', mode: :update, from: { cadence: '1d', keep_n: 10, older_than: '90d' }, to: { cadence: '3month', keep_n: 100, older_than: '14d' } it_behaves_like 'updating the container expiration policy attributes', mode: :update, from: { cadence: '1d', keep_n: 10, older_than: '90d' }, to: { cadence: '3month', keep_n: 100, older_than: '14d' }
...@@ -80,6 +103,7 @@ RSpec.describe 'Updating the container expiration policy' do ...@@ -80,6 +103,7 @@ RSpec.describe 'Updating the container expiration policy' do
it_behaves_like 'rejecting invalid regex for', :name_regex it_behaves_like 'rejecting invalid regex for', :name_regex
it_behaves_like 'rejecting invalid regex for', :name_regex_keep it_behaves_like 'rejecting invalid regex for', :name_regex_keep
it_behaves_like 'rejecting blank name_regex when enabled'
end end
RSpec.shared_examples 'accepting the mutation request creating the container expiration policy' do RSpec.shared_examples 'accepting the mutation request creating the container expiration policy' do
...@@ -89,6 +113,7 @@ RSpec.describe 'Updating the container expiration policy' do ...@@ -89,6 +113,7 @@ RSpec.describe 'Updating the container expiration policy' do
it_behaves_like 'rejecting invalid regex for', :name_regex it_behaves_like 'rejecting invalid regex for', :name_regex
it_behaves_like 'rejecting invalid regex for', :name_regex_keep it_behaves_like 'rejecting invalid regex for', :name_regex_keep
it_behaves_like 'rejecting blank name_regex when enabled'
end end
RSpec.shared_examples 'denying the mutation request' do RSpec.shared_examples 'denying the mutation request' do
......
...@@ -2659,6 +2659,7 @@ RSpec.describe API::Projects do ...@@ -2659,6 +2659,7 @@ RSpec.describe API::Projects do
project_param = { project_param = {
container_expiration_policy_attributes: { container_expiration_policy_attributes: {
cadence: '1month', cadence: '1month',
enabled: true,
keep_n: 1, keep_n: 1,
name_regex_keep: '[' name_regex_keep: '['
} }
......
...@@ -13,6 +13,10 @@ RSpec.describe ContainerExpirationPolicies::CleanupContainerRepositoryWorker do ...@@ -13,6 +13,10 @@ RSpec.describe ContainerExpirationPolicies::CleanupContainerRepositoryWorker do
describe '#perform_work' do describe '#perform_work' do
subject { worker.perform_work } subject { worker.perform_work }
before do
policy.update_column(:enabled, true)
end
RSpec.shared_examples 'handling all repository conditions' do RSpec.shared_examples 'handling all repository conditions' do
it 'sends the repository for cleaning' do it 'sends the repository for cleaning' do
expect(ContainerExpirationPolicies::CleanupService) expect(ContainerExpirationPolicies::CleanupService)
......
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