Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-04-20 00:09:27 +00:00
parent 3007cf75a9
commit afbf001676
41 changed files with 543 additions and 190 deletions

View File

@ -232,35 +232,40 @@ export const trackTransaction = (transactionDetails) => {
pushEnhancedEcommerceEvent('EECtransactionSuccess', eventData);
};
export const trackAddToCartUsageTab = () => {
export const pushEECproductAddToCartEvent = () => {
if (!isSupported()) {
return;
}
const getStartedButton = document.querySelector('.js-buy-additional-minutes');
getStartedButton.addEventListener('click', () => {
window.dataLayer.push({
event: 'EECproductAddToCart',
ecommerce: {
currencyCode: 'USD',
add: {
products: [
{
name: 'CI/CD Minutes',
id: '0003',
price: '10',
brand: 'GitLab',
category: 'DevOps',
variant: 'add-on',
quantity: 1,
},
],
},
window.dataLayer.push({
event: 'EECproductAddToCart',
ecommerce: {
currencyCode: 'USD',
add: {
products: [
{
name: 'CI/CD Minutes',
id: '0003',
price: '10',
brand: 'GitLab',
category: 'DevOps',
variant: 'add-on',
quantity: 1,
},
],
},
});
},
});
};
export const trackAddToCartUsageTab = () => {
const getStartedButton = document.querySelector('.js-buy-additional-minutes');
if (!getStartedButton) {
return;
}
getStartedButton.addEventListener('click', pushEECproductAddToCartEvent);
};
export const trackCombinedGroupProjectForm = () => {
if (!isSupported()) {
return;

View File

@ -1,13 +1,13 @@
<script>
import { GlTooltipDirective, GlButton } from '@gitlab/ui';
import { GlButton, GlLink, GlTooltip, GlSprintf } from '@gitlab/ui';
export default {
name: 'DeleteButton',
components: {
GlButton,
},
directives: {
GlTooltip: GlTooltipDirective,
GlLink,
GlTooltip,
GlSprintf,
},
props: {
title: {
@ -18,6 +18,11 @@ export default {
type: String,
required: true,
},
tooltipLink: {
type: String,
default: '',
required: false,
},
disabled: {
type: Boolean,
default: false,
@ -29,21 +34,12 @@ export default {
required: false,
},
},
computed: {
tooltipConfiguration() {
return {
disabled: this.tooltipDisabled,
title: this.tooltipTitle,
};
},
},
};
</script>
<template>
<div v-gl-tooltip="tooltipConfiguration">
<div ref="deleteImageButton">
<gl-button
v-gl-tooltip
:disabled="disabled"
:title="title"
:aria-label="title"
@ -52,5 +48,14 @@ export default {
icon="remove"
@click="$emit('delete')"
/>
<gl-tooltip :target="() => $refs.deleteImageButton" :disabled="tooltipDisabled" placement="top">
<gl-sprintf :message="tooltipTitle">
<template #docLink="{ content }">
<gl-link v-if="tooltipLink" :href="tooltipLink" target="_blank">
{{ content }}
</gl-link>
</template>
</gl-sprintf>
</gl-tooltip>
</div>
</template>

View File

@ -8,11 +8,13 @@ import ListItem from '~/vue_shared/components/registry/list_item.vue';
import {
ASYNC_DELETE_IMAGE_ERROR_MESSAGE,
LIST_DELETE_BUTTON_DISABLED,
LIST_DELETE_BUTTON_DISABLED_FOR_MIGRATION,
REMOVE_REPOSITORY_LABEL,
ROW_SCHEDULED_FOR_DELETION,
CLEANUP_TIMED_OUT_ERROR_MESSAGE,
IMAGE_DELETE_SCHEDULED_STATUS,
IMAGE_FAILED_DELETED_STATUS,
IMAGE_MIGRATING_STATE,
ROOT_IMAGE_TEXT,
} from '../../constants/index';
import DeleteButton from '../delete_button.vue';
@ -32,6 +34,7 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
inject: ['config'],
props: {
item: {
type: Object,
@ -44,13 +47,12 @@ export default {
},
},
i18n: {
LIST_DELETE_BUTTON_DISABLED,
REMOVE_REPOSITORY_LABEL,
ROW_SCHEDULED_FOR_DELETION,
},
computed: {
disabledDelete() {
return !this.item.canDelete || this.deleting;
return !this.item.canDelete || this.deleting || this.migrating;
},
id() {
return getIdFromGraphQLId(this.item.id);
@ -58,6 +60,9 @@ export default {
deleting() {
return this.item.status === IMAGE_DELETE_SCHEDULED_STATUS;
},
migrating() {
return this.item.migrationState === IMAGE_MIGRATING_STATE;
},
failedDelete() {
return this.item.status === IMAGE_FAILED_DELETED_STATUS;
},
@ -83,6 +88,11 @@ export default {
routerLinkEvent() {
return this.deleting ? '' : 'click';
},
deleteButtonTooltipTitle() {
return this.migrating
? LIST_DELETE_BUTTON_DISABLED_FOR_MIGRATION
: LIST_DELETE_BUTTON_DISABLED;
},
},
};
</script>
@ -144,8 +154,9 @@ export default {
<delete-button
:title="$options.i18n.REMOVE_REPOSITORY_LABEL"
:disabled="disabledDelete"
:tooltip-disabled="item.canDelete"
:tooltip-title="$options.i18n.LIST_DELETE_BUTTON_DISABLED"
:tooltip-disabled="!disabledDelete"
:tooltip-link="config.containerRegistryImportingHelpPagePath"
:tooltip-title="deleteButtonTooltipTitle"
@delete="$emit('delete', item)"
/>
</template>

View File

@ -14,6 +14,9 @@ export const LIST_INTRO_TEXT = s__(
export const LIST_DELETE_BUTTON_DISABLED = s__(
'ContainerRegistry|Missing or insufficient permission, delete button disabled',
);
export const LIST_DELETE_BUTTON_DISABLED_FOR_MIGRATION = s__(
`ContainerRegistry|Image repository temporarily cannot be marked for deletion. Please try again in a few minutes. %{docLinkStart}More details%{docLinkEnd}`,
);
export const REMOVE_REPOSITORY_LABEL = s__('ContainerRegistry|Remove repository');
export const REMOVE_REPOSITORY_MODAL_TEXT = s__(
'ContainerRegistry|You are about to remove repository %{title}. Once you confirm, this repository will be permanently deleted.',
@ -45,6 +48,7 @@ export const EMPTY_RESULT_MESSAGE = s__(
export const IMAGE_DELETE_SCHEDULED_STATUS = 'DELETE_SCHEDULED';
export const IMAGE_FAILED_DELETED_STATUS = 'DELETE_FAILED';
export const IMAGE_MIGRATING_STATE = 'importing';
export const GRAPHQL_PAGE_SIZE = 10;
export const SORT_FIELDS = [

View File

@ -0,0 +1,30 @@
@import 'mixins_and_variables_and_functions';
.description.work-items-enabled {
ul.task-list {
> li.task-list-item {
padding-inline-start: 2.5rem;
.js-add-task {
svg {
visibility: hidden;
}
&:focus svg {
visibility: visible;
}
}
> input.task-list-item-checkbox {
left: 1.25rem;
}
&:hover,
&:focus-within {
.js-add-task svg {
visibility: visible;
}
}
}
}
}

View File

@ -307,32 +307,3 @@ ul.related-merge-requests > li gl-emoji {
.issuable-header-slide-leave-to {
transform: translateY(-100%);
}
.description.work-items-enabled {
ul.task-list {
> li.task-list-item {
padding-inline-start: 2.5rem;
.js-add-task {
svg {
visibility: hidden;
}
&:focus svg {
visibility: visible;
}
}
> input.task-list-item-checkbox {
left: 1.25rem;
}
&:hover,
&:focus-within {
.js-add-task svg {
visibility: visible;
}
}
}
}
}

View File

@ -23,6 +23,7 @@ query getProjectContainerRepositories(
__typename
nodes {
id
migrationState
name
path
status
@ -57,6 +58,7 @@ query getProjectContainerRepositories(
__typename
nodes {
id
migrationState
name
path
status

View File

@ -9,6 +9,13 @@ module Types
field_class Types::BaseField
edge_type_class Types::BaseEdge
def self.authorize(*args)
raise 'Cannot redefine authorize' if @authorize_args && args.any?
@authorize_args = args.freeze if args.any?
@authorize_args || (superclass.respond_to?(:authorize) ? superclass.authorize : nil)
end
def self.accepts(*types)
@accepts ||= []
@accepts += types

View File

@ -14,6 +14,7 @@ module Types
field :expiration_policy_started_at, Types::TimeType, null: true, description: 'Timestamp when the cleanup done by the expiration policy was started on the container repository.'
field :id, GraphQL::Types::ID, null: false, description: 'ID of the container repository.'
field :location, GraphQL::Types::String, null: false, description: 'URL of the container repository.'
field :migration_state, GraphQL::Types::String, null: false, description: 'Migration state of the container repository.'
field :name, GraphQL::Types::String, null: false, description: 'Name of the container repository.'
field :path, GraphQL::Types::String, null: false, description: 'Path of the container repository.'
field :project, Types::ProjectType, null: false, description: 'Project of the container registry.'

View File

@ -88,6 +88,15 @@ module NamespacesHelper
}.to_json
end
def pipeline_usage_quota_app_data(namespace)
{
namespace_actual_plan_name: namespace.actual_plan_name,
namespace_path: namespace.full_path,
namespace_id: namespace.id,
page_size: page_size
}
end
private
# Many importers create a temporary Group, so use the real

View File

@ -2,6 +2,7 @@
- add_to_breadcrumbs _("Issues"), project_issues_path(@project)
- breadcrumb_title @issue.to_reference
- page_title "#{@issue.title} (#{@issue.to_reference})", _("Issues")
- add_page_specific_style 'page_bundles/issues_show'
= render 'projects/issuable/show', issuable: @issue, api_awards_path: award_emoji_issue_api_path(@issue)
= render 'projects/invite_members_modal', project: @project

View File

@ -12,12 +12,24 @@ module BulkImports
worker_has_external_dependencies!
def perform(entity_id, current_stage = nil)
return if stage_running?(entity_id, current_stage)
if stage_running?(entity_id, current_stage)
logger.info(
structured_payload(
entity_id: entity_id,
current_stage: current_stage,
message: 'Stage running'
)
)
return
end
logger.info(
worker: self.class.name,
entity_id: entity_id,
current_stage: current_stage
structured_payload(
entity_id: entity_id,
current_stage: current_stage,
message: 'Stage starting'
)
)
next_pipeline_trackers_for(entity_id).each do |pipeline_tracker|
@ -29,10 +41,11 @@ module BulkImports
end
rescue StandardError => e
logger.error(
worker: self.class.name,
entity_id: entity_id,
current_stage: current_stage,
error_message: e.message
structured_payload(
entity_id: entity_id,
current_stage: current_stage,
message: e.message
)
)
Gitlab::ErrorTracking.track_exception(e, entity_id: entity_id)

View File

@ -42,10 +42,12 @@ module BulkImports
correlation_id_value: Labkit::Correlation::CorrelationId.current_or_new_id
}
Gitlab::Import::Logger.warn(
attributes.merge(
bulk_import_id: entity.bulk_import.id,
bulk_import_entity_type: entity.source_type
Gitlab::Import::Logger.error(
structured_payload(
attributes.merge(
bulk_import_id: entity.bulk_import.id,
bulk_import_entity_type: entity.source_type
)
)
)

View File

@ -18,18 +18,20 @@ module BulkImports
if pipeline_tracker.present?
logger.info(
worker: self.class.name,
entity_id: pipeline_tracker.entity.id,
pipeline_name: pipeline_tracker.pipeline_name
structured_payload(
entity_id: pipeline_tracker.entity.id,
pipeline_name: pipeline_tracker.pipeline_name
)
)
run(pipeline_tracker)
else
logger.error(
worker: self.class.name,
entity_id: entity_id,
pipeline_tracker_id: pipeline_tracker_id,
message: 'Unstarted pipeline not found'
structured_payload(
entity_id: entity_id,
pipeline_tracker_id: pipeline_tracker_id,
message: 'Unstarted pipeline not found'
)
)
end
@ -63,10 +65,11 @@ module BulkImports
rescue BulkImports::NetworkError => e
if e.retriable?(pipeline_tracker)
logger.error(
worker: self.class.name,
entity_id: pipeline_tracker.entity.id,
pipeline_name: pipeline_tracker.pipeline_name,
message: "Retrying error: #{e.message}"
structured_payload(
entity_id: pipeline_tracker.entity.id,
pipeline_name: pipeline_tracker.pipeline_name,
message: "Retrying error: #{e.message}"
)
)
pipeline_tracker.update!(status_event: 'retry', jid: jid)
@ -83,10 +86,11 @@ module BulkImports
pipeline_tracker.update!(status_event: 'fail_op', jid: jid)
logger.error(
worker: self.class.name,
entity_id: pipeline_tracker.entity.id,
pipeline_name: pipeline_tracker.pipeline_name,
message: exception.message
structured_payload(
entity_id: pipeline_tracker.entity.id,
pipeline_name: pipeline_tracker.pipeline_name,
message: exception.message
)
)
Gitlab::ErrorTracking.track_exception(

View File

@ -272,6 +272,7 @@ module Gitlab
config.assets.precompile << "page_bundles/import.css"
config.assets.precompile << "page_bundles/incident_management_list.css"
config.assets.precompile << "page_bundles/issues_list.css"
config.assets.precompile << "page_bundles/issues_show.css"
config.assets.precompile << "page_bundles/jira_connect.css"
config.assets.precompile << "page_bundles/jira_connect_users.css"
config.assets.precompile << "page_bundles/learn_gitlab.css"

View File

@ -1,5 +0,0 @@
# frozen_string_literal: true
GraphQL::ObjectType.accepts_definitions(authorize: GraphQL::Define.assign_metadata_key(:authorize))
GraphQL::Schema::Object.accepts_definition(:authorize)

View File

@ -66,7 +66,7 @@ Rails.application.routes.draw do
end
Gitlab.ee do
resources :company, only: [:new]
resource :company, only: [:new, :create], controller: 'company'
resources :groups, only: [:new, :create]
resources :projects, only: [:new, :create]
resources :groups_projects, only: [:new, :create] do

View File

@ -4,6 +4,6 @@ classes:
- Ci::NamespaceMirror
feature_categories:
- sharding
description: TODO
description: Mirrors some data from the `main` database into the `ci` database so that we can join directly in a single query
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75621
milestone: '14.6'

View File

@ -4,6 +4,6 @@ classes:
- Ci::ProjectMirror
feature_categories:
- sharding
description: TODO
description: Mirrors some data from the `main` database into the `ci` database so that we can join directly in a single query
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75621
milestone: '14.6'

View File

@ -4,6 +4,6 @@ classes:
- LooseForeignKeys::DeletedRecord
feature_categories:
- sharding
description: TODO
description: Used by the loose foreign keys feature as a queue of parent records whose child records (via foreign keys) need to be deleted/nullified
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/70152
milestone: '14.3'

View File

@ -4,6 +4,6 @@ classes:
- Namespaces::SyncEvent
feature_categories:
- sharding
description: TODO
description: Used as a queue of data that needs to be synchronized between the `ci` and `main` database
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75517
milestone: '14.6'

View File

@ -4,6 +4,6 @@ classes:
- Projects::SyncEvent
feature_categories:
- sharding
description: TODO
description: Used as a queue of data that needs to be synchronized between the `ci` and `main` database
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75517
milestone: '14.6'

View File

@ -0,0 +1,12 @@
# frozen_string_literal: true
class FinalizeTraversalIdsBackgroundMigrations < Gitlab::Database::Migration[1.0]
def up
finalize_background_migration('BackfillNamespaceTraversalIdsRoots')
finalize_background_migration('BackfillNamespaceTraversalIdsChildren')
end
def down
# no-op
end
end

View File

@ -0,0 +1 @@
1d6ed98ad2da7be75e09d853db86905ed1fb1847d387cc6d1980ff5516db06d9

View File

@ -348,13 +348,23 @@ If this occurs, run `remove-repository` again.
### Manually list untracked repositories
> [Introduced](https://gitlab.com/gitlab-org/gitaly/-/merge_requests/3926) in GitLab 14.4.
> - [Introduced](https://gitlab.com/gitlab-org/gitaly/-/merge_requests/3926) in GitLab 14.4.
> - `older-than` option added in GitLab 15.0.
The `list-untracked-repositories` Praefect sub-command lists repositories of the Gitaly Cluster that both:
- Exist for at least one Gitaly storage.
- Aren't tracked in the Praefect database.
Add the `-older-than` option to avoid showing repositories that are the process of being created and for which a record doesn't yet exist in the
Praefect database. Replace <duration> with a time duration (for example, `5s`, `10m`, or `1h`). Defaults to `6h`.
```shell
sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml list-untracked-repositories -older-than <duration>
```
Only repositories with a creation time before the specified duration are considered.
The command outputs:
- Result to `STDOUT` and the command's logs.

View File

@ -9773,6 +9773,7 @@ A container repository.
| <a id="containerrepositoryexpirationpolicystartedat"></a>`expirationPolicyStartedAt` | [`Time`](#time) | Timestamp when the cleanup done by the expiration policy was started on the container repository. |
| <a id="containerrepositoryid"></a>`id` | [`ID!`](#id) | ID of the container repository. |
| <a id="containerrepositorylocation"></a>`location` | [`String!`](#string) | URL of the container repository. |
| <a id="containerrepositorymigrationstate"></a>`migrationState` | [`String!`](#string) | Migration state of the container repository. |
| <a id="containerrepositoryname"></a>`name` | [`String!`](#string) | Name of the container repository. |
| <a id="containerrepositorypath"></a>`path` | [`String!`](#string) | Path of the container repository. |
| <a id="containerrepositoryproject"></a>`project` | [`Project!`](#project) | Project of the container registry. |
@ -9794,6 +9795,7 @@ Details of a container repository.
| <a id="containerrepositorydetailsexpirationpolicystartedat"></a>`expirationPolicyStartedAt` | [`Time`](#time) | Timestamp when the cleanup done by the expiration policy was started on the container repository. |
| <a id="containerrepositorydetailsid"></a>`id` | [`ID!`](#id) | ID of the container repository. |
| <a id="containerrepositorydetailslocation"></a>`location` | [`String!`](#string) | URL of the container repository. |
| <a id="containerrepositorydetailsmigrationstate"></a>`migrationState` | [`String!`](#string) | Migration state of the container repository. |
| <a id="containerrepositorydetailsname"></a>`name` | [`String!`](#string) | Name of the container repository. |
| <a id="containerrepositorydetailspath"></a>`path` | [`String!`](#string) | Path of the container repository. |
| <a id="containerrepositorydetailsproject"></a>`project` | [`Project!`](#project) | Project of the container registry. |

View File

@ -366,8 +366,8 @@ The docs generator code comes from our side giving us more flexibility, like usi
To edit the content, you may need to edit the following:
- The template. You can edit the template at `lib/gitlab/graphql/docs/templates/default.md.haml`.
The actual renderer is at `Gitlab::Graphql::Docs::Renderer`.
- The template. You can edit the template at `tooling/graphql/docs/templates/default.md.haml`.
The actual renderer is at `Tooling::Graphql::Docs::Renderer`.
- The applicable `description` field in the code, which
[Updates machine-readable schema files](#update-machine-readable-schema-files),
which is then used by the `rake` task described earlier.

View File

@ -592,6 +592,7 @@ profile increases as the number of tests increases.
| `FUZZAPI_CONFIG` | [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/276395) in GitLab 13.12, replaced with default `.gitlab/gitlab-api-fuzzing-config.yml`. API Fuzzing configuration file. |
|[`FUZZAPI_PROFILE`](#api-fuzzing-profiles) | Configuration profile to use during testing. Defaults to `Quick-10`. |
|[`FUZZAPI_EXCLUDE_PATHS`](#exclude-paths) | Exclude API URL paths from testing. |
|[`FUZZAPI_EXCLUDE_URLS`](#exclude-urls) | Exclude API URL from testing. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/357195) in GitLab 14.10. |
|[`FUZZAPI_EXCLUDE_PARAMETER_ENV`](#exclude-parameters) | JSON string containing excluded parameters. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/292196) in GitLab 14.10. |
|[`FUZZAPI_EXCLUDE_PARAMETER_FILE`](#exclude-parameters) | Path to a JSON file containing excluded parameters. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/292196) in GitLab 14.10. |
|[`FUZZAPI_OPENAPI`](#openapi-specification) | OpenAPI Specification file or URL. |
@ -1295,6 +1296,65 @@ variables:
The `api-fuzzing-exclude-parameters.json` is a JSON document that follows the structure of [exclude parameters document](#exclude-parameters-using-a-json-document).
### Exclude URLS
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/357195) in GitLab 14.10.
As an alternative to excluding by paths, you can filter by any other component in the URL by using the `FUZZAPI_EXCLUDE_URLS` CI/CD variable. This variable can be set in your `.gitlab-ci.yml` file. The variable can store multiple values, separated by commas (`,`). Each value is a regular expression. Because each entry is a regular expression, an entry such as `.*` excludes all URLs because it is a regular expression that matches everything.
In your job output you can check if any URLs matched any provided regular expression from `FUZZAPI_EXCLUDE_URLS`. Matching operations are listed in the **Excluded Operations** section. Operations listed in the **Excluded Operations** should not be listed in the **Tested Operations** section. For example the following portion of a job output:
```plaintext
2021-05-27 21:51:08 [INF] API Security: --[ Tested Operations ]-------------------------
2021-05-27 21:51:08 [INF] API Security: 201 POST http://target:7777/api/users CREATED
2021-05-27 21:51:08 [INF] API Security: ------------------------------------------------
2021-05-27 21:51:08 [INF] API Security: --[ Excluded Operations ]-----------------------
2021-05-27 21:51:08 [INF] API Security: GET http://target:7777/api/messages
2021-05-27 21:51:08 [INF] API Security: POST http://target:7777/api/messages
2021-05-27 21:51:08 [INF] API Security: ------------------------------------------------
```
NOTE:
Each value in `FUZZAPI_EXCLUDE_URLS` is a regular expression. Characters such as `.` , `*` and `$` among many others have special meanings in [regular expressions](https://en.wikipedia.org/wiki/Regular_expression#Standards).
#### Examples
##### Excluding a URL and child resources
The following example excludes the URL `http://target/api/auth` and its child resources.
```yaml
variables:
FUZZAPI_EXCLUDE_URLS: http://target/api/auth
```
##### Excluding two URLs and allow their child resources
To exclude the URLs `http://target/api/buy` and `http://target/api/sell` but allowing to scan their child resources, for instance: `http://target/api/buy/toy` or `http://target/api/sell/chair`. You could use the value `http://target/api/buy/$,http://target/api/sell/$`. This value is using two regular expressions, each of them separated by a `,` character. Hence, it contains `http://target/api/buy$` and `http://target/api/sell$`. In each regular expression, the trailing `$` character points out where the matching URL should end.
```yaml
variables:
FUZZAPI_EXCLUDE_URLS: http://target/api/buy/$,http://target/api/sell/$
```
##### Excluding two URLs and their child resources
In order to exclude the URLs: `http://target/api/buy` and `http://target/api/sell`, and their child resources. To provide multiple URLs we use the `,` character as follows:
```yaml
variables:
FUZZAPI_EXCLUDE_URLS: http://target/api/buy,http://target/api/sell
```
##### Excluding URL using regular expressions
In order to exclude exactly `https://target/api/v1/user/create` and `https://target/api/v2/user/create` or any other version (`v3`,`v4`, and more). We could use `https://target/api/v.*/user/create$`, in the previous regular expression `.` indicates any character and `*` indicates zero or more times, additionally `$` indicates that the URL should end there.
```yaml
variables:
FUZZAPI_EXCLUDE_URLS: https://target/api/v.*/user/create$
```
### Header Fuzzing
Header fuzzing is disabled by default due to the high number of false positives that occur with many

View File

@ -544,6 +544,7 @@ can be added, removed, and modified by creating a custom configuration.
|[`DAST_API_CONFIG`](#configuration-files) | DAST API configuration file. Defaults to `.gitlab-dast-api.yml`. |
|[`DAST_API_PROFILE`](#configuration-files) | Configuration profile to use during testing. Defaults to `Quick`. |
|[`DAST_API_EXCLUDE_PATHS`](#exclude-paths) | Exclude API URL paths from testing. |
|[`DAST_API_EXCLUDE_URLS`](#exclude-urls) | Exclude API URL from testing. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/357195) in GitLab 14.10. |
|[`DAST_API_EXCLUDE_PARAMETER_ENV`](#exclude-parameters) | JSON string containing excluded parameters. |
|[`DAST_API_EXCLUDE_PARAMETER_FILE`](#exclude-parameters) | Path to a JSON file containing excluded parameters. |
|[`DAST_API_OPENAPI`](#openapi-specification) | OpenAPI specification file or URL. |
@ -1249,6 +1250,65 @@ variables:
The `dast-api-exclude-parameters.json` is a JSON document that follows the structure of [exclude parameters document](#exclude-parameters-using-a-json-document).
### Exclude URLS
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/357195) in GitLab 14.10.
As an alternative to excluding by paths, you can filter by any other component in the URL by using the `DAST_API_EXCLUDE_URLS` CI/CD variable. This variable can be set in your `.gitlab-ci.yml` file. The variable can store multiple values, separated by commas (`,`). Each value is a regular expression. Because each entry is a regular expression, an entry like `.*` will exclude all URLs because it is a regular expression that matches everything.
In your job output you can check if any URLs matched any provided regular expression from `DAST_API_EXCLUDE_URLS`. Matching operations are listed in the **Excluded Operations** section. Operations listed in the **Excluded Operations** should not be listed in the **Tested Operations** section. For example the following portion of a job output:
```plaintext
2021-05-27 21:51:08 [INF] API Security: --[ Tested Operations ]-------------------------
2021-05-27 21:51:08 [INF] API Security: 201 POST http://target:7777/api/users CREATED
2021-05-27 21:51:08 [INF] API Security: ------------------------------------------------
2021-05-27 21:51:08 [INF] API Security: --[ Excluded Operations ]-----------------------
2021-05-27 21:51:08 [INF] API Security: GET http://target:7777/api/messages
2021-05-27 21:51:08 [INF] API Security: POST http://target:7777/api/messages
2021-05-27 21:51:08 [INF] API Security: ------------------------------------------------
```
NOTE:
Each value in `DAST_API_EXCLUDE_URLS` is a regular expression. Characters such as `.` , `*` and `$` among many others have special meanings in [regular expressions](https://en.wikipedia.org/wiki/Regular_expression#Standards).
#### Examples
##### Excluding a URL and child resources
The following example excludes the URL `http://target/api/auth` and its child resources.
```yaml
variables:
DAST_API_EXCLUDE_URLS: http://target/api/auth
```
##### Excluding two URLs and allow their child resources
To exclude the URLs `http://target/api/buy` and `http://target/api/sell` but allowing to scan their child resources, for instance: `http://target/api/buy/toy` or `http://target/api/sell/chair`. You could use the value `http://target/api/buy/$,http://target/api/sell/$`. This value is using two regular expressions, each of them separated by a `,` character. Hence, it contains `http://target/api/buy$` and `http://target/api/sell$`. In each regular expression, the trailing `$` character points out where the matching URL should end.
```yaml
variables:
DAST_API_EXCLUDE_URLS: http://target/api/buy/$,http://target/api/sell/$
```
##### Excluding two URLs and their child resources
In order to exclude the URLs: `http://target/api/buy` and `http://target/api/sell`, and their child resources. To provide multiple URLs we use the `,` character as follows:
```yaml
variables:
DAST_API_EXCLUDE_URLS: http://target/api/buy,http://target/api/sell
```
##### Excluding URL using regular expressions
In order to exclude exactly `https://target/api/v1/user/create` and `https://target/api/v2/user/create` or any other version (`v3`,`v4`, and more). We could use `https://target/api/v.*/user/create$`, in the previous regular expression `.` indicates any character and `*` indicates zero or more times, additionally `$` indicates that the URL should end there.
```yaml
variables:
DAST_API_EXCLUDE_URLS: https://target/api/v.*/user/create$
```
## Running your first scan
When configured correctly, a CI/CD pipeline contains a `dast` stage and an `dast_api` job. The job only fails when an invalid configuration is provided. During normal operation, the job always succeeds even if vulnerabilities are identified during testing.

View File

@ -9773,6 +9773,9 @@ msgstr ""
msgid "ContainerRegistry|Image repository not found"
msgstr ""
msgid "ContainerRegistry|Image repository temporarily cannot be marked for deletion. Please try again in a few minutes. %{docLinkStart}More details%{docLinkEnd}"
msgstr ""
msgid "ContainerRegistry|Image repository will be deleted"
msgstr ""

View File

@ -1,7 +1,7 @@
import { GlButton } from '@gitlab/ui';
import { GlButton, GlTooltip, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import component from '~/packages_and_registries/container_registry/explorer/components/delete_button.vue';
import { LIST_DELETE_BUTTON_DISABLED_FOR_MIGRATION } from '~/packages_and_registries/container_registry/explorer/constants/list';
describe('delete_button', () => {
let wrapper;
@ -12,6 +12,7 @@ describe('delete_button', () => {
};
const findButton = () => wrapper.find(GlButton);
const findTooltip = () => wrapper.find(GlTooltip);
const mountComponent = (props) => {
wrapper = shallowMount(component, {
@ -19,8 +20,9 @@ describe('delete_button', () => {
...defaultProps,
...props,
},
directives: {
GlTooltip: createMockDirective(),
stubs: {
GlTooltip,
GlSprintf,
},
});
};
@ -33,41 +35,50 @@ describe('delete_button', () => {
describe('tooltip', () => {
it('the title is controlled by tooltipTitle prop', () => {
mountComponent();
const tooltip = getBinding(wrapper.element, 'gl-tooltip');
const tooltip = findTooltip();
expect(tooltip).toBeDefined();
expect(tooltip.value.title).toBe(defaultProps.tooltipTitle);
expect(tooltip.text()).toBe(defaultProps.tooltipTitle);
});
it('is disabled when tooltipTitle is disabled', () => {
mountComponent({ tooltipDisabled: true });
const tooltip = getBinding(wrapper.element, 'gl-tooltip');
expect(tooltip.value.disabled).toBe(true);
expect(findTooltip().props('disabled')).toBe(true);
});
describe('button', () => {
it('exists', () => {
mountComponent();
expect(findButton().exists()).toBe(true);
it('works with a link', () => {
mountComponent({
tooltipTitle: LIST_DELETE_BUTTON_DISABLED_FOR_MIGRATION,
tooltipLink: 'foo',
});
expect(findTooltip().text()).toMatchInterpolatedText(
LIST_DELETE_BUTTON_DISABLED_FOR_MIGRATION,
);
});
});
it('has the correct props/attributes bound', () => {
mountComponent({ disabled: true });
expect(findButton().attributes()).toMatchObject({
'aria-label': 'Foo title',
icon: 'remove',
title: 'Foo title',
variant: 'danger',
disabled: 'true',
category: 'secondary',
});
});
describe('button', () => {
it('exists', () => {
mountComponent();
expect(findButton().exists()).toBe(true);
});
it('emits a delete event', () => {
mountComponent();
expect(wrapper.emitted('delete')).toEqual(undefined);
findButton().vm.$emit('click');
expect(wrapper.emitted('delete')).toEqual([[]]);
it('has the correct props/attributes bound', () => {
mountComponent({ disabled: true });
expect(findButton().attributes()).toMatchObject({
'aria-label': 'Foo title',
icon: 'remove',
title: 'Foo title',
variant: 'danger',
disabled: 'true',
category: 'secondary',
});
});
it('emits a delete event', () => {
mountComponent();
expect(wrapper.emitted('delete')).toEqual(undefined);
findButton().vm.$emit('click');
expect(wrapper.emitted('delete')).toEqual([[]]);
});
});
});

View File

@ -10,6 +10,7 @@ import {
LIST_DELETE_BUTTON_DISABLED,
REMOVE_REPOSITORY_LABEL,
IMAGE_DELETE_SCHEDULED_STATUS,
IMAGE_MIGRATING_STATE,
SCHEDULED_STATUS,
ROOT_IMAGE_TEXT,
} from '~/packages_and_registries/container_registry/explorer/constants';
@ -41,6 +42,9 @@ describe('Image List Row', () => {
item,
...props,
},
provide: {
config: {},
},
directives: {
GlTooltip: createMockDirective(),
},
@ -178,6 +182,12 @@ describe('Image List Row', () => {
expect(findDeleteBtn().props('disabled')).toBe(state);
},
);
it('is disabled when migrationState is importing', () => {
mountComponent({ item: { ...item, migrationState: IMAGE_MIGRATING_STATE } });
expect(findDeleteBtn().props('disabled')).toBe(true);
});
});
describe('tags count', () => {

View File

@ -5,6 +5,7 @@ export const imagesListResponse = [
name: 'rails-12009',
path: 'gitlab-org/gitlab-test/rails-12009',
status: null,
migrationState: 'default',
location: '0.0.0.0:5000/gitlab-org/gitlab-test/rails-12009',
canDelete: true,
createdAt: '2020-11-03T13:29:21Z',
@ -17,6 +18,7 @@ export const imagesListResponse = [
name: 'rails-20572',
path: 'gitlab-org/gitlab-test/rails-20572',
status: null,
migrationState: 'default',
location: '0.0.0.0:5000/gitlab-org/gitlab-test/rails-20572',
canDelete: true,
createdAt: '2020-09-21T06:57:43Z',

View File

@ -428,5 +428,25 @@ RSpec.describe Types::BaseObject do
expect(result.dig('data', 'users', 'nodes'))
.to contain_exactly({ 'name' => active_users.first.name })
end
describe '.authorize' do
let_it_be(:read_only_type) do
Class.new(described_class) do
authorize :read_only
end
end
let_it_be(:inherited_read_only_type) { Class.new(read_only_type) }
it 'keeps track of the specified value' do
expect(described_class.authorize).to be_nil
expect(read_only_type.authorize).to match_array [:read_only]
expect(inherited_read_only_type.authorize).to match_array [:read_only]
end
it 'can not redefine the authorize value' do
expect { read_only_type.authorize(:write_only) }.to raise_error('Cannot redefine authorize')
end
end
end
end

View File

@ -3,7 +3,9 @@
require 'spec_helper'
RSpec.describe GitlabSchema.types['ContainerRepositoryDetails'] do
fields = %i[id name path location created_at updated_at expiration_policy_started_at status tags_count can_delete expiration_policy_cleanup_status tags size project]
fields = %i[id name path location created_at updated_at expiration_policy_started_at
status tags_count can_delete expiration_policy_cleanup_status tags size
project migration_state]
it { expect(described_class.graphql_name).to eq('ContainerRepositoryDetails') }

View File

@ -3,7 +3,9 @@
require 'spec_helper'
RSpec.describe GitlabSchema.types['ContainerRepository'] do
fields = %i[id name path location created_at updated_at expiration_policy_started_at status tags_count can_delete expiration_policy_cleanup_status project]
fields = %i[id name path location created_at updated_at expiration_policy_started_at
status tags_count can_delete expiration_policy_cleanup_status project
migration_state]
it { expect(described_class.graphql_name).to eq('ContainerRepository') }

View File

@ -268,4 +268,15 @@ RSpec.describe NamespacesHelper do
end
end
end
describe '#pipeline_usage_quota_app_data' do
it 'returns a hash with necessary data for the frontend' do
expect(helper.pipeline_usage_quota_app_data(user_group)).to eql({
namespace_actual_plan_name: user_group.actual_plan_name,
namespace_path: user_group.full_path,
namespace_id: user_group.id,
page_size: Kaminari.config.default_per_page
})
end
end
end

View File

@ -0,0 +1,60 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!('finalize_traversal_ids_background_migrations')
RSpec.describe FinalizeTraversalIdsBackgroundMigrations, :migration do
shared_context 'incomplete background migration' do
before do
# Jobs enqueued in Sidekiq.
Sidekiq::Testing.disable! do
BackgroundMigrationWorker.perform_in(10, job_class_name, [1, 2, 100])
BackgroundMigrationWorker.perform_in(20, job_class_name, [3, 4, 100])
end
# Jobs tracked in the database.
# table(:background_migration_jobs).create!(
Gitlab::Database::BackgroundMigrationJob.create!(
class_name: job_class_name,
arguments: [5, 6, 100],
status: Gitlab::Database::BackgroundMigrationJob.statuses['pending']
)
# table(:background_migration_jobs).create!(
Gitlab::Database::BackgroundMigrationJob.create!(
class_name: job_class_name,
arguments: [7, 8, 100],
status: Gitlab::Database::BackgroundMigrationJob.statuses['succeeded']
)
end
end
context 'BackfillNamespaceTraversalIdsRoots background migration' do
let(:job_class_name) { 'BackfillNamespaceTraversalIdsRoots' }
include_context 'incomplete background migration'
before do
migrate!
end
it_behaves_like(
'finalized tracked background migration',
Gitlab::BackgroundMigration::BackfillNamespaceTraversalIdsRoots
)
end
context 'BackfillNamespaceTraversalIdsChildren background migration' do
let(:job_class_name) { 'BackfillNamespaceTraversalIdsChildren' }
include_context 'incomplete background migration'
before do
migrate!
end
it_behaves_like(
'finalized tracked background migration',
Gitlab::BackgroundMigration::BackfillNamespaceTraversalIdsChildren
)
end
end

View File

@ -36,9 +36,11 @@ RSpec.describe BulkImports::EntityWorker do
expect(logger)
.to receive(:info).twice
.with(
worker: described_class.name,
entity_id: entity.id,
current_stage: nil
hash_including(
'entity_id' => entity.id,
'current_stage' => nil,
'message' => 'Stage starting'
)
)
end
@ -58,24 +60,26 @@ RSpec.describe BulkImports::EntityWorker do
expect(BulkImports::PipelineWorker)
.to receive(:perform_async)
.and_raise(exception)
.and_raise(exception)
expect_next_instance_of(Gitlab::Import::Logger) do |logger|
expect(logger)
.to receive(:info).twice
.with(
worker: described_class.name,
entity_id: entity.id,
current_stage: nil
hash_including(
'entity_id' => entity.id,
'current_stage' => nil
)
)
expect(logger)
.to receive(:error)
.with(
worker: described_class.name,
entity_id: entity.id,
current_stage: nil,
error_message: 'Error!'
hash_including(
'entity_id' => entity.id,
'current_stage' => nil,
'message' => 'Error!'
)
)
end
@ -90,6 +94,18 @@ RSpec.describe BulkImports::EntityWorker do
let(:job_args) { [entity.id, 0] }
it 'do not enqueue a new pipeline job if the current stage still running' do
expect_next_instance_of(Gitlab::Import::Logger) do |logger|
expect(logger)
.to receive(:info).twice
.with(
hash_including(
'entity_id' => entity.id,
'current_stage' => 0,
'message' => 'Stage running'
)
)
end
expect(BulkImports::PipelineWorker)
.not_to receive(:perform_async)
@ -110,9 +126,10 @@ RSpec.describe BulkImports::EntityWorker do
expect(logger)
.to receive(:info).twice
.with(
worker: described_class.name,
entity_id: entity.id,
current_stage: 0
hash_including(
'entity_id' => entity.id,
'current_stage' => 0
)
)
end

View File

@ -35,14 +35,16 @@ RSpec.describe BulkImports::ExportRequestWorker do
expect(client).to receive(:post).and_raise(BulkImports::NetworkError, 'Export error').twice
end
expect(Gitlab::Import::Logger).to receive(:warn).with(
bulk_import_entity_id: entity.id,
pipeline_class: 'ExportRequestWorker',
exception_class: 'BulkImports::NetworkError',
exception_message: 'Export error',
correlation_id_value: anything,
bulk_import_id: bulk_import.id,
bulk_import_entity_type: entity.source_type
expect(Gitlab::Import::Logger).to receive(:error).with(
hash_including(
'bulk_import_entity_id' => entity.id,
'pipeline_class' => 'ExportRequestWorker',
'exception_class' => 'BulkImports::NetworkError',
'exception_message' => 'Export error',
'correlation_id_value' => anything,
'bulk_import_id' => bulk_import.id,
'bulk_import_entity_type' => entity.source_type
)
).twice
perform_multiple(job_args)

View File

@ -34,9 +34,10 @@ RSpec.describe BulkImports::PipelineWorker do
expect(logger)
.to receive(:info)
.with(
worker: described_class.name,
pipeline_name: 'FakePipeline',
entity_id: entity.id
hash_including(
'pipeline_name' => 'FakePipeline',
'entity_id' => entity.id
)
)
end
@ -44,7 +45,7 @@ RSpec.describe BulkImports::PipelineWorker do
.to receive(:perform_async)
.with(entity.id, pipeline_tracker.stage)
expect(subject).to receive(:jid).and_return('jid')
allow(subject).to receive(:jid).and_return('jid')
subject.perform(pipeline_tracker.id, pipeline_tracker.stage, entity.id)
@ -79,10 +80,11 @@ RSpec.describe BulkImports::PipelineWorker do
expect(logger)
.to receive(:error)
.with(
worker: described_class.name,
pipeline_tracker_id: pipeline_tracker.id,
entity_id: entity.id,
message: 'Unstarted pipeline not found'
hash_including(
'pipeline_tracker_id' => pipeline_tracker.id,
'entity_id' => entity.id,
'message' => 'Unstarted pipeline not found'
)
)
end
@ -107,10 +109,11 @@ RSpec.describe BulkImports::PipelineWorker do
expect(logger)
.to receive(:error)
.with(
worker: described_class.name,
pipeline_name: 'InexistentPipeline',
entity_id: entity.id,
message: "'InexistentPipeline' is not a valid BulkImport Pipeline"
hash_including(
'pipeline_name' => 'InexistentPipeline',
'entity_id' => entity.id,
'message' => "'InexistentPipeline' is not a valid BulkImport Pipeline"
)
)
end
@ -126,7 +129,7 @@ RSpec.describe BulkImports::PipelineWorker do
.to receive(:perform_async)
.with(entity.id, pipeline_tracker.stage)
expect(subject).to receive(:jid).and_return('jid')
allow(subject).to receive(:jid).and_return('jid')
subject.perform(pipeline_tracker.id, pipeline_tracker.stage, entity.id)
@ -151,10 +154,11 @@ RSpec.describe BulkImports::PipelineWorker do
expect(logger)
.to receive(:error)
.with(
worker: described_class.name,
pipeline_name: 'Pipeline',
entity_id: entity.id,
message: 'Failed entity status'
hash_including(
'pipeline_name' => 'Pipeline',
'entity_id' => entity.id,
'message' => 'Failed entity status'
)
)
end
@ -183,7 +187,7 @@ RSpec.describe BulkImports::PipelineWorker do
.and_raise(exception)
end
expect(subject).to receive(:jid).and_return('jid').twice
allow(subject).to receive(:jid).and_return('jid')
expect_any_instance_of(BulkImports::Tracker) do |tracker|
expect(tracker).to receive(:retry).and_call_original
@ -193,9 +197,10 @@ RSpec.describe BulkImports::PipelineWorker do
expect(logger)
.to receive(:info)
.with(
worker: described_class.name,
pipeline_name: 'FakePipeline',
entity_id: entity.id
hash_including(
'pipeline_name' => 'FakePipeline',
'entity_id' => entity.id
)
)
end
@ -292,10 +297,11 @@ RSpec.describe BulkImports::PipelineWorker do
expect(logger)
.to receive(:error)
.with(
worker: described_class.name,
pipeline_name: 'NdjsonPipeline',
entity_id: entity.id,
message: 'Pipeline timeout'
hash_including(
'pipeline_name' => 'NdjsonPipeline',
'entity_id' => entity.id,
'message' => 'Pipeline timeout'
)
)
end
@ -318,10 +324,11 @@ RSpec.describe BulkImports::PipelineWorker do
expect(logger)
.to receive(:error)
.with(
worker: described_class.name,
pipeline_name: 'NdjsonPipeline',
entity_id: entity.id,
message: 'Error!'
hash_including(
'pipeline_name' => 'NdjsonPipeline',
'entity_id' => entity.id,
'message' => 'Error!'
)
)
end