Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-05-07 06:10:43 +00:00
parent 1707659118
commit a158bebe03
39 changed files with 552 additions and 242 deletions

View File

@ -2,6 +2,11 @@ import axios from '~/lib/utils/axios_utils';
import { reportToSentry } from '../../utils';
export const reportPerformance = (path, stats) => {
// FIXME: https://gitlab.com/gitlab-org/gitlab/-/issues/330245
if (!path) {
return;
}
axios.post(path, stats).catch((err) => {
reportToSentry('links_inner_perf', `error: ${err}`);
});

View File

@ -25,6 +25,16 @@ module Mutations
required: false,
description: copy_field_description(Types::Namespace::PackageSettingsType, :maven_duplicate_exception_regex)
argument :generic_duplicates_allowed,
GraphQL::BOOLEAN_TYPE,
required: false,
description: copy_field_description(Types::Namespace::PackageSettingsType, :generic_duplicates_allowed)
argument :generic_duplicate_exception_regex,
Types::UntrustedRegexp,
required: false,
description: copy_field_description(Types::Namespace::PackageSettingsType, :generic_duplicate_exception_regex)
field :package_settings,
Types::Namespace::PackageSettingsType,
null: true,

View File

@ -10,5 +10,7 @@ module Types
field :maven_duplicates_allowed, GraphQL::BOOLEAN_TYPE, null: false, description: 'Indicates whether duplicate Maven packages are allowed for this namespace.'
field :maven_duplicate_exception_regex, Types::UntrustedRegexp, null: true, description: 'When maven_duplicates_allowed is false, you can publish duplicate packages with names that match this regex. Otherwise, this setting has no effect.'
field :generic_duplicates_allowed, GraphQL::BOOLEAN_TYPE, null: false, description: 'Indicates whether duplicate generic packages are allowed for this namespace.'
field :generic_duplicate_exception_regex, Types::UntrustedRegexp, null: true, description: 'When generic_duplicates_allowed is false, you can publish duplicate packages with names that match this regex. Otherwise, this setting has no effect.'
end
end

View File

@ -6,13 +6,15 @@ class Namespace::PackageSetting < ApplicationRecord
PackageSettingNotImplemented = Class.new(StandardError)
PACKAGES_WITH_SETTINGS = %w[maven].freeze
PACKAGES_WITH_SETTINGS = %w[maven generic].freeze
belongs_to :namespace, inverse_of: :package_setting_relation
validates :namespace, presence: true
validates :maven_duplicates_allowed, inclusion: { in: [true, false] }
validates :maven_duplicate_exception_regex, untrusted_regexp: true, length: { maximum: 255 }
validates :generic_duplicates_allowed, inclusion: { in: [true, false] }
validates :generic_duplicate_exception_regex, untrusted_regexp: true, length: { maximum: 255 }
class << self
def duplicates_allowed?(package)

View File

@ -1,5 +1,7 @@
# frozen_string_literal: true
module Packages
DuplicatePackageError = Class.new(StandardError)
def self.table_name_prefix
'packages_'
end

View File

@ -995,7 +995,13 @@ class Repository
def search_files_by_wildcard_path(path, ref = 'HEAD')
# We need to use RE2 to match Gitaly's regexp engine
regexp_string = RE2::Regexp.escape(path).gsub('\*', '.*?')
regexp_string = RE2::Regexp.escape(path)
anything = '.*?'
anything_but_not_slash = '([^\/])*?'
regexp_string.gsub!('\*\*', anything)
regexp_string.gsub!('\*', anything_but_not_slash)
raw_repository.search_files_by_regexp("^#{regexp_string}$", ref)
end

View File

@ -5,7 +5,10 @@ module Namespaces
class UpdateService < BaseContainerService
include Gitlab::Utils::StrongMemoize
ALLOWED_ATTRIBUTES = %i[maven_duplicates_allowed maven_duplicate_exception_regex].freeze
ALLOWED_ATTRIBUTES = %i[maven_duplicates_allowed
maven_duplicate_exception_regex
generic_duplicates_allowed
generic_duplicate_exception_regex].freeze
def execute
return ServiceResponse.error(message: 'Access Denied', http_status: 403) unless allowed?

View File

@ -23,6 +23,10 @@ module Packages
.new(project, current_user, package_params)
.execute
unless Namespace::PackageSetting.duplicates_allowed?(package)
raise ::Packages::DuplicatePackageError if target_file_is_duplicate?(package)
end
package.update_column(:status, params[:status]) if params[:status] && params[:status] != package.status
package.build_infos.safe_find_or_create_by!(pipeline: params[:build].pipeline) if params[:build].present?
@ -40,6 +44,10 @@ module Packages
::Packages::CreatePackageFileService.new(package, file_params).execute
end
def target_file_is_duplicate?(package)
package.package_files.with_file_name(params[:file_name]).exists?
end
end
end
end

View File

@ -8,7 +8,7 @@
= link_to gitlab_snippet_path(snippet) do
= snippet.title
%ul.controls
%ul.controls{ data: { qa_selector: 'snippet_file_count_content', qa_snippet_files: snippet.statistics&.file_count } }
%li
= snippet_file_count(snippet)
%li
@ -16,7 +16,7 @@
= sprite_icon('comments', css_class: 'gl-vertical-align-text-bottom')
= notes_count
%li
%span.sr-only
%span.sr-only{ data: { qa_selector: 'snippet_visibility_content', qa_snippet_visibility: visibility_level_label(snippet.visibility_level) } }
= visibility_level_label(snippet.visibility_level)
= visibility_level_icon(snippet.visibility_level)

View File

@ -0,0 +1,5 @@
---
title: Add setting to allow or disallow duplicates for generic packages
merge_request: 60664
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Add kubernetes_agent_proxy_request to usage ping
merge_request: 60978
author:
type: changed

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
class AddGenericPackageDuplicateSettingsToNamespacePackageSettings < ActiveRecord::Migration[6.0]
# rubocop:disable Migration/AddLimitToTextColumns
# limit is added in 20210429193106_add_text_limit_to_namespace_package_settings_generic_duplicate_exception_regex
def change
add_column :namespace_package_settings, :generic_duplicates_allowed, :boolean, null: false, default: true
add_column :namespace_package_settings, :generic_duplicate_exception_regex, :text, null: false, default: ''
end
# rubocop:enable Migration/AddLimitToTextColumns
end

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
class AddTextLimitToNamespacePackageSettingsGenericDuplicateExceptionRegex < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
def up
add_text_limit :namespace_package_settings, :generic_duplicate_exception_regex, 255
end
def down
remove_text_limit :namespace_package_settings, :generic_duplicate_exception_regex
end
end

View File

@ -0,0 +1 @@
c2b5ad6786e1c71ccff391b03fcd0635dfd42d69484443291a692cef9f3ffda5

View File

@ -0,0 +1 @@
e0898e4e439cde4e3b84808e7505490fe956cf17922f5c779b3384997d36cafd

View File

@ -14798,6 +14798,9 @@ CREATE TABLE namespace_package_settings (
namespace_id bigint NOT NULL,
maven_duplicates_allowed boolean DEFAULT true NOT NULL,
maven_duplicate_exception_regex text DEFAULT ''::text NOT NULL,
generic_duplicates_allowed boolean DEFAULT true NOT NULL,
generic_duplicate_exception_regex text DEFAULT ''::text NOT NULL,
CONSTRAINT check_31340211b1 CHECK ((char_length(generic_duplicate_exception_regex) <= 255)),
CONSTRAINT check_d63274b2b6 CHECK ((char_length(maven_duplicate_exception_regex) <= 255))
);

View File

@ -3911,6 +3911,8 @@ Input type: `UpdateNamespacePackageSettingsInput`
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationupdatenamespacepackagesettingsclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationupdatenamespacepackagesettingsgenericduplicateexceptionregex"></a>`genericDuplicateExceptionRegex` | [`UntrustedRegexp`](#untrustedregexp) | When generic_duplicates_allowed is false, you can publish duplicate packages with names that match this regex. Otherwise, this setting has no effect. |
| <a id="mutationupdatenamespacepackagesettingsgenericduplicatesallowed"></a>`genericDuplicatesAllowed` | [`Boolean`](#boolean) | Indicates whether duplicate generic packages are allowed for this namespace. |
| <a id="mutationupdatenamespacepackagesettingsmavenduplicateexceptionregex"></a>`mavenDuplicateExceptionRegex` | [`UntrustedRegexp`](#untrustedregexp) | When maven_duplicates_allowed is false, you can publish duplicate packages with names that match this regex. Otherwise, this setting has no effect. |
| <a id="mutationupdatenamespacepackagesettingsmavenduplicatesallowed"></a>`mavenDuplicatesAllowed` | [`Boolean`](#boolean) | Indicates whether duplicate Maven packages are allowed for this namespace. |
| <a id="mutationupdatenamespacepackagesettingsnamespacepath"></a>`namespacePath` | [`ID!`](#id) | The namespace path where the namespace package setting is located. |
@ -10488,6 +10490,8 @@ Namespace-level Package Registry settings.
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="packagesettingsgenericduplicateexceptionregex"></a>`genericDuplicateExceptionRegex` | [`UntrustedRegexp`](#untrustedregexp) | When generic_duplicates_allowed is false, you can publish duplicate packages with names that match this regex. Otherwise, this setting has no effect. |
| <a id="packagesettingsgenericduplicatesallowed"></a>`genericDuplicatesAllowed` | [`Boolean!`](#boolean) | Indicates whether duplicate generic packages are allowed for this namespace. |
| <a id="packagesettingsmavenduplicateexceptionregex"></a>`mavenDuplicateExceptionRegex` | [`UntrustedRegexp`](#untrustedregexp) | When maven_duplicates_allowed is false, you can publish duplicate packages with names that match this regex. Otherwise, this setting has no effect. |
| <a id="packagesettingsmavenduplicatesallowed"></a>`mavenDuplicatesAllowed` | [`Boolean!`](#boolean) | Indicates whether duplicate Maven packages are allowed for this namespace. |

View File

@ -487,7 +487,7 @@ Use local includes instead of symbolic links.
> - It's not recommended for production use.
> - To use it in GitLab self-managed instances, ask a GitLab administrator to enable it. **(CORE ONLY)**
You can use wildcard paths (`*`) with `include:local`.
You can use wildcard paths (`*` and `**`) with `include:local`.
Example:
@ -495,7 +495,19 @@ Example:
include: 'configs/*.yml'
```
When the pipeline runs, it adds all `.yml` files in the `configs` folder into the pipeline configuration.
When the pipeline runs, GitLab:
- Adds all `.yml` files in the `configs` directory into the pipeline configuration.
- Does not add `.yml` files in subfolders of the `configs` directory. To allow this,
add the following configuration:
```yaml
# This matches all `.yml` files in `configs` and any subfolder in it.
include: 'configs/**.yml'
# This matches all `.yml` files only in subfolders of `configs`.
include: 'configs/**/*.yml'
```
The wildcard file paths feature is under development and not ready for production use. It is
deployed behind a feature flag that is **disabled by default**.

View File

@ -453,7 +453,8 @@ metric counters.
| Attribute | Type | Required | Description |
|:----------|:-------|:---------|:------------|
| `gitops_sync_count` | integer| yes | The number to increase the `gitops_sync_count` counter by |
| `gitops_sync_count` | integer| no | The number to increase the `gitops_sync_count` counter by |
| `k8s_api_proxy_request_count` | integer| no | The number to increase the `k8s_api_proxy_request_count` counter by |
```plaintext
POST /internal/kubernetes/usage_metrics

View File

@ -2902,6 +2902,18 @@ Status: `data_available`
Tiers: `premium`, `ultimate`
### `counts.kubernetes_agent_k8s_api_proxy_request`
Count of Kubernetes API proxy requests
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_all/20210505015532_kubernetes_agent_k8s_api_proxy_request.yml)
Group: `group::configure`
Status: `implemented`
Tiers: `premium`, `ultimate`
### `counts.kubernetes_agents`
Count of Kubernetes agents

View File

@ -5,139 +5,134 @@ info: To determine the technical writer assigned to the Stage/Group associated w
type: howto
---
# Numerous undo possibilities in Git **(FREE)**
# Undo possibilities in Git **(FREE)**
This tutorial shows you different ways of undoing your work in Git.
We assume you have a basic working knowledge of Git. Check the GitLab
[Git documentation](../index.md) for reference.
[Nothing in Git is deleted](https://git-scm.com/book/en/v2/Git-Internals-Maintenance-and-Data-Recovery),
so when you work in Git, you can undo your work.
We only provide some general information about the commands to get you started.
For more advanced examples, refer to the [Git book](https://git-scm.com/book/en/v2).
All version control systems have options for undoing work. However,
because of the de-centralized nature of Git, these options are multiplied.
The actions you take are based on the
[stage of development](https://git-scm.com/book/en/v2/Git-Basics-Recording-Changes-to-the-Repository)
you are in.
A few different techniques exist to undo your changes, based on the stage
of the change in your current development. Remember that
[nothing in Git is really deleted](https://git-scm.com/book/en/v2/Git-Internals-Maintenance-and-Data-Recovery).
Until Git cleans detached commits - commits that cannot be accessed by branch or tag -
you can view them with `git reflog` command, and access them with direct commit ID.
Read more about [redoing the undo](#redoing-the-undo) in the section below.
For more information about working with Git and GitLab:
> For more information about working with Git and GitLab:
>
> - <i class="fa fa-youtube-play youtube" aria-hidden="true"></i>&nbsp;Learn why [North Western Mutual chose GitLab](https://youtu.be/kPNMyxKRRoM) for their Enterprise source code management.
> - Learn how to [get started with Git](https://about.gitlab.com/resources/whitepaper-moving-to-git/).
- <i class="fa fa-youtube-play youtube" aria-hidden="true"></i>&nbsp;Learn why [North Western Mutual chose GitLab](https://youtu.be/kPNMyxKRRoM) for their enterprise source code management.
- Learn how to [get started with Git](https://about.gitlab.com/resources/whitepaper-moving-to-git/).
- For more advanced examples, refer to the [Git book](https://git-scm.com/book/en/v2).
## Introduction
## When you can undo changes
This guide is organized depending on the [stage of development](https://git-scm.com/book/en/v2/Git-Basics-Recording-Changes-to-the-Repository):
In the standard Git workflow:
- Where do you want to undo your changes from?
- Were they shared with other developers?
1. You create or edit a file. It starts in the unstaged state. If it's new, it is not yet tracked by Git.
1. You add the file to your local repository (`git add`), which puts the file into the staged state.
1. You commit the file to your local repository (`git commit`).
1. You can then share the file with other developers, by committing to a remote repository (`git push`).
Because Git tracks changes, a created or edited file is in the unstaged state
(if created it is untracked by Git). After you add it to a repository (`git add`) you put
a file into the **staged** state, which is then committed (`git commit`) to your
local repository. After that, file can be shared with other developers (`git push`).
This tutorial covers:
You can undo changes at any point in this workflow:
- [Undo local changes](#undo-local-changes) which were not pushed to a remote repository:
- Before you commit, in both unstaged and staged state.
- After you committed.
- Undo changes after they are pushed to a remote repository:
- [Without history modification](#undo-remote-changes-without-changing-history) (preferred way).
- [With history modification](#undo-remote-changes-with-modifying-history) (requires
- [When you're working locally](#undo-local-changes) and haven't yet pushed to a remote repository.
- When you have already pushed to a remote repository and you want to:
- [Keep the history intact](#undo-remote-changes-without-changing-history) (preferred).
- [Change the history](#undo-remote-changes-with-modifying-history) (requires
coordination with team and force pushes).
- [Use cases when modifying history is generally acceptable](#where-modifying-history-is-generally-acceptable).
- [How to modify history](#how-modifying-history-is-done).
- [How to remove sensitive information from repository](#deleting-sensitive-information-from-commits).
### Branching strategy
[Git](https://git-scm.com/) is a de-centralized version control system. Beside regular
versioning of the whole repository, it has possibilities to exchange changes
with other repositories.
To avoid chaos with
[multiple sources of truth](https://git-scm.com/about/distributed), various
development workflows have to be followed. It depends on your internal
workflow how certain changes or commits can be undone or changed.
[GitLab Flow](https://about.gitlab.com/topics/version-control/what-is-gitlab-flow/) provides a good
balance between developers clashing with each other while
developing the same feature and cooperating seamlessly. However, it does not enable
joined development of the same feature by multiple developers by default.
When multiple developers develop the same feature on the same branch, clashing
with every synchronization is unavoidable. However, a proper or chosen Git Workflow
prevents lost or out-of-sync data when the feature is complete.
You can also
read through this blog post on [Git Tips & Tricks](https://about.gitlab.com/blog/2016/12/08/git-tips-and-tricks/)
to learn how to do things in Git.
## Undo local changes
Until you push your changes to any remote repository, they only affect you.
That broadens your options on how to handle undoing them. Still, local changes
can be on various stages and each stage has a different approach on how to tackle them.
Until you push your changes to a remote repository, changes
you make in Git are only in your local development environment.
### Unstaged local changes (before you commit)
### Undo unstaged local changes before you commit
When a change is made, but not added to the staged tree, Git
proposes a solution to discard changes to the file.
When you make a change, but have not yet staged it, you can undo your work.
Suppose you edited a file to change the content using your favorite editor:
1. Confirm that the file is unstaged (that you did not use `git add <file>`) by running `git status`:
```shell
vim <file>
```
```shell
$ git status
On branch main
Your branch is up-to-date with 'origin/main'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
Because you did not `git add <file>` to staging, it should be under unstaged files (or
untracked if file was created). You can confirm that with:
modified: <file>
no changes added to commit (use "git add" and/or "git commit -a")
```
```shell
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
1. Choose an option and undo your changes:
modified: <file>
no changes added to commit (use "git add" and/or "git commit -a")
```
- To overwrite local changes:
At this point there are 3 options to undo the local changes you have:
```shell
git checkout -- <file>
```
- Discard all local changes, but save them for possible re-use [later](#quickly-save-local-changes):
- To save local changes so you can [re-use them later](#quickly-save-local-changes):
```shell
git stash
```
```shell
git stash
```
- Discarding local changes (permanently) to a file:
- To discard local changes to all files, permanently:
```shell
git checkout -- <file>
```
```shell
git reset --hard
```
- Discard all local changes to all files permanently:
### Undo staged local changes before you commit
```shell
git reset --hard
```
If you added a file to staging, you can undo it.
Before executing `git reset --hard`, keep in mind that there is also a way to
just temporary store the changes without committing them using `git stash`.
This command resets the changes to all files, but it also saves them in case
you would like to apply them at some later time. You can read more about it in
[section below](#quickly-save-local-changes).
1. Confirm that the file is staged (that you used `git add <file>`) by running `git status`:
```shell
$ git status
On branch main
Your branch is up-to-date with 'origin/main'.
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: <file>
```
1. Choose an option and undo your changes:
- To unstage the file but keep your changes:
```shell
git restore --staged <file>
```
- To unstage everything but keep your changes:
```shell
git reset
```
- To unstage the file to current commit (HEAD):
```shell
git reset HEAD <file>
```
- To discard all local changes, but save them for [later](#quickly-save-local-changes):
```shell
git stash
```
- To discard everything permanently:
```shell
git reset --hard
```
### Quickly save local changes
You are working on a feature when a boss drops by with an urgent task. Because your
You are working on a feature when someone drops by with an urgent task. Because your
feature is not complete, but you need to swap to another branch, you can use
`git stash` to:
@ -159,60 +154,7 @@ additional options like:
- `git stash pop`, which redoes previously stashed changes and removes them from stashed list
- `git stash apply`, which redoes previously stashed changes, but keeps them on stashed list
### Staged local changes (before you commit)
If you add some files to staging, but you want to remove them from the
current commit while retaining those changes, move them outside
of the staging tree. You can also discard all changes with
`git reset --hard` or think about `git stash` [as described earlier.](#quickly-save-local-changes)
Lets start the example by editing a file with your favorite editor to change the
content and add it to staging:
```shell
vim <file>
git add <file>
```
The file is now added to staging as confirmed by `git status` command:
```shell
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: <file>
```
Now you have 4 options to undo your changes:
- Unstage the file to current commit (HEAD):
```shell
git reset HEAD <file>
```
- Unstage everything - retain changes:
```shell
git reset
```
- Discard all local changes, but save them for [later](#quickly-save-local-changes):
```shell
git stash
```
- Discard everything permanently:
```shell
git reset --hard
```
## Committed local changes
## Undo committed local changes
After you commit, your changes are recorded by the version control system.
Because you haven't pushed to your remote repository yet, your changes are
@ -289,6 +231,9 @@ these options to remove all or part of it from our repository:
### With history modification
You can rewrite history in Git, but you should avoid it, because it can cause problems
when multiple developers are contributing to the same codebase.
There is one command for history modification and that is `git rebase`. Command
provides interactive mode (`-i` flag) which enables you to:
@ -335,7 +280,7 @@ In case you want to modify something introduced in commit `B`.
You can find some more examples in the section explaining
[how to modify history](#how-modifying-history-is-done).
### Redoing the Undo
### Redoing the undo
Sometimes you realize that the changes you undid were useful and you want them
back. Well because of first paragraph you are in luck. Command `git reflog`
@ -501,14 +446,6 @@ feature set as `git filter-branch` does, but focus on specific use cases.
Refer [Reduce repository size](../../../user/project/repository/reducing_the_repo_size_using_git.md) page to know more about purging files from repository history & GitLab storage.
## Conclusion
Various options exist for undoing your work with any version control system, but
because of the de-centralized nature of Git, these options are multiplied (or limited)
depending on the stage of your process. Git also enables rewriting history, but that
should be avoided as it might cause problems when multiple developers are
contributing to the same codebase.
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues

View File

@ -74,6 +74,8 @@ module API
Gitlab::ErrorTracking.track_exception(e, extra: { file_name: params[:file_name], project_id: project.id })
forbidden!
rescue ::Packages::DuplicatePackageError
bad_request!('Duplicate package is not allowed')
end
desc 'Download package file' do

View File

@ -107,18 +107,18 @@ module API
detail 'Updates usage metrics for agent'
end
params do
requires :gitops_sync_count, type: Integer, desc: 'The count to increment the gitops_sync metric by'
optional :gitops_sync_count, type: Integer, desc: 'The count to increment the gitops_sync metric by'
optional :k8s_api_proxy_request_count, type: Integer, desc: 'The count to increment the k8s_api_proxy_request_count metric by'
end
post '/' do
gitops_sync_count = params[:gitops_sync_count]
events = params.slice(:gitops_sync_count, :k8s_api_proxy_request_count)
events.transform_keys! { |event| event.to_s.chomp('_count') }
if gitops_sync_count < 0
bad_request!('gitops_sync_count must be greater than or equal to zero')
else
Gitlab::UsageDataCounters::KubernetesAgentCounter.increment_gitops_sync(gitops_sync_count)
Gitlab::UsageDataCounters::KubernetesAgentCounter.increment_event_counts(events)
no_content!
end
no_content!
rescue ArgumentError => e
bad_request!(e.message)
end
end
end

View File

@ -4,17 +4,27 @@ module Gitlab
module UsageDataCounters
class KubernetesAgentCounter < BaseCounter
PREFIX = 'kubernetes_agent'
KNOWN_EVENTS = %w[gitops_sync].freeze
KNOWN_EVENTS = %w[gitops_sync k8s_api_proxy_request].freeze
class << self
def increment_gitops_sync(incr)
raise ArgumentError, 'must be greater than or equal to zero' if incr < 0
def increment_event_counts(events)
validate!(events)
# rather then hitting redis for this no-op, we return early
# note: redis returns the increment, so we mimic this here
return 0 if incr == 0
events.each do |event, incr|
# rather then hitting redis for this no-op, we return early
next if incr == 0
increment_by(redis_key(:gitops_sync), incr)
increment_by(redis_key(event), incr)
end
end
private
def validate!(events)
events.each do |event, incr|
raise ArgumentError, "unknown event #{event}" unless event.in?(KNOWN_EVENTS)
raise ArgumentError, "#{event} count must be greater than or equal to zero" if incr < 0
end
end
end
end

View File

@ -10,10 +10,32 @@ module QA
element :global_new_snippet_link
end
view 'app/views/shared/snippets/_snippet.html.haml' do
element :snippet_link
element :snippet_visibility_content
element :snippet_file_count_content
end
def go_to_new_snippet_page
click_element :new_menu_toggle
click_element :global_new_snippet_link
end
def has_snippet_title?(snippet_title)
has_element?(:snippet_link, snippet_title: snippet_title)
end
def has_visibility_level?(snippet_title, visibility)
within_element(:snippet_link, snippet_title: snippet_title) do
has_element?(:snippet_visibility_content, snippet_visibility: visibility)
end
end
def has_number_of_files?(snippet_title, number)
within_element(:snippet_link, snippet_title: snippet_title) do
has_element?(:snippet_file_count_content, snippet_files: number)
end
end
end
end
end

View File

@ -3,7 +3,7 @@
module QA
module Resource
class Snippet < Base
attr_accessor :title, :description, :file_content, :visibility, :file_name
attr_accessor :title, :description, :file_content, :visibility, :file_name, :files
attribute :id
attribute :http_url_to_repo

View File

@ -0,0 +1,82 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Create' do
describe 'Snippet index page' do
let(:personal_snippet_with_single_file) do
Resource::Snippet.fabricate_via_api! do |snippet|
snippet.title = "Personal snippet with one file-#{SecureRandom.hex(8)}"
snippet.visibility = 'Public'
end
end
let(:personal_snippet_with_multiple_files) do
Resource::Snippet.fabricate_via_api! do |snippet|
snippet.title = "Personal snippet with multiple files-#{SecureRandom.hex(8)}"
snippet.visibility = 'Private'
snippet.file_name = 'First file name'
snippet.file_content = 'first file content'
snippet.add_files do |files|
files.append(name: 'Second file name', content: 'second file content')
files.append(name: 'Third file name', content: 'third file content')
end
end
end
let(:project_snippet_with_single_file) do
Resource::ProjectSnippet.fabricate_via_api! do |snippet|
snippet.title = "Project snippet with one file-#{SecureRandom.hex(8)}"
snippet.visibility = 'Private'
end
end
let(:project_snippet_with_multiple_files) do
Resource::ProjectSnippet.fabricate_via_api! do |snippet|
snippet.title = "Project snippet with multiple files-#{SecureRandom.hex(8)}"
snippet.visibility = 'Public'
snippet.file_name = 'First file name'
snippet.file_content = 'first file content'
snippet.add_files do |files|
files.append(name: 'Second file name', content: 'second file content')
files.append(name: 'Third file name', content: 'third file content')
end
end
end
before do
Flow::Login.sign_in
end
after do
personal_snippet_with_single_file.remove_via_api!
personal_snippet_with_multiple_files.remove_via_api!
project_snippet_with_single_file.remove_via_api!
project_snippet_with_multiple_files.remove_via_api!
end
shared_examples 'displaying details on index page' do |snippet_type|
it "shows correct details of #{snippet_type} including file number" do
send(snippet_type)
Page::Main::Menu.perform do |menu|
menu.go_to_more_dropdown_option(:snippets_link)
end
Page::Dashboard::Snippet::Index.perform do |snippet|
aggregate_failures 'file content verification' do
expect(snippet).to have_snippet_title(send(snippet_type).title)
expect(snippet).to have_visibility_level(send(snippet_type).title, send(snippet_type).visibility)
expect(snippet).to have_number_of_files(send(snippet_type).title, send(snippet_type).files.count)
end
end
end
end
it_behaves_like 'displaying details on index page', :personal_snippet_with_single_file
it_behaves_like 'displaying details on index page', :personal_snippet_with_multiple_files
it_behaves_like 'displaying details on index page', :project_snippet_with_single_file
it_behaves_like 'displaying details on index page', :project_snippet_with_multiple_files
end
end
end

View File

@ -7,6 +7,9 @@ FactoryBot.define do
maven_duplicates_allowed { true }
maven_duplicate_exception_regex { 'SNAPSHOT' }
generic_duplicates_allowed { true }
generic_duplicate_exception_regex { 'foo' }
trait :group do
namespace { association(:group) }
end

View File

@ -96,23 +96,19 @@ describe('links layer component', () => {
});
describe('performance metrics', () => {
const metricsPath = '/root/project/-/ci/prometheus_metrics/histograms.json';
let markAndMeasure;
let reportToSentry;
let reportPerformance;
let mock;
beforeEach(() => {
mock = new MockAdapter(axios);
jest.spyOn(window, 'requestAnimationFrame').mockImplementation((cb) => cb());
markAndMeasure = jest.spyOn(perfUtils, 'performanceMarkAndMeasure');
reportToSentry = jest.spyOn(sentryUtils, 'reportToSentry');
reportPerformance = jest.spyOn(Api, 'reportPerformance');
});
afterEach(() => {
mock.restore();
});
describe('with no metrics config object', () => {
beforeEach(() => {
createComponent();
@ -164,7 +160,6 @@ describe('links layer component', () => {
});
describe('with metrics path and collect set to true', () => {
const metricsPath = '/root/project/-/ci/prometheus_metrics/histograms.json';
const duration = 875;
const numLinks = 7;
const totalGroups = 8;
@ -204,6 +199,9 @@ describe('links layer component', () => {
describe('with duration and no error', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
mock.onPost(metricsPath).reply(200, {});
jest.spyOn(window.performance, 'getEntriesByName').mockImplementation(() => {
return [{ duration }];
});
@ -218,6 +216,10 @@ describe('links layer component', () => {
});
});
afterEach(() => {
mock.restore();
});
it('it calls reportPerformance with expected arguments', () => {
expect(markAndMeasure).toHaveBeenCalled();
expect(reportPerformance).toHaveBeenCalled();

View File

@ -25,7 +25,9 @@ RSpec.describe Mutations::Namespace::PackageSettings::Update do
end
RSpec.shared_examples 'updating the namespace package setting' do
it_behaves_like 'updating the namespace package setting attributes', from: { maven_duplicates_allowed: true, maven_duplicate_exception_regex: 'SNAPSHOT' }, to: { maven_duplicates_allowed: false, maven_duplicate_exception_regex: 'RELEASE' }
it_behaves_like 'updating the namespace package setting attributes',
from: { maven_duplicates_allowed: true, maven_duplicate_exception_regex: 'SNAPSHOT', generic_duplicates_allowed: true, generic_duplicate_exception_regex: 'foo' },
to: { maven_duplicates_allowed: false, maven_duplicate_exception_regex: 'RELEASE', generic_duplicates_allowed: false, generic_duplicate_exception_regex: 'bar' }
it_behaves_like 'returning a success'
@ -56,7 +58,13 @@ RSpec.describe Mutations::Namespace::PackageSettings::Update do
context 'with existing namespace package setting' do
let_it_be(:package_settings) { create(:namespace_package_setting, namespace: namespace) }
let_it_be(:params) { { namespace_path: namespace.full_path, maven_duplicates_allowed: false, maven_duplicate_exception_regex: 'RELEASE' } }
let_it_be(:params) do
{ namespace_path: namespace.full_path,
maven_duplicates_allowed: false,
maven_duplicate_exception_regex: 'RELEASE',
generic_duplicates_allowed: false,
generic_duplicate_exception_regex: 'bar' }
end
where(:user_role, :shared_examples_name) do
:maintainer | 'updating the namespace package setting'

View File

@ -3,21 +3,57 @@
require 'spec_helper'
RSpec.describe Gitlab::UsageDataCounters::KubernetesAgentCounter do
it_behaves_like 'a redis usage counter', 'Kubernetes Agent', :gitops_sync
described_class::KNOWN_EVENTS.each do |event|
it_behaves_like 'a redis usage counter', 'Kubernetes Agent', event
it_behaves_like 'a redis usage counter with totals', :kubernetes_agent, event => 1
end
it_behaves_like 'a redis usage counter with totals', :kubernetes_agent, gitops_sync: 1
describe '.increment_gitops_sync' do
it 'increments the gtops_sync counter by the new increment amount' do
described_class.increment_gitops_sync(7)
described_class.increment_gitops_sync(2)
described_class.increment_gitops_sync(0)
expect(described_class.totals).to eq(kubernetes_agent_gitops_sync: 9)
describe '.increment_event_counts' do
let(:events) do
{
'gitops_sync' => 1,
'k8s_api_proxy_request' => 2
}
end
it 'raises for negative numbers' do
expect { described_class.increment_gitops_sync(-1) }.to raise_error(ArgumentError)
subject { described_class.increment_event_counts(events) }
it 'increments the specified counters by the new increment amount' do
described_class.increment_event_counts(events)
described_class.increment_event_counts(events)
described_class.increment_event_counts(events)
expect(described_class.totals).to eq(kubernetes_agent_gitops_sync: 3, kubernetes_agent_k8s_api_proxy_request: 6)
end
context 'event is unknown' do
let(:events) do
{
'gitops_sync' => 1,
'other_event' => 2
}
end
it 'raises an ArgumentError' do
expect(described_class).not_to receive(:increment_by)
expect { subject }.to raise_error(ArgumentError, 'unknown event other_event')
end
end
context 'increment is negative' do
let(:events) do
{
'gitops_sync' => -1,
'k8s_api_proxy_request' => 2
}
end
it 'raises an ArgumentError' do
expect(described_class).not_to receive(:increment_by)
expect { subject }.to raise_error(ArgumentError, 'gitops_sync count must be greater than or equal to zero')
end
end
end
end

View File

@ -774,6 +774,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
subject { described_class.usage_counters }
it { is_expected.to include(:kubernetes_agent_gitops_sync) }
it { is_expected.to include(:kubernetes_agent_k8s_api_proxy_request) }
it { is_expected.to include(:static_site_editor_views) }
it { is_expected.to include(:package_events_i_package_pull_package) }
it { is_expected.to include(:package_events_i_package_delete_package_by_user) }

View File

@ -14,9 +14,12 @@ RSpec.describe Namespace::PackageSetting do
it { is_expected.to allow_value(true).for(:maven_duplicates_allowed) }
it { is_expected.to allow_value(false).for(:maven_duplicates_allowed) }
it { is_expected.not_to allow_value(nil).for(:maven_duplicates_allowed) }
it { is_expected.to allow_value(true).for(:generic_duplicates_allowed) }
it { is_expected.to allow_value(false).for(:generic_duplicates_allowed) }
it { is_expected.not_to allow_value(nil).for(:generic_duplicates_allowed) }
end
describe '#maven_duplicate_exception_regex' do
describe 'regex values' do
let_it_be(:package_settings) { create(:namespace_package_setting) }
subject { package_settings }
@ -24,12 +27,14 @@ RSpec.describe Namespace::PackageSetting do
valid_regexps = %w[SNAPSHOT .* v.+ v10.1.* (?:v.+|SNAPSHOT|TEMP)]
invalid_regexps = ['[', '(?:v.+|SNAPSHOT|TEMP']
valid_regexps.each do |valid_regexp|
it { is_expected.to allow_value(valid_regexp).for(:maven_duplicate_exception_regex) }
end
[:maven_duplicate_exception_regex, :generic_duplicate_exception_regex].each do |attribute|
valid_regexps.each do |valid_regexp|
it { is_expected.to allow_value(valid_regexp).for(attribute) }
end
invalid_regexps.each do |invalid_regexp|
it { is_expected.not_to allow_value(invalid_regexp).for(:maven_duplicate_exception_regex) }
invalid_regexps.each do |invalid_regexp|
it { is_expected.not_to allow_value(invalid_regexp).for(attribute) }
end
end
end
end
@ -41,7 +46,7 @@ RSpec.describe Namespace::PackageSetting do
context 'package types with package_settings' do
# As more package types gain settings they will be added to this list
[:maven_package].each do |format|
[:maven_package, :generic_package].each do |format|
let_it_be(:package) { create(format, name: 'foo', version: 'beta') } # rubocop:disable Rails/SaveBang
let_it_be(:package_type) { package.package_type }
let_it_be(:package_setting) { package.project.namespace.package_settings }
@ -70,7 +75,7 @@ RSpec.describe Namespace::PackageSetting do
end
context 'package types without package_settings' do
[:npm_package, :conan_package, :nuget_package, :pypi_package, :composer_package, :generic_package, :golang_package, :debian_package].each do |format|
[:npm_package, :conan_package, :nuget_package, :pypi_package, :composer_package, :golang_package, :debian_package].each do |format|
let_it_be(:package) { create(format) } # rubocop:disable Rails/SaveBang
let_it_be(:package_setting) { package.project.namespace.package_settings }

View File

@ -1006,19 +1006,58 @@ RSpec.describe Repository do
end
end
context 'when specifying a path with wildcard' do
let(:path) { 'files/*/*.png' }
context 'when specifying a wildcard path' do
let(:path) { '*.md' }
it 'returns all files matching the path' do
expect(result).to contain_exactly('files/images/logo-black.png',
'files/images/logo-white.png')
it 'returns files matching the path in the root folder' do
expect(result).to contain_exactly('CONTRIBUTING.md',
'MAINTENANCE.md',
'PROCESS.md',
'README.md')
end
end
context 'when specifying an extension with wildcard' do
let(:path) { '*.rb' }
context 'when specifying a wildcard path for all' do
let(:path) { '**.md' }
it 'returns all files matching the extension' do
it 'returns all matching files in all folders' do
expect(result).to contain_exactly('CONTRIBUTING.md',
'MAINTENANCE.md',
'PROCESS.md',
'README.md',
'files/markdown/ruby-style-guide.md',
'with space/README.md')
end
end
context 'when specifying a path to subfolders using two asterisks and a slash' do
let(:path) { 'files/**/*.md' }
it 'returns all files matching the path' do
expect(result).to contain_exactly('files/markdown/ruby-style-guide.md')
end
end
context 'when specifying a wildcard path to subfolder with just two asterisks' do
let(:path) { 'files/**.md' }
it 'returns all files in the matching path' do
expect(result).to contain_exactly('files/markdown/ruby-style-guide.md')
end
end
context 'when specifying a wildcard path to subfolder with one asterisk' do
let(:path) { 'files/*/*.md' }
it 'returns all files in the matching path' do
expect(result).to contain_exactly('files/markdown/ruby-style-guide.md')
end
end
context 'when specifying a wildcard path for an unknown number of subfolder levels' do
let(:path) { '**/*.rb' }
it 'returns all matched files in all subfolders' do
expect(result).to contain_exactly('encoding/russian.rb',
'files/ruby/popen.rb',
'files/ruby/regex.rb',
@ -1026,6 +1065,14 @@ RSpec.describe Repository do
end
end
context 'when specifying a wildcard path to one level of subfolders' do
let(:path) { '*/*.rb' }
it 'returns all matched files in one subfolder' do
expect(result).to contain_exactly('encoding/russian.rb')
end
end
context 'when sending regexp' do
let(:path) { '.*\.rb' }

View File

@ -12,7 +12,9 @@ RSpec.describe 'Updating the package settings' do
{
namespace_path: namespace.full_path,
maven_duplicates_allowed: false,
maven_duplicate_exception_regex: 'foo-.*'
maven_duplicate_exception_regex: 'foo-.*',
generic_duplicates_allowed: false,
generic_duplicate_exception_regex: 'bar-.*'
}
end
@ -22,6 +24,8 @@ RSpec.describe 'Updating the package settings' do
packageSettings {
mavenDuplicatesAllowed
mavenDuplicateExceptionRegex
genericDuplicatesAllowed
genericDuplicateExceptionRegex
}
errors
QL
@ -40,6 +44,8 @@ RSpec.describe 'Updating the package settings' do
expect(mutation_response['errors']).to be_empty
expect(package_settings_response['mavenDuplicatesAllowed']).to eq(params[:maven_duplicates_allowed])
expect(package_settings_response['mavenDuplicateExceptionRegex']).to eq(params[:maven_duplicate_exception_regex])
expect(package_settings_response['genericDuplicatesAllowed']).to eq(params[:generic_duplicates_allowed])
expect(package_settings_response['genericDuplicateExceptionRegex']).to eq(params[:generic_duplicate_exception_regex])
end
end
@ -69,8 +75,8 @@ RSpec.describe 'Updating the package settings' do
RSpec.shared_examples 'accepting the mutation request updating the package settings' do
it_behaves_like 'updating the namespace package setting attributes',
from: { maven_duplicates_allowed: true, maven_duplicate_exception_regex: 'SNAPSHOT' },
to: { maven_duplicates_allowed: false, maven_duplicate_exception_regex: 'foo-.*' }
from: { maven_duplicates_allowed: true, maven_duplicate_exception_regex: 'SNAPSHOT', generic_duplicates_allowed: true, generic_duplicate_exception_regex: 'foo' },
to: { maven_duplicates_allowed: false, maven_duplicate_exception_regex: 'foo-.*', generic_duplicates_allowed: false, generic_duplicate_exception_regex: 'bar-.*' }
it_behaves_like 'returning a success'
it_behaves_like 'rejecting invalid regex'

View File

@ -67,26 +67,26 @@ RSpec.describe API::Internal::Kubernetes do
context 'is authenticated for an agent' do
let!(:agent_token) { create(:cluster_agent_token) }
it 'returns no_content for valid gitops_sync_count' do
send_request(params: { gitops_sync_count: 10 })
it 'returns no_content for valid events' do
send_request(params: { gitops_sync_count: 10, k8s_api_proxy_request_count: 5 })
expect(response).to have_gitlab_http_status(:no_content)
end
it 'returns no_content 0 gitops_sync_count' do
send_request(params: { gitops_sync_count: 0 })
it 'returns no_content for counts of zero' do
send_request(params: { gitops_sync_count: 0, k8s_api_proxy_request_count: 0 })
expect(response).to have_gitlab_http_status(:no_content)
end
it 'returns 400 for non number' do
send_request(params: { gitops_sync_count: 'string' })
send_request(params: { gitops_sync_count: 'string', k8s_api_proxy_request_count: 1 })
expect(response).to have_gitlab_http_status(:bad_request)
end
it 'returns 400 for negative number' do
send_request(params: { gitops_sync_count: '-1' })
send_request(params: { gitops_sync_count: -1, k8s_api_proxy_request_count: 1 })
expect(response).to have_gitlab_http_status(:bad_request)
end

View File

@ -32,7 +32,9 @@ RSpec.describe ::Namespaces::PackageSettings::UpdateService do
end
shared_examples 'updating the namespace package setting' do
it_behaves_like 'updating the namespace package setting attributes', from: { maven_duplicates_allowed: true, maven_duplicate_exception_regex: 'SNAPSHOT' }, to: { maven_duplicates_allowed: false, maven_duplicate_exception_regex: 'RELEASE' }
it_behaves_like 'updating the namespace package setting attributes',
from: { maven_duplicates_allowed: true, maven_duplicate_exception_regex: 'SNAPSHOT', generic_duplicates_allowed: true, generic_duplicate_exception_regex: 'foo' },
to: { maven_duplicates_allowed: false, maven_duplicate_exception_regex: 'RELEASE', generic_duplicates_allowed: false, generic_duplicate_exception_regex: 'bar' }
it_behaves_like 'returning a success'
@ -60,7 +62,12 @@ RSpec.describe ::Namespaces::PackageSettings::UpdateService do
context 'with existing namespace package setting' do
let_it_be(:package_settings) { create(:namespace_package_setting, namespace: namespace) }
let_it_be(:params) { { maven_duplicates_allowed: false, maven_duplicate_exception_regex: 'RELEASE' } }
let_it_be(:params) do
{ maven_duplicates_allowed: false,
maven_duplicate_exception_regex: 'RELEASE',
generic_duplicates_allowed: false,
generic_duplicate_exception_regex: 'bar' }
end
where(:user_role, :shared_examples_name) do
:maintainer | 'updating the namespace package setting'

View File

@ -6,13 +6,16 @@ RSpec.describe Packages::Generic::CreatePackageFileService do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:pipeline) { create(:ci_pipeline, user: user) }
let_it_be(:file_name) { 'myfile.tar.gz.1' }
let(:build) { double('build', pipeline: pipeline) }
describe '#execute' do
let_it_be(:package) { create(:generic_package, project: project) }
let(:sha256) { '440e5e148a25331bbd7991575f7d54933c0ebf6cc735a18ee5066ac1381bb590' }
let(:temp_file) { Tempfile.new("test") }
let(:file) { UploadedFile.new(temp_file.path, sha256: sha256) }
let(:package) { create(:generic_package, project: project) }
let(:package_service) { double }
let(:params) do
@ -20,7 +23,7 @@ RSpec.describe Packages::Generic::CreatePackageFileService do
package_name: 'mypackage',
package_version: '0.0.1',
file: file,
file_name: 'myfile.tar.gz.1',
file_name: file_name,
build: build
}
end
@ -34,7 +37,7 @@ RSpec.describe Packages::Generic::CreatePackageFileService do
}
end
subject { described_class.new(project, user, params).execute }
subject(:execute_service) { described_class.new(project, user, params).execute }
before do
FileUtils.touch(temp_file)
@ -47,14 +50,14 @@ RSpec.describe Packages::Generic::CreatePackageFileService do
end
it 'creates package file', :aggregate_failures do
expect { subject }.to change { package.package_files.count }.by(1)
expect { execute_service }.to change { package.package_files.count }.by(1)
.and change { Packages::PackageFileBuildInfo.count }.by(1)
package_file = package.package_files.last
aggregate_failures do
expect(package_file.package.status).to eq('default')
expect(package_file.package).to eq(package)
expect(package_file.file_name).to eq('myfile.tar.gz.1')
expect(package_file.file_name).to eq(file_name)
expect(package_file.size).to eq(file.size)
expect(package_file.file_sha256).to eq(sha256)
end
@ -65,7 +68,7 @@ RSpec.describe Packages::Generic::CreatePackageFileService do
let(:package_params) { super().merge(status: 'hidden') }
it 'updates an existing packages status' do
expect { subject }.to change { package.package_files.count }.by(1)
expect { execute_service }.to change { package.package_files.count }.by(1)
.and change { Packages::PackageFileBuildInfo.count }.by(1)
package_file = package.package_files.last
@ -76,5 +79,32 @@ RSpec.describe Packages::Generic::CreatePackageFileService do
end
it_behaves_like 'assigns build to package file'
context 'with existing package' do
before do
create(:package_file, package: package, file_name: file_name)
end
it { expect { execute_service }.to change { project.package_files.count }.by(1) }
context 'when duplicates are not allowed' do
before do
package.project.namespace.package_settings.update!(generic_duplicates_allowed: false)
end
it 'does not allow duplicates' do
expect { execute_service }.to raise_error(::Packages::DuplicatePackageError)
.and change { project.package_files.count }.by(0)
end
context 'when the package name matches the exception regex' do
before do
package.project.namespace.package_settings.update!(generic_duplicate_exception_regex: '.*')
end
it { expect { execute_service }.to change { project.package_files.count }.by(1) }
end
end
end
end
end

View File

@ -7,6 +7,8 @@ RSpec.shared_examples 'updating the namespace package setting attributes' do |fr
expect { subject }
.to change { namespace.package_settings.reload.maven_duplicates_allowed }.from(from[:maven_duplicates_allowed]).to(to[:maven_duplicates_allowed])
.and change { namespace.package_settings.reload.maven_duplicate_exception_regex }.from(from[:maven_duplicate_exception_regex]).to(to[:maven_duplicate_exception_regex])
.and change { namespace.package_settings.reload.generic_duplicates_allowed }.from(from[:generic_duplicates_allowed]).to(to[:generic_duplicates_allowed])
.and change { namespace.package_settings.reload.generic_duplicate_exception_regex }.from(from[:generic_duplicate_exception_regex]).to(to[:generic_duplicate_exception_regex])
end
end
@ -26,6 +28,8 @@ RSpec.shared_examples 'creating the namespace package setting' do
expect(namespace.package_setting_relation.maven_duplicates_allowed).to eq(package_settings[:maven_duplicates_allowed])
expect(namespace.package_setting_relation.maven_duplicate_exception_regex).to eq(package_settings[:maven_duplicate_exception_regex])
expect(namespace.package_setting_relation.generic_duplicates_allowed).to eq(package_settings[:generic_duplicates_allowed])
expect(namespace.package_setting_relation.generic_duplicate_exception_regex).to eq(package_settings[:generic_duplicate_exception_regex])
end
it_behaves_like 'returning a success'