Commit 3e1346f9 authored by Steve Abrams's avatar Steve Abrams

Merge branch 'helm_charts_mvc' into 'master'

Helm Charts Package Manager MVC

See merge request gitlab-org/gitlab!57017
parents af886524 6e7cc27c
# frozen_string_literal: true
module Packages
module Helm
def self.table_name_prefix
'packages_helm_'
end
end
end
# frozen_string_literal: true
module Packages
module Helm
class FileMetadatum < ApplicationRecord
self.primary_key = :package_file_id
belongs_to :package_file, inverse_of: :helm_file_metadatum
validates :package_file, presence: true
validate :valid_helm_package_type
validates :channel,
presence: true,
length: { maximum: 63 },
format: { with: Gitlab::Regex.helm_channel_regex }
validates :metadata,
json_schema: { filename: "helm_metadata" }
private
def valid_helm_package_type
return if package_file&.package&.helm?
errors.add(:package_file, _('Package type must be Helm'))
end
end
end
end
......@@ -47,6 +47,7 @@ class Packages::Package < ApplicationRecord
validate :package_already_taken, if: :npm?
validates :name, format: { with: Gitlab::Regex.conan_recipe_component_regex }, if: :conan?
validates :name, format: { with: Gitlab::Regex.generic_package_name_regex }, if: :generic?
validates :name, format: { with: Gitlab::Regex.helm_package_regex }, if: :helm?
validates :name, format: { with: Gitlab::Regex.npm_package_name_regex }, if: :npm?
validates :name, format: { with: Gitlab::Regex.nuget_package_name_regex }, if: :nuget?
validates :name, format: { with: Gitlab::Regex.debian_package_name_regex }, if: :debian_package?
......@@ -56,6 +57,7 @@ class Packages::Package < ApplicationRecord
validates :version, format: { with: Gitlab::Regex.maven_version_regex }, if: -> { version? && maven? }
validates :version, format: { with: Gitlab::Regex.pypi_version_regex }, if: :pypi?
validates :version, format: { with: Gitlab::Regex.prefixed_semver_regex }, if: :golang?
validates :version, format: { with: Gitlab::Regex.prefixed_semver_regex }, if: :helm?
validates :version, format: { with: Gitlab::Regex.semver_regex }, if: -> { composer_tag_version? || npm? }
validates :version,
......@@ -70,7 +72,7 @@ class Packages::Package < ApplicationRecord
enum package_type: { maven: 1, npm: 2, conan: 3, nuget: 4, pypi: 5,
composer: 6, generic: 7, golang: 8, debian: 9,
rubygems: 10 }
rubygems: 10, helm: 11 }
enum status: { default: 0, hidden: 1, processing: 2, error: 3 }
......
......@@ -6,6 +6,7 @@ class Packages::PackageFile < ApplicationRecord
delegate :project, :project_id, to: :package
delegate :conan_file_type, to: :conan_file_metadatum
delegate :file_type, :architecture, :fields, to: :debian_file_metadatum, prefix: :debian
delegate :channel, :metadata, to: :helm_file_metadatum, prefix: :helm
belongs_to :package
......@@ -13,9 +14,11 @@ class Packages::PackageFile < ApplicationRecord
has_many :package_file_build_infos, inverse_of: :package_file, class_name: 'Packages::PackageFileBuildInfo'
has_many :pipelines, through: :package_file_build_infos
has_one :debian_file_metadatum, inverse_of: :package_file, class_name: 'Packages::Debian::FileMetadatum'
has_one :helm_file_metadatum, inverse_of: :package_file, class_name: 'Packages::Helm::FileMetadatum'
accepts_nested_attributes_for :conan_file_metadatum
accepts_nested_attributes_for :debian_file_metadatum
accepts_nested_attributes_for :helm_file_metadatum
validates :package, presence: true
validates :file, presence: true
......
{
"description": "Helm metadata",
"type": "object",
"properties": {
"name": {
"type": "string"
},
"home": {
"type": "string"
},
"sources": {
"type": "array",
"items": {
"type": "string"
}
},
"version": {
"type": "string"
},
"description": {
"type": "string"
},
"keywords": {
"type": "array",
"items": {
"type": "string"
}
},
"maintainers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"email": {
"type": "string"
},
"url": {
"type": "string"
}
},
"additionalProperties": false
}
},
"icon": {
"type": "string"
},
"apiVersion": {
"type": "string"
},
"condition": {
"type": "string"
},
"tags": {
"type": "string"
},
"appVersion": {
"type": "string"
},
"deprecated": {
"type": "boolean"
},
"annotations": {
"type": "object",
"patternProperties": {
".+": {
"type": "string"
},
"additionalProperties": false
}
},
"kubeVersion": {
"type": "string"
},
"dependencies": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"repository": {
"type": "string"
},
"condition": {
"type": "string"
},
"tags": {
"type": "array",
"items": {
"type": "string"
}
},
"enabled": {
"type": "boolean"
},
"import-values": {
"type": "array",
"items": {
}
},
"alias": {
"type": "string",
"pattern": "^[a-zA-Z0-9_-]+$"
},
"additionalProperties": false
}
}
},
"type": {
"type": "string",
"enum": ["application", "library"]
}
},
"additionalProperties": false,
"required": [
"name",
"version",
"apiVersion"
]
}
\ No newline at end of file
---
title: Create packages_helm_file_metadata table
merge_request: 57017
author: Mathieu Parent
type: added
# frozen_string_literal: true
class CreatePackagesHelmFileMetadata < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
create_table_with_constraints :packages_helm_file_metadata, id: false do |t|
t.timestamps_with_timezone
t.references :package_file, primary_key: true, index: false, default: nil, null: false, foreign_key: { to_table: :packages_package_files, on_delete: :cascade }, type: :bigint
t.text :channel, null: false
t.jsonb :metadata
t.text_limit :channel, 63
t.index :channel
end
end
end
# frozen_string_literal: true
class AddHelmMaxFileSizeToPlanLimits < ActiveRecord::Migration[6.0]
DOWNTIME = false
def change
add_column :plan_limits, :helm_max_file_size, :bigint, default: 5.megabyte, null: false
end
end
b17c853b2bc82cfa83cd82b8023eca39d875d898b99e78c81d767a73391a0b75
\ No newline at end of file
3f9e229fc13075c2a2d42931b163c8069089458d66bc565609b393e07460f25d
\ No newline at end of file
......@@ -15639,6 +15639,15 @@ CREATE SEQUENCE packages_events_id_seq
ALTER SEQUENCE packages_events_id_seq OWNED BY packages_events.id;
CREATE TABLE packages_helm_file_metadata (
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
package_file_id bigint NOT NULL,
channel text NOT NULL,
metadata jsonb,
CONSTRAINT check_c34067922d CHECK ((char_length(channel) <= 63))
);
CREATE TABLE packages_maven_metadata (
id bigint NOT NULL,
package_id bigint NOT NULL,
......@@ -16015,7 +16024,8 @@ CREATE TABLE plan_limits (
pull_mirror_interval_seconds integer DEFAULT 300 NOT NULL,
daily_invites integer DEFAULT 0 NOT NULL,
rubygems_max_file_size bigint DEFAULT '3221225472'::bigint NOT NULL,
terraform_module_max_file_size bigint DEFAULT 1073741824 NOT NULL
terraform_module_max_file_size bigint DEFAULT 1073741824 NOT NULL,
helm_max_file_size bigint DEFAULT 5242880 NOT NULL
);
CREATE SEQUENCE plan_limits_id_seq
......@@ -21273,6 +21283,9 @@ ALTER TABLE ONLY packages_dependency_links
ALTER TABLE ONLY packages_events
ADD CONSTRAINT packages_events_pkey PRIMARY KEY (id);
ALTER TABLE ONLY packages_helm_file_metadata
ADD CONSTRAINT packages_helm_file_metadata_pkey PRIMARY KEY (package_file_id);
ALTER TABLE ONLY packages_maven_metadata
ADD CONSTRAINT packages_maven_metadata_pkey PRIMARY KEY (id);
......@@ -23610,6 +23623,8 @@ CREATE INDEX index_packages_dependency_links_on_dependency_id ON packages_depend
CREATE INDEX index_packages_events_on_package_id ON packages_events USING btree (package_id);
CREATE INDEX index_packages_helm_file_metadata_on_channel ON packages_helm_file_metadata USING btree (channel);
CREATE INDEX index_packages_maven_metadata_on_package_id_and_path ON packages_maven_metadata USING btree (package_id, path);
CREATE INDEX index_packages_maven_metadata_on_path ON packages_maven_metadata USING btree (path);
......@@ -26645,6 +26660,9 @@ ALTER TABLE ONLY fork_network_members
ALTER TABLE ONLY operations_feature_flag_scopes
ADD CONSTRAINT fk_rails_a50a04d0a4 FOREIGN KEY (feature_flag_id) REFERENCES operations_feature_flags(id) ON DELETE CASCADE;
ALTER TABLE ONLY packages_helm_file_metadata
ADD CONSTRAINT fk_rails_a559865345 FOREIGN KEY (package_file_id) REFERENCES packages_package_files(id) ON DELETE CASCADE;
ALTER TABLE ONLY cluster_projects
ADD CONSTRAINT fk_rails_a5a958bca1 FOREIGN KEY (cluster_id) REFERENCES clusters(id) ON DELETE CASCADE;
......@@ -14015,6 +14015,7 @@ Values for sorting package.
| <a id="packagetypeenumdebian"></a>`DEBIAN` | Packages from the Debian package manager. |
| <a id="packagetypeenumgeneric"></a>`GENERIC` | Packages from the Generic package manager. |
| <a id="packagetypeenumgolang"></a>`GOLANG` | Packages from the Golang package manager. |
| <a id="packagetypeenumhelm"></a>`HELM` | Packages from the Helm package manager. |
| <a id="packagetypeenummaven"></a>`MAVEN` | Packages from the Maven package manager. |
| <a id="packagetypeenumnpm"></a>`NPM` | Packages from the npm package manager. |
| <a id="packagetypeenumnuget"></a>`NUGET` | Packages from the Nuget package manager. |
......
......@@ -125,6 +125,18 @@ module Gitlab
@debian_component_regex ||= %r{#{debian_distribution_regex}}.freeze
end
def helm_channel_regex
@helm_channel_regex ||= %r{\A[-\.\_a-zA-Z0-9]+\z}.freeze
end
def helm_package_regex
@helm_package_regex ||= %r{#{helm_channel_regex}}.freeze
end
def helm_version_regex
@helm_version_regex ||= %r{#{prefixed_semver_regex}}.freeze
end
def unbounded_semver_regex
# See the official regex: https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
......
......@@ -23023,6 +23023,9 @@ msgstr ""
msgid "Package type must be Debian"
msgstr ""
msgid "Package type must be Helm"
msgstr ""
msgid "Package type must be Maven"
msgstr ""
......
......@@ -94,6 +94,22 @@ FactoryBot.define do
end
end
factory :helm_package do
sequence(:name) { |n| "package-#{n}" }
sequence(:version) { |n| "v1.0.#{n}" }
package_type { :helm }
transient do
without_package_files { false }
end
after :create do |package, evaluator|
unless evaluator.without_package_files
create :helm_package_file, package: package
end
end
end
factory :npm_package do
sequence(:name) { |n| "@#{project.root_namespace.path}/package-#{n}"}
version { '1.0.0' }
......
# frozen_string_literal: true
FactoryBot.define do
factory :helm_file_metadatum, class: 'Packages::Helm::FileMetadatum' do
package_file { association(:helm_package_file, without_loaded_metadatum: true) }
channel { 'stable' }
metadata { { 'name': package_file.package.name, 'version': package_file.package.version, 'apiVersion': 'v2' } }
end
end
......@@ -201,6 +201,23 @@ FactoryBot.define do
end
end
factory :helm_package_file do
package { association(:helm_package, without_package_files: true) }
file_name { "#{package.name}-#{package.version}.tgz" }
file_fixture { "spec/fixtures/packages/helm/rook-ceph-v1.5.8.tgz" }
transient do
without_loaded_metadatum { false }
channel { 'stable' }
end
after :create do |package_file, evaluator|
unless evaluator.without_loaded_metadatum
create :helm_file_metadatum, package_file: package_file, channel: evaluator.channel
end
end
end
trait(:jar) do
file_fixture { 'spec/fixtures/packages/maven/my-app-1.0-20180724.124855-1.jar' }
file_name { 'my-app-1.0-20180724.124855-1.jar' }
......
......@@ -4,6 +4,6 @@ require 'spec_helper'
RSpec.describe GitlabSchema.types['PackageTypeEnum'] do
it 'exposes all package types' do
expect(described_class.values.keys).to contain_exactly(*%w[MAVEN NPM CONAN NUGET PYPI COMPOSER GENERIC GOLANG DEBIAN RUBYGEMS])
expect(described_class.values.keys).to contain_exactly(*%w[MAVEN NPM CONAN NUGET PYPI COMPOSER GENERIC GOLANG DEBIAN RUBYGEMS HELM])
end
end
......@@ -628,6 +628,50 @@ RSpec.describe Gitlab::Regex do
it { is_expected.not_to match('hé') }
end
describe '.helm_channel_regex' do
subject { described_class.helm_channel_regex }
it { is_expected.to match('release') }
it { is_expected.to match('my-repo') }
it { is_expected.to match('my-repo42') }
# Do not allow empty
it { is_expected.not_to match('') }
# Do not allow Unicode
it { is_expected.not_to match('hé') }
end
describe '.helm_package_regex' do
subject { described_class.helm_package_regex }
it { is_expected.to match('release') }
it { is_expected.to match('my-repo') }
it { is_expected.to match('my-repo42') }
# Do not allow empty
it { is_expected.not_to match('') }
# Do not allow Unicode
it { is_expected.not_to match('hé') }
it { is_expected.not_to match('my/../repo') }
it { is_expected.not_to match('me%2f%2e%2e%2f') }
end
describe '.helm_version_regex' do
subject { described_class.helm_version_regex }
it { is_expected.to match('v1.2.3') }
it { is_expected.to match('v1.2.3-beta') }
it { is_expected.to match('v1.2.3-alpha.3') }
it { is_expected.not_to match('v1') }
it { is_expected.not_to match('v1.2') }
it { is_expected.not_to match('v1./2.3') }
it { is_expected.not_to match('v../../../../../1.2.3') }
it { is_expected.not_to match('v%2e%2e%2f1.2.3') }
end
describe '.semver_regex' do
subject { described_class.semver_regex }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Packages::Helm::FileMetadatum, type: :model do
describe 'relationships' do
it { is_expected.to belong_to(:package_file) }
end
describe 'validations' do
describe '#package_file' do
it { is_expected.to validate_presence_of(:package_file) }
end
describe '#valid_helm_package_type' do
let_it_be_with_reload(:helm_package_file) { create(:helm_package_file) }
let(:helm_file_metadatum) { helm_package_file.helm_file_metadatum }
before do
helm_package_file.package.package_type = :pypi
end
it 'validates package of type helm' do
expect(helm_file_metadatum).not_to be_valid
expect(helm_file_metadatum.errors.to_a).to contain_exactly('Package file Package type must be Helm')
end
end
describe '#channel' do
it 'validates #channel', :aggregate_failures do
is_expected.to validate_presence_of(:channel)
is_expected.to allow_value('a' * 63).for(:channel)
is_expected.not_to allow_value('a' * 64).for(:channel)
is_expected.to allow_value('release').for(:channel)
is_expected.to allow_value('my-repo').for(:channel)
is_expected.to allow_value('my-repo42').for(:channel)
# Do not allow empty
is_expected.not_to allow_value('').for(:channel)
# Do not allow Unicode
is_expected.not_to allow_value('hé').for(:channel)
end
end
describe '#metadata' do
it 'validates #metadata', :aggregate_failures do
is_expected.not_to validate_presence_of(:metadata)
is_expected.to allow_value({ 'name': 'foo', 'version': 'v1.0', 'apiVersion': 'v2' }).for(:metadata)
is_expected.not_to allow_value({}).for(:metadata)
is_expected.not_to allow_value({ 'version': 'v1.0', 'apiVersion': 'v2' }).for(:metadata)
is_expected.not_to allow_value({ 'name': 'foo', 'apiVersion': 'v2' }).for(:metadata)
is_expected.not_to allow_value({ 'name': 'foo', 'version': 'v1.0' }).for(:metadata)
end
end
end
end
......@@ -8,6 +8,7 @@ RSpec.describe Packages::PackageFile, type: :model do
it { is_expected.to have_many(:package_file_build_infos).inverse_of(:package_file) }
it { is_expected.to have_many(:pipelines).through(:package_file_build_infos) }
it { is_expected.to have_one(:debian_file_metadatum).inverse_of(:package_file).class_name('Packages::Debian::FileMetadatum') }
it { is_expected.to have_one(:helm_file_metadatum).inverse_of(:package_file).class_name('Packages::Helm::FileMetadatum') }
end
describe 'validations' do
......
......@@ -173,6 +173,15 @@ RSpec.describe Packages::Package, type: :model do
it { is_expected.not_to allow_value('!!().for(:name)().for(:name)').for(:name) }
end
context 'helm package' do
subject { build(:helm_package) }
it { is_expected.to allow_value('prometheus').for(:name) }
it { is_expected.to allow_value('rook-ceph').for(:name) }
it { is_expected.not_to allow_value('a+b').for(:name) }
it { is_expected.not_to allow_value('Hé').for(:name) }
end
context 'nuget package' do
subject { build_stubbed(:nuget_package) }
......@@ -376,6 +385,15 @@ RSpec.describe Packages::Package, type: :model do
it { is_expected.not_to allow_value(nil).for(:version) }
end
context 'helm package' do
subject { build_stubbed(:helm_package) }
it { is_expected.not_to allow_value(nil).for(:version) }
it { is_expected.not_to allow_value('').for(:version) }
it { is_expected.to allow_value('v1.2.3').for(:version) }
it { is_expected.not_to allow_value('1.2.3').for(:version) }
end
it_behaves_like 'validating version to be SemVer compliant for', :npm_package
context 'nuget package' do
......
......@@ -203,7 +203,8 @@ RSpec.shared_examples 'filters on each package_type' do |is_project: false|
let_it_be(:package7) { create(:generic_package, project: project) }
let_it_be(:package8) { create(:golang_package, project: project) }
let_it_be(:package9) { create(:debian_package, project: project) }
let_it_be(:package9) { create(:rubygems_package, project: project) }
let_it_be(:package10) { create(:rubygems_package, project: project) }
let_it_be(:package11) { create(:helm_package, project: project) }
Packages::Package.package_types.keys.each do |package_type|
context "for package type #{package_type}" 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