Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
c77fda905a
commit
f54a50aa82
|
|
@ -1,8 +1,12 @@
|
|||
<script>
|
||||
import { GlLoadingIcon } from '@gitlab/ui';
|
||||
import { n__ } from '~/locale';
|
||||
|
||||
export default {
|
||||
name: 'AssigneeTitle',
|
||||
components: {
|
||||
GlLoadingIcon,
|
||||
},
|
||||
props: {
|
||||
loading: {
|
||||
type: Boolean,
|
||||
|
|
@ -34,7 +38,7 @@ export default {
|
|||
<template>
|
||||
<div class="title hide-collapsed">
|
||||
{{ assigneeTitle }}
|
||||
<i v-if="loading" aria-hidden="true" class="fa fa-spinner fa-spin block-loading"></i>
|
||||
<gl-loading-icon v-if="loading" inline class="align-bottom" />
|
||||
<a
|
||||
v-if="editable"
|
||||
class="js-sidebar-dropdown-toggle edit-link float-right"
|
||||
|
|
|
|||
|
|
@ -15,9 +15,6 @@ module Clusters
|
|||
include ::Clusters::Concerns::ApplicationData
|
||||
include ::Gitlab::Utils::StrongMemoize
|
||||
|
||||
include IgnorableColumns
|
||||
ignore_column :kibana_hostname, remove_with: '12.9', remove_after: '2020-02-22'
|
||||
|
||||
default_value_for :version, VERSION
|
||||
|
||||
def chart
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ class Issue < ApplicationRecord
|
|||
has_many :assignees, class_name: "User", through: :issue_assignees
|
||||
has_many :zoom_meetings
|
||||
has_many :user_mentions, class_name: "IssueUserMention", dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
|
||||
has_many :sent_notifications, as: :noteable
|
||||
|
||||
has_one :sentry_issue
|
||||
|
||||
accepts_nested_attributes_for :sentry_issue
|
||||
|
|
|
|||
|
|
@ -3,7 +3,13 @@
|
|||
class Milestone < ApplicationRecord
|
||||
# Represents a "No Milestone" state used for filtering Issues and Merge
|
||||
# Requests that have no milestone assigned.
|
||||
MilestoneStruct = Struct.new(:title, :name, :id)
|
||||
MilestoneStruct = Struct.new(:title, :name, :id) do
|
||||
# Ensure these models match the interface required for exporting
|
||||
def serializable_hash(_opts = {})
|
||||
{ title: title, name: name, id: id }
|
||||
end
|
||||
end
|
||||
|
||||
None = MilestoneStruct.new('No Milestone', 'No Milestone', 0)
|
||||
Any = MilestoneStruct.new('Any Milestone', '', -1)
|
||||
Upcoming = MilestoneStruct.new('Upcoming', '#upcoming', -2)
|
||||
|
|
@ -128,11 +134,12 @@ class Milestone < ApplicationRecord
|
|||
reorder(nil).group(:state).count
|
||||
end
|
||||
|
||||
def predefined_id?(id)
|
||||
[Any.id, None.id, Upcoming.id, Started.id].include?(id)
|
||||
end
|
||||
|
||||
def predefined?(milestone)
|
||||
milestone == Any ||
|
||||
milestone == None ||
|
||||
milestone == Upcoming ||
|
||||
milestone == Started
|
||||
predefined_id?(milestone&.id)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ module Projects
|
|||
|
||||
private
|
||||
|
||||
def trash_repositories!
|
||||
def trash_project_repositories!
|
||||
unless remove_repository(project.repository)
|
||||
raise_error(s_('DeleteProject|Failed to remove project repository. Please try again or contact administrator.'))
|
||||
end
|
||||
|
|
@ -57,6 +57,18 @@ module Projects
|
|||
end
|
||||
end
|
||||
|
||||
def trash_relation_repositories!
|
||||
unless remove_snippets
|
||||
raise_error(s_('DeleteProject|Failed to remove project snippets. Please try again or contact administrator.'))
|
||||
end
|
||||
end
|
||||
|
||||
def remove_snippets
|
||||
response = Snippets::BulkDestroyService.new(current_user, project.snippets).execute
|
||||
|
||||
response.success?
|
||||
end
|
||||
|
||||
def remove_repository(repository)
|
||||
return true unless repository
|
||||
|
||||
|
|
@ -95,7 +107,8 @@ module Projects
|
|||
|
||||
Project.transaction do
|
||||
log_destroy_event
|
||||
trash_repositories!
|
||||
trash_relation_repositories!
|
||||
trash_project_repositories!
|
||||
|
||||
# Rails attempts to load all related records into memory before
|
||||
# destroying: https://github.com/rails/rails/issues/22510
|
||||
|
|
@ -103,7 +116,7 @@ module Projects
|
|||
#
|
||||
# Exclude container repositories because its before_destroy would be
|
||||
# called multiple times, and it doesn't destroy any database records.
|
||||
project.destroy_dependent_associations_in_batches(exclude: [:container_repositories])
|
||||
project.destroy_dependent_associations_in_batches(exclude: [:container_repositories, :snippets])
|
||||
project.destroy!
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ class Repositories::BaseService < BaseService
|
|||
|
||||
attr_reader :repository
|
||||
|
||||
delegate :project, :disk_path, :full_path, to: :repository
|
||||
delegate :repository_storage, to: :project
|
||||
delegate :container, :disk_path, :full_path, to: :repository
|
||||
delegate :repository_storage, to: :container
|
||||
|
||||
def initialize(repository)
|
||||
@repository = repository
|
||||
|
|
@ -31,7 +31,7 @@ class Repositories::BaseService < BaseService
|
|||
# gitlab/cookies.git -> gitlab/cookies+119+deleted.git
|
||||
#
|
||||
def removal_path
|
||||
"#{disk_path}+#{project.id}#{DELETED_FLAG}"
|
||||
"#{disk_path}+#{container.id}#{DELETED_FLAG}"
|
||||
end
|
||||
|
||||
# If we get a Gitaly error, the repository may be corrupted. We can
|
||||
|
|
@ -40,7 +40,7 @@ class Repositories::BaseService < BaseService
|
|||
def ignore_git_errors(&block)
|
||||
yield
|
||||
rescue Gitlab::Git::CommandError => e
|
||||
Gitlab::GitLogger.warn(class: self.class.name, project_id: project.id, disk_path: disk_path, message: e.to_s)
|
||||
Gitlab::GitLogger.warn(class: self.class.name, container_id: container.id, disk_path: disk_path, message: e.to_s)
|
||||
end
|
||||
|
||||
def move_error(path)
|
||||
|
|
|
|||
|
|
@ -14,11 +14,11 @@ class Repositories::DestroyService < Repositories::BaseService
|
|||
log_info(%Q{Repository "#{disk_path}" moved to "#{removal_path}" for repository "#{full_path}"})
|
||||
|
||||
current_repository = repository
|
||||
project.run_after_commit do
|
||||
container.run_after_commit do
|
||||
Repositories::ShellDestroyService.new(current_repository).execute
|
||||
end
|
||||
|
||||
log_info("Project \"#{project.full_path}\" was removed")
|
||||
log_info("Repository \"#{full_path}\" was removed")
|
||||
|
||||
success
|
||||
else
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Snippets
|
||||
class BulkDestroyService
|
||||
include Gitlab::Allowable
|
||||
|
||||
attr_reader :current_user, :snippets
|
||||
|
||||
DeleteRepositoryError = Class.new(StandardError)
|
||||
SnippetAccessError = Class.new(StandardError)
|
||||
|
||||
def initialize(user, snippets)
|
||||
@current_user = user
|
||||
@snippets = snippets
|
||||
end
|
||||
|
||||
def execute
|
||||
return ServiceResponse.success(message: 'No snippets found.') if snippets.empty?
|
||||
|
||||
user_can_delete_snippets!
|
||||
attempt_delete_repositories!
|
||||
snippets.destroy_all # rubocop: disable DestroyAll
|
||||
|
||||
ServiceResponse.success(message: 'Snippets were deleted.')
|
||||
rescue SnippetAccessError
|
||||
service_response_error("You don't have access to delete these snippets.", 403)
|
||||
rescue DeleteRepositoryError
|
||||
attempt_rollback_repositories
|
||||
service_response_error('Failed to delete snippet repositories.', 400)
|
||||
rescue
|
||||
# In case the delete operation fails
|
||||
attempt_rollback_repositories
|
||||
service_response_error('Failed to remove snippets.', 400)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def user_can_delete_snippets!
|
||||
allowed = DeclarativePolicy.user_scope do
|
||||
snippets.find_each.all? { |snippet| user_can_delete_snippet?(snippet) }
|
||||
end
|
||||
|
||||
raise SnippetAccessError unless allowed
|
||||
end
|
||||
|
||||
def user_can_delete_snippet?(snippet)
|
||||
can?(current_user, :admin_snippet, snippet)
|
||||
end
|
||||
|
||||
def attempt_delete_repositories!
|
||||
snippets.each do |snippet|
|
||||
result = Repositories::DestroyService.new(snippet.repository).execute
|
||||
|
||||
raise DeleteRepositoryError if result[:status] == :error
|
||||
end
|
||||
end
|
||||
|
||||
def attempt_rollback_repositories
|
||||
snippets.each do |snippet|
|
||||
result = Repositories::DestroyRollbackService.new(snippet.repository).execute
|
||||
|
||||
log_rollback_error(snippet) if result[:status] == :error
|
||||
end
|
||||
end
|
||||
|
||||
def log_rollback_error(snippet)
|
||||
Gitlab::AppLogger.error("Repository #{snippet.full_path} in path #{snippet.disk_path} could not be rolled back")
|
||||
end
|
||||
|
||||
def service_response_error(message, http_status)
|
||||
ServiceResponse.error(message: message, http_status: http_status)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -4,12 +4,13 @@ module Snippets
|
|||
class DestroyService
|
||||
include Gitlab::Allowable
|
||||
|
||||
attr_reader :current_user, :project
|
||||
attr_reader :current_user, :snippet
|
||||
|
||||
DestroyError = Class.new(StandardError)
|
||||
|
||||
def initialize(user, snippet)
|
||||
@current_user = user
|
||||
@snippet = snippet
|
||||
@project = snippet&.project
|
||||
end
|
||||
|
||||
def execute
|
||||
|
|
@ -24,16 +25,29 @@ module Snippets
|
|||
)
|
||||
end
|
||||
|
||||
if snippet.destroy
|
||||
ServiceResponse.success(message: 'Snippet was deleted.')
|
||||
else
|
||||
service_response_error('Failed to remove snippet.', 400)
|
||||
end
|
||||
attempt_destroy!
|
||||
|
||||
ServiceResponse.success(message: 'Snippet was deleted.')
|
||||
rescue DestroyError
|
||||
service_response_error('Failed to remove snippet repository.', 400)
|
||||
rescue
|
||||
attempt_rollback_repository
|
||||
service_response_error('Failed to remove snippet.', 400)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :snippet
|
||||
def attempt_destroy!
|
||||
result = Repositories::DestroyService.new(snippet.repository).execute
|
||||
|
||||
raise DestroyError if result[:status] == :error
|
||||
|
||||
snippet.destroy!
|
||||
end
|
||||
|
||||
def attempt_rollback_repository
|
||||
Repositories::DestroyRollbackService.new(snippet.repository).execute
|
||||
end
|
||||
|
||||
def user_can_delete_snippet?
|
||||
can?(current_user, :admin_snippet, snippet)
|
||||
|
|
|
|||
|
|
@ -56,10 +56,13 @@ module Users
|
|||
|
||||
MigrateToGhostUserService.new(user).execute unless options[:hard_delete]
|
||||
|
||||
response = Snippets::BulkDestroyService.new(current_user, user.snippets).execute
|
||||
raise DestroyError, response.message if response.error?
|
||||
|
||||
# Rails attempts to load all related records into memory before
|
||||
# destroying: https://github.com/rails/rails/issues/22510
|
||||
# This ensures we delete records in batches.
|
||||
user.destroy_dependent_associations_in_batches
|
||||
user.destroy_dependent_associations_in_batches(exclude: [:snippets])
|
||||
|
||||
# Destroy the namespace after destroying the user since certain methods may depend on the namespace existing
|
||||
user_data = user.destroy
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
#js-vue-sidebar-assignees{ data: { field: "#{issuable_type}", signed_in: signed_in } }
|
||||
.title.hide-collapsed
|
||||
= _('Assignee')
|
||||
= icon('spinner spin')
|
||||
.spinner.spinner-sm.align-bottom
|
||||
|
||||
.selectbox.hide-collapsed
|
||||
- if assignees.none?
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add/update services to delete snippets repositories
|
||||
merge_request: 22672
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update moved service desk issues notifications
|
||||
merge_request: 25640
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Migrated from .fa-spinner to .spinner in app/assets/javascripts/sidebar/components/assignees/assignee_title.vue
|
||||
merge_request: 24919
|
||||
author: rk4bir
|
||||
type: changed
|
||||
|
|
@ -283,10 +283,17 @@ incoming_email:
|
|||
idle_timeout: 60
|
||||
```
|
||||
|
||||
#### MS Exchange
|
||||
#### Microsoft Exchange Server
|
||||
|
||||
Example configuration for Microsoft Exchange mail server with IMAP enabled. Assumes the
|
||||
catch-all mailbox incoming@exchange.example.com.
|
||||
Example configurations for Microsoft Exchange Server with IMAP enabled. Since
|
||||
Exchange does not support sub-addressing, only two options exist:
|
||||
|
||||
- Catch-all mailbox (recommended for Exchange-only)
|
||||
- Dedicated email address (supports Reply by Email only)
|
||||
|
||||
##### Catch-all mailbox
|
||||
|
||||
Assumes the catch-all mailbox `incoming@exchange.example.com`.
|
||||
|
||||
Example for Omnibus installs:
|
||||
|
||||
|
|
@ -335,11 +342,53 @@ incoming_email:
|
|||
port: 993
|
||||
# Whether the IMAP server uses SSL
|
||||
ssl: true
|
||||
# Whether the IMAP server uses StartTLS
|
||||
start_tls: false
|
||||
|
||||
# The mailbox where incoming mail will end up. Usually "inbox".
|
||||
mailbox: "inbox"
|
||||
# The IDLE command timeout.
|
||||
idle_timeout: 60
|
||||
```
|
||||
|
||||
##### Dedicated email address
|
||||
|
||||
Assumes the dedicated email address `incoming@exchange.example.com`.
|
||||
|
||||
Example for Omnibus installs:
|
||||
|
||||
```ruby
|
||||
gitlab_rails['incoming_email_enabled'] = true
|
||||
|
||||
# Exchange does not support sub-addressing, and we're not using a catch-all mailbox so %{key} is not used here
|
||||
gitlab_rails['incoming_email_address'] = "incoming@exchange.example.com"
|
||||
|
||||
# Email account username
|
||||
# Typically this is the userPrincipalName (UPN)
|
||||
gitlab_rails['incoming_email_email'] = "incoming@ad-domain.example.com"
|
||||
# Email account password
|
||||
gitlab_rails['incoming_email_password'] = "[REDACTED]"
|
||||
|
||||
# IMAP server host
|
||||
gitlab_rails['incoming_email_host'] = "exchange.example.com"
|
||||
# IMAP server port
|
||||
gitlab_rails['incoming_email_port'] = 993
|
||||
# Whether the IMAP server uses SSL
|
||||
gitlab_rails['incoming_email_ssl'] = true
|
||||
```
|
||||
|
||||
Example for source installs:
|
||||
|
||||
```yaml
|
||||
incoming_email:
|
||||
enabled: true
|
||||
|
||||
# Exchange does not support sub-addressing, and we're not using a catch-all mailbox so %{key} is not used here
|
||||
address: "incoming@exchange.example.com"
|
||||
|
||||
# Email account username
|
||||
# Typically this is the userPrincipalName (UPN)
|
||||
user: "incoming@ad-domain.example.com"
|
||||
# Email account password
|
||||
password: "[REDACTED]"
|
||||
|
||||
# IMAP server host
|
||||
host: "exchange.example.com"
|
||||
# IMAP server port
|
||||
port: 993
|
||||
# Whether the IMAP server uses SSL
|
||||
ssl: true
|
||||
```
|
||||
|
|
|
|||
|
|
@ -37,23 +37,9 @@ Activity history for projects and individuals' profiles was limited to one year
|
|||
|
||||
## Number of webhooks
|
||||
|
||||
A maximum number of webhooks applies to each GitLab.com tier. Limits apply to project and group webhooks.
|
||||
On GitLab.com, the [maximum number of webhooks](../user/gitlab_com/index.md#maximum-number-of-webhooks) per project, and per group, is limited.
|
||||
|
||||
### Project Webhooks
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/20730) in GitLab 12.6.
|
||||
|
||||
Check the [Maximum number of project webhooks (per tier)](../user/project/integrations/webhooks.md#maximum-number-of-project-webhooks-per-tier) section in the Webhooks page.
|
||||
|
||||
### Group Webhooks
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/25129) in GitLab 12.9.
|
||||
|
||||
Check the [Maximum number of group webhooks (per tier)](../user/project/integrations/webhooks.md#maximum-number-of-group-webhooks-per-tier) section in the Webhooks page.
|
||||
|
||||
### Setting the limit on a self-hosted installation
|
||||
|
||||
To set this limit on a self-hosted installation, run the following in the
|
||||
To set this limit on a self-managed installation, run the following in the
|
||||
[GitLab Rails console](https://docs.gitlab.com/omnibus/maintenance/#starting-a-rails-console-session):
|
||||
|
||||
```ruby
|
||||
|
|
|
|||
|
|
@ -260,7 +260,7 @@ returned with status code `404`:
|
|||
Example of a valid API call and a request using cURL with sudo request,
|
||||
providing a username:
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects?private_token=<your_access_token>&sudo=username
|
||||
```
|
||||
|
||||
|
|
@ -271,7 +271,7 @@ curl --header "Private-Token: <your_access_token>" --header "Sudo: username" "ht
|
|||
Example of a valid API call and a request using cURL with sudo request,
|
||||
providing an ID:
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects?private_token=<your_access_token>&sudo=23
|
||||
```
|
||||
|
||||
|
|
@ -444,7 +444,7 @@ URL-encoded.
|
|||
|
||||
For example, `/` is represented by `%2F`:
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /api/v4/projects/diaspora%2Fdiaspora
|
||||
```
|
||||
|
||||
|
|
@ -460,7 +460,7 @@ URL-encoded.
|
|||
|
||||
For example, `/` is represented by `%2F`:
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /api/v4/projects/1/branches/my%2Fbranch/commits
|
||||
```
|
||||
|
||||
|
|
@ -604,13 +604,13 @@ to a [W3 recommendation](http://www.w3.org/Addressing/URL/4_URI_Recommentations.
|
|||
causes a `+` to be interpreted as a space. For example, in an ISO 8601 date, you may want to pass
|
||||
a time in Mountain Standard Time, such as:
|
||||
|
||||
```
|
||||
```plaintext
|
||||
2017-10-17T23:11:13.000+05:30
|
||||
```
|
||||
|
||||
The correct encoding for the query parameter would be:
|
||||
|
||||
```
|
||||
```plaintext
|
||||
2017-10-17T23:11:13.000%2B05:30
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ Epics are available only in the [Ultimate/Gold tier](https://about.gitlab.com/pr
|
|||
|
||||
Gets all child epics of an epic.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /groups/:id/epics/:epic_iid/epics
|
||||
```
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ Example response:
|
|||
|
||||
Creates an association between two epics, designating one as the parent epic and the other as the child epic. A parent epic can have multiple child epics. If the new child epic already belonged to another epic, it is unassigned from that previous parent.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
POST /groups/:id/epics/:epic_iid/epics
|
||||
```
|
||||
|
||||
|
|
@ -122,7 +122,7 @@ Example response:
|
|||
|
||||
Creates a a new epic and associates it with provided parent epic. The response is LinkedEpic object.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
POST /groups/:id/epics/:epic_iid/epics
|
||||
```
|
||||
|
||||
|
|
@ -155,7 +155,7 @@ Example response:
|
|||
|
||||
## Re-order a child epic
|
||||
|
||||
```
|
||||
```plaintext
|
||||
PUT /groups/:id/epics/:epic_iid/epics/:child_epic_id
|
||||
```
|
||||
|
||||
|
|
@ -212,7 +212,7 @@ Example response:
|
|||
|
||||
Unassigns a child epic from a parent epic.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
DELETE /groups/:id/epics/:epic_iid/epics/:child_epic_id
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ are [paginated](README.md#pagination).
|
|||
|
||||
Gets all feature flags of the requested project.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects/:id/feature_flags
|
||||
```
|
||||
|
||||
|
|
@ -145,7 +145,7 @@ Example response:
|
|||
|
||||
Creates a new feature flag.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
POST /projects/:id/feature_flags
|
||||
```
|
||||
|
||||
|
|
@ -219,7 +219,7 @@ Example response:
|
|||
|
||||
Gets a single feature flag.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects/:id/feature_flags/:name
|
||||
```
|
||||
|
||||
|
|
@ -294,7 +294,7 @@ Example response:
|
|||
|
||||
Deletes a feature flag.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
DELETE /projects/:id/feature_flags/:name
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ Example response:
|
|||
|
||||
Gets issues count statistics for given project.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects/:id/issues_statistics
|
||||
GET /projects/:id/issues_statistics?labels=foo
|
||||
GET /projects/:id/issues_statistics?labels=foo,bar
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
The access levels are defined in the `Gitlab::Access` module. Currently, these levels are recognized:
|
||||
|
||||
```
|
||||
```plaintext
|
||||
10 => Guest access
|
||||
20 => Reporter access
|
||||
30 => Developer access
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ easily accessible, therefore secrets can leak easily.
|
|||
To request the access token, you should redirect the user to the
|
||||
`/oauth/authorize` endpoint using `token` response type:
|
||||
|
||||
```
|
||||
```plaintext
|
||||
https://gitlab.example.com/oauth/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&response_type=token&state=YOUR_UNIQUE_STATE_HASH&scope=REQUESTED_SCOPES
|
||||
```
|
||||
|
||||
|
|
@ -124,7 +124,7 @@ would request `read_user` and `profile` scopes). The redirect
|
|||
will include a fragment with `access_token` as well as token details in GET
|
||||
parameters, for example:
|
||||
|
||||
```
|
||||
```plaintext
|
||||
http://myapp.com/oauth/redirect#access_token=ABCDExyz123&state=YOUR_UNIQUE_STATE_HASH&token_type=bearer&expires_in=3600
|
||||
```
|
||||
|
||||
|
|
@ -182,7 +182,7 @@ curl --data "@auth.txt" --request POST https://gitlab.example.com/oauth/token
|
|||
|
||||
Then, you'll receive the access token back in the response:
|
||||
|
||||
```
|
||||
```json
|
||||
{
|
||||
"access_token": "1f0af717251950dbd4d73154fdf0a474a5c5119adad999683f5b450c460726aa",
|
||||
"token_type": "bearer",
|
||||
|
|
@ -192,7 +192,7 @@ Then, you'll receive the access token back in the response:
|
|||
|
||||
For testing, you can use the `oauth2` Ruby gem:
|
||||
|
||||
```
|
||||
```ruby
|
||||
client = OAuth2::Client.new('the_client_id', 'the_client_secret', :site => "http://example.com")
|
||||
access_token = client.password.get_token('user@example.com', 'secret')
|
||||
puts access_token.token
|
||||
|
|
@ -203,13 +203,13 @@ puts access_token.token
|
|||
The `access token` allows you to make requests to the API on behalf of a user.
|
||||
You can pass the token either as GET parameter:
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET https://gitlab.example.com/api/v4/user?access_token=OAUTH-TOKEN
|
||||
```
|
||||
|
||||
or you can put the token to the Authorization header:
|
||||
|
||||
```
|
||||
```shell
|
||||
curl --header "Authorization: Bearer OAUTH-TOKEN" https://gitlab.example.com/api/v4/user
|
||||
```
|
||||
|
||||
|
|
@ -222,7 +222,7 @@ You must supply the access token, either:
|
|||
|
||||
- As a parameter:
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET https://gitlab.example.com/oauth/token/info?access_token=<OAUTH-TOKEN>
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ This is the API docs of [GitLab Packages](../administration/packages/index.md).
|
|||
Get a list of project packages. All package types are included in results. When
|
||||
accessed without authentication, only packages of public projects are returned.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects/:id/packages
|
||||
```
|
||||
|
||||
|
|
@ -56,7 +56,7 @@ By default, the `GET` request will return 20 results, since the API is [paginate
|
|||
Get a list of project packages at the group level.
|
||||
When accessed without authentication, only packages of public projects are returned.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /groups/:id/packages
|
||||
```
|
||||
|
||||
|
|
@ -135,7 +135,7 @@ The `_links` object contains the following properties:
|
|||
|
||||
Get a single project package.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects/:id/packages/:package_id
|
||||
```
|
||||
|
||||
|
|
@ -186,7 +186,7 @@ The `_links` object contains the following properties:
|
|||
|
||||
Get a list of package files of a single package.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects/:id/packages/:package_id/package_files
|
||||
```
|
||||
|
||||
|
|
@ -241,7 +241,7 @@ By default, the `GET` request will return 20 results, since the API is [paginate
|
|||
|
||||
Deletes a project package.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
DELETE /projects/:id/packages/:package_id
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ You can read more about [pipeline schedules](../user/project/pipelines/schedules
|
|||
|
||||
Get a list of the pipeline schedules of a project.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects/:id/pipeline_schedules
|
||||
```
|
||||
|
||||
|
|
@ -47,7 +47,7 @@ curl --header "PRIVATE-TOKEN: k5ESFgWY2Qf5xEvDcFxZ" "https://gitlab.example.com/
|
|||
|
||||
Get the pipeline schedule of a project.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects/:id/pipeline_schedules/:pipeline_schedule_id
|
||||
```
|
||||
|
||||
|
|
@ -99,7 +99,7 @@ curl --header "PRIVATE-TOKEN: k5ESFgWY2Qf5xEvDcFxZ" "https://gitlab.example.com/
|
|||
|
||||
Create a new pipeline schedule of a project.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
POST /projects/:id/pipeline_schedules
|
||||
```
|
||||
|
||||
|
|
@ -143,7 +143,7 @@ curl --request POST --header "PRIVATE-TOKEN: k5ESFgWY2Qf5xEvDcFxZ" --form descri
|
|||
|
||||
Updates the pipeline schedule of a project. Once the update is done, it will be rescheduled automatically.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
PUT /projects/:id/pipeline_schedules/:pipeline_schedule_id
|
||||
```
|
||||
|
||||
|
|
@ -193,7 +193,7 @@ curl --request PUT --header "PRIVATE-TOKEN: k5ESFgWY2Qf5xEvDcFxZ" --form cron="0
|
|||
|
||||
Update the owner of the pipeline schedule of a project.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
POST /projects/:id/pipeline_schedules/:pipeline_schedule_id/take_ownership
|
||||
```
|
||||
|
||||
|
|
@ -238,7 +238,7 @@ curl --request POST --header "PRIVATE-TOKEN: hf2CvZXB9w8Uc5pZKpSB" "https://gitl
|
|||
|
||||
Delete the pipeline schedule of a project.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
DELETE /projects/:id/pipeline_schedules/:pipeline_schedule_id
|
||||
```
|
||||
|
||||
|
|
@ -317,7 +317,7 @@ Example response:
|
|||
|
||||
Create a new variable of a pipeline schedule.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
POST /projects/:id/pipeline_schedules/:pipeline_schedule_id/variables
|
||||
```
|
||||
|
||||
|
|
@ -345,7 +345,7 @@ curl --request POST --header "PRIVATE-TOKEN: k5ESFgWY2Qf5xEvDcFxZ" --form "key=N
|
|||
|
||||
Updates the variable of a pipeline schedule.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
PUT /projects/:id/pipeline_schedules/:pipeline_schedule_id/variables/:key
|
||||
```
|
||||
|
||||
|
|
@ -373,7 +373,7 @@ curl --request PUT --header "PRIVATE-TOKEN: k5ESFgWY2Qf5xEvDcFxZ" --form "value=
|
|||
|
||||
Delete the variable of a pipeline schedule.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
DELETE /projects/:id/pipeline_schedules/:pipeline_schedule_id/variables/:key
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
> [Introduced][ce-5837] in GitLab 8.11
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects/:id/pipelines
|
||||
```
|
||||
|
||||
|
|
@ -23,7 +23,7 @@ GET /projects/:id/pipelines
|
|||
| `order_by`| string | no | Order pipelines by `id`, `status`, `ref`, `updated_at` or `user_id` (default: `id`) |
|
||||
| `sort` | string | no | Sort pipelines in `asc` or `desc` order (default: `desc`) |
|
||||
|
||||
```
|
||||
```shell
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/pipelines"
|
||||
```
|
||||
|
||||
|
|
@ -56,7 +56,7 @@ Example of response
|
|||
|
||||
> [Introduced][ce-5837] in GitLab 8.11
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects/:id/pipelines/:pipeline_id
|
||||
```
|
||||
|
||||
|
|
@ -65,7 +65,7 @@ GET /projects/:id/pipelines/:pipeline_id
|
|||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `pipeline_id` | integer | yes | The ID of a pipeline |
|
||||
|
||||
```
|
||||
```shell
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/pipelines/46"
|
||||
```
|
||||
|
||||
|
|
@ -101,7 +101,7 @@ Example of response
|
|||
|
||||
### Get variables of a pipeline
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects/:id/pipelines/:pipeline_id/variables
|
||||
```
|
||||
|
||||
|
|
@ -110,7 +110,7 @@ GET /projects/:id/pipelines/:pipeline_id/variables
|
|||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `pipeline_id` | integer | yes | The ID of a pipeline |
|
||||
|
||||
```
|
||||
```shell
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/pipelines/46/variables"
|
||||
```
|
||||
|
||||
|
|
@ -134,7 +134,7 @@ Example of response
|
|||
|
||||
> [Introduced][ce-7209] in GitLab 8.14
|
||||
|
||||
```
|
||||
```plaintext
|
||||
POST /projects/:id/pipeline
|
||||
```
|
||||
|
||||
|
|
@ -144,7 +144,7 @@ POST /projects/:id/pipeline
|
|||
| `ref` | string | yes | Reference to commit |
|
||||
| `variables` | array | no | An array containing the variables available in the pipeline, matching the structure `[{ 'key' => 'UPLOAD_TO_S3', 'variable_type' => 'file', 'value' => 'true' }]` |
|
||||
|
||||
```
|
||||
```shell
|
||||
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/pipeline?ref=master"
|
||||
```
|
||||
|
||||
|
|
@ -182,7 +182,7 @@ Example of response
|
|||
|
||||
> [Introduced][ce-5837] in GitLab 8.11
|
||||
|
||||
```
|
||||
```plaintext
|
||||
POST /projects/:id/pipelines/:pipeline_id/retry
|
||||
```
|
||||
|
||||
|
|
@ -191,7 +191,7 @@ POST /projects/:id/pipelines/:pipeline_id/retry
|
|||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `pipeline_id` | integer | yes | The ID of a pipeline |
|
||||
|
||||
```
|
||||
```shell
|
||||
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/pipelines/46/retry"
|
||||
```
|
||||
|
||||
|
|
@ -229,7 +229,7 @@ Response:
|
|||
|
||||
> [Introduced][ce-5837] in GitLab 8.11
|
||||
|
||||
```
|
||||
```plaintext
|
||||
POST /projects/:id/pipelines/:pipeline_id/cancel
|
||||
```
|
||||
|
||||
|
|
@ -238,7 +238,7 @@ POST /projects/:id/pipelines/:pipeline_id/cancel
|
|||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `pipeline_id` | integer | yes | The ID of a pipeline |
|
||||
|
||||
```
|
||||
```shell
|
||||
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/pipelines/46/cancel"
|
||||
```
|
||||
|
||||
|
|
@ -276,7 +276,7 @@ Response:
|
|||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/22988) in GitLab 11.6.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
DELETE /projects/:id/pipelines/:pipeline_id
|
||||
```
|
||||
|
||||
|
|
@ -285,7 +285,7 @@ DELETE /projects/:id/pipelines/:pipeline_id
|
|||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `pipeline_id` | integer | yes | The ID of a pipeline |
|
||||
|
||||
```
|
||||
```shell
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" --request "DELETE" "https://gitlab.example.com/api/v4/projects/1/pipelines/46"
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ All methods require administrator authorization.
|
|||
|
||||
Get a list of all project aliases:
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /project_aliases
|
||||
```
|
||||
|
||||
```
|
||||
```shell
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/project_aliases"
|
||||
```
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ Example response:
|
|||
|
||||
Get details of a project alias:
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /project_aliases/:name
|
||||
```
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ GET /project_aliases/:name
|
|||
|-----------|--------|----------|-----------------------|
|
||||
| `name` | string | yes | The name of the alias |
|
||||
|
||||
```
|
||||
```shell
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/project_aliases/gitlab"
|
||||
```
|
||||
|
||||
|
|
@ -64,7 +64,7 @@ Example response:
|
|||
Add a new alias for a project. Responds with a 201 when successful,
|
||||
400 when there are validation errors (e.g. alias already exists):
|
||||
|
||||
```
|
||||
```plaintext
|
||||
POST /project_aliases
|
||||
```
|
||||
|
||||
|
|
@ -73,13 +73,13 @@ POST /project_aliases
|
|||
| `project_id` | integer/string | yes | The ID or path of the project. |
|
||||
| `name` | string | yes | The name of the alias. Must be unique. |
|
||||
|
||||
```
|
||||
```shell
|
||||
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/project_aliases" --form "project_id=1" --form "name=gitlab"
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
```shell
|
||||
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/project_aliases" --form "project_id=gitlab-org/gitlab" --form "name=gitlab"
|
||||
```
|
||||
|
||||
|
|
@ -98,7 +98,7 @@ Example response:
|
|||
Removes a project aliases. Responds with a 204 when project alias
|
||||
exists, 404 when it doesn't:
|
||||
|
||||
```
|
||||
```plaintext
|
||||
DELETE /project_aliases/:name
|
||||
```
|
||||
|
||||
|
|
@ -106,6 +106,6 @@ DELETE /project_aliases/:name
|
|||
|-----------|--------|----------|-----------------------|
|
||||
| `name` | string | yes | The name of the alias |
|
||||
|
||||
```
|
||||
```shell
|
||||
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/project_aliases/gitlab"
|
||||
```
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ Badges support placeholders that will be replaced in real time in both the link
|
|||
|
||||
Gets a list of a project's badges and its group badges.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects/:id/badges
|
||||
```
|
||||
|
||||
|
|
@ -58,7 +58,7 @@ Example response:
|
|||
|
||||
Gets a badge of a project.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects/:id/badges/:badge_id
|
||||
```
|
||||
|
||||
|
|
@ -88,7 +88,7 @@ Example response:
|
|||
|
||||
Adds a badge to a project.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
POST /projects/:id/badges
|
||||
```
|
||||
|
||||
|
|
@ -119,7 +119,7 @@ Example response:
|
|||
|
||||
Updates a badge of a project.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
PUT /projects/:id/badges/:badge_id
|
||||
```
|
||||
|
||||
|
|
@ -151,7 +151,7 @@ Example response:
|
|||
|
||||
Removes a badge from a project. Only project's badges will be removed by using this endpoint.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
DELETE /projects/:id/badges/:badge_id
|
||||
```
|
||||
|
||||
|
|
@ -168,7 +168,7 @@ curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitl
|
|||
|
||||
Returns how the `link_url` and `image_url` final URLs would be after resolving the placeholder interpolation.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects/:id/badges/render
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ User will need at least maintainer access to use these endpoints.
|
|||
|
||||
Returns a list of project clusters.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects/:id/clusters
|
||||
```
|
||||
|
||||
|
|
@ -368,7 +368,7 @@ Example response:
|
|||
|
||||
Deletes an existing project cluster.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
DELETE /projects/:id/clusters/:cluster_id
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
Get list of a project's variables.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects/:id/variables
|
||||
```
|
||||
|
||||
|
|
@ -12,7 +12,7 @@ GET /projects/:id/variables
|
|||
|-----------|---------|----------|---------------------|
|
||||
| `id` | integer/string | yes | The ID of a project or [urlencoded NAMESPACE/PROJECT_NAME of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
|
||||
```
|
||||
```shell
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/variables"
|
||||
```
|
||||
|
||||
|
|
@ -35,7 +35,7 @@ curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/a
|
|||
|
||||
Get the details of a project's specific variable.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects/:id/variables/:key
|
||||
```
|
||||
|
||||
|
|
@ -44,7 +44,7 @@ GET /projects/:id/variables/:key
|
|||
| `id` | integer/string | yes | The ID of a project or [urlencoded NAMESPACE/PROJECT_NAME of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `key` | string | yes | The `key` of a variable |
|
||||
|
||||
```
|
||||
```shell
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/variables/TEST_VARIABLE_1"
|
||||
```
|
||||
|
||||
|
|
@ -62,7 +62,7 @@ curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/a
|
|||
|
||||
Create a new variable.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
POST /projects/:id/variables
|
||||
```
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ POST /projects/:id/variables
|
|||
| `masked` | boolean | no | Whether the variable is masked |
|
||||
| `environment_scope` | string | no | The `environment_scope` of the variable |
|
||||
|
||||
```
|
||||
```shell
|
||||
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/variables" --form "key=NEW_VARIABLE" --form "value=new value"
|
||||
```
|
||||
|
||||
|
|
@ -95,7 +95,7 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitla
|
|||
|
||||
Update a project's variable.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
PUT /projects/:id/variables/:key
|
||||
```
|
||||
|
||||
|
|
@ -109,7 +109,7 @@ PUT /projects/:id/variables/:key
|
|||
| `masked` | boolean | no | Whether the variable is masked |
|
||||
| `environment_scope` | string | no | The `environment_scope` of the variable |
|
||||
|
||||
```
|
||||
```shell
|
||||
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/variables/NEW_VARIABLE" --form "value=updated value"
|
||||
```
|
||||
|
||||
|
|
@ -128,7 +128,7 @@ curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab
|
|||
|
||||
Remove a project's variable.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
DELETE /projects/:id/variables/:key
|
||||
```
|
||||
|
||||
|
|
@ -137,6 +137,6 @@ DELETE /projects/:id/variables/:key
|
|||
| `id` | integer/string | yes | The ID of a project or [urlencoded NAMESPACE/PROJECT_NAME of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `key` | string | yes | The `key` of a variable |
|
||||
|
||||
```
|
||||
```shell
|
||||
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/variables/VARIABLE_1"
|
||||
```
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ visibility setting keep this setting. You can read more about the change in the
|
|||
|
||||
Get a list of project snippets.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects/:id/snippets
|
||||
```
|
||||
|
||||
|
|
@ -35,7 +35,7 @@ Parameters:
|
|||
|
||||
Get a single project snippet.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects/:id/snippets/:snippet_id
|
||||
```
|
||||
|
||||
|
|
@ -68,7 +68,7 @@ Parameters:
|
|||
|
||||
Creates a new project snippet. The user must have permission to create new snippets.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
POST /projects/:id/snippets
|
||||
```
|
||||
|
||||
|
|
@ -106,7 +106,7 @@ curl --request POST https://gitlab.com/api/v4/projects/:id/snippets \
|
|||
|
||||
Updates an existing project snippet. The user must have permission to change an existing snippet.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
PUT /projects/:id/snippets/:snippet_id
|
||||
```
|
||||
|
||||
|
|
@ -145,7 +145,7 @@ curl --request PUT https://gitlab.com/api/v4/projects/:id/snippets/:snippet_id \
|
|||
|
||||
Deletes an existing project snippet. This returns a `204 No Content` status code if the operation was successfully or `404` if the resource was not found.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
DELETE /projects/:id/snippets/:snippet_id
|
||||
```
|
||||
|
||||
|
|
@ -165,7 +165,7 @@ curl --request DELETE https://gitlab.com/api/v4/projects/:id/snippets/:snippet_i
|
|||
|
||||
Returns the raw project snippet as plain text.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects/:id/snippets/:snippet_id/raw
|
||||
```
|
||||
|
||||
|
|
@ -187,7 +187,7 @@ curl https://gitlab.com/api/v4/projects/:id/snippets/:snippet_id/raw \
|
|||
|
||||
Available only for admins.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects/:id/snippets/:snippet_id/user_agent_detail
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ Retrieving the statistics requires write access to the repository.
|
|||
Currently only HTTP fetches statistics are returned.
|
||||
Fetches statistics includes both clones and pulls count and are HTTP only, SSH fetches are not included.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects/:id/statistics
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ in GitLab 11.5
|
|||
|
||||
## Get all templates of a particular type
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects/:id/templates/:type
|
||||
```
|
||||
|
||||
|
|
@ -87,7 +87,7 @@ Example response (licenses):
|
|||
|
||||
## Get one template of a particular type
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects/:id/templates/:type/:key
|
||||
```
|
||||
|
||||
|
|
@ -106,7 +106,6 @@ Example response (Dockerfile):
|
|||
"name": "Binary",
|
||||
"content": "# This file is a template, and might need editing before it works on your project.\n# This Dockerfile installs a compiled binary into a bare system.\n# You must either commit your compiled binary into source control (not recommended)\n# or build the binary first as part of a CI/CD pipeline.\n\nFROM buildpack-deps:jessie\n\nWORKDIR /usr/local/bin\n\n# Change `app` to whatever your binary is called\nAdd app .\nCMD [\"./app\"]\n"
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Example response (license):
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ There are currently three options for `merge_method` to choose from:
|
|||
Get a list of all visible projects across GitLab for the authenticated user.
|
||||
When accessed without authentication, only public projects with "simple" fields are returned.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects
|
||||
```
|
||||
|
||||
|
|
@ -298,7 +298,7 @@ the `approvals_before_merge` parameter:
|
|||
|
||||
You can filter by [custom attributes](custom_attributes.md) with:
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects?custom_attributes[key]=value&custom_attributes[other_key]=other_value
|
||||
```
|
||||
|
||||
|
|
@ -315,7 +315,7 @@ Note that keyset pagination only supports `order_by=id`. Other sorting options a
|
|||
|
||||
Get a list of visible projects owned by the given user. When accessed without authentication, only public projects are returned.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /users/:user_id/projects
|
||||
```
|
||||
|
||||
|
|
@ -530,7 +530,7 @@ This endpoint supports [keyset pagination](README.md#keyset-based-pagination) fo
|
|||
|
||||
Get a list of visible projects owned by the given user. When accessed without authentication, only public projects are returned.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /users/:user_id/starred_projects
|
||||
```
|
||||
|
||||
|
|
@ -740,7 +740,7 @@ Example response:
|
|||
Get a specific project. This endpoint can be accessed without authentication if
|
||||
the project is publicly accessible.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects/:id
|
||||
```
|
||||
|
||||
|
|
@ -955,7 +955,7 @@ If the project is a fork, and you provide a valid token to authenticate, the
|
|||
|
||||
Get the users list of a project.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects/:id/users
|
||||
```
|
||||
|
||||
|
|
@ -993,7 +993,7 @@ Please refer to the [Events API documentation](events.md#list-a-projects-visible
|
|||
|
||||
Creates a new project owned by the authenticated user.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
POST /projects
|
||||
```
|
||||
|
||||
|
|
@ -1061,7 +1061,7 @@ where `password` is a public access key with the `api` scope enabled.
|
|||
|
||||
Creates a new project owned by the specified user. Available only for admins.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
POST /projects/user/:user_id
|
||||
```
|
||||
|
||||
|
|
@ -1128,7 +1128,7 @@ where `password` is a public access key with the `api` scope enabled.
|
|||
|
||||
Updates an existing project.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
PUT /projects/:id
|
||||
```
|
||||
|
||||
|
|
@ -1200,7 +1200,7 @@ The forking operation for a project is asynchronous and is completed in a
|
|||
background job. The request will return immediately. To determine whether the
|
||||
fork of the project has completed, query the `import_status` for the new project.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
POST /projects/:id/fork
|
||||
```
|
||||
|
||||
|
|
@ -1217,7 +1217,7 @@ POST /projects/:id/fork
|
|||
|
||||
List the projects accessible to the calling user that have an established, forked relationship with the specified project
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects/:id/forks
|
||||
```
|
||||
|
||||
|
|
@ -1315,7 +1315,7 @@ Example responses:
|
|||
|
||||
Stars a given project. Returns status code `304` if the project is already starred.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
POST /projects/:id/star
|
||||
```
|
||||
|
||||
|
|
@ -1405,7 +1405,7 @@ Example response:
|
|||
|
||||
Unstars a given project. Returns status code `304` if the project is not starred.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
POST /projects/:id/unstar
|
||||
```
|
||||
|
||||
|
|
@ -1495,7 +1495,7 @@ Example response:
|
|||
|
||||
List the users who starred the specified project.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects/:id/starrers
|
||||
```
|
||||
|
||||
|
|
@ -1540,7 +1540,7 @@ Example responses:
|
|||
|
||||
Get languages used in a project with percentage value.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects/:id/languages
|
||||
```
|
||||
|
||||
|
|
@ -1564,7 +1564,7 @@ Example response:
|
|||
Archives the project if the user is either admin or the project owner of this project. This action is
|
||||
idempotent, thus archiving an already archived project will not change the project.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
POST /projects/:id/archive
|
||||
```
|
||||
|
||||
|
|
@ -1673,7 +1673,7 @@ Example response:
|
|||
Unarchives the project if the user is either admin or the project owner of this project. This action is
|
||||
idempotent, thus unarchiving a non-archived project will not change the project.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
POST /projects/:id/unarchive
|
||||
```
|
||||
|
||||
|
|
@ -1786,7 +1786,7 @@ This endpoint either:
|
|||
deletion happens after number of days specified in
|
||||
[instance settings](../user/admin_area/settings/visibility_and_access_controls.md#default-deletion-adjourned-period-premium-only).
|
||||
|
||||
```
|
||||
```plaintext
|
||||
DELETE /projects/:id
|
||||
```
|
||||
|
||||
|
|
@ -1800,7 +1800,7 @@ DELETE /projects/:id
|
|||
|
||||
Restores project marked for deletion.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
POST /projects/:id/restore
|
||||
```
|
||||
|
||||
|
|
@ -1812,7 +1812,7 @@ POST /projects/:id/restore
|
|||
|
||||
Uploads a file to the specified project to be used in an issue or merge request description, or a comment.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
POST /projects/:id/uploads
|
||||
```
|
||||
|
||||
|
|
@ -1848,7 +1848,7 @@ In Markdown contexts, the link is automatically expanded when the format in
|
|||
|
||||
Allow to share project with group.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
POST /projects/:id/share
|
||||
```
|
||||
|
||||
|
|
@ -1863,7 +1863,7 @@ POST /projects/:id/share
|
|||
|
||||
Unshare the project from the group. Returns `204` and no content on success.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
DELETE /projects/:id/share/:group_id
|
||||
```
|
||||
|
||||
|
|
@ -1885,7 +1885,7 @@ These are different for [System Hooks](system_hooks.md) that are system wide.
|
|||
|
||||
Get a list of project hooks.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects/:id/hooks
|
||||
```
|
||||
|
||||
|
|
@ -1897,7 +1897,7 @@ GET /projects/:id/hooks
|
|||
|
||||
Get a specific hook for a project.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects/:id/hooks/:hook_id
|
||||
```
|
||||
|
||||
|
|
@ -1930,7 +1930,7 @@ GET /projects/:id/hooks/:hook_id
|
|||
|
||||
Adds a hook to a specified project.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
POST /projects/:id/hooks
|
||||
```
|
||||
|
||||
|
|
@ -1955,7 +1955,7 @@ POST /projects/:id/hooks
|
|||
|
||||
Edits a hook for a specified project.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
PUT /projects/:id/hooks/:hook_id
|
||||
```
|
||||
|
||||
|
|
@ -1982,7 +1982,7 @@ PUT /projects/:id/hooks/:hook_id
|
|||
Removes a hook from a project. This is an idempotent method and can be called multiple times.
|
||||
Either the hook is available or not.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
DELETE /projects/:id/hooks/:hook_id
|
||||
```
|
||||
|
||||
|
|
@ -2000,7 +2000,7 @@ Allows modification of the forked relationship between existing projects. Availa
|
|||
|
||||
### Create a forked from/to relation between existing projects
|
||||
|
||||
```
|
||||
```plaintext
|
||||
POST /projects/:id/fork/:forked_from_id
|
||||
```
|
||||
|
||||
|
|
@ -2011,7 +2011,7 @@ POST /projects/:id/fork/:forked_from_id
|
|||
|
||||
### Delete an existing forked from relationship
|
||||
|
||||
```
|
||||
```plaintext
|
||||
DELETE /projects/:id/fork
|
||||
```
|
||||
|
||||
|
|
@ -2025,7 +2025,7 @@ Search for projects by name which are accessible to the authenticated user. This
|
|||
endpoint can be accessed without authentication if the project is publicly
|
||||
accessible.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects
|
||||
```
|
||||
|
||||
|
|
@ -2043,7 +2043,7 @@ curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/ap
|
|||
|
||||
> Introduced in GitLab 9.0.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
POST /projects/:id/housekeeping
|
||||
```
|
||||
|
||||
|
|
@ -2057,7 +2057,7 @@ POST /projects/:id/housekeeping
|
|||
|
||||
Get the push rules of a project.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects/:id/push_rule
|
||||
```
|
||||
|
||||
|
|
@ -2101,7 +2101,7 @@ the `commit_committer_check` and `reject_unsigned_commits` parameters:
|
|||
|
||||
Adds a push rule to a specified project.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
POST /projects/:id/push_rule
|
||||
```
|
||||
|
||||
|
|
@ -2124,7 +2124,7 @@ POST /projects/:id/push_rule
|
|||
|
||||
Edits a push rule for a specified project.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
PUT /projects/:id/push_rule
|
||||
```
|
||||
|
||||
|
|
@ -2150,7 +2150,7 @@ PUT /projects/:id/push_rule
|
|||
Removes a push rule from a project. This is an idempotent method and can be called multiple times.
|
||||
Either the push rule is available or not.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
DELETE /projects/:id/push_rule
|
||||
```
|
||||
|
||||
|
|
@ -2162,7 +2162,7 @@ DELETE /projects/:id/push_rule
|
|||
|
||||
> Introduced in GitLab 11.1.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
PUT /projects/:id/transfer
|
||||
```
|
||||
|
||||
|
|
@ -2186,7 +2186,7 @@ Read more in the [Project members](members.md) documentation.
|
|||
|
||||
> Introduced in [GitLab Starter](https://about.gitlab.com/pricing/) 10.3.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
POST /projects/:id/mirror/pull
|
||||
```
|
||||
|
||||
|
|
@ -2219,7 +2219,7 @@ format.
|
|||
If a repository is corrupted to the point where `git clone` does not work, the
|
||||
snapshot may allow some of the data to be retrieved.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects/:id/snapshot
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
The access levels are defined in the `ProtectedRefAccess.allowed_access_levels` method. Currently, these levels are recognized:
|
||||
|
||||
```
|
||||
```plaintext
|
||||
0 => No access
|
||||
30 => Developer access
|
||||
40 => Maintainer access
|
||||
|
|
@ -17,7 +17,7 @@ The access levels are defined in the `ProtectedRefAccess.allowed_access_levels`
|
|||
|
||||
Gets a list of protected branches from a project.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects/:id/protected_branches
|
||||
```
|
||||
|
||||
|
|
@ -91,7 +91,7 @@ Example response:
|
|||
|
||||
Gets a single protected branch or wildcard protected branch.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects/:id/protected_branches/:name
|
||||
```
|
||||
|
||||
|
|
@ -160,7 +160,7 @@ Example response:
|
|||
Protects a single repository branch or several project repository
|
||||
branches using a wildcard protected branch.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
POST /projects/:id/protected_branches
|
||||
```
|
||||
|
||||
|
|
@ -292,7 +292,7 @@ Example response:
|
|||
|
||||
Unprotects the given protected branch or wildcard protected branch.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
DELETE /projects/:id/protected_branches/:name
|
||||
```
|
||||
|
||||
|
|
@ -309,7 +309,7 @@ curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" 'https://git
|
|||
|
||||
Update the "code owner approval required" option for the given protected branch protected branch.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
PATCH /projects/:id/protected_branches/:name
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
The access levels are defined in the `ProtectedEnvironment::DeployAccessLevel::ALLOWED_ACCESS_LEVELS` method.
|
||||
Currently, these levels are recognized:
|
||||
|
||||
```
|
||||
```plaintext
|
||||
30 => Developer access
|
||||
40 => Maintainer access
|
||||
60 => Admin access
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
Currently, these levels are recognized:
|
||||
|
||||
```
|
||||
```plaintext
|
||||
0 => No access
|
||||
30 => Developer access
|
||||
40 => Maintainer access
|
||||
|
|
@ -17,7 +17,7 @@ Currently, these levels are recognized:
|
|||
Gets a list of protected tags from a project.
|
||||
This function takes pagination parameters `page` and `per_page` to restrict the list of protected tags.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects/:id/protected_tags
|
||||
```
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ Example response:
|
|||
Gets a single protected tag or wildcard protected tag.
|
||||
The pagination parameters `page` and `per_page` can be used to restrict the list of protected tags.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
GET /projects/:id/protected_tags/:name
|
||||
```
|
||||
|
||||
|
|
@ -83,7 +83,7 @@ Example response:
|
|||
Protects a single repository tag or several project repository
|
||||
tags using a wildcard protected tag.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
POST /projects/:id/protected_tags
|
||||
```
|
||||
|
||||
|
|
@ -115,7 +115,7 @@ Example response:
|
|||
|
||||
Unprotects the given protected tag or wildcard protected tag.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
DELETE /projects/:id/protected_tags/:name
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -340,6 +340,12 @@ deploy:
|
|||
- master
|
||||
```
|
||||
|
||||
When deploying to a Kubernetes cluster using GitLab's Kubernetes integration,
|
||||
information about the cluster and namespace will be displayed above the job
|
||||
trace on the deployment job page:
|
||||
|
||||

|
||||
|
||||
NOTE: **Note:**
|
||||
Kubernetes configuration is not supported for Kubernetes clusters
|
||||
that are [managed by GitLab](../user/project/clusters/index.md#gitlab-managed-clusters).
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 57 KiB |
|
|
@ -4,6 +4,9 @@ type: concepts, reference, howto
|
|||
|
||||
# Webhooks and insecure internal web services
|
||||
|
||||
NOTE: **Note:**
|
||||
On GitLab.com the [maximum number of webhooks](../user/gitlab_com/index.md#maximum-number-of-webhooks) per project is limited.
|
||||
|
||||
If you have non-GitLab web services running on your GitLab server or within its
|
||||
local network, these may be vulnerable to exploitation via Webhooks.
|
||||
|
||||
|
|
|
|||
|
|
@ -181,7 +181,7 @@ To make full use of Auto DevOps, you will need:
|
|||
If you have configured GitLab's Kubernetes integration, you can deploy it to
|
||||
your cluster by installing the
|
||||
[GitLab-managed app for cert-manager](../../user/clusters/applications.md#cert-manager).
|
||||
|
||||
|
||||
If you do not have Kubernetes or Prometheus installed, then Auto Review Apps,
|
||||
Auto Deploy, and Auto Monitoring will be silently skipped.
|
||||
|
||||
|
|
@ -1030,6 +1030,32 @@ It is also possible to copy and paste the contents of the [Auto DevOps
|
|||
template] into your project and edit this as needed. You may prefer to do it
|
||||
that way if you want to specifically remove any part of it.
|
||||
|
||||
### Customizing the Kubernetes namespace
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/27630) in GitLab 12.6.
|
||||
|
||||
For **non**-GitLab-managed clusters, the namespace can be customized using
|
||||
`.gitlab-ci.yml` by specifying
|
||||
[`environment:kubernetes:namespace`](../../ci/environments.md#configuring-kubernetes-deployments).
|
||||
For example, the following configuration overrides the namespace used for
|
||||
`production` deployments:
|
||||
|
||||
```yaml
|
||||
include:
|
||||
- template: Auto-DevOps.gitlab-ci.yml
|
||||
|
||||
production:
|
||||
environment:
|
||||
kubernetes:
|
||||
namespace: production
|
||||
```
|
||||
|
||||
When deploying to a custom namespace with Auto DevOps, the service account
|
||||
provided with the cluster needs at least the `edit` role within the namespace.
|
||||
|
||||
- If the service account can create namespaces, then the namespace can be created on-demand.
|
||||
- Otherwise, the namespace must exist prior to deployment.
|
||||
|
||||
### Using components of Auto DevOps
|
||||
|
||||
If you only require a subset of the features offered by Auto DevOps, you can include
|
||||
|
|
|
|||
|
|
@ -94,6 +94,13 @@ IP based firewall can be configured by looking up all
|
|||
|
||||
[Static endpoints](https://gitlab.com/groups/gitlab-com/gl-infra/-/epics/97) are being considered.
|
||||
|
||||
## Maximum number of webhooks
|
||||
|
||||
A limit of:
|
||||
|
||||
- 100 webhooks applies to projects.
|
||||
- 50 webhooks applies to groups. **(BRONZE ONLY)**
|
||||
|
||||
## Shared Runners
|
||||
|
||||
GitLab offers Linux and Windows shared runners hosted on GitLab.com for executing your pipelines.
|
||||
|
|
|
|||
|
|
@ -281,22 +281,28 @@ GitLab CI/CD build environment.
|
|||
| `KUBECONFIG` | Path to a file containing `kubeconfig` for this deployment. CA bundle would be embedded if specified. This config also embeds the same token defined in `KUBE_TOKEN` so you likely will only need this variable. This variable name is also automatically picked up by `kubectl` so you won't actually need to reference it explicitly if using `kubectl`. |
|
||||
| `KUBE_INGRESS_BASE_DOMAIN` | From GitLab 11.8, this variable can be used to set a domain per cluster. See [cluster domains](#base-domain) for more information. |
|
||||
|
||||
NOTE: **NOTE:**
|
||||
NOTE: **Note:**
|
||||
Prior to GitLab 11.5, `KUBE_TOKEN` was the Kubernetes token of the main
|
||||
service account of the cluster integration.
|
||||
|
||||
NOTE: **Note:**
|
||||
If your cluster was created before GitLab 12.2, default `KUBE_NAMESPACE` will be set to `<project_name>-<project_id>`.
|
||||
|
||||
When deploying a custom namespace:
|
||||
### Custom namespace
|
||||
|
||||
- The custom namespace must exist in your cluster.
|
||||
- The project's deployment service account must have permission to deploy to the namespace.
|
||||
- `KUBECONFIG` must be updated to use the custom namespace instead of the GitLab-provided default (this is [not automatic](https://gitlab.com/gitlab-org/gitlab/issues/31519)).
|
||||
- If deploying with Auto DevOps, you must *also* override `KUBE_NAMESPACE` with the custom namespace.
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/27630) in GitLab 12.6.
|
||||
|
||||
CAUTION: **Caution:**
|
||||
GitLab does not save custom namespaces in the database. So while deployments work with custom namespaces, GitLab's integration for already-deployed environments will not pick up the customized values. For example, [Deploy Boards](../deploy_boards.md) will not work as intended for those deployments. For more information, see the [related issue](https://gitlab.com/gitlab-org/gitlab/issues/27630).
|
||||
The Kubernetes integration defaults to project-environment-specific namespaces
|
||||
of the form `<project_name>-<project_id>-<environment>` (see [Deployment
|
||||
variables](#deployment-variables)).
|
||||
|
||||
For **non**-GitLab-managed clusters, the namespace can be customized using
|
||||
[`environment:kubernetes:namespace`](../../../ci/environments.md#configuring-kubernetes-deployments)
|
||||
in `.gitlab-ci.yml`.
|
||||
|
||||
NOTE: **Note:** When using a [GitLab-managed cluster](#gitlab-managed-clusters), the
|
||||
namespaces are created automatically prior to deployment and [can not be
|
||||
customized](https://gitlab.com/gitlab-org/gitlab/issues/38054).
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
|
|
|
|||
|
|
@ -47,33 +47,8 @@ and **per project and per group** for **GitLab Enterprise Edition**.
|
|||
Navigate to the webhooks page by going to your project's
|
||||
**Settings ➔ Webhooks**.
|
||||
|
||||
## Maximum number of project webhooks (per tier)
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/20730) in GitLab 12.6.
|
||||
|
||||
A maximum number of project webhooks applies to each [GitLab.com
|
||||
tier](https://about.gitlab.com/pricing/), as shown in the following table:
|
||||
|
||||
| Tier | Number of webhooks per project |
|
||||
|----------|--------------------------------|
|
||||
| Free | 100 |
|
||||
| Bronze | 100 |
|
||||
| Silver | 100 |
|
||||
| Gold | 100 |
|
||||
|
||||
## Maximum number of group webhooks (per tier)
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/25129) in GitLab 12.9.
|
||||
|
||||
A maximum number of group webhooks applies to each [GitLab.com
|
||||
tier](https://about.gitlab.com/pricing/), as shown in the following table:
|
||||
|
||||
| Tier | Number of webhooks per group |
|
||||
|----------|--------------------------------|
|
||||
| Free | feature not available |
|
||||
| Bronze | 50 |
|
||||
| Silver | 50 |
|
||||
| Gold | 50 |
|
||||
NOTE: **Note:**
|
||||
On GitLab.com, the [maximum number of webhooks](../../../user/gitlab_com/index.md#maximum-number-of-webhooks) per project, and per group, is limited.
|
||||
|
||||
## Use-cases
|
||||
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ module Gitlab
|
|||
# the relation_hash, updating references with new object IDs, mapping users using
|
||||
# the "members_mapper" object, also updating notes if required.
|
||||
def create
|
||||
return if invalid_relation?
|
||||
return if invalid_relation? || predefined_relation?
|
||||
|
||||
setup_base_models
|
||||
setup_models
|
||||
|
|
@ -89,6 +89,10 @@ module Gitlab
|
|||
false
|
||||
end
|
||||
|
||||
def predefined_relation?
|
||||
relation_class.try(:predefined_id?, @relation_hash['id'])
|
||||
end
|
||||
|
||||
def setup_models
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ ee:
|
|||
- :push_event_payload
|
||||
- boards:
|
||||
- :board_assignee
|
||||
- :milestone
|
||||
- labels:
|
||||
- :priorities
|
||||
- lists:
|
||||
|
|
|
|||
|
|
@ -188,6 +188,7 @@ excluded_attributes:
|
|||
issues:
|
||||
- :milestone_id
|
||||
- :moved_to_id
|
||||
- :sent_notifications
|
||||
- :state_id
|
||||
- :duplicated_to_id
|
||||
- :promoted_to_epic_id
|
||||
|
|
|
|||
|
|
@ -6260,6 +6260,9 @@ msgstr ""
|
|||
msgid "DeleteProject|Failed to remove project repository. Please try again or contact administrator."
|
||||
msgstr ""
|
||||
|
||||
msgid "DeleteProject|Failed to remove project snippets. Please try again or contact administrator."
|
||||
msgstr ""
|
||||
|
||||
msgid "DeleteProject|Failed to remove some tags in project container registry. Please try again or contact administrator."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -20790,6 +20793,9 @@ msgstr ""
|
|||
msgid "Unable to connect to server: %{error}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Unable to fetch unscanned projects"
|
||||
msgstr ""
|
||||
|
||||
msgid "Unable to fetch vulnerable projects"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -20904,6 +20910,18 @@ msgstr ""
|
|||
msgid "Unresolve thread"
|
||||
msgstr ""
|
||||
|
||||
msgid "UnscannedProjects|15 or more days"
|
||||
msgstr ""
|
||||
|
||||
msgid "UnscannedProjects|30 or more days"
|
||||
msgstr ""
|
||||
|
||||
msgid "UnscannedProjects|5 or more days"
|
||||
msgstr ""
|
||||
|
||||
msgid "UnscannedProjects|60 or more days"
|
||||
msgstr ""
|
||||
|
||||
msgid "UnscannedProjects|Default branch scanning by project"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ FactoryBot.define do
|
|||
TestEnv.copy_repo(snippet,
|
||||
bare_repo: TestEnv.factory_repo_path_bare,
|
||||
refs: TestEnv::BRANCH_SHA)
|
||||
|
||||
snippet.track_snippet_repository
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ describe 'User views merged merge request from deleted fork' do
|
|||
fork_owner = source_project.namespace.owners.first
|
||||
# Place the source_project in the weird in between state
|
||||
source_project.update_attribute(:pending_delete, true)
|
||||
Projects::DestroyService.new(source_project, fork_owner, {}).__send__(:trash_repositories!)
|
||||
Projects::DestroyService.new(source_project, fork_owner, {}).__send__(:trash_project_repositories!)
|
||||
end
|
||||
|
||||
it 'correctly shows the merge request' do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,116 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { GlLoadingIcon } from '@gitlab/ui';
|
||||
import { mockTracking, triggerEvent } from 'helpers/tracking_helper';
|
||||
import Component from '~/sidebar/components/assignees/assignee_title.vue';
|
||||
|
||||
describe('AssigneeTitle component', () => {
|
||||
let wrapper;
|
||||
|
||||
const createComponent = props => {
|
||||
return shallowMount(Component, {
|
||||
propsData: {
|
||||
numberOfAssignees: 0,
|
||||
editable: false,
|
||||
...props,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
});
|
||||
|
||||
describe('assignee title', () => {
|
||||
it('renders assignee', () => {
|
||||
wrapper = createComponent({
|
||||
numberOfAssignees: 1,
|
||||
editable: false,
|
||||
});
|
||||
|
||||
expect(wrapper.vm.$el.innerText.trim()).toEqual('Assignee');
|
||||
});
|
||||
|
||||
it('renders 2 assignees', () => {
|
||||
wrapper = createComponent({
|
||||
numberOfAssignees: 2,
|
||||
editable: false,
|
||||
});
|
||||
|
||||
expect(wrapper.vm.$el.innerText.trim()).toEqual('2 Assignees');
|
||||
});
|
||||
});
|
||||
|
||||
describe('gutter toggle', () => {
|
||||
it('does not show toggle by default', () => {
|
||||
wrapper = createComponent({
|
||||
numberOfAssignees: 2,
|
||||
editable: false,
|
||||
});
|
||||
|
||||
expect(wrapper.vm.$el.querySelector('.gutter-toggle')).toBeNull();
|
||||
});
|
||||
|
||||
it('shows toggle when showToggle is true', () => {
|
||||
wrapper = createComponent({
|
||||
numberOfAssignees: 2,
|
||||
editable: false,
|
||||
showToggle: true,
|
||||
});
|
||||
|
||||
expect(wrapper.vm.$el.querySelector('.gutter-toggle')).toEqual(expect.any(Object));
|
||||
});
|
||||
});
|
||||
|
||||
it('does not render spinner by default', () => {
|
||||
wrapper = createComponent({
|
||||
numberOfAssignees: 0,
|
||||
editable: false,
|
||||
});
|
||||
|
||||
expect(wrapper.find(GlLoadingIcon).exists()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('renders spinner when loading', () => {
|
||||
wrapper = createComponent({
|
||||
loading: true,
|
||||
numberOfAssignees: 0,
|
||||
editable: false,
|
||||
});
|
||||
|
||||
expect(wrapper.find(GlLoadingIcon).exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('does not render edit link when not editable', () => {
|
||||
wrapper = createComponent({
|
||||
numberOfAssignees: 0,
|
||||
editable: false,
|
||||
});
|
||||
|
||||
expect(wrapper.vm.$el.querySelector('.edit-link')).toBeNull();
|
||||
});
|
||||
|
||||
it('renders edit link when editable', () => {
|
||||
wrapper = createComponent({
|
||||
numberOfAssignees: 0,
|
||||
editable: true,
|
||||
});
|
||||
|
||||
expect(wrapper.vm.$el.querySelector('.edit-link')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('tracks the event when edit is clicked', () => {
|
||||
wrapper = createComponent({
|
||||
numberOfAssignees: 0,
|
||||
editable: true,
|
||||
});
|
||||
|
||||
const spy = mockTracking('_category_', wrapper.element, jest.spyOn);
|
||||
triggerEvent('.js-sidebar-dropdown-toggle');
|
||||
|
||||
expect(spy).toHaveBeenCalledWith('_category_', 'click_edit_button', {
|
||||
label: 'right_sidebar',
|
||||
property: 'assignee',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,123 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import { mockTracking, triggerEvent } from 'spec/helpers/tracking_helper';
|
||||
import AssigneeTitle from '~/sidebar/components/assignees/assignee_title.vue';
|
||||
|
||||
describe('AssigneeTitle component', () => {
|
||||
let component;
|
||||
let AssigneeTitleComponent;
|
||||
|
||||
beforeEach(() => {
|
||||
AssigneeTitleComponent = Vue.extend(AssigneeTitle);
|
||||
});
|
||||
|
||||
describe('assignee title', () => {
|
||||
it('renders assignee', () => {
|
||||
component = new AssigneeTitleComponent({
|
||||
propsData: {
|
||||
numberOfAssignees: 1,
|
||||
editable: false,
|
||||
},
|
||||
}).$mount();
|
||||
|
||||
expect(component.$el.innerText.trim()).toEqual('Assignee');
|
||||
});
|
||||
|
||||
it('renders 2 assignees', () => {
|
||||
component = new AssigneeTitleComponent({
|
||||
propsData: {
|
||||
numberOfAssignees: 2,
|
||||
editable: false,
|
||||
},
|
||||
}).$mount();
|
||||
|
||||
expect(component.$el.innerText.trim()).toEqual('2 Assignees');
|
||||
});
|
||||
});
|
||||
|
||||
describe('gutter toggle', () => {
|
||||
it('does not show toggle by default', () => {
|
||||
component = new AssigneeTitleComponent({
|
||||
propsData: {
|
||||
numberOfAssignees: 2,
|
||||
editable: false,
|
||||
},
|
||||
}).$mount();
|
||||
|
||||
expect(component.$el.querySelector('.gutter-toggle')).toBeNull();
|
||||
});
|
||||
|
||||
it('shows toggle when showToggle is true', () => {
|
||||
component = new AssigneeTitleComponent({
|
||||
propsData: {
|
||||
numberOfAssignees: 2,
|
||||
editable: false,
|
||||
showToggle: true,
|
||||
},
|
||||
}).$mount();
|
||||
|
||||
expect(component.$el.querySelector('.gutter-toggle')).toEqual(jasmine.any(Object));
|
||||
});
|
||||
});
|
||||
|
||||
it('does not render spinner by default', () => {
|
||||
component = new AssigneeTitleComponent({
|
||||
propsData: {
|
||||
numberOfAssignees: 0,
|
||||
editable: false,
|
||||
},
|
||||
}).$mount();
|
||||
|
||||
expect(component.$el.querySelector('.fa')).toBeNull();
|
||||
});
|
||||
|
||||
it('renders spinner when loading', () => {
|
||||
component = new AssigneeTitleComponent({
|
||||
propsData: {
|
||||
loading: true,
|
||||
numberOfAssignees: 0,
|
||||
editable: false,
|
||||
},
|
||||
}).$mount();
|
||||
|
||||
expect(component.$el.querySelector('.fa')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('does not render edit link when not editable', () => {
|
||||
component = new AssigneeTitleComponent({
|
||||
propsData: {
|
||||
numberOfAssignees: 0,
|
||||
editable: false,
|
||||
},
|
||||
}).$mount();
|
||||
|
||||
expect(component.$el.querySelector('.edit-link')).toBeNull();
|
||||
});
|
||||
|
||||
it('renders edit link when editable', () => {
|
||||
component = new AssigneeTitleComponent({
|
||||
propsData: {
|
||||
numberOfAssignees: 0,
|
||||
editable: true,
|
||||
},
|
||||
}).$mount();
|
||||
|
||||
expect(component.$el.querySelector('.edit-link')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('tracks the event when edit is clicked', () => {
|
||||
component = new AssigneeTitleComponent({
|
||||
propsData: {
|
||||
numberOfAssignees: 0,
|
||||
editable: true,
|
||||
},
|
||||
}).$mount();
|
||||
|
||||
const spy = mockTracking('_category_', component.$el, spyOn);
|
||||
triggerEvent('.js-sidebar-dropdown-toggle');
|
||||
|
||||
expect(spy).toHaveBeenCalledWith('_category_', 'click_edit_button', {
|
||||
label: 'right_sidebar',
|
||||
property: 'assignee',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -10,6 +10,7 @@ issues:
|
|||
- resource_label_events
|
||||
- resource_weight_events
|
||||
- resource_milestone_events
|
||||
- sent_notifications
|
||||
- sentry_issue
|
||||
- label_links
|
||||
- labels
|
||||
|
|
|
|||
|
|
@ -33,6 +33,15 @@ describe Gitlab::ImportExport::Base::RelationFactory do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when the relation is predefined' do
|
||||
let(:relation_sym) { :milestone }
|
||||
let(:relation_hash) { { 'name' => '#upcoming', 'title' => 'Upcoming', 'id' => -2 } }
|
||||
|
||||
it 'returns without creating a new relation' do
|
||||
expect(subject).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when #setup_models is not implemented' do
|
||||
it 'raises NotImplementedError' do
|
||||
expect { subject }.to raise_error(NotImplementedError)
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ describe Gitlab::ImportExport::Group::TreeSaver do
|
|||
create(:group_badge, group: group)
|
||||
group_label = create(:group_label, group: group)
|
||||
create(:label_priority, label: group_label, priority: 1)
|
||||
board = create(:board, group: group)
|
||||
board = create(:board, group: group, milestone_id: Milestone::Upcoming.id)
|
||||
create(:list, board: board, label: group_label)
|
||||
create(:group_badge, group: group)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,18 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Milestone do
|
||||
describe 'MilestoneStruct#serializable_hash' do
|
||||
let(:predefined_milestone) { described_class::MilestoneStruct.new('Test Milestone', '#test', 1) }
|
||||
|
||||
it 'presents the predefined milestone as a hash' do
|
||||
expect(predefined_milestone.serializable_hash).to eq(
|
||||
title: predefined_milestone.title,
|
||||
name: predefined_milestone.name,
|
||||
id: predefined_milestone.id
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'modules' do
|
||||
context 'with a project' do
|
||||
it_behaves_like 'AtomicInternalId' do
|
||||
|
|
@ -179,6 +191,16 @@ describe Milestone do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.predefined_id?' do
|
||||
it 'returns true for a predefined Milestone ID' do
|
||||
expect(Milestone.predefined_id?(described_class::Upcoming.id)).to be true
|
||||
end
|
||||
|
||||
it 'returns false for a Milestone ID that is not predefined' do
|
||||
expect(Milestone.predefined_id?(milestone.id)).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe '.order_by_name_asc' do
|
||||
it 'sorts by name ascending' do
|
||||
milestone1 = create(:milestone, title: 'Foo')
|
||||
|
|
|
|||
|
|
@ -536,7 +536,7 @@ describe Snippet do
|
|||
end
|
||||
|
||||
describe '#track_snippet_repository' do
|
||||
let(:snippet) { create(:snippet, :repository) }
|
||||
let(:snippet) { create(:snippet) }
|
||||
|
||||
context 'when a snippet repository entry does not exist' do
|
||||
it 'creates a new entry' do
|
||||
|
|
@ -554,7 +554,8 @@ describe Snippet do
|
|||
end
|
||||
|
||||
context 'when a tracking entry exists' do
|
||||
let!(:snippet_repository) { create(:snippet_repository, snippet: snippet) }
|
||||
let!(:snippet) { create(:snippet, :repository) }
|
||||
let(:snippet_repository) { snippet.snippet_repository }
|
||||
let!(:shard) { create(:shard, name: 'foo') }
|
||||
|
||||
it 'does not create a new entry in the database' do
|
||||
|
|
@ -592,7 +593,7 @@ describe Snippet do
|
|||
end
|
||||
|
||||
context 'when repository exists' do
|
||||
let(:snippet) { create(:snippet, :repository) }
|
||||
let!(:snippet) { create(:snippet, :repository) }
|
||||
|
||||
it 'does not try to create repository' do
|
||||
expect(snippet.repository).not_to receive(:after_create)
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ describe Projects::DestroyService do
|
|||
allow(project.repository).to receive(:before_delete).and_raise(::Gitlab::Git::CommandError)
|
||||
allow(Gitlab::GitLogger).to receive(:warn).with(
|
||||
class: Repositories::DestroyService.name,
|
||||
project_id: project.id,
|
||||
container_id: project.id,
|
||||
disk_path: project.disk_path,
|
||||
message: 'Gitlab::Git::CommandError').and_call_original
|
||||
end
|
||||
|
|
@ -338,6 +338,39 @@ describe Projects::DestroyService do
|
|||
end
|
||||
end
|
||||
|
||||
context 'snippets' do
|
||||
let!(:snippet1) { create(:project_snippet, project: project, author: user) }
|
||||
let!(:snippet2) { create(:project_snippet, project: project, author: user) }
|
||||
|
||||
it 'does not include snippets when deleting in batches' do
|
||||
expect(project).to receive(:destroy_dependent_associations_in_batches).with({ exclude: [:container_repositories, :snippets] })
|
||||
|
||||
destroy_project(project, user)
|
||||
end
|
||||
|
||||
it 'calls the bulk snippet destroy service' do
|
||||
expect(project.snippets.count).to eq 2
|
||||
|
||||
expect(Snippets::BulkDestroyService).to receive(:new)
|
||||
.with(user, project.snippets).and_call_original
|
||||
|
||||
expect do
|
||||
destroy_project(project, user)
|
||||
end.to change(Snippet, :count).by(-2)
|
||||
end
|
||||
|
||||
context 'when an error is raised deleting snippets' do
|
||||
it 'does not delete project' do
|
||||
allow_next_instance_of(Snippets::BulkDestroyService) do |instance|
|
||||
allow(instance).to receive(:execute).and_return(ServiceResponse.error(message: 'foo'))
|
||||
end
|
||||
|
||||
expect(destroy_project(project, user)).to be_falsey
|
||||
expect(project.gitlab_shell.repository_exists?(project.repository_storage, path + '.git')).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def destroy_project(project, user, params = {})
|
||||
described_class.new(project, user, params).public_send(async ? :async_execute : :execute)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,161 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Snippets::BulkDestroyService do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let(:user) { create(:user) }
|
||||
let!(:personal_snippet) { create(:personal_snippet, :repository, author: user) }
|
||||
let!(:project_snippet) { create(:project_snippet, :repository, project: project, author: user) }
|
||||
let(:snippets) { user.snippets }
|
||||
let(:gitlab_shell) { Gitlab::Shell.new }
|
||||
let(:service_user) { user }
|
||||
|
||||
before do
|
||||
project.add_developer(user)
|
||||
end
|
||||
|
||||
subject { described_class.new(service_user, snippets) }
|
||||
|
||||
describe '#execute' do
|
||||
it 'deletes the snippets in bulk' do
|
||||
response = nil
|
||||
|
||||
expect(Repositories::ShellDestroyService).to receive(:new).with(personal_snippet.repository).and_call_original
|
||||
expect(Repositories::ShellDestroyService).to receive(:new).with(project_snippet.repository).and_call_original
|
||||
|
||||
aggregate_failures do
|
||||
expect do
|
||||
response = subject.execute
|
||||
end.to change(Snippet, :count).by(-2)
|
||||
|
||||
expect(response).to be_success
|
||||
expect(repository_exists?(personal_snippet)).to be_falsey
|
||||
expect(repository_exists?(project_snippet)).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context 'when snippets is empty' do
|
||||
let(:snippets) { Snippet.none }
|
||||
|
||||
it 'returns a ServiceResponse success response' do
|
||||
response = subject.execute
|
||||
|
||||
expect(response).to be_success
|
||||
expect(response.message).to eq 'No snippets found.'
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'error is raised' do
|
||||
it 'returns error' do
|
||||
response = subject.execute
|
||||
|
||||
aggregate_failures do
|
||||
expect(response).to be_error
|
||||
expect(response.message).to eq error_message
|
||||
end
|
||||
end
|
||||
|
||||
it 'no record is deleted' do
|
||||
expect do
|
||||
subject.execute
|
||||
end.not_to change(Snippet, :count)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user does not have access to remove the snippet' do
|
||||
let(:service_user) { create(:user) }
|
||||
|
||||
it_behaves_like 'error is raised' do
|
||||
let(:error_message) { "You don't have access to delete these snippets." }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when an error is raised deleting the repository' do
|
||||
before do
|
||||
allow_next_instance_of(Repositories::DestroyService) do |instance|
|
||||
allow(instance).to receive(:execute).and_return({ status: :error })
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'error is raised' do
|
||||
let(:error_message) { 'Failed to delete snippet repositories.' }
|
||||
end
|
||||
|
||||
it 'tries to rollback the repository' do
|
||||
expect(subject).to receive(:attempt_rollback_repositories)
|
||||
|
||||
subject.execute
|
||||
end
|
||||
end
|
||||
|
||||
context 'when an error is raised deleting the records' do
|
||||
before do
|
||||
allow(snippets).to receive(:destroy_all).and_raise(ActiveRecord::ActiveRecordError)
|
||||
end
|
||||
|
||||
it_behaves_like 'error is raised' do
|
||||
let(:error_message) { 'Failed to remove snippets.' }
|
||||
end
|
||||
|
||||
it 'tries to rollback the repository' do
|
||||
expect(subject).to receive(:attempt_rollback_repositories)
|
||||
|
||||
subject.execute
|
||||
end
|
||||
end
|
||||
|
||||
context 'when snippet does not have a repository attached' do
|
||||
let!(:snippet_without_repo) { create(:personal_snippet, author: user) }
|
||||
|
||||
it 'does not schedule anything for the snippet without repository and return success' do
|
||||
response = nil
|
||||
|
||||
expect(Repositories::ShellDestroyService).to receive(:new).with(personal_snippet.repository).and_call_original
|
||||
expect(Repositories::ShellDestroyService).to receive(:new).with(project_snippet.repository).and_call_original
|
||||
|
||||
expect do
|
||||
response = subject.execute
|
||||
end.to change(Snippet, :count).by(-3)
|
||||
|
||||
expect(response).to be_success
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#attempt_rollback_repositories' do
|
||||
before do
|
||||
Repositories::DestroyService.new(personal_snippet.repository).execute
|
||||
end
|
||||
|
||||
it 'rollbacks the repository' do
|
||||
error_msg = personal_snippet.disk_path + "+#{personal_snippet.id}+deleted.git"
|
||||
expect(repository_exists?(personal_snippet, error_msg)).to be_truthy
|
||||
|
||||
subject.__send__(:attempt_rollback_repositories)
|
||||
|
||||
aggregate_failures do
|
||||
expect(repository_exists?(personal_snippet, error_msg)).to be_falsey
|
||||
expect(repository_exists?(personal_snippet)).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'when an error is raised' do
|
||||
before do
|
||||
allow_next_instance_of(Repositories::DestroyRollbackService) do |instance|
|
||||
allow(instance).to receive(:execute).and_return({ status: :error })
|
||||
end
|
||||
end
|
||||
|
||||
it 'logs the error' do
|
||||
expect(Gitlab::AppLogger).to receive(:error).with(/\ARepository .* in path .* could not be rolled back\z/).twice
|
||||
|
||||
subject.__send__(:attempt_rollback_repositories)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def repository_exists?(snippet, path = snippet.disk_path + ".git")
|
||||
gitlab_shell.repository_exists?(snippet.snippet_repository.shard_name, path)
|
||||
end
|
||||
end
|
||||
|
|
@ -18,7 +18,7 @@ describe Snippets::CreateService do
|
|||
let(:extra_opts) { {} }
|
||||
let(:creator) { admin }
|
||||
|
||||
subject { Snippets::CreateService.new(project, creator, opts).execute }
|
||||
subject { described_class.new(project, creator, opts).execute }
|
||||
|
||||
let(:snippet) { subject.payload[:snippet] }
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ describe Snippets::DestroyService do
|
|||
let_it_be(:other_user) { create(:user) }
|
||||
|
||||
describe '#execute' do
|
||||
subject { Snippets::DestroyService.new(user, snippet).execute }
|
||||
subject { described_class.new(user, snippet).execute }
|
||||
|
||||
context 'when snippet is nil' do
|
||||
let(:snippet) { nil }
|
||||
|
|
@ -30,7 +30,7 @@ describe Snippets::DestroyService do
|
|||
|
||||
shared_examples 'an unsuccessful destroy' do
|
||||
it 'does not delete the snippet' do
|
||||
expect { subject }.to change { Snippet.count }.by(0)
|
||||
expect { subject }.not_to change { Snippet.count }
|
||||
end
|
||||
|
||||
it 'returns ServiceResponse error' do
|
||||
|
|
@ -38,8 +38,63 @@ describe Snippets::DestroyService do
|
|||
end
|
||||
end
|
||||
|
||||
shared_examples 'deletes the snippet repository' do
|
||||
it 'removes the snippet repository' do
|
||||
expect(snippet.repository.exists?).to be_truthy
|
||||
expect(GitlabShellWorker).to receive(:perform_in)
|
||||
expect_next_instance_of(Repositories::DestroyService) do |instance|
|
||||
expect(instance).to receive(:execute).and_call_original
|
||||
end
|
||||
|
||||
expect(subject).to be_success
|
||||
end
|
||||
|
||||
context 'when the repository deletion service raises an error' do
|
||||
before do
|
||||
allow_next_instance_of(Repositories::DestroyService) do |instance|
|
||||
allow(instance).to receive(:execute).and_return({ status: :error })
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'an unsuccessful destroy'
|
||||
|
||||
it 'does not try to rollback repository' do
|
||||
expect(Repositories::DestroyRollbackService).not_to receive(:new)
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a destroy error is raised' do
|
||||
before do
|
||||
allow(snippet).to receive(:destroy!).and_raise(ActiveRecord::ActiveRecordError)
|
||||
end
|
||||
|
||||
it_behaves_like 'an unsuccessful destroy'
|
||||
|
||||
it 'attempts to rollback the repository' do
|
||||
expect(Repositories::DestroyRollbackService).to receive(:new).and_call_original
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
|
||||
context 'when repository is nil' do
|
||||
it 'does not schedule anything and return success' do
|
||||
allow(snippet).to receive(:repository).and_return(nil)
|
||||
|
||||
expect(GitlabShellWorker).not_to receive(:perform_in)
|
||||
expect_next_instance_of(Repositories::DestroyService) do |instance|
|
||||
expect(instance).to receive(:execute).and_call_original
|
||||
end
|
||||
|
||||
expect(subject).to be_success
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when ProjectSnippet' do
|
||||
let!(:snippet) { create(:project_snippet, project: project, author: author) }
|
||||
let!(:snippet) { create(:project_snippet, :repository, project: project, author: author) }
|
||||
|
||||
context 'when user is able to admin_project_snippet' do
|
||||
let(:author) { user }
|
||||
|
|
@ -49,6 +104,7 @@ describe Snippets::DestroyService do
|
|||
end
|
||||
|
||||
it_behaves_like 'a successful destroy'
|
||||
it_behaves_like 'deletes the snippet repository'
|
||||
end
|
||||
|
||||
context 'when user is not able to admin_project_snippet' do
|
||||
|
|
@ -59,12 +115,13 @@ describe Snippets::DestroyService do
|
|||
end
|
||||
|
||||
context 'when PersonalSnippet' do
|
||||
let!(:snippet) { create(:personal_snippet, author: author) }
|
||||
let!(:snippet) { create(:personal_snippet, :repository, author: author) }
|
||||
|
||||
context 'when user is able to admin_personal_snippet' do
|
||||
let(:author) { user }
|
||||
|
||||
it_behaves_like 'a successful destroy'
|
||||
it_behaves_like 'deletes the snippet repository'
|
||||
end
|
||||
|
||||
context 'when user is not able to admin_personal_snippet' do
|
||||
|
|
@ -73,5 +130,21 @@ describe Snippets::DestroyService do
|
|||
it_behaves_like 'an unsuccessful destroy'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the repository does not exists' do
|
||||
let(:snippet) { create(:personal_snippet, author: user) }
|
||||
|
||||
it 'does not schedule anything and return success' do
|
||||
expect(snippet.repository).not_to be_nil
|
||||
expect(snippet.repository.exists?).to be_falsey
|
||||
|
||||
expect(GitlabShellWorker).not_to receive(:perform_in)
|
||||
expect_next_instance_of(Repositories::DestroyService) do |instance|
|
||||
expect(instance).to receive(:execute).and_call_original
|
||||
end
|
||||
|
||||
expect(subject).to be_success
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ describe Snippets::UpdateService do
|
|||
let(:updater) { user }
|
||||
|
||||
subject do
|
||||
Snippets::UpdateService.new(
|
||||
described_class.new(
|
||||
project,
|
||||
updater,
|
||||
options
|
||||
|
|
|
|||
|
|
@ -26,6 +26,12 @@ describe Users::DestroyService do
|
|||
service.execute(user)
|
||||
end
|
||||
|
||||
it 'does not include snippets when deleting in batches' do
|
||||
expect(user).to receive(:destroy_dependent_associations_in_batches).with({ exclude: [:snippets] })
|
||||
|
||||
service.execute(user)
|
||||
end
|
||||
|
||||
it 'will delete the project' do
|
||||
expect_next_instance_of(Projects::DestroyService) do |destroy_service|
|
||||
expect(destroy_service).to receive(:execute).once.and_return(true)
|
||||
|
|
@ -33,6 +39,54 @@ describe Users::DestroyService do
|
|||
|
||||
service.execute(user)
|
||||
end
|
||||
|
||||
it 'calls the bulk snippet destroy service for the user personal snippets' do
|
||||
repo1 = create(:personal_snippet, :repository, author: user).snippet_repository
|
||||
repo2 = create(:project_snippet, :repository, author: user).snippet_repository
|
||||
repo3 = create(:project_snippet, :repository, project: project, author: user).snippet_repository
|
||||
|
||||
aggregate_failures do
|
||||
expect(gitlab_shell.repository_exists?(repo1.shard_name, repo1.disk_path + '.git')).to be_truthy
|
||||
expect(gitlab_shell.repository_exists?(repo2.shard_name, repo2.disk_path + '.git')).to be_truthy
|
||||
expect(gitlab_shell.repository_exists?(repo3.shard_name, repo3.disk_path + '.git')).to be_truthy
|
||||
end
|
||||
|
||||
# Call made when destroying user personal projects
|
||||
expect(Snippets::BulkDestroyService).to receive(:new)
|
||||
.with(admin, project.snippets).and_call_original
|
||||
|
||||
# Call to remove user personal snippets and for
|
||||
# project snippets where projects are not user personal
|
||||
# ones
|
||||
expect(Snippets::BulkDestroyService).to receive(:new)
|
||||
.with(admin, user.snippets).and_call_original
|
||||
|
||||
service.execute(user)
|
||||
|
||||
aggregate_failures do
|
||||
expect(gitlab_shell.repository_exists?(repo1.shard_name, repo1.disk_path + '.git')).to be_falsey
|
||||
expect(gitlab_shell.repository_exists?(repo2.shard_name, repo2.disk_path + '.git')).to be_falsey
|
||||
expect(gitlab_shell.repository_exists?(repo3.shard_name, repo3.disk_path + '.git')).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context 'when an error is raised deleting snippets' do
|
||||
it 'does not delete user' do
|
||||
snippet = create(:personal_snippet, :repository, author: user)
|
||||
|
||||
bulk_service = double
|
||||
allow(Snippets::BulkDestroyService).to receive(:new).and_call_original
|
||||
allow(Snippets::BulkDestroyService).to receive(:new).with(admin, user.snippets).and_return(bulk_service)
|
||||
allow(bulk_service).to receive(:execute).and_return(ServiceResponse.error(message: 'foo'))
|
||||
|
||||
aggregate_failures do
|
||||
expect { service.execute(user) }
|
||||
.to raise_error(Users::DestroyService::DestroyError, 'foo' )
|
||||
expect(snippet.reload).not_to be_nil
|
||||
expect(gitlab_shell.repository_exists?(snippet.repository_storage, snippet.disk_path + '.git')).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'projects in pending_delete' do
|
||||
|
|
|
|||
Loading…
Reference in New Issue