Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
dfda8b7e77
commit
c8deb6a801
49 changed files with 896 additions and 196 deletions
|
@ -1 +1 @@
|
|||
a88ceb12912277ab1e07ca325792df40550e6bcc
|
||||
6f0bfe7c326aeabcb5c3bc35bd3994160d20d6c3
|
||||
|
|
55
app/assets/javascripts/admin/users/new.js
Normal file
55
app/assets/javascripts/admin/users/new.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
const DATA_ATTR_REGEX_PATTERN = 'data-user-internal-regex-pattern';
|
||||
const DATA_ATTR_REGEX_OPTIONS = 'data-user-internal-regex-options';
|
||||
export const ID_USER_EXTERNAL = 'user_external';
|
||||
export const ID_WARNING = 'warning_external_automatically_set';
|
||||
export const ID_USER_EMAIL = 'user_email';
|
||||
|
||||
const getAttributeValue = (attr) => document.querySelector(`[${attr}]`)?.getAttribute(attr);
|
||||
|
||||
const getRegexPattern = () => getAttributeValue(DATA_ATTR_REGEX_PATTERN);
|
||||
|
||||
const getRegexOptions = () => getAttributeValue(DATA_ATTR_REGEX_OPTIONS);
|
||||
|
||||
export const setupInternalUserRegexHandler = () => {
|
||||
const regexPattern = getRegexPattern();
|
||||
|
||||
if (!regexPattern) {
|
||||
return;
|
||||
}
|
||||
|
||||
const regexOptions = getRegexOptions();
|
||||
const elExternal = document.getElementById(ID_USER_EXTERNAL);
|
||||
const elWarningMessage = document.getElementById(ID_WARNING);
|
||||
const elUserEmail = document.getElementById(ID_USER_EMAIL);
|
||||
|
||||
const isEmailInternal = (email) => {
|
||||
const regex = new RegExp(regexPattern, regexOptions);
|
||||
return regex.test(email);
|
||||
};
|
||||
|
||||
const setExternalCheckbox = (email) => {
|
||||
const isChecked = elExternal.checked;
|
||||
|
||||
if (isEmailInternal(email)) {
|
||||
if (isChecked) {
|
||||
elExternal.checked = false;
|
||||
elWarningMessage.classList.remove('hidden');
|
||||
}
|
||||
} else if (!isChecked) {
|
||||
elExternal.checked = true;
|
||||
elWarningMessage.classList.add('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
const setupListeners = () => {
|
||||
elUserEmail.addEventListener('input', (event) => {
|
||||
setExternalCheckbox(event.target.value);
|
||||
});
|
||||
|
||||
elExternal.addEventListener('change', () => {
|
||||
elWarningMessage.classList.add('hidden');
|
||||
});
|
||||
};
|
||||
|
||||
setupListeners();
|
||||
};
|
|
@ -1,49 +1,3 @@
|
|||
import $ from 'jquery';
|
||||
import { setupInternalUserRegexHandler } from '~/admin/users/new';
|
||||
|
||||
export default class UserInternalRegexHandler {
|
||||
constructor() {
|
||||
this.regexPattern = $('[data-user-internal-regex-pattern]').data('user-internal-regex-pattern');
|
||||
if (this.regexPattern && this.regexPattern !== '') {
|
||||
this.regexOptions = $('[data-user-internal-regex-options]').data(
|
||||
'user-internal-regex-options',
|
||||
);
|
||||
this.external = $('#user_external');
|
||||
this.warningMessage = $('#warning_external_automatically_set');
|
||||
this.addListenerToEmailField();
|
||||
this.addListenerToUserExternalCheckbox();
|
||||
}
|
||||
}
|
||||
|
||||
addListenerToEmailField() {
|
||||
$('#user_email').on('input', (event) => {
|
||||
this.setExternalCheckbox(event.currentTarget.value);
|
||||
});
|
||||
}
|
||||
|
||||
addListenerToUserExternalCheckbox() {
|
||||
this.external.on('click', () => {
|
||||
this.warningMessage.addClass('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
isEmailInternal(email) {
|
||||
const regex = new RegExp(this.regexPattern, this.regexOptions);
|
||||
return regex.test(email);
|
||||
}
|
||||
|
||||
setExternalCheckbox(email) {
|
||||
const isChecked = this.external.prop('checked');
|
||||
if (this.isEmailInternal(email)) {
|
||||
if (isChecked) {
|
||||
this.external.prop('checked', false);
|
||||
this.warningMessage.removeClass('hidden');
|
||||
}
|
||||
} else if (!isChecked) {
|
||||
this.external.prop('checked', true);
|
||||
this.warningMessage.addClass('hidden');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new UserInternalRegexHandler();
|
||||
setupInternalUserRegexHandler();
|
||||
|
|
|
@ -10,6 +10,7 @@ class Groups::BoardsController < Groups::ApplicationController
|
|||
before_action do
|
||||
push_frontend_feature_flag(:graphql_board_lists, group, default_enabled: false)
|
||||
push_frontend_feature_flag(:boards_filtered_search, group)
|
||||
push_frontend_feature_flag(:swimlanes_buffered_rendering, group, default_enabled: :yaml)
|
||||
end
|
||||
|
||||
feature_category :boards
|
||||
|
|
|
@ -9,6 +9,7 @@ class Projects::BoardsController < Projects::ApplicationController
|
|||
before_action :assign_endpoint_vars
|
||||
before_action do
|
||||
push_frontend_feature_flag(:add_issues_button)
|
||||
push_frontend_feature_flag(:swimlanes_buffered_rendering, project, default_enabled: :yaml)
|
||||
end
|
||||
|
||||
feature_category :boards
|
||||
|
|
|
@ -9,6 +9,7 @@ module Mutations
|
|||
ERROR_MESSAGE = 'You cannot perform write operations on a read-only instance'
|
||||
|
||||
field_class ::Types::BaseField
|
||||
argument_class ::Types::BaseArgument
|
||||
|
||||
field :errors, [GraphQL::STRING_TYPE],
|
||||
null: false,
|
||||
|
|
|
@ -4,8 +4,10 @@ module Types
|
|||
class BaseArgument < GraphQL::Schema::Argument
|
||||
include GitlabStyleDeprecations
|
||||
|
||||
attr_reader :deprecation
|
||||
|
||||
def initialize(*args, **kwargs, &block)
|
||||
kwargs = gitlab_deprecation(kwargs)
|
||||
@deprecation = gitlab_deprecation(kwargs)
|
||||
|
||||
super(*args, **kwargs, &block)
|
||||
end
|
||||
|
|
|
@ -26,7 +26,7 @@ module Types
|
|||
|
||||
def value(*args, **kwargs, &block)
|
||||
enum[args[0].downcase] = kwargs[:value] || args[0]
|
||||
kwargs = gitlab_deprecation(kwargs)
|
||||
gitlab_deprecation(kwargs)
|
||||
|
||||
super(*args, **kwargs, &block)
|
||||
end
|
||||
|
|
|
@ -8,6 +8,8 @@ module Types
|
|||
|
||||
DEFAULT_COMPLEXITY = 1
|
||||
|
||||
attr_reader :deprecation
|
||||
|
||||
def initialize(**kwargs, &block)
|
||||
@calls_gitaly = !!kwargs.delete(:calls_gitaly)
|
||||
@constant_complexity = kwargs[:complexity].is_a?(Integer) && kwargs[:complexity] > 0
|
||||
|
@ -16,7 +18,7 @@ module Types
|
|||
kwargs[:complexity] = field_complexity(kwargs[:resolver_class], kwargs[:complexity])
|
||||
@feature_flag = kwargs[:feature_flag]
|
||||
kwargs = check_feature_flag(kwargs)
|
||||
kwargs = gitlab_deprecation(kwargs)
|
||||
@deprecation = gitlab_deprecation(kwargs)
|
||||
|
||||
super(**kwargs, &block)
|
||||
|
||||
|
|
|
@ -7,25 +7,21 @@ module GitlabStyleDeprecations
|
|||
|
||||
private
|
||||
|
||||
# Mutate the arguments, returns the deprecation
|
||||
def gitlab_deprecation(kwargs)
|
||||
if kwargs[:deprecation_reason].present?
|
||||
raise ArgumentError, 'Use `deprecated` property instead of `deprecation_reason`. ' \
|
||||
'See https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#deprecating-fields-arguments-and-enum-values'
|
||||
end
|
||||
|
||||
deprecation = kwargs.delete(:deprecated)
|
||||
return kwargs unless deprecation
|
||||
deprecation = ::Gitlab::Graphql::Deprecation.parse(kwargs.delete(:deprecated))
|
||||
return unless deprecation
|
||||
|
||||
milestone, reason = deprecation.values_at(:milestone, :reason).map(&:presence)
|
||||
raise ArgumentError, "Bad deprecation. #{deprecation.errors.full_messages.to_sentence}" unless deprecation.valid?
|
||||
|
||||
raise ArgumentError, 'Please provide a `milestone` within `deprecated`' unless milestone
|
||||
raise ArgumentError, 'Please provide a `reason` within `deprecated`' unless reason
|
||||
raise ArgumentError, '`milestone` must be a `String`' unless milestone.is_a?(String)
|
||||
kwargs[:deprecation_reason] = deprecation.deprecation_reason
|
||||
kwargs[:description] = deprecation.edit_description(kwargs[:description])
|
||||
|
||||
deprecated_in = "Deprecated in #{milestone}"
|
||||
kwargs[:deprecation_reason] = "#{reason}. #{deprecated_in}."
|
||||
kwargs[:description] += " #{deprecated_in}: #{reason}." if kwargs[:description]
|
||||
|
||||
kwargs
|
||||
deprecation
|
||||
end
|
||||
end
|
||||
|
|
|
@ -29,6 +29,7 @@ class Packages::Package < ApplicationRecord
|
|||
|
||||
delegate :recipe, :recipe_path, to: :conan_metadatum, prefix: :conan
|
||||
delegate :codename, :suite, to: :debian_distribution, prefix: :debian_distribution
|
||||
delegate :target_sha, to: :composer_metadatum, prefix: :composer
|
||||
|
||||
validates :project, presence: true
|
||||
validates :name, presence: true
|
||||
|
|
|
@ -28,4 +28,4 @@
|
|||
= _("When merge requests and commits in the default branch close, any issues they reference also close.")
|
||||
= link_to sprite_icon('question-o'), help_page_path('user/project/issues/managing_issues.md', anchor: 'disabling-automatic-issue-closing'), target: '_blank'
|
||||
|
||||
= f.submit _('Save changes'), class: "gl-button btn btn-success"
|
||||
= f.submit _('Save changes'), class: "gl-button btn btn-confirm"
|
||||
|
|
5
changelogs/unreleased/247531-composer-source-jsonn.yml
Normal file
5
changelogs/unreleased/247531-composer-source-jsonn.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Support for --prefer-source option for Composer registry
|
||||
merge_request: 56693
|
||||
author:
|
||||
type: changed
|
5
changelogs/unreleased/ar-approval-settings-header.yml
Normal file
5
changelogs/unreleased/ar-approval-settings-header.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Updated MR Approvals to specify settings section
|
||||
merge_request: 54985
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Move from btn-success to btn-confirm in default_branch directory
|
||||
merge_request: 56330
|
||||
author: Yogi (@yo)
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: In admin new user page, fix external checkbox warning hide with keyboard interaction
|
||||
merge_request: 56896
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: swimlanes_buffered_rendering
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/56614
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/324994
|
||||
milestone: '13.11'
|
||||
type: development
|
||||
group: group::product planning
|
||||
default_enabled: false
|
|
@ -672,7 +672,7 @@ For each Patroni instance on the secondary site:
|
|||
## Migrating from repmgr to Patroni
|
||||
|
||||
1. Before migrating, it is recommended that there is no replication lag between the primary and secondary sites and that replication is paused. In GitLab 13.2 and later, you can pause and resume replication with `gitlab-ctl geo-replication-pause` and `gitlab-ctl geo-replication-resume` on a Geo secondary database node.
|
||||
1. Follow the [instructions to migrate repmgr to Patroni](../../postgresql/replication_and_failover.md#switching-from-repmgr-to-patroni). When configuring Patroni on each primary site database node, add `patroni['replicaton_slots'] = { '<slot_name>' => 'physical' }`
|
||||
1. Follow the [instructions to migrate repmgr to Patroni](../../postgresql/replication_and_failover.md#switching-from-repmgr-to-patroni). When configuring Patroni on each primary site database node, add `patroni['replication_slots'] = { '<slot_name>' => 'physical' }`
|
||||
to `gitlab.rb` where `<slot_name>` is the name of the replication slot for your Geo secondary. This will ensure that Patroni recognizes the replication slot as permanent and will not drop it upon restarting.
|
||||
1. If database replication to the secondary was paused before migration, resume replication once Patroni is confirmed working on the primary.
|
||||
|
||||
|
|
|
@ -97,6 +97,39 @@ The following settings are:
|
|||
| `remote_directory` | The bucket name where Terraform state files are stored | |
|
||||
| `connection` | Various connection options described below | |
|
||||
|
||||
### Migrate to object storage
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/247042) in GitLab 13.9.
|
||||
|
||||
To migrate Terraform state files to object storage, follow the instructions below.
|
||||
|
||||
- For Omnibus package installations:
|
||||
|
||||
```shell
|
||||
gitlab-rake gitlab:terraform_states:migrate
|
||||
```
|
||||
|
||||
- For source installations:
|
||||
|
||||
```shell
|
||||
sudo -u git -H bundle exec rake gitlab:terraform_states:migrate RAILS_ENV=production
|
||||
```
|
||||
|
||||
For GitLab 13.8 and earlier versions, you can use a workaround for the Rake task:
|
||||
|
||||
1. Open the GitLab [Rails console](operations/rails_console.md).
|
||||
1. Run the following commands:
|
||||
|
||||
```ruby
|
||||
Terraform::StateUploader.alias_method(:upload, :model)
|
||||
|
||||
Terraform::StateVersion.where(file_store: ::ObjectStorage::Store::LOCAL). find_each(batch_size: 10) do |terraform_state_version|
|
||||
puts "Migrating: #{terraform_state_version.inspect}"
|
||||
|
||||
terraform_state_version.file.migrate!(::ObjectStorage::Store::REMOTE)
|
||||
end
|
||||
```
|
||||
|
||||
### S3-compatible connection settings
|
||||
|
||||
See [the available connection settings for different providers](object_storage.md#connection-settings).
|
||||
|
|
|
@ -203,7 +203,7 @@ To do this:
|
|||
```rails
|
||||
u = User.find_by_email('email_of_user_doing_search')
|
||||
s = SearchService.new(u, {:search => 'search_term'})
|
||||
pp s.search_objects.class.name
|
||||
pp s.search_objects.class
|
||||
```
|
||||
|
||||
The output from the last command is the key here. If it shows:
|
||||
|
@ -217,7 +217,9 @@ The output from the last command is the key here. If it shows:
|
|||
|
||||
If all the settings look correct and it is still not using Elasticsearch for the search function, it is best to escalate to GitLab support. This could be a bug/issue.
|
||||
|
||||
Moving past that, it is best to attempt the same search using the [Elasticsearch Search API](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html) and compare the results from what you see in GitLab.
|
||||
Moving past that, it is best to attempt the same [search via the Rails console](../../integration/elasticsearch.md#i-indexed-all-the-repositories-but-i-cant-get-any-hits-for-my-search-term-in-the-ui)
|
||||
or the [Elasticsearch Search API](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html),
|
||||
and compare the results from what you see in GitLab.
|
||||
|
||||
If the results:
|
||||
|
||||
|
|
|
@ -3210,7 +3210,8 @@ test:
|
|||
```
|
||||
|
||||
With this configuration, GitLab adds a link **artifact 1** to the relevant merge request
|
||||
that points to `file1.txt`.
|
||||
that points to `file1.txt`. To access the link, select **View exposed artifact**
|
||||
below the pipeline graph in the merge request overview.
|
||||
|
||||
An example that matches an entire directory:
|
||||
|
||||
|
|
|
@ -91,6 +91,14 @@ Since Elasticsearch can read and use indices created in the previous major versi
|
|||
|
||||
The only thing worth noting is that if you have created your current index before GitLab 13.0, you might want to reindex from scratch (which will implicitly create an alias) in order to use some features, for example [Zero downtime reindexing](#zero-downtime-reindexing). Once you do that, you'll be able to perform zero-downtime reindexing and will benefit from any future features that make use of the alias.
|
||||
|
||||
If you are unsure when your current index was created,
|
||||
you can check whether it was created after GitLab 13.0 by using the
|
||||
[Elasticsearch cat aliases API](https://www.elastic.co/guide/en/elasticsearch/reference/7.11/cat-alias.html).
|
||||
If the list of aliases returned contains an entry for `gitlab-production` that points to an index
|
||||
named `gitlab-production-<numerical timestamp>`, your index was created after GitLab 13.0.
|
||||
If the `gitlab-production` alias is missing, you'll need to reindex from scratch to use
|
||||
features such as Zero-downtime reindexing.
|
||||
|
||||
## Elasticsearch repository indexer
|
||||
|
||||
For indexing Git repository data, GitLab uses an [indexer written in Go](https://gitlab.com/gitlab-org/gitlab-elasticsearch-indexer).
|
||||
|
@ -936,3 +944,10 @@ sudo gitlab-rake gitlab:elastic:index
|
|||
cd /home/git/gitlab
|
||||
sudo -u git -H bundle exec rake gitlab:elastic:index
|
||||
```
|
||||
|
||||
### How does Advanced Search handle private projects?
|
||||
|
||||
Advanced Search will store all the projects in the same Elasticsearch indexes,
|
||||
however searches will only surface results that can be viewed by the user.
|
||||
Advanced Search will honor all permission checks in the application by
|
||||
filtering out projects that a user does not have access to at search time.
|
||||
|
|
|
@ -7,7 +7,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
# GitLab Kubernetes Agent **(PREMIUM SELF)**
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/223061) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.4.
|
||||
> - It's disabled on GitLab.com. Rolling this feature out to GitLab.com is [planned](https://gitlab.com/groups/gitlab-org/-/epics/3834).
|
||||
> - [In GitLab 13.10](https://gitlab.com/gitlab-org/gitlab/-/issues/300960), KAS became available on GitLab.com under `wss://kas.gitlab.com` through an Early Adopter Program.
|
||||
|
||||
WARNING:
|
||||
This feature might not be available to you. Check the **version history** note above for details.
|
||||
|
@ -85,7 +85,7 @@ component, `agentk`.
|
|||
|
||||
Upgrade your agent installations together with GitLab upgrades. To decide which version of `agentk`to install follow:
|
||||
|
||||
1. Open the [GITLAB_KAS_VERSION](https://gitlab.com/gitlab-org/gitlab/-/blob/master/GITLAB_KAS_VERSION) file from the GitLab Repository, which contains the latest `agentk` version associated with the `master` branch.
|
||||
1. Open the [`GITLAB_KAS_VERSION`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/GITLAB_KAS_VERSION) file from the GitLab Repository, which contains the latest `agentk` version associated with the `master` branch.
|
||||
1. Change the `master` branch and select the Git tag associated with your version. For instance, you could change it to GitLab [v13.5.3-ee release](https://gitlab.com/gitlab-org/gitlab/-/blob/v13.5.3-ee/GITLAB_KAS_VERSION)
|
||||
|
||||
The available `agentk` and `kas` versions can be found in
|
||||
|
@ -96,20 +96,21 @@ The available `agentk` and `kas` versions can be found in
|
|||
[Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3834) in GitLab 13.10,
|
||||
the GitLab Kubernetes Agent Server (KAS) is available on GitLab.com under `wss://kas.gitlab.com`.
|
||||
If you are a GitLab.com user, skip this step and directly
|
||||
[set up the configuration repository](#define-a-configuration-repository)
|
||||
[set up the configuration repository](#define-a-configuration-repository)
|
||||
for your agent.
|
||||
|
||||
The GitLab Kubernetes Agent Server (KAS) can be deployed using [Omnibus
|
||||
GitLab](https://docs.gitlab.com/omnibus/) or the [GitLab
|
||||
chart](https://gitlab.com/gitlab-org/charts/gitlab). If you don't already have
|
||||
The GitLab Kubernetes Agent Server (KAS) can be installed through Omnibus GitLab or
|
||||
through the GitLab Helm Chart. If you don't already have
|
||||
GitLab installed, please refer to our [installation
|
||||
documentation](https://docs.gitlab.com/ee/install/README.html).
|
||||
You can install the KAS within GitLab as explained below according to your GitLab installation method.
|
||||
You can also opt to use an [external KAS](#use-an-external-kas-installation).
|
||||
|
||||
#### Install with Omnibus
|
||||
#### Install KAS with Omnibus
|
||||
|
||||
When using the [Omnibus GitLab](https://docs.gitlab.com/omnibus/) package:
|
||||
For [Omnibus](https://docs.gitlab.com/omnibus/) package installations:
|
||||
|
||||
1. Edit `/etc/gitlab/gitlab.rb`:
|
||||
1. Edit `/etc/gitlab/gitlab.rb` to enable the Kubernetes Agent Server:
|
||||
|
||||
```plaintext
|
||||
gitlab_kas['enable'] = true
|
||||
|
@ -121,9 +122,9 @@ To configure any additional options related to GitLab Kubernetes Agent Server,
|
|||
refer to the **Enable GitLab KAS** section of the
|
||||
[`gitlab.rb.template`](https://gitlab.com/gitlab-org/omnibus-gitlab/-/blob/master/files/gitlab-config-template/gitlab.rb.template).
|
||||
|
||||
#### Install with the Helm chart
|
||||
#### Install KAS with GitLab Helm Chart
|
||||
|
||||
When installing or upgrading the GitLab Helm chart, consider the following Helm v3 example.
|
||||
For GitLab [Helm Chart](https://gitlab.com/gitlab-org/charts/gitlab) installations, consider the following Helm v3 example.
|
||||
If you're using Helm v2, you must modify this example. See our [notes regarding deploy with Helm](https://docs.gitlab.com/charts/installation/deployment.html#deploy-using-helm).
|
||||
|
||||
You must set `global.kas.enabled=true` for the KAS to be properly installed and configured:
|
||||
|
@ -150,6 +151,29 @@ gitlab:
|
|||
|
||||
For details, read [Using the GitLab-KAS chart](https://docs.gitlab.com/charts/charts/gitlab/kas/).
|
||||
|
||||
#### Use an external KAS installation
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/299850) in GitLab 13.10.
|
||||
|
||||
Besides installing KAS with GitLab, you can opt to configure GitLab to use an external KAS.
|
||||
|
||||
For GitLab instances installed through the GitLab Helm Chart, see [how to configure your external KAS](https://docs.gitlab.com/charts/charts/globals.html#external-kas).
|
||||
|
||||
For GitLab instances installed through Omnibus packages:
|
||||
|
||||
1. Edit `/etc/gitlab/gitlab.rb` adding the paths to your external KAS:
|
||||
|
||||
```ruby
|
||||
gitlab_kas['enable'] = false
|
||||
gitlab_kas['api_secret_key'] = 'Your shared secret between GitLab and KAS'
|
||||
|
||||
gitlab_rails['gitlab_kas_enabled'] = true
|
||||
gitlab_rails['gitlab_kas_external_url'] = 'wss://kas.gitlab.example.com' # User-facing URL for the in-cluster agentk
|
||||
gitlab_rails['gitlab_kas_internal_url'] = 'grpc://kas.internal.gitlab.example.com' # Internal URL for the GitLab backend
|
||||
```
|
||||
|
||||
1. [Reconfigure GitLab](../../../administration/restart_gitlab.md#omnibus-gitlab-reconfigure).
|
||||
|
||||
### Define a configuration repository
|
||||
|
||||
Next, you need a GitLab repository to contain your Agent configuration. The minimal
|
||||
|
|
|
@ -121,7 +121,7 @@ NOTE:
|
|||
|
||||
## IP range
|
||||
|
||||
GitLab.com is using the IP range `34.74.90.64/28` for traffic from its Web/API
|
||||
GitLab.com uses the IP ranges `34.74.90.64/28` and `34.74.226.0/24` for traffic from its Web/API
|
||||
fleet. This whole range is solely allocated to GitLab. You can expect connections from webhooks or repository mirroring to come
|
||||
from those IPs and allow them.
|
||||
|
||||
|
|
|
@ -274,8 +274,21 @@ To install a package:
|
|||
composer update
|
||||
```
|
||||
|
||||
Or to install the single package:
|
||||
|
||||
```shell
|
||||
composer req <package-name>:<package-version>
|
||||
```
|
||||
|
||||
If successful, you should see output indicating that the package installed successfully.
|
||||
|
||||
You can also install from source (by pulling the Git repository directly) using the
|
||||
`--prefer-source` option:
|
||||
|
||||
```shell
|
||||
composer update --prefer-source
|
||||
```
|
||||
|
||||
WARNING:
|
||||
Never commit the `auth.json` file to your repository. To install packages from a CI/CD job,
|
||||
consider using the [`composer config`](https://getcomposer.org/doc/articles/handling-private-packages.md#satis) tool with your personal access token
|
||||
|
|
|
@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
type: howto
|
||||
---
|
||||
|
||||
# Deploy Tokens
|
||||
# Deploy tokens
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/17894) in GitLab 10.7.
|
||||
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/199370) from **Settings > Repository** in GitLab 12.9.
|
||||
|
@ -23,15 +23,15 @@ Deploy tokens cannot be used with the GitLab API.
|
|||
If you have a key pair, you might want to use [deploy keys](../../project/deploy_keys/index.md)
|
||||
instead.
|
||||
|
||||
## Creating a Deploy Token
|
||||
## Creating a Deploy token
|
||||
|
||||
You can create as many deploy tokens as you need from the settings of your
|
||||
project. Alternatively, you can also create [group-scoped deploy tokens](#group-deploy-token).
|
||||
|
||||
1. Sign in to your GitLab account.
|
||||
1. Go to the project (or group) you want to create Deploy Tokens for.
|
||||
1. Go to the project (or group) you want to create deploy tokens for.
|
||||
1. Go to **Settings > Repository**.
|
||||
1. Click on "Expand" on **Deploy Tokens** section.
|
||||
1. Expand the **Deploy tokens** section.
|
||||
1. Choose a name, expiry date (optional), and username (optional) for the token.
|
||||
1. Choose the [desired scopes](#limiting-scopes-of-a-deploy-token).
|
||||
1. Select **Create deploy token**.
|
||||
|
@ -46,8 +46,8 @@ Deploy tokens expire at midnight UTC on the date you define.
|
|||
|
||||
## Revoking a deploy token
|
||||
|
||||
At any time, you can revoke any deploy token by just clicking the respective
|
||||
**Revoke** button under the 'Active deploy tokens' area.
|
||||
To revoke a deploy token, under the **Active deploy tokens** area,
|
||||
select the respective **Revoke** button.
|
||||
|
||||
## Limiting scopes of a deploy token
|
||||
|
||||
|
@ -75,11 +75,11 @@ username to be used when creating the deploy token.
|
|||
|
||||
### Git clone a repository
|
||||
|
||||
To download a repository using a Deploy Token, you just need to:
|
||||
To download a repository using a deploy token:
|
||||
|
||||
1. Create a Deploy Token with `read_repository` as a scope.
|
||||
1. Create a deploy token with `read_repository` as a scope.
|
||||
1. Take note of your `username` and `token`.
|
||||
1. `git clone` the project using the Deploy Token:
|
||||
1. `git clone` the project using the deploy token:
|
||||
|
||||
```shell
|
||||
git clone https://<username>:<deploy_token>@gitlab.example.com/tanuki/awesome_project.git
|
||||
|
@ -91,7 +91,7 @@ Replace `<username>` and `<deploy_token>` with the proper values.
|
|||
|
||||
To read the container registry images, you must:
|
||||
|
||||
1. Create a Deploy Token with `read_registry` as a scope.
|
||||
1. Create a deploy token with `read_registry` as a scope.
|
||||
1. Take note of your `username` and `token`.
|
||||
1. Sign in to the GitLab Container Registry using the deploy token:
|
||||
|
||||
|
@ -108,7 +108,7 @@ pull images from your Container Registry.
|
|||
|
||||
To push the container registry images, you must:
|
||||
|
||||
1. Create a Deploy Token with `write_registry` as a scope.
|
||||
1. Create a deploy token with `write_registry` as a scope.
|
||||
1. Take note of your `username` and `token`.
|
||||
1. Sign in to the GitLab Container Registry using the deploy token:
|
||||
|
||||
|
@ -125,7 +125,7 @@ push images to your Container Registry.
|
|||
|
||||
To pull packages in the GitLab package registry, you must:
|
||||
|
||||
1. Create a Deploy Token with `read_package_registry` as a scope.
|
||||
1. Create a deploy token with `read_package_registry` as a scope.
|
||||
1. Take note of your `username` and `token`.
|
||||
1. For the [package type of your choice](../../packages/index.md), follow the
|
||||
authentication instructions for deploy tokens.
|
||||
|
@ -152,12 +152,12 @@ Example response:
|
|||
|
||||
To upload packages in the GitLab package registry, you must:
|
||||
|
||||
1. Create a Deploy Token with `write_package_registry` as a scope.
|
||||
1. Create a deploy token with `write_package_registry` as a scope.
|
||||
1. Take note of your `username` and `token`.
|
||||
1. For the [package type of your choice](../../packages/index.md), follow the
|
||||
authentication instructions for deploy tokens.
|
||||
|
||||
### Group Deploy Token
|
||||
### Group deploy token
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/21765) in GitLab 12.9.
|
||||
|
||||
|
@ -167,7 +167,7 @@ belong either to the specific group or to one of its subgroups.
|
|||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
|
||||
For an overview, see [Group Deploy Tokens](https://youtu.be/8kxTJvaD9ks).
|
||||
|
||||
The Group Deploy Tokens UI is now accessible under **Settings > Repository**,
|
||||
The Group deploy tokens UI is now accessible under **Settings > Repository**,
|
||||
not **Settings > CI/CD** as indicated in the video.
|
||||
|
||||
To use a group deploy token:
|
||||
|
@ -179,12 +179,12 @@ To use a group deploy token:
|
|||
The scopes applied to a group deploy token (such as `read_repository`)
|
||||
apply consistently when cloning the repository of related projects.
|
||||
|
||||
### GitLab Deploy Token
|
||||
### GitLab deploy token
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/18414) in GitLab 10.8.
|
||||
|
||||
There's a special case when it comes to Deploy Tokens. If a user creates one
|
||||
named `gitlab-deploy-token`, the username and token of the Deploy Token is
|
||||
There's a special case when it comes to deploy tokens. If a user creates one
|
||||
named `gitlab-deploy-token`, the username and token of the deploy token is
|
||||
automatically exposed to the CI/CD jobs as CI/CD variables: `CI_DEPLOY_USER`
|
||||
and `CI_DEPLOY_PASSWORD`, respectively.
|
||||
|
||||
|
|
|
@ -53,7 +53,7 @@ be merged, and optionally which users should do the approving. Approvals can be
|
|||
|
||||
If no approval rules are defined, any user can approve a merge request. However, the default
|
||||
minimum number of required approvers can still be set in the
|
||||
[project settings for merge request approvals](#merge-request-approvals-project-settings).
|
||||
[settings for merge request approvals](#approval-settings).
|
||||
|
||||
You can opt to define one single rule to approve a merge request among the available rules
|
||||
or choose more than one with [multiple approval rules](#multiple-approval-rules).
|
||||
|
@ -278,9 +278,9 @@ else blocking it. Note that the merge request could still be blocked by other co
|
|||
such as merge conflicts, [pending discussions](../../discussions/index.md#only-allow-merge-requests-to-be-merged-if-all-threads-are-resolved),
|
||||
or a [failed CI/CD pipeline](merge_when_pipeline_succeeds.md).
|
||||
|
||||
### Merge request approvals project settings
|
||||
### Approval settings
|
||||
|
||||
The project settings for Merge request approvals are found by going to
|
||||
The settings for Merge request approvals are found by going to
|
||||
**Settings > General** and expanding **Merge request approvals**.
|
||||
|
||||
#### Prevent overriding default approvals
|
||||
|
|
|
@ -28,20 +28,34 @@ module Gitlab
|
|||
def package_metadata(package)
|
||||
json = package.composer_metadatum.composer_json
|
||||
|
||||
json.merge('dist' => package_dist(package), 'uid' => package.id, 'version' => package.version)
|
||||
json.merge(
|
||||
'dist' => package_dist(package),
|
||||
'source' => package_source(package),
|
||||
'uid' => package.id,
|
||||
'version' => package.version
|
||||
)
|
||||
end
|
||||
|
||||
def package_dist(package)
|
||||
sha = package.composer_metadatum.target_sha
|
||||
archive_api_path = api_v4_projects_packages_composer_archives_package_name_path({ id: package.project_id, package_name: package.name, format: '.zip' }, true)
|
||||
|
||||
{
|
||||
'type' => 'zip',
|
||||
'url' => expose_url(archive_api_path) + "?sha=#{sha}",
|
||||
'reference' => sha,
|
||||
'url' => expose_url(archive_api_path) + "?sha=#{package.composer_target_sha}",
|
||||
'reference' => package.composer_target_sha,
|
||||
'shasum' => ''
|
||||
}
|
||||
end
|
||||
|
||||
def package_source(package)
|
||||
git_url = package.project.http_url_to_repo
|
||||
|
||||
{
|
||||
'type' => 'git',
|
||||
'url' => git_url,
|
||||
'reference' => package.composer_target_sha
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
116
lib/gitlab/graphql/deprecation.rb
Normal file
116
lib/gitlab/graphql/deprecation.rb
Normal file
|
@ -0,0 +1,116 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Graphql
|
||||
class Deprecation
|
||||
REASONS = {
|
||||
renamed: 'This was renamed.',
|
||||
discouraged: 'Use of this is not recommended.'
|
||||
}.freeze
|
||||
|
||||
include ActiveModel::Validations
|
||||
|
||||
validates :milestone, presence: true, format: { with: /\A\d+\.\d+\z/, message: 'must be milestone-ish' }
|
||||
validates :reason, presence: true
|
||||
validates :reason,
|
||||
format: { with: /.*[^.]\z/, message: 'must not end with a period' },
|
||||
if: :reason_is_string?
|
||||
validate :milestone_is_string
|
||||
validate :reason_known_or_string
|
||||
|
||||
def self.parse(options)
|
||||
new(**options) if options
|
||||
end
|
||||
|
||||
def initialize(reason: nil, milestone: nil, replacement: nil)
|
||||
@reason = reason.presence
|
||||
@milestone = milestone.presence
|
||||
@replacement = replacement.presence
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
return false unless other.is_a?(self.class)
|
||||
|
||||
[reason_text, milestone, replacement] == [:reason_text, :milestone, :replacement].map do |attr|
|
||||
other.send(attr) # rubocop: disable GitlabSecurity/PublicSend
|
||||
end
|
||||
end
|
||||
alias_method :eql, :==
|
||||
|
||||
def markdown(context: :inline)
|
||||
parts = [
|
||||
"#{deprecated_in(format: :markdown)}.",
|
||||
reason_text,
|
||||
replacement.then { |r| "Use: `#{r}`." if r }
|
||||
].compact
|
||||
|
||||
case context
|
||||
when :block
|
||||
['WARNING:', *parts].join("\n")
|
||||
when :inline
|
||||
parts.join(' ')
|
||||
end
|
||||
end
|
||||
|
||||
def edit_description(original_description)
|
||||
@original_description = original_description
|
||||
return unless original_description
|
||||
|
||||
original_description + description_suffix
|
||||
end
|
||||
|
||||
def original_description
|
||||
return unless @original_description
|
||||
return @original_description if @original_description.ends_with?('.')
|
||||
|
||||
"#{@original_description}."
|
||||
end
|
||||
|
||||
def deprecation_reason
|
||||
[
|
||||
reason_text,
|
||||
replacement && "Please use `#{replacement}`.",
|
||||
"#{deprecated_in}."
|
||||
].compact.join(' ')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :reason, :milestone, :replacement
|
||||
|
||||
def milestone_is_string
|
||||
return if milestone.is_a?(String)
|
||||
|
||||
errors.add(:milestone, 'must be a string')
|
||||
end
|
||||
|
||||
def reason_known_or_string
|
||||
return if REASONS.key?(reason)
|
||||
return if reason_is_string?
|
||||
|
||||
errors.add(:reason, 'must be a known reason or a string')
|
||||
end
|
||||
|
||||
def reason_is_string?
|
||||
reason.is_a?(String)
|
||||
end
|
||||
|
||||
def reason_text
|
||||
@reason_text ||= REASONS[reason] || "#{reason.to_s.strip}."
|
||||
end
|
||||
|
||||
def description_suffix
|
||||
" #{deprecated_in}: #{reason_text}"
|
||||
end
|
||||
|
||||
def deprecated_in(format: :plain)
|
||||
case format
|
||||
when :plain
|
||||
"Deprecated in #{milestone}"
|
||||
when :markdown
|
||||
"**Deprecated** in #{milestone}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -3844,6 +3844,9 @@ msgstr ""
|
|||
msgid "Approval rules reset to project defaults"
|
||||
msgstr ""
|
||||
|
||||
msgid "Approval settings"
|
||||
msgstr ""
|
||||
|
||||
msgid "ApprovalRuleRemove|%d member"
|
||||
msgid_plural "ApprovalRuleRemove|%d members"
|
||||
msgstr[0] ""
|
||||
|
@ -13893,6 +13896,9 @@ msgstr ""
|
|||
msgid "Geo|In sync"
|
||||
msgstr ""
|
||||
|
||||
msgid "Geo|Internal URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "Geo|Last repository check run"
|
||||
msgstr ""
|
||||
|
||||
|
@ -13917,9 +13923,6 @@ msgstr ""
|
|||
msgid "Geo|Next sync scheduled at"
|
||||
msgstr ""
|
||||
|
||||
msgid "Geo|Node Details"
|
||||
msgstr ""
|
||||
|
||||
msgid "Geo|Node name can't be blank"
|
||||
msgstr ""
|
||||
|
||||
|
@ -13938,6 +13941,9 @@ msgstr ""
|
|||
msgid "Geo|Please refer to Geo Troubleshooting."
|
||||
msgstr ""
|
||||
|
||||
msgid "Geo|Primary Details"
|
||||
msgstr ""
|
||||
|
||||
msgid "Geo|Primary node"
|
||||
msgstr ""
|
||||
|
||||
|
@ -13986,6 +13992,9 @@ msgstr ""
|
|||
msgid "Geo|Review replication status, and resynchronize and reverify items with the primary node."
|
||||
msgstr ""
|
||||
|
||||
msgid "Geo|Secondary Details"
|
||||
msgstr ""
|
||||
|
||||
msgid "Geo|Secondary node"
|
||||
msgstr ""
|
||||
|
||||
|
@ -14214,6 +14223,9 @@ msgstr ""
|
|||
msgid "GitLab uses %{jaeger_link} to monitor distributed systems."
|
||||
msgstr ""
|
||||
|
||||
msgid "GitLab version"
|
||||
msgstr ""
|
||||
|
||||
msgid "GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
|
||||
msgstr ""
|
||||
|
||||
|
|
1
qa/qa.rb
1
qa/qa.rb
|
@ -96,6 +96,7 @@ module QA
|
|||
autoload :ProjectSnippet, 'qa/resource/project_snippet'
|
||||
autoload :Design, 'qa/resource/design'
|
||||
autoload :RegistryRepository, 'qa/resource/registry_repository'
|
||||
autoload :Package, 'qa/resource/package'
|
||||
|
||||
module KubernetesCluster
|
||||
autoload :Base, 'qa/resource/kubernetes_cluster/base'
|
||||
|
|
52
qa/qa/resource/package.rb
Normal file
52
qa/qa/resource/package.rb
Normal file
|
@ -0,0 +1,52 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'securerandom'
|
||||
|
||||
module QA
|
||||
module Resource
|
||||
class Package < Base
|
||||
attr_accessor :name
|
||||
|
||||
attribute :project do
|
||||
Project.fabricate_via_api! do |resource|
|
||||
resource.name = 'project-with-package'
|
||||
resource.description = 'Project with Package'
|
||||
end
|
||||
end
|
||||
|
||||
attribute :id do
|
||||
packages = project.packages
|
||||
|
||||
return unless (this_package = packages&.find { |package| package[:name] == "#{project.path_with_namespace}/#{name}" }) # rubocop:disable Cop/AvoidReturnFromBlocks
|
||||
|
||||
this_package[:id]
|
||||
end
|
||||
|
||||
def fabricate!
|
||||
end
|
||||
|
||||
def fabricate_via_api!
|
||||
resource_web_url(api_get)
|
||||
rescue ResourceNotFoundError
|
||||
super
|
||||
end
|
||||
|
||||
def remove_via_api!
|
||||
packages = project.packages
|
||||
|
||||
if packages && !packages.empty?
|
||||
QA::Runtime::Logger.debug("Deleting package '#{name}' from '#{project.path_with_namespace}' via API")
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def api_delete_path
|
||||
"/projects/#{project.id}/packages/#{id}"
|
||||
end
|
||||
|
||||
def api_get_path
|
||||
"/projects/#{project.id}/packages"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -155,6 +155,10 @@ module QA
|
|||
"#{api_get_path}/registry/repositories"
|
||||
end
|
||||
|
||||
def api_packages_path
|
||||
"#{api_get_path}/packages"
|
||||
end
|
||||
|
||||
def api_commits_path
|
||||
"#{api_get_path}/repository/commits"
|
||||
end
|
||||
|
@ -262,7 +266,11 @@ module QA
|
|||
|
||||
def registry_repositories
|
||||
response = get Runtime::API::Request.new(api_client, "#{api_registry_repositories_path}").url
|
||||
parse_body(response)
|
||||
end
|
||||
|
||||
def packages
|
||||
response = get Runtime::API::Request.new(api_client, "#{api_packages_path}").url
|
||||
parse_body(response)
|
||||
end
|
||||
|
||||
|
|
|
@ -1,18 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'securerandom'
|
||||
|
||||
module QA
|
||||
RSpec.describe 'Package', :orchestrated, :packages do
|
||||
describe 'Composer Repository' do
|
||||
include Runtime::Fixtures
|
||||
|
||||
let(:package_name) { 'my_package' }
|
||||
|
||||
let(:project) do
|
||||
Resource::Project.fabricate_via_api! do |project|
|
||||
project.name = 'composer-package-project'
|
||||
end
|
||||
end
|
||||
|
||||
let(:package) do
|
||||
Resource::Package.new.tap do |package|
|
||||
package.name = "my_package-#{SecureRandom.hex(4)}"
|
||||
package.project = project
|
||||
end
|
||||
end
|
||||
|
||||
let!(:runner) do
|
||||
Resource::Runner.fabricate! do |runner|
|
||||
runner.name = "qa-runner-#{Time.now.to_i}"
|
||||
|
@ -30,7 +37,7 @@ module QA
|
|||
let(:composer_json_file) do
|
||||
<<~EOF
|
||||
{
|
||||
"name": "#{project.path_with_namespace}/#{package_name}",
|
||||
"name": "#{project.path_with_namespace}/#{package.name}",
|
||||
"description": "Library XY",
|
||||
"type": "library",
|
||||
"license": "GPL-3.0-only",
|
||||
|
@ -94,14 +101,15 @@ module QA
|
|||
|
||||
after do
|
||||
runner.remove_via_api!
|
||||
package.remove_via_api!
|
||||
end
|
||||
|
||||
it 'publishes a composer package and deletes it', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1088' do
|
||||
Page::Project::Menu.perform(&:click_packages_link)
|
||||
|
||||
Page::Project::Packages::Index.perform do |index|
|
||||
expect(index).to have_package(package_name)
|
||||
index.click_package(package_name)
|
||||
expect(index).to have_package(package.name)
|
||||
index.click_package(package.name)
|
||||
end
|
||||
|
||||
Page::Project::Packages::Show.perform(&:click_delete)
|
||||
|
@ -109,7 +117,7 @@ module QA
|
|||
Page::Project::Packages::Index.perform do |index|
|
||||
aggregate_failures 'package deletion' do
|
||||
expect(index).to have_content("Package deleted successfully")
|
||||
expect(index).not_to have_package(package_name)
|
||||
expect(index).not_to have_package(package.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,14 +5,19 @@ module QA
|
|||
describe 'Conan Repository' do
|
||||
include Runtime::Fixtures
|
||||
|
||||
let(:package_name) { 'conantest' }
|
||||
|
||||
let(:project) do
|
||||
Resource::Project.fabricate_via_api! do |project|
|
||||
project.name = 'conan-package-project'
|
||||
end
|
||||
end
|
||||
|
||||
let(:package) do
|
||||
Resource::Package.new.tap do |package|
|
||||
package.name = 'conantest'
|
||||
package.project = project
|
||||
end
|
||||
end
|
||||
|
||||
let!(:runner) do
|
||||
Resource::Runner.fabricate! do |runner|
|
||||
runner.name = "qa-runner-#{Time.now.to_i}"
|
||||
|
@ -29,6 +34,7 @@ module QA
|
|||
|
||||
after do
|
||||
runner.remove_via_api!
|
||||
package.remove_via_api!
|
||||
end
|
||||
|
||||
it 'publishes, installs, and deletes a Conan package', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1077' do
|
||||
|
@ -47,10 +53,10 @@ module QA
|
|||
stage: deploy
|
||||
script:
|
||||
- "conan remote add gitlab #{gitlab_address_with_port}/api/v4/projects/#{project.id}/packages/conan"
|
||||
- "conan new #{package_name}/0.1 -t"
|
||||
- "conan new #{package.name}/0.1 -t"
|
||||
- "conan create . mycompany/stable"
|
||||
- "CONAN_LOGIN_USERNAME=ci_user CONAN_PASSWORD=${CI_JOB_TOKEN} conan upload #{package_name}/0.1@mycompany/stable --all --remote=gitlab"
|
||||
- "conan install conantest/0.1@mycompany/stable --remote=gitlab"
|
||||
- "CONAN_LOGIN_USERNAME=ci_user CONAN_PASSWORD=${CI_JOB_TOKEN} conan upload #{package.name}/0.1@mycompany/stable --all --remote=gitlab"
|
||||
- "conan install #{package.name}/0.1@mycompany/stable --remote=gitlab"
|
||||
tags:
|
||||
- "runner-for-#{project.name}"
|
||||
YAML
|
||||
|
@ -71,15 +77,15 @@ module QA
|
|||
Page::Project::Menu.perform(&:click_packages_link)
|
||||
|
||||
Page::Project::Packages::Index.perform do |index|
|
||||
expect(index).to have_package(package_name)
|
||||
index.click_package(package_name)
|
||||
expect(index).to have_package(package.name)
|
||||
index.click_package(package.name)
|
||||
end
|
||||
|
||||
Page::Project::Packages::Show.perform(&:click_delete)
|
||||
|
||||
Page::Project::Packages::Index.perform do |index|
|
||||
expect(index).to have_content("Package deleted successfully")
|
||||
expect(index).not_to have_package(package_name)
|
||||
expect(index).not_to have_package(package.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,14 +3,19 @@
|
|||
module QA
|
||||
RSpec.describe 'Package', :orchestrated, :packages do
|
||||
describe 'Generic Repository' do
|
||||
let(:package_name) { 'my_package' }
|
||||
|
||||
let(:project) do
|
||||
Resource::Project.fabricate_via_api! do |project|
|
||||
project.name = 'generic-package-project'
|
||||
end
|
||||
end
|
||||
|
||||
let(:package) do
|
||||
Resource::Package.new.tap do |package|
|
||||
package.name = "my_package"
|
||||
package.project = project
|
||||
end
|
||||
end
|
||||
|
||||
let!(:runner) do
|
||||
Resource::Runner.fabricate! do |runner|
|
||||
runner.name = "qa-runner-#{Time.now.to_i}"
|
||||
|
@ -90,14 +95,15 @@ module QA
|
|||
|
||||
after do
|
||||
runner.remove_via_api!
|
||||
package.remove_via_api!
|
||||
end
|
||||
|
||||
it 'uploads a generic package, downloads and deletes it', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1108' do
|
||||
Page::Project::Menu.perform(&:click_packages_link)
|
||||
|
||||
Page::Project::Packages::Index.perform do |index|
|
||||
expect(index).to have_package(package_name)
|
||||
index.click_package(package_name)
|
||||
expect(index).to have_package(package.name)
|
||||
index.click_package(package.name)
|
||||
end
|
||||
|
||||
Page::Project::Packages::Show.perform(&:click_delete)
|
||||
|
@ -105,7 +111,7 @@ module QA
|
|||
Page::Project::Packages::Index.perform do |index|
|
||||
aggregate_failures 'package deletion' do
|
||||
expect(index).to have_content("Package deleted successfully")
|
||||
expect(index).to have_no_package(package_name)
|
||||
expect(index).to have_no_package(package.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -23,6 +23,13 @@ module QA
|
|||
end
|
||||
end
|
||||
|
||||
let(:package) do
|
||||
Resource::Package.new.tap do |package|
|
||||
package.name = package_name
|
||||
package.project = project
|
||||
end
|
||||
end
|
||||
|
||||
let!(:runner) do
|
||||
Resource::Runner.fabricate! do |runner|
|
||||
runner.name = "qa-runner-#{Time.now.to_i}"
|
||||
|
@ -39,6 +46,7 @@ module QA
|
|||
|
||||
after do
|
||||
runner.remove_via_api!
|
||||
package.remove_via_api!
|
||||
end
|
||||
|
||||
it 'publishes a maven package via gradle', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1074' do
|
||||
|
|
|
@ -31,6 +31,13 @@ module QA
|
|||
end
|
||||
end
|
||||
|
||||
let(:package) do
|
||||
Resource::Package.new.tap do |package|
|
||||
package.name = package_name
|
||||
package.project = project
|
||||
end
|
||||
end
|
||||
|
||||
let!(:runner) do
|
||||
Resource::Runner.fabricate! do |runner|
|
||||
runner.name = "qa-runner-#{Time.now.to_i}"
|
||||
|
|
|
@ -5,8 +5,7 @@ module QA
|
|||
describe 'npm registry' do
|
||||
include Runtime::Fixtures
|
||||
|
||||
let(:registry_scope) { project.group.sandbox.path }
|
||||
let(:package_name) { "@#{registry_scope}/#{project.name}" }
|
||||
let!(:registry_scope) { project.group.sandbox.path }
|
||||
let(:auth_token) do
|
||||
unless Page::Main::Menu.perform(&:signed_in?)
|
||||
Flow::Login.sign_in
|
||||
|
@ -15,12 +14,23 @@ module QA
|
|||
Resource::PersonalAccessToken.fabricate!.token
|
||||
end
|
||||
|
||||
let(:project) do
|
||||
let!(:project) do
|
||||
Resource::Project.fabricate_via_api! do |project|
|
||||
project.name = 'npm-registry-project'
|
||||
end
|
||||
end
|
||||
|
||||
let(:package) do
|
||||
Resource::Package.new.tap do |package|
|
||||
package.name = "@#{registry_scope}/#{project.name}"
|
||||
package.project = project
|
||||
end
|
||||
end
|
||||
|
||||
after do
|
||||
package.remove_via_api!
|
||||
end
|
||||
|
||||
it 'publishes an npm package and then deletes it', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/944' do
|
||||
uri = URI.parse(Runtime::Scenario.gitlab_address)
|
||||
gitlab_host_with_port = "#{uri.host}:#{uri.port}"
|
||||
|
@ -29,7 +39,7 @@ module QA
|
|||
file_path: 'package.json',
|
||||
content: <<~JSON
|
||||
{
|
||||
"name": "#{package_name}",
|
||||
"name": "#{package.name}",
|
||||
"version": "1.0.0",
|
||||
"description": "Example package for GitLab npm registry",
|
||||
"publishConfig": {
|
||||
|
@ -56,20 +66,20 @@ module QA
|
|||
Page::Project::Menu.perform(&:click_packages_link)
|
||||
|
||||
Page::Project::Packages::Index.perform do |index|
|
||||
expect(index).to have_package(package_name)
|
||||
expect(index).to have_package(package.name)
|
||||
|
||||
index.click_package(package_name)
|
||||
index.click_package(package.name)
|
||||
end
|
||||
|
||||
Page::Project::Packages::Show.perform do |show|
|
||||
expect(show).to have_package_info(package_name, "1.0.0")
|
||||
expect(show).to have_package_info(package.name, "1.0.0")
|
||||
|
||||
show.click_delete
|
||||
end
|
||||
|
||||
Page::Project::Packages::Index.perform do |index|
|
||||
expect(index).to have_content("Package deleted successfully")
|
||||
expect(index).not_to have_package(package_name)
|
||||
expect(index).not_to have_package(package.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,8 +6,6 @@ module QA
|
|||
RSpec.describe 'Package', :orchestrated, :packages do
|
||||
describe 'NuGet Repository' do
|
||||
include Runtime::Fixtures
|
||||
|
||||
let(:package_name) { "dotnetcore-#{SecureRandom.hex(8)}" }
|
||||
let(:project) do
|
||||
Resource::Project.fabricate_via_api! do |project|
|
||||
project.name = 'nuget-package-project'
|
||||
|
@ -15,6 +13,13 @@ module QA
|
|||
end
|
||||
end
|
||||
|
||||
let(:package) do
|
||||
Resource::Package.new.tap do |package|
|
||||
package.name = "dotnetcore-#{SecureRandom.hex(8)}"
|
||||
package.project = project
|
||||
end
|
||||
end
|
||||
|
||||
let(:another_project) do
|
||||
Resource::Project.fabricate_via_api! do |project|
|
||||
project.name = 'nuget-package-install-project'
|
||||
|
@ -43,6 +48,7 @@ module QA
|
|||
after do
|
||||
runner.remove_via_api!
|
||||
another_runner.remove_via_api!
|
||||
package.remove_via_api!
|
||||
end
|
||||
|
||||
it 'publishes a nuget package at the project level, installs and deletes it at the group level', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1073' do
|
||||
|
@ -66,7 +72,7 @@ module QA
|
|||
script:
|
||||
- dotnet restore -p:Configuration=Release
|
||||
- dotnet build -c Release
|
||||
- dotnet pack -c Release -p:PackageID=#{package_name}
|
||||
- dotnet pack -c Release -p:PackageID=#{package.name}
|
||||
- dotnet nuget add source "$CI_SERVER_URL/api/v4/projects/$CI_PROJECT_ID/packages/nuget/index.json" --name gitlab --username gitlab-ci-token --password $CI_JOB_TOKEN --store-password-in-clear-text
|
||||
- dotnet nuget push "bin/Release/*.nupkg" --source gitlab
|
||||
only:
|
||||
|
@ -127,7 +133,7 @@ module QA
|
|||
script:
|
||||
- dotnet nuget locals all --clear
|
||||
- dotnet nuget add source "$CI_SERVER_URL/api/v4/groups/#{another_project.group.id}/-/packages/nuget/index.json" --name gitlab --username gitlab-ci-token --password $CI_JOB_TOKEN --store-password-in-clear-text
|
||||
- "dotnet add otherdotnet.csproj package #{package_name} --version 1.0.0"
|
||||
- "dotnet add otherdotnet.csproj package #{package.name} --version 1.0.0"
|
||||
only:
|
||||
- "#{another_project.default_branch}"
|
||||
tags:
|
||||
|
@ -153,15 +159,15 @@ module QA
|
|||
Page::Group::Menu.perform(&:go_to_group_packages)
|
||||
|
||||
Page::Project::Packages::Index.perform do |index|
|
||||
expect(index).to have_package(package_name)
|
||||
index.click_package(package_name)
|
||||
expect(index).to have_package(package.name)
|
||||
index.click_package(package.name)
|
||||
end
|
||||
|
||||
Page::Project::Packages::Show.perform(&:click_delete)
|
||||
|
||||
Page::Project::Packages::Index.perform do |index|
|
||||
expect(index).to have_content("Package deleted successfully")
|
||||
expect(index).not_to have_package(package_name)
|
||||
expect(index).not_to have_package(package.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,15 +4,19 @@ module QA
|
|||
RSpec.describe 'Package', :orchestrated, :packages do
|
||||
describe 'PyPI Repository' do
|
||||
include Runtime::Fixtures
|
||||
|
||||
let(:package_name) { 'mypypipackage' }
|
||||
|
||||
let(:project) do
|
||||
Resource::Project.fabricate_via_api! do |project|
|
||||
project.name = 'pypi-package-project'
|
||||
end
|
||||
end
|
||||
|
||||
let(:package) do
|
||||
Resource::Package.new.tap do |package|
|
||||
package.name = 'mypypipackage'
|
||||
package.project = project
|
||||
end
|
||||
end
|
||||
|
||||
let!(:runner) do
|
||||
Resource::Runner.fabricate! do |runner|
|
||||
runner.name = "qa-runner-#{Time.now.to_i}"
|
||||
|
@ -87,6 +91,7 @@ module QA
|
|||
|
||||
after do
|
||||
runner.remove_via_api!
|
||||
package.remove_via_api!
|
||||
project&.remove_via_api!
|
||||
end
|
||||
|
||||
|
@ -94,8 +99,8 @@ module QA
|
|||
Page::Project::Menu.perform(&:click_packages_link)
|
||||
|
||||
Page::Project::Packages::Index.perform do |index|
|
||||
expect(index).to have_package(package_name)
|
||||
index.click_package(package_name)
|
||||
expect(index).to have_package(package.name)
|
||||
index.click_package(package.name)
|
||||
end
|
||||
|
||||
Page::Project::Packages::Show.perform(&:click_delete)
|
||||
|
@ -103,7 +108,7 @@ module QA
|
|||
Page::Project::Packages::Index.perform do |index|
|
||||
aggregate_failures do
|
||||
expect(index).to have_content("Package deleted successfully")
|
||||
expect(index).not_to have_package(package_name)
|
||||
expect(index).not_to have_package(package.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -127,8 +132,8 @@ module QA
|
|||
Page::Project::Menu.perform(&:click_packages_link)
|
||||
|
||||
Page::Project::Packages::Index.perform do |index|
|
||||
index.wait_for_package_replication(package_name)
|
||||
expect(index).to have_package(package_name)
|
||||
index.wait_for_package_replication(package.name)
|
||||
expect(index).to have_package(package.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -16,6 +16,15 @@ RSpec.describe Groups::BoardsController do
|
|||
expect { list_boards }.to change(group.boards, :count).by(1)
|
||||
end
|
||||
|
||||
it 'pushes swimlanes_buffered_rendering feature flag' do
|
||||
allow(controller).to receive(:push_frontend_feature_flag).and_call_original
|
||||
|
||||
expect(controller).to receive(:push_frontend_feature_flag)
|
||||
.with(:swimlanes_buffered_rendering, group, default_enabled: :yaml)
|
||||
|
||||
list_boards
|
||||
end
|
||||
|
||||
context 'when format is HTML' do
|
||||
it 'renders template' do
|
||||
list_boards
|
||||
|
@ -98,6 +107,15 @@ RSpec.describe Groups::BoardsController do
|
|||
describe 'GET show' do
|
||||
let!(:board) { create(:board, group: group) }
|
||||
|
||||
it 'pushes swimlanes_buffered_rendering feature flag' do
|
||||
allow(controller).to receive(:push_frontend_feature_flag).and_call_original
|
||||
|
||||
expect(controller).to receive(:push_frontend_feature_flag)
|
||||
.with(:swimlanes_buffered_rendering, group, default_enabled: :yaml)
|
||||
|
||||
read_board board: board
|
||||
end
|
||||
|
||||
context 'when format is HTML' do
|
||||
it 'renders template' do
|
||||
expect { read_board board: board }.to change(BoardGroupRecentVisit, :count).by(1)
|
||||
|
|
|
@ -22,6 +22,15 @@ RSpec.describe Projects::BoardsController do
|
|||
expect(assigns(:boards_endpoint)).to eq project_boards_path(project)
|
||||
end
|
||||
|
||||
it 'pushes swimlanes_buffered_rendering feature flag' do
|
||||
allow(controller).to receive(:push_frontend_feature_flag).and_call_original
|
||||
|
||||
expect(controller).to receive(:push_frontend_feature_flag)
|
||||
.with(:swimlanes_buffered_rendering, project, default_enabled: :yaml)
|
||||
|
||||
list_boards
|
||||
end
|
||||
|
||||
context 'when format is HTML' do
|
||||
it 'renders template' do
|
||||
list_boards
|
||||
|
@ -116,6 +125,15 @@ RSpec.describe Projects::BoardsController do
|
|||
describe 'GET show' do
|
||||
let!(:board) { create(:board, project: project) }
|
||||
|
||||
it 'pushes swimlanes_buffered_rendering feature flag' do
|
||||
allow(controller).to receive(:push_frontend_feature_flag).and_call_original
|
||||
|
||||
expect(controller).to receive(:push_frontend_feature_flag)
|
||||
.with(:swimlanes_buffered_rendering, project, default_enabled: :yaml)
|
||||
|
||||
read_board board: board
|
||||
end
|
||||
|
||||
it 'sets boards_endpoint instance variable to a boards path' do
|
||||
read_board board: board
|
||||
|
||||
|
|
76
spec/frontend/admin/users/new_spec.js
Normal file
76
spec/frontend/admin/users/new_spec.js
Normal file
|
@ -0,0 +1,76 @@
|
|||
import {
|
||||
setupInternalUserRegexHandler,
|
||||
ID_USER_EMAIL,
|
||||
ID_USER_EXTERNAL,
|
||||
ID_WARNING,
|
||||
} from '~/admin/users/new';
|
||||
|
||||
describe('admin/users/new', () => {
|
||||
const FIXTURE = 'admin/users/new_with_internal_user_regex.html';
|
||||
|
||||
let elExternal;
|
||||
let elUserEmail;
|
||||
let elWarningMessage;
|
||||
|
||||
beforeEach(() => {
|
||||
loadFixtures(FIXTURE);
|
||||
setupInternalUserRegexHandler();
|
||||
|
||||
elExternal = document.getElementById(ID_USER_EXTERNAL);
|
||||
elUserEmail = document.getElementById(ID_USER_EMAIL);
|
||||
elWarningMessage = document.getElementById(ID_WARNING);
|
||||
|
||||
elExternal.checked = true;
|
||||
});
|
||||
|
||||
const changeEmail = (val) => {
|
||||
elUserEmail.value = val;
|
||||
elUserEmail.dispatchEvent(new Event('input'));
|
||||
};
|
||||
|
||||
const hasHiddenWarning = () => elWarningMessage.classList.contains('hidden');
|
||||
|
||||
describe('Behaviour of userExternal checkbox', () => {
|
||||
it('hides warning by default', () => {
|
||||
expect(hasHiddenWarning()).toBe(true);
|
||||
});
|
||||
|
||||
describe('when matches email as internal', () => {
|
||||
beforeEach(() => {
|
||||
changeEmail('test@');
|
||||
});
|
||||
|
||||
it('has external unchecked', () => {
|
||||
expect(elExternal.checked).toBe(false);
|
||||
});
|
||||
|
||||
it('shows warning', () => {
|
||||
expect(hasHiddenWarning()).toBe(false);
|
||||
});
|
||||
|
||||
describe('when external is checked again', () => {
|
||||
beforeEach(() => {
|
||||
elExternal.dispatchEvent(new Event('change'));
|
||||
});
|
||||
|
||||
it('hides warning', () => {
|
||||
expect(hasHiddenWarning()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when matches emails as external', () => {
|
||||
beforeEach(() => {
|
||||
changeEmail('test.ext@');
|
||||
});
|
||||
|
||||
it('has external checked', () => {
|
||||
expect(elExternal.checked).toBe(true);
|
||||
});
|
||||
|
||||
it('hides warning', () => {
|
||||
expect(hasHiddenWarning()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,41 +0,0 @@
|
|||
import $ from 'jquery';
|
||||
import UserInternalRegexHandler from '~/pages/admin/users/new/index';
|
||||
|
||||
describe('UserInternalRegexHandler', () => {
|
||||
const FIXTURE = 'admin/users/new_with_internal_user_regex.html';
|
||||
let $userExternal;
|
||||
let $userEmail;
|
||||
let $warningMessage;
|
||||
|
||||
beforeEach(() => {
|
||||
loadFixtures(FIXTURE);
|
||||
// eslint-disable-next-line no-new
|
||||
new UserInternalRegexHandler();
|
||||
$userExternal = $('#user_external');
|
||||
$userEmail = $('#user_email');
|
||||
$warningMessage = $('#warning_external_automatically_set');
|
||||
if (!$userExternal.prop('checked')) $userExternal.prop('checked', 'checked');
|
||||
});
|
||||
|
||||
describe('Behaviour of userExternal checkbox when', () => {
|
||||
it('matches email as internal', (done) => {
|
||||
expect($warningMessage.hasClass('hidden')).toBeTruthy();
|
||||
|
||||
$userEmail.val('test@').trigger('input');
|
||||
|
||||
expect($userExternal.prop('checked')).toBeFalsy();
|
||||
expect($warningMessage.hasClass('hidden')).toBeFalsy();
|
||||
done();
|
||||
});
|
||||
|
||||
it('matches email as external', (done) => {
|
||||
expect($warningMessage.hasClass('hidden')).toBeTruthy();
|
||||
|
||||
$userEmail.val('test.ext@').trigger('input');
|
||||
|
||||
expect($userExternal.prop('checked')).toBeTruthy();
|
||||
expect($warningMessage.hasClass('hidden')).toBeTruthy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -27,6 +27,11 @@ RSpec.describe Gitlab::Composer::VersionIndex do
|
|||
'type' => 'zip',
|
||||
'url' => "http://localhost/api/v4/projects/#{project.id}/packages/composer/archives/#{package.name}.zip?sha=#{branch.target}"
|
||||
},
|
||||
'source' => {
|
||||
'reference' => branch.target,
|
||||
'type' => 'git',
|
||||
'url' => project.http_url_to_repo
|
||||
},
|
||||
'name' => package.name,
|
||||
'uid' => package.id,
|
||||
'version' => package.version
|
||||
|
|
213
spec/lib/gitlab/graphql/deprecation_spec.rb
Normal file
213
spec/lib/gitlab/graphql/deprecation_spec.rb
Normal file
|
@ -0,0 +1,213 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'fast_spec_helper'
|
||||
require 'active_model'
|
||||
|
||||
RSpec.describe ::Gitlab::Graphql::Deprecation do
|
||||
let(:options) { {} }
|
||||
|
||||
subject(:deprecation) { described_class.parse(options) }
|
||||
|
||||
describe '.parse' do
|
||||
context 'with nil' do
|
||||
let(:options) { nil }
|
||||
|
||||
it 'parses to nil' do
|
||||
expect(deprecation).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'with empty options' do
|
||||
let(:options) { {} }
|
||||
|
||||
it 'parses to an empty deprecation' do
|
||||
expect(deprecation).to eq(described_class.new)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with defined options' do
|
||||
let(:options) { { reason: :renamed, milestone: '10.10' } }
|
||||
|
||||
it 'assigns the properties' do
|
||||
expect(deprecation).to eq(described_class.new(reason: 'This was renamed', milestone: '10.10'))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'validations' do
|
||||
let(:options) { { reason: :renamed, milestone: '10.10' } }
|
||||
|
||||
it { is_expected.to be_valid }
|
||||
|
||||
context 'when the milestone is absent' do
|
||||
before do
|
||||
options.delete(:milestone)
|
||||
end
|
||||
|
||||
it { is_expected.not_to be_valid }
|
||||
end
|
||||
|
||||
context 'when the milestone is not milestone-ish' do
|
||||
before do
|
||||
options[:milestone] = 'next year'
|
||||
end
|
||||
|
||||
it { is_expected.not_to be_valid }
|
||||
end
|
||||
|
||||
context 'when the milestone is not a string' do
|
||||
before do
|
||||
options[:milestone] = 10.01
|
||||
end
|
||||
|
||||
it { is_expected.not_to be_valid }
|
||||
end
|
||||
|
||||
context 'when the reason is absent' do
|
||||
before do
|
||||
options.delete(:reason)
|
||||
end
|
||||
|
||||
it { is_expected.not_to be_valid }
|
||||
end
|
||||
|
||||
context 'when the reason is not a known reason' do
|
||||
before do
|
||||
options[:reason] = :not_stylish_enough
|
||||
end
|
||||
|
||||
it { is_expected.not_to be_valid }
|
||||
end
|
||||
|
||||
context 'when the reason is a string' do
|
||||
before do
|
||||
options[:reason] = 'not stylish enough'
|
||||
end
|
||||
|
||||
it { is_expected.to be_valid }
|
||||
end
|
||||
|
||||
context 'when the reason is a string ending with a period' do
|
||||
before do
|
||||
options[:reason] = 'not stylish enough.'
|
||||
end
|
||||
|
||||
it { is_expected.not_to be_valid }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#deprecation_reason' do
|
||||
context 'when there is a replacement' do
|
||||
let(:options) { { reason: :renamed, milestone: '10.10', replacement: 'X.y' } }
|
||||
|
||||
it 'renders as reason-replacement-milestone' do
|
||||
expect(deprecation.deprecation_reason).to eq('This was renamed. Please use `X.y`. Deprecated in 10.10.')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is no replacement' do
|
||||
let(:options) { { reason: :renamed, milestone: '10.10' } }
|
||||
|
||||
it 'renders as reason-milestone' do
|
||||
expect(deprecation.deprecation_reason).to eq('This was renamed. Deprecated in 10.10.')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'processing of reason' do
|
||||
described_class::REASONS.each_key do |known_reason|
|
||||
context "when the reason is a known reason such as #{known_reason.inspect}" do
|
||||
let(:options) { { reason: known_reason } }
|
||||
|
||||
it 'renders the reason_text correctly' do
|
||||
expect(deprecation.deprecation_reason).to start_with(described_class::REASONS[known_reason])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the reason is any other string' do
|
||||
let(:options) { { reason: 'unhelpful' } }
|
||||
|
||||
it 'appends a period' do
|
||||
expect(deprecation.deprecation_reason).to start_with('unhelpful.')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#edit_description' do
|
||||
let(:options) { { reason: :renamed, milestone: '10.10' } }
|
||||
|
||||
it 'appends milestone:reason with a leading space if there is a description' do
|
||||
desc = deprecation.edit_description('Some description.')
|
||||
|
||||
expect(desc).to eq('Some description. Deprecated in 10.10: This was renamed.')
|
||||
end
|
||||
|
||||
it 'returns nil if there is no description' do
|
||||
desc = deprecation.edit_description(nil)
|
||||
|
||||
expect(desc).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '#original_description' do
|
||||
it 'records the description passed to it' do
|
||||
deprecation.edit_description('Some description.')
|
||||
|
||||
expect(deprecation.original_description).to eq('Some description.')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#markdown' do
|
||||
context 'when there is a replacement' do
|
||||
let(:options) { { reason: :renamed, milestone: '10.10', replacement: 'X.y' } }
|
||||
|
||||
context 'when the context is :inline' do
|
||||
it 'renders on one line' do
|
||||
expectation = '**Deprecated** in 10.10. This was renamed. Use: `X.y`.'
|
||||
|
||||
expect(deprecation.markdown).to eq(expectation)
|
||||
expect(deprecation.markdown(context: :inline)).to eq(expectation)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the context is :block' do
|
||||
it 'renders a warning note' do
|
||||
expectation = <<~MD.chomp
|
||||
WARNING:
|
||||
**Deprecated** in 10.10.
|
||||
This was renamed.
|
||||
Use: `X.y`.
|
||||
MD
|
||||
|
||||
expect(deprecation.markdown(context: :block)).to eq(expectation)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is no replacement' do
|
||||
let(:options) { { reason: 'Removed', milestone: '10.10' } }
|
||||
|
||||
context 'when the context is :inline' do
|
||||
it 'renders on one line' do
|
||||
expectation = '**Deprecated** in 10.10. Removed.'
|
||||
|
||||
expect(deprecation.markdown).to eq(expectation)
|
||||
expect(deprecation.markdown(context: :inline)).to eq(expectation)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the context is :block' do
|
||||
it 'renders a warning note' do
|
||||
expectation = <<~MD.chomp
|
||||
WARNING:
|
||||
**Deprecated** in 10.10.
|
||||
Removed.
|
||||
MD
|
||||
|
||||
expect(deprecation.markdown(context: :block)).to eq(expectation)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -9,8 +9,8 @@ RSpec.describe ::Packages::Composer::PackagesPresenter do
|
|||
let_it_be(:json) { { 'name' => package_name } }
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project) { create(:project, :custom_repo, files: { 'composer.json' => json.to_json }, group: group) }
|
||||
let_it_be(:package1) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '1.0.0', json: json) }
|
||||
let_it_be(:package2) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '2.0.0', json: json) }
|
||||
let!(:package1) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '1.0.0', json: json) }
|
||||
let!(:package2) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '2.0.0', json: json) }
|
||||
|
||||
let(:branch) { project.repository.find_branch('master') }
|
||||
|
||||
|
@ -28,6 +28,11 @@ RSpec.describe ::Packages::Composer::PackagesPresenter do
|
|||
'type' => 'zip',
|
||||
'url' => "http://localhost/api/v4/projects/#{project.id}/packages/composer/archives/#{package.name}.zip?sha=#{branch.target}"
|
||||
},
|
||||
'source' => {
|
||||
'reference' => branch.target,
|
||||
'type' => 'git',
|
||||
'url' => "http://localhost/#{group.path}/#{project.path}.git"
|
||||
},
|
||||
'name' => package.name,
|
||||
'uid' => package.id,
|
||||
'version' => package.version
|
||||
|
|
|
@ -13,18 +13,18 @@ RSpec.shared_examples 'Gitlab-style deprecations' do
|
|||
it 'raises an error if a required property is missing', :aggregate_failures do
|
||||
expect { subject(deprecated: { milestone: '1.10' }) }.to raise_error(
|
||||
ArgumentError,
|
||||
'Please provide a `reason` within `deprecated`'
|
||||
include("Reason can't be blank")
|
||||
)
|
||||
expect { subject(deprecated: { reason: 'Deprecation reason' }) }.to raise_error(
|
||||
ArgumentError,
|
||||
'Please provide a `milestone` within `deprecated`'
|
||||
include("Milestone can't be blank")
|
||||
)
|
||||
end
|
||||
|
||||
it 'raises an error if milestone is not a String', :aggregate_failures do
|
||||
expect { subject(deprecated: { milestone: 1.10, reason: 'Deprecation reason' }) }.to raise_error(
|
||||
ArgumentError,
|
||||
'`milestone` must be a `String`'
|
||||
include("Milestone must be a string")
|
||||
)
|
||||
end
|
||||
end
|
||||
|
@ -49,4 +49,22 @@ RSpec.shared_examples 'Gitlab-style deprecations' do
|
|||
|
||||
expect(deprecable.description).to be_nil
|
||||
end
|
||||
|
||||
it 'adds information about the replacement if provided' do
|
||||
deprecable = subject(deprecated: { milestone: '1.10', reason: :renamed, replacement: 'Foo.bar' })
|
||||
|
||||
expect(deprecable.deprecation_reason).to include 'Please use `Foo.bar`'
|
||||
end
|
||||
|
||||
it 'supports named reasons: renamed' do
|
||||
deprecable = subject(deprecated: { milestone: '1.10', reason: :renamed })
|
||||
|
||||
expect(deprecable.deprecation_reason).to include 'This was renamed.'
|
||||
end
|
||||
|
||||
it 'supports named reasons: discouraged' do
|
||||
deprecable = subject(deprecated: { milestone: '1.10', reason: :discouraged })
|
||||
|
||||
expect(deprecable.deprecation_reason).to include 'Use of this is not recommended.'
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue