diff --git a/app/assets/javascripts/integrations/edit/components/jira_issues_fields.vue b/app/assets/javascripts/integrations/edit/components/jira_issues_fields.vue index f8a44f0c2a2..d3d1fd8ddc3 100644 --- a/app/assets/javascripts/integrations/edit/components/jira_issues_fields.vue +++ b/app/assets/javascripts/integrations/edit/components/jira_issues_fields.vue @@ -49,7 +49,7 @@ export default { initialVulnerabilitiesIssuetype: { type: String, required: false, - default: '', + default: undefined, }, initialProjectKey: { type: String, diff --git a/app/assets/javascripts/pages/projects/jobs/index/index.js b/app/assets/javascripts/pages/projects/jobs/index/index.js index f66c09cb1ac..6a70d4cf26d 100644 --- a/app/assets/javascripts/pages/projects/jobs/index/index.js +++ b/app/assets/javascripts/pages/projects/jobs/index/index.js @@ -1,6 +1,5 @@ import Vue from 'vue'; import GlCountdown from '~/vue_shared/components/gl_countdown.vue'; -import Tracking from '~/tracking'; document.addEventListener('DOMContentLoaded', () => { const remainingTimeElements = document.querySelectorAll('.js-remaining-time'); @@ -17,13 +16,4 @@ document.addEventListener('DOMContentLoaded', () => { }, }), ); - - const trackButtonClick = () => { - if (gon.tracking_data) { - const { category, action, ...data } = gon.tracking_data; - Tracking.event(category, action, data); - } - }; - const buttons = document.querySelectorAll('.js-empty-state-button'); - buttons.forEach((button) => button.addEventListener('click', trackButtonClick)); }); diff --git a/app/controllers/groups/dependency_proxy_for_containers_controller.rb b/app/controllers/groups/dependency_proxy_for_containers_controller.rb index ff3c24a91a1..0f640397320 100644 --- a/app/controllers/groups/dependency_proxy_for_containers_controller.rb +++ b/app/controllers/groups/dependency_proxy_for_containers_controller.rb @@ -16,12 +16,7 @@ class Groups::DependencyProxyForContainersController < Groups::ApplicationContro result = DependencyProxy::FindOrCreateManifestService.new(group, image, tag, token).execute if result[:status] == :success - response.headers['Docker-Content-Digest'] = result[:manifest].digest - response.headers['Content-Length'] = result[:manifest].size - response.headers['Docker-Distribution-Api-Version'] = DependencyProxy::DISTRIBUTION_API_VERSION - response.headers['Etag'] = "\"#{result[:manifest].digest}\"" - - send_upload(result[:manifest].file, send_params: { type: result[:manifest].content_type }) + send_upload(result[:manifest].file) else render status: result[:http_status], json: result[:message] end diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb index d2703f5cc38..8a2ea51ba9d 100644 --- a/app/controllers/projects/jobs_controller.rb +++ b/app/controllers/projects/jobs_controller.rb @@ -15,9 +15,6 @@ class Projects::JobsController < Projects::ApplicationController before_action :verify_api_request!, only: :terminal_websocket_authorize before_action :authorize_create_proxy_build!, only: :proxy_websocket_authorize before_action :verify_proxy_request!, only: :proxy_websocket_authorize - before_action only: :index do - frontend_experimentation_tracking_data(:jobs_empty_state, 'click_button') - end layout 'project' diff --git a/app/models/dependency_proxy.rb b/app/models/dependency_proxy.rb index 0ed17921aaa..9cbaf7e9884 100644 --- a/app/models/dependency_proxy.rb +++ b/app/models/dependency_proxy.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true module DependencyProxy URL_SUFFIX = '/dependency_proxy/containers' - DISTRIBUTION_API_VERSION = 'registry/2.0' def self.table_name_prefix 'dependency_proxy_' diff --git a/app/models/dependency_proxy/manifest.rb b/app/models/dependency_proxy/manifest.rb index d613d5708f0..f3c7f34e0d7 100644 --- a/app/models/dependency_proxy/manifest.rb +++ b/app/models/dependency_proxy/manifest.rb @@ -12,10 +12,5 @@ class DependencyProxy::Manifest < ApplicationRecord mount_file_store_uploader DependencyProxy::FileUploader - def self.find_or_initialize_by_file_name_or_digest(file_name:, digest:) - result = find_by(file_name: file_name) || find_by(digest: digest) - return result if result - - new(file_name: file_name, digest: digest) - end + scope :find_or_initialize_by_file_name, ->(file_name) { find_or_initialize_by(file_name: file_name) } end diff --git a/app/models/packages/debian/project_distribution.rb b/app/models/packages/debian/project_distribution.rb index a73c12d172d..22f1008b3b5 100644 --- a/app/models/packages/debian/project_distribution.rb +++ b/app/models/packages/debian/project_distribution.rb @@ -5,5 +5,8 @@ class Packages::Debian::ProjectDistribution < ApplicationRecord :project end + has_many :publications, class_name: 'Packages::Debian::Publication', inverse_of: :distribution, foreign_key: :distribution_id + has_many :packages, class_name: 'Packages::Package', through: :publications + include Packages::Debian::Distribution end diff --git a/app/models/packages/debian/publication.rb b/app/models/packages/debian/publication.rb new file mode 100644 index 00000000000..93f5aa11d81 --- /dev/null +++ b/app/models/packages/debian/publication.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +class Packages::Debian::Publication < ApplicationRecord + belongs_to :package, + -> { where(package_type: :debian).where.not(version: nil) }, + inverse_of: :debian_publication, + class_name: 'Packages::Package' + belongs_to :distribution, + inverse_of: :publications, + class_name: 'Packages::Debian::ProjectDistribution', + foreign_key: :distribution_id + + validates :package, presence: true + validate :valid_debian_package_type + + validates :distribution, presence: true + + private + + def valid_debian_package_type + return errors.add(:package, _('type must be Debian')) unless package&.debian? + return errors.add(:package, _('must be a Debian package')) unless package.debian_package? + end +end diff --git a/app/models/packages/package.rb b/app/models/packages/package.rb index 2067a800ad5..4200f68d8fd 100644 --- a/app/models/packages/package.rb +++ b/app/models/packages/package.rb @@ -19,11 +19,15 @@ class Packages::Package < ApplicationRecord has_one :composer_metadatum, inverse_of: :package, class_name: 'Packages::Composer::Metadatum' has_many :build_infos, inverse_of: :package has_many :pipelines, through: :build_infos + has_one :debian_publication, inverse_of: :package, class_name: 'Packages::Debian::Publication' + has_one :debian_distribution, through: :debian_publication, source: :distribution, inverse_of: :packages, class_name: 'Packages::Debian::ProjectDistribution' accepts_nested_attributes_for :conan_metadatum + accepts_nested_attributes_for :debian_publication accepts_nested_attributes_for :maven_metadatum delegate :recipe, :recipe_path, to: :conan_metadatum, prefix: :conan + delegate :codename, :suite, to: :debian_distribution, prefix: :debian_distribution validates :project, presence: true validates :name, presence: true @@ -31,7 +35,8 @@ class Packages::Package < ApplicationRecord validates :name, format: { with: Gitlab::Regex.package_name_regex }, unless: -> { conan? || generic? || debian? } validates :name, - uniqueness: { scope: %i[project_id version package_type] }, unless: :conan? + uniqueness: { scope: %i[project_id version package_type] }, unless: -> { conan? || debian_package? } + validate :unique_debian_package_name, if: :debian_package? validate :valid_conan_package_recipe, if: :conan? validate :valid_npm_package_name, if: :npm? @@ -251,6 +256,18 @@ class Packages::Package < ApplicationRecord end end + def unique_debian_package_name + return unless debian_publication&.distribution + + package_exists = debian_publication.distribution.packages + .with_name(name) + .with_version(version) + .id_not_in(id) + .exists? + + errors.add(:base, _('Debian package already exists in Distribution')) if package_exists + end + def forbidden_debian_changes return unless persisted? diff --git a/app/services/dependency_proxy/find_or_create_manifest_service.rb b/app/services/dependency_proxy/find_or_create_manifest_service.rb index ee608d715aa..6b46f5e4c59 100644 --- a/app/services/dependency_proxy/find_or_create_manifest_service.rb +++ b/app/services/dependency_proxy/find_or_create_manifest_service.rb @@ -13,7 +13,7 @@ module DependencyProxy def execute @manifest = @group.dependency_proxy_manifests - .find_or_initialize_by_file_name_or_digest(file_name: @file_name, digest: @tag) + .find_or_initialize_by_file_name(@file_name) head_result = DependencyProxy::HeadManifestService.new(@image, @tag, @token).execute @@ -30,7 +30,6 @@ module DependencyProxy def pull_new_manifest DependencyProxy::PullManifestService.new(@image, @tag, @token).execute_with_manifest do |new_manifest| @manifest.update!( - content_type: new_manifest[:content_type], digest: new_manifest[:digest], file: new_manifest[:file], size: new_manifest[:file].size @@ -39,9 +38,7 @@ module DependencyProxy end def cached_manifest_matches?(head_result) - return false if head_result[:status] == :error - - @manifest && @manifest.digest == head_result[:digest] && @manifest.content_type == head_result[:content_type] + @manifest && @manifest.digest == head_result[:digest] end def respond diff --git a/app/services/dependency_proxy/head_manifest_service.rb b/app/services/dependency_proxy/head_manifest_service.rb index ecc3eb77399..87d9c417c98 100644 --- a/app/services/dependency_proxy/head_manifest_service.rb +++ b/app/services/dependency_proxy/head_manifest_service.rb @@ -2,8 +2,6 @@ module DependencyProxy class HeadManifestService < DependencyProxy::BaseService - ACCEPT_HEADERS = ::ContainerRegistry::Client::ACCEPTED_TYPES.join(',') - def initialize(image, tag, token) @image = image @tag = tag @@ -11,10 +9,10 @@ module DependencyProxy end def execute - response = Gitlab::HTTP.head(manifest_url, headers: auth_headers.merge(Accept: ACCEPT_HEADERS)) + response = Gitlab::HTTP.head(manifest_url, headers: auth_headers) if response.success? - success(digest: response.headers['docker-content-digest'], content_type: response.headers['content-type']) + success(digest: response.headers['docker-content-digest']) else error(response.body, response.code) end diff --git a/app/services/dependency_proxy/pull_manifest_service.rb b/app/services/dependency_proxy/pull_manifest_service.rb index 737414c396e..5c804489fd1 100644 --- a/app/services/dependency_proxy/pull_manifest_service.rb +++ b/app/services/dependency_proxy/pull_manifest_service.rb @@ -11,7 +11,7 @@ module DependencyProxy def execute_with_manifest raise ArgumentError, 'Block must be provided' unless block_given? - response = Gitlab::HTTP.get(manifest_url, headers: auth_headers.merge(Accept: ::ContainerRegistry::Client::ACCEPTED_TYPES.join(','))) + response = Gitlab::HTTP.get(manifest_url, headers: auth_headers) if response.success? file = Tempfile.new @@ -20,7 +20,7 @@ module DependencyProxy file.write(response) file.flush - yield(success(file: file, digest: response.headers['docker-content-digest'], content_type: response.headers['content-type'])) + yield(success(file: file, digest: response.headers['docker-content-digest'])) ensure file.close file.unlink diff --git a/app/views/projects/jobs/_table.html.haml b/app/views/projects/jobs/_table.html.haml index b126b452dea..402f7ddb38d 100644 --- a/app/views/projects/jobs/_table.html.haml +++ b/app/views/projects/jobs/_table.html.haml @@ -1,7 +1,7 @@ - admin = local_assigns.fetch(:admin, false) - if builds.blank? - - if experiment_enabled?(:jobs_empty_state) + - if @project .row.empty-state .col-12 .svg-content.svg-250 @@ -12,7 +12,7 @@ = s_('Jobs|Use jobs to automate your tasks') %p = s_('Jobs|Jobs are the building blocks of a GitLab CI/CD pipeline. Each job has a specific task, like testing code. To set up jobs in a CI/CD pipeline, add a CI/CD configuration file to your project.') - = link_to s_('Jobs|Create CI/CD configuration file'), help_page_path('ci/quick_start/README'), class: 'btn gl-button btn-info js-empty-state-button' + = link_to s_('Jobs|Create CI/CD configuration file'), @project.present(current_user: current_user).add_ci_yml_path, class: 'btn gl-button btn-info js-empty-state-button' - else .nothing-here-block= s_('Jobs|No jobs to show') - else diff --git a/app/views/projects/jobs/index.html.haml b/app/views/projects/jobs/index.html.haml index e14473708af..a0ec6002db7 100644 --- a/app/views/projects/jobs/index.html.haml +++ b/app/views/projects/jobs/index.html.haml @@ -7,9 +7,6 @@ .nav-controls - if can?(current_user, :update_build, @project) - - if !@repository.gitlab_ci_yml && !experiment_enabled?(:jobs_empty_state) - = link_to s_('Pipelines|Get started with Pipelines'), help_page_path('ci/quick_start/README'), class: 'btn gl-button btn-info js-empty-state-button' - = link_to project_ci_lint_path(@project), class: 'btn gl-button btn-default' do %span = _('CI Lint') diff --git a/changelogs/unreleased/290944-pull-by-digest.yml b/changelogs/unreleased/290944-pull-by-digest.yml index eea16bb359b..911f6be7e9c 100644 --- a/changelogs/unreleased/290944-pull-by-digest.yml +++ b/changelogs/unreleased/290944-pull-by-digest.yml @@ -1,5 +1,5 @@ --- -title: Pull-by-digest and Docker 20.x support for the Dependency Proxy +title: Add content_type column to dependency_proxy_manifests merge_request: 52805 author: type: changed diff --git a/changelogs/unreleased/300860-jobs-empty-state-successful-cleanup.yml b/changelogs/unreleased/300860-jobs-empty-state-successful-cleanup.yml new file mode 100644 index 00000000000..1442d008f88 --- /dev/null +++ b/changelogs/unreleased/300860-jobs-empty-state-successful-cleanup.yml @@ -0,0 +1,5 @@ +--- +title: Add empty jobs page with link to editor +merge_request: 53240 +author: +type: added diff --git a/changelogs/unreleased/debian_publications.yml b/changelogs/unreleased/debian_publications.yml new file mode 100644 index 00000000000..d9310539e62 --- /dev/null +++ b/changelogs/unreleased/debian_publications.yml @@ -0,0 +1,5 @@ +--- +title: Debian Publications +merge_request: 52916 +author: Mathieu Parent +type: added diff --git a/changelogs/unreleased/mc-api-pipeline-webhook-remove-retried-jobs.yml b/changelogs/unreleased/mc-api-pipeline-webhook-remove-retried-jobs.yml new file mode 100644 index 00000000000..f75824b04ce --- /dev/null +++ b/changelogs/unreleased/mc-api-pipeline-webhook-remove-retried-jobs.yml @@ -0,0 +1,5 @@ +--- +title: Send only latest jobs in pipeline webhook payload. +merge_request: 53159 +author: +type: fixed diff --git a/changelogs/unreleased/revert-pull-by-digest.yml b/changelogs/unreleased/revert-pull-by-digest.yml new file mode 100644 index 00000000000..b2de5e27638 --- /dev/null +++ b/changelogs/unreleased/revert-pull-by-digest.yml @@ -0,0 +1,6 @@ +--- +title: Remove dependency_proxy_manifests records with content_type to prevent Dependency + Proxy failures +merge_request: 53506 +author: +type: fixed diff --git a/config/feature_flags/experiment/jobs_empty_state_experiment_percentage.yml b/config/feature_flags/experiment/jobs_empty_state_experiment_percentage.yml deleted file mode 100644 index 33a15e28d44..00000000000 --- a/config/feature_flags/experiment/jobs_empty_state_experiment_percentage.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: jobs_empty_state_experiment_percentage -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/48686 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/281054 -milestone: '13.7' -type: experiment -group: group::activation -default_enabled: false diff --git a/db/migrate/20201204111600_create_packages_debian_publications.rb b/db/migrate/20201204111600_create_packages_debian_publications.rb new file mode 100644 index 00000000000..2fd26c2c367 --- /dev/null +++ b/db/migrate/20201204111600_create_packages_debian_publications.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class CreatePackagesDebianPublications < ActiveRecord::Migration[5.2] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + create_table :packages_debian_publications do |t| + t.references :package, + index: { unique: true }, + null: false, + foreign_key: { to_table: :packages_packages, on_delete: :cascade } + t.references :distribution, + null: false, + foreign_key: { to_table: :packages_debian_project_distributions, on_delete: :cascade } + end + end +end diff --git a/db/post_migrate/20210205174154_remove_bad_dependency_proxy_manifests.rb b/db/post_migrate/20210205174154_remove_bad_dependency_proxy_manifests.rb new file mode 100644 index 00000000000..eb302fb7009 --- /dev/null +++ b/db/post_migrate/20210205174154_remove_bad_dependency_proxy_manifests.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class RemoveBadDependencyProxyManifests < ActiveRecord::Migration[6.0] + DOWNTIME = false + + def up + # We run destroy on each record because we need the callback to remove + # the underlying files + DependencyProxy::Manifest.where.not(content_type: nil).destroy_all # rubocop:disable Cop/DestroyAll + end + + def down + # no op + end +end diff --git a/db/schema_migrations/20201204111600 b/db/schema_migrations/20201204111600 new file mode 100644 index 00000000000..d6c5e232962 --- /dev/null +++ b/db/schema_migrations/20201204111600 @@ -0,0 +1 @@ +51967d740ce184b27d0d9417fc86cb896fd3e3aa8a5e40759b290f47b9f3e99b \ No newline at end of file diff --git a/db/schema_migrations/20210205174154 b/db/schema_migrations/20210205174154 new file mode 100644 index 00000000000..fc8c4b6cff1 --- /dev/null +++ b/db/schema_migrations/20210205174154 @@ -0,0 +1 @@ +483d1b4a24086fa57efe7f3b3fa872cf793352f80aba5c25614f07eafa2d30c5 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index e9628af9a0d..8c1ed479c69 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -15030,6 +15030,21 @@ CREATE SEQUENCE packages_debian_project_distributions_id_seq ALTER SEQUENCE packages_debian_project_distributions_id_seq OWNED BY packages_debian_project_distributions.id; +CREATE TABLE packages_debian_publications ( + id bigint NOT NULL, + package_id bigint NOT NULL, + distribution_id bigint NOT NULL +); + +CREATE SEQUENCE packages_debian_publications_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + +ALTER SEQUENCE packages_debian_publications_id_seq OWNED BY packages_debian_publications.id; + CREATE TABLE packages_dependencies ( id bigint NOT NULL, name character varying(255) NOT NULL, @@ -19015,6 +19030,8 @@ ALTER TABLE ONLY packages_debian_project_components ALTER COLUMN id SET DEFAULT ALTER TABLE ONLY packages_debian_project_distributions ALTER COLUMN id SET DEFAULT nextval('packages_debian_project_distributions_id_seq'::regclass); +ALTER TABLE ONLY packages_debian_publications ALTER COLUMN id SET DEFAULT nextval('packages_debian_publications_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); @@ -20398,6 +20415,9 @@ ALTER TABLE ONLY packages_debian_project_components ALTER TABLE ONLY packages_debian_project_distributions ADD CONSTRAINT packages_debian_project_distributions_pkey PRIMARY KEY (id); +ALTER TABLE ONLY packages_debian_publications + ADD CONSTRAINT packages_debian_publications_pkey PRIMARY KEY (id); + ALTER TABLE ONLY packages_dependencies ADD CONSTRAINT packages_dependencies_pkey PRIMARY KEY (id); @@ -22623,6 +22643,10 @@ CREATE INDEX index_packages_debian_project_distributions_on_creator_id ON packag CREATE INDEX index_packages_debian_project_distributions_on_project_id ON packages_debian_project_distributions USING btree (project_id); +CREATE INDEX index_packages_debian_publications_on_distribution_id ON packages_debian_publications USING btree (distribution_id); + +CREATE UNIQUE INDEX index_packages_debian_publications_on_package_id ON packages_debian_publications USING btree (package_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); @@ -25032,6 +25056,9 @@ ALTER TABLE ONLY aws_roles ALTER TABLE ONLY security_scans ADD CONSTRAINT fk_rails_4ef1e6b4c6 FOREIGN KEY (build_id) REFERENCES ci_builds(id) ON DELETE CASCADE; +ALTER TABLE ONLY packages_debian_publications + ADD CONSTRAINT fk_rails_4fc8ebd03e FOREIGN KEY (distribution_id) REFERENCES packages_debian_project_distributions(id) ON DELETE CASCADE; + ALTER TABLE ONLY merge_request_diff_files ADD CONSTRAINT fk_rails_501aa0a391 FOREIGN KEY (merge_request_diff_id) REFERENCES merge_request_diffs(id) ON DELETE CASCADE; @@ -25275,6 +25302,9 @@ ALTER TABLE ONLY x509_certificates ALTER TABLE ONLY pages_domain_acme_orders ADD CONSTRAINT fk_rails_76581b1c16 FOREIGN KEY (pages_domain_id) REFERENCES pages_domains(id) ON DELETE CASCADE; +ALTER TABLE ONLY packages_debian_publications + ADD CONSTRAINT fk_rails_7668c1d606 FOREIGN KEY (package_id) REFERENCES packages_packages(id) ON DELETE CASCADE; + ALTER TABLE ONLY boards_epic_user_preferences ADD CONSTRAINT fk_rails_76c4e9732d FOREIGN KEY (epic_id) REFERENCES epics(id) ON DELETE CASCADE; diff --git a/doc/user/packages/dependency_proxy/index.md b/doc/user/packages/dependency_proxy/index.md index 878354ab264..b45d0ac9500 100644 --- a/doc/user/packages/dependency_proxy/index.md +++ b/doc/user/packages/dependency_proxy/index.md @@ -7,10 +7,9 @@ info: To determine the technical writer assigned to the Stage/Group associated w # Dependency Proxy > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7934) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.11. -> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/273655) to [GitLab Free](https://about.gitlab.com/pricing/) in GitLab 13.6. -> - [Support for private groups](https://gitlab.com/gitlab-org/gitlab/-/issues/11582) in [GitLab Free](https://about.gitlab.com/pricing/) 13.7. -> - Anonymous access to images in public groups is no longer available starting in [GitLab Free](https://about.gitlab.com/pricing/) 13.7. -> - [Support for pull-by-digest and Docker version 20.x](https://gitlab.com/gitlab-org/gitlab/-/issues/290944) in [GitLab Free](https://about.gitlab.com/pricing/) 13.9. +> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/273655) to [GitLab Core](https://about.gitlab.com/pricing/) in GitLab 13.6. +> - [Support for private groups](https://gitlab.com/gitlab-org/gitlab/-/issues/11582) in [GitLab Core](https://about.gitlab.com/pricing/) 13.7. +> - Anonymous access to images in public groups is no longer available starting in [GitLab Core](https://about.gitlab.com/pricing/) 13.7. The GitLab Dependency Proxy is a local proxy you can use for your frequently-accessed upstream images. @@ -18,6 +17,11 @@ upstream images. In the case of CI/CD, the Dependency Proxy receives a request and returns the upstream image from a registry, acting as a pull-through cache. +NOTE: +The Dependency Proxy is not compatible with Docker version 20.x and later. +If you are using the Dependency Proxy, Docker version 19.x.x is recommended until +[issue #290944](https://gitlab.com/gitlab-org/gitlab/-/issues/290944) is resolved. + ## Prerequisites The Dependency Proxy must be [enabled by an administrator](../../../administration/packages/dependency_proxy.md). @@ -56,7 +60,7 @@ Prerequisites: ### Authenticate with the Dependency Proxy -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/11582) in [GitLab Free](https://about.gitlab.com/pricing/) 13.7. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/11582) in [GitLab Core](https://about.gitlab.com/pricing/) 13.7. > - It's [deployed behind a feature flag](../../feature_flags.md), enabled by default. > - It's enabled on GitLab.com. > - It's recommended for production use. @@ -158,7 +162,7 @@ the [Dependency Proxy API](../../../api/dependency_proxy.md). ## Docker Hub rate limits and the Dependency Proxy -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/241639) in [GitLab Free](https://about.gitlab.com/pricing/) 13.7. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/241639) in [GitLab Core](https://about.gitlab.com/pricing/) 13.7. Watch how to [use the Dependency Proxy to help avoid Docker Hub rate limits](https://youtu.be/Nc4nUo7Pq08). diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md index 27c2cb08d10..0cf01adef13 100644 --- a/doc/user/project/integrations/webhooks.md +++ b/doc/user/project/integrations/webhooks.md @@ -1029,6 +1029,9 @@ X-Gitlab-Event: Wiki Page Hook ### Pipeline events +In [GitLab 13.9](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/53159) +and later, the pipeline webhook returns only the latest jobs. + Triggered on status change of Pipeline. **Request Header**: diff --git a/lib/gitlab/data_builder/pipeline.rb b/lib/gitlab/data_builder/pipeline.rb index 2413f68f4d0..3036bc57ca5 100644 --- a/lib/gitlab/data_builder/pipeline.rb +++ b/lib/gitlab/data_builder/pipeline.rb @@ -13,7 +13,7 @@ module Gitlab user: pipeline.user.try(:hook_attrs), project: pipeline.project.hook_attrs(backward: false), commit: pipeline.commit.try(:hook_attrs), - builds: pipeline.builds.map(&method(:build_hook_attrs)) + builds: pipeline.builds.latest.map(&method(:build_hook_attrs)) } end diff --git a/lib/gitlab/experimentation.rb b/lib/gitlab/experimentation.rb index a5143839182..0deda3596f5 100644 --- a/lib/gitlab/experimentation.rb +++ b/lib/gitlab/experimentation.rb @@ -66,9 +66,6 @@ module Gitlab tracking_category: 'Growth::Conversion::Experiment::GroupOnlyTrials', use_backwards_compatible_subject_index: true }, - jobs_empty_state: { - tracking_category: 'Growth::Activation::Experiment::JobsEmptyState' - }, remove_known_trial_form_fields: { tracking_category: 'Growth::Conversion::Experiment::RemoveKnownTrialFormFields' }, diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 1d246e9ee25..2ac62c8f808 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -9290,6 +9290,9 @@ msgstr "" msgid "Dear Administrator," msgstr "" +msgid "Debian package already exists in Distribution" +msgstr "" + msgid "Debug" msgstr "" @@ -21511,9 +21514,6 @@ msgstr "" msgid "Pipelines|Get started with CI/CD" msgstr "" -msgid "Pipelines|Get started with Pipelines" -msgstr "" - msgid "Pipelines|GitLab CI/CD can automatically build, test, and deploy your code. Let GitLab take care of time consuming tasks, so you can spend more time creating." msgstr "" @@ -34940,6 +34940,9 @@ msgstr "" msgid "mrWidget|to start a merge train when the pipeline succeeds" msgstr "" +msgid "must be a Debian package" +msgstr "" + msgid "must be a boolean value" msgstr "" @@ -35316,6 +35319,9 @@ msgstr "" msgid "two-factor authentication settings" msgstr "" +msgid "type must be Debian" +msgstr "" + msgid "unicode domains should use IDNA encoding" msgstr "" diff --git a/qa/spec/support/matchers/have_assignee.rb b/qa/spec/support/matchers/have_assignee.rb deleted file mode 100644 index 5e7aa2162b2..00000000000 --- a/qa/spec/support/matchers/have_assignee.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -module Matchers - module HaveAssignee - RSpec::Matchers.define :have_assignee do |assignee| - match do |page_object| - page_object.has_assignee?(assignee) - end - - match_when_negated do |page_object| - page_object.has_no_assignee?(assignee) - end - end - end -end diff --git a/qa/spec/support/matchers/have_child_pipeline.rb b/qa/spec/support/matchers/have_child_pipeline.rb deleted file mode 100644 index d05d9d4209a..00000000000 --- a/qa/spec/support/matchers/have_child_pipeline.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -module Matchers - module HaveChildPipeline - RSpec::Matchers.define :have_child_pipeline do - match do |page_object| - page_object.has_child_pipeline? - end - - match_when_negated do |page_object| - page_object.has_no_child_pipeline? - end - end - end -end diff --git a/qa/spec/support/matchers/have_content.rb b/qa/spec/support/matchers/have_content.rb deleted file mode 100644 index 66b30b3b6e4..00000000000 --- a/qa/spec/support/matchers/have_content.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -module Matchers - module HaveContent - RSpec::Matchers.define :have_content do |content| - match do |page_object| - page_object.has_content?(content) - end - - match_when_negated do |page_object| - page_object.has_no_content?(content) - end - end - end -end diff --git a/qa/spec/support/matchers/have_design.rb b/qa/spec/support/matchers/have_design.rb deleted file mode 100644 index 85f1367297a..00000000000 --- a/qa/spec/support/matchers/have_design.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -module Matchers - module HaveDesign - RSpec::Matchers.define :have_design do |design| - match do |page_object| - page_object.has_design?(design) - end - - match_when_negated do |page_object| - page_object.has_no_design?(design) - end - end - end -end diff --git a/qa/spec/support/matchers/have_element.rb b/qa/spec/support/matchers/have_element.rb deleted file mode 100644 index bf74a78a3b5..00000000000 --- a/qa/spec/support/matchers/have_element.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -module Matchers - module HaveElement - RSpec::Matchers.define :have_element do |element, **kwargs| - match do |page_object| - page_object.has_element?(element, **kwargs) - end - - match_when_negated do |page_object| - page_object.has_no_element?(element, **kwargs) - end - end - end -end diff --git a/qa/spec/support/matchers/have_file.rb b/qa/spec/support/matchers/have_file.rb deleted file mode 100644 index 2ae295d5ca2..00000000000 --- a/qa/spec/support/matchers/have_file.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -module Matchers - module HaveFile - RSpec::Matchers.define :have_file do |file| - match do |page_object| - page_object.has_file?(file) - end - - match_when_negated do |page_object| - page_object.has_no_file?(file) - end - end - end -end diff --git a/qa/spec/support/matchers/have_file_content.rb b/qa/spec/support/matchers/have_file_content.rb deleted file mode 100644 index e42ece6d59e..00000000000 --- a/qa/spec/support/matchers/have_file_content.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -module Matchers - module HaveFileContent - RSpec::Matchers.define :have_file_content do |file_content, file_number| - match do |page_object| - page_object.has_file_content?(file_content, file_number) - end - - match_when_negated do |page_object| - page_object.has_no_file_content?(file_content, file_number) - end - end - end -end diff --git a/qa/spec/support/matchers/have_issue.rb b/qa/spec/support/matchers/have_issue.rb deleted file mode 100644 index 7ef30f22726..00000000000 --- a/qa/spec/support/matchers/have_issue.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -module Matchers - module HaveIssue - RSpec::Matchers.define :have_issue do |issue| - match do |page_object| - page_object.has_issue?(issue) - end - - match_when_negated do |page_object| - page_object.has_no_issue?(issue) - end - end - end -end diff --git a/qa/spec/support/matchers/have_job.rb b/qa/spec/support/matchers/have_job.rb deleted file mode 100644 index 89829915fce..00000000000 --- a/qa/spec/support/matchers/have_job.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -module Matchers - module HaveJob - RSpec::Matchers.define :have_job do |job| - match do |page_object| - page_object.has_job?(job) - end - - match_when_negated do |page_object| - page_object.has_no_job?(job) - end - end - end -end diff --git a/qa/spec/support/matchers/have_matcher.rb b/qa/spec/support/matchers/have_matcher.rb new file mode 100644 index 00000000000..43ae27f8796 --- /dev/null +++ b/qa/spec/support/matchers/have_matcher.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module Matchers + PREDICATE_TARGETS = %w[ + element + file_content + assignee + child_pipeline + content + design + file + issue + job + package + pipeline + related_issue_item + snippet_description + ].each do |predicate| + RSpec::Matchers.define "have_#{predicate}" do |*args, **kwargs| + match do |page_object| + page_object.public_send("has_#{predicate}?", *args, **kwargs) # rubocop:disable GitlabSecurity/PublicSend + end + + match_when_negated do |page_object| + page_object.public_send("has_no_#{predicate}?", *args, **kwargs) # rubocop:disable GitlabSecurity/PublicSend + end + end + end +end diff --git a/qa/spec/support/matchers/have_package.rb b/qa/spec/support/matchers/have_package.rb deleted file mode 100644 index 86e9bfee4d1..00000000000 --- a/qa/spec/support/matchers/have_package.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -module Matchers - module HavePackage - RSpec::Matchers.define :have_package do |package| - match do |page_object| - page_object.has_package?(package) - end - - match_when_negated do |page_object| - page_object.has_no_package?(package) - end - end - end -end diff --git a/qa/spec/support/matchers/have_pipeline.rb b/qa/spec/support/matchers/have_pipeline.rb deleted file mode 100644 index 2bfd49d671a..00000000000 --- a/qa/spec/support/matchers/have_pipeline.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -module Matchers - module HavePipeline - RSpec::Matchers.define :have_pipeline do - match do |page_object| - page_object.has_pipeline? - end - - match_when_negated do |page_object| - page_object.has_no_pipeline? - end - end - end -end diff --git a/qa/spec/support/matchers/have_related_issue_item.rb b/qa/spec/support/matchers/have_related_issue_item.rb deleted file mode 100644 index 89403f2422a..00000000000 --- a/qa/spec/support/matchers/have_related_issue_item.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -module Matchers - module HaveRelatedIssueItem - RSpec::Matchers.define :have_related_issue_item do - match do |page_object| - page_object.has_related_issue_item? - end - - match_when_negated do |page_object| - page_object.has_no_related_issue_item? - end - end - end -end diff --git a/qa/spec/support/matchers/have_snippet_description.rb b/qa/spec/support/matchers/have_snippet_description.rb deleted file mode 100644 index 7c407aefc83..00000000000 --- a/qa/spec/support/matchers/have_snippet_description.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -module Matchers - module HaveSnippetDescription - RSpec::Matchers.define :have_snippet_description do |description| - match do |page_object| - page_object.has_snippet_description?(description) - end - - match_when_negated do |page_object| - page_object.has_no_snippet_description? - end - end - end -end diff --git a/spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb b/spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb index 2056feb6434..39cbdfb9123 100644 --- a/spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb +++ b/spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb @@ -130,7 +130,7 @@ RSpec.describe Groups::DependencyProxyForContainersController do } end - it 'proxies status from the remote token request', :aggregate_failures do + it 'proxies status from the remote token request' do subject expect(response).to have_gitlab_http_status(:service_unavailable) @@ -147,7 +147,7 @@ RSpec.describe Groups::DependencyProxyForContainersController do } end - it 'proxies status from the remote manifest request', :aggregate_failures do + it 'proxies status from the remote manifest request' do subject expect(response).to have_gitlab_http_status(:bad_request) @@ -156,19 +156,15 @@ RSpec.describe Groups::DependencyProxyForContainersController do end it 'sends a file' do - expect(controller).to receive(:send_file).with(manifest.file.path, type: manifest.content_type) + expect(controller).to receive(:send_file).with(manifest.file.path, {}) subject end - it 'returns Content-Disposition: attachment', :aggregate_failures do + it 'returns Content-Disposition: attachment' do subject expect(response).to have_gitlab_http_status(:ok) - expect(response.headers['Docker-Content-Digest']).to eq(manifest.digest) - expect(response.headers['Content-Length']).to eq(manifest.size) - expect(response.headers['Docker-Distribution-Api-Version']).to eq(DependencyProxy::DISTRIBUTION_API_VERSION) - expect(response.headers['Etag']).to eq("\"#{manifest.digest}\"") expect(response.headers['Content-Disposition']).to match(/^attachment/) end end @@ -211,7 +207,7 @@ RSpec.describe Groups::DependencyProxyForContainersController do } end - it 'proxies status from the remote blob request', :aggregate_failures do + it 'proxies status from the remote blob request' do subject expect(response).to have_gitlab_http_status(:bad_request) @@ -225,7 +221,7 @@ RSpec.describe Groups::DependencyProxyForContainersController do subject end - it 'returns Content-Disposition: attachment', :aggregate_failures do + it 'returns Content-Disposition: attachment' do subject expect(response).to have_gitlab_http_status(:ok) diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb index 430808e1c63..80e1268cb01 100644 --- a/spec/controllers/projects/jobs_controller_spec.rb +++ b/spec/controllers/projects/jobs_controller_spec.rb @@ -15,54 +15,6 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do end describe 'GET index' do - describe 'pushing tracking_data to Gon' do - before do - stub_experiment(jobs_empty_state: experiment_active) - stub_experiment_for_subject(jobs_empty_state: in_experiment_group) - - get_index - end - - context 'when experiment not active' do - let(:experiment_active) { false } - let(:in_experiment_group) { false } - - it 'does not push tracking_data to Gon' do - expect(Gon.tracking_data).to be_nil - end - end - - context 'when experiment active and user in control group' do - let(:experiment_active) { true } - let(:in_experiment_group) { false } - - it 'pushes tracking_data to Gon' do - expect(Gon.tracking_data).to match( - { - category: 'Growth::Activation::Experiment::JobsEmptyState', - action: 'click_button', - label: anything, - property: 'control_group' - } - ) - end - end - - context 'when experiment active and user in experimental group' do - let(:experiment_active) { true } - let(:in_experiment_group) { true } - - it 'pushes tracking_data to gon' do - expect(Gon.tracking_data).to match( - category: 'Growth::Activation::Experiment::JobsEmptyState', - action: 'click_button', - label: anything, - property: 'experimental_group' - ) - end - end - end - context 'when scope is pending' do before do create(:ci_build, :pending, pipeline: pipeline) diff --git a/spec/factories/dependency_proxy.rb b/spec/factories/dependency_proxy.rb index 94a7986a8fa..de95df19876 100644 --- a/spec/factories/dependency_proxy.rb +++ b/spec/factories/dependency_proxy.rb @@ -10,8 +10,7 @@ FactoryBot.define do factory :dependency_proxy_manifest, class: 'DependencyProxy::Manifest' do group file { fixture_file_upload('spec/fixtures/dependency_proxy/manifest') } - digest { 'sha256:d0710affa17fad5f466a70159cc458227bd25d4afb39514ef662ead3e6c99515' } + digest { 'sha256:5ab5a6872b264fe4fd35d63991b9b7d8425f4bc79e7cf4d563c10956581170c9' } file_name { 'alpine:latest.json' } - content_type { 'application/vnd.docker.distribution.manifest.v2+json' } end end diff --git a/spec/factories/packages.rb b/spec/factories/packages.rb index ca38793ac08..158c5974c6a 100644 --- a/spec/factories/packages.rb +++ b/spec/factories/packages.rb @@ -29,6 +29,15 @@ FactoryBot.define do transient do without_package_files { false } file_metadatum_trait { :keep } + published_in { :create } + end + + after :build do |package, evaluator| + if evaluator.published_in == :create + create(:debian_publication, package: package) + elsif !evaluator.published_in.nil? + create(:debian_publication, package: package, distribution: evaluator.published_in) + end end after :create do |package, evaluator| @@ -50,6 +59,7 @@ FactoryBot.define do transient do without_package_files { false } file_metadatum_trait { :unknown } + published_in { nil } end end end diff --git a/spec/factories/packages/debian/publication.rb b/spec/factories/packages/debian/publication.rb new file mode 100644 index 00000000000..314c7064e9b --- /dev/null +++ b/spec/factories/packages/debian/publication.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :debian_publication, class: 'Packages::Debian::Publication' do + package { association(:debian_package, published_in: nil) } + + distribution { association(:debian_project_distribution, project: package.project) } + end +end diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index adf664f26af..7811394b541 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -27,40 +27,12 @@ RSpec.describe 'Jobs', :clean_gitlab_redis_shared_state do describe "GET /:project/jobs" do context 'with no jobs' do before do - stub_experiment(jobs_empty_state: experiment_active) - stub_experiment_for_subject(jobs_empty_state: in_experiment_group) - visit project_jobs_path(project) end - context 'when experiment not active' do - let(:experiment_active) { false } - let(:in_experiment_group) { false } - - it 'shows the empty state control page' do - expect(page).to have_content('No jobs to show') - expect(page).to have_link('Get started with Pipelines') - end - end - - context 'when experiment active and user in control group' do - let(:experiment_active) { true } - let(:in_experiment_group) { false } - - it 'shows the empty state control page' do - expect(page).to have_content('No jobs to show') - expect(page).to have_link('Get started with Pipelines') - end - end - - context 'when experiment active and user in experimental group' do - let(:experiment_active) { true } - let(:in_experiment_group) { true } - - it 'shows the empty state experiment page' do - expect(page).to have_content('Use jobs to automate your tasks') - expect(page).to have_link('Create CI/CD configuration file') - end + it 'shows the empty state page' do + expect(page).to have_content('Use jobs to automate your tasks') + expect(page).to have_link('Create CI/CD configuration file', href: project.present(current_user: user).add_ci_yml_path) end end @@ -102,7 +74,7 @@ RSpec.describe 'Jobs', :clean_gitlab_redis_shared_state do it "shows Finished tab jobs" do expect(page).to have_selector('.nav-links li.active', text: 'Finished') - expect(page).to have_content 'No jobs to show' + expect(page).to have_content('Use jobs to automate your tasks') end end diff --git a/spec/fixtures/dependency_proxy/manifest b/spec/fixtures/dependency_proxy/manifest index ed543883d60..a899d05d697 100644 --- a/spec/fixtures/dependency_proxy/manifest +++ b/spec/fixtures/dependency_proxy/manifest @@ -1,16 +1,38 @@ { - "schemaVersion": 2, - "mediaType": "application/vnd.docker.distribution.manifest.v2+json", - "config": { - "mediaType": "application/vnd.docker.container.image.v1+json", - "size": 1472, - "digest": "sha256:7731472c3f2a25edbb9c085c78f42ec71259f2b83485aa60648276d408865839" - }, - "layers": [ + "schemaVersion": 1, + "name": "library/alpine", + "tag": "latest", + "architecture": "amd64", + "fsLayers": [ { - "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", - "size": 2810825, - "digest": "sha256:596ba82af5aaa3e2fd9d6f955b8b94f0744a2b60710e3c243ba3e4a467f051d1" + "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4" + }, + { + "blobSum": "sha256:188c0c94c7c576fff0792aca7ec73d67a2f7f4cb3a6e53a84559337260b36964" + } + ], + "history": [ + { + "v1Compatibility": "{\"architecture\":\"amd64\",\"config\":{\"Hostname\":\"\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/sh\"],\"ArgsEscaped\":true,\"Image\":\"sha256:3543079adc6fb5170279692361be8b24e89ef1809a374c1b4429e1d560d1459c\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":null,\"Labels\":null},\"container\":\"8c59eb170e19b8c3768b8d06c91053b0debf4a6fa6a452df394145fe9b885ea5\",\"container_config\":{\"Hostname\":\"8c59eb170e19\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) \",\"CMD [\\\"/bin/sh\\\"]\"],\"ArgsEscaped\":true,\"Image\":\"sha256:3543079adc6fb5170279692361be8b24e89ef1809a374c1b4429e1d560d1459c\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":null,\"Labels\":{}},\"created\":\"2020-10-22T02:19:24.499382102Z\",\"docker_version\":\"18.09.7\",\"id\":\"c5f1aab5bb88eaf1aa62bea08ea6654547d43fd4d15b1a476c77e705dd5385ba\",\"os\":\"linux\",\"parent\":\"dc0b50cc52bc340d7848a62cfe8a756f4420592f4984f7a680ef8f9d258176ed\",\"throwaway\":true}" + }, + { + "v1Compatibility": "{\"id\":\"dc0b50cc52bc340d7848a62cfe8a756f4420592f4984f7a680ef8f9d258176ed\",\"created\":\"2020-10-22T02:19:24.33416307Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) ADD file:f17f65714f703db9012f00e5ec98d0b2541ff6147c2633f7ab9ba659d0c507f4 in / \"]}}" + } + ], + "signatures": [ + { + "header": { + "jwk": { + "crv": "P-256", + "kid": "XOTE:DZ4C:YBPJ:3O3L:YI4B:NYXU:T4VR:USH6:CXXN:SELU:CSCC:FVPE", + "kty": "EC", + "x": "cR1zye_3354mdbD7Dn-mtXNXvtPtmLlUVDa5vH6Lp74", + "y": "rldUXSllLit6_2BW6AV8aqkwWJXHoYPG9OwkIBouwxQ" + }, + "alg": "ES256" + }, + "signature": "DYB2iB-XKIisqp5Q0OXFOBIOlBOuRV7pnZuKy0cxVB2Qj1VFRhWX4Tq336y0VMWbF6ma1he5A1E_Vk4jazrJ9g", + "protected": "eyJmb3JtYXRMZW5ndGgiOjIxMzcsImZvcm1hdFRhaWwiOiJDbjAiLCJ0aW1lIjoiMjAyMC0xMS0yNFQyMjowMTo1MVoifQ" } ] } \ No newline at end of file diff --git a/spec/frontend/integrations/edit/components/jira_issues_fields_spec.js b/spec/frontend/integrations/edit/components/jira_issues_fields_spec.js index b866cf8bc03..d6b53261172 100644 --- a/spec/frontend/integrations/edit/components/jira_issues_fields_spec.js +++ b/spec/frontend/integrations/edit/components/jira_issues_fields_spec.js @@ -126,6 +126,20 @@ describe('JiraIssuesFields', () => { }, ); + it('passes down the correct initial-issue-type-id value when value is empty', async () => { + await setEnableCheckbox(true); + expect(findJiraForVulnerabilities().attributes('initial-issue-type-id')).toBeUndefined(); + }); + + it('passes down the correct initial-issue-type-id value when value is not empty', async () => { + const jiraIssueType = 'some-jira-issue-type'; + wrapper.setProps({ initialVulnerabilitiesIssuetype: jiraIssueType }); + await setEnableCheckbox(true); + expect(findJiraForVulnerabilities().attributes('initial-issue-type-id')).toBe( + jiraIssueType, + ); + }); + it('emits "getJiraIssueTypes" to the eventHub when the jira-vulnerabilities component requests to fetch issue types', async () => { const eventHubEmitSpy = jest.spyOn(eventHub, '$emit'); diff --git a/spec/lib/gitlab/data_builder/pipeline_spec.rb b/spec/lib/gitlab/data_builder/pipeline_spec.rb index 32619fc4c37..fd7cadeb89e 100644 --- a/spec/lib/gitlab/data_builder/pipeline_spec.rb +++ b/spec/lib/gitlab/data_builder/pipeline_spec.rb @@ -104,5 +104,16 @@ RSpec.describe Gitlab::DataBuilder::Pipeline do expect(merge_request_attrs[:url]).to eq("http://localhost/#{merge_request.target_project.full_path}/-/merge_requests/#{merge_request.iid}") end end + + context 'when pipeline has retried builds' do + before do + create(:ci_build, :retried, pipeline: pipeline) + end + + it 'does not contain retried builds in payload' do + expect(data[:builds].count).to eq(1) + expect(build_data[:id]).to eq(build.id) + end + end end end diff --git a/spec/migrations/20210205174154_remove_bad_dependency_proxy_manifests_spec.rb b/spec/migrations/20210205174154_remove_bad_dependency_proxy_manifests_spec.rb new file mode 100644 index 00000000000..7e351617ea3 --- /dev/null +++ b/spec/migrations/20210205174154_remove_bad_dependency_proxy_manifests_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20210205174154_remove_bad_dependency_proxy_manifests.rb') + +RSpec.describe RemoveBadDependencyProxyManifests, schema: 20210128140157 do + let_it_be(:namespaces) { table(:namespaces) } + let_it_be(:dependency_proxy_manifests) { table(:dependency_proxy_manifests) } + let_it_be(:group) { namespaces.create!(type: 'Group', name: 'test', path: 'test') } + + let_it_be(:dependency_proxy_manifest_with_content_type) do + dependency_proxy_manifests.create!(group_id: group.id, file: 'foo', file_name: 'foo', digest: 'asdf1234', content_type: 'content-type' ) + end + + let_it_be(:dependency_proxy_manifest_without_content_type) do + dependency_proxy_manifests.create!(group_id: group.id, file: 'bar', file_name: 'bar', digest: 'fdsa6789') + end + + it 'removes the dependency_proxy_manifests with a content_type', :aggregate_failures do + expect(dependency_proxy_manifest_with_content_type).to be_present + expect(dependency_proxy_manifest_without_content_type).to be_present + + expect { migrate! }.to change { dependency_proxy_manifests.count }.from(2).to(1) + + expect(dependency_proxy_manifests.where.not(content_type: nil)).to be_empty + expect(dependency_proxy_manifest_without_content_type.reload).to be_present + end +end diff --git a/spec/models/dependency_proxy/manifest_spec.rb b/spec/models/dependency_proxy/manifest_spec.rb index 4203644c003..aa2e73356dd 100644 --- a/spec/models/dependency_proxy/manifest_spec.rb +++ b/spec/models/dependency_proxy/manifest_spec.rb @@ -29,32 +29,24 @@ RSpec.describe DependencyProxy::Manifest, type: :model do end end - describe '.find_or_initialize_by_file_name_or_digest' do - let_it_be(:file_name) { 'foo' } - let_it_be(:digest) { 'bar' } - - subject { DependencyProxy::Manifest.find_or_initialize_by_file_name_or_digest(file_name: file_name, digest: digest) } + describe '.find_or_initialize_by_file_name' do + subject { DependencyProxy::Manifest.find_or_initialize_by_file_name(file_name) } context 'no manifest exists' do + let_it_be(:file_name) { 'foo' } + it 'initializes a manifest' do - expect(DependencyProxy::Manifest).to receive(:new).with(file_name: file_name, digest: digest) + expect(DependencyProxy::Manifest).to receive(:new).with(file_name: file_name) subject end end - context 'manifest exists and matches file_name' do + context 'manifest exists' do let_it_be(:dependency_proxy_manifest) { create(:dependency_proxy_manifest) } let_it_be(:file_name) { dependency_proxy_manifest.file_name } it { is_expected.to eq(dependency_proxy_manifest) } end - - context 'manifest exists and matches digest' do - let_it_be(:dependency_proxy_manifest) { create(:dependency_proxy_manifest) } - let_it_be(:digest) { dependency_proxy_manifest.digest } - - it { is_expected.to eq(dependency_proxy_manifest) } - end end end diff --git a/spec/models/packages/debian/publication_spec.rb b/spec/models/packages/debian/publication_spec.rb new file mode 100644 index 00000000000..0ed056f499b --- /dev/null +++ b/spec/models/packages/debian/publication_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true +require 'spec_helper' + +RSpec.describe Packages::Debian::Publication, type: :model do + let_it_be_with_reload(:publication) { create(:debian_publication) } + + subject { publication } + + describe 'relationships' do + it { is_expected.to belong_to(:package).inverse_of(:debian_publication).class_name('Packages::Package') } + it { is_expected.to belong_to(:distribution).inverse_of(:publications).class_name('Packages::Debian::ProjectDistribution').with_foreign_key(:distribution_id) } + end + + describe 'validations' do + describe '#package' do + it { is_expected.to validate_presence_of(:package) } + end + + describe '#valid_debian_package_type' do + context 'with package type not being Debian' do + before do + publication.package.package_type = 'generic' + end + + it 'will not allow package type not being Debian' do + expect(publication).not_to be_valid + expect(publication.errors.to_a).to eq(['Package type must be Debian']) + end + end + + context 'with package not being a Debian package' do + before do + publication.package.version = nil + end + + it 'will not allow package not being a distribution' do + expect(publication).not_to be_valid + expect(publication.errors.to_a).to eq(['Package must be a Debian package']) + end + end + end + + describe '#distribution' do + it { is_expected.to validate_presence_of(:distribution) } + end + end +end diff --git a/spec/models/packages/package_spec.rb b/spec/models/packages/package_spec.rb index 6645db33503..b5ff417e610 100644 --- a/spec/models/packages/package_spec.rb +++ b/spec/models/packages/package_spec.rb @@ -14,6 +14,8 @@ RSpec.describe Packages::Package, type: :model do it { is_expected.to have_many(:pipelines).through(:build_infos) } it { is_expected.to have_one(:conan_metadatum).inverse_of(:package) } it { is_expected.to have_one(:maven_metadatum).inverse_of(:package) } + it { is_expected.to have_one(:debian_publication).inverse_of(:package).class_name('Packages::Debian::Publication') } + it { is_expected.to have_one(:debian_distribution).through(:debian_publication).source(:distribution).inverse_of(:packages).class_name('Packages::Debian::ProjectDistribution') } it { is_expected.to have_one(:nuget_metadatum).inverse_of(:package) } end @@ -374,7 +376,28 @@ RSpec.describe Packages::Package, type: :model do end end - Packages::Package.package_types.keys.without('conan').each do |pt| + describe "#unique_debian_package_name" do + let!(:package) { create(:debian_package) } + + it "will allow a Debian package with same project, name and version, but different distribution" do + new_package = build(:debian_package, project: package.project, name: package.name, version: package.version) + expect(new_package).to be_valid + end + + it "will not allow a Debian package with same project, name, version and distribution" do + new_package = build(:debian_package, project: package.project, name: package.name, version: package.version) + new_package.debian_publication.distribution = package.debian_publication.distribution + expect(new_package).not_to be_valid + expect(new_package.errors.to_a).to include('Debian package already exists in Distribution') + end + + it "will allow a Debian package with same project, name, version, but no distribution" do + new_package = build(:debian_package, project: package.project, name: package.name, version: package.version, published_in: nil) + expect(new_package).to be_valid + end + end + + Packages::Package.package_types.keys.without('conan', 'debian').each do |pt| context "project id, name, version and package type uniqueness for package type #{pt}" do let(:package) { create("#{pt}_package") } diff --git a/spec/services/dependency_proxy/find_or_create_manifest_service_spec.rb b/spec/services/dependency_proxy/find_or_create_manifest_service_spec.rb index 40a2f954786..c375e5a2fa3 100644 --- a/spec/services/dependency_proxy/find_or_create_manifest_service_spec.rb +++ b/spec/services/dependency_proxy/find_or_create_manifest_service_spec.rb @@ -10,12 +10,7 @@ RSpec.describe DependencyProxy::FindOrCreateManifestService do let(:manifest) { dependency_proxy_manifest.file.read } let(:group) { dependency_proxy_manifest.group } let(:token) { Digest::SHA256.hexdigest('123') } - let(:headers) do - { - 'docker-content-digest' => dependency_proxy_manifest.digest, - 'content-type' => dependency_proxy_manifest.content_type - } - end + let(:headers) { { 'docker-content-digest' => dependency_proxy_manifest.digest } } describe '#execute' do subject { described_class.new(group, image, tag, token).execute } @@ -23,37 +18,22 @@ RSpec.describe DependencyProxy::FindOrCreateManifestService do context 'when no manifest exists' do let_it_be(:image) { 'new-image' } - shared_examples 'downloading the manifest' do - it 'downloads manifest from remote registry if there is no cached one', :aggregate_failures do - expect { subject }.to change { group.dependency_proxy_manifests.count }.by(1) - expect(subject[:status]).to eq(:success) - expect(subject[:manifest]).to be_a(DependencyProxy::Manifest) - expect(subject[:manifest]).to be_persisted - end + before do + stub_manifest_head(image, tag, digest: dependency_proxy_manifest.digest) + stub_manifest_download(image, tag, headers: headers) end - context 'successful head request' do - before do - stub_manifest_head(image, tag, headers: headers) - stub_manifest_download(image, tag, headers: headers) - end - - it_behaves_like 'downloading the manifest' - end - - context 'failed head request' do - before do - stub_manifest_head(image, tag, status: :error) - stub_manifest_download(image, tag, headers: headers) - end - - it_behaves_like 'downloading the manifest' + it 'downloads manifest from remote registry if there is no cached one', :aggregate_failures do + expect { subject }.to change { group.dependency_proxy_manifests.count }.by(1) + expect(subject[:status]).to eq(:success) + expect(subject[:manifest]).to be_a(DependencyProxy::Manifest) + expect(subject[:manifest]).to be_persisted end end context 'when manifest exists' do before do - stub_manifest_head(image, tag, headers: headers) + stub_manifest_head(image, tag, digest: dependency_proxy_manifest.digest) end shared_examples 'using the cached manifest' do @@ -68,17 +48,15 @@ RSpec.describe DependencyProxy::FindOrCreateManifestService do context 'when digest is stale' do let(:digest) { 'new-digest' } - let(:content_type) { 'new-content-type' } before do - stub_manifest_head(image, tag, headers: { 'docker-content-digest' => digest, 'content-type' => content_type }) - stub_manifest_download(image, tag, headers: { 'docker-content-digest' => digest, 'content-type' => content_type }) + stub_manifest_head(image, tag, digest: digest) + stub_manifest_download(image, tag, headers: { 'docker-content-digest' => digest }) end it 'downloads the new manifest and updates the existing record', :aggregate_failures do expect(subject[:status]).to eq(:success) expect(subject[:manifest]).to eq(dependency_proxy_manifest) - expect(subject[:manifest].content_type).to eq(content_type) expect(subject[:manifest].digest).to eq(digest) end end diff --git a/spec/services/dependency_proxy/head_manifest_service_spec.rb b/spec/services/dependency_proxy/head_manifest_service_spec.rb index 9c1e4d650f8..7c7ebe4d181 100644 --- a/spec/services/dependency_proxy/head_manifest_service_spec.rb +++ b/spec/services/dependency_proxy/head_manifest_service_spec.rb @@ -8,19 +8,12 @@ RSpec.describe DependencyProxy::HeadManifestService do let(:tag) { 'latest' } let(:token) { Digest::SHA256.hexdigest('123') } let(:digest) { '12345' } - let(:content_type) { 'foo' } - let(:headers) do - { - 'docker-content-digest' => digest, - 'content-type' => content_type - } - end subject { described_class.new(image, tag, token).execute } context 'remote request is successful' do before do - stub_manifest_head(image, tag, headers: headers) + stub_manifest_head(image, tag, digest: digest) end it { expect(subject[:status]).to eq(:success) } diff --git a/spec/services/dependency_proxy/pull_manifest_service_spec.rb b/spec/services/dependency_proxy/pull_manifest_service_spec.rb index b3053174cc0..b760839d1fb 100644 --- a/spec/services/dependency_proxy/pull_manifest_service_spec.rb +++ b/spec/services/dependency_proxy/pull_manifest_service_spec.rb @@ -9,10 +9,7 @@ RSpec.describe DependencyProxy::PullManifestService do let(:token) { Digest::SHA256.hexdigest('123') } let(:manifest) { { foo: 'bar' }.to_json } let(:digest) { '12345' } - let(:content_type) { 'foo' } - let(:headers) do - { 'docker-content-digest' => digest, 'content-type' => content_type } - end + let(:headers) { { 'docker-content-digest' => digest } } subject { described_class.new(image, tag, token).execute_with_manifest(&method(:check_response)) } @@ -28,7 +25,6 @@ RSpec.describe DependencyProxy::PullManifestService do expect(response[:status]).to eq(:success) expect(response[:file].read).to eq(manifest) expect(response[:digest]).to eq(digest) - expect(response[:content_type]).to eq(content_type) end subject diff --git a/spec/support/helpers/dependency_proxy_helpers.rb b/spec/support/helpers/dependency_proxy_helpers.rb index 0d8f56906e3..ebb849628bf 100644 --- a/spec/support/helpers/dependency_proxy_helpers.rb +++ b/spec/support/helpers/dependency_proxy_helpers.rb @@ -18,11 +18,11 @@ module DependencyProxyHelpers .to_return(status: status, body: body || manifest, headers: headers) end - def stub_manifest_head(image, tag, status: 200, body: nil, headers: {}) + def stub_manifest_head(image, tag, status: 200, body: nil, digest: '123456') manifest_url = registry.manifest_url(image, tag) stub_full_request(manifest_url, method: :head) - .to_return(status: status, body: body, headers: headers ) + .to_return(status: status, body: body, headers: { 'docker-content-digest' => digest } ) end def stub_blob_download(image, blob_sha, status = 200, body = '123456') diff --git a/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb b/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb index 0587ad4c51c..b4ec146df14 100644 --- a/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb +++ b/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb @@ -19,6 +19,11 @@ RSpec.shared_examples 'Debian Distribution' do |factory, container, can_freeze| it { is_expected.to have_many(:components).class_name("Packages::Debian::#{container.capitalize}Component").inverse_of(:distribution) } it { is_expected.to have_many(:architectures).class_name("Packages::Debian::#{container.capitalize}Architecture").inverse_of(:distribution) } + + if container != :group + it { is_expected.to have_many(:publications).class_name('Packages::Debian::Publication').inverse_of(:distribution).with_foreign_key(:distribution_id) } + it { is_expected.to have_many(:packages).class_name('Packages::Package').through(:publications) } + end end describe 'validations' do