Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-11-09 18:13:13 +00:00
parent e6a54b33a9
commit efcaec8a14
111 changed files with 2491 additions and 1369 deletions

View File

@ -9,6 +9,7 @@ import initSearchSettings from '~/search_settings';
import initSettingsPanels from '~/settings_panels';
import setupTransferEdit from '~/transfer_edit';
import UserCallout from '~/user_callout';
import initTopicsTokenSelector from '~/projects/settings/topics';
import initProjectPermissionsSettings from '../shared/permissions';
import initProjectLoadingSpinner from '../shared/save_project_loader';
@ -28,3 +29,4 @@ setupTransferEdit('.js-project-transfer-form', 'select.select2');
dirtySubmitFactory(document.querySelectorAll('.js-general-settings-form, .js-mr-settings-form'));
initSearchSettings();
initTopicsTokenSelector();

View File

@ -0,0 +1,92 @@
<script>
import { GlTokenSelector, GlAvatarLabeled } from '@gitlab/ui';
import { s__ } from '~/locale';
import searchProjectTopics from '../queries/project_topics_search.query.graphql';
export default {
components: {
GlTokenSelector,
GlAvatarLabeled,
},
i18n: {
placeholder: s__('ProjectSettings|Search for topic'),
},
props: {
selected: {
type: Array,
required: false,
default: () => [],
},
},
apollo: {
topics: {
query: searchProjectTopics,
variables() {
return {
search: this.search,
};
},
update(data) {
return (
data.topics?.nodes.filter(
(topic) => !this.selectedTokens.some((token) => token.name === topic.name),
) || []
);
},
debounce: 250,
},
},
data() {
return {
topics: [],
selectedTokens: this.selected,
search: '',
};
},
computed: {
loading() {
return this.$apollo.queries.topics.loading;
},
placeholderText() {
return this.selectedTokens.length ? '' : this.$options.i18n.placeholder;
},
},
methods: {
handleEnter(event) {
// Prevent form from submitting when adding a token
if (event.target.value !== '') {
event.preventDefault();
}
},
filterTopics(searchTerm) {
this.search = searchTerm;
},
onTokensUpdate(tokens) {
this.$emit('update', tokens);
},
},
};
</script>
<template>
<gl-token-selector
ref="tokenSelector"
v-model="selectedTokens"
:dropdown-items="topics"
:loading="loading"
allow-user-defined-tokens
:placeholder="placeholderText"
@keydown.enter="handleEnter"
@text-input="filterTopics"
@input="onTokensUpdate"
>
<template #dropdown-item-content="{ dropdownItem }">
<gl-avatar-labeled
:src="dropdownItem.avatarUrl"
:entity-name="dropdownItem.name"
:label="dropdownItem.name"
:size="32"
shape="rect"
/>
</template>
</gl-token-selector>
</template>

View File

@ -0,0 +1,51 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import TopicsTokenSelector from './components/topics_token_selector.vue';
Vue.use(VueApollo);
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
});
export default () => {
const el = document.querySelector('.js-topics-selector');
if (!el) return null;
const { hiddenInputId } = el.dataset;
const hiddenInput = document.getElementById(hiddenInputId);
const selected = hiddenInput.value
? hiddenInput.value.split(/,\s*/).map((token, index) => ({
id: index,
name: token,
}))
: [];
return new Vue({
el,
apolloProvider,
render(createElement) {
return createElement(TopicsTokenSelector, {
props: {
selected,
},
on: {
update(tokens) {
const value = tokens.map(({ name }) => name).join(', ');
hiddenInput.value = value;
// Dispatch `input` event so form submit button becomes active
hiddenInput.dispatchEvent(
new Event('input', {
bubbles: true,
cancelable: true,
}),
);
},
},
});
},
});
};

View File

@ -0,0 +1,9 @@
query searchProjectTopics($search: String) {
topics(search: $search) {
nodes {
id
name
avatarUrl
}
}
}

View File

@ -95,6 +95,8 @@ export default {
noRefSelected: __('No tag selected'),
dropdownHeader: __('Tag name'),
searchPlaceholder: __('Search or create tag'),
label: __('Tag name'),
labelDescription: __('*Required'),
},
createFrom: {
noRefSelected: __('No source selected'),
@ -108,11 +110,12 @@ export default {
<template>
<div>
<gl-form-group
:label="__('Tag name')"
:label-for="tagNameInputId"
data-testid="tag-name-field"
:state="!showTagNameValidationError"
:invalid-feedback="__('Tag name is required')"
:label="$options.translations.tagName.label"
:label-for="tagNameInputId"
:label-description="$options.translations.tagName.labelDescription"
>
<form-field-container>
<ref-selector

View File

@ -40,7 +40,7 @@ export default {
</script>
<template>
<div v-gl-tooltip.left.viewport :class="containerClass" :title="tooltipText" @click="click">
<div v-gl-tooltip.left.viewport="tooltipText" :class="containerClass" @click="click">
<gl-icon v-if="showIcon" name="calendar" />
<slot>
<span> {{ text }} </span>

View File

@ -43,12 +43,7 @@ export default {
</script>
<template>
<div
v-gl-tooltip.left.viewport
:title="labelsList"
class="sidebar-collapsed-icon"
@click="handleClick"
>
<div v-gl-tooltip.left.viewport="labelsList" class="sidebar-collapsed-icon" @click="handleClick">
<gl-icon name="labels" />
<span>{{ labels.length }}</span>
</div>

View File

@ -12,4 +12,20 @@ kbd {
border-image: none;
border-radius: 3px;
box-shadow: 0 -1px 0 var(--gray-200, $gray-200) inset;
&.flat {
color: $code-color;
background-color: $gray-100;
border-color: var(--gray-10, $gray-10) var(--gray-10, $gray-10) var(--gray-50, $gray-50);
box-shadow: none;
border-radius: $border-radius-default;
font-family: $monospace-font;
font-size: $gl-font-size-small;
line-height: 1;
white-space: pre-wrap;
// Safari
word-wrap: break-word;
overflow-wrap: break-word;
word-break: keep-all;
}
}

View File

@ -12,6 +12,7 @@ class GitlabSchema < GraphQL::Schema
# Tracers (order is important)
use Gitlab::Graphql::Tracers::ApplicationContextTracer
use Gitlab::Graphql::Tracers::MetricsTracer
use Gitlab::Graphql::Tracers::LoggerTracer
use Gitlab::Graphql::GenericTracing # Old tracer which will be removed eventually
use Gitlab::Graphql::Tracers::TimerTracer

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
module Resolvers
class TopicsResolver < BaseResolver
type Types::Projects::TopicType, null: true
argument :search, GraphQL::Types::String,
required: false,
description: 'Search query for topic name.'
def resolve(**args)
if args[:search].present?
::Projects::Topic.search(args[:search]).order_by_total_projects_count
else
::Projects::Topic.order_by_total_projects_count
end
end
end
end

View File

@ -0,0 +1,28 @@
# frozen_string_literal: true
module Types
module Projects
# rubocop: disable Graphql/AuthorizeTypes
class TopicType < BaseObject
graphql_name 'Topic'
field :id, GraphQL::Types::ID, null: false,
description: 'ID of the topic.'
field :name, GraphQL::Types::String, null: false,
description: 'Name of the topic.'
field :description, GraphQL::Types::String, null: true,
description: 'Description of the topic.'
markdown_field :description_html, null: true
field :avatar_url, GraphQL::Types::String, null: true,
description: 'URL to avatar image file of the topic.'
def avatar_url
object.avatar_url(only_path: false)
end
end
# rubocop: enable Graphql/AuthorizeTypes
end
end

View File

@ -140,6 +140,11 @@ module Types
null: true,
resolver: Resolvers::BoardListResolver
field :topics, Types::Projects::TopicType.connection_type,
null: true,
resolver: Resolvers::TopicsResolver,
description: "Find project topics."
def design_management
DesignManagementObject.new(nil)
end

View File

@ -14,12 +14,13 @@ module StorageHelper
counter_repositories: storage_counter(statistics.repository_size),
counter_wikis: storage_counter(statistics.wiki_size),
counter_build_artifacts: storage_counter(statistics.build_artifacts_size),
counter_pipeline_artifacts: storage_counter(statistics.pipeline_artifacts_size),
counter_lfs_objects: storage_counter(statistics.lfs_objects_size),
counter_snippets: storage_counter(statistics.snippets_size),
counter_packages: storage_counter(statistics.packages_size),
counter_uploads: storage_counter(statistics.uploads_size)
}
_("Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets} / Packages: %{counter_packages} / Uploads: %{counter_uploads}") % counters
_("Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / Pipeline Artifacts: %{counter_pipeline_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets} / Packages: %{counter_packages} / Uploads: %{counter_uploads}") % counters
end
end

View File

@ -11,9 +11,7 @@ module Timebox
include StripAttribute
include FromUnion
TimeboxStruct = Struct.new(:title, :name, :id) do
include GlobalID::Identification
TimeboxStruct = Struct.new(:title, :name, :id, :class_name) do
# Ensure these models match the interface required for exporting
def serializable_hash(_opts = {})
{ title: title, name: name, id: id }
@ -22,6 +20,10 @@ module Timebox
def self.declarative_policy_class
"TimeboxPolicy"
end
def to_global_id
::Gitlab::GlobalId.build(self, model_name: class_name, id: id)
end
end
# Represents a "No Timebox" state used for filtering Issues and Merge
@ -33,10 +35,10 @@ module Timebox
included do
# Defines the same constants above, but inside the including class.
const_set :None, TimeboxStruct.new("No #{self.name}", "No #{self.name}", 0)
const_set :Any, TimeboxStruct.new("Any #{self.name}", '', -1)
const_set :Upcoming, TimeboxStruct.new('Upcoming', '#upcoming', -2)
const_set :Started, TimeboxStruct.new('Started', '#started', -3)
const_set :None, TimeboxStruct.new("No #{self.name}", "No #{self.name}", 0, self.name)
const_set :Any, TimeboxStruct.new("Any #{self.name}", '', -1, self.name)
const_set :Upcoming, TimeboxStruct.new('Upcoming', '#upcoming', -2, self.name)
const_set :Started, TimeboxStruct.new('Started', '#started', -3, self.name)
alias_method :timebox_id, :id

View File

@ -18,9 +18,10 @@ class ErrorTracking::Error < ApplicationRecord
scope :for_status, -> (status) { where(status: status) }
validates :project, presence: true
validates :name, presence: true
validates :description, presence: true
validates :actor, presence: true
validates :name, presence: true, length: { maximum: 255 }
validates :description, presence: true, length: { maximum: 1024 }
validates :actor, presence: true, length: { maximum: 255 }
validates :platform, length: { maximum: 255 }
validates :status, presence: true
enum status: {

View File

@ -6,7 +6,9 @@ class ErrorTracking::ErrorEvent < ApplicationRecord
validates :payload, json_schema: { filename: 'error_tracking_event_payload' }
validates :error, presence: true
validates :description, presence: true
validates :description, presence: true, length: { maximum: 1024 }
validates :level, length: { maximum: 255 }
validates :environment, length: { maximum: 255 }
validates :occurred_at, presence: true
def stacktrace

View File

@ -14,7 +14,7 @@ class Integration < ApplicationRecord
asana assembla bamboo bugzilla buildkite campfire confluence custom_issue_tracker datadog discord
drone_ci emails_on_push ewm external_wiki flowdock hangouts_chat irker jira
mattermost mattermost_slash_commands microsoft_teams packagist pipelines_email
pivotaltracker prometheus pushover redmine slack slack_slash_commands teamcity unify_circuit webex_teams youtrack zentao
pivotaltracker prometheus pushover redmine shimo slack slack_slash_commands teamcity unify_circuit webex_teams youtrack zentao
].freeze
PROJECT_SPECIFIC_INTEGRATION_NAMES = %w[

View File

@ -0,0 +1,47 @@
# frozen_string_literal: true
module Integrations
class Shimo < Integration
prop_accessor :external_wiki_url
validates :external_wiki_url, presence: true, public_url: true, if: :activated?
def render?
valid? && activated?
end
def title
s_('Shimo|Shimo')
end
def description
s_('Shimo|Link to a Shimo Workspace from the sidebar.')
end
def self.to_param
'shimo'
end
# support for `test` method
def execute(_data)
response = Gitlab::HTTP.get(properties['external_wiki_url'], verify: true, use_read_total_timeout: true)
response.body if response.code == 200
rescue StandardError
nil
end
def self.supported_events
%w()
end
def fields
[
{
type: 'text',
name: 'external_wiki_url',
title: s_('Shimo|Shimo Workspace URL'),
required: true
}
]
end
end
end

View File

@ -189,6 +189,7 @@ class Project < ApplicationRecord
has_one :prometheus_integration, class_name: 'Integrations::Prometheus', inverse_of: :project
has_one :pushover_integration, class_name: 'Integrations::Pushover'
has_one :redmine_integration, class_name: 'Integrations::Redmine'
has_one :shimo_integration, class_name: 'Integrations::Shimo'
has_one :slack_integration, class_name: 'Integrations::Slack'
has_one :slack_slash_commands_integration, class_name: 'Integrations::SlackSlashCommands'
has_one :teamcity_integration, class_name: 'Integrations::Teamcity'
@ -1453,7 +1454,7 @@ class Project < ApplicationRecord
end
def disabled_integrations
[]
[:shimo]
end
def find_or_initialize_integration(name)

View File

@ -28,7 +28,18 @@ module ErrorTracking
private
def event
params[:event]
@event ||= format_event(params[:event])
end
def format_event(event)
# Some SDK send exception payload as Array. For exmple Go lang SDK.
# We need to convert it to hash format we expect.
if event['exception'].is_a?(Array)
exception = event['exception']
event['exception'] = { 'values' => exception }
end
event
end
def exception

View File

@ -10,7 +10,7 @@
%li
%button.js-shortcuts-modal-trigger{ type: "button" }
= _("Keyboard shortcuts")
%span.text-secondary.float-right{ "aria-hidden": "true" }= '?'.html_safe
%kbd.flat.float-right{ "aria-hidden": "true" }= '?'.html_safe
%li.divider
%li
= link_to _("Submit feedback"), "https://about.gitlab.com/submit-feedback"

View File

@ -1,3 +1,4 @@
- hidden_topics_field_id = 'project_topic_list_field'
= form_for [@project], html: { multipart: true, class: "edit-project js-general-settings-form" }, authenticity_token: true do |f|
%input{ name: 'update_section', type: 'hidden', value: 'js-general-settings' }
@ -15,9 +16,9 @@
.row
.form-group.col-md-9
= f.label :topics, _('Topics (optional)'), class: 'label-bold'
= f.text_field :topics, value: @project.topic_list.join(', '), maxlength: 2000, class: "form-control gl-form-input"
%p.form-text.text-muted= _('Separate topics with commas.')
= f.label :topics, _('Topics'), class: 'label-bold'
.js-topics-selector{ data: { hidden_input_id: hidden_topics_field_id } }
= f.hidden_field :topics, value: @project.topic_list.join(', '), id: hidden_topics_field_id
.row
.form-group.col-md-9

View File

@ -12,7 +12,6 @@
Rails.application.configure do
# Rails 6.1
config.action_dispatch.cookies_same_site_protection = nil # New default is :lax
config.action_dispatch.ssl_default_redirect_status = nil # New default is 308
ActiveSupport.utc_to_local_returns_utc_offset_times = false
config.action_controller.urlsafe_csrf_tokens = false
config.action_view.preload_links_header = false
@ -31,5 +30,4 @@ Rails.application.configure do
config.action_controller.per_form_csrf_tokens = false
config.action_controller.forgery_protection_origin_check = false
ActiveSupport.to_time_preserves_timezone = false
config.ssl_options = {} # New default is { hsts: { subdomains: true } }
end

View File

@ -0,0 +1,22 @@
---
key_path: counts.projects_shimo_active
name: count_all_projects_shimo_active
description: Count of projects with active Shimo integrations
product_section: dev
product_stage: ecosystem
product_group: group::integrations
product_category: integrations
value_type: number
status: active
milestone: "14.5"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/343386
time_frame: all
data_source: database
data_category: optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate

View File

@ -0,0 +1,22 @@
---
key_path: counts.groups_shimo_active
name: count_all_groups_shimo_active
description: Count of groups with active Shimo integrations
product_section: dev
product_stage: ecosystem
product_group: group::integrations
product_category: integrations
value_type: number
status: active
milestone: "14.5"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/343386
time_frame: all
data_source: database
data_category: optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate

View File

@ -0,0 +1,22 @@
---
key_path: counts.instances_shimo_active
name: count_all_instances_shimo_active
description: Count of instances with active Shimo integrations
product_section: dev
product_stage: ecosystem
product_group: group::integrations
product_category: integrations
value_type: number
status: active
milestone: "14.5"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/343386
time_frame: all
data_source: database
data_category: optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate

View File

@ -0,0 +1,22 @@
---
key_path: counts.projects_inheriting_shimo_active
name: count_all_projects_inheriting_shimo_active
description: Count of projects that inherit active Shimo integrations
product_section: dev
product_stage: ecosystem
product_group: group::integrations
product_category: integrations
value_type: number
status: active
milestone: "14.5"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/343386
time_frame: all
data_source: database
data_category: optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate

View File

@ -0,0 +1,22 @@
---
key_path: counts.groups_inheriting_shimo_active
name: count_all_groups_inheriting_shimo_active
description: Count of groups that inherit active Shimo integrations
product_section: dev
product_stage: ecosystem
product_group: group::integrations
product_category: integrations
value_type: number
status: active
milestone: "14.5"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/343386
time_frame: all
data_source: database
data_category: optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate

View File

@ -41,7 +41,7 @@ To enable the Atlassian OmniAuth provider for passwordless authentication you mu
sudo -u git -H editor /home/git/gitlab/config/gitlab.yml
```
1. See [Initial OmniAuth Configuration](../../integration/omniauth.md#initial-omniauth-configuration) for initial settings to enable single sign-on and add `atlassian_oauth2` as an OAuth provider.
1. See [Configure initial settings](../../integration/omniauth.md#configure-initial-settings) for initial settings to enable single sign-on and add `atlassian_oauth2` as an OAuth provider.
1. Add the provider configuration for Atlassian:
For Omnibus GitLab installations:

View File

@ -27,7 +27,7 @@ Authentiq generates a Client ID and the accompanying Client Secret for you to us
sudo -u git -H editor /home/git/gitlab/config/gitlab.yml
```
1. See [Initial OmniAuth Configuration](../../integration/omniauth.md#initial-omniauth-configuration) for initial settings to enable single sign-on and add Authentiq as an OAuth provider.
1. See [Configure initial settings](../../integration/omniauth.md#configure-initial-settings) for initial settings to enable single sign-on and add Authentiq as an OAuth provider.
1. Add the provider configuration for Authentiq:

View File

@ -40,7 +40,7 @@ The following steps enable AWS Cognito as an authentication provider:
## Configure GitLab
1. See [Initial OmniAuth Configuration](../../integration/omniauth.md#initial-omniauth-configuration) for initial settings.
1. See [Configure initial settings](../../integration/omniauth.md#configure-initial-settings) for initial settings.
1. On your GitLab server, open the configuration file.
**For Omnibus installations**
@ -88,4 +88,4 @@ Your sign-in page should now display a Cognito button below the regular sign-in
To begin the authentication process, click the icon, and AWS Cognito asks the user to sign in and authorize the GitLab application.
If successful, the user is redirected and signed in to your GitLab instance.
For more information, see the [Initial OmniAuth Configuration](../../integration/omniauth.md#initial-omniauth-configuration).
For more information, see [Configure initial settings](../../integration/omniauth.md#configure-initial-settings).

View File

@ -36,7 +36,7 @@ this provider also allows Crowd authentication for Git-over-https requests.
sudo -u git -H editor config/gitlab.yml
```
1. See [Initial OmniAuth Configuration](../../integration/omniauth.md#initial-omniauth-configuration)
1. See [Configure initial settings](../../integration/omniauth.md#configure-initial-settings)
for initial settings.
1. Add the provider configuration:

View File

@ -25,7 +25,7 @@ JWT will provide you with a secret key for you to use.
sudo -u git -H editor config/gitlab.yml
```
1. See [Initial OmniAuth Configuration](../../integration/omniauth.md#initial-omniauth-configuration) for initial settings.
1. See [Configure initial settings](../../integration/omniauth.md#configure-initial-settings) for initial settings.
1. Add the provider configuration.
For Omnibus GitLab:

View File

@ -27,7 +27,7 @@ The OpenID Connect provides you with a client's details and secret for you to us
sudo -u git -H editor config/gitlab.yml
```
See [Initial OmniAuth Configuration](../../integration/omniauth.md#initial-omniauth-configuration) for initial settings.
See [Configure initial settings](../../integration/omniauth.md#configure-initial-settings) for initial settings.
1. Add the provider configuration.
@ -228,7 +228,7 @@ Azure B2C [offers two ways of defining the business logic for logging in a user]
While cumbersome to configure, custom policies are required because
standard Azure B2C user flows [do not send the OpenID `email` claim](https://github.com/MicrosoftDocs/azure-docs/issues/16566). In
other words, they do not work with the [`allow_single_sign_on` or `auto_link_user` parameters](../../integration/omniauth.md#initial-omniauth-configuration).
other words, they do not work with the [`allow_single_sign_on` or `auto_link_user` parameters](../../integration/omniauth.md#configure-initial-settings).
With a standard Azure B2C policy, GitLab cannot create a new account or
link to an existing one with an email address.

View File

@ -157,7 +157,7 @@ Confirm the following are all true:
successfully creates the project but doesn't create the README.
- When [tailing the logs](https://docs.gitlab.com/omnibus/settings/logs.html#tail-logs-in-a-console-on-the-server)
on a Gitaly client and reproducing the error, you get `401` errors
when reaching the [`/api/v4/internal/allowed`](../../development/internal_api.md) endpoint:
when reaching the [`/api/v4/internal/allowed`](../../development/internal_api/index.md) endpoint:
```shell
# api_json.log

View File

@ -119,7 +119,7 @@ For most JSON requests, POST, PUT, PATCH, and DELETE are blocked, and the API re
| POST | `/admin/session`, `/admin/session/destroy` | To allow [Administrator mode for GitLab administrators](https://gitlab.com/groups/gitlab-org/-/epics/2158) |
| POST | Paths ending with `/compare`| Git revision routes. |
| POST | `.git/git-upload-pack` | To allow Git pull/clone. |
| POST | `/api/v4/internal` | [internal API routes](../../development/internal_api.md) |
| POST | `/api/v4/internal` | [internal API routes](../../development/internal_api/index.md) |
| POST | `/admin/sidekiq` | To allow management of background jobs in the Admin UI |
| POST | `/admin/geo` | To allow updating Geo Nodes in the administrator UI |
| POST | `/api/v4/geo_replication`| To allow certain Geo-specific administrator UI actions on secondary sites |

View File

@ -258,7 +258,7 @@ separate Rails process to debug the issue:
### GitLab: API is not accessible
This often occurs when GitLab Shell attempts to request authorization via the
[internal API](../../development/internal_api.md) (for example, `http://localhost:8080/api/v4/internal/allowed`), and
[internal API](../../development/internal_api/index.md) (for example, `http://localhost:8080/api/v4/internal/allowed`), and
something in the check fails. There are many reasons why this may happen:
1. Timeout connecting to a database (for example, PostgreSQL or Redis)
@ -275,7 +275,7 @@ strace -ttTfyyy -s 1024 -p <PID of puma worker> -o /tmp/puma.txt
If you cannot isolate which Unicorn worker is the issue, try to run `strace`
on all the Unicorn workers to see where the
[`/internal/allowed`](../../development/internal_api.md) endpoint gets stuck:
[`/internal/allowed`](../../development/internal_api/index.md) endpoint gets stuck:
```shell
ps auwx | grep puma | awk '{ print " -p " $2}' | xargs strace -ttTfyyy -s 1024 -o /tmp/puma.txt

View File

@ -419,6 +419,22 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="querytimelogsstarttime"></a>`startTime` | [`Time`](#time) | List timelogs within a time range where the logged time is equal to or after startTime. |
| <a id="querytimelogsusername"></a>`username` | [`String`](#string) | List timelogs for a user. |
### `Query.topics`
Find project topics.
Returns [`TopicConnection`](#topicconnection).
This field returns a [connection](#connections). It accepts the
four standard [pagination arguments](#connection-pagination-arguments):
`before: String`, `after: String`, `first: Int`, `last: Int`.
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="querytopicssearch"></a>`search` | [`String`](#string) | Search query for topic name. |
### `Query.usageTrendsMeasurements`
Get statistics on the instance.
@ -5676,6 +5692,29 @@ The edge type for [`ContainerRepositoryTag`](#containerrepositorytag).
| <a id="containerrepositorytagedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="containerrepositorytagedgenode"></a>`node` | [`ContainerRepositoryTag`](#containerrepositorytag) | The item at the end of the edge. |
#### `CoverageFuzzingCorpusConnection`
The connection type for [`CoverageFuzzingCorpus`](#coveragefuzzingcorpus).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="coveragefuzzingcorpusconnectionedges"></a>`edges` | [`[CoverageFuzzingCorpusEdge]`](#coveragefuzzingcorpusedge) | A list of edges. |
| <a id="coveragefuzzingcorpusconnectionnodes"></a>`nodes` | [`[CoverageFuzzingCorpus]`](#coveragefuzzingcorpus) | A list of nodes. |
| <a id="coveragefuzzingcorpusconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
#### `CoverageFuzzingCorpusEdge`
The edge type for [`CoverageFuzzingCorpus`](#coveragefuzzingcorpus).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="coveragefuzzingcorpusedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="coveragefuzzingcorpusedgenode"></a>`node` | [`CoverageFuzzingCorpus`](#coveragefuzzingcorpus) | The item at the end of the edge. |
#### `CustomEmojiConnection`
The connection type for [`CustomEmoji`](#customemoji).
@ -7690,6 +7729,29 @@ The edge type for [`Todo`](#todo).
| <a id="todoedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="todoedgenode"></a>`node` | [`Todo`](#todo) | The item at the end of the edge. |
#### `TopicConnection`
The connection type for [`Topic`](#topic).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="topicconnectionedges"></a>`edges` | [`[TopicEdge]`](#topicedge) | A list of edges. |
| <a id="topicconnectionnodes"></a>`nodes` | [`[Topic]`](#topic) | A list of nodes. |
| <a id="topicconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
#### `TopicEdge`
The edge type for [`Topic`](#topic).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="topicedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="topicedgenode"></a>`node` | [`Topic`](#topic) | The item at the end of the edge. |
#### `TreeConnection`
The connection type for [`Tree`](#tree).
@ -8991,6 +9053,17 @@ A tag from a container repository.
| <a id="containerrepositorytagshortrevision"></a>`shortRevision` | [`String`](#string) | Short revision of the tag. |
| <a id="containerrepositorytagtotalsize"></a>`totalSize` | [`BigInt`](#bigint) | Size of the tag. |
### `CoverageFuzzingCorpus`
Corpus for a coverage fuzzing job.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="coveragefuzzingcorpusid"></a>`id` | [`AppSecFuzzingCoverageCorpusID!`](#appsecfuzzingcoveragecorpusid) | ID of the corpus. |
| <a id="coveragefuzzingcorpuspackage"></a>`package` | [`PackageDetailsType!`](#packagedetailstype) | Package of the corpus. |
### `CurrentLicense`
Represents the current license.
@ -12763,6 +12836,7 @@ Represents vulnerability finding of a security report on the pipeline.
| <a id="projectcontainerexpirationpolicy"></a>`containerExpirationPolicy` | [`ContainerExpirationPolicy`](#containerexpirationpolicy) | Container expiration policy of the project. |
| <a id="projectcontainerregistryenabled"></a>`containerRegistryEnabled` | [`Boolean`](#boolean) | Indicates if Container Registry is enabled for the current user. |
| <a id="projectcontainerrepositoriescount"></a>`containerRepositoriesCount` | [`Int!`](#int) | Number of container repositories in the project. |
| <a id="projectcorpuses"></a>`corpuses` | [`CoverageFuzzingCorpusConnection`](#coveragefuzzingcorpusconnection) | Find corpuses of the project. Available only when feature flag `corpus_management` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice. (see [Connections](#connections)) |
| <a id="projectcreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp of the project creation. |
| <a id="projectdastscannerprofiles"></a>`dastScannerProfiles` | [`DastScannerProfileConnection`](#dastscannerprofileconnection) | DAST scanner profiles associated with the project. (see [Connections](#connections)) |
| <a id="projectdastsiteprofiles"></a>`dastSiteProfiles` | [`DastSiteProfileConnection`](#dastsiteprofileconnection) | DAST Site Profiles associated with the project. (see [Connections](#connections)) |
@ -14807,6 +14881,18 @@ Representing a to-do entry.
| <a id="todostate"></a>`state` | [`TodoStateEnum!`](#todostateenum) | State of the to-do item. |
| <a id="todotargettype"></a>`targetType` | [`TodoTargetEnum!`](#todotargetenum) | Target type of the to-do item. |
### `Topic`
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="topicavatarurl"></a>`avatarUrl` | [`String`](#string) | URL to avatar image file of the topic. |
| <a id="topicdescription"></a>`description` | [`String`](#string) | Description of the topic. |
| <a id="topicdescriptionhtml"></a>`descriptionHtml` | [`String`](#string) | The GitLab Flavored Markdown rendering of `description`. |
| <a id="topicid"></a>`id` | [`ID!`](#id) | ID of the topic. |
| <a id="topicname"></a>`name` | [`String!`](#string) | Name of the topic. |
### `Tree`
#### Fields
@ -16819,6 +16905,7 @@ State of a Sentry error.
| <a id="servicetypeprometheus_service"></a>`PROMETHEUS_SERVICE` | PrometheusService type. |
| <a id="servicetypepushover_service"></a>`PUSHOVER_SERVICE` | PushoverService type. |
| <a id="servicetyperedmine_service"></a>`REDMINE_SERVICE` | RedmineService type. |
| <a id="servicetypeshimo_service"></a>`SHIMO_SERVICE` | ShimoService type. |
| <a id="servicetypeslack_service"></a>`SLACK_SERVICE` | SlackService type. |
| <a id="servicetypeslack_slash_commands_service"></a>`SLACK_SLASH_COMMANDS_SERVICE` | SlackSlashCommandsService type. |
| <a id="servicetypeteamcity_service"></a>`TEAMCITY_SERVICE` | TeamcityService type. |
@ -17137,6 +17224,12 @@ A `AnalyticsDevopsAdoptionEnabledNamespaceID` is a global ID. It is encoded as a
An example `AnalyticsDevopsAdoptionEnabledNamespaceID` is: `"gid://gitlab/Analytics::DevopsAdoption::EnabledNamespace/1"`.
### `AppSecFuzzingCoverageCorpusID`
A `AppSecFuzzingCoverageCorpusID` is a global ID. It is encoded as a string.
An example `AppSecFuzzingCoverageCorpusID` is: `"gid://gitlab/AppSec::Fuzzing::Coverage::Corpus/1"`.
### `AuditEventsExternalAuditEventDestinationID`
A `AuditEventsExternalAuditEventDestinationID` is a global ID. It is encoded as a string.

View File

@ -426,8 +426,11 @@ Use `include:local` instead of symbolic links.
**Keyword type**: Global keyword.
**Possible inputs**: A full path relative to the root directory (`/`).
The YAML file must have the extension `.yml` or `.yaml`. Wildcard paths (`*` and `**`) are supported.
**Possible inputs**:
- A full path relative to the root directory (`/`).
- The YAML file must have the extension `.yml` or `.yaml`.
- You can [use `*` and `**` wildcards in the file path](includes.md#use-includelocal-with-wildcard-file-paths).
**Example of `include:local`**:
@ -449,10 +452,6 @@ include: '.gitlab-ci-production.yml'
- All [nested includes](includes.md#use-nested-includes) are executed in the scope of the same project,
so you can use local, project, remote, or template includes.
**Related topics**:
- [Use `include:local` with wildcard file paths](includes.md#use-includelocal-with-wildcard-file-paths).
#### `include:file`
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/53903) in GitLab 11.7.

View File

@ -240,7 +240,7 @@ it's own file in the [`validators`](https://gitlab.com/gitlab-org/gitlab/-/blob/
## Internal API
The [internal API](internal_api.md) is documented for internal use. Please keep it up to date so we know what endpoints
The [internal API](internal_api/index.md) is documented for internal use. Please keep it up to date so we know what endpoints
different components are making use of.
## Avoiding N+1 problems

View File

@ -976,12 +976,12 @@ in Rails, scheduled to run whenever an SSH key is modified by a user.
instead of keys. In this case, `AuthorizedKeysCommand` is replaced with an
`AuthorizedPrincipalsCommand`. This extracts a username from the certificate
without using the Rails internal API, which is used instead of `key_id` in the
[`/api/internal/allowed`](internal_api.md) call later.
[`/api/internal/allowed`](internal_api/index.md) call later.
GitLab Shell also has a few operations that do not involve Gitaly, such as
resetting two-factor authentication codes. These are handled in the same way,
except there is no round-trip into Gitaly - Rails performs the action as part
of the [internal API](internal_api.md) call, and GitLab Shell streams the
of the [internal API](internal_api/index.md) call, and GitLab Shell streams the
response back to the user directly.
## System layout

View File

@ -49,8 +49,8 @@ METHOD /endpoint
Supported attributes:
| Attribute | Type | Required | Description |
|:------------|:---------|:---------|:----------------------|
| Attribute | Type | Required | Description |
| :---------- | :------- | :--------------------- | :-------------------- |
| `attribute` | datatype | **{check-circle}** Yes | Detailed description. |
| `attribute` | datatype | **{dotted-circle}** No | Detailed description. |
| `attribute` | datatype | **{dotted-circle}** No | Detailed description. |
@ -80,16 +80,23 @@ to describe the GitLab release that introduced the API call.
Use the following table headers to describe the methods. Attributes should
always be in code blocks using backticks (`` ` ``).
Sort the attributes in the table: first, required, then alphabetically.
```markdown
| Attribute | Type | Required | Description |
|:----------|:-----|:---------|:------------|
| Attribute | Type | Required | Description |
| :------------- | :------------ | :--------------------- | :--------------------------------------------------- |
| `user` | string | **{check-circle}** Yes | The GitLab username. |
| `assignee_ids` | integer array | **{dotted-circle}** No | The IDs of the users to assign the issue to. |
| `confidential` | boolean | **{dotted-circle}** No | Set an issue to be confidential. Default is `false`. |
```
Rendered example:
| Attribute | Type | Required | Description |
|:----------|:-------|:---------|:--------------------|
| `user` | string | yes | The GitLab username. |
| Attribute | Type | Required | Description |
| :------------- | :------------ | :--------------------- | :--------------------------------------------------- |
| `user` | string | **{check-circle}** Yes | The GitLab username. |
| `assignee_ids` | integer array | **{dotted-circle}** No | The IDs of the users to assign the issue to. |
| `confidential` | boolean | **{dotted-circle}** No | Set an issue to be confidential. Default is `false`. |
## cURL commands
@ -101,12 +108,12 @@ Rendered example:
- Prefer to use examples using the personal access token and don't pass data of
username and password.
| Methods | Description |
|:------------------------------------------- |:------------------------------------------------------|
| Methods | Description |
| :---------------------------------------------- | :----------------------------------------------------- |
| `--header "PRIVATE-TOKEN: <your_access_token>"` | Use this method as is, whenever authentication needed. |
| `--request POST` | Use this method when creating new objects |
| `--request PUT` | Use this method when updating existing objects |
| `--request DELETE` | Use this method when removing existing objects |
| `--request POST` | Use this method when creating new objects |
| `--request PUT` | Use this method when updating existing objects |
| `--request DELETE` | Use this method when removing existing objects |
## cURL Examples

View File

@ -1,829 +1,9 @@
---
stage: Create
group: Source Code
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments"
type: reference, api
redirect_to: 'internal_api/index.md'
remove_date: '2022-02-09'
---
# Internal API **(FREE)**
This document was moved to [another location](internal_api/index.md).
The internal API is used by different GitLab components, it can not be
used by other consumers. This documentation is intended for people
working on the GitLab codebase.
This documentation does not yet include the internal API used by
GitLab Pages.
## Adding new endpoints
API endpoints should be externally accessible by default, with proper authentication and authorization.
Before adding a new internal endpoint, consider if the API would potentially be
useful to the wider GitLab community and can be made externally accessible.
One reason we might favor internal API endpoints sometimes is when using such an endpoint requires
internal data that external actors can not have. For example, in the internal Pages API we might use
a secret token that identifies a request as internal or sign a request with a public key that is
not available to a wider community.
Another reason to separate something into an internal API is when request to such API endpoint
should never go through an edge (public) load balancer. This way we can configure different rate
limiting rules and policies around how the endpoint is being accessed, because we know that only
internal requests can be made to that endpoint going through an internal load balancer.
## Authentication
These methods are all authenticated using a shared secret. This secret
is stored in a file at the path configured in `config/gitlab.yml` by
default this is in the root of the rails app named
`.gitlab_shell_secret`
To authenticate using that token, clients read the contents of that
file, and include the token Base64 encoded in a `secret_token` parameter
or in the `Gitlab-Shared-Secret` header.
NOTE:
The internal API used by GitLab Pages, and GitLab Kubernetes Agent Server (`kas`) uses JSON Web Token (JWT)
authentication, which is different from GitLab Shell.
## Git Authentication
This is called by [Gitaly](https://gitlab.com/gitlab-org/gitaly) and
[GitLab Shell](https://gitlab.com/gitlab-org/gitlab-shell) to check access to a
repository.
- **When called from GitLab Shell**: No changes are passed, and the internal
API replies with the information needed to pass the request on to Gitaly.
- **When called from Gitaly in a `pre-receive` hook**: The changes are passed
and validated to determine if the push is allowed.
Calls are limited to 50 seconds each.
```plaintext
POST /internal/allowed
```
| Attribute | Type | Required | Description |
|:----------|:-------|:---------|:------------|
| `key_id` | string | no | ID of the SSH-key used to connect to GitLab Shell |
| `username` | string | no | Username from the certificate used to connect to GitLab Shell |
| `project` | string | no (if `gl_repository` is passed) | Path to the project |
| `gl_repository` | string | no (if `project` is passed) | Repository identifier, such as `project-7` |
| `protocol` | string | yes | SSH when called from GitLab Shell, HTTP or SSH when called from Gitaly |
| `action` | string | yes | Git command being run (`git-upload-pack`, `git-receive-pack`, `git-upload-archive`) |
| `changes` | string | yes | `<oldrev> <newrev> <refname>` when called from Gitaly, the magic string `_any` when called from GitLab Shell |
| `check_ip` | string | no | IP address from which call to GitLab Shell was made |
Example request:
```shell
curl --request POST --header "Gitlab-Shared-Secret: <Base64 encoded token>" \
--data "key_id=11&project=gnuwget/wget2&action=git-upload-pack&protocol=ssh" \
"http://localhost:3001/api/v4/internal/allowed"
```
Example response:
```json
{
"status": true,
"gl_repository": "project-3",
"gl_project_path": "gnuwget/wget2",
"gl_id": "user-1",
"gl_username": "root",
"git_config_options": [],
"gitaly": {
"repository": {
"storage_name": "default",
"relative_path": "@hashed/4e/07/4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce.git",
"git_object_directory": "",
"git_alternate_object_directories": [],
"gl_repository": "project-3",
"gl_project_path": "gnuwget/wget2"
},
"address": "unix:/Users/bvl/repos/gitlab/gitaly.socket",
"token": null
},
"gl_console_messages": []
}
```
### Known consumers
- Gitaly
- GitLab Shell
## LFS Authentication
This is the endpoint that gets called from GitLab Shell to provide
information for LFS clients when the repository is accessed over SSH.
| Attribute | Type | Required | Description |
|:----------|:-------|:---------|:------------|
| `key_id` | string | no | ID of the SSH-key used to connect to GitLab Shell |
| `username`| string | no | Username from the certificate used to connect to GitLab Shell |
| `project` | string | no | Path to the project |
Example request:
```shell
curl --request POST --header "Gitlab-Shared-Secret: <Base64 encoded token>" \
--data "key_id=11&project=gnuwget/wget2" "http://localhost:3001/api/v4/internal/lfs_authenticate"
```
```json
{
"username": "root",
"lfs_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7ImFjdG9yIjoicm9vdCJ9LCJqdGkiOiIyYWJhZDcxZC0xNDFlLTQ2NGUtOTZlMi1mODllYWRiMGVmZTYiLCJpYXQiOjE1NzAxMTc2NzYsIm5iZiI6MTU3MDExNzY3MSwiZXhwIjoxNTcwMTE5NDc2fQ.g7atlBw1QMY7QEBVPE0LZ8ZlKtaRzaMRmNn41r2YITM",
"repository_http_path": "http://localhost:3001/gnuwget/wget2.git",
"expires_in": 1800
}
```
### Known consumers
- GitLab Shell
## Authorized Keys Check
This endpoint is called by the GitLab Shell authorized keys
check. Which is called by OpenSSH for [fast SSH key
lookup](../administration/operations/fast_ssh_key_lookup.md).
| Attribute | Type | Required | Description |
|:----------|:-------|:---------|:------------|
| `key` | string | yes | SSH key as passed by OpenSSH to GitLab Shell |
```plaintext
GET /internal/authorized_keys
```
Example request:
```shell
curl --request GET --header "Gitlab-Shared-Secret: <Base64 encoded secret>" "http://localhost:3001/api/v4/internal/authorized_keys?key=<key as passed by OpenSSH>"
```
Example response:
```json
{
"id": 11,
"title": "admin@example.com",
"key": "ssh-rsa ...",
"created_at": "2019-06-27T15:29:02.219Z"
}
```
### Known consumers
- GitLab Shell
## Get user for user ID or key
This endpoint is used when a user performs `ssh git@gitlab.com`. It
discovers the user associated with an SSH key.
| Attribute | Type | Required | Description |
|:----------|:-------|:---------|:------------|
| `key_id` | integer | no | The ID of the SSH key used as found in the authorized-keys file or through the `/authorized_keys` check |
| `username` | string | no | Username of the user being looked up, used by GitLab Shell when authenticating using a certificate |
```plaintext
GET /internal/discover
```
Example request:
```shell
curl --request GET --header "Gitlab-Shared-Secret: <Base64 encoded secret>" "http://localhost:3001/api/v4/internal/discover?key_id=7"
```
Example response:
```json
{
"id": 7,
"name": "Dede Eichmann",
"username": "rubi"
}
```
### Known consumers
- GitLab Shell
## Instance information
This gets some generic information about the instance. This is used
by Geo nodes to get information about each other.
```plaintext
GET /internal/check
```
Example request:
```shell
curl --request GET --header "Gitlab-Shared-Secret: <Base64 encoded secret>" "http://localhost:3001/api/v4/internal/check"
```
Example response:
```json
{
"api_version": "v4",
"gitlab_version": "12.3.0-pre",
"gitlab_rev": "d69c988e6a6",
"redis": true
}
```
### Known consumers
- GitLab Geo
- GitLab Shell's `bin/check`
- Gitaly
## Get new 2FA recovery codes using an SSH key
This is called from GitLab Shell and allows users to get new 2FA
recovery codes based on their SSH key.
| Attribute | Type | Required | Description |
|:----------|:-------|:---------|:------------|
| `key_id` | integer | no | The ID of the SSH key used as found in the authorized-keys file or through the `/authorized_keys` check |
| `user_id` | integer | no | **Deprecated** User_id for which to generate new recovery codes |
```plaintext
GET /internal/two_factor_recovery_codes
```
Example request:
```shell
curl --request POST --header "Gitlab-Shared-Secret: <Base64 encoded secret>" \
--data "key_id=7" "http://localhost:3001/api/v4/internal/two_factor_recovery_codes"
```
Example response:
```json
{
"success": true,
"recovery_codes": [
"d93ee7037944afd5",
"19d7b84862de93dd",
"1e8c52169195bf71",
"be50444dddb7ca84",
"26048c77d161d5b7",
"482d5c03d1628c47",
"d2c695e309ce7679",
"dfb4748afc4f12a7",
"0e5f53d1399d7979",
"af04d5622153b020"
]
}
```
### Known consumers
- GitLab Shell
## Get new personal access-token
This is called from GitLab Shell and allows users to generate a new
personal access token.
| Attribute | Type | Required | Description |
|:----------|:-------|:---------|:------------|
| `name` | string | yes | The name of the new token |
| `scopes` | string array | yes | The authorization scopes for the new token, these must be valid token scopes |
| `expires_at` | string | no | The expiry date for the new token |
| `key_id` | integer | no | The ID of the SSH key used as found in the authorized-keys file or through the `/authorized_keys` check |
| `user_id` | integer | no | User ID for which to generate the new token |
```plaintext
POST /internal/personal_access_token
```
Example request:
```shell
curl --request POST --header "Gitlab-Shared-Secret: <Base64 encoded secret>" \
--data "user_id=29&name=mytokenname&scopes[]=read_user&scopes[]=read_repository&expires_at=2020-07-24" \
"http://localhost:3001/api/v4/internal/personal_access_token"
```
Example response:
```json
{
"success": true,
"token": "Hf_79B288hRv_3-TSD1R",
"scopes": ["read_user","read_repository"],
"expires_at": "2020-07-24"
}
```
### Known consumers
- GitLab Shell
## Incrementing counter on pre-receive
This is called from the Gitaly hooks increasing the reference counter
for a push that might be accepted.
| Attribute | Type | Required | Description |
|:----------|:-------|:---------|:------------|
| `gl_repository` | string | yes | repository identifier for the repository receiving the push |
```plaintext
POST /internal/pre_receive
```
Example request:
```shell
curl --request POST --header "Gitlab-Shared-Secret: <Base64 encoded secret>" \
--data "gl_repository=project-7" "http://localhost:3001/api/v4/internal/pre_receive"
```
Example response:
```json
{
"reference_counter_increased": true
}
```
## PostReceive
Called from Gitaly after a receiving a push. This triggers the
`PostReceive`-worker in Sidekiq, processes the passed push options and
builds the response including messages that need to be displayed to
the user.
| Attribute | Type | Required | Description |
|:----------|:-------|:---------|:------------|
| `identifier` | string | yes | `user-[id]` or `key-[id]` Identifying the user performing the push |
| `gl_repository` | string | yes | identifier of the repository being pushed to |
| `push_options` | string array | no | array of push options |
| `changes` | string | no | refs to be updated in the push in the format `oldrev newrev refname\n`. |
```plaintext
POST /internal/post_receive
```
Example Request:
```shell
curl --request POST --header "Gitlab-Shared-Secret: <Base64 encoded secret>" \
--data "gl_repository=project-7" --data "identifier=user-1" \
--data "changes=0000000000000000000000000000000000000000 fd9e76b9136bdd9fe217061b497745792fe5a5ee gh-pages\n" \
"http://localhost:3001/api/v4/internal/post_receive"
```
Example response:
```json
{
"messages": [
{
"message": "Hello from post-receive",
"type": "alert"
}
],
"reference_counter_decreased": true
}
```
## Kubernetes agent endpoints
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/41045) in GitLab 13.4.
> - This feature is not deployed on GitLab.com
> - It's not recommended for production use.
The following endpoints are used by the GitLab Kubernetes Agent Server (`kas`)
for various purposes.
These endpoints are all authenticated using JWT. The JWT secret is stored in a file
specified in `config/gitlab.yml`. By default, the location is in the root of the
GitLab Rails app in a file called `.gitlab_kas_secret`.
WARNING:
The Kubernetes agent is under development and is not recommended for production use.
### Kubernetes agent information
Called from GitLab Kubernetes Agent Server (`kas`) to retrieve agent
information for the given agent token. This returns the Gitaly connection
information for the agent's project in order for `kas` to fetch and update
the agent's configuration.
```plaintext
GET /internal/kubernetes/agent_info
```
Example Request:
```shell
curl --request GET --header "Gitlab-Kas-Api-Request: <JWT token>" \
--header "Authorization: Bearer <agent token>" "http://localhost:3000/api/v4/internal/kubernetes/agent_info"
```
### Kubernetes agent project information
Called from GitLab Kubernetes Agent Server (`kas`) to retrieve project
information for the given agent token. This returns the Gitaly
connection for the requested project. GitLab `kas` uses this to configure
the agent to fetch Kubernetes resources from the project repository to
sync.
Only public projects are supported. For private projects, the ability for the
agent to be authorized is [not yet implemented](https://gitlab.com/gitlab-org/gitlab/-/issues/220912).
| Attribute | Type | Required | Description |
|:----------|:-------|:---------|:------------|
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../api/index.md#namespaced-path-encoding) |
```plaintext
GET /internal/kubernetes/project_info
```
Example Request:
```shell
curl --request GET --header "Gitlab-Kas-Api-Request: <JWT token>" \
--header "Authorization: Bearer <agent token>" "http://localhost:3000/api/v4/internal/kubernetes/project_info?id=7"
```
### Kubernetes agent usage metrics
Called from GitLab Kubernetes Agent Server (`kas`) to increase the usage
metric counters.
| Attribute | Type | Required | Description |
|:----------|:-------|:---------|:------------|
| `gitops_sync_count` | integer| no | The number to increase the `gitops_sync_count` counter by |
| `k8s_api_proxy_request_count` | integer| no | The number to increase the `k8s_api_proxy_request_count` counter by |
```plaintext
POST /internal/kubernetes/usage_metrics
```
Example Request:
```shell
curl --request POST --header "Gitlab-Kas-Api-Request: <JWT token>" --header "Content-Type: application/json" \
--data '{"gitops_sync_count":1}' "http://localhost:3000/api/v4/internal/kubernetes/usage_metrics"
```
### Kubernetes agent alert metrics
Called from GitLab Kubernetes Agent Server (KAS) to save alerts derived from Cilium on Kubernetes
Cluster.
| Attribute | Type | Required | Description |
|:----------|:-------|:---------|:------------|
| `alert` | Hash | yes | Alerts detail. Same format as [3rd party alert](../operations/incident_management/integrations.md#customize-the-alert-payload-outside-of-gitlab). |
```plaintext
POST internal/kubernetes/modules/cilium_alert
```
Example Request:
```shell
curl --request POST --header "Gitlab-Kas-Api-Request: <JWT token>" \
--header "Authorization: Bearer <agent token>" --header "Content-Type: application/json" \
--data '"{\"alert\":{\"title\":\"minimal\",\"message\":\"network problem\",\"evalMatches\":[{\"value\":1,\"metric\":\"Count\",\"tags\":{}}]}}"' \
"http://localhost:3000/api/v4/internal/kubernetes/modules/cilium_alert"
```
### Create Starboard vulnerability
Called from the GitLab Kubernetes Agent Server (`kas`) to create a security vulnerability
from a Starboard vulnerability report. This request is idempotent. Multiple requests with the same data
create a single vulnerability.
| Attribute | Type | Required | Description |
|:----------------|:-------|:---------|:------------|
| `vulnerability` | Hash | yes | Vulnerability data matching the security report schema [`vulnerability` field](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/src/security-report-format.json). |
| `scanner` | Hash | yes | Scanner data matching the security report schema [`scanner` field](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/src/security-report-format.json). |
```plaintext
PUT internal/kubernetes/modules/starboard_vulnerability
```
Example Request:
```shell
curl --request PUT --header "Gitlab-Kas-Api-Request: <JWT token>" \
--header "Authorization: Bearer <agent token>" --header "Content-Type: application/json" \
--url "http://localhost:3000/api/v4/internal/kubernetes/modules/starboard_vulnerability" \
--data '{
"vulnerability": {
"name": "CVE-123-4567 in libc",
"severity": "high",
"confidence": "unknown",
"location": {
"kubernetes_resource": {
"namespace": "production",
"kind": "deployment",
"name": "nginx",
"container": "nginx"
}
},
"identifiers": [
{
"type": "cve",
"name": "CVE-123-4567",
"value": "CVE-123-4567"
}
]
},
"scanner": {
"id": "starboard_trivy",
"name": "Trivy (via Starboard Operator)",
"vendor": "GitLab"
}
}'
```
## Subscriptions
The subscriptions endpoint is used by [CustomersDot](https://gitlab.com/gitlab-org/customers-gitlab-com) (`customers.gitlab.com`)
in order to apply subscriptions including trials, and add-on purchases, for personal namespaces or top-level groups within GitLab.com.
### Creating a subscription
Use a POST to create a subscription.
```plaintext
POST /namespaces/:id/gitlab_subscription
```
| Attribute | Type | Required | Description |
|:------------|:--------|:---------|:------------|
| `start_date` | date | yes | Start date of subscription |
| `end_date` | date | no | End date of subscription |
| `plan_code` | string | no | Subscription tier code |
| `seats` | integer | no | Number of seats in subscription |
| `max_seats_used` | integer | no | Highest number of active users in the last month |
| `auto_renew` | boolean | no | Whether subscription auto-renews on end date |
| `trial` | boolean | no | Whether subscription is a trial |
| `trial_starts_on` | date | no | Start date of trial |
| `trial_ends_on` | date | no | End date of trial |
Example request:
```shell
curl --request POST --header "TOKEN: <admin_access_token>" "https://gitlab.com/api/v4/namespaces/1234/gitlab_subscription?start_date="2020-07-15"&plan="premium"&seats=10"
```
Example response:
```json
{
"plan": {
"code":"premium",
"name":"premium",
"trial":false,
"auto_renew":null,
"upgradable":false
},
"usage": {
"seats_in_subscription":10,
"seats_in_use":1,
"max_seats_used":0,
"seats_owed":0
},
"billing": {
"subscription_start_date":"2020-07-15",
"subscription_end_date":null,
"trial_ends_on":null
}
}
```
### Updating a subscription
Use a PUT command to update an existing subscription.
```plaintext
PUT /namespaces/:id/gitlab_subscription
```
| Attribute | Type | Required | Description |
|:------------|:--------|:---------|:------------|
| `start_date` | date | no | Start date of subscription |
| `end_date` | date | no | End date of subscription |
| `plan_code` | string | no | Subscription tier code |
| `seats` | integer | no | Number of seats in subscription |
| `max_seats_used` | integer | no | Highest number of active users in the last month |
| `auto_renew` | boolean | no | Whether subscription auto-renews on end date |
| `trial` | boolean | no | Whether subscription is a trial |
| `trial_starts_on` | date | no | Start date of trial. Required if trial is true. |
| `trial_ends_on` | date | no | End date of trial |
Example request:
```shell
curl --request PUT --header "TOKEN: <admin_access_token>" "https://gitlab.com/api/v4/namespaces/1234/gitlab_subscription?max_seats_used=0"
```
Example response:
```json
{
"plan": {
"code":"premium",
"name":"premium",
"trial":false,
"auto_renew":null,
"upgradable":false
},
"usage": {
"seats_in_subscription":80,
"seats_in_use":82,
"max_seats_used":0,
"seats_owed":2
},
"billing": {
"subscription_start_date":"2020-07-15",
"subscription_end_date":"2021-07-15",
"trial_ends_on":null
}
}
```
### Retrieving a subscription
Use a GET command to view an existing subscription.
```plaintext
GET /namespaces/:id/gitlab_subscription
```
Example request:
```shell
curl --header "TOKEN: <admin_access_token>" "https://gitlab.com/api/v4/namespaces/1234/gitlab_subscription"
```
Example response:
```json
{
"plan": {
"code":"premium",
"name":"premium",
"trial":false,
"auto_renew":null,
"upgradable":false
},
"usage": {
"seats_in_subscription":80,
"seats_in_use":82,
"max_seats_used":82,
"seats_owed":2
},
"billing": {
"subscription_start_date":"2020-07-15",
"subscription_end_date":"2021-07-15",
"trial_ends_on":null
}
}
```
### Known consumers
- CustomersDot
## CI minute provisioning
The CI Minute endpoints are used by [CustomersDot](https://gitlab.com/gitlab-org/customers-gitlab-com) (`customers.gitlab.com`)
to apply additional packs of CI minutes, for personal namespaces or top-level groups within GitLab.com.
### Creating an additional pack
Use a POST to create additional packs.
```plaintext
POST /namespaces/:id/minutes
```
| Attribute | Type | Required | Description |
|:------------|:--------|:---------|:------------|
| `packs` | array | yes | An array of purchased minutes packs |
| `packs[expires_at]` | date | yes | Expiry date of the purchased pack|
| `packs[number_of_minutes]` | integer | yes | Number of additional minutes |
| `packs[purchase_xid]` | string | yes | The unique ID of the purchase |
Example request:
```shell
curl --request POST \
--url "http://localhost:3000/api/v4/namespaces/123/minutes" \
--header 'Content-Type: application/json' \
--header 'PRIVATE-TOKEN: <admin access token>' \
--data '{
"packs": [
{
"number_of_minutes": 10000,
"expires_at": "2022-01-01",
"purchase_xid": "46952fe69bebc1a4de10b2b4ff439d0c"
}
]
}'
```
Example response:
```json
[
{
"namespace_id": 123,
"expires_at": "2022-01-01",
"number_of_minutes": 10000,
"purchase_xid": "46952fe69bebc1a4de10b2b4ff439d0c"
}
]
```
### Moving additional packs
Use a PATCH to move additional packs from one namespace to another.
```plaintext
PATCH /namespaces/:id/minutes/move/:target_id
```
| Attribute | Type | Required | Description |
|:------------|:--------|:---------|:------------|
| `id` | string | yes | The ID of the namespace to transfer packs from |
| `target_id` | string | yes | The ID of the target namespace to transfer the packs to |
Example request:
```shell
curl --request PATCH \
--url "http://localhost:3000/api/v4/namespaces/123/minutes/move/321" \
--header 'PRIVATE-TOKEN: <admin access token>'
```
Example response:
```json
{
"message": "202 Accepted"
}
```
### Known consumers
- CustomersDot
## Upcoming reconciliations
The `upcoming_reconciliations` endpoint is used by [CustomersDot](https://gitlab.com/gitlab-org/customers-gitlab-com) (`customers.gitlab.com`)
to update upcoming reconciliations for namespaces.
### Updating `upcoming_reconciliations`
Use a PUT command to update `upcoming_reconciliations`.
```plaintext
PUT /internal/upcoming_reconciliations
```
| Attribute | Type | Required | Description |
|:-------------------|:-----------|:---------|:------------|
| `upcoming_reconciliations` | array | yes | Array of upcoming reconciliations |
Each array element contains:
| Attribute | Type | Required | Description |
|:-------------------|:-----------|:---------|:------------|
| `namespace_id` | integer | yes | ID of the namespace to be reconciled |
| `next_reconciliation_date` | date | yes | Date when next reconciliation will happen |
| `display_alert_from` | date | yes | Start date to display alert of upcoming reconciliation |
Example request:
```shell
curl --request PUT --header "PRIVATE-TOKEN: <admin_access_token>" --header "Content-Type: application/json" \
--data '{"upcoming_reconciliations": [{"namespace_id": 127, "next_reconciliation_date": "13 Jun 2021", "display_alert_from": "06 Jun 2021"}, {"namespace_id": 129, "next_reconciliation_date": "12 Jun 2021", "display_alert_from": "05 Jun 2021"}]}' \
"https://gitlab.com/api/v4/internal/upcoming_reconciliations"
```
Example response:
```plaintext
200
```
### Known consumers
- CustomersDot
<!-- This redirect file can be deleted after <YYYY-MM-DD>. -->
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page -->

View File

@ -0,0 +1,829 @@
---
stage: Create
group: Source Code
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments"
type: reference, api
---
# Internal API **(FREE)**
The internal API is used by different GitLab components, it can not be
used by other consumers. This documentation is intended for people
working on the GitLab codebase.
This documentation does not yet include the internal API used by
GitLab Pages.
## Adding new endpoints
API endpoints should be externally accessible by default, with proper authentication and authorization.
Before adding a new internal endpoint, consider if the API would potentially be
useful to the wider GitLab community and can be made externally accessible.
One reason we might favor internal API endpoints sometimes is when using such an endpoint requires
internal data that external actors can not have. For example, in the internal Pages API we might use
a secret token that identifies a request as internal or sign a request with a public key that is
not available to a wider community.
Another reason to separate something into an internal API is when request to such API endpoint
should never go through an edge (public) load balancer. This way we can configure different rate
limiting rules and policies around how the endpoint is being accessed, because we know that only
internal requests can be made to that endpoint going through an internal load balancer.
## Authentication
These methods are all authenticated using a shared secret. This secret
is stored in a file at the path configured in `config/gitlab.yml` by
default this is in the root of the rails app named
`.gitlab_shell_secret`
To authenticate using that token, clients read the contents of that
file, and include the token Base64 encoded in a `secret_token` parameter
or in the `Gitlab-Shared-Secret` header.
NOTE:
The internal API used by GitLab Pages, and GitLab Kubernetes Agent Server (`kas`) uses JSON Web Token (JWT)
authentication, which is different from GitLab Shell.
## Git Authentication
This is called by [Gitaly](https://gitlab.com/gitlab-org/gitaly) and
[GitLab Shell](https://gitlab.com/gitlab-org/gitlab-shell) to check access to a
repository.
- **When called from GitLab Shell**: No changes are passed, and the internal
API replies with the information needed to pass the request on to Gitaly.
- **When called from Gitaly in a `pre-receive` hook**: The changes are passed
and validated to determine if the push is allowed.
Calls are limited to 50 seconds each.
```plaintext
POST /internal/allowed
```
| Attribute | Type | Required | Description |
|:----------|:-------|:---------|:------------|
| `key_id` | string | no | ID of the SSH-key used to connect to GitLab Shell |
| `username` | string | no | Username from the certificate used to connect to GitLab Shell |
| `project` | string | no (if `gl_repository` is passed) | Path to the project |
| `gl_repository` | string | no (if `project` is passed) | Repository identifier, such as `project-7` |
| `protocol` | string | yes | SSH when called from GitLab Shell, HTTP or SSH when called from Gitaly |
| `action` | string | yes | Git command being run (`git-upload-pack`, `git-receive-pack`, `git-upload-archive`) |
| `changes` | string | yes | `<oldrev> <newrev> <refname>` when called from Gitaly, the magic string `_any` when called from GitLab Shell |
| `check_ip` | string | no | IP address from which call to GitLab Shell was made |
Example request:
```shell
curl --request POST --header "Gitlab-Shared-Secret: <Base64 encoded token>" \
--data "key_id=11&project=gnuwget/wget2&action=git-upload-pack&protocol=ssh" \
"http://localhost:3001/api/v4/internal/allowed"
```
Example response:
```json
{
"status": true,
"gl_repository": "project-3",
"gl_project_path": "gnuwget/wget2",
"gl_id": "user-1",
"gl_username": "root",
"git_config_options": [],
"gitaly": {
"repository": {
"storage_name": "default",
"relative_path": "@hashed/4e/07/4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce.git",
"git_object_directory": "",
"git_alternate_object_directories": [],
"gl_repository": "project-3",
"gl_project_path": "gnuwget/wget2"
},
"address": "unix:/Users/bvl/repos/gitlab/gitaly.socket",
"token": null
},
"gl_console_messages": []
}
```
### Known consumers
- Gitaly
- GitLab Shell
## LFS Authentication
This is the endpoint that gets called from GitLab Shell to provide
information for LFS clients when the repository is accessed over SSH.
| Attribute | Type | Required | Description |
|:----------|:-------|:---------|:------------|
| `key_id` | string | no | ID of the SSH-key used to connect to GitLab Shell |
| `username`| string | no | Username from the certificate used to connect to GitLab Shell |
| `project` | string | no | Path to the project |
Example request:
```shell
curl --request POST --header "Gitlab-Shared-Secret: <Base64 encoded token>" \
--data "key_id=11&project=gnuwget/wget2" "http://localhost:3001/api/v4/internal/lfs_authenticate"
```
```json
{
"username": "root",
"lfs_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7ImFjdG9yIjoicm9vdCJ9LCJqdGkiOiIyYWJhZDcxZC0xNDFlLTQ2NGUtOTZlMi1mODllYWRiMGVmZTYiLCJpYXQiOjE1NzAxMTc2NzYsIm5iZiI6MTU3MDExNzY3MSwiZXhwIjoxNTcwMTE5NDc2fQ.g7atlBw1QMY7QEBVPE0LZ8ZlKtaRzaMRmNn41r2YITM",
"repository_http_path": "http://localhost:3001/gnuwget/wget2.git",
"expires_in": 1800
}
```
### Known consumers
- GitLab Shell
## Authorized Keys Check
This endpoint is called by the GitLab Shell authorized keys
check. Which is called by OpenSSH for [fast SSH key
lookup](../../administration/operations/fast_ssh_key_lookup.md).
| Attribute | Type | Required | Description |
|:----------|:-------|:---------|:------------|
| `key` | string | yes | SSH key as passed by OpenSSH to GitLab Shell |
```plaintext
GET /internal/authorized_keys
```
Example request:
```shell
curl --request GET --header "Gitlab-Shared-Secret: <Base64 encoded secret>" "http://localhost:3001/api/v4/internal/authorized_keys?key=<key as passed by OpenSSH>"
```
Example response:
```json
{
"id": 11,
"title": "admin@example.com",
"key": "ssh-rsa ...",
"created_at": "2019-06-27T15:29:02.219Z"
}
```
### Known consumers
- GitLab Shell
## Get user for user ID or key
This endpoint is used when a user performs `ssh git@gitlab.com`. It
discovers the user associated with an SSH key.
| Attribute | Type | Required | Description |
|:----------|:-------|:---------|:------------|
| `key_id` | integer | no | The ID of the SSH key used as found in the authorized-keys file or through the `/authorized_keys` check |
| `username` | string | no | Username of the user being looked up, used by GitLab Shell when authenticating using a certificate |
```plaintext
GET /internal/discover
```
Example request:
```shell
curl --request GET --header "Gitlab-Shared-Secret: <Base64 encoded secret>" "http://localhost:3001/api/v4/internal/discover?key_id=7"
```
Example response:
```json
{
"id": 7,
"name": "Dede Eichmann",
"username": "rubi"
}
```
### Known consumers
- GitLab Shell
## Instance information
This gets some generic information about the instance. This is used
by Geo nodes to get information about each other.
```plaintext
GET /internal/check
```
Example request:
```shell
curl --request GET --header "Gitlab-Shared-Secret: <Base64 encoded secret>" "http://localhost:3001/api/v4/internal/check"
```
Example response:
```json
{
"api_version": "v4",
"gitlab_version": "12.3.0-pre",
"gitlab_rev": "d69c988e6a6",
"redis": true
}
```
### Known consumers
- GitLab Geo
- GitLab Shell's `bin/check`
- Gitaly
## Get new 2FA recovery codes using an SSH key
This is called from GitLab Shell and allows users to get new 2FA
recovery codes based on their SSH key.
| Attribute | Type | Required | Description |
|:----------|:-------|:---------|:------------|
| `key_id` | integer | no | The ID of the SSH key used as found in the authorized-keys file or through the `/authorized_keys` check |
| `user_id` | integer | no | **Deprecated** User_id for which to generate new recovery codes |
```plaintext
GET /internal/two_factor_recovery_codes
```
Example request:
```shell
curl --request POST --header "Gitlab-Shared-Secret: <Base64 encoded secret>" \
--data "key_id=7" "http://localhost:3001/api/v4/internal/two_factor_recovery_codes"
```
Example response:
```json
{
"success": true,
"recovery_codes": [
"d93ee7037944afd5",
"19d7b84862de93dd",
"1e8c52169195bf71",
"be50444dddb7ca84",
"26048c77d161d5b7",
"482d5c03d1628c47",
"d2c695e309ce7679",
"dfb4748afc4f12a7",
"0e5f53d1399d7979",
"af04d5622153b020"
]
}
```
### Known consumers
- GitLab Shell
## Get new personal access-token
This is called from GitLab Shell and allows users to generate a new
personal access token.
| Attribute | Type | Required | Description |
|:----------|:-------|:---------|:------------|
| `name` | string | yes | The name of the new token |
| `scopes` | string array | yes | The authorization scopes for the new token, these must be valid token scopes |
| `expires_at` | string | no | The expiry date for the new token |
| `key_id` | integer | no | The ID of the SSH key used as found in the authorized-keys file or through the `/authorized_keys` check |
| `user_id` | integer | no | User ID for which to generate the new token |
```plaintext
POST /internal/personal_access_token
```
Example request:
```shell
curl --request POST --header "Gitlab-Shared-Secret: <Base64 encoded secret>" \
--data "user_id=29&name=mytokenname&scopes[]=read_user&scopes[]=read_repository&expires_at=2020-07-24" \
"http://localhost:3001/api/v4/internal/personal_access_token"
```
Example response:
```json
{
"success": true,
"token": "Hf_79B288hRv_3-TSD1R",
"scopes": ["read_user","read_repository"],
"expires_at": "2020-07-24"
}
```
### Known consumers
- GitLab Shell
## Incrementing counter on pre-receive
This is called from the Gitaly hooks increasing the reference counter
for a push that might be accepted.
| Attribute | Type | Required | Description |
|:----------|:-------|:---------|:------------|
| `gl_repository` | string | yes | repository identifier for the repository receiving the push |
```plaintext
POST /internal/pre_receive
```
Example request:
```shell
curl --request POST --header "Gitlab-Shared-Secret: <Base64 encoded secret>" \
--data "gl_repository=project-7" "http://localhost:3001/api/v4/internal/pre_receive"
```
Example response:
```json
{
"reference_counter_increased": true
}
```
## PostReceive
Called from Gitaly after a receiving a push. This triggers the
`PostReceive`-worker in Sidekiq, processes the passed push options and
builds the response including messages that need to be displayed to
the user.
| Attribute | Type | Required | Description |
|:----------|:-------|:---------|:------------|
| `identifier` | string | yes | `user-[id]` or `key-[id]` Identifying the user performing the push |
| `gl_repository` | string | yes | identifier of the repository being pushed to |
| `push_options` | string array | no | array of push options |
| `changes` | string | no | refs to be updated in the push in the format `oldrev newrev refname\n`. |
```plaintext
POST /internal/post_receive
```
Example Request:
```shell
curl --request POST --header "Gitlab-Shared-Secret: <Base64 encoded secret>" \
--data "gl_repository=project-7" --data "identifier=user-1" \
--data "changes=0000000000000000000000000000000000000000 fd9e76b9136bdd9fe217061b497745792fe5a5ee gh-pages\n" \
"http://localhost:3001/api/v4/internal/post_receive"
```
Example response:
```json
{
"messages": [
{
"message": "Hello from post-receive",
"type": "alert"
}
],
"reference_counter_decreased": true
}
```
## Kubernetes agent endpoints
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/41045) in GitLab 13.4.
> - This feature is not deployed on GitLab.com
> - It's not recommended for production use.
The following endpoints are used by the GitLab Kubernetes Agent Server (`kas`)
for various purposes.
These endpoints are all authenticated using JWT. The JWT secret is stored in a file
specified in `config/gitlab.yml`. By default, the location is in the root of the
GitLab Rails app in a file called `.gitlab_kas_secret`.
WARNING:
The Kubernetes agent is under development and is not recommended for production use.
### Kubernetes agent information
Called from GitLab Kubernetes Agent Server (`kas`) to retrieve agent
information for the given agent token. This returns the Gitaly connection
information for the agent's project in order for `kas` to fetch and update
the agent's configuration.
```plaintext
GET /internal/kubernetes/agent_info
```
Example Request:
```shell
curl --request GET --header "Gitlab-Kas-Api-Request: <JWT token>" \
--header "Authorization: Bearer <agent token>" "http://localhost:3000/api/v4/internal/kubernetes/agent_info"
```
### Kubernetes agent project information
Called from GitLab Kubernetes Agent Server (`kas`) to retrieve project
information for the given agent token. This returns the Gitaly
connection for the requested project. GitLab `kas` uses this to configure
the agent to fetch Kubernetes resources from the project repository to
sync.
Only public projects are supported. For private projects, the ability for the
agent to be authorized is [not yet implemented](https://gitlab.com/gitlab-org/gitlab/-/issues/220912).
| Attribute | Type | Required | Description |
|:----------|:-------|:---------|:------------|
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../../api/index.md#namespaced-path-encoding) |
```plaintext
GET /internal/kubernetes/project_info
```
Example Request:
```shell
curl --request GET --header "Gitlab-Kas-Api-Request: <JWT token>" \
--header "Authorization: Bearer <agent token>" "http://localhost:3000/api/v4/internal/kubernetes/project_info?id=7"
```
### Kubernetes agent usage metrics
Called from GitLab Kubernetes Agent Server (`kas`) to increase the usage
metric counters.
| Attribute | Type | Required | Description |
|:----------|:-------|:---------|:------------|
| `gitops_sync_count` | integer| no | The number to increase the `gitops_sync_count` counter by |
| `k8s_api_proxy_request_count` | integer| no | The number to increase the `k8s_api_proxy_request_count` counter by |
```plaintext
POST /internal/kubernetes/usage_metrics
```
Example Request:
```shell
curl --request POST --header "Gitlab-Kas-Api-Request: <JWT token>" --header "Content-Type: application/json" \
--data '{"gitops_sync_count":1}' "http://localhost:3000/api/v4/internal/kubernetes/usage_metrics"
```
### Kubernetes agent alert metrics
Called from GitLab Kubernetes Agent Server (KAS) to save alerts derived from Cilium on Kubernetes
Cluster.
| Attribute | Type | Required | Description |
|:----------|:-------|:---------|:------------|
| `alert` | Hash | yes | Alerts detail. Same format as [3rd party alert](../../operations/incident_management/integrations.md#customize-the-alert-payload-outside-of-gitlab). |
```plaintext
POST internal/kubernetes/modules/cilium_alert
```
Example Request:
```shell
curl --request POST --header "Gitlab-Kas-Api-Request: <JWT token>" \
--header "Authorization: Bearer <agent token>" --header "Content-Type: application/json" \
--data '"{\"alert\":{\"title\":\"minimal\",\"message\":\"network problem\",\"evalMatches\":[{\"value\":1,\"metric\":\"Count\",\"tags\":{}}]}}"' \
"http://localhost:3000/api/v4/internal/kubernetes/modules/cilium_alert"
```
### Create Starboard vulnerability
Called from the GitLab Kubernetes Agent Server (`kas`) to create a security vulnerability
from a Starboard vulnerability report. This request is idempotent. Multiple requests with the same data
create a single vulnerability.
| Attribute | Type | Required | Description |
|:----------------|:-------|:---------|:------------|
| `vulnerability` | Hash | yes | Vulnerability data matching the security report schema [`vulnerability` field](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/src/security-report-format.json). |
| `scanner` | Hash | yes | Scanner data matching the security report schema [`scanner` field](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/src/security-report-format.json). |
```plaintext
PUT internal/kubernetes/modules/starboard_vulnerability
```
Example Request:
```shell
curl --request PUT --header "Gitlab-Kas-Api-Request: <JWT token>" \
--header "Authorization: Bearer <agent token>" --header "Content-Type: application/json" \
--url "http://localhost:3000/api/v4/internal/kubernetes/modules/starboard_vulnerability" \
--data '{
"vulnerability": {
"name": "CVE-123-4567 in libc",
"severity": "high",
"confidence": "unknown",
"location": {
"kubernetes_resource": {
"namespace": "production",
"kind": "deployment",
"name": "nginx",
"container": "nginx"
}
},
"identifiers": [
{
"type": "cve",
"name": "CVE-123-4567",
"value": "CVE-123-4567"
}
]
},
"scanner": {
"id": "starboard_trivy",
"name": "Trivy (via Starboard Operator)",
"vendor": "GitLab"
}
}'
```
## Subscriptions
The subscriptions endpoint is used by [CustomersDot](https://gitlab.com/gitlab-org/customers-gitlab-com) (`customers.gitlab.com`)
in order to apply subscriptions including trials, and add-on purchases, for personal namespaces or top-level groups within GitLab.com.
### Creating a subscription
Use a POST to create a subscription.
```plaintext
POST /namespaces/:id/gitlab_subscription
```
| Attribute | Type | Required | Description |
|:------------|:--------|:---------|:------------|
| `start_date` | date | yes | Start date of subscription |
| `end_date` | date | no | End date of subscription |
| `plan_code` | string | no | Subscription tier code |
| `seats` | integer | no | Number of seats in subscription |
| `max_seats_used` | integer | no | Highest number of active users in the last month |
| `auto_renew` | boolean | no | Whether subscription auto-renews on end date |
| `trial` | boolean | no | Whether subscription is a trial |
| `trial_starts_on` | date | no | Start date of trial |
| `trial_ends_on` | date | no | End date of trial |
Example request:
```shell
curl --request POST --header "TOKEN: <admin_access_token>" "https://gitlab.com/api/v4/namespaces/1234/gitlab_subscription?start_date="2020-07-15"&plan="premium"&seats=10"
```
Example response:
```json
{
"plan": {
"code":"premium",
"name":"premium",
"trial":false,
"auto_renew":null,
"upgradable":false
},
"usage": {
"seats_in_subscription":10,
"seats_in_use":1,
"max_seats_used":0,
"seats_owed":0
},
"billing": {
"subscription_start_date":"2020-07-15",
"subscription_end_date":null,
"trial_ends_on":null
}
}
```
### Updating a subscription
Use a PUT command to update an existing subscription.
```plaintext
PUT /namespaces/:id/gitlab_subscription
```
| Attribute | Type | Required | Description |
|:------------|:--------|:---------|:------------|
| `start_date` | date | no | Start date of subscription |
| `end_date` | date | no | End date of subscription |
| `plan_code` | string | no | Subscription tier code |
| `seats` | integer | no | Number of seats in subscription |
| `max_seats_used` | integer | no | Highest number of active users in the last month |
| `auto_renew` | boolean | no | Whether subscription auto-renews on end date |
| `trial` | boolean | no | Whether subscription is a trial |
| `trial_starts_on` | date | no | Start date of trial. Required if trial is true. |
| `trial_ends_on` | date | no | End date of trial |
Example request:
```shell
curl --request PUT --header "TOKEN: <admin_access_token>" "https://gitlab.com/api/v4/namespaces/1234/gitlab_subscription?max_seats_used=0"
```
Example response:
```json
{
"plan": {
"code":"premium",
"name":"premium",
"trial":false,
"auto_renew":null,
"upgradable":false
},
"usage": {
"seats_in_subscription":80,
"seats_in_use":82,
"max_seats_used":0,
"seats_owed":2
},
"billing": {
"subscription_start_date":"2020-07-15",
"subscription_end_date":"2021-07-15",
"trial_ends_on":null
}
}
```
### Retrieving a subscription
Use a GET command to view an existing subscription.
```plaintext
GET /namespaces/:id/gitlab_subscription
```
Example request:
```shell
curl --header "TOKEN: <admin_access_token>" "https://gitlab.com/api/v4/namespaces/1234/gitlab_subscription"
```
Example response:
```json
{
"plan": {
"code":"premium",
"name":"premium",
"trial":false,
"auto_renew":null,
"upgradable":false
},
"usage": {
"seats_in_subscription":80,
"seats_in_use":82,
"max_seats_used":82,
"seats_owed":2
},
"billing": {
"subscription_start_date":"2020-07-15",
"subscription_end_date":"2021-07-15",
"trial_ends_on":null
}
}
```
### Known consumers
- CustomersDot
## CI minute provisioning
The CI Minute endpoints are used by [CustomersDot](https://gitlab.com/gitlab-org/customers-gitlab-com) (`customers.gitlab.com`)
to apply additional packs of CI minutes, for personal namespaces or top-level groups within GitLab.com.
### Creating an additional pack
Use a POST to create additional packs.
```plaintext
POST /namespaces/:id/minutes
```
| Attribute | Type | Required | Description |
|:------------|:--------|:---------|:------------|
| `packs` | array | yes | An array of purchased minutes packs |
| `packs[expires_at]` | date | yes | Expiry date of the purchased pack|
| `packs[number_of_minutes]` | integer | yes | Number of additional minutes |
| `packs[purchase_xid]` | string | yes | The unique ID of the purchase |
Example request:
```shell
curl --request POST \
--url "http://localhost:3000/api/v4/namespaces/123/minutes" \
--header 'Content-Type: application/json' \
--header 'PRIVATE-TOKEN: <admin access token>' \
--data '{
"packs": [
{
"number_of_minutes": 10000,
"expires_at": "2022-01-01",
"purchase_xid": "46952fe69bebc1a4de10b2b4ff439d0c"
}
]
}'
```
Example response:
```json
[
{
"namespace_id": 123,
"expires_at": "2022-01-01",
"number_of_minutes": 10000,
"purchase_xid": "46952fe69bebc1a4de10b2b4ff439d0c"
}
]
```
### Moving additional packs
Use a PATCH to move additional packs from one namespace to another.
```plaintext
PATCH /namespaces/:id/minutes/move/:target_id
```
| Attribute | Type | Required | Description |
|:------------|:--------|:---------|:------------|
| `id` | string | yes | The ID of the namespace to transfer packs from |
| `target_id` | string | yes | The ID of the target namespace to transfer the packs to |
Example request:
```shell
curl --request PATCH \
--url "http://localhost:3000/api/v4/namespaces/123/minutes/move/321" \
--header 'PRIVATE-TOKEN: <admin access token>'
```
Example response:
```json
{
"message": "202 Accepted"
}
```
### Known consumers
- CustomersDot
## Upcoming reconciliations
The `upcoming_reconciliations` endpoint is used by [CustomersDot](https://gitlab.com/gitlab-org/customers-gitlab-com) (`customers.gitlab.com`)
to update upcoming reconciliations for namespaces.
### Updating `upcoming_reconciliations`
Use a PUT command to update `upcoming_reconciliations`.
```plaintext
PUT /internal/upcoming_reconciliations
```
| Attribute | Type | Required | Description |
|:-------------------|:-----------|:---------|:------------|
| `upcoming_reconciliations` | array | yes | Array of upcoming reconciliations |
Each array element contains:
| Attribute | Type | Required | Description |
|:-------------------|:-----------|:---------|:------------|
| `namespace_id` | integer | yes | ID of the namespace to be reconciled |
| `next_reconciliation_date` | date | yes | Date when next reconciliation will happen |
| `display_alert_from` | date | yes | Start date to display alert of upcoming reconciliation |
Example request:
```shell
curl --request PUT --header "PRIVATE-TOKEN: <admin_access_token>" --header "Content-Type: application/json" \
--data '{"upcoming_reconciliations": [{"namespace_id": 127, "next_reconciliation_date": "13 Jun 2021", "display_alert_from": "06 Jun 2021"}, {"namespace_id": 129, "next_reconciliation_date": "12 Jun 2021", "display_alert_from": "05 Jun 2021"}]}' \
"https://gitlab.com/api/v4/internal/upcoming_reconciliations"
```
Example response:
```plaintext
200
```
### Known consumers
- CustomersDot

View File

@ -13,7 +13,7 @@ GitLab Maintenance Mode **only** blocks writes from HTTP and SSH requests at the
- [the read-only database method](https://gitlab.com/gitlab-org/gitlab/-/blob/2425e9de50c678413ceaad6ee3bf66f42b7e228c/ee/lib/ee/gitlab/database.rb#L13), which toggles special behavior when we are not allowed to write to the database. [Search the codebase for `Gitlab::Database.read_only?`.](https://gitlab.com/search?search=Gitlab%3A%3ADatabase.read_only%3F&group_id=9970&project_id=278964&scope=blobs&search_code=false&snippets=false&repository_ref=)
- [the read-only middleware](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/ee/gitlab/middleware/read_only/controller.rb), where HTTP requests that cause database writes are blocked, unless explicitly allowed.
- [Git push access via SSH is denied](https://gitlab.com/gitlab-org/gitlab/-/blob/2425e9de50c678413ceaad6ee3bf66f42b7e228c/ee/lib/ee/gitlab/git_access.rb#L13) by returning 401 when `gitlab-shell` POSTs to [`/internal/allowed`](internal_api.md) to [check if access is allowed](internal_api.md#git-authentication).
- [Git push access via SSH is denied](https://gitlab.com/gitlab-org/gitlab/-/blob/2425e9de50c678413ceaad6ee3bf66f42b7e228c/ee/lib/ee/gitlab/git_access.rb#L13) by returning 401 when `gitlab-shell` POSTs to [`/internal/allowed`](internal_api/index.md) to [check if access is allowed](internal_api/index.md#git-authentication).
- [Container registry authentication service](https://gitlab.com/gitlab-org/gitlab/-/blob/2425e9de50c678413ceaad6ee3bf66f42b7e228c/ee/app/services/ee/auth/container_registry_authentication_service.rb#L12), where updates to the container registry are blocked.
The database itself is not in read-only mode (except in a Geo secondary site) and can be written by sources other than the ones blocked.

View File

@ -48,7 +48,7 @@ application.
sudo -u git -H editor config/gitlab.yml
```
1. Read [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration)
1. Read [Configure initial settings](omniauth.md#configure-initial-settings)
for initial settings.
1. Add the provider configuration:

View File

@ -48,7 +48,7 @@ As you go through the Microsoft procedure, keep the following in mind:
sudo -u git -H editor config/gitlab.yml
```
1. Refer to [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration)
1. Refer to [Configure initial settings](omniauth.md#configure-initial-settings)
for initial settings.
1. Add the provider configuration:
@ -154,7 +154,7 @@ After you have created an application, follow the [Microsoft Quickstart document
sudo -u git -H editor config/gitlab.yml
```
1. Refer to [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration)
1. Refer to [Configure initial settings](omniauth.md#configure-initial-settings)
for initial settings.
1. Add the provider configuration:

View File

@ -28,7 +28,7 @@ configure CAS for back-channel logout.
sudo -u git -H editor config/gitlab.yml
```
1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
1. See [Configure initial settings](omniauth.md#configure-initial-settings) for initial settings.
1. Add the provider configuration:

View File

@ -72,7 +72,7 @@ Facebook. Facebook generates an app ID and secret key for you to use.
sudo -u git -H editor config/gitlab.yml
```
1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
1. See [Configure initial settings](omniauth.md#configure-initial-settings) for initial settings.
1. Add the provider configuration:

View File

@ -29,7 +29,7 @@ When you create an OAuth 2 app in GitHub, you need the following information:
- The URL of your GitLab instance, such as `https://gitlab.example.com`.
- The authorization callback URL; in this case, `https://gitlab.example.com/users/auth`. Include the port number if your GitLab instance uses a non-default port.
See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
See [Configure initial settings](omniauth.md#configure-initial-settings) for initial settings.
After you have configured the GitHub provider, you need the following information. You must substitute that information in the GitLab configuration file in these next steps.

View File

@ -45,7 +45,7 @@ GitLab.com generates an application ID and secret key for you to use.
sudo -u git -H editor config/gitlab.yml
```
1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
1. See [Configure initial settings](omniauth.md#configure-initial-settings) for initial settings.
1. Add the provider configuration:
For Omnibus installations authenticating against **GitLab.com**:

View File

@ -71,7 +71,7 @@ On your GitLab server:
sudo -u git -H editor config/gitlab.yml
```
1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
1. See [Configure initial settings](omniauth.md#configure-initial-settings) for initial settings.
1. Add the provider configuration:
For Omnibus GitLab:

View File

@ -100,7 +100,7 @@ to authenticate with Kerberos tokens.
#### Enable single sign-on
See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration)
See [Configure initial settings](omniauth.md#configure-initial-settings)
for initial settings to enable single sign-on and add Kerberos servers
as an identity provider.
@ -137,7 +137,7 @@ with your Kerberos credentials.
The first time users sign in to GitLab with their Kerberos accounts,
GitLab creates a matching account.
Before you continue, review the [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) options in Omnibus and GitLab source. You must also include `kerberos`.
Before you continue, review the [Configure initial settings](omniauth.md#configure-initial-settings) options in Omnibus and GitLab source. You must also include `kerberos`.
With that information at hand:

View File

@ -55,7 +55,7 @@ This strategy is designed to allow configuration of the simple OmniAuth SSO proc
sudo -u git -H editor config/gitlab.yml
```
1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings
1. See [Configure initial settings](omniauth.md#configure-initial-settings) for initial settings
1. Add the provider-specific configuration for your provider, as [described in the gem's README](https://gitlab.com/satorix/omniauth-oauth2-generic#gitlab-config-example)

View File

@ -6,25 +6,16 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# OmniAuth **(FREE SELF)**
GitLab leverages OmniAuth to allow users to sign in using Twitter, GitHub, and
other popular services. [OmniAuth](https://rubygems.org/gems/omniauth/) is a
"generalized Rack framework for multiple-provider authentication" built on Ruby.
Users can sign in to GitLab by using their credentials from Twitter, GitHub, and other popular services.
[OmniAuth](https://rubygems.org/gems/omniauth/) is the Rack
framework that GitLab uses to provide this authentication.
Configuring OmniAuth does not prevent standard GitLab authentication or LDAP
(if configured) from continuing to work. Users can choose to sign in using any
of the configured mechanisms.
If you configure OmniAuth, users can continue to sign in using other
mechanisms, including standard GitLab authentication or LDAP (if configured).
- [Initial OmniAuth Configuration](#initial-omniauth-configuration)
- [Supported Providers](#supported-providers)
- [Enable OmniAuth for an Existing User](#enable-omniauth-for-an-existing-user)
- [OmniAuth configuration sample when using Omnibus GitLab](https://gitlab.com/gitlab-org/omnibus-gitlab/tree/master#omniauth-google-twitter-github-login)
- [Enable or disable Sign In with an OmniAuth provider without disabling import sources](#enable-or-disable-sign-in-with-an-omniauth-provider-without-disabling-import-sources)
## Supported providers
## Supported Providers
This is a list of the current supported OmniAuth providers. Before proceeding on
each provider's documentation, make sure to first read this document as it
contains some settings that are common for all providers.
GitLab supports the following OmniAuth providers.
| Provider documentation | OmniAuth provider name |
|---------------------------------------------------------------------|----------------------------|
@ -50,100 +41,78 @@ contains some settings that are common for all providers.
| [Shibboleth](saml.md) | `shibboleth` |
| [Twitter](twitter.md) | `twitter` |
## Initial OmniAuth Configuration
The OmniAuth provider names from the table above are needed to configure a few
global settings that are in common for all providers.
## Configure initial settings
NOTE:
Starting from GitLab 11.4, OmniAuth is enabled by default. If you're using an
In GitLab 11.4 and later, OmniAuth is enabled by default. If you're using an
earlier version, you must explicitly enable it.
- `allow_single_sign_on` allows you to specify the providers that automatically
create a GitLab account. For example, if you wish to enable Azure (v2) and Google,
in Omnibus, specify a list of provider names:
Before you configure the OmniAuth provider,
configure the settings that are common for all providers.
```ruby
gitlab_rails['omniauth_allow_single_sign_on'] = ['azure_activedirectory_v2', 'google_oauth2']
```
The value defaults to `false`. If `false` users must be created manually, or
they can't sign in by using OmniAuth.
- `auto_link_ldap_user` can be used if you have [LDAP / ActiveDirectory](../administration/auth/ldap/index.md)
integration enabled. It defaults to `false`. When enabled, users automatically
created through an OmniAuth provider have their LDAP identity created in GitLab as well.
- `block_auto_created_users` defaults to `true`. If `true`, auto created users will
be blocked pending approval by an administrator before they are able to sign in.
NOTE:
If you set `block_auto_created_users` to `false`, make sure to only
define providers under `allow_single_sign_on` that you are able to control, like
SAML, Shibboleth, Crowd, or Google. Otherwise, set it to `true`, or any user on
the Internet can successfully sign in to your GitLab without
administrative approval.
NOTE:
`auto_link_ldap_user` requires the `uid` of the user to be the same in both LDAP
and the OmniAuth provider.
Setting | Description | Default value
---------------------------|-------------|--------------
`allow_single_sign_on` | Enables you to list the providers that automatically create a GitLab account. The provider names are available in the **OmniAuth provider name** column in the [supported providers table](#supported-providers). | The default is `false`. If `false`, users must be created manually, or they can't sign in using OmniAuth.
`auto_link_ldap_user` | If enabled, creates an LDAP identity in GitLab for users that are created through an OmniAuth provider. You can enable this setting if you have the [LDAP (ActiveDirectory)](../administration/auth/ldap/index.md) integration enabled. Requires the `uid` of the user to be the same in both LDAP and the OmniAuth provider. | The default is `false`.
`block_auto_created_users` | If enabled, blocks users that are automatically created from signing in until they are approved by an administrator. | The default is `true`. If you set the value to `false`, make sure you only define providers for `allow_single_sign_on` that you can control, like SAML, Shibboleth, Crowd, or Google. Otherwise, any user on the internet can sign in to GitLab without an administrator's approval.
To change these settings:
- **For Omnibus package**
Open the configuration file:
1. Open the configuration file:
```shell
sudo editor /etc/gitlab/gitlab.rb
```
```shell
sudo editor /etc/gitlab/gitlab.rb
```
and change:
1. Update the following section:
```ruby
# CAUTION!
# This allows users to sign in without having a user account first. Define the allowed providers
# using an array, for example, ["saml", "twitter"], or as true/false to allow all providers or none.
# User accounts will be created automatically when authentication was successful.
gitlab_rails['omniauth_allow_single_sign_on'] = ['saml', 'twitter']
gitlab_rails['omniauth_auto_link_ldap_user'] = true
gitlab_rails['omniauth_block_auto_created_users'] = true
```
```ruby
# CAUTION!
# This allows users to sign in without having a user account first. Define the allowed providers
# using an array, for example, ["saml", "twitter"], or as true/false to allow all providers or none.
# User accounts will be created automatically when authentication was successful.
gitlab_rails['omniauth_allow_single_sign_on'] = ['saml', 'twitter']
gitlab_rails['omniauth_auto_link_ldap_user'] = true
gitlab_rails['omniauth_block_auto_created_users'] = true
```
- **For installations from source**
Open the configuration file:
1. Open the configuration file:
```shell
cd /home/git/gitlab
```shell
cd /home/git/gitlab
sudo -u git -H editor config/gitlab.yml
```
sudo -u git -H editor config/gitlab.yml
```
and change the following section:
1. Update the following section:
```yaml
## OmniAuth settings
omniauth:
# Allow sign-in by using Twitter, Google, etc. using OmniAuth providers
# Versions prior to 11.4 require this to be set to true
# enabled: true
```yaml
## OmniAuth settings
omniauth:
# Allow sign-in by using Twitter, Google, etc. using OmniAuth providers
# Versions prior to 11.4 require this to be set to true
# enabled: true
# CAUTION!
# This allows users to sign in without having a user account first. Define the allowed providers
# using an array, for example, ["saml", "twitter"], or as true/false to allow all providers or none.
# User accounts will be created automatically when authentication was successful.
allow_single_sign_on: ["saml", "twitter"]
# CAUTION!
# This allows users to sign in without having a user account first. Define the allowed providers
# using an array, for example, ["saml", "twitter"], or as true/false to allow all providers or none.
# User accounts will be created automatically when authentication was successful.
allow_single_sign_on: ["saml", "twitter"]
auto_link_ldap_user: true
auto_link_ldap_user: true
# Locks down those users until they have been cleared by the admin (default: true).
block_auto_created_users: true
```
# Locks down those users until they have been cleared by the admin (default: true).
block_auto_created_users: true
```
Now we can choose one or more of the [Supported Providers](#supported-providers)
listed above to continue the configuration process.
After configuring these settings, you can configure
your chosen [provider](#supported-providers).
## Enable OmniAuth for an Existing User
## Enable OmniAuth for an existing user
Existing users can enable OmniAuth for specific providers after the account is
created. For example, if the user originally signed in with LDAP, an OmniAuth
@ -160,7 +129,7 @@ OmniAuth provider for an existing user.
The chosen OmniAuth provider is now active and can be used to sign in to GitLab from then on.
## Automatically Link Existing Users to OmniAuth Users
## Link existing users to OmniAuth users
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36664) in GitLab 13.4.
@ -185,7 +154,7 @@ feature for both an **OpenID Connect provider** and a **Twitter OAuth provider**
auto_link_user: ["openid_connect", "twitter"]
```
## Configure OmniAuth Providers as External
## Configure OmniAuth providers as external
You can define which OmniAuth providers you want to be `external`. Users
creating accounts, or logging in by using these `external` providers cannot have
@ -211,7 +180,7 @@ their accounts to be upgraded to full internal accounts.
external_providers: ['twitter', 'google_oauth2']
```
## Using Custom OmniAuth Providers
## Use a custom OmniAuth provider
NOTE:
The following information only applies for installations from source.
@ -221,8 +190,6 @@ with a few providers pre-installed, such as LDAP, GitHub, and Twitter. You may a
have to integrate with other authentication solutions. For
these cases, you can use the OmniAuth provider.
### Steps
These steps are fairly general and you must figure out the exact details
from the OmniAuth provider's documentation.
@ -252,7 +219,7 @@ from the OmniAuth provider's documentation.
sudo service gitlab start
```
### Examples
### Custom OmniAuth provider examples
If you have successfully set up a provider that is not shipped with GitLab itself,
please let us know.
@ -260,7 +227,7 @@ please let us know.
While we can't officially support every possible authentication mechanism out there,
we'd like to at least help those with specific needs.
## Enable or disable Sign In with an OmniAuth provider without disabling import sources
## Enable or disable sign-in with an OmniAuth provider without disabling import sources
Administrators are able to enable or disable **Sign In** by using some OmniAuth providers.
@ -276,7 +243,7 @@ To enable/disable an OmniAuth provider:
![Enabled OAuth Sign-In sources](img/enabled-oauth-sign-in-sources_v13_10.png)
## Disabling OmniAuth
## Disable OmniAuth
Starting from version 11.4 of GitLab, OmniAuth is enabled by default. This only
has an effect if providers are configured and [enabled](#enable-or-disable-sign-in-with-an-omniauth-provider-without-disabling-import-sources).
@ -318,7 +285,7 @@ When authenticating using LDAP, the user's name and email are always synced.
sync_profile_attributes: ['email', 'location']
```
## Bypassing two factor authentication
## Bypass two-factor authentication
In GitLab 12.3 or later, users can sign in with specified providers _without_
using two factor authentication.
@ -341,7 +308,7 @@ configured only for providers which already have two factor authentication
allow_bypass_two_factor: ['twitter', 'google_oauth2']
```
## Automatically sign in with provider
## Sign in with a provider automatically
You can add the `auto_sign_in_with_provider` setting to your GitLab
configuration to redirect login requests to your OmniAuth provider for

View File

@ -48,7 +48,7 @@ To get the credentials (a pair of Client ID and Client Secret), you must [create
sudo -u git -H editor config/gitlab.yml
```
1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
1. See [Configure initial settings](omniauth.md#configure-initial-settings) for initial settings.
1. Add the provider configuration:

View File

@ -53,7 +53,7 @@ in your SAML IdP:
sudo -u git -H editor config/gitlab.yml
```
1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
1. See [Configure initial settings](omniauth.md#configure-initial-settings) for initial settings.
1. To allow your users to use SAML to sign up without having to manually create
an account first, add the following values to your configuration:

View File

@ -53,7 +53,7 @@ Twitter. Twitter generates a client ID and secret key for you to use.
sudo -u git -H editor config/gitlab.yml
```
1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
1. See [Configure initial settings](omniauth.md#configure-initial-settings) for initial settings.
1. Add the provider configuration:

View File

@ -108,7 +108,7 @@ GitLab has several features which can help you manage the number of users:
- Enable the [**Require administrator approval for new sign ups**](../../user/admin_area/settings/sign_up_restrictions.md#require-administrator-approval-for-new-sign-ups)
option.
- Enable `block_auto_created_users` for new sign-ups via [LDAP](../../administration/auth/ldap/index.md#basic-configuration-settings) or [OmniAuth](../../integration/omniauth.md#initial-omniauth-configuration).
- Enable `block_auto_created_users` for new sign-ups via [LDAP](../../administration/auth/ldap/index.md#basic-configuration-settings) or [OmniAuth](../../integration/omniauth.md#configure-initial-settings).
- Enable the [User cap](../../user/admin_area/settings/sign_up_restrictions.md#user-cap)
option. **Available in GitLab 13.7 and later**.
- [Disable new sign-ups](../../user/admin_area/settings/sign_up_restrictions.md), and instead manage new

View File

@ -145,6 +145,11 @@ To rebase from the UI:
GitLab schedules a rebase of the feature branch against the default branch and
executes it as soon as possible.
The user performing the rebase action is considered
a user that added commits to the merge request. When the merge request approvals setting
[**Prevent approvals by users who add commits**](../../user/project/merge_requests/approvals/settings.md#prevent-approvals-by-users-who-add-commits)
is enabled, this setting prevents the user from also approving the merge request.
### Interactive rebase
You can use interactive rebase to modify commits. For example, amend a commit

View File

@ -17,7 +17,7 @@ pending approval state because an administrator has enabled any of the following
- [Require admin approval for new sign-ups](settings/sign_up_restrictions.md#require-administrator-approval-for-new-sign-ups) setting.
- [User cap](settings/sign_up_restrictions.md#user-cap).
- [Block auto-created users (OmniAuth)](../../integration/omniauth.md#initial-omniauth-configuration)
- [Block auto-created users (OmniAuth)](../../integration/omniauth.md#configure-initial-settings)
- [Block auto-created users (LDAP)](../../administration/auth/ldap/index.md#basic-configuration-settings)
When a user registers for an account while this setting is enabled:

View File

@ -48,7 +48,7 @@ automatically approved in a background job.
NOTE:
This setting doesn't apply to LDAP or OmniAuth users. To enforce approvals for new users
signing up using OmniAuth or LDAP, set `block_auto_created_users` to `true` in the
[OmniAuth configuration](../../../integration/omniauth.md#initial-omniauth-configuration) or
[OmniAuth configuration](../../../integration/omniauth.md#configure-initial-settings) or
[LDAP configuration](../../../administration/auth/ldap/index.md#basic-configuration-settings).
## Require email confirmation

View File

@ -10,8 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> - [Deprecated](https://gitlab.com/groups/gitlab-org/configure/-/epics/8) in GitLab 14.5.
WARNING:
This feature was [deprecated](https://gitlab.com/groups/gitlab-org/configure/-/epics/8) in GitLab 14.5.
The cluster management project was [deprecated](https://gitlab.com/groups/gitlab-org/configure/-/epics/8) in GitLab 14.5.
To manage cluster applications, use the [GitLab Kubernetes Agent](agent/index.md)
with the [Cluster Management Project Template](management_project_template.md).

View File

@ -25,10 +25,11 @@ To create an epic in the group you're in:
- In an empty [roadmap](../roadmap/index.md), select **New epic**.
1. Enter a title.
1. Optional. Enter a description.
1. Optional. To make the epic confidential, select the [Confidentiality checkbox](#make-an-epic-confidential).
1. Optional. Choose labels.
1. Optional. Select a start and due date, or [inherit](#start-and-due-date-inheritance) them.
1. Complete the fields.
- Enter a description.
- To [make the epic confidential](#make-an-epic-confidential), select the checkbox under **Confidentiality**.
- Choose labels.
- Select a start and due date, or [inherit](#start-and-due-date-inheritance) them.
1. Select **Create epic**.
The newly created epic opens.
@ -69,13 +70,13 @@ After you create an epic, you can edit the following details:
To edit an epic's title or description:
1. Select the **Edit title and description** **{pencil}** button.
1. Select **Edit title and description** **{pencil}**.
1. Make your changes.
1. Select **Save changes**.
To edit an epic's start date, due date, or labels:
1. Select **Edit** next to each section in the epic sidebar.
1. Next to each section in the right sidebar, select **Edit**.
1. Select the dates or labels for your epic.
## Bulk edit epics
@ -89,18 +90,21 @@ When bulk editing epics in a group, you can edit their labels.
To update multiple epics at the same time:
1. In a group, go to **Epics > List**.
1. Click **Edit epics**. A sidebar on the right-hand side of your screen appears with editable fields.
1. Check the checkboxes next to each epic you want to edit.
1. Select **Edit epics**. A sidebar on the right appears with editable fields.
1. Select the checkboxes next to each epic you want to edit.
1. Select the appropriate fields and their values from the sidebar.
1. Click **Update all**.
1. Select **Update all**.
## Delete an epic
NOTE:
To delete an epic, you need to be an [Owner](../../permissions.md#group-members-permissions) of a group/subgroup.
To delete an epic, you must be an [Owner](../../permissions.md#group-members-permissions) of a group
or subgroup.
When editing the description of an epic, select the **Delete** button to delete the epic.
A modal appears to confirm your action.
To delete the epic:
1. Select **Edit title and description** **{pencil}**.
1. Select **Delete**. A modal appears to confirm your action.
Deleting an epic releases all existing issues from their associated epic in the system.
@ -112,26 +116,26 @@ If you delete an epic, all its child epics and their descendants are deleted as
Whenever you decide that there is no longer need for that epic,
close the epic by:
- Selecting the **Close epic** button.
- Selecting **Close epic**.
![close epic - button](img/button_close_epic.png)
- Using a [quick action](../../project/quick_actions.md).
- Using the `/close` [quick action](../../project/quick_actions.md).
## Reopen a closed epic
You can reopen an epic that was closed by:
- Clicking the **Reopen epic** button.
- Selecting **Reopen epic**.
![reopen epic - button](img/button_reopen_epic.png)
- Using a [quick action](../../project/quick_actions.md).
- Using the `/reopen` [quick action](../../project/quick_actions.md).
## Go to an epic from an issue
If an issue belongs to an epic, you can navigate to the containing epic with the
link in the issue sidebar.
If an issue belongs to an epic, you can go to the parent epic with the
link in the right sidebar.
![containing epic](img/containing_epic.png)
@ -156,13 +160,12 @@ than 1000. The cached value is rounded to thousands or millions and updated ever
## Search for an epic from epics list page
> - Introduced in GitLab 10.5.
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/37081) from GitLab Ultimate to GitLab Premium in 12.8.
> - Searching by the user's reaction emoji [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/325630) in GitLab 13.11.
> - Sorting by epic titles [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/331625) in GitLab 14.1.
You can search for an epic from the list of epics using filtered search bar (similar to
that of issues and merge requests) based on following parameters:
You can search for an epic from the list of epics using filtered search bar based on following
parameters:
- Title or description
- Author name / username
@ -171,10 +174,13 @@ that of issues and merge requests) based on following parameters:
![epics search](img/epics_search_v13_11.png)
To search, go to the list of epics and select the field **Search or filter results**.
It displays a dropdown menu, from which you can add an author. You can also enter plain
text to search by epic title or description. When done, press <kbd>Enter</kbd> on your
keyboard to filter the list.
To search:
1. On the top bar, select **Menu > Groups** and find your group.
1. On the left sidebar, select **Epics**.
1. Select the field **Search or filter results**.
1. From the dropdown menu, select the scope or enter plain text to search by epic title or description.
1. Press <kbd>Enter</kbd> on your keyboard. The list is filtered.
You can also sort epics list by:
@ -198,7 +204,7 @@ You can reverse the default order and interact with the activity feed sorted by
at the top. Your preference is saved via local storage and automatically applied to every epic and issue
you view.
To change the activity sort order, click the **Oldest first** dropdown menu and select either oldest
To change the activity sort order, select the **Oldest first** dropdown menu and select either oldest
or newest items to be shown first.
![Issue activity sort order dropdown button](img/epic_activity_sort_order_v13_2.png)
@ -219,8 +225,8 @@ to learn how to create a confidential merge request.
To make an epic confidential:
- **When creating an epic:** select the checkbox **Make this epic confidential**.
- **In an existing epic:** in the epic's sidebar, select **Edit** next to **Confidentiality** then
- **When creating an epic:** select the checkbox under **Confidentiality**.
- **In an existing epic:** on the right sidebar, select **Edit** next to **Confidentiality**, and then
select **Turn on**.
## Manage issues assigned to an epic
@ -252,7 +258,7 @@ current parent.
To add a new issue to an epic:
1. On the epic's page, under **Epics and Issues**, select the **Add** dropdown button.
1. On the epic's page, under **Epics and Issues**, select **Add**.
1. Select **Add an existing issue**.
1. Identify the issue to be added, using either of the following methods:
- Paste the link of the issue.
@ -271,7 +277,7 @@ while dividing work into smaller parts.
To create an issue from an epic:
1. On the epic's page, under **Epics and Issues**, select the **Add** dropdown button.
1. On the epic's page, under **Epics and Issues**, select **Add**.
1. Select **Add a new issue**.
1. Under **Title**, enter the title for the new issue.
1. From the **Project** dropdown, select the project in which the issue should be created.
@ -284,7 +290,7 @@ After you remove an issue from an epic, the issue is no longer associated with t
To remove an issue from an epic:
1. Select the **Remove** (**{close}**) button next to the issue you want to remove.
1. Next to the issue you want to remove, select **Remove** (**{close}**).
The **Remove issue** warning appears.
1. Select **Remove**.
@ -350,8 +356,6 @@ For more on epic templates, see [Epic Templates - Repeatable sets of issues](htt
## Multi-level child epics **(ULTIMATE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/8333) in GitLab 11.7.
You can add any epic that belongs to a group or subgroup of the parent epic's group.
New child epics appear at the top of the list of epics in the **Epics and Issues** tab.
@ -363,7 +367,7 @@ Epics can contain multiple nested child epics, up to a total of seven levels dee
To add a child epic to an epic:
1. Select the **Add** dropdown button.
1. Select **Add**.
1. Select **Add a new epic**.
1. Identify the epic to be added, using either of the following methods:
- Paste the link of the epic.
@ -403,5 +407,6 @@ To reorder child epics assigned to an epic:
To remove a child epic from a parent epic:
1. Select the <kbd>x</kbd> button in the parent epic's list of epics.
1. Select **Remove** in the **Remove epic** warning message.
1. Select **Remove** (**{close}**) in the parent epic's list of epics.
The **Remove epic** warning appears.
1. Select **Remove**.

View File

@ -25,6 +25,7 @@ The following aspects of a project are imported:
- Pull request "merged by" information (GitLab.com & 13.7+)
- Regular issue and pull request comments
- [Git Large File Storage (LFS) Objects](../../../topics/git/lfs/index.md)
- Pull request comments replies in discussions ([GitLab.com & 14.5+](https://gitlab.com/gitlab-org/gitlab/-/issues/336596))
References to pull requests and issues are preserved (GitLab.com & 8.7+), and
each imported repository maintains visibility level unless that [visibility

View File

@ -38,6 +38,8 @@ Now, when you visit the merge request page, you can accept it
If a fast-forward merge is not possible but a conflict free rebase is possible,
a rebase button is offered.
The rebase action is also available as a [quick action command: `/rebase`](../../../topics/git/git_rebase.md#rebase-from-the-gitlab-ui).
![Fast forward merge request](img/ff_merge_rebase.png)
If the target branch is ahead of the source branch and a conflict free rebase is

View File

@ -7,14 +7,13 @@ type: index, reference
# Suggest changes **(FREE)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/18008) in GitLab 11.6.
> - Custom commit messages for suggestions was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/25381) in GitLab 13.9 behind a [feature flag](../../../feature_flags.md), disabled by default.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/25381) custom commit messages for suggestions in GitLab 13.9 [with a flag](../../../../administration/feature_flags.md) named `suggestions_custom_commit`. Disabled by default.
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/297404) in GitLab 13.10.
As a reviewer, you're able to suggest code changes with a Markdown syntax in merge request
diff threads. Then, the merge request author (or other users with appropriate
[permission](../../../permissions.md)) is able to apply these suggestions with a click,
which generates a commit in the merge request authored by the user that suggested the changes.
[permission](../../../permissions.md)) can apply these suggestions with a click.
This action generates a commit in the merge request, authored by the user that suggested the changes.
1. Choose a line of code to be changed, add a new comment, then select
the **Insert suggestion** icon in the toolbar:
@ -38,15 +37,16 @@ which generates a commit in the merge request authored by the user that suggeste
![Custom commit](img/custom_commit_v13_9.png)
After the author applies a suggestion, it's marked with the **Applied** label,
the thread is automatically resolved, and GitLab creates a new commit
and pushes the suggested change directly into the codebase in the merge request's
branch. The [Developer role](../../../permissions.md) is required to do so.
After the author applies a suggestion:
1. The suggestion is marked as **Applied**.
1. The thread is resolved.
1. GitLab creates a new commit with the changes.
1. If the user has the [Developer role](../../../permissions.md), GitLab pushes
the suggested change directly into the codebase in the merge request's branch.
## Multi-line suggestions
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/53310) in GitLab 11.10.
Reviewers can also suggest changes to multiple lines with a single suggestion
within merge request diff threads by adjusting the range offsets. The
offsets are relative to the position of the diff thread, and specify the
@ -67,9 +67,9 @@ suggestion.
## Code block nested in suggestions
If you need to make a suggestion that involves a
[fenced code block](../../../markdown.md#code-spans-and-blocks), wrap your suggestion in four backticks
instead of the usual three.
To add a suggestion that includes a
[fenced code block](../../../markdown.md#code-spans-and-blocks), wrap your suggestion
in four backticks instead of three:
![A comment editor with a suggestion with a fenced code block](img/suggestion_code_block_editor_v12_8.png)
@ -95,14 +95,14 @@ You can also use following variables besides static text:
| Variable | Description | Output example |
|------------------------|-------------|----------------|
| `%{branch_name}` | The name of the branch the suggestion(s) was(were) applied to. | `my-feature-branch` |
| `%{files_count}` | The number of file(s) to which suggestion(s) was(were) applied.| **2** |
| `%{file_paths}` | The path(s) of the file(s) suggestion(s) was(were) applied to. Paths are separated by commas.| `docs/index.md, docs/about.md` |
| `%{branch_name}` | The name of the branch to which suggestions were applied. | `my-feature-branch` |
| `%{files_count}` | The number of files to which suggestions were applied.| **2** |
| `%{file_paths}` | The paths of the file to which suggestions were applied. Paths are separated by commas.| `docs/index.md, docs/about.md` |
| `%{project_path}` | The project path. | `my-group/my-project` |
| `%{project_name}` | The human-readable name of the project. | **My Project** |
| `%{suggestions_count}` | The number of suggestions applied.| **3** |
| `%{username}` | The username of the user applying suggestion(s). | `user_1` |
| `%{user_full_name}` | The full name of the user applying suggestion(s). | **User 1** |
| `%{username}` | The username of the user applying suggestions. | `user_1` |
| `%{user_full_name}` | The full name of the user applying suggestions. | **User 1** |
For example, to customize the commit message to output
**Addresses user_1's review**, set the custom text to
@ -114,32 +114,32 @@ introduced by [#25381](https://gitlab.com/gitlab-org/gitlab/-/issues/25381).
## Batch suggestions
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/25486) in GitLab 13.1 as an [alpha feature](https://about.gitlab.com/handbook/product/gitlab-the-product/#alpha) behind a feature flag, disabled by default.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/25486) in GitLab 13.1 as an [alpha feature](https://about.gitlab.com/handbook/product/gitlab-the-product/#alpha) with a flag named `batch_suggestions`, disabled by default.
> - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/227799) in GitLab 13.2.
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/320755) in GitLab 13.11.
> - Custom commit messages for batch suggestions [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/326168) in GitLab 14.4.
> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/320755) in GitLab 13.11. [Feature flag `batch_suggestions`](https://gitlab.com/gitlab-org/gitlab/-/issues/320755) removed.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/326168) custom commit messages for batch suggestions in GitLab 14.4.
You can apply multiple suggestions at once to reduce the number of commits added
to your branch to address your reviewers' requests.
1. To start a batch of suggestions to apply with a single commit, select **Add suggestion to batch**:
![A code change suggestion displayed, with the button to add the suggestion to a batch highlighted.](img/add_first_suggestion_to_batch_v13_1.jpg "Add a suggestion to a batch")
![A code change suggestion displayed, with the add-suggestion option highlighted.](img/add_first_suggestion_to_batch_v13_1.jpg "Add a suggestion to a batch")
1. Add as many additional suggestions to the batch as you wish:
![A code change suggestion displayed, with the button to add an additional suggestion to a batch highlighted.](img/add_another_suggestion_to_batch_v13_1.jpg "Add another suggestion to a batch")
![A code change suggestion displayed, with the add-more suggestion option highlighted.](img/add_another_suggestion_to_batch_v13_1.jpg "Add another suggestion to a batch")
1. To remove suggestions, select **Remove from batch**:
![A code change suggestion displayed, with the button to remove that suggestion from its batch highlighted.](img/remove_suggestion_from_batch_v13_1.jpg "Remove a suggestion from a batch")
![A code change suggestion displayed, with the option to remove that suggestion from its batch highlighted.](img/remove_suggestion_from_batch_v13_1.jpg "Remove a suggestion from a batch")
1. Having added all the suggestions to your liking, when ready, select **Apply suggestions**. You
can optionally specify a custom commit message for [batch suggestions](#batch-suggestions)
(GitLab 14.4 and later) to describe your change. If you don't specify it, the default commit
message is used.
![A code change suggestion displayed, with the button to apply the batch of suggestions highlighted.](img/apply_batch_of_suggestions_v13_1.jpg "Apply a batch of suggestions")
![A code change suggestion displayed, with the option to apply the batch of suggestions highlighted.](img/apply_batch_of_suggestions_v13_1.jpg "Apply a batch of suggestions")
WARNING:
Suggestions applied from multiple authors creates a commit authored by the user applying the suggestions.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

View File

@ -28,12 +28,29 @@ functionality of a project.
Adjust your project's name, description, avatar, [default branch](../repository/branches/default.md), and topics:
![general project settings](img/general_settings_v13_11.png)
The project description also partially supports [standard Markdown](../../markdown.md#features-extended-from-standard-markdown).
You can use [emphasis](../../markdown.md#emphasis), [links](../../markdown.md#links), and
[line-breaks](../../markdown.md#line-breaks) to add more context to the project description.
#### Topics
Use topics to categorize projects and find similar new projects.
To assign topics to a project:
1. On the top bar, select **Menu > Projects** and find your project.
1. On the left sidebar, select **Settings** > **General**.
1. Under **Topics**, enter the project topics. Existing popular topics are suggested as you type.
1. Select **Save changes**.
For GitLab.com, explore popular topics on the [Explore topics page](https://gitlab.com/explore/projects/topics).
When you select a topic, you can see relevant projects.
NOTE:
The assigned topics are visible only to everyone with access to the project,
but everyone can see which topics exist at all on the GitLab instance.
Do not include sensitive information in the name of a topic.
#### Compliance frameworks **(PREMIUM)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/276221) in GitLab 13.9.

View File

@ -530,6 +530,14 @@ module API
desc: 'The Mattermost token'
}
],
'shimo' => [
{
required: true,
name: :external_wiki_url,
type: String,
desc: 'Shimo workspace URL'
}
],
'slack-slash-commands' => [
{
required: true,

View File

@ -141,6 +141,10 @@ module Gitlab
scanner <=> other.scanner
end
def has_signatures?
signatures.present?
end
private
def generate_project_fingerprint

View File

@ -69,6 +69,10 @@ module Gitlab
primary_scanner <=> other.primary_scanner
end
def has_signatures?
findings.any?(&:has_signatures?)
end
end
end
end

View File

@ -20,7 +20,11 @@ module Gitlab
class << self
def register_models(models)
registered_models.merge(models)
models.each do |model|
raise "#{model} should have partitioning strategy defined" unless model.respond_to?(:partitioning_strategy)
registered_models << model
end
end
def register_tables(tables)
@ -55,8 +59,6 @@ module Gitlab
Gitlab::AppLogger.info(message: 'Finished dropping detached postgres partitions')
end
private
def registered_models
@registered_models ||= Set.new
end
@ -65,6 +67,8 @@ module Gitlab
@registered_tables ||= Set.new
end
private
def registered_for_sync
registered_models + registered_tables.map do |table|
TableWithoutModel.new(**table)

View File

@ -16,6 +16,9 @@ module Gitlab
def execute
return if merge_request_id.blank?
note.project = project
note.merge_request = merge_request
build_author_attributes
# Diff notes with suggestions are imported with DiffNote, which is
@ -68,10 +71,10 @@ module Gitlab
# allows us to efficiently insert data (even if it's just 1 row)
# without having to use all sorts of hacks to disable callbacks.
Gitlab::Database.main.bulk_insert(LegacyDiffNote.table_name, [{
noteable_type: 'MergeRequest',
noteable_type: note.noteable_type,
system: false,
type: 'LegacyDiffNote',
discussion_id: Discussion.discussion_id(note),
discussion_id: note.discussion_id,
noteable_id: merge_request_id,
project_id: project.id,
author_id: author_id,
@ -89,16 +92,17 @@ module Gitlab
log_diff_note_creation('DiffNote')
::Import::Github::Notes::CreateService.new(project, author, {
noteable_type: 'MergeRequest',
noteable_type: note.noteable_type,
system: false,
type: 'DiffNote',
noteable_id: merge_request_id,
project_id: project.id,
note: note_body,
discussion_id: note.discussion_id,
commit_id: note.original_commit_id,
created_at: note.created_at,
updated_at: note.updated_at,
position: note.diff_position(merge_request)
position: note.diff_position
}).execute
end

View File

@ -7,14 +7,14 @@ module Gitlab
include ToHash
include ExposeAttribute
attr_reader :attributes
expose_attribute :noteable_type, :noteable_id, :commit_id, :file_path,
:diff_hunk, :author, :created_at, :updated_at,
:original_commit_id, :note_id, :end_line, :start_line,
:side
NOTEABLE_TYPE = 'MergeRequest'
NOTEABLE_ID_REGEX = %r{/pull/(?<iid>\d+)}i.freeze
DISCUSSION_CACHE_KEY = 'github-importer/discussion-id-map/%{project_id}/%{noteable_id}/%{original_note_id}'
expose_attribute :noteable_id, :commit_id, :file_path,
:diff_hunk, :author, :created_at, :updated_at,
:original_commit_id, :note_id, :end_line, :start_line,
:side, :in_reply_to_id
# Builds a diff note from a GitHub API response.
#
@ -31,7 +31,6 @@ module Gitlab
user = Representation::User.from_api_response(note.user) if note.user
hash = {
noteable_type: 'MergeRequest',
noteable_id: matches[:iid].to_i,
file_path: note.path,
commit_id: note.commit_id,
@ -44,7 +43,8 @@ module Gitlab
note_id: note.id,
end_line: note.line,
start_line: note.start_line,
side: note.side
side: note.side,
in_reply_to_id: note.in_reply_to_id
}
new(hash)
@ -58,6 +58,8 @@ module Gitlab
new(hash)
end
attr_accessor :merge_request, :project
# attributes - A Hash containing the raw note details. The keys of this
# Hash must be Symbols.
def initialize(attributes)
@ -70,6 +72,10 @@ module Gitlab
)
end
def noteable_type
NOTEABLE_TYPE
end
def contains_suggestion?
@note_formatter.contains_suggestion?
end
@ -102,7 +108,7 @@ module Gitlab
end
# Used when importing with DiffNote
def diff_position(merge_request)
def diff_position
position_params = {
diff_refs: merge_request.diff_refs,
old_path: file_path,
@ -120,8 +126,25 @@ module Gitlab
}
end
def discussion_id
if in_reply_to_id.present?
current_discussion_id
else
Discussion.discussion_id(
Struct
.new(:noteable_id, :noteable_type)
.new(merge_request.id, NOTEABLE_TYPE)
).tap do |discussion_id|
cache_discussion_id(discussion_id)
end
end
end
private
# Required by ExposeAttribute
attr_reader :attributes
def diff_line_params
if addition?
{ new_line: end_line, old_line: nil }
@ -133,6 +156,22 @@ module Gitlab
def addition?
side == 'RIGHT'
end
def cache_discussion_id(discussion_id)
Gitlab::Cache::Import::Caching.write(discussion_id_cache_key(note_id), discussion_id)
end
def current_discussion_id
Gitlab::Cache::Import::Caching.read(discussion_id_cache_key(in_reply_to_id))
end
def discussion_id_cache_key(id)
DISCUSSION_CACHE_KEY % {
project_id: project.id,
noteable_id: merge_request.id,
original_note_id: id
}
end
end
end
end

View File

@ -7,6 +7,11 @@ module Gitlab
def to_caller_id
"graphql:#{name}"
end
def query_urgency
# We'll be able to actually correlate query_urgency with https://gitlab.com/gitlab-org/gitlab/-/issues/345141
::Gitlab::EndpointAttributes::DEFAULT_URGENCY
end
end
ANONYMOUS = Operation.new("anonymous").freeze

View File

@ -0,0 +1,48 @@
# frozen_string_literal: true
module Gitlab
module Graphql
module Tracers
class MetricsTracer
def self.use(schema)
schema.tracer(self.new)
end
# See https://graphql-ruby.org/api-doc/1.12.16/GraphQL/Tracing for full list of events
def trace(key, data)
result = yield
case key
when "execute_query"
increment_query_sli(data)
end
result
end
private
def increment_query_sli(data)
duration_s = data.fetch(:duration_s, nil)
query = data.fetch(:query, nil)
# We're just being defensive here...
# duration_s comes from TimerTracer and we should be pretty much guaranteed it exists
return unless duration_s && query
operation = ::Gitlab::Graphql::KnownOperations.default.from_query(query)
query_urgency = operation.query_urgency
Gitlab::Metrics::RailsSlis.graphql_query_apdex.increment(
labels: {
endpoint_id: ::Gitlab::ApplicationContext.current_context_attribute(:caller_id),
feature_category: ::Gitlab::ApplicationContext.current_context_attribute(:feature_category),
query_urgency: query_urgency.name
},
success: duration_s <= query_urgency.duration
)
end
end
end
end
end

View File

@ -7,7 +7,7 @@ module Gitlab
Asana Assembla Bamboo Bugzilla Buildkite Campfire Confluence CustomIssueTracker Datadog
Discord DroneCi EmailsOnPush Ewm ExternalWiki Flowdock HangoutsChat Irker Jenkins Jira Mattermost
MattermostSlashCommands MicrosoftTeams MockCi MockMonitoring Packagist PipelinesEmail Pivotaltracker
Prometheus Pushover Redmine Slack SlackSlashCommands Teamcity UnifyCircuit WebexTeams Youtrack Zentao
Prometheus Pushover Redmine Shimo Slack SlackSlashCommands Teamcity UnifyCircuit WebexTeams Youtrack Zentao
)).freeze
def self.namespaced_integrations

View File

@ -5,17 +5,31 @@ module Gitlab
module RailsSlis
class << self
def initialize_request_slis_if_needed!
return if Gitlab::Metrics::Sli.initialized?(:rails_request_apdex)
Gitlab::Metrics::Sli.initialize_sli(:rails_request_apdex, possible_request_labels)
Gitlab::Metrics::Sli.initialize_sli(:rails_request_apdex, possible_request_labels) unless Gitlab::Metrics::Sli.initialized?(:rails_request_apdex)
Gitlab::Metrics::Sli.initialize_sli(:graphql_query_apdex, possible_graphql_query_labels) unless Gitlab::Metrics::Sli.initialized?(:graphql_query_apdex)
end
def request_apdex
Gitlab::Metrics::Sli[:rails_request_apdex]
end
def graphql_query_apdex
Gitlab::Metrics::Sli[:graphql_query_apdex]
end
private
def possible_graphql_query_labels
::Gitlab::Graphql::KnownOperations.default.operations.map do |op|
{
endpoint_id: op.to_caller_id,
# We'll be able to correlate feature_category with https://gitlab.com/gitlab-org/gitlab/-/issues/328535
feature_category: nil,
query_urgency: op.query_urgency.name
}
end
end
def possible_request_labels
possible_controller_labels + possible_api_labels
end

View File

@ -1139,6 +1139,9 @@ msgstr ""
msgid "* All times are in UTC unless specified"
msgstr ""
msgid "*Required"
msgstr ""
msgid "+ %{amount} more"
msgstr ""
@ -27216,6 +27219,9 @@ msgstr ""
msgid "ProjectSettings|Requirements management system."
msgstr ""
msgid "ProjectSettings|Search for topic"
msgstr ""
msgid "ProjectSettings|Security & Compliance"
msgstr ""
@ -29190,7 +29196,7 @@ msgstr ""
msgid "Repository update events"
msgstr ""
msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets} / Packages: %{counter_packages} / Uploads: %{counter_uploads}"
msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / Pipeline Artifacts: %{counter_pipeline_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets} / Packages: %{counter_packages} / Uploads: %{counter_uploads}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@ -31299,9 +31305,6 @@ msgstr ""
msgid "Sep"
msgstr ""
msgid "Separate topics with commas."
msgstr ""
msgid "September"
msgstr ""
@ -31751,6 +31754,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
msgid "Shimo|Link to a Shimo Workspace from the sidebar."
msgstr ""
msgid "Shimo|Shimo"
msgstr ""
msgid "Shimo|Shimo Workspace URL"
msgstr ""
msgid "Should you ever lose your phone or access to your one time password secret, each of these recovery codes can be used one time each to regain access to your account. Please save them in a safe place, or you %{boldStart}will%{boldEnd} lose access to your account."
msgstr ""
@ -36217,9 +36229,6 @@ msgstr ""
msgid "Topics"
msgstr ""
msgid "Topics (optional)"
msgstr ""
msgid "Total"
msgstr ""

View File

@ -57,7 +57,7 @@
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/svgs": "1.220.0",
"@gitlab/tributejs": "1.0.0",
"@gitlab/ui": "32.33.0",
"@gitlab/ui": "32.36.0",
"@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "6.1.4-1",
"@rails/ujs": "6.1.4-1",

View File

@ -111,6 +111,12 @@ FactoryBot.define do
end
end
factory :shimo_integration, class: 'Integrations::Shimo' do
project
active { true }
external_wiki_url { 'https://shimo.example.com/desktop' }
end
factory :confluence_integration, class: 'Integrations::Confluence' do
project
active { true }

View File

@ -16,7 +16,7 @@ RSpec.describe "Admin > Admin sees project statistics" do
let(:project) { create(:project, :repository) }
it "shows project statistics" do
expect(page).to have_content("Storage: 0 Bytes (Repository: 0 Bytes / Wikis: 0 Bytes / Build Artifacts: 0 Bytes / LFS: 0 Bytes / Snippets: 0 Bytes / Packages: 0 Bytes / Uploads: 0 Bytes)")
expect(page).to have_content("Storage: 0 Bytes (Repository: 0 Bytes / Wikis: 0 Bytes / Build Artifacts: 0 Bytes / Pipeline Artifacts: 0 Bytes / LFS: 0 Bytes / Snippets: 0 Bytes / Packages: 0 Bytes / Uploads: 0 Bytes)")
end
end

View File

@ -2,22 +2,40 @@
require 'spec_helper'
RSpec.describe 'Projects > Settings > User tags a project' do
RSpec.describe 'Projects > Settings > User tags a project', :js do
let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) }
let!(:topic) { create(:topic, name: 'topic1') }
before do
sign_in(user)
visit edit_project_path(project)
wait_for_all_requests
end
it 'sets project topics' do
fill_in 'Topics', with: 'topic1, topic2'
it 'select existing topic' do
fill_in class: 'gl-token-selector-input', with: 'topic1'
wait_for_all_requests
find('.gl-avatar-labeled[entity-name="topic1"]').click
page.within '.general-settings' do
click_button 'Save changes'
end
expect(find_field('Topics').value).to eq 'topic1, topic2'
expect(find('#project_topic_list_field', visible: :hidden).value).to eq 'topic1'
end
it 'select new topic' do
fill_in class: 'gl-token-selector-input', with: 'topic2'
wait_for_all_requests
click_button 'Add "topic2"'
page.within '.general-settings' do
click_button 'Save changes'
end
expect(find('#project_topic_list_field', visible: :hidden).value).to eq 'topic2'
end
end

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,98 @@
import { GlTokenSelector, GlToken } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { nextTick } from 'vue';
import TopicsTokenSelector from '~/projects/settings/topics/components/topics_token_selector.vue';
const mockTopics = [
{ id: 1, name: 'topic1', avatarUrl: 'avatar.com/topic1.png' },
{ id: 2, name: 'GitLab', avatarUrl: 'avatar.com/GitLab.png' },
];
describe('TopicsTokenSelector', () => {
let wrapper;
let div;
let input;
const createComponent = (selected) => {
wrapper = mount(TopicsTokenSelector, {
attachTo: div,
propsData: {
selected,
},
data() {
return {
topics: mockTopics,
};
},
mocks: {
$apollo: {
queries: {
topics: { loading: false },
},
},
},
});
};
const findTokenSelector = () => wrapper.findComponent(GlTokenSelector);
const findTokenSelectorInput = () => findTokenSelector().find('input[type="text"]');
const setTokenSelectorInputValue = (value) => {
const tokenSelectorInput = findTokenSelectorInput();
tokenSelectorInput.element.value = value;
tokenSelectorInput.trigger('input');
return nextTick();
};
const tokenSelectorTriggerEnter = (event) => {
const tokenSelectorInput = findTokenSelectorInput();
tokenSelectorInput.trigger('keydown.enter', event);
};
beforeEach(() => {
div = document.createElement('div');
input = document.createElement('input');
input.setAttribute('type', 'text');
input.id = 'project_topic_list_field';
document.body.appendChild(div);
document.body.appendChild(input);
});
afterEach(() => {
wrapper.destroy();
div.remove();
input.remove();
});
describe('when component is mounted', () => {
it('parses selected into tokens', async () => {
const selected = [
{ id: 11, name: 'topic1' },
{ id: 12, name: 'topic2' },
{ id: 13, name: 'topic3' },
];
createComponent(selected);
await nextTick();
wrapper.findAllComponents(GlToken).wrappers.forEach((tokenWrapper, index) => {
expect(tokenWrapper.text()).toBe(selected[index].name);
});
});
});
describe('when enter key is pressed', () => {
it('does not submit the form if token selector text input has a value', async () => {
createComponent();
await setTokenSelectorInputValue('topic');
const event = { preventDefault: jest.fn() };
tokenSelectorTriggerEnter(event);
expect(event.preventDefault).toHaveBeenCalled();
});
});
});

View File

@ -1,6 +1,7 @@
import { GlDropdownItem } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import { __ } from '~/locale';
import TagFieldNew from '~/releases/components/tag_field_new.vue';
import createStore from '~/releases/stores';
import createEditNewModule from '~/releases/stores/modules/edit_new';
@ -84,7 +85,8 @@ describe('releases/components/tag_field_new', () => {
beforeEach(() => createComponent());
it('renders a label', () => {
expect(findTagNameFormGroup().attributes().label).toBe('Tag name');
expect(findTagNameFormGroup().attributes().label).toBe(__('Tag name'));
expect(findTagNameFormGroup().props().labelDescription).toBe(__('*Required'));
});
describe('when the user selects a new tag name', () => {

View File

@ -1,36 +1,68 @@
import Vue from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper';
import collapsedCalendarIcon from '~/vue_shared/components/sidebar/collapsed_calendar_icon.vue';
import { shallowMount } from '@vue/test-utils';
import { GlIcon } from '@gitlab/ui';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
describe('collapsedCalendarIcon', () => {
let vm;
beforeEach(() => {
const CollapsedCalendarIcon = Vue.extend(collapsedCalendarIcon);
vm = mountComponent(CollapsedCalendarIcon, {
containerClass: 'test-class',
text: 'text',
showIcon: false,
import CollapsedCalendarIcon from '~/vue_shared/components/sidebar/collapsed_calendar_icon.vue';
describe('CollapsedCalendarIcon', () => {
let wrapper;
const defaultProps = {
containerClass: 'test-class',
text: 'text',
tooltipText: 'tooltip text',
showIcon: false,
};
const createComponent = ({ props = {} } = {}) => {
wrapper = shallowMount(CollapsedCalendarIcon, {
propsData: { ...defaultProps, ...props },
directives: {
GlTooltip: createMockDirective(),
},
});
};
beforeEach(() => {
createComponent();
});
it('should add class to container', () => {
expect(vm.$el.classList.contains('test-class')).toEqual(true);
afterEach(() => {
wrapper.destroy();
});
it('should hide calendar icon if showIcon', () => {
expect(vm.$el.querySelector('[data-testid="calendar-icon"]')).toBeNull();
const findGlIcon = () => wrapper.findComponent(GlIcon);
const getTooltip = () => getBinding(wrapper.element, 'gl-tooltip');
it('adds class to container', () => {
expect(wrapper.classes()).toContain(defaultProps.containerClass);
});
it('should render text', () => {
expect(vm.$el.querySelector('span').innerText.trim()).toEqual('text');
it('does not render calendar icon when showIcon is false', () => {
expect(findGlIcon().exists()).toBe(false);
});
it('should emit click event when container is clicked', () => {
const click = jest.fn();
vm.$on('click', click);
it('renders calendar icon when showIcon is true', () => {
createComponent({
props: { showIcon: true },
});
vm.$el.click();
expect(findGlIcon().exists()).toBe(true);
});
expect(click).toHaveBeenCalled();
it('renders text', () => {
expect(wrapper.text()).toBe(defaultProps.text);
});
it('renders tooltipText as tooltip', () => {
expect(getTooltip().value).toBe(defaultProps.tooltipText);
});
it('emits click event when container is clicked', async () => {
wrapper.trigger('click');
await wrapper.vm.$nextTick();
expect(wrapper.emitted('click')[0]).toBeDefined();
});
});

View File

@ -1,86 +1,103 @@
import Vue from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper';
import collapsedGroupedDatePicker from '~/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue';
import { shallowMount } from '@vue/test-utils';
describe('collapsedGroupedDatePicker', () => {
let vm;
beforeEach(() => {
const CollapsedGroupedDatePicker = Vue.extend(collapsedGroupedDatePicker);
vm = mountComponent(CollapsedGroupedDatePicker, {
showToggleSidebar: true,
import CollapsedGroupedDatePicker from '~/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue';
import CollapsedCalendarIcon from '~/vue_shared/components/sidebar/collapsed_calendar_icon.vue';
describe('CollapsedGroupedDatePicker', () => {
let wrapper;
const defaultProps = {
showToggleSidebar: true,
};
const minDate = new Date('07/17/2016');
const maxDate = new Date('07/17/2017');
const createComponent = ({ props = {} } = {}) => {
wrapper = shallowMount(CollapsedGroupedDatePicker, {
propsData: { ...defaultProps, ...props },
});
};
afterEach(() => {
wrapper.destroy();
});
const findCollapsedCalendarIcon = () => wrapper.findComponent(CollapsedCalendarIcon);
const findAllCollapsedCalendarIcons = () => wrapper.findAllComponents(CollapsedCalendarIcon);
describe('toggleCollapse events', () => {
beforeEach((done) => {
jest.spyOn(vm, 'toggleSidebar').mockImplementation(() => {});
vm.minDate = new Date('07/17/2016');
Vue.nextTick(done);
});
it('should emit when collapsed-calendar-icon is clicked', () => {
vm.$el.querySelector('.sidebar-collapsed-icon').click();
createComponent();
expect(vm.toggleSidebar).toHaveBeenCalled();
findCollapsedCalendarIcon().trigger('click');
expect(wrapper.emitted('toggleCollapse')[0]).toBeDefined();
});
});
describe('minDate and maxDate', () => {
beforeEach((done) => {
vm.minDate = new Date('07/17/2016');
vm.maxDate = new Date('07/17/2017');
Vue.nextTick(done);
});
it('should render both collapsed-calendar-icon', () => {
const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon');
createComponent({
props: {
minDate,
maxDate,
},
});
expect(icons.length).toEqual(2);
expect(icons[0].innerText.trim()).toEqual('Jul 17 2016');
expect(icons[1].innerText.trim()).toEqual('Jul 17 2017');
const icons = findAllCollapsedCalendarIcons();
expect(icons.length).toBe(2);
expect(icons.at(0).text()).toBe('Jul 17 2016');
expect(icons.at(1).text()).toBe('Jul 17 2017');
});
});
describe('minDate', () => {
beforeEach((done) => {
vm.minDate = new Date('07/17/2016');
Vue.nextTick(done);
});
it('should render minDate in collapsed-calendar-icon', () => {
const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon');
createComponent({
props: {
minDate,
},
});
expect(icons.length).toEqual(1);
expect(icons[0].innerText.trim()).toEqual('From Jul 17 2016');
const icons = findAllCollapsedCalendarIcons();
expect(icons.length).toBe(1);
expect(icons.at(0).text()).toBe('From Jul 17 2016');
});
});
describe('maxDate', () => {
beforeEach((done) => {
vm.maxDate = new Date('07/17/2017');
Vue.nextTick(done);
});
it('should render maxDate in collapsed-calendar-icon', () => {
const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon');
createComponent({
props: {
maxDate,
},
});
const icons = findAllCollapsedCalendarIcons();
expect(icons.length).toEqual(1);
expect(icons[0].innerText.trim()).toEqual('Until Jul 17 2017');
expect(icons.length).toBe(1);
expect(icons.at(0).text()).toBe('Until Jul 17 2017');
});
});
describe('no dates', () => {
it('should render None', () => {
const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon');
beforeEach(() => {
createComponent();
});
expect(icons.length).toEqual(1);
expect(icons[0].innerText.trim()).toEqual('None');
it('should render None', () => {
const icons = findAllCollapsedCalendarIcons();
expect(icons.length).toBe(1);
expect(icons.at(0).text()).toBe('None');
});
it('should have tooltip as `Start and due date`', () => {
const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon');
const icons = findAllCollapsedCalendarIcons();
expect(icons[0].title).toBe('Start and due date');
expect(icons.at(0).props('tooltipText')).toBe('Start and due date');
});
});
});

View File

@ -1,3 +1,4 @@
import { GlLoadingIcon } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import DatePicker from '~/vue_shared/components/pikaday.vue';
import SidebarDatePicker from '~/vue_shared/components/sidebar/date_picker.vue';
@ -5,14 +6,8 @@ import SidebarDatePicker from '~/vue_shared/components/sidebar/date_picker.vue';
describe('SidebarDatePicker', () => {
let wrapper;
const mountComponent = (propsData = {}, data = {}) => {
if (wrapper) {
throw new Error('tried to call mountComponent without d');
}
const createComponent = (propsData = {}, data = {}) => {
wrapper = mount(SidebarDatePicker, {
stubs: {
DatePicker: true,
},
propsData,
data: () => data,
});
@ -20,87 +15,93 @@ describe('SidebarDatePicker', () => {
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
it('should emit toggleCollapse when collapsed toggle sidebar is clicked', () => {
mountComponent();
const findDatePicker = () => wrapper.findComponent(DatePicker);
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findEditButton = () => wrapper.find('.title .btn-blank');
const findRemoveButton = () => wrapper.find('.value-content .btn-blank');
const findSidebarToggle = () => wrapper.find('.title .gutter-toggle');
const findValueContent = () => wrapper.find('.value-content');
wrapper.find('.issuable-sidebar-header .gutter-toggle').element.click();
it('should emit toggleCollapse when collapsed toggle sidebar is clicked', () => {
createComponent();
wrapper.find('.issuable-sidebar-header .gutter-toggle').trigger('click');
expect(wrapper.emitted('toggleCollapse')).toEqual([[]]);
});
it('should render collapsed-calendar-icon', () => {
mountComponent();
createComponent();
expect(wrapper.find('.sidebar-collapsed-icon').element).toBeDefined();
expect(wrapper.find('.sidebar-collapsed-icon').exists()).toBe(true);
});
it('should render value when not editing', () => {
mountComponent();
createComponent();
expect(wrapper.find('.value-content').element).toBeDefined();
expect(findValueContent().exists()).toBe(true);
});
it('should render None if there is no selectedDate', () => {
mountComponent();
createComponent();
expect(wrapper.find('.value-content span').text().trim()).toEqual('None');
expect(findValueContent().text()).toBe('None');
});
it('should render date-picker when editing', () => {
mountComponent({}, { editing: true });
createComponent({}, { editing: true });
expect(wrapper.find(DatePicker).element).toBeDefined();
expect(findDatePicker().exists()).toBe(true);
});
it('should render label', () => {
const label = 'label';
mountComponent({ label });
expect(wrapper.find('.title').text().trim()).toEqual(label);
createComponent({ label });
expect(wrapper.find('.title').text()).toBe(label);
});
it('should render loading-icon when isLoading', () => {
mountComponent({ isLoading: true });
expect(wrapper.find('.gl-spinner').element).toBeDefined();
createComponent({ isLoading: true });
expect(findLoadingIcon().exists()).toBe(true);
});
describe('editable', () => {
beforeEach(() => {
mountComponent({ editable: true });
createComponent({ editable: true });
});
it('should render edit button', () => {
expect(wrapper.find('.title .btn-blank').text().trim()).toEqual('Edit');
expect(findEditButton().text()).toBe('Edit');
});
it('should enable editing when edit button is clicked', async () => {
wrapper.find('.title .btn-blank').element.click();
findEditButton().trigger('click');
await wrapper.vm.$nextTick();
expect(wrapper.vm.editing).toEqual(true);
expect(wrapper.vm.editing).toBe(true);
});
});
it('should render date if selectedDate', () => {
mountComponent({ selectedDate: new Date('07/07/2017') });
createComponent({ selectedDate: new Date('07/07/2017') });
expect(wrapper.find('.value-content strong').text().trim()).toEqual('Jul 7, 2017');
expect(wrapper.find('.value-content strong').text()).toBe('Jul 7, 2017');
});
describe('selectedDate and editable', () => {
beforeEach(() => {
mountComponent({ selectedDate: new Date('07/07/2017'), editable: true });
createComponent({ selectedDate: new Date('07/07/2017'), editable: true });
});
it('should render remove button if selectedDate and editable', () => {
expect(wrapper.find('.value-content .btn-blank').text().trim()).toEqual('remove');
expect(findRemoveButton().text()).toBe('remove');
});
it('should emit saveDate with null when remove button is clicked', () => {
wrapper.find('.value-content .btn-blank').element.click();
findRemoveButton().trigger('click');
expect(wrapper.emitted('saveDate')).toEqual([[null]]);
});
@ -108,15 +109,15 @@ describe('SidebarDatePicker', () => {
describe('showToggleSidebar', () => {
beforeEach(() => {
mountComponent({ showToggleSidebar: true });
createComponent({ showToggleSidebar: true });
});
it('should render toggle-sidebar when showToggleSidebar', () => {
expect(wrapper.find('.title .gutter-toggle').element).toBeDefined();
expect(findSidebarToggle().exists()).toBe(true);
});
it('should emit toggleCollapse when toggle sidebar is clicked', () => {
wrapper.find('.title .gutter-toggle').element.click();
findSidebarToggle().trigger('click');
expect(wrapper.emitted('toggleCollapse')).toEqual([[]]);
});

View File

@ -1,95 +1,74 @@
import Vue from 'vue';
import { shallowMount } from '@vue/test-utils';
import { GlIcon } from '@gitlab/ui';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import DropdownValueCollapsedComponent from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_value_collapsed.vue';
import mountComponent from 'helpers/vue_mount_component_helper';
import dropdownValueCollapsedComponent from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_value_collapsed.vue';
import { mockCollapsedLabels as mockLabels } from './mock_data';
const createComponent = (labels = mockLabels) => {
const Component = Vue.extend(dropdownValueCollapsedComponent);
return mountComponent(Component, {
labels,
});
};
import { mockCollapsedLabels as mockLabels, mockRegularLabel } from './mock_data';
describe('DropdownValueCollapsedComponent', () => {
let vm;
let wrapper;
beforeEach(() => {
vm = createComponent();
});
const defaultProps = {
labels: [],
};
const mockManyLabels = [...mockLabels, ...mockLabels, ...mockLabels];
const createComponent = ({ props = {} } = {}) => {
wrapper = shallowMount(DropdownValueCollapsedComponent, {
propsData: { ...defaultProps, ...props },
directives: {
GlTooltip: createMockDirective(),
},
});
};
afterEach(() => {
vm.$destroy();
wrapper.destroy();
});
describe('computed', () => {
describe('labelsList', () => {
it('returns default text when `labels` prop is empty array', () => {
const vmEmptyLabels = createComponent([]);
expect(vmEmptyLabels.labelsList).toBe('Labels');
vmEmptyLabels.$destroy();
});
it('returns labels names separated by coma when `labels` prop has more than one item', () => {
const labels = mockLabels.concat(mockLabels);
const vmMoreLabels = createComponent(labels);
const expectedText = labels.map((label) => label.title).join(', ');
expect(vmMoreLabels.labelsList).toBe(expectedText);
vmMoreLabels.$destroy();
});
it('returns labels names separated by coma with remaining labels count and `and more` phrase when `labels` prop has more than five items', () => {
const mockMoreLabels = Object.assign([], mockLabels);
for (let i = 0; i < 6; i += 1) {
mockMoreLabels.unshift(mockLabels[0]);
}
const vmMoreLabels = createComponent(mockMoreLabels);
const expectedText = `${mockMoreLabels
.slice(0, 5)
.map((label) => label.title)
.join(', ')}, and ${mockMoreLabels.length - 5} more`;
expect(vmMoreLabels.labelsList).toBe(expectedText);
vmMoreLabels.$destroy();
});
it('returns first label name when `labels` prop has only one item present', () => {
const text = mockLabels.map((label) => label.title).join(', ');
expect(vm.labelsList).toBe(text);
});
});
});
describe('methods', () => {
describe('handleClick', () => {
it('emits onValueClick event on component', () => {
jest.spyOn(vm, '$emit').mockImplementation(() => {});
vm.handleClick();
expect(vm.$emit).toHaveBeenCalledWith('onValueClick');
});
});
});
const findGlIcon = () => wrapper.findComponent(GlIcon);
const getTooltip = () => getBinding(wrapper.element, 'gl-tooltip');
describe('template', () => {
it('renders component container element with tooltip`', () => {
expect(vm.$el.title).toBe(vm.labelsList);
});
it('renders tags icon element', () => {
expect(vm.$el.querySelector('[data-testid="labels-icon"]')).not.toBeNull();
createComponent();
expect(findGlIcon().exists()).toBe(true);
});
it('renders labels count', () => {
expect(vm.$el.querySelector('span').innerText.trim()).toBe(`${vm.labels.length}`);
it('emits onValueClick event on click', async () => {
createComponent();
wrapper.trigger('click');
await wrapper.vm.$nextTick();
expect(wrapper.emitted('onValueClick')[0]).toBeDefined();
});
describe.each`
scenario | labels | expectedResult | expectedText
${'`labels` is empty'} | ${[]} | ${'default text'} | ${'Labels'}
${'`labels` has 1 item'} | ${[mockRegularLabel]} | ${'label name'} | ${'Foo Label'}
${'`labels` has 2 items'} | ${mockLabels} | ${'comma separated label names'} | ${'Foo Label, Foo::Bar'}
${'`labels` has more than 5 items'} | ${mockManyLabels} | ${'comma separated label names with "and more" phrase'} | ${'Foo Label, Foo::Bar, Foo Label, Foo::Bar, Foo Label, and 1 more'}
`('when $scenario', ({ labels, expectedResult, expectedText }) => {
beforeEach(() => {
createComponent({
props: {
labels,
},
});
});
it('renders labels count', () => {
expect(wrapper.text()).toBe(`${labels.length}`);
});
it(`renders "${expectedResult}" as tooltip`, () => {
expect(getTooltip().value).toBe(expectedText);
});
});
});
});

View File

@ -1,31 +1,45 @@
import Vue from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper';
import toggleSidebar from '~/vue_shared/components/sidebar/toggle_sidebar.vue';
import { GlButton } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
describe('toggleSidebar', () => {
let vm;
beforeEach(() => {
const ToggleSidebar = Vue.extend(toggleSidebar);
vm = mountComponent(ToggleSidebar, {
collapsed: true,
import ToggleSidebar from '~/vue_shared/components/sidebar/toggle_sidebar.vue';
describe('ToggleSidebar', () => {
let wrapper;
const defaultProps = {
collapsed: true,
};
const createComponent = ({ mountFn = shallowMount, props = {} } = {}) => {
wrapper = mountFn(ToggleSidebar, {
propsData: { ...defaultProps, ...props },
});
};
afterEach(() => {
wrapper.destroy();
});
const findGlButton = () => wrapper.findComponent(GlButton);
it('should render the "chevron-double-lg-left" icon when collapsed', () => {
expect(vm.$el.querySelector('[data-testid="chevron-double-lg-left-icon"]')).not.toBeNull();
createComponent();
expect(findGlButton().props('icon')).toBe('chevron-double-lg-left');
});
it('should render the "chevron-double-lg-right" icon when expanded', async () => {
vm.collapsed = false;
await Vue.nextTick();
expect(vm.$el.querySelector('[data-testid="chevron-double-lg-right-icon"]')).not.toBeNull();
createComponent({ props: { collapsed: false } });
expect(findGlButton().props('icon')).toBe('chevron-double-lg-right');
});
it('should emit toggle event when button clicked', () => {
const toggle = jest.fn();
vm.$on('toggle', toggle);
vm.$el.click();
it('should emit toggle event when button clicked', async () => {
createComponent({ mountFn: mount });
expect(toggle).toHaveBeenCalled();
findGlButton().trigger('click');
await wrapper.vm.$nextTick();
expect(wrapper.emitted('toggle')[0]).toBeDefined();
});
});

View File

@ -0,0 +1,33 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Resolvers::TopicsResolver do
include GraphqlHelpers
describe '#resolve' do
let!(:topic1) { create(:topic, name: 'GitLab', total_projects_count: 1) }
let!(:topic2) { create(:topic, name: 'git', total_projects_count: 2) }
let!(:topic3) { create(:topic, name: 'topic3', total_projects_count: 3) }
it 'finds all topics' do
expect(resolve_topics).to eq([topic3, topic2, topic1])
end
context 'with search' do
it 'searches environment by name' do
expect(resolve_topics(search: 'git')).to eq([topic2, topic1])
end
context 'when the search term does not match any topic' do
it 'is empty' do
expect(resolve_topics(search: 'nonsense')).to be_empty
end
end
end
end
def resolve_topics(args = {})
resolve(described_class, args: args)
end
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Types::Projects::TopicType do
specify { expect(described_class.graphql_name).to eq('Topic') }
specify do
expect(described_class).to have_graphql_fields(
:id,
:name,
:description,
:description_html,
:avatar_url
)
end
end

View File

@ -28,6 +28,7 @@ RSpec.describe GitlabSchema.types['Query'] do
runners
timelogs
board_list
topics
]
expect(described_class).to have_graphql_fields(*expected_fields).at_least

View File

@ -27,17 +27,18 @@ RSpec.describe StorageHelper do
create(:project,
namespace: namespace,
statistics: build(:project_statistics,
namespace: namespace,
repository_size: 10.kilobytes,
wiki_size: 10.bytes,
lfs_objects_size: 20.gigabytes,
build_artifacts_size: 30.megabytes,
snippets_size: 40.megabytes,
packages_size: 12.megabytes,
uploads_size: 15.megabytes))
namespace: namespace,
repository_size: 10.kilobytes,
wiki_size: 10.bytes,
lfs_objects_size: 20.gigabytes,
build_artifacts_size: 30.megabytes,
pipeline_artifacts_size: 11.megabytes,
snippets_size: 40.megabytes,
packages_size: 12.megabytes,
uploads_size: 15.megabytes))
end
let(:message) { 'Repository: 10 KB / Wikis: 10 Bytes / Build Artifacts: 30 MB / LFS: 20 GB / Snippets: 40 MB / Packages: 12 MB / Uploads: 15 MB' }
let(:message) { 'Repository: 10 KB / Wikis: 10 Bytes / Build Artifacts: 30 MB / Pipeline Artifacts: 11 MB / LFS: 20 GB / Snippets: 40 MB / Packages: 12 MB / Uploads: 15 MB' }
it 'works on ProjectStatistics' do
expect(helper.storage_counters_details(project.statistics)).to eq(message)

View File

@ -221,4 +221,26 @@ RSpec.describe Gitlab::Ci::Reports::Security::Report do
end
end
end
describe '#has_signatures?' do
let(:finding) { create(:ci_reports_security_finding, signatures: signatures) }
subject { report.has_signatures? }
before do
report.add_finding(finding)
end
context 'when the findings of the report does not have signatures' do
let(:signatures) { [] }
it { is_expected.to be_falsey }
end
context 'when the findings of the report have signatures' do
let(:signatures) { [instance_double(Gitlab::Ci::Reports::Security::FindingSignature)] }
it { is_expected.to be_truthy }
end
end
end

View File

@ -8,6 +8,28 @@ RSpec.describe Gitlab::Database::Partitioning do
let(:connection) { ApplicationRecord.connection }
around do |example|
previously_registered_models = described_class.registered_models.dup
described_class.instance_variable_set('@registered_models', Set.new)
previously_registered_tables = described_class.registered_tables.dup
described_class.instance_variable_set('@registered_tables', Set.new)
example.run
described_class.instance_variable_set('@registered_models', previously_registered_models)
described_class.instance_variable_set('@registered_tables', previously_registered_tables)
end
describe '.register_models' do
context 'ensure that the registered models have partitioning strategy' do
it 'fails when partitioning_strategy is not specified for the model' do
model = Class.new(ApplicationRecord)
expect { described_class.register_models([model]) }.to raise_error /should have partitioning strategy defined/
end
end
end
describe '.sync_partitions' do
let(:table_names) { %w[partitioning_test1 partitioning_test2] }
let(:models) do
@ -40,24 +62,18 @@ RSpec.describe Gitlab::Database::Partitioning do
end
context 'when no partitioned models are given' do
let(:partition_manager_class) { described_class::PartitionManager }
let(:partition_manager) { double('partition manager') }
let(:model) { double('model') }
it 'manages partitions for each registered model' do
registered_for_sync = described_class.__send__(:registered_for_sync)
described_class.register_models([models.first])
described_class.register_tables([
{
table_name: table_names.last,
partitioned_column: :created_at, strategy: :monthly
}
])
allow(described_class).to receive(:registered_for_sync)
.and_return(registered_for_sync)
expect(Gitlab::Database::EachDatabase).to receive(:each_model_connection)
.with(registered_for_sync)
.and_yield(model)
expect(partition_manager_class).to receive(:new).with(model).and_return(partition_manager)
expect(partition_manager).to receive(:sync_partitions)
described_class.sync_partitions
expect { described_class.sync_partitions }
.to change { find_partitions(table_names.first).size }.from(0)
.and change { find_partitions(table_names.last).size }.from(0)
end
end
end
@ -125,10 +141,4 @@ RSpec.describe Gitlab::Database::Partitioning do
table_oid(table_name).present?
end
end
context 'ensure that the registered models have partitioning strategy' do
it 'fails when partitioning_strategy is not specified for the model' do
expect(described_class.__send__(:registered_models)).to all(respond_to(:partitioning_strategy))
end
end
end

View File

@ -19,6 +19,7 @@ RSpec.describe Gitlab::GithubImport::Importer::DiffNotesImporter do
updated_at: Time.zone.now,
line: 23,
start_line: nil,
in_reply_to_id: nil,
id: 1,
side: 'RIGHT',
body: <<~BODY

Some files were not shown because too many files have changed in this diff Show More