Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-10-11 18:09:46 +00:00
parent 046d6f5277
commit 5da842297d
46 changed files with 431 additions and 68 deletions

View File

@ -9,4 +9,5 @@ export const dateFormats = {
isoDate, isoDate,
defaultDate: mediumDate, defaultDate: mediumDate,
defaultDateTime: 'mmm d, yyyy h:MMtt', defaultDateTime: 'mmm d, yyyy h:MMtt',
month: 'mmmm',
}; };

View File

@ -247,7 +247,11 @@ export default class FileTemplateMediator {
} }
setFilename(name) { setFilename(name) {
this.$filenameInput.val(name).trigger('change'); const input = this.$filenameInput.get(0);
if (name !== undefined && input.value !== name) {
input.value = name;
input.dispatchEvent(new Event('change'));
}
} }
getSelected() { getSelected() {

View File

@ -107,7 +107,9 @@ export default {
this.groupToFilterBy = this.userGroups.find( this.groupToFilterBy = this.userGroups.find(
(group) => getIdFromGraphQLId(group.id) === groupId, (group) => getIdFromGraphQLId(group.id) === groupId,
); );
this.setNamespace(this.groupToFilterBy); if (this.groupToFilterBy) {
this.setNamespace(this.groupToFilterBy);
}
}, },
setNamespace({ id, fullPath }) { setNamespace({ id, fullPath }) {
this.selectedNamespace = { this.selectedNamespace = {

View File

@ -5,7 +5,7 @@ import { s__, __ } from '~/locale';
export const CONTAINER_REGISTRY_TITLE = s__('ContainerRegistry|Container Registry'); export const CONTAINER_REGISTRY_TITLE = s__('ContainerRegistry|Container Registry');
export const CONNECTION_ERROR_TITLE = s__('ContainerRegistry|Docker connection error'); export const CONNECTION_ERROR_TITLE = s__('ContainerRegistry|Docker connection error');
export const CONNECTION_ERROR_MESSAGE = s__( export const CONNECTION_ERROR_MESSAGE = s__(
`ContainerRegistry|We are having trouble connecting to the Registry, which could be due to an issue with your project name or path. %{docLinkStart}More information%{docLinkEnd}`, `ContainerRegistry|We are having trouble connecting to the Container Registry. Please try refreshing the page. If this error persists, please review %{docLinkStart}the troubleshooting documentation%{docLinkEnd}.`,
); );
export const LIST_INTRO_TEXT = s__( export const LIST_INTRO_TEXT = s__(
`ContainerRegistry|With the GitLab Container Registry, every project can have its own space to store images. %{docLinkStart}More information%{docLinkEnd}`, `ContainerRegistry|With the GitLab Container Registry, every project can have its own space to store images. %{docLinkStart}More information%{docLinkEnd}`,

View File

@ -36,6 +36,8 @@ export default () => {
isAdmin, isAdmin,
showCleanupPolicyOnAlert, showCleanupPolicyOnAlert,
showUnfinishedTagCleanupCallout, showUnfinishedTagCleanupCallout,
connectionError,
invalidPathError,
...config ...config
} = el.dataset; } = el.dataset;
@ -67,6 +69,8 @@ export default () => {
isAdmin: parseBoolean(isAdmin), isAdmin: parseBoolean(isAdmin),
showCleanupPolicyOnAlert: parseBoolean(showCleanupPolicyOnAlert), showCleanupPolicyOnAlert: parseBoolean(showCleanupPolicyOnAlert),
showUnfinishedTagCleanupCallout: parseBoolean(showUnfinishedTagCleanupCallout), showUnfinishedTagCleanupCallout: parseBoolean(showUnfinishedTagCleanupCallout),
connectionError: parseBoolean(connectionError),
invalidPathError: parseBoolean(invalidPathError),
}, },
/* eslint-disable @gitlab/require-i18n-strings */ /* eslint-disable @gitlab/require-i18n-strings */
dockerBuildCommand: `docker build -t ${config.repositoryUrl} .`, dockerBuildCommand: `docker build -t ${config.repositoryUrl} .`,

View File

@ -171,6 +171,9 @@ export default {
showDeleteAlert() { showDeleteAlert() {
return this.deleteAlertType && this.itemToDelete?.path; return this.deleteAlertType && this.itemToDelete?.path;
}, },
showConnectionError() {
return this.config.connectionError || this.config.invalidPathError;
},
deleteImageAlertMessage() { deleteImageAlertMessage() {
return this.deleteAlertType === 'success' return this.deleteAlertType === 'success'
? DELETE_IMAGE_SUCCESS_MESSAGE ? DELETE_IMAGE_SUCCESS_MESSAGE
@ -292,7 +295,7 @@ export default {
/> />
<gl-empty-state <gl-empty-state
v-if="config.characterError" v-if="showConnectionError"
:title="$options.i18n.CONNECTION_ERROR_TITLE" :title="$options.i18n.CONNECTION_ERROR_TITLE"
:svg-path="config.containersErrorImage" :svg-path="config.containersErrorImage"
> >

View File

@ -0,0 +1,38 @@
# frozen_string_literal: true
module Registry
module ConnectionErrorsHandler
extend ActiveSupport::Concern
included do
rescue_from ContainerRegistry::Path::InvalidRegistryPathError, with: :invalid_registry_path
rescue_from Faraday::Error, with: :connection_error
before_action :ping_container_registry
end
private
# rubocop:disable Gitlab/ModuleWithInstanceVariables
# These instance variables are only read by a view helper to pass
# them to the frontend
# See app/views/projects/registry/repositories/index.html.haml
# app/views/groups/registry/repositories/index.html.haml
def invalid_registry_path
@invalid_path_error = true
render :index
end
def connection_error
@connection_error = true
render :index
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
def ping_container_registry
ContainerRegistry::Client.registry_info
end
end
end

View File

@ -3,6 +3,7 @@ module Groups
module Registry module Registry
class RepositoriesController < Groups::ApplicationController class RepositoriesController < Groups::ApplicationController
include PackagesHelper include PackagesHelper
include ::Registry::ConnectionErrorsHandler
before_action :verify_container_registry_enabled! before_action :verify_container_registry_enabled!
before_action :authorize_read_container_image! before_action :authorize_read_container_image!

View File

@ -4,6 +4,7 @@ module Projects
module Registry module Registry
class RepositoriesController < ::Projects::Registry::ApplicationController class RepositoriesController < ::Projects::Registry::ApplicationController
include PackagesHelper include PackagesHelper
include ::Registry::ConnectionErrorsHandler
before_action :authorize_update_container_image!, only: [:destroy] before_action :authorize_update_container_image!, only: [:destroy]
@ -48,8 +49,6 @@ module Projects
repository.save! if repository.has_tags? repository.save! if repository.has_tags?
end end
end end
rescue ContainerRegistry::Path::InvalidRegistryPathError
@character_error = true
end end
end end
end end

View File

@ -17,5 +17,11 @@ module Types
def can_delete def can_delete
Ability.allowed?(current_user, :destroy_container_image, object) Ability.allowed?(current_user, :destroy_container_image, object)
end end
def tags
object.tags
rescue Faraday::Error
raise ::Gitlab::Graphql::Errors::ResourceNotAvailable, 'We are having trouble connecting to the Container Registry. If this error persists, please review the troubleshooting documentation.'
end
end end
end end

View File

@ -28,5 +28,11 @@ module Types
def project def project
Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, object.project_id).find Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, object.project_id).find
end end
def tags_count
object.tags_count
rescue Faraday::Error
raise ::Gitlab::Graphql::Errors::ResourceNotAvailable, 'We are having trouble connecting to the Container Registry. If this error persists, please review the troubleshooting documentation.'
end
end end
end end

View File

@ -16,7 +16,8 @@
is_group_page: "true", is_group_page: "true",
"group_path": @group.full_path, "group_path": @group.full_path,
"gid_prefix": container_repository_gid_prefix, "gid_prefix": container_repository_gid_prefix,
character_error: @character_error.to_s, connection_error: (!!@connection_error).to_s,
invalid_path_error: (!!@invalid_path_error).to_s,
user_callouts_path: user_callouts_path, user_callouts_path: user_callouts_path,
user_callout_id: UserCalloutsHelper::UNFINISHED_TAG_CLEANUP_CALLOUT, user_callout_id: UserCalloutsHelper::UNFINISHED_TAG_CLEANUP_CALLOUT,
show_unfinished_tag_cleanup_callout: show_unfinished_tag_cleanup_callout?.to_s } } show_unfinished_tag_cleanup_callout: show_unfinished_tag_cleanup_callout?.to_s } }

View File

@ -20,7 +20,8 @@
"is_admin": current_user&.admin.to_s, "is_admin": current_user&.admin.to_s,
"show_cleanup_policy_on_alert": show_cleanup_policy_on_alert(@project).to_s, "show_cleanup_policy_on_alert": show_cleanup_policy_on_alert(@project).to_s,
"cleanup_policies_settings_path": project_settings_packages_and_registries_path(@project), "cleanup_policies_settings_path": project_settings_packages_and_registries_path(@project),
character_error: @character_error.to_s, connection_error: (!!@connection_error).to_s,
invalid_path_error: (!!@invalid_path_error).to_s,
user_callouts_path: user_callouts_path, user_callouts_path: user_callouts_path,
user_callout_id: UserCalloutsHelper::UNFINISHED_TAG_CLEANUP_CALLOUT, user_callout_id: UserCalloutsHelper::UNFINISHED_TAG_CLEANUP_CALLOUT,
show_unfinished_tag_cleanup_callout: show_unfinished_tag_cleanup_callout?.to_s, } } show_unfinished_tag_cleanup_callout: show_unfinished_tag_cleanup_callout?.to_s, } }

View File

@ -1,7 +1,7 @@
--- ---
name: jira_connect_asymmetric_jwt name: jira_connect_asymmetric_jwt
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/71080 introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/71080
rollout_issue_url: rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/342808
milestone: '14.4' milestone: '14.4'
type: development type: development
group: group::integrations group: group::integrations

View File

@ -126,6 +126,7 @@ Supported attributes:
## Get file archive ## Get file archive
> Support for [including Git LFS blobs](../topics/git/lfs/index.md#lfs-objects-in-project-archives) was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/15079) in GitLab 13.5. > Support for [including Git LFS blobs](../topics/git/lfs/index.md#lfs-objects-in-project-archives) was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/15079) in GitLab 13.5.
> Support for downloading a subfolder was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/28827) in GitLab 14.4.
Get an archive of the repository. This endpoint can be accessed without Get an archive of the repository. This endpoint can be accessed without
authentication if the repository is publicly accessible. authentication if the repository is publicly accessible.
@ -147,11 +148,12 @@ Supported attributes:
|:------------|:---------------|:---------|:----------------------| |:------------|:---------------|:---------|:----------------------|
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. | | `id` | integer/string | yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
| `sha` | string | no | The commit SHA to download. A tag, branch reference, or SHA can be used. This defaults to the tip of the default branch if not specified. | | `sha` | string | no | The commit SHA to download. A tag, branch reference, or SHA can be used. This defaults to the tip of the default branch if not specified. |
| `path` | string | no | The subpath of the repository to download. This defaults to the whole repository (empty string). |
Example request: Example request:
```shell ```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.com/api/v4/projects/<project_id>/repository/archive?sha=<commit_sha>" curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.com/api/v4/projects/<project_id>/repository/archive?sha=<commit_sha>&path=<path>"
``` ```
## Compare branches, tags or commits ## Compare branches, tags or commits

View File

@ -156,11 +156,3 @@ LIMIT 20
### Web-specific parameters ### Web-specific parameters
Snowplow JS adds [web-specific parameters](https://docs.snowplowanalytics.com/docs/collecting-data/collecting-from-own-applications/snowplow-tracker-protocol/#Web-specific_parameters) to all web events by default. Snowplow JS adds [web-specific parameters](https://docs.snowplowanalytics.com/docs/collecting-data/collecting-from-own-applications/snowplow-tracker-protocol/#Web-specific_parameters) to all web events by default.
## Implement Snowplow tracking
See the [Implementation](implementation.md) guide.
## Snowplow schemas
See the [Snowplow schemas](schemas.md) guide.

View File

@ -14,6 +14,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> - Dependency Scanning metrics [added](https://gitlab.com/gitlab-org/gitlab/-/issues/328034) in GitLab 14.2. > - Dependency Scanning metrics [added](https://gitlab.com/gitlab-org/gitlab/-/issues/328034) in GitLab 14.2.
> - Multiselect [added](https://gitlab.com/gitlab-org/gitlab/-/issues/333586) in GitLab 14.2. > - Multiselect [added](https://gitlab.com/gitlab-org/gitlab/-/issues/333586) in GitLab 14.2.
> - Overview table [added](https://gitlab.com/gitlab-org/gitlab/-/issues/335638) in GitLab 14.3. > - Overview table [added](https://gitlab.com/gitlab-org/gitlab/-/issues/335638) in GitLab 14.3.
> - Adoption over time chart [added](https://gitlab.com/gitlab-org/gitlab/-/issues/337561) in GitLab 14.4.
Prerequisites: Prerequisites:
@ -69,6 +70,13 @@ Each group appears as a separate row in the table.
For each row, a feature is considered "adopted" if it has been used in a project in the given group For each row, a feature is considered "adopted" if it has been used in a project in the given group
during the time period (including projects in any subgroups of the given group). during the time period (including projects in any subgroups of the given group).
## Adoption over time
The **Adoption over time** chart in the **Overview** tab displays DevOps Adoption over time. The chart displays the total number of adopted features from the previous twelve months,
from when you enabled DevOps Adoption for the group.
The tooltip displays information about the features tracked for individual months.
## When is a feature considered adopted ## When is a feature considered adopted
A feature is considered "adopted" if it has been used anywhere in the group in the specified time. A feature is considered "adopted" if it has been used anywhere in the group in the specified time.

View File

@ -3,6 +3,8 @@
module API module API
class ContainerRepositories < ::API::Base class ContainerRepositories < ::API::Base
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
include ::API::Helpers::ContainerRegistryHelpers
helpers ::API::Helpers::PackagesHelpers helpers ::API::Helpers::PackagesHelpers
before { authenticate! } before { authenticate! }

View File

@ -3,6 +3,7 @@
module API module API
class GroupContainerRepositories < ::API::Base class GroupContainerRepositories < ::API::Base
include PaginationParams include PaginationParams
include ::API::Helpers::ContainerRegistryHelpers
helpers ::API::Helpers::PackagesHelpers helpers ::API::Helpers::PackagesHelpers

View File

@ -430,8 +430,8 @@ module API
render_api_error!('406 Not Acceptable', 406) render_api_error!('406 Not Acceptable', 406)
end end
def service_unavailable! def service_unavailable!(message = nil)
render_api_error!('503 Service Unavailable', 503) render_api_error!(message || '503 Service Unavailable', 503)
end end
def conflict!(message = nil) def conflict!(message = nil)

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
module API
module Helpers
module ContainerRegistryHelpers
extend ActiveSupport::Concern
included do
rescue_from Faraday::Error, ContainerRegistry::Path::InvalidRegistryPathError do |e|
service_unavailable!('We are having trouble connecting to the Container Registry. If this error persists, please review the troubleshooting documentation.')
end
end
end
end
end

View File

@ -3,6 +3,8 @@
module API module API
class ProjectContainerRepositories < ::API::Base class ProjectContainerRepositories < ::API::Base
include PaginationParams include PaginationParams
include ::API::Helpers::ContainerRegistryHelpers
helpers ::API::Helpers::PackagesHelpers helpers ::API::Helpers::PackagesHelpers
REPOSITORY_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge( REPOSITORY_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(

View File

@ -101,6 +101,7 @@ module API
params do params do
optional :sha, type: String, desc: 'The commit sha of the archive to be downloaded' optional :sha, type: String, desc: 'The commit sha of the archive to be downloaded'
optional :format, type: String, desc: 'The archive format' optional :format, type: String, desc: 'The archive format'
optional :path, type: String, desc: 'Subfolder of the repository to be downloaded'
end end
get ':id/repository/archive', requirements: { format: Gitlab::PathRegex.archive_formats_regex } do get ':id/repository/archive', requirements: { format: Gitlab::PathRegex.archive_formats_regex } do
if archive_rate_limit_reached?(current_user, user_project) if archive_rate_limit_reached?(current_user, user_project)
@ -109,7 +110,7 @@ module API
not_acceptable! if Gitlab::HotlinkingDetector.intercept_hotlinking?(request) not_acceptable! if Gitlab::HotlinkingDetector.intercept_hotlinking?(request)
send_git_archive user_project.repository, ref: params[:sha], format: params[:format], append_sha: true send_git_archive user_project.repository, ref: params[:sha], format: params[:format], append_sha: true, path: params[:path]
rescue StandardError rescue StandardError
not_found!('File') not_found!('File')
end end

View File

@ -51,6 +51,15 @@ module ContainerRegistry
client.supports_tag_delete? client.supports_tag_delete?
end end
def self.registry_info
registry_config = Gitlab.config.registry
return unless registry_config.enabled && registry_config.api_url.present?
token = Auth::ContainerRegistryAuthenticationService.access_token([], [])
client = new(registry_config.api_url, token: token)
client.registry_info
end
def initialize(base_uri, options = {}) def initialize(base_uri, options = {})
@base_uri = base_uri @base_uri = base_uri
@options = options @options = options

View File

@ -9111,7 +9111,7 @@ msgstr ""
msgid "ContainerRegistry|To widen your search, change or remove the filters above." msgid "ContainerRegistry|To widen your search, change or remove the filters above."
msgstr "" msgstr ""
msgid "ContainerRegistry|We are having trouble connecting to the Registry, which could be due to an issue with your project name or path. %{docLinkStart}More information%{docLinkEnd}" msgid "ContainerRegistry|We are having trouble connecting to the Container Registry. Please try refreshing the page. If this error persists, please review %{docLinkStart}the troubleshooting documentation%{docLinkEnd}."
msgstr "" msgstr ""
msgid "ContainerRegistry|With the Container Registry, every project can have its own space to store its Docker images. %{docLinkStart}More Information%{docLinkEnd}" msgid "ContainerRegistry|With the Container Registry, every project can have its own space to store its Docker images. %{docLinkStart}More Information%{docLinkEnd}"
@ -11704,6 +11704,9 @@ msgstr ""
msgid "DevopsAdoption|Adoption by subgroup" msgid "DevopsAdoption|Adoption by subgroup"
msgstr "" msgstr ""
msgid "DevopsAdoption|Adoption over time"
msgstr ""
msgid "DevopsAdoption|An error occurred while removing the group. Please try again." msgid "DevopsAdoption|An error occurred while removing the group. Please try again."
msgstr "" msgstr ""
@ -11782,6 +11785,9 @@ msgstr ""
msgid "DevopsAdoption|No results…" msgid "DevopsAdoption|No results…"
msgstr "" msgstr ""
msgid "DevopsAdoption|No tracked features"
msgstr ""
msgid "DevopsAdoption|Not adopted" msgid "DevopsAdoption|Not adopted"
msgstr "" msgstr ""
@ -11827,6 +11833,9 @@ msgstr ""
msgid "DevopsAdoption|This group has no subgroups" msgid "DevopsAdoption|This group has no subgroups"
msgstr "" msgstr ""
msgid "DevopsAdoption|Total number of features adopted"
msgstr ""
msgid "DevopsAdoption|You cannot remove the group you are currently in." msgid "DevopsAdoption|You cannot remove the group you are currently in."
msgstr "" msgstr ""
@ -24104,7 +24113,7 @@ msgstr ""
msgid "PQL|Submit information" msgid "PQL|Submit information"
msgstr "" msgstr ""
msgid "PQL|Thank you for reaching out! Our sales team will bet back to you soon." msgid "PQL|Thank you for reaching out! Our sales team will get back to you soon."
msgstr "" msgstr ""
msgid "Package Registry" msgid "Package Registry"

View File

@ -19,6 +19,7 @@ RSpec.describe Groups::Registry::RepositoriesController do
before do before do
stub_container_registry_config(enabled: true) stub_container_registry_config(enabled: true)
stub_container_registry_tags(repository: :any, tags: []) stub_container_registry_tags(repository: :any, tags: [])
stub_container_registry_info
group.add_owner(user) group.add_owner(user)
group.add_guest(guest) group.add_guest(guest)
sign_in(user) sign_in(user)
@ -37,6 +38,18 @@ RSpec.describe Groups::Registry::RepositoriesController do
'name' => repo.name 'name' => repo.name
) )
end end
[ContainerRegistry::Path::InvalidRegistryPathError, Faraday::Error].each do |error_class|
context "when there is a #{error_class}" do
it 'displays a connection error message' do
expect(::ContainerRegistry::Client).to receive(:registry_info).and_raise(error_class, nil, nil)
subject
expect(response).to have_gitlab_http_status(:ok)
end
end
end
end end
shared_examples 'with name parameter' do shared_examples 'with name parameter' do
@ -71,6 +84,18 @@ RSpec.describe Groups::Registry::RepositoriesController do
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect_no_snowplow_event expect_no_snowplow_event
end end
[ContainerRegistry::Path::InvalidRegistryPathError, Faraday::Error].each do |error_class|
context "when there is an invalid path error #{error_class}" do
it 'displays a connection error message' do
expect(::ContainerRegistry::Client).to receive(:registry_info).and_raise(error_class, nil, nil)
subject
expect(response).to have_gitlab_http_status(:ok)
end
end
end
end end
context 'json format' do context 'json format' do

View File

@ -9,6 +9,7 @@ RSpec.describe Projects::Registry::RepositoriesController do
before do before do
sign_in(user) sign_in(user)
stub_container_registry_config(enabled: true) stub_container_registry_config(enabled: true)
stub_container_registry_info
end end
context 'when user has access to registry' do context 'when user has access to registry' do
@ -30,6 +31,18 @@ RSpec.describe Projects::Registry::RepositoriesController do
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:not_found)
end end
[ContainerRegistry::Path::InvalidRegistryPathError, Faraday::Error].each do |error_class|
context "when there is a #{error_class}" do
it 'displays a connection error message' do
expect(::ContainerRegistry::Client).to receive(:registry_info).and_raise(error_class, nil, nil)
go_to_index
expect(response).to have_gitlab_http_status(:ok)
end
end
end
end end
shared_examples 'renders a list of repositories' do shared_examples 'renders a list of repositories' do

View File

@ -16,6 +16,7 @@ RSpec.describe 'Container Registry', :js do
sign_in(user) sign_in(user)
stub_container_registry_config(enabled: true) stub_container_registry_config(enabled: true)
stub_container_registry_tags(repository: :any, tags: []) stub_container_registry_tags(repository: :any, tags: [])
stub_container_registry_info
end end
it 'has a page title set' do it 'has a page title set' do
@ -57,6 +58,16 @@ RSpec.describe 'Container Registry', :js do
expect(page).to have_content 'latest' expect(page).to have_content 'latest'
end end
[ContainerRegistry::Path::InvalidRegistryPathError, Faraday::Error].each do |error_class|
context "when there is a #{error_class}" do
before do
expect(::ContainerRegistry::Client).to receive(:registry_info).and_raise(error_class, nil, nil)
end
it_behaves_like 'handling feature network errors with the container registry'
end
end
describe 'image repo details' do describe 'image repo details' do
before do before do
visit_container_registry_details 'my/image' visit_container_registry_details 'my/image'

View File

@ -20,6 +20,7 @@ RSpec.describe 'Container Registry', :js do
sign_in(user) sign_in(user)
project.add_developer(user) project.add_developer(user)
stub_container_registry_config(enabled: true) stub_container_registry_config(enabled: true)
stub_container_registry_info
stub_container_registry_tags(repository: :any, tags: []) stub_container_registry_tags(repository: :any, tags: [])
end end
@ -122,6 +123,16 @@ RSpec.describe 'Container Registry', :js do
expect(page).to have_content('Digest: N/A') expect(page).to have_content('Digest: N/A')
end end
end end
[ContainerRegistry::Path::InvalidRegistryPathError, Faraday::Error].each do |error_class|
context "when there is a #{error_class}" do
before do
expect(::ContainerRegistry::Client).to receive(:registry_info).and_raise(error_class, nil, nil)
end
it_behaves_like 'handling feature network errors with the container registry'
end
end
end end
describe 'image repo details when image has no name' do describe 'image repo details when image has no name' do

View File

@ -553,6 +553,7 @@ RSpec.describe "Internal Project Access" do
before do before do
stub_container_registry_tags(repository: :any, tags: ['latest']) stub_container_registry_tags(repository: :any, tags: ['latest'])
stub_container_registry_config(enabled: true) stub_container_registry_config(enabled: true)
stub_container_registry_info
project.container_repositories << container_repository project.container_repositories << container_repository
end end

View File

@ -570,6 +570,7 @@ RSpec.describe "Private Project Access" do
before do before do
stub_container_registry_tags(repository: :any, tags: ['latest']) stub_container_registry_tags(repository: :any, tags: ['latest'])
stub_container_registry_config(enabled: true) stub_container_registry_config(enabled: true)
stub_container_registry_info
project.container_repositories << container_repository project.container_repositories << container_repository
end end

View File

@ -552,6 +552,7 @@ RSpec.describe "Public Project Access" do
before do before do
stub_container_registry_tags(repository: :any, tags: ['latest']) stub_container_registry_tags(repository: :any, tags: ['latest'])
stub_container_registry_config(enabled: true) stub_container_registry_config(enabled: true)
stub_container_registry_info
project.container_repositories << container_repository project.container_repositories << container_repository
end end

View File

@ -0,0 +1,53 @@
import TemplateSelectorMediator from '~/blob/file_template_mediator';
describe('Template Selector Mediator', () => {
let mediator;
describe('setFilename', () => {
let input;
const newFileName = 'foo';
const editor = jest.fn().mockImplementationOnce(() => ({
getValue: jest.fn().mockImplementation(() => {}),
}))();
beforeEach(() => {
setFixtures('<div class="file-editor"><input class="js-file-path-name-input" /></div>');
input = document.querySelector('.js-file-path-name-input');
mediator = new TemplateSelectorMediator({
editor,
currentAction: jest.fn(),
projectId: jest.fn(),
});
});
it('fills out the input field', () => {
expect(input.value).toBe('');
mediator.setFilename(newFileName);
expect(input.value).toBe(newFileName);
});
it.each`
name | newName | shouldDispatch
${newFileName} | ${newFileName} | ${false}
${newFileName} | ${''} | ${true}
${newFileName} | ${undefined} | ${false}
${''} | ${''} | ${false}
${''} | ${newFileName} | ${true}
${''} | ${undefined} | ${false}
`(
'correctly reacts to the name change when current name is $name and newName is $newName',
({ name, newName, shouldDispatch }) => {
input.value = name;
const eventHandler = jest.fn();
input.addEventListener('change', eventHandler);
mediator.setFilename(newName);
if (shouldDispatch) {
expect(eventHandler).toHaveBeenCalledTimes(1);
} else {
expect(eventHandler).not.toHaveBeenCalled();
}
},
);
});
});

View File

@ -129,13 +129,16 @@ describe('List Page', () => {
}); });
}); });
describe('connection error', () => { describe.each([
{ error: 'connectionError', errorName: 'connection error' },
{ error: 'invalidPathError', errorName: 'invalid path error' },
])('handling $errorName', ({ error }) => {
const config = { const config = {
characterError: true,
containersErrorImage: 'foo', containersErrorImage: 'foo',
helpPagePath: 'bar', helpPagePath: 'bar',
isGroupPage: false, isGroupPage: false,
}; };
config[error] = true;
it('should show an empty state', () => { it('should show an empty state', () => {
mountComponent({ config }); mountComponent({ config });

View File

@ -111,6 +111,49 @@ RSpec.describe ContainerRegistry::Client do
it_behaves_like 'handling timeouts' it_behaves_like 'handling timeouts'
end end
shared_examples 'handling repository info' do
context 'when the check is successful' do
context 'when using the GitLab container registry' do
before do
stub_registry_info(headers: {
'GitLab-Container-Registry-Version' => '2.9.1-gitlab',
'GitLab-Container-Registry-Features' => 'a,b,c'
})
end
it 'identifies the vendor as "gitlab"' do
expect(subject).to include(vendor: 'gitlab')
end
it 'identifies version and features' do
expect(subject).to include(version: '2.9.1-gitlab', features: %w[a b c])
end
end
context 'when using a third-party container registry' do
before do
stub_registry_info
end
it 'identifies the vendor as "other"' do
expect(subject).to include(vendor: 'other')
end
it 'does not identify version or features' do
expect(subject).to include(version: nil, features: [])
end
end
end
context 'when the check is not successful' do
it 'does not identify vendor, version or features' do
stub_registry_info(status: 500)
expect(subject).to eq({})
end
end
end
describe '#repository_manifest' do describe '#repository_manifest' do
subject { client.repository_manifest('group/test', 'mytag') } subject { client.repository_manifest('group/test', 'mytag') }
@ -316,46 +359,7 @@ RSpec.describe ContainerRegistry::Client do
describe '#registry_info' do describe '#registry_info' do
subject { client.registry_info } subject { client.registry_info }
context 'when the check is successful' do it_behaves_like 'handling repository info'
context 'when using the GitLab container registry' do
before do
stub_registry_info(headers: {
'GitLab-Container-Registry-Version' => '2.9.1-gitlab',
'GitLab-Container-Registry-Features' => 'a,b,c'
})
end
it 'identifies the vendor as "gitlab"' do
expect(subject).to include(vendor: 'gitlab')
end
it 'identifies version and features' do
expect(subject).to include(version: '2.9.1-gitlab', features: %w[a b c])
end
end
context 'when using a third-party container registry' do
before do
stub_registry_info
end
it 'identifies the vendor as "other"' do
expect(subject).to include(vendor: 'other')
end
it 'does not identify version or features' do
expect(subject).to include(version: nil, features: [])
end
end
end
context 'when the check is not successful' do
it 'does not identify vendor, version or features' do
stub_registry_info(status: 500)
expect(subject).to eq({})
end
end
end end
describe '.supports_tag_delete?' do describe '.supports_tag_delete?' do
@ -418,6 +422,16 @@ RSpec.describe ContainerRegistry::Client do
end end
end end
describe '.registry_info' do
subject { described_class.registry_info }
before do
stub_container_registry_config(enabled: true, api_url: registry_api_url, key: 'spec/fixtures/x509_certificate_pk.key')
end
it_behaves_like 'handling repository info'
end
def stub_upload(path, content, digest, status = 200) def stub_upload(path, content, digest, status = 200)
stub_request(:post, "#{registry_api_url}/v2/#{path}/blobs/uploads/") stub_request(:post, "#{registry_api_url}/v2/#{path}/blobs/uploads/")
.with(headers: headers_with_accept_types) .with(headers: headers_with_accept_types)

View File

@ -48,6 +48,19 @@ RSpec.describe API::ContainerRepositories do
expect(response).to match_response_schema('registry/repository') expect(response).to match_response_schema('registry/repository')
end end
context 'with a network error' do
before do
stub_container_registry_network_error(client_method: :repository_tags)
end
it 'returns a matching schema' do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('registry/repository')
end
end
context 'with tags param' do context 'with tags param' do
let(:url) { "/registry/repositories/#{repository.id}?tags=true" } let(:url) { "/registry/repositories/#{repository.id}?tags=true" }
@ -61,6 +74,19 @@ RSpec.describe API::ContainerRepositories do
expect(json_response['id']).to eq(repository.id) expect(json_response['id']).to eq(repository.id)
expect(response.body).to include('tags') expect(response.body).to include('tags')
end end
context 'with a network error' do
before do
stub_container_registry_network_error(client_method: :repository_tags)
end
it 'returns a connection error message' do
subject
expect(response).to have_gitlab_http_status(:service_unavailable)
expect(json_response['message']).to include('We are having trouble connecting to the Container Registry')
end
end
end end
context 'with tags_count param' do context 'with tags_count param' do

View File

@ -153,4 +153,6 @@ RSpec.describe 'container repository details' do
end end
end end
end end
it_behaves_like 'handling graphql network errors with the container registry'
end end

View File

@ -14,11 +14,12 @@ RSpec.describe 'getting container repositories in a group' do
let_it_be(:container_repositories) { [container_repository, container_repositories_delete_scheduled, container_repositories_delete_failed].flatten } let_it_be(:container_repositories) { [container_repository, container_repositories_delete_scheduled, container_repositories_delete_failed].flatten }
let_it_be(:container_expiration_policy) { project.container_expiration_policy } let_it_be(:container_expiration_policy) { project.container_expiration_policy }
let(:excluded_fields) { [] }
let(:container_repositories_fields) do let(:container_repositories_fields) do
<<~GQL <<~GQL
edges { edges {
node { node {
#{all_graphql_fields_for('container_repositories'.classify, max_depth: 1)} #{all_graphql_fields_for('container_repositories'.classify, max_depth: 1, excluded: excluded_fields)}
} }
} }
GQL GQL
@ -152,6 +153,12 @@ RSpec.describe 'getting container repositories in a group' do
end end
end end
it_behaves_like 'handling graphql network errors with the container registry'
it_behaves_like 'not hitting graphql network errors with the container registry' do
let(:excluded_fields) { %w[tags tagsCount] }
end
it 'returns the total count of container repositories' do it 'returns the total count of container repositories' do
subject subject

View File

@ -12,11 +12,12 @@ RSpec.describe 'getting container repositories in a project' do
let_it_be(:container_repositories) { [container_repository, container_repositories_delete_scheduled, container_repositories_delete_failed].flatten } let_it_be(:container_repositories) { [container_repository, container_repositories_delete_scheduled, container_repositories_delete_failed].flatten }
let_it_be(:container_expiration_policy) { project.container_expiration_policy } let_it_be(:container_expiration_policy) { project.container_expiration_policy }
let(:excluded_fields) { %w[pipeline jobs] }
let(:container_repositories_fields) do let(:container_repositories_fields) do
<<~GQL <<~GQL
edges { edges {
node { node {
#{all_graphql_fields_for('container_repositories'.classify, excluded: %w(pipeline jobs))} #{all_graphql_fields_for('container_repositories'.classify, excluded: excluded_fields)}
} }
} }
GQL GQL
@ -151,6 +152,12 @@ RSpec.describe 'getting container repositories in a project' do
end end
end end
it_behaves_like 'handling graphql network errors with the container registry'
it_behaves_like 'not hitting graphql network errors with the container registry' do
let(:excluded_fields) { %w[pipeline jobs tags tagsCount] }
end
it 'returns the total count of container repositories' do it 'returns the total count of container repositories' do
subject subject

View File

@ -20,12 +20,14 @@ RSpec.describe API::GroupContainerRepositories do
end end
let(:api_user) { reporter } let(:api_user) { reporter }
let(:params) { {} }
before do before do
group.add_reporter(reporter) group.add_reporter(reporter)
group.add_guest(guest) group.add_guest(guest)
stub_container_registry_config(enabled: true) stub_container_registry_config(enabled: true)
stub_container_registry_info
root_repository root_repository
test_repository test_repository
@ -35,10 +37,13 @@ RSpec.describe API::GroupContainerRepositories do
let(:url) { "/groups/#{group.id}/registry/repositories" } let(:url) { "/groups/#{group.id}/registry/repositories" }
let(:snowplow_gitlab_standard_context) { { user: api_user, namespace: group } } let(:snowplow_gitlab_standard_context) { { user: api_user, namespace: group } }
subject { get api(url, api_user) } subject { get api(url, api_user), params: params }
it_behaves_like 'rejected container repository access', :guest, :forbidden it_behaves_like 'rejected container repository access', :guest, :forbidden
it_behaves_like 'rejected container repository access', :anonymous, :not_found it_behaves_like 'rejected container repository access', :anonymous, :not_found
it_behaves_like 'handling network errors with the container registry' do
let(:params) { { tags: true } }
end
it_behaves_like 'returns repositories for allowed users', :reporter, 'group' do it_behaves_like 'returns repositories for allowed users', :reporter, 'group' do
let(:object) { group } let(:object) { group }

View File

@ -52,6 +52,7 @@ RSpec.describe API::ProjectContainerRepositories do
test_repository test_repository
stub_container_registry_config(enabled: true) stub_container_registry_config(enabled: true)
stub_container_registry_info
end end
shared_context 'using API user' do shared_context 'using API user' do
@ -105,6 +106,9 @@ RSpec.describe API::ProjectContainerRepositories do
it_behaves_like 'rejected container repository access', :guest, :forbidden unless context == 'using job token' it_behaves_like 'rejected container repository access', :guest, :forbidden unless context == 'using job token'
it_behaves_like 'rejected container repository access', :anonymous, :not_found it_behaves_like 'rejected container repository access', :anonymous, :not_found
it_behaves_like 'a package tracking event', described_class.name, 'list_repositories' it_behaves_like 'a package tracking event', described_class.name, 'list_repositories'
it_behaves_like 'handling network errors with the container registry' do
let(:params) { { tags: true } }
end
it_behaves_like 'returns repositories for allowed users', :reporter, 'project' do it_behaves_like 'returns repositories for allowed users', :reporter, 'project' do
let(:object) { project } let(:object) { project }
@ -154,6 +158,7 @@ RSpec.describe API::ProjectContainerRepositories do
it_behaves_like 'rejected container repository access', :guest, :forbidden unless context == 'using job token' it_behaves_like 'rejected container repository access', :guest, :forbidden unless context == 'using job token'
it_behaves_like 'rejected container repository access', :anonymous, :not_found it_behaves_like 'rejected container repository access', :anonymous, :not_found
it_behaves_like 'handling network errors with the container registry'
context 'for reporter' do context 'for reporter' do
let(:api_user) { reporter } let(:api_user) { reporter }

View File

@ -305,6 +305,18 @@ RSpec.describe API::Repositories do
end end
end end
it 'returns only a part of the repository with path set' do
path = 'bar'
get api("#{route}?path=#{path}", current_user)
expect(response).to have_gitlab_http_status(:ok)
type, params = workhorse_send_data
expect(type).to eq('git-archive')
expect(params['ArchivePath']).to match(/#{project.path}\-[^\.]+\-#{path}\.tar.gz/)
end
it 'rate limits user when thresholds hit' do it 'rate limits user when thresholds hit' do
allow(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).and_return(true) allow(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).and_return(true)

View File

@ -9,6 +9,7 @@ RSpec.describe Groups::Registry::RepositoriesController do
before do before do
stub_container_registry_config(enabled: true) stub_container_registry_config(enabled: true)
stub_container_registry_tags(repository: :any, tags: []) stub_container_registry_tags(repository: :any, tags: [])
stub_container_registry_info
group.add_reporter(user) group.add_reporter(user)
login_as(user) login_as(user)
end end

View File

@ -79,6 +79,18 @@ module StubGitlabCalls
end end
end end
def stub_container_registry_info(info: {})
allow(ContainerRegistry::Client)
.to receive(:registry_info)
.and_return(info)
end
def stub_container_registry_network_error(client_method:)
allow_next_instance_of(ContainerRegistry::Client) do |client|
allow(client).to receive(client_method).and_raise(::Faraday::Error, nil, nil)
end
end
def stub_commonmark_sourcepos_disabled def stub_commonmark_sourcepos_disabled
allow_any_instance_of(Banzai::Filter::MarkdownEngines::CommonMark) allow_any_instance_of(Banzai::Filter::MarkdownEngines::CommonMark)
.to receive(:render_options) .to receive(:render_options)

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
RSpec.shared_examples 'handling feature network errors with the container registry' do
it 'displays the error message' do
visit_container_registry
expect(page).to have_content 'We are having trouble connecting to the Container Registry'
end
end

View File

@ -79,3 +79,40 @@ RSpec.shared_examples 'returns repositories for allowed users' do |user_type, sc
end end
end end
end end
RSpec.shared_examples 'handling network errors with the container registry' do
before do
stub_container_registry_network_error(client_method: :repository_tags)
end
it 'returns a connection error' do
subject
expect(response).to have_gitlab_http_status(:service_unavailable)
expect(json_response['message']).to include('We are having trouble connecting to the Container Registry')
end
end
RSpec.shared_examples 'handling graphql network errors with the container registry' do
before do
stub_container_registry_network_error(client_method: :repository_tags)
end
it 'returns a connection error' do
subject
expect_graphql_errors_to_include('We are having trouble connecting to the Container Registry')
end
end
RSpec.shared_examples 'not hitting graphql network errors with the container registry' do
before do
stub_container_registry_network_error(client_method: :repository_tags)
end
it 'does not return any error' do
subject
expect_graphql_errors_to_be_empty
end
end