Commit 50586fe9 authored by Robert Speicher's avatar Robert Speicher

Merge branch '273655-dependency-proxy-to-core' into 'master'

[RUN AS-IF-FOSS] Move the Dependency Proxy to Core

See merge request gitlab-org/gitlab!47471
parents 6342dcbd e3b2bd52
import $ from 'jquery';
import initDependencyProxy from 'ee/dependency_proxy';
import initDependencyProxy from '~/dependency_proxy';
document.addEventListener('DOMContentLoaded', () => {
initDependencyProxy();
......
......@@ -170,6 +170,10 @@ module GroupsHelper
group_container_registry_nav?
end
def group_dependency_proxy_nav?
@group.dependency_proxy_feature_available?
end
def group_packages_list_nav?
@group.packages_feature_enabled?
end
......
......@@ -71,6 +71,9 @@ class Group < Namespace
has_many :group_deploy_tokens
has_many :deploy_tokens, through: :group_deploy_tokens
has_one :dependency_proxy_setting, class_name: 'DependencyProxy::GroupSetting'
has_many :dependency_proxy_blobs, class_name: 'DependencyProxy::Blob'
accepts_nested_attributes_for :variables, allow_destroy: true
validate :visibility_level_allowed_by_projects
......@@ -203,6 +206,10 @@ class Group < Namespace
::Gitlab.config.packages.enabled
end
def dependency_proxy_feature_available?
::Gitlab.config.dependency_proxy.enabled
end
def notification_email_for(user)
# Finds the closest notification_setting with a `notification_email`
notification_settings = notification_settings_for(user, hierarchy_order: :asc)
......
......@@ -46,6 +46,10 @@ class GroupPolicy < BasePolicy
group_projects_for(user: @user, group: @subject, only_owned: false).any? { |p| p.design_management_enabled? }
end
condition(:dependency_proxy_available) do
@subject.dependency_proxy_feature_available?
end
desc "Deploy token with read_package_registry scope"
condition(:read_package_registry_deploy_token) do
@user.is_a?(DeployToken) && @user.groups.include?(@subject) && @user.read_package_registry
......@@ -193,6 +197,12 @@ class GroupPolicy < BasePolicy
enable :read_group
end
rule { can?(:read_group) & dependency_proxy_available }
.enable :read_dependency_proxy
rule { developer & dependency_proxy_available }
.enable :admin_dependency_proxy
rule { resource_access_token_available & can?(:admin_group) }.policy do
enable :admin_resource_access_tokens
end
......
- packages_link = group_packages_list_nav? ? group_packages_path(@group) : group_container_registries_path(@group)
- if group_packages_nav?
= nav_link(controller: ['groups/packages', 'groups/registry/repositories']) do
= nav_link(controller: ['groups/packages', 'groups/registry/repositories', 'groups/dependency_proxies']) do
= link_to packages_link, title: _('Packages') do
.nav-icon-container
= sprite_icon('package')
......@@ -21,3 +21,7 @@
= nav_link(controller: 'groups/registry/repositories') do
= link_to group_container_registries_path(@group), title: _('Container Registry') do
%span= _('Container Registry')
- if group_dependency_proxy_nav?
= nav_link(controller: 'groups/dependency_proxies') do
= link_to group_dependency_proxy_path(@group), title: _('Dependency Proxy') do
%span= _('Dependency Proxy')
......@@ -451,6 +451,14 @@
:weight: 1
:idempotent: true
:tags: []
- :name: dependency_proxy:purge_dependency_proxy_cache
:feature_category: :dependency_proxy
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent: true
:tags: []
- :name: deployment:deployments_drop_older_deployments
:feature_category: :continuous_delivery
:has_external_dependencies:
......
---
title: Dependency proxy feature is moved to GitLab core
merge_request: 47471
author:
type: changed
......@@ -362,18 +362,16 @@ Settings.packages['object_store'] = ObjectStoreSettings.legacy_parse(Settings.p
#
# Dependency Proxy
#
Gitlab.ee do
Settings['dependency_proxy'] ||= Settingslogic.new({})
Settings.dependency_proxy['enabled'] = true if Settings.dependency_proxy['enabled'].nil?
Settings.dependency_proxy['storage_path'] = Settings.absolute(Settings.dependency_proxy['storage_path'] || File.join(Settings.shared['path'], "dependency_proxy"))
Settings.dependency_proxy['object_store'] = ObjectStoreSettings.legacy_parse(Settings.dependency_proxy['object_store'])
# For first iteration dependency proxy uses Rails server to download blobs.
# To ensure acceptable performance we only allow feature to be used with
# multithreaded web-server Puma. This will be removed once download logic is moved
# to GitLab workhorse
Settings.dependency_proxy['enabled'] = false unless Gitlab::Runtime.puma?
end
Settings['dependency_proxy'] ||= Settingslogic.new({})
Settings.dependency_proxy['enabled'] = true if Settings.dependency_proxy['enabled'].nil?
Settings.dependency_proxy['storage_path'] = Settings.absolute(Settings.dependency_proxy['storage_path'] || File.join(Settings.shared['path'], "dependency_proxy"))
Settings.dependency_proxy['object_store'] = ObjectStoreSettings.legacy_parse(Settings.dependency_proxy['object_store'])
# For first iteration dependency proxy uses Rails server to download blobs.
# To ensure acceptable performance we only allow feature to be used with
# multithreaded web-server Puma. This will be removed once download logic is moved
# to GitLab workhorse
Settings.dependency_proxy['enabled'] = false unless Gitlab::Runtime.puma?
#
# Terraform state
......
......@@ -107,6 +107,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
end
resources :container_registries, only: [:index, :show], controller: 'registry/repositories'
resource :dependency_proxy, only: [:show, :update]
end
scope(path: '*id',
......@@ -119,3 +120,14 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
delete '/', action: :destroy
end
end
# Dependency proxy for containers
# Because docker adds v2 prefix to URI this need to be outside of usual group routes
scope format: false do
get 'v2', to: proc { [200, {}, ['']] } # rubocop:disable Cop/PutGroupRoutesUnderScope
constraints image: Gitlab::PathRegex.container_image_regex, sha: Gitlab::PathRegex.container_image_blob_sha_regex do
get 'v2/*group_id/dependency_proxy/containers/*image/manifests/*tag' => 'groups/dependency_proxy_for_containers#manifest' # rubocop:todo Cop/PutGroupRoutesUnderScope
get 'v2/*group_id/dependency_proxy/containers/*image/blobs/:sha' => 'groups/dependency_proxy_for_containers#blob' # rubocop:todo Cop/PutGroupRoutesUnderScope
end
end
......@@ -269,7 +269,7 @@ The following documentation relates to the DevOps **Package** stage:
| Package topics | Description |
|:----------------------------------------------------------------|:-------------------------------------------------------|
| [Container Registry](user/packages/container_registry/index.md) | The GitLab Container Registry enables every project in GitLab to have its own space to store [Docker](https://www.docker.com/) images. |
| [Dependency Proxy](user/packages/dependency_proxy/index.md) **(PREMIUM)** | The GitLab Dependency Proxy sets up a local proxy for frequently used upstream images/packages. |
| [Dependency Proxy](user/packages/dependency_proxy/index.md) | The GitLab Dependency Proxy sets up a local proxy for frequently used upstream images/packages. |
| [Package Registry](user/packages/package_registry/index.md) | Use GitLab as a private or public registry for a variety of common package managers, including [NPM](user/packages/npm_registry/index.md), [Maven](user/packages/maven_repository/index.md), [PyPI](user/packages/pypi_repository/index.md), and others. You can also store generic files. |
<div align="right">
......
......@@ -143,7 +143,7 @@ Learn how to install, configure, update, and maintain your GitLab instance.
- [Container Registry](packages/container_registry.md): Configure Container Registry with GitLab.
- [Package Registry](packages/index.md): Enable GitLab to act as an NPM Registry and a Maven Repository.
- [Dependency Proxy](packages/dependency_proxy.md): Configure the Dependency Proxy, a local proxy for frequently used upstream images/packages. **(PREMIUM ONLY)**
- [Dependency Proxy](packages/dependency_proxy.md): Configure the Dependency Proxy, a local proxy for frequently used upstream images/packages.
### Repository settings
......
......@@ -4,9 +4,10 @@ group: Package
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
---
# GitLab Dependency Proxy administration **(PREMIUM ONLY)**
# GitLab Dependency Proxy administration
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7934) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.11.
> - [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 Core](https://about.gitlab.com/pricing/) in GitLab 13.6.
GitLab can be utilized as a dependency proxy for a variety of common package managers.
......
......@@ -2007,7 +2007,7 @@ on what features you intend to use:
| [Merge request diffs](../merge_request_diffs.md#using-object-storage) | Yes |
| [Mattermost](https://docs.mattermost.com/administration/config-settings.html#file-storage)| No |
| [Packages](../packages/index.md#using-object-storage) (optional feature) | Yes |
| [Dependency Proxy](../packages/dependency_proxy.md#using-object-storage) (optional feature) **(PREMIUM ONLY)** | Yes |
| [Dependency Proxy](../packages/dependency_proxy.md#using-object-storage) (optional feature) | Yes |
| [Pseudonymizer](../pseudonymizer.md#configuration) (optional feature) **(ULTIMATE ONLY)** | No |
| [Autoscale runner caching](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching) (optional for improved performance) | No |
| [Terraform state files](../terraform_state.md#using-object-storage) | Yes |
......
......@@ -2007,7 +2007,7 @@ on what features you intend to use:
| [Merge request diffs](../merge_request_diffs.md#using-object-storage) | Yes |
| [Mattermost](https://docs.mattermost.com/administration/config-settings.html#file-storage)| No |
| [Packages](../packages/index.md#using-object-storage) (optional feature) | Yes |
| [Dependency Proxy](../packages/dependency_proxy.md#using-object-storage) (optional feature) **(PREMIUM ONLY)** | Yes |
| [Dependency Proxy](../packages/dependency_proxy.md#using-object-storage) (optional feature) | Yes |
| [Pseudonymizer](../pseudonymizer.md#configuration) (optional feature) **(ULTIMATE ONLY)** | No |
| [Autoscale runner caching](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching) (optional for improved performance) | No |
| [Terraform state files](../terraform_state.md#using-object-storage) | Yes |
......
......@@ -866,7 +866,7 @@ on what features you intend to use:
| [Merge request diffs](../merge_request_diffs.md#using-object-storage) | Yes |
| [Mattermost](https://docs.mattermost.com/administration/config-settings.html#file-storage)| No |
| [Packages](../packages/index.md#using-object-storage) (optional feature) | Yes |
| [Dependency Proxy](../packages/dependency_proxy.md#using-object-storage) (optional feature) **(PREMIUM ONLY)** | Yes |
| [Dependency Proxy](../packages/dependency_proxy.md#using-object-storage) (optional feature) | Yes |
| [Pseudonymizer](../pseudonymizer.md#configuration) (optional feature) **(ULTIMATE ONLY)** | No |
| [Autoscale runner caching](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching) (optional for improved performance) | No |
| [Terraform state files](../terraform_state.md#using-object-storage) | Yes |
......
......@@ -1742,7 +1742,7 @@ on what features you intend to use:
| [Merge request diffs](../merge_request_diffs.md#using-object-storage) | Yes |
| [Mattermost](https://docs.mattermost.com/administration/config-settings.html#file-storage)| No |
| [Packages](../packages/index.md#using-object-storage) (optional feature) | Yes |
| [Dependency Proxy](../packages/dependency_proxy.md#using-object-storage) (optional feature) **(PREMIUM ONLY)** | Yes |
| [Dependency Proxy](../packages/dependency_proxy.md#using-object-storage) (optional feature) | Yes |
| [Pseudonymizer](../pseudonymizer.md#configuration) (optional feature) **(ULTIMATE ONLY)** | No |
| [Autoscale runner caching](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching) (optional for improved performance) | No |
| [Terraform state files](../terraform_state.md#using-object-storage) | Yes |
......
......@@ -2007,7 +2007,7 @@ on what features you intend to use:
| [Merge request diffs](../merge_request_diffs.md#using-object-storage) | Yes |
| [Mattermost](https://docs.mattermost.com/administration/config-settings.html#file-storage)| No |
| [Packages](../packages/index.md#using-object-storage) (optional feature) | Yes |
| [Dependency Proxy](../packages/dependency_proxy.md#using-object-storage) (optional feature) **(PREMIUM ONLY)** | Yes |
| [Dependency Proxy](../packages/dependency_proxy.md#using-object-storage) (optional feature) | Yes |
| [Pseudonymizer](../pseudonymizer.md#configuration) (optional feature) **(ULTIMATE ONLY)** | No |
| [Autoscale runner caching](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching) (optional for improved performance) | No |
| [Terraform state files](../terraform_state.md#using-object-storage) | Yes |
......
......@@ -1741,7 +1741,7 @@ on what features you intend to use:
| [Merge request diffs](../merge_request_diffs.md#using-object-storage) | Yes |
| [Mattermost](https://docs.mattermost.com/administration/config-settings.html#file-storage)| No |
| [Packages](../packages/index.md#using-object-storage) (optional feature) | Yes |
| [Dependency Proxy](../packages/dependency_proxy.md#using-object-storage) (optional feature) **(PREMIUM ONLY)** | Yes |
| [Dependency Proxy](../packages/dependency_proxy.md#using-object-storage) (optional feature) | Yes |
| [Pseudonymizer](../pseudonymizer.md#configuration) (optional feature) **(ULTIMATE ONLY)** | No |
| [Autoscale runner caching](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching) (optional for improved performance) | No |
| [Terraform state files](../terraform_state.md#using-object-storage) | Yes |
......
......@@ -4,11 +4,12 @@ group: unassigned
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
---
# Dependency Proxy API **(PREMIUM)**
# Dependency Proxy API
## Purge the dependency proxy for a group
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/11631) in GitLab 12.10.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/11631) in GitLab 12.10.
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/273655) to [GitLab Core](https://about.gitlab.com/pricing/) in GitLab 13.6.
Deletes the cached blobs for a group. This endpoint requires group admin access.
......
......@@ -798,7 +798,7 @@ With [GitLab Issue Analytics](issues_analytics/index.md), you can see a bar char
With [GitLab Repositories Analytics](repositories_analytics/index.md), you can download a CSV of the latest coverage data for all the projects in your group.
## Dependency Proxy **(PREMIUM)**
## Dependency Proxy
Use GitLab as a [dependency proxy](../packages/dependency_proxy/index.md) for upstream Docker images.
......
......@@ -4,9 +4,10 @@ group: Package
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
---
# Dependency Proxy **(PREMIUM)**
# Dependency Proxy
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7934) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.11.
> - [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 Core](https://about.gitlab.com/pricing/) in GitLab 13.6.
The GitLab Dependency Proxy is a local proxy you can use for your frequently-accessed
upstream images.
......
......@@ -256,7 +256,7 @@ group.
| Share (invite) groups with groups | | | | | ✓ |
| Create/edit/delete group milestones | | | ✓ | ✓ | ✓ |
| Create/edit/delete iterations | | | ✓ | ✓ | ✓ |
| Enable/disable a dependency proxy **(PREMIUM)** | | | ✓ | ✓ | ✓ |
| Enable/disable a dependency proxy | | | ✓ | ✓ | ✓ |
| Create and edit group wiki pages **(PREMIUM)** | | | ✓ | ✓ | ✓ |
| Use security dashboard **(ULTIMATE)** | | | ✓ | ✓ | ✓ |
| Create/edit/delete metrics dashboard annotations | | | ✓ | ✓ | ✓ |
......
......@@ -30,9 +30,6 @@ module EE
has_many :saml_group_links, foreign_key: 'group_id'
has_many :hooks, dependent: :destroy, class_name: 'GroupHook' # rubocop:disable Cop/ActiveRecordDependent
has_one :dependency_proxy_setting, class_name: 'DependencyProxy::GroupSetting'
has_many :dependency_proxy_blobs, class_name: 'DependencyProxy::Blob'
has_many :allowed_email_domains, -> { order(id: :asc) }, autosave: true
# We cannot simply set `has_many :audit_events, as: :entity, dependent: :destroy`
......@@ -316,10 +313,6 @@ module EE
end
end
def dependency_proxy_feature_available?
::Gitlab.config.dependency_proxy.enabled && feature_available?(:dependency_proxy)
end
override :supports_events?
def supports_events?
feature_available?(:epics)
......
......@@ -71,7 +71,6 @@ class License < ApplicationRecord
db_load_balancing
default_branch_protection_restriction_in_groups
default_project_deletion_protection
dependency_proxy
deploy_board
disable_name_update_for_users
email_additional_text
......
......@@ -61,10 +61,6 @@ module EE
!::Gitlab::IpRestriction::Enforcer.new(subject).allows_current_ip?
end
condition(:dependency_proxy_available) do
@subject.feature_available?(:dependency_proxy)
end
condition(:cluster_deployments_available) do
@subject.feature_available?(:cluster_deployments)
end
......@@ -166,12 +162,6 @@ module EE
enable :change_prevent_group_forking
end
rule { can?(:read_group) & dependency_proxy_available }
.enable :read_dependency_proxy
rule { developer & dependency_proxy_available }
.enable :admin_dependency_proxy
rule { can?(:read_group) & epics_available }.enable :read_epic
rule { can?(:read_group) & iterations_available }.enable :read_iteration
......
......@@ -299,14 +299,6 @@
:weight: 1
:idempotent:
:tags: []
- :name: dependency_proxy:purge_dependency_proxy_cache
:feature_category: :dependency_proxy
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent: true
:tags: []
- :name: epics:epics_update_epics_dates
:feature_category: :epics
:has_external_dependencies:
......
......@@ -160,19 +160,6 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
resource :roadmap, only: [:show], controller: 'roadmap'
resource :dependency_proxy, only: [:show, :update]
post '/restore' => '/groups#restore', as: :restore
end
end
# Dependency proxy for containers
# Because docker adds v2 prefix to URI this need to be outside of usual group routes
scope format: false do
get 'v2', to: proc { [200, {}, ['']] } # rubocop:disable Cop/PutGroupRoutesUnderScope
constraints image: Gitlab::PathRegex.container_image_regex, sha: Gitlab::PathRegex.container_image_blob_sha_regex do
get 'v2/*group_id/dependency_proxy/containers/*image/manifests/*tag' => 'groups/dependency_proxy_for_containers#manifest' # rubocop:todo Cop/PutGroupRoutesUnderScope
get 'v2/*group_id/dependency_proxy/containers/*image/blobs/:sha' => 'groups/dependency_proxy_for_containers#blob' # rubocop:todo Cop/PutGroupRoutesUnderScope
end
end
......@@ -13,7 +13,6 @@ module EE
mount ::API::AuditEvents
mount ::API::ProjectApprovalRules
mount ::API::ProjectApprovalSettings
mount ::API::DependencyProxy
mount ::API::EpicIssues
mount ::API::EpicLinks
mount ::API::Epics
......
......@@ -9,14 +9,6 @@ module EE
def saml_callback_regex
@saml_callback_regex ||= %r(\A\/groups\/(?<group>#{full_namespace_route_regex})\/\-\/saml\/callback\z).freeze
end
def container_image_regex
@container_image_regex ||= %r{([\w\.-]+\/){0,1}[\w\.-]+}.freeze
end
def container_image_blob_sha_regex
@container_image_blob_sha_regex ||= %r{[\w+.-]+:?[\w]+}.freeze
end
end
end
end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::PathRegex do
describe '.container_image_regex' do
subject { described_class.container_image_regex }
it { is_expected.to match('gitlab-foss') }
it { is_expected.to match('gitlab_foss') }
it { is_expected.to match('gitlab-org/gitlab-foss') }
it { is_expected.to match('100px.com/100px.ruby') }
it 'only matches at most one slash' do
expect(subject.match('foo/bar/baz')[0]).to eq('foo/bar')
end
it 'does not match other non-word characters' do
expect(subject.match('ruby:2.7.0')[0]).to eq('ruby')
end
end
describe '.container_image_blob_sha_regex' do
subject { described_class.container_image_blob_sha_regex }
it { is_expected.to match('sha256:asdf1234567890ASDF') }
it { is_expected.to match('foo:123') }
it { is_expected.to match('a12bc3f590szp') }
it { is_expected.not_to match('') }
it 'does not match malicious characters' do
expect(subject.match('sha256:asdf1234%2f')[0]).to eq('sha256:asdf1234')
end
end
end
......@@ -13,13 +13,11 @@ RSpec.describe Group do
# the presence check works, but since this is a private method that
# method can't be called with a public_send.
it { is_expected.to belong_to(:file_template_project).class_name('Project').without_validating_presence }
it { is_expected.to have_many(:dependency_proxy_blobs) }
it { is_expected.to have_many(:cycle_analytics_stages) }
it { is_expected.to have_many(:value_streams) }
it { is_expected.to have_many(:ip_restrictions) }
it { is_expected.to have_many(:allowed_email_domains) }
it { is_expected.to have_many(:compliance_management_frameworks) }
it { is_expected.to have_one(:dependency_proxy_setting) }
it { is_expected.to have_one(:deletion_schedule) }
it { is_expected.to have_one(:group_wiki_repository) }
it { is_expected.to belong_to(:push_rule) }
......
......@@ -31,48 +31,6 @@ RSpec.describe 'Group routing', "routing" do
end
end
describe 'dependency proxy for containers' do
context 'image name without namespace' do
it 'routes to #manifest' do
expect(get('/v2/gitlabhq/dependency_proxy/containers/ruby/manifests/2.3.6'))
.to route_to('groups/dependency_proxy_for_containers#manifest', group_id: 'gitlabhq', image: 'ruby', tag: '2.3.6')
end
it 'routes to #blob' do
expect(get('/v2/gitlabhq/dependency_proxy/containers/ruby/blobs/abc12345'))
.to route_to('groups/dependency_proxy_for_containers#blob', group_id: 'gitlabhq', image: 'ruby', sha: 'abc12345')
end
it "does not route to #blob with an invalid sha" do
expect(get("/v2/gitlabhq/dependency_proxy/containers/ruby/blobs/sha256:asdf1234%2f%2e%2e"))
.not_to route_to(group_id: 'gitlabhq', image: 'ruby', sha: 'sha256:asdf1234%2f%2e%2e')
end
it "does not route to #blob with an invalid image" do
expect(get("/v2/gitlabhq/dependency_proxy/containers/ru*by/blobs/abc12345"))
.not_to route_to('groups/dependency_proxy_for_containers#blob', group_id: 'gitlabhq', image: 'ru*by', sha: 'abc12345')
end
end
context 'image name with namespace' do
it 'routes to #manifest' do
expect(get('/v2/gitlabhq/dependency_proxy/containers/foo/bar/manifests/2.3.6'))
.to route_to('groups/dependency_proxy_for_containers#manifest', group_id: 'gitlabhq', image: 'foo/bar', tag: '2.3.6')
end
it 'routes to #blob' do
expect(get('/v2/gitlabhq/dependency_proxy/containers/foo/bar/blobs/abc12345'))
.to route_to('groups/dependency_proxy_for_containers#blob', group_id: 'gitlabhq', image: 'foo/bar', sha: 'abc12345')
end
end
end
describe 'hooks' do
it 'routes to hooks edit page' do
expect(get('/groups/gitlabhq/-/hooks/2/edit')).to route_to('groups/hooks#edit', group_id: 'gitlabhq', id: '2')
end
end
describe 'packages' do
it 'routes to packages index page' do
expect(get('/groups/gitlabhq/-/packages')).to route_to('groups/packages#index', group_id: 'gitlabhq')
......
# frozen_string_literal: true
module EE
module DependencyProxyHelpers
include StubRequests
def stub_registry_auth(image, token, status = 200, body = nil)
auth_body = { 'token' => token }.to_json
auth_link = registry.auth_url(image)
stub_full_request(auth_link)
.to_return(status: status, body: body || auth_body)
end
def stub_manifest_download(image, tag, status = 200, body = nil)
manifest_url = registry.manifest_url(image, tag)
stub_full_request(manifest_url)
.to_return(status: status, body: body || manifest)
end
def stub_blob_download(image, blob_sha, status = 200, body = '123456')
download_link = registry.blob_url(image, blob_sha)
stub_full_request(download_link)
.to_return(status: status, body: body)
end
private
def registry
@registry ||= DependencyProxy::Registry
end
end
end
......@@ -162,6 +162,7 @@ module API
mount ::API::CommitStatuses
mount ::API::ContainerRegistryEvent
mount ::API::ContainerRepositories
mount ::API::DependencyProxy
mount ::API::DeployKeys
mount ::API::DeployTokens
mount ::API::Deployments
......
......@@ -254,6 +254,14 @@ module Gitlab
%r{#{personal_snippet_path_regex}|#{project_snippet_path_regex}}
end
def container_image_regex
@container_image_regex ||= %r{([\w\.-]+\/){0,1}[\w\.-]+}.freeze
end
def container_image_blob_sha_regex
@container_image_blob_sha_regex ||= %r{[\w+.-]+:?\w+}.freeze
end
private
def personal_snippet_path_regex
......
......@@ -26,6 +26,8 @@ RSpec.describe Groups::DependencyProxiesController do
end
it 'returns 404 when feature is disabled' do
disable_dependency_proxy
get :show, params: { group_id: group.to_param }
expect(response).to have_gitlab_http_status(:not_found)
......@@ -60,9 +62,12 @@ RSpec.describe Groups::DependencyProxiesController do
end
def enable_dependency_proxy
allow(Gitlab.config.dependency_proxy)
.to receive(:enabled).and_return(true)
stub_config(dependency_proxy: { enabled: true })
group.create_dependency_proxy_setting!(enabled: true)
end
stub_licensed_features(dependency_proxy: true)
def disable_dependency_proxy
group.create_dependency_proxy_setting!(enabled: false)
end
end
......@@ -6,6 +6,20 @@ RSpec.describe Groups::DependencyProxyForContainersController do
let(:group) { create(:group) }
let(:token_response) { { status: :success, token: 'abcd1234' } }
shared_examples 'not found when disabled' do
context 'feature disabled' do
before do
disable_dependency_proxy
end
it 'returns 404' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
before do
allow(Gitlab.config.dependency_proxy)
.to receive(:enabled).and_return(true)
......@@ -25,6 +39,8 @@ RSpec.describe Groups::DependencyProxyForContainersController do
end
end
subject { get_manifest }
context 'feature enabled' do
before do
enable_dependency_proxy
......@@ -40,7 +56,7 @@ RSpec.describe Groups::DependencyProxyForContainersController do
end
it 'proxies status from the remote token request' do
get_manifest
subject
expect(response).to have_gitlab_http_status(:service_unavailable)
expect(response.body).to eq('Service Unavailable')
......@@ -57,7 +73,7 @@ RSpec.describe Groups::DependencyProxyForContainersController do
end
it 'proxies status from the remote manifest request' do
get_manifest
subject
expect(response).to have_gitlab_http_status(:bad_request)
expect(response.body).to be_empty
......@@ -65,18 +81,14 @@ RSpec.describe Groups::DependencyProxyForContainersController do
end
it 'returns 200 with manifest file' do
get_manifest
subject
expect(response).to have_gitlab_http_status(:ok)
expect(response.body).to eq(manifest)
end
end
it 'returns 404 when feature is disabled' do
get_manifest
expect(response).to have_gitlab_http_status(:not_found)
end
it_behaves_like 'not found when disabled'
def get_manifest
get :manifest, params: { group_id: group.to_param, image: 'alpine', tag: '3.9.2' }
......@@ -94,6 +106,8 @@ RSpec.describe Groups::DependencyProxyForContainersController do
end
end
subject { get_blob }
context 'feature enabled' do
before do
enable_dependency_proxy
......@@ -109,7 +123,7 @@ RSpec.describe Groups::DependencyProxyForContainersController do
end
it 'proxies status from the remote blob request' do
get_blob
subject
expect(response).to have_gitlab_http_status(:bad_request)
expect(response.body).to be_empty
......@@ -119,22 +133,18 @@ RSpec.describe Groups::DependencyProxyForContainersController do
it 'sends a file' do
expect(controller).to receive(:send_file).with(blob.file.path, {})
get_blob
subject
end
it 'returns Content-Disposition: attachment' do
get_blob
subject
expect(response).to have_gitlab_http_status(:ok)
expect(response.headers['Content-Disposition']).to match(/^attachment/)
end
end
it 'returns 404 when feature is disabled' do
get_blob
expect(response).to have_gitlab_http_status(:not_found)
end
it_behaves_like 'not found when disabled'
def get_blob
get :blob, params: { group_id: group.to_param, image: 'alpine', sha: blob_sha }
......@@ -142,7 +152,10 @@ RSpec.describe Groups::DependencyProxyForContainersController do
end
def enable_dependency_proxy
stub_licensed_features(dependency_proxy: true)
group.create_dependency_proxy_setting!(enabled: true)
end
def disable_dependency_proxy
group.create_dependency_proxy_setting!(enabled: false)
end
end
# frozen_string_literal: true
FactoryBot.define do
factory :dependency_proxy_blob, class: 'DependencyProxy::Blob' do
group
file { fixture_file_upload('ee/spec/fixtures/dependency_proxy/a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4.gz') }
file { fixture_file_upload('spec/fixtures/dependency_proxy/a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4.gz') }
file_name { 'a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4.gz' }
end
end
......@@ -13,7 +13,6 @@ RSpec.describe 'Group Dependency Proxy' do
group.add_reporter(reporter)
enable_feature
stub_licensed_features(dependency_proxy: true)
end
describe 'feature settings' do
......@@ -90,16 +89,6 @@ RSpec.describe 'Group Dependency Proxy' do
end
end
context 'feature is not supported by the license' do
it 'renders 404 page' do
stub_licensed_features(dependency_proxy: false)
visit path
expect(page).to have_gitlab_http_status(:not_found)
end
end
context 'feature is disabled globally' do
it 'renders 404 page' do
disable_feature
......@@ -113,10 +102,10 @@ RSpec.describe 'Group Dependency Proxy' do
end
def enable_feature
allow(Gitlab.config.dependency_proxy).to receive(:enabled).and_return(true)
stub_config(dependency_proxy: { enabled: true })
end
def disable_feature
allow(Gitlab.config.dependency_proxy).to receive(:enabled).and_return(false)
stub_config(dependency_proxy: { enabled: false })
end
end
......@@ -50,6 +50,8 @@ RSpec.describe 'Group navbar' do
insert_package_nav(_('Kubernetes'))
stub_feature_flags(group_iterations: false)
stub_config(dependency_proxy: { enabled: false })
stub_config(registry: { enabled: false })
stub_group_wikis(false)
group.add_maintainer(user)
sign_in(user)
......@@ -73,6 +75,18 @@ RSpec.describe 'Group navbar' do
it_behaves_like 'verified navigation bar'
end
context 'when dependency proxy is available' do
before do
stub_config(dependency_proxy: { enabled: true })
insert_dependency_proxy_nav(_('Dependency Proxy'))
visit group_path(group)
end
it_behaves_like 'verified navigation bar'
end
context 'when invite team members is not available' do
it 'does not display the js-invite-members-trigger' do
visit group_path(group)
......
......@@ -465,4 +465,34 @@ RSpec.describe Gitlab::PathRegex do
it_behaves_like 'invalid snippet routes'
end
describe '.container_image_regex' do
subject { described_class.container_image_regex }
it { is_expected.to match('gitlab-foss') }
it { is_expected.to match('gitlab_foss') }
it { is_expected.to match('gitlab-org/gitlab-foss') }
it { is_expected.to match('100px.com/100px.ruby') }
it 'only matches at most one slash' do
expect(subject.match('foo/bar/baz')[0]).to eq('foo/bar')
end
it 'does not match other non-word characters' do
expect(subject.match('ruby:2.7.0')[0]).to eq('ruby')
end
end
describe '.container_image_blob_sha_regex' do
subject { described_class.container_image_blob_sha_regex }
it { is_expected.to match('sha256:asdf1234567890ASDF') }
it { is_expected.to match('foo:123') }
it { is_expected.to match('a12bc3f590szp') }
it { is_expected.not_to match('') }
it 'does not match malicious characters' do
expect(subject.match('sha256:asdf1234%2f')[0]).to eq('sha256:asdf1234')
end
end
end
......@@ -28,6 +28,8 @@ RSpec.describe Group do
it { is_expected.to have_many(:iterations) }
it { is_expected.to have_many(:group_deploy_keys) }
it { is_expected.to have_many(:services) }
it { is_expected.to have_one(:dependency_proxy_setting) }
it { is_expected.to have_many(:dependency_proxy_blobs) }
describe '#members & #requesters' do
let(:requester) { create(:user) }
......
......@@ -7,12 +7,13 @@ RSpec.describe API::DependencyProxy, api: true do
let_it_be(:user) { create(:user) }
let_it_be(:blob) { create(:dependency_proxy_blob )}
let_it_be(:group) { blob.group }
let_it_be(:group, reload: true) { blob.group }
before do
group.add_owner(user)
stub_config(dependency_proxy: { enabled: true })
stub_licensed_features(dependency_proxy: true)
stub_last_activity_update
group.create_dependency_proxy_setting!(enabled: true)
end
describe 'DELETE /groups/:id/dependency_proxy/cache' do
......@@ -67,14 +68,5 @@ RSpec.describe API::DependencyProxy, api: true do
it_behaves_like 'returning response status', :not_found
end
context 'dependency feature is not available' do
before do
stub_config(dependency_proxy: { enabled: true })
stub_licensed_features(dependency_proxy: false)
end
it_behaves_like 'returning response status', :not_found
end
end
end
......@@ -79,4 +79,40 @@ RSpec.describe "Groups", "routing" do
let(:group_path) { 'projects.abc123' }
end
end
describe 'dependency proxy for containers' do
context 'image name without namespace' do
it 'routes to #manifest' do
expect(get('/v2/gitlabhq/dependency_proxy/containers/ruby/manifests/2.3.6'))
.to route_to('groups/dependency_proxy_for_containers#manifest', group_id: 'gitlabhq', image: 'ruby', tag: '2.3.6')
end
it 'routes to #blob' do
expect(get('/v2/gitlabhq/dependency_proxy/containers/ruby/blobs/abc12345'))
.to route_to('groups/dependency_proxy_for_containers#blob', group_id: 'gitlabhq', image: 'ruby', sha: 'abc12345')
end
it 'does not route to #blob with an invalid sha' do
expect(get('/v2/gitlabhq/dependency_proxy/containers/ruby/blobs/sha256:asdf1234%2f%2e%2e'))
.not_to route_to(group_id: 'gitlabhq', image: 'ruby', sha: 'sha256:asdf1234%2f%2e%2e')
end
it 'does not route to #blob with an invalid image' do
expect(get('/v2/gitlabhq/dependency_proxy/containers/ru*by/blobs/abc12345'))
.not_to route_to('groups/dependency_proxy_for_containers#blob', group_id: 'gitlabhq', image: 'ru*by', sha: 'abc12345')
end
end
context 'image name with namespace' do
it 'routes to #manifest' do
expect(get('/v2/gitlabhq/dependency_proxy/containers/foo/bar/manifests/2.3.6'))
.to route_to('groups/dependency_proxy_for_containers#manifest', group_id: 'gitlabhq', image: 'foo/bar', tag: '2.3.6')
end
it 'routes to #blob' do
expect(get('/v2/gitlabhq/dependency_proxy/containers/foo/bar/blobs/abc12345'))
.to route_to('groups/dependency_proxy_for_containers#blob', group_id: 'gitlabhq', image: 'foo/bar', sha: 'abc12345')
end
end
end
end
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe DependencyProxy::DownloadBlobService do
include EE::DependencyProxyHelpers
include DependencyProxyHelpers
let(:image) { 'alpine' }
let(:token) { Digest::SHA256.hexdigest('123') }
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe DependencyProxy::FindOrCreateBlobService do
include EE::DependencyProxyHelpers
include DependencyProxyHelpers
let(:blob) { create(:dependency_proxy_blob) }
let(:group) { blob.group }
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe DependencyProxy::PullManifestService do
include EE::DependencyProxyHelpers
include DependencyProxyHelpers
let(:image) { 'alpine' }
let(:tag) { '3.9' }
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe DependencyProxy::RequestTokenService do
include EE::DependencyProxyHelpers
include DependencyProxyHelpers
let(:image) { 'alpine:3.9' }
let(:token) { Digest::SHA256.hexdigest('123') }
......
......@@ -56,6 +56,7 @@ require_relative('../ee/spec/spec_helper') if Gitlab.ee?
# Load these first since they may be required by other helpers
require Rails.root.join("spec/support/helpers/git_helpers.rb")
require Rails.root.join("spec/support/helpers/stub_requests.rb")
# Then the rest
Dir[Rails.root.join("spec/support/helpers/*.rb")].sort.each { |f| require f }
......
# frozen_string_literal: true
module DependencyProxyHelpers
include StubRequests
def stub_registry_auth(image, token, status = 200, body = nil)
auth_body = { 'token' => token }.to_json
auth_link = registry.auth_url(image)
stub_full_request(auth_link)
.to_return(status: status, body: body || auth_body)
end
def stub_manifest_download(image, tag, status = 200, body = nil)
manifest_url = registry.manifest_url(image, tag)
stub_full_request(manifest_url)
.to_return(status: status, body: body || manifest)
end
def stub_blob_download(image, blob_sha, status = 200, body = '123456')
download_link = registry.blob_url(image, blob_sha)
stub_full_request(download_link)
.to_return(status: status, body: body)
end
private
def registry
@registry ||= DependencyProxy::Registry
end
end
......@@ -36,4 +36,12 @@ module NavbarStructureHelper
new_sub_nav_item_name: _('Container Registry')
)
end
def insert_dependency_proxy_nav(within)
insert_after_sub_nav_item(
_('Package Registry'),
within: _('Packages & Registries'),
new_sub_nav_item_name: _('Dependency Proxy')
)
end
end
......@@ -5,14 +5,14 @@ require 'spec_helper'
RSpec.describe PurgeDependencyProxyCacheWorker do
let_it_be(:user) { create(:admin) }
let_it_be(:blob) { create(:dependency_proxy_blob )}
let_it_be(:group) { blob.group }
let_it_be(:group, reload: true) { blob.group }
let_it_be(:group_id) { group.id }
subject { described_class.new.perform(user.id, group_id) }
before do
stub_config(dependency_proxy: { enabled: true })
stub_licensed_features(dependency_proxy: true)
group.create_dependency_proxy_setting!(enabled: true)
end
describe '#perform' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment