Commit 83d7cfa0 authored by Steve Abrams's avatar Steve Abrams Committed by Bob Van Landuyt

Add ContainerExpirationPolicy model and table

Migration to create container_expiration_policies
Add model with associations to project
Add helpers to provide drop down options for
container_expiration_policies
Autopopulate container_expiration_policy record
on project create
parent 2eb33c8d
# frozen_string_literal: true
module ContainerExpirationPoliciesHelper
def cadence_options
ContainerExpirationPolicy.cadence_options.map do |key, val|
{ key: key.to_s, label: val }
end
end
def keep_n_options
ContainerExpirationPolicy.keep_n_options.map do |key, val|
{ key: key, label: val }
end
end
def older_than_options
ContainerExpirationPolicy.older_than_options.map do |key, val|
{ key: key.to_s, label: val }
end
end
end
# frozen_string_literal: true
class ContainerExpirationPolicy < ApplicationRecord
belongs_to :project, inverse_of: :container_expiration_policy
validates :project, presence: true
validates :enabled, inclusion: { in: [true, false] }
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 :keep_n, inclusion: { in: ->(_) { self.keep_n_options.keys } }, allow_nil: true
def self.keep_n_options
{
1 => _('%{tags} tag per image name') % { tags: 1 },
5 => _('%{tags} tags per image name') % { tags: 5 },
10 => _('%{tags} tags per image name') % { tags: 10 },
25 => _('%{tags} tags per image name') % { tags: 25 },
50 => _('%{tags} tags per image name') % { tags: 50 },
100 => _('%{tags} tags per image name') % { tags: 100 }
}
end
def self.cadence_options
{
'1d': _('Every day'),
'7d': _('Every week'),
'14d': _('Every two weeks'),
'1month': _('Every month'),
'3month': _('Every three months')
}
end
def self.older_than_options
{
'7d': _('%{days} days until tags are automatically removed') % { days: 7 },
'14d': _('%{days} days until tags are automatically removed') % { days: 14 },
'30d': _('%{days} days until tags are automatically removed') % { days: 30 },
'90d': _('%{days} days until tags are automatically removed') % { days: 90 }
}
end
end
...@@ -97,8 +97,11 @@ class Project < ApplicationRecord ...@@ -97,8 +97,11 @@ class Project < ApplicationRecord
unless: :ci_cd_settings, unless: :ci_cd_settings,
if: proc { ProjectCiCdSetting.available? } if: proc { ProjectCiCdSetting.available? }
after_create :create_container_expiration_policy,
unless: :container_expiration_policy
after_create :create_pages_metadatum, after_create :create_pages_metadatum,
unless: :pages_metadatum unless: :pages_metadatum
after_create :set_timestamps_for_create after_create :set_timestamps_for_create
after_update :update_forks_visibility_level after_update :update_forks_visibility_level
...@@ -248,6 +251,7 @@ class Project < ApplicationRecord ...@@ -248,6 +251,7 @@ class Project < ApplicationRecord
# which is not managed by the DB. Hence we're still using dependent: :destroy # which is not managed by the DB. Hence we're still using dependent: :destroy
# here. # here.
has_many :container_repositories, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :container_repositories, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_one :container_expiration_policy, inverse_of: :project
has_many :commit_statuses has_many :commit_statuses
# The relation :all_pipelines is intended to be used when we want to get the # The relation :all_pipelines is intended to be used when we want to get the
...@@ -305,6 +309,7 @@ class Project < ApplicationRecord ...@@ -305,6 +309,7 @@ class Project < ApplicationRecord
accepts_nested_attributes_for :import_data accepts_nested_attributes_for :import_data
accepts_nested_attributes_for :auto_devops, update_only: true accepts_nested_attributes_for :auto_devops, update_only: true
accepts_nested_attributes_for :ci_cd_settings, update_only: true accepts_nested_attributes_for :ci_cd_settings, update_only: true
accepts_nested_attributes_for :container_expiration_policy, update_only: true
accepts_nested_attributes_for :remote_mirrors, accepts_nested_attributes_for :remote_mirrors,
allow_destroy: true, allow_destroy: true,
......
---
title: Create container expiration policies for projects
merge_request: 20412
author:
type: added
# frozen_string_literal: true
class CreateContainerExpirationPolicies < ActiveRecord::Migration[5.2]
DOWNTIME = false
def change
create_table :container_expiration_policies, id: false, primary_key: :project_id do |t|
t.timestamps_with_timezone null: false
t.datetime_with_timezone :next_run_at
t.references :project, primary_key: true, default: nil, index: false, foreign_key: { on_delete: :cascade }
t.string :name_regex, limit: 255
t.string :cadence, null: false, limit: 12, default: '7d'
t.string :older_than, limit: 12
t.integer :keep_n
t.boolean :enabled, null: false, default: false
end
add_index :container_expiration_policies, [:next_run_at, :enabled],
name: 'index_container_expiration_policies_on_next_run_at_and_enabled'
end
end
...@@ -1224,6 +1224,18 @@ ActiveRecord::Schema.define(version: 2019_12_06_122926) do ...@@ -1224,6 +1224,18 @@ ActiveRecord::Schema.define(version: 2019_12_06_122926) do
t.index ["note_id"], name: "index_commit_user_mentions_on_note_id", unique: true t.index ["note_id"], name: "index_commit_user_mentions_on_note_id", unique: true
end end
create_table "container_expiration_policies", primary_key: "project_id", id: :bigint, default: nil, force: :cascade do |t|
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
t.datetime_with_timezone "next_run_at"
t.string "name_regex", limit: 255
t.string "cadence", limit: 12, default: "7d", null: false
t.string "older_than", limit: 12
t.integer "keep_n"
t.boolean "enabled", default: false, null: false
t.index ["next_run_at", "enabled"], name: "index_container_expiration_policies_on_next_run_at_and_enabled"
end
create_table "container_repositories", id: :serial, force: :cascade do |t| create_table "container_repositories", id: :serial, force: :cascade do |t|
t.integer "project_id", null: false t.integer "project_id", null: false
t.string "name", null: false t.string "name", null: false
...@@ -4410,6 +4422,7 @@ ActiveRecord::Schema.define(version: 2019_12_06_122926) do ...@@ -4410,6 +4422,7 @@ ActiveRecord::Schema.define(version: 2019_12_06_122926) do
add_foreign_key "clusters_kubernetes_namespaces", "environments", on_delete: :nullify add_foreign_key "clusters_kubernetes_namespaces", "environments", on_delete: :nullify
add_foreign_key "clusters_kubernetes_namespaces", "projects", on_delete: :nullify add_foreign_key "clusters_kubernetes_namespaces", "projects", on_delete: :nullify
add_foreign_key "commit_user_mentions", "notes", on_delete: :cascade add_foreign_key "commit_user_mentions", "notes", on_delete: :cascade
add_foreign_key "container_expiration_policies", "projects", on_delete: :cascade
add_foreign_key "container_repositories", "projects" add_foreign_key "container_repositories", "projects"
add_foreign_key "dependency_proxy_blobs", "namespaces", column: "group_id", on_delete: :cascade add_foreign_key "dependency_proxy_blobs", "namespaces", column: "group_id", on_delete: :cascade
add_foreign_key "dependency_proxy_group_settings", "namespaces", column: "group_id", on_delete: :cascade add_foreign_key "dependency_proxy_group_settings", "namespaces", column: "group_id", on_delete: :cascade
......
...@@ -73,6 +73,7 @@ tree: ...@@ -73,6 +73,7 @@ tree:
- :auto_devops - :auto_devops
- :triggers - :triggers
- :pipeline_schedules - :pipeline_schedules
- :container_expiration_policy
- :services - :services
- protected_branches: - protected_branches:
- :merge_access_levels - :merge_access_levels
......
...@@ -234,6 +234,9 @@ msgstr[1] "" ...@@ -234,6 +234,9 @@ msgstr[1] ""
msgid "%{count} related %{pluralized_subject}: %{links}" msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr "" msgstr ""
msgid "%{days} days until tags are automatically removed"
msgstr ""
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}" msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr "" msgstr ""
...@@ -380,6 +383,12 @@ msgstr[1] "" ...@@ -380,6 +383,12 @@ msgstr[1] ""
msgid "%{tabname} changed" msgid "%{tabname} changed"
msgstr "" msgstr ""
msgid "%{tags} tag per image name"
msgstr ""
msgid "%{tags} tags per image name"
msgstr ""
msgid "%{tag}-evidence.json" msgid "%{tag}-evidence.json"
msgstr "" msgstr ""
...@@ -7056,12 +7065,27 @@ msgstr "" ...@@ -7056,12 +7065,27 @@ msgstr ""
msgid "Every %{action} attempt has failed: %{job_error_message}. Please try again." msgid "Every %{action} attempt has failed: %{job_error_message}. Please try again."
msgstr "" msgstr ""
msgid "Every day"
msgstr ""
msgid "Every day (at 4:00am)" msgid "Every day (at 4:00am)"
msgstr "" msgstr ""
msgid "Every month"
msgstr ""
msgid "Every month (on the 1st at 4:00am)" msgid "Every month (on the 1st at 4:00am)"
msgstr "" msgstr ""
msgid "Every three months"
msgstr ""
msgid "Every two weeks"
msgstr ""
msgid "Every week"
msgstr ""
msgid "Every week (Sundays at 4:00am)" msgid "Every week (Sundays at 4:00am)"
msgstr "" msgstr ""
......
# frozen_string_literal: true
require 'spec_helper'
describe ContainerExpirationPoliciesHelper do
describe '#keep_n_options' do
it 'returns keep_n options formatted for dropdown usage' do
expected_result = [
{ key: 1, label: '1 tag per image name' },
{ key: 5, label: '5 tags per image name' },
{ key: 10, label: '10 tags per image name' },
{ key: 25, label: '25 tags per image name' },
{ key: 50, label: '50 tags per image name' },
{ key: 100, label: '100 tags per image name' }
]
expect(helper.keep_n_options).to eq(expected_result)
end
end
describe '#cadence_options' do
it 'returns cadence options formatted for dropdown usage' do
expected_result = [
{ key: '1d', label: 'Every day' },
{ key: '7d', label: 'Every week' },
{ key: '14d', label: 'Every two weeks' },
{ key: '1month', label: 'Every month' },
{ key: '3month', label: 'Every three months' }
]
expect(helper.cadence_options).to eq(expected_result)
end
end
describe '#older_than_options' do
it 'returns older_than options formatted for dropdown usage' do
expected_result = [
{ key: '7d', label: '7 days until tags are automatically removed' },
{ key: '14d', label: '14 days until tags are automatically removed' },
{ key: '30d', label: '30 days until tags are automatically removed' },
{ key: '90d', label: '90 days until tags are automatically removed' }
]
expect(helper.older_than_options).to eq(expected_result)
end
end
end
...@@ -443,6 +443,7 @@ project: ...@@ -443,6 +443,7 @@ project:
- downstream_project_subscriptions - downstream_project_subscriptions
- service_desk_setting - service_desk_setting
- import_failures - import_failures
- container_expiration_policy
award_emoji: award_emoji:
- awardable - awardable
- user - user
......
...@@ -773,3 +773,13 @@ ZoomMeeting: ...@@ -773,3 +773,13 @@ ZoomMeeting:
ServiceDeskSetting: ServiceDeskSetting:
- project_id - project_id
- issue_template_key - issue_template_key
ContainerExpirationPolicy:
- created_at
- updated_at
- next_run_at
- project_id
- name_regex
- cadence
- older_than
- keep_n
- enabled
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ContainerExpirationPolicy, type: :model do
describe 'relationships' do
it { is_expected.to belong_to(:project) }
end
describe 'validations' do
it { is_expected.to validate_presence_of(:project) }
describe '#enabled' do
it { is_expected.to allow_value(true).for(:enabled) }
it { is_expected.to allow_value(false).for(:enabled) }
it { is_expected.not_to allow_value(nil).for(:enabled) }
end
describe '#cadence' do
it { is_expected.to validate_presence_of(:cadence) }
it { is_expected.to allow_value('1d').for(:cadence) }
it { is_expected.to allow_value('1month').for(:cadence) }
it { is_expected.not_to allow_value('123asdf').for(:cadence) }
it { is_expected.not_to allow_value(nil).for(:cadence) }
end
describe '#older_than' do
it { is_expected.to allow_value('7d').for(:older_than) }
it { is_expected.to allow_value('14d').for(:older_than) }
it { is_expected.to allow_value(nil).for(:older_than) }
it { is_expected.not_to allow_value('123asdf').for(:older_than) }
end
describe '#keep_n' do
it { is_expected.to allow_value(10).for(:keep_n) }
it { is_expected.to allow_value(nil).for(:keep_n) }
it { is_expected.not_to allow_value('foo').for(:keep_n) }
end
end
end
...@@ -62,6 +62,7 @@ describe Project do ...@@ -62,6 +62,7 @@ describe Project do
it { is_expected.to have_one(:external_wiki_service) } it { is_expected.to have_one(:external_wiki_service) }
it { is_expected.to have_one(:project_feature) } it { is_expected.to have_one(:project_feature) }
it { is_expected.to have_one(:project_repository) } it { is_expected.to have_one(:project_repository) }
it { is_expected.to have_one(:container_expiration_policy) }
it { is_expected.to have_one(:statistics).class_name('ProjectStatistics') } it { is_expected.to have_one(:statistics).class_name('ProjectStatistics') }
it { is_expected.to have_one(:import_data).class_name('ProjectImportData') } it { is_expected.to have_one(:import_data).class_name('ProjectImportData') }
it { is_expected.to have_one(:last_event).class_name('Event') } it { is_expected.to have_one(:last_event).class_name('Event') }
...@@ -137,6 +138,13 @@ describe Project do ...@@ -137,6 +138,13 @@ describe Project do
expect(project.ci_cd_settings).to be_persisted expect(project.ci_cd_settings).to be_persisted
end end
it 'automatically creates a container expiration policy row' do
project = create(:project)
expect(project.container_expiration_policy).to be_an_instance_of(ContainerExpirationPolicy)
expect(project.container_expiration_policy).to be_persisted
end
it 'automatically creates a Pages metadata row' do it 'automatically creates a Pages metadata row' do
project = create(:project) project = create(:project)
......
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