Commit d46eebd8 authored by Mathieu Parent's avatar Mathieu Parent

Debian Group and Project Distributions

See #5835
parent 1c8deba0
# frozen_string_literal: true
module Packages
module Debian
module Distribution
extend ActiveSupport::Concern
included do
include FileStoreMounter
def self.container_foreign_key
"#{container_type}_id".to_sym
end
alias_attribute :container, container_type
alias_attribute :container_id, "#{container_type}_id"
belongs_to container_type
belongs_to :creator, class_name: 'User'
validates :codename,
presence: true,
uniqueness: { scope: [container_foreign_key] },
format: { with: Gitlab::Regex.debian_distribution_regex }
validates :suite,
allow_nil: true,
format: { with: Gitlab::Regex.debian_distribution_regex }
validates :suite,
uniqueness: { scope: [container_foreign_key] },
if: :suite
validate :unique_codename_and_suite
validates :origin,
allow_nil: true,
format: { with: Gitlab::Regex.debian_distribution_regex }
validates :label,
allow_nil: true,
format: { with: Gitlab::Regex.debian_distribution_regex }
validates :version,
allow_nil: true,
format: { with: Gitlab::Regex.debian_version_regex }
# The Valid-Until field is a security measure to prevent malicious attackers to
# serve an outdated repository, with vulnerable packages
# (keeping in mind that most Debian repository are not using TLS but use GPG
# signatures instead).
# A minimum of 24 hours is simply to avoid generating indices too often
# (which generates load).
# Official Debian repositories are generated 4 times a day, and valid for 7 days.
# Full ref: https://wiki.debian.org/DebianRepository/Format#Date.2C_Valid-Until
validates :valid_time_duration_seconds,
allow_nil: true,
numericality: { greater_than_or_equal_to: 24.hours.to_i }
validates container_type, presence: true
validates :file_store, presence: true
validates :file_signature, absence: true
validates :signing_keys, absence: true
scope :with_container, ->(subject) { where(container_type => subject) }
scope :with_codename, ->(codename) { where(codename: codename) }
scope :with_suite, ->(suite) { where(suite: suite) }
attr_encrypted :signing_keys,
mode: :per_attribute_iv,
key: Settings.attr_encrypted_db_key_base_truncated,
algorithm: 'aes-256-gcm',
encode: false,
encode_iv: false
mount_file_store_uploader Packages::Debian::DistributionReleaseFileUploader
def needs_update?
!file.exists? || time_duration_expired?
end
private
def time_duration_expired?
return false unless valid_time_duration_seconds.present?
updated_at + valid_time_duration_seconds.seconds + 6.hours < Time.current
end
def unique_codename_and_suite
errors.add(:codename, _('has already been taken as Suite')) if codename_exists_as_suite?
errors.add(:suite, _('has already been taken as Codename')) if suite_exists_as_codename?
end
def codename_exists_as_suite?
return false unless codename.present?
self.class.with_container(container).with_suite(codename).exists?
end
def suite_exists_as_codename?
return false unless suite.present?
self.class.with_container(container).with_codename(suite).exists?
end
end
end
end
end
......@@ -75,6 +75,9 @@ class Group < Namespace
has_many :dependency_proxy_blobs, class_name: 'DependencyProxy::Blob'
has_many :dependency_proxy_manifests, class_name: 'DependencyProxy::Manifest'
# debian_distributions must be destroyed by ruby code in order to properly remove carrierwave uploads
has_many :debian_distributions, class_name: 'Packages::Debian::GroupDistribution', dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
accepts_nested_attributes_for :variables, allow_destroy: true
validate :visibility_level_allowed_by_projects
......
# frozen_string_literal: true
class Packages::Debian::GroupDistribution < ApplicationRecord
def self.container_type
:group
end
include Packages::Debian::Distribution
end
# frozen_string_literal: true
class Packages::Debian::ProjectDistribution < ApplicationRecord
def self.container_type
:project
end
include Packages::Debian::Distribution
end
......@@ -200,6 +200,8 @@ class Project < ApplicationRecord
# Packages
has_many :packages, class_name: 'Packages::Package'
has_many :package_files, through: :packages, class_name: 'Packages::PackageFile'
# debian_distributions must be destroyed by ruby code in order to properly remove carrierwave uploads
has_many :debian_distributions, class_name: 'Packages::Debian::ProjectDistribution', dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_one :import_state, autosave: true, class_name: 'ProjectImportState', inverse_of: :project
has_one :import_export_upload, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
......
# frozen_string_literal: true
class Packages::Debian::DistributionReleaseFileUploader < GitlabUploader
extend Workhorse::UploadPath
include ObjectStorage::Concern
storage_options Gitlab.config.packages
after :store, :schedule_background_upload
alias_method :upload, :model
def filename
'Release'
end
def store_dir
dynamic_segment
end
private
def dynamic_segment
raise ObjectNotReadyError, 'Package model not ready' unless model.id
Gitlab::HashedPath.new("debian_#{model.class.container_type}_distribution", model.id, root_hash: model.container_id)
end
end
---
title: Debian Group and Project Distributions
merge_request: 49405
author: Mathieu Parent
type: added
# frozen_string_literal: true
class CreatePackagesDebianProjectDistributions < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
UNIQUE_CODENAME = 'uniq_pkgs_debian_project_distributions_project_id_and_codename'
UNIQUE_SUITE = 'uniq_pkgs_debian_project_distributions_project_id_and_suite'
disable_ddl_transaction!
def up
with_lock_retries do
unless table_exists?(:packages_debian_project_distributions)
create_table :packages_debian_project_distributions do |t|
t.timestamps_with_timezone
t.references :project, foreign_key: { to_table: :projects, on_delete: :restrict }, null: false
t.references :creator, foreign_key: { to_table: :users, on_delete: :nullify }
t.integer :valid_time_duration_seconds
t.integer :file_store, limit: 2, default: 1, null: false
t.boolean :automatic, default: true, null: false
t.boolean :automatic_upgrades, default: false, null: false
t.text :codename, null: false
t.text :suite
t.text :origin
t.text :label
t.text :version
t.text :description
t.text :encrypted_signing_keys
t.text :encrypted_signing_keys_iv
t.text :file
t.text :file_signature
t.index %w(project_id codename),
name: UNIQUE_CODENAME,
unique: true,
using: :btree
t.index %w(project_id suite),
name: UNIQUE_SUITE,
unique: true,
using: :btree
end
end
end
add_text_limit :packages_debian_project_distributions, :codename, 255
add_text_limit :packages_debian_project_distributions, :suite, 255
add_text_limit :packages_debian_project_distributions, :origin, 255
add_text_limit :packages_debian_project_distributions, :label, 255
add_text_limit :packages_debian_project_distributions, :version, 255
add_text_limit :packages_debian_project_distributions, :description, 255
add_text_limit :packages_debian_project_distributions, :encrypted_signing_keys, 2048
add_text_limit :packages_debian_project_distributions, :encrypted_signing_keys_iv, 255
add_text_limit :packages_debian_project_distributions, :file, 255
add_text_limit :packages_debian_project_distributions, :file_signature, 4096
end
def down
drop_table :packages_debian_project_distributions
end
end
# frozen_string_literal: true
class CreatePackagesDebianGroupDistributions < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
UNIQUE_CODENAME = 'uniq_pkgs_debian_group_distributions_group_id_and_codename'
UNIQUE_SUITE = 'uniq_pkgs_debian_group_distributions_group_id_and_suite'
disable_ddl_transaction!
def up
with_lock_retries do
unless table_exists?(:packages_debian_group_distributions)
create_table :packages_debian_group_distributions do |t|
t.timestamps_with_timezone
t.references :group, foreign_key: { to_table: :namespaces, on_delete: :restrict }, null: false
t.references :creator, foreign_key: { to_table: :users, on_delete: :nullify }
t.integer :valid_time_duration_seconds
t.integer :file_store, limit: 2, default: 1, null: false
t.boolean :automatic, default: true, null: false
t.boolean :automatic_upgrades, default: false, null: false
t.text :codename, null: false
t.text :suite
t.text :origin
t.text :label
t.text :version
t.text :description
t.text :encrypted_signing_keys
t.text :encrypted_signing_keys_iv
t.text :file
t.text :file_signature
t.index %w(group_id codename),
name: UNIQUE_CODENAME,
unique: true,
using: :btree
t.index %w(group_id suite),
name: UNIQUE_SUITE,
unique: true,
using: :btree
end
end
end
add_text_limit :packages_debian_group_distributions, :codename, 255
add_text_limit :packages_debian_group_distributions, :suite, 255
add_text_limit :packages_debian_group_distributions, :origin, 255
add_text_limit :packages_debian_group_distributions, :label, 255
add_text_limit :packages_debian_group_distributions, :version, 255
add_text_limit :packages_debian_group_distributions, :description, 255
add_text_limit :packages_debian_group_distributions, :encrypted_signing_keys, 2048
add_text_limit :packages_debian_group_distributions, :encrypted_signing_keys_iv, 255
add_text_limit :packages_debian_group_distributions, :file, 255
add_text_limit :packages_debian_group_distributions, :file_signature, 4096
end
def down
drop_table :packages_debian_group_distributions
end
end
986ffa5e3e168ce9acf9b346c94bdee05d85c71abe238b8aa21f95cc472faabc
\ No newline at end of file
aecf517402d3decf8f7323e8f43fdfe7160cbe7542a474e392996abd75b2d70f
\ No newline at end of file
......@@ -14721,6 +14721,88 @@ CREATE TABLE packages_debian_file_metadata (
CONSTRAINT check_e6e1fffcca CHECK ((char_length(architecture) <= 255))
);
CREATE TABLE packages_debian_group_distributions (
id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
group_id bigint NOT NULL,
creator_id bigint,
valid_time_duration_seconds integer,
file_store smallint DEFAULT 1 NOT NULL,
automatic boolean DEFAULT true NOT NULL,
automatic_upgrades boolean DEFAULT false NOT NULL,
codename text NOT NULL,
suite text,
origin text,
label text,
version text,
description text,
encrypted_signing_keys text,
encrypted_signing_keys_iv text,
file text,
file_signature text,
CONSTRAINT check_310ac457b8 CHECK ((char_length(description) <= 255)),
CONSTRAINT check_3d6f87fc31 CHECK ((char_length(file_signature) <= 4096)),
CONSTRAINT check_3fdadf4a0c CHECK ((char_length(version) <= 255)),
CONSTRAINT check_590e18405a CHECK ((char_length(codename) <= 255)),
CONSTRAINT check_9b90bc0f07 CHECK ((char_length(encrypted_signing_keys_iv) <= 255)),
CONSTRAINT check_b057cd840a CHECK ((char_length(origin) <= 255)),
CONSTRAINT check_b811ec1218 CHECK ((char_length(encrypted_signing_keys) <= 2048)),
CONSTRAINT check_be5ed8d307 CHECK ((char_length(file) <= 255)),
CONSTRAINT check_d3244bfc0b CHECK ((char_length(label) <= 255)),
CONSTRAINT check_e7c928a24b CHECK ((char_length(suite) <= 255))
);
CREATE SEQUENCE packages_debian_group_distributions_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE packages_debian_group_distributions_id_seq OWNED BY packages_debian_group_distributions.id;
CREATE TABLE packages_debian_project_distributions (
id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
project_id bigint NOT NULL,
creator_id bigint,
valid_time_duration_seconds integer,
file_store smallint DEFAULT 1 NOT NULL,
automatic boolean DEFAULT true NOT NULL,
automatic_upgrades boolean DEFAULT false NOT NULL,
codename text NOT NULL,
suite text,
origin text,
label text,
version text,
description text,
encrypted_signing_keys text,
encrypted_signing_keys_iv text,
file text,
file_signature text,
CONSTRAINT check_6177ccd4a6 CHECK ((char_length(origin) <= 255)),
CONSTRAINT check_6f6b55a4c4 CHECK ((char_length(label) <= 255)),
CONSTRAINT check_834dabadb6 CHECK ((char_length(codename) <= 255)),
CONSTRAINT check_96965792c2 CHECK ((char_length(version) <= 255)),
CONSTRAINT check_a56ae58a17 CHECK ((char_length(suite) <= 255)),
CONSTRAINT check_a5a2ac6af2 CHECK ((char_length(file_signature) <= 4096)),
CONSTRAINT check_b93154339f CHECK ((char_length(description) <= 255)),
CONSTRAINT check_c25603a25b CHECK ((char_length(encrypted_signing_keys) <= 2048)),
CONSTRAINT check_cb4ac9599e CHECK ((char_length(file) <= 255)),
CONSTRAINT check_d488f8cce3 CHECK ((char_length(encrypted_signing_keys_iv) <= 255))
);
CREATE SEQUENCE packages_debian_project_distributions_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE packages_debian_project_distributions_id_seq OWNED BY packages_debian_project_distributions.id;
CREATE TABLE packages_dependencies (
id bigint NOT NULL,
name character varying(255) NOT NULL,
......@@ -18657,6 +18739,10 @@ ALTER TABLE ONLY packages_conan_file_metadata ALTER COLUMN id SET DEFAULT nextva
ALTER TABLE ONLY packages_conan_metadata ALTER COLUMN id SET DEFAULT nextval('packages_conan_metadata_id_seq'::regclass);
ALTER TABLE ONLY packages_debian_group_distributions ALTER COLUMN id SET DEFAULT nextval('packages_debian_group_distributions_id_seq'::regclass);
ALTER TABLE ONLY packages_debian_project_distributions ALTER COLUMN id SET DEFAULT nextval('packages_debian_project_distributions_id_seq'::regclass);
ALTER TABLE ONLY packages_dependencies ALTER COLUMN id SET DEFAULT nextval('packages_dependencies_id_seq'::regclass);
ALTER TABLE ONLY packages_dependency_links ALTER COLUMN id SET DEFAULT nextval('packages_dependency_links_id_seq'::regclass);
......@@ -20008,6 +20094,12 @@ ALTER TABLE ONLY packages_conan_metadata
ALTER TABLE ONLY packages_debian_file_metadata
ADD CONSTRAINT packages_debian_file_metadata_pkey PRIMARY KEY (package_file_id);
ALTER TABLE ONLY packages_debian_group_distributions
ADD CONSTRAINT packages_debian_group_distributions_pkey PRIMARY KEY (id);
ALTER TABLE ONLY packages_debian_project_distributions
ADD CONSTRAINT packages_debian_project_distributions_pkey PRIMARY KEY (id);
ALTER TABLE ONLY packages_dependencies
ADD CONSTRAINT packages_dependencies_pkey PRIMARY KEY (id);
......@@ -22190,6 +22282,14 @@ CREATE UNIQUE INDEX index_packages_conan_file_metadata_on_package_file_id ON pac
CREATE UNIQUE INDEX index_packages_conan_metadata_on_package_id_username_channel ON packages_conan_metadata USING btree (package_id, package_username, package_channel);
CREATE INDEX index_packages_debian_group_distributions_on_creator_id ON packages_debian_group_distributions USING btree (creator_id);
CREATE INDEX index_packages_debian_group_distributions_on_group_id ON packages_debian_group_distributions USING btree (group_id);
CREATE INDEX index_packages_debian_project_distributions_on_creator_id ON packages_debian_project_distributions USING btree (creator_id);
CREATE INDEX index_packages_debian_project_distributions_on_project_id ON packages_debian_project_distributions USING btree (project_id);
CREATE UNIQUE INDEX index_packages_dependencies_on_name_and_version_pattern ON packages_dependencies USING btree (name, version_pattern);
CREATE INDEX index_packages_dependency_links_on_dependency_id ON packages_dependency_links USING btree (dependency_id);
......@@ -23146,6 +23246,14 @@ CREATE INDEX tmp_index_oauth_applications_on_id_where_trusted ON oauth_applicati
CREATE INDEX tmp_index_on_vulnerabilities_non_dismissed ON vulnerabilities USING btree (id) WHERE (state <> 2);
CREATE UNIQUE INDEX uniq_pkgs_debian_group_distributions_group_id_and_codename ON packages_debian_group_distributions USING btree (group_id, codename);
CREATE UNIQUE INDEX uniq_pkgs_debian_group_distributions_group_id_and_suite ON packages_debian_group_distributions USING btree (group_id, suite);
CREATE UNIQUE INDEX uniq_pkgs_debian_project_distributions_project_id_and_codename ON packages_debian_project_distributions USING btree (project_id, codename);
CREATE UNIQUE INDEX uniq_pkgs_debian_project_distributions_project_id_and_suite ON packages_debian_project_distributions USING btree (project_id, suite);
CREATE UNIQUE INDEX unique_merge_request_metrics_by_merge_request_id ON merge_request_metrics USING btree (merge_request_id);
CREATE UNIQUE INDEX vulnerability_feedback_unique_idx ON vulnerability_feedback USING btree (project_id, category, feedback_type, project_fingerprint);
......@@ -24148,6 +24256,9 @@ ALTER TABLE ONLY trending_projects
ALTER TABLE ONLY project_deploy_tokens
ADD CONSTRAINT fk_rails_0aca134388 FOREIGN KEY (deploy_token_id) REFERENCES deploy_tokens(id) ON DELETE CASCADE;
ALTER TABLE ONLY packages_debian_group_distributions
ADD CONSTRAINT fk_rails_0adf75c347 FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE RESTRICT;
ALTER TABLE ONLY packages_conan_file_metadata
ADD CONSTRAINT fk_rails_0afabd9328 FOREIGN KEY (package_file_id) REFERENCES packages_package_files(id) ON DELETE CASCADE;
......@@ -24934,6 +25045,9 @@ ALTER TABLE ONLY alert_management_alert_assignees
ALTER TABLE ONLY scim_identities
ADD CONSTRAINT fk_rails_9421a0bffb FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
ALTER TABLE ONLY packages_debian_project_distributions
ADD CONSTRAINT fk_rails_94b95e1f84 FOREIGN KEY (creator_id) REFERENCES users(id) ON DELETE SET NULL;
ALTER TABLE ONLY packages_pypi_metadata
ADD CONSTRAINT fk_rails_9698717cdd FOREIGN KEY (package_id) REFERENCES packages_packages(id) ON DELETE CASCADE;
......@@ -25294,6 +25408,9 @@ ALTER TABLE ONLY user_callouts
ALTER TABLE ONLY vulnerability_feedback
ADD CONSTRAINT fk_rails_debd54e456 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY packages_debian_project_distributions
ADD CONSTRAINT fk_rails_df44271a30 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE RESTRICT;
ALTER TABLE ONLY incident_management_oncall_shifts
ADD CONSTRAINT fk_rails_df4feb286a FOREIGN KEY (rotation_id) REFERENCES incident_management_oncall_rotations(id) ON DELETE CASCADE;
......@@ -25369,6 +25486,9 @@ ALTER TABLE ONLY snippet_statistics
ALTER TABLE ONLY project_security_settings
ADD CONSTRAINT fk_rails_ed4abe1338 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY packages_debian_group_distributions
ADD CONSTRAINT fk_rails_ede0bb937f FOREIGN KEY (creator_id) REFERENCES users(id) ON DELETE SET NULL;
ALTER TABLE ONLY experiment_subjects
ADD CONSTRAINT fk_rails_ede5754774 FOREIGN KEY (experiment_id) REFERENCES experiments(id) ON DELETE CASCADE;
......
......@@ -33255,6 +33255,12 @@ msgstr ""
msgid "has already been taken"
msgstr ""
msgid "has already been taken as Codename"
msgstr ""
msgid "has already been taken as Suite"
msgstr ""
msgid "has been completed."
msgstr ""
......
# frozen_string_literal: true
FactoryBot.define do
factory :debian_project_distribution, class: 'Packages::Debian::ProjectDistribution' do
container { association(:project) }
sequence(:codename) { |n| "project-dist-#{n}" }
factory :debian_group_distribution, class: 'Packages::Debian::GroupDistribution' do
container { association(:group) }
sequence(:codename) { |n| "group-dist-#{n}" }
end
trait(:with_file) do
after(:build) do |distribution, evaluator|
distribution.file = fixture_file_upload('spec/fixtures/packages/debian/README.md')
end
end
trait(:object_storage) do
file_store { Packages::PackageFileUploader::Store::REMOTE }
end
end
end
......@@ -561,6 +561,7 @@ project:
- alert_management_http_integrations
- exported_protected_branches
- incident_management_oncall_schedules
- debian_distributions
award_emoji:
- awardable
- user
......
......@@ -31,6 +31,7 @@ RSpec.describe Group do
it { is_expected.to have_one(:dependency_proxy_setting) }
it { is_expected.to have_many(:dependency_proxy_blobs) }
it { is_expected.to have_many(:dependency_proxy_manifests) }
it { is_expected.to have_many(:debian_distributions).class_name('Packages::Debian::GroupDistribution').dependent(:destroy) }
describe '#members & #requesters' do
let(:requester) { create(:user) }
......@@ -1751,4 +1752,23 @@ RSpec.describe Group do
it { is_expected.to eq(false) }
end
end
describe 'with Debian Distributions' do
subject { create(:group) }
let!(:distributions) { create_list(:debian_group_distribution, 2, :with_file, container: subject) }
it 'removes distribution files on removal' do
distribution_file_paths = distributions.map do |distribution|
distribution.file.path
end
expect { subject.destroy }
.to change {
distribution_file_paths.select do |path|
File.exist? path
end.length
}.from(distribution_file_paths.length).to(0)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Packages::Debian::GroupDistribution do
it_behaves_like 'Debian Distribution', :debian_group_distribution, :group, false
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Packages::Debian::ProjectDistribution do
it_behaves_like 'Debian Distribution', :debian_project_distribution, :project, true
end
......@@ -127,6 +127,7 @@ RSpec.describe Project, factory_default: :keep do
it { is_expected.to have_many(:reviews).inverse_of(:project) }
it { is_expected.to have_many(:packages).class_name('Packages::Package') }
it { is_expected.to have_many(:package_files).class_name('Packages::PackageFile') }
it { is_expected.to have_many(:debian_distributions).class_name('Packages::Debian::ProjectDistribution').dependent(:destroy) }
it { is_expected.to have_many(:pipeline_artifacts) }
it { is_expected.to have_many(:terraform_states).class_name('Terraform::State').inverse_of(:project) }
......@@ -6489,6 +6490,25 @@ RSpec.describe Project, factory_default: :keep do
end
end
describe 'with Debian Distributions' do
subject { create(:project) }
let!(:distributions) { create_list(:debian_project_distribution, 2, :with_file, container: subject) }
it 'removes distribution files on removal' do
distribution_file_paths = distributions.map do |distribution|
distribution.file.path
end
expect { subject.destroy }
.to change {
distribution_file_paths.select do |path|
File.exist? path
end.length
}.from(distribution_file_paths.length).to(0)
end
end
describe '#environments_for_scope' do
let_it_be(:project, reload: true) { create(:project) }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.shared_examples 'Debian Distribution' do |factory, container, can_freeze|
let_it_be(:distribution_with_suite, freeze: can_freeze) { create(factory, suite: 'mysuite') }
let_it_be(:distribution_with_same_container, freeze: can_freeze) { create(factory, container: distribution_with_suite.container ) }
let_it_be(:distribution_with_same_codename, freeze: can_freeze) { create(factory, codename: distribution_with_suite.codename ) }
let_it_be(:distribution_with_same_suite, freeze: can_freeze) { create(factory, suite: distribution_with_suite.suite ) }
let_it_be_with_refind(:distribution) { create(factory, container: distribution_with_suite.container ) }
subject { distribution }
describe 'relationships' do
it { is_expected.to belong_to(container) }
it { is_expected.to belong_to(:creator).class_name('User') }
end
describe 'validations' do
describe "##{container}" do
it { is_expected.to validate_presence_of(container) }
end
describe "#creator" do
it { is_expected.not_to validate_presence_of(:creator) }
end
describe '#codename' do
it { is_expected.to validate_presence_of(:codename) }
it { is_expected.to allow_value('buster').for(:codename) }
it { is_expected.to allow_value('buster-updates').for(:codename) }
it { is_expected.to allow_value('Debian10.5').for(:codename) }
it { is_expected.not_to allow_value('jessie/updates').for(:codename) }
it { is_expected.not_to allow_value('hé').for(:codename) }
end
describe '#suite' do
it { is_expected.to allow_value(nil).for(:suite) }
it { is_expected.to allow_value('testing').for(:suite) }
it { is_expected.not_to allow_value('hé').for(:suite) }
end
describe '#unique_debian_suite_and_codename' do
using RSpec::Parameterized::TableSyntax
where(:with_existing_suite, :suite, :codename, :errors) do
false | nil | :keep | nil
false | 'testing' | :keep | nil
false | nil | :codename | ["Codename has already been taken"]
false | :codename | :keep | ["Suite has already been taken as Codename"]
false | :codename | :codename | ["Codename has already been taken", "Suite has already been taken as Codename"]
true | nil | :keep | nil
true | 'testing' | :keep | nil
true | nil | :codename | ["Codename has already been taken"]
true | :codename | :keep | ["Suite has already been taken as Codename"]
true | :codename | :codename | ["Codename has already been taken", "Suite has already been taken as Codename"]
true | nil | :suite | ["Codename has already been taken as Suite"]
true | :suite | :keep | ["Suite has already been taken"]
true | :suite | :suite | ["Suite has already been taken", "Codename has already been taken as Suite"]
end
with_them do
context factory do
let(:new_distribution) { build(factory, container: distribution.container) }
before do
distribution.update_column(:suite, 'suite-' + distribution.codename) if with_existing_suite
if suite.is_a?(Symbol)
new_distribution.suite = distribution.send suite unless suite == :keep
else
new_distribution.suite = suite
end
if codename.is_a?(Symbol)
new_distribution.codename = distribution.send codename unless codename == :keep
else
new_distribution.codename = codename
end
end
it do
if errors
expect(new_distribution).not_to be_valid
expect(new_distribution.errors.to_a).to eq(errors)
else
expect(new_distribution).to be_valid
end
end
end
end
end
describe '#origin' do
it { is_expected.to allow_value(nil).for(:origin) }
it { is_expected.to allow_value('Debian').for(:origin) }
it { is_expected.not_to allow_value('hé').for(:origin) }
end
describe '#label' do
it { is_expected.to allow_value(nil).for(:label) }
it { is_expected.to allow_value('Debian').for(:label) }
it { is_expected.not_to allow_value('hé').for(:label) }
end
describe '#version' do
it { is_expected.to allow_value(nil).for(:version) }
it { is_expected.to allow_value('10.6').for(:version) }
it { is_expected.not_to allow_value('hé').for(:version) }
end
describe '#description' do
it { is_expected.to allow_value(nil).for(:description) }
it { is_expected.to allow_value('Debian 10.6 Released 26 September 2020').for(:description) }
it { is_expected.to allow_value('Hé !').for(:description) }
end
describe '#valid_time_duration_seconds' do
it { is_expected.to allow_value(nil).for(:valid_time_duration_seconds) }
it { is_expected.to allow_value(24.hours.to_i).for(:valid_time_duration_seconds) }
it { is_expected.not_to allow_value(12.hours.to_i).for(:valid_time_duration_seconds) }
end
describe '#signing_keys' do
it { is_expected.to validate_absence_of(:signing_keys) }
end
describe '#file' do
it { is_expected.not_to validate_presence_of(:file) }
end
describe '#file_store' do
it { is_expected.to validate_presence_of(:file_store) }
end
describe '#file_signature' do
it { is_expected.to validate_absence_of(:file_signature) }
end
end
describe 'scopes' do
describe '.with_container' do
subject { described_class.with_container(distribution_with_suite.container) }
it 'does not return other distributions' do
expect(subject.to_a).to eq([distribution_with_suite, distribution, distribution_with_same_container])
end
end
describe '.with_codename' do
subject { described_class.with_codename(distribution_with_suite.codename) }
it 'does not return other distributions' do
expect(subject.to_a).to eq([distribution_with_suite, distribution_with_same_codename])
end
end
describe '.with_suite' do
subject { described_class.with_suite(distribution_with_suite.suite) }
it 'does not return other distributions' do
expect(subject.to_a).to eq([distribution_with_suite, distribution_with_same_suite])
end
end
end
describe '#needs_update?' do
subject { distribution.needs_update? }
context 'with new distribution' do
let(:distribution) { create(factory, container: distribution_with_suite.container) }
it { is_expected.to be_truthy }
end
context 'with file' do
context 'without valid_time_duration_seconds' do
let(:distribution) { create(factory, :with_file, container: distribution_with_suite.container) }
it { is_expected.to be_falsey }
end
context 'with valid_time_duration_seconds' do
let(:distribution) { create(factory, :with_file, container: distribution_with_suite.container, valid_time_duration_seconds: 2.days.to_i) }
context 'when not yet expired' do
it { is_expected.to be_falsey }
end
context 'when expired' do
it do
distribution
travel_to(4.days.from_now) do
is_expected.to be_truthy
end
end
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Packages::Debian::DistributionReleaseFileUploader do
[:project, :group].each do |container_type|
context "Packages::Debian::#{container_type.capitalize}Distribution" do
let(:factory) { "debian_#{container_type}_distribution" }
let(:distribution) { create(factory, :with_file) }
let(:uploader) { described_class.new(distribution, :file) }
let(:path) { Gitlab.config.packages.storage_path }
subject { uploader }
it_behaves_like "builds correct paths",
store_dir: %r[^\h{2}/\h{2}/\h{64}/debian_#{container_type}_distribution/\d+$],
cache_dir: %r[/packages/tmp/cache$],
work_dir: %r[/packages/tmp/work$]
context 'object store is remote' do
before do
stub_package_file_object_storage
end
include_context 'with storage', described_class::Store::REMOTE
it_behaves_like "builds correct paths",
store_dir: %r[^\h{2}/\h{2}/\h{64}/debian_#{container_type}_distribution/\d+$],
cache_dir: %r[/packages/tmp/cache$],
work_dir: %r[/packages/tmp/work$]
end
describe 'remote file' do
let(:distribution) { create(factory, :with_file, :object_storage) }
context 'with object storage enabled' do
before do
stub_package_file_object_storage
end
it 'can store file remotely' do
allow(ObjectStorage::BackgroundMoveWorker).to receive(:perform_async)
distribution
expect(distribution.file_store).to eq(described_class::Store::REMOTE)
expect(distribution.file.path).not_to be_blank
end
end
end
end
end
end
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