Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
e9f7a72759
commit
1c6e8c1498
|
|
@ -49,7 +49,7 @@ export default {
|
|||
initialVulnerabilitiesIssuetype: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
default: undefined,
|
||||
},
|
||||
initialProjectKey: {
|
||||
type: String,
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
||||
|
|
|
|||
|
|
@ -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_'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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?
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add empty jobs page with link to editor
|
||||
merge_request: 53240
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Debian Publications
|
||||
merge_request: 52916
|
||||
author: Mathieu Parent
|
||||
type: added
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Send only latest jobs in pipeline webhook payload.
|
||||
merge_request: 53159
|
||||
author:
|
||||
type: fixed
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: Remove dependency_proxy_manifests records with content_type to prevent Dependency
|
||||
Proxy failures
|
||||
merge_request: 53506
|
||||
author:
|
||||
type: fixed
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
51967d740ce184b27d0d9417fc86cb896fd3e3aa8a5e40759b290f47b9f3e99b
|
||||
|
|
@ -0,0 +1 @@
|
|||
483d1b4a24086fa57efe7f3b3fa872cf793352f80aba5c25614f07eafa2d30c5
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
|
||||
Watch how to [use the Dependency Proxy to help avoid Docker Hub rate limits](https://youtu.be/Nc4nUo7Pq08).
|
||||
|
|
|
|||
|
|
@ -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**:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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');
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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") }
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue