Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-12-14 21:13:08 +00:00
parent 0c6a209989
commit 30f908f6b9
51 changed files with 491 additions and 171 deletions

View file

@ -1,7 +1,7 @@
<script>
import { GlButton, GlFormCheckbox, GlIcon, GlLink, GlAlert } from '@gitlab/ui';
import CiLintResults from '~/pipeline_editor/components/lint/ci_lint_results.vue';
import lintCiMutation from '~/pipeline_editor/graphql/mutations/lint_ci.mutation.graphql';
import lintCiMutation from '~/pipeline_editor/graphql/mutations/client/lint_ci.mutation.graphql';
import SourceEditor from '~/vue_shared/components/source_editor.vue';
export default {

View file

@ -3,7 +3,7 @@ import Api from '~/api';
import dismissUserCallout from '~/graphql_shared/mutations/dismiss_user_callout.mutation.graphql';
import axios from '~/lib/utils/axios_utils';
import { joinPaths, escapeFileUrl } from '~/lib/utils/url_utility';
import ciConfig from '~/pipeline_editor/graphql/queries/ci_config.graphql';
import ciConfig from '~/pipeline_editor/graphql/queries/ci_config.query.graphql';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { query, mutate } from './gql';

View file

@ -8,10 +8,10 @@ import {
COMMIT_SUCCESS,
} from '../../constants';
import commitCIFile from '../../graphql/mutations/commit_ci_file.mutation.graphql';
import updateCurrentBranchMutation from '../../graphql/mutations/update_current_branch.mutation.graphql';
import updateLastCommitBranchMutation from '../../graphql/mutations/update_last_commit_branch.mutation.graphql';
import updatePipelineEtag from '../../graphql/mutations/update_pipeline_etag.mutation.graphql';
import getCurrentBranch from '../../graphql/queries/client/current_branch.graphql';
import updateCurrentBranchMutation from '../../graphql/mutations/client/update_current_branch.mutation.graphql';
import updateLastCommitBranchMutation from '../../graphql/mutations/client/update_last_commit_branch.mutation.graphql';
import updatePipelineEtag from '../../graphql/mutations/client/update_pipeline_etag.mutation.graphql';
import getCurrentBranch from '../../graphql/queries/client/current_branch.query.graphql';
import CommitForm from './commit_form.vue';

View file

@ -18,9 +18,9 @@ import {
BRANCH_SEARCH_DEBOUNCE,
DEFAULT_FAILURE,
} from '~/pipeline_editor/constants';
import updateCurrentBranchMutation from '~/pipeline_editor/graphql/mutations/update_current_branch.mutation.graphql';
import getAvailableBranchesQuery from '~/pipeline_editor/graphql/queries/available_branches.graphql';
import getCurrentBranchQuery from '~/pipeline_editor/graphql/queries/client/current_branch.graphql';
import updateCurrentBranchMutation from '~/pipeline_editor/graphql/mutations/client/update_current_branch.mutation.graphql';
import getAvailableBranchesQuery from '~/pipeline_editor/graphql/queries/available_branches.query.graphql';
import getCurrentBranchQuery from '~/pipeline_editor/graphql/queries/client/current_branch.query.graphql';
import getLastCommitBranchQuery from '~/pipeline_editor/graphql/queries/client/last_commit_branch.query.graphql';
export default {

View file

@ -3,8 +3,8 @@ import { GlButton, GlIcon, GlLink, GlLoadingIcon, GlSprintf, GlTooltipDirective
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { truncateSha } from '~/lib/utils/text_utility';
import { s__ } from '~/locale';
import getPipelineQuery from '~/pipeline_editor/graphql/queries/client/pipeline.graphql';
import getPipelineEtag from '~/pipeline_editor/graphql/queries/client/pipeline_etag.graphql';
import getPipelineQuery from '~/pipeline_editor/graphql/queries/pipeline.query.graphql';
import getPipelineEtag from '~/pipeline_editor/graphql/queries/client/pipeline_etag.query.graphql';
import {
getQueryHeaders,
toggleQueryPollingByVisibility,

View file

@ -1,7 +1,7 @@
<script>
import { GlIcon, GlLink, GlLoadingIcon } from '@gitlab/ui';
import { __, s__, sprintf } from '~/locale';
import getAppStatus from '~/pipeline_editor/graphql/queries/client/app_status.graphql';
import getAppStatus from '~/pipeline_editor/graphql/queries/client/app_status.query.graphql';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
import {
EDITOR_APP_STATUS_EMPTY,

View file

@ -17,7 +17,7 @@ import {
TABS_INDEX,
VISUALIZE_TAB,
} from '../constants';
import getAppStatus from '../graphql/queries/client/app_status.graphql';
import getAppStatus from '../graphql/queries/client/app_status.query.graphql';
import CiConfigMergedPreview from './editor/ci_config_merged_preview.vue';
import CiEditorHeader from './editor/ci_editor_header.vue';
import TextEditor from './editor/text_editor.vue';

View file

@ -1,8 +1,8 @@
import axios from '~/lib/utils/axios_utils';
import getAppStatus from './queries/client/app_status.graphql';
import getCurrentBranchQuery from './queries/client/current_branch.graphql';
import getAppStatus from './queries/client/app_status.query.graphql';
import getCurrentBranchQuery from './queries/client/current_branch.query.graphql';
import getLastCommitBranchQuery from './queries/client/last_commit_branch.query.graphql';
import getPipelineEtag from './queries/client/pipeline_etag.graphql';
import getPipelineEtag from './queries/client/pipeline_etag.query.graphql';
export const resolvers = {
Mutation: {

View file

@ -5,10 +5,10 @@ import createDefaultClient from '~/lib/graphql';
import { resetServiceWorkersPublicPath } from '../lib/utils/webpack';
import { EDITOR_APP_STATUS_LOADING } from './constants';
import { CODE_SNIPPET_SOURCE_SETTINGS } from './components/code_snippet_alert/constants';
import getCurrentBranch from './graphql/queries/client/current_branch.graphql';
import getAppStatus from './graphql/queries/client/app_status.graphql';
import getCurrentBranch from './graphql/queries/client/current_branch.query.graphql';
import getAppStatus from './graphql/queries/client/app_status.query.graphql';
import getLastCommitBranchQuery from './graphql/queries/client/last_commit_branch.query.graphql';
import getPipelineEtag from './graphql/queries/client/pipeline_etag.graphql';
import getPipelineEtag from './graphql/queries/client/pipeline_etag.query.graphql';
import { resolvers } from './graphql/resolvers';
import typeDefs from './graphql/typedefs.graphql';
import PipelineEditorApp from './pipeline_editor_app.vue';

View file

@ -17,11 +17,11 @@ import {
LOAD_FAILURE_UNKNOWN,
STARTER_TEMPLATE_NAME,
} from './constants';
import updateAppStatus from './graphql/mutations/update_app_status.mutation.graphql';
import getBlobContent from './graphql/queries/blob_content.graphql';
import getCiConfigData from './graphql/queries/ci_config.graphql';
import getAppStatus from './graphql/queries/client/app_status.graphql';
import getCurrentBranch from './graphql/queries/client/current_branch.graphql';
import updateAppStatus from './graphql/mutations/client/update_app_status.mutation.graphql';
import getBlobContent from './graphql/queries/blob_content.query.graphql';
import getCiConfigData from './graphql/queries/ci_config.query.graphql';
import getAppStatus from './graphql/queries/client/app_status.query.graphql';
import getCurrentBranch from './graphql/queries/client/current_branch.query.graphql';
import getTemplate from './graphql/queries/get_starter_template.query.graphql';
import getLatestCommitShaQuery from './graphql/queries/latest_commit_sha.query.graphql';
import PipelineEditorHome from './pipeline_editor_home.vue';

View file

@ -41,6 +41,10 @@ class ApplicationExperiment < Gitlab::Experiment # rubocop:disable Gitlab/Namesp
# define a default nil control behavior so we can omit it when not needed
end
def track(action, **event_args)
super(action, **tracking_context.merge(event_args))
end
# TODO: remove
# This is deprecated logic as of v0.6.0 and should eventually be removed, but
# needs to stay intact for actively running experiments. The new strategy
@ -60,6 +64,19 @@ class ApplicationExperiment < Gitlab::Experiment # rubocop:disable Gitlab/Namesp
private
def tracking_context
{
namespace: context.try(:namespace) || context.try(:group),
project: context.try(:project),
user: user_or_actor
}.compact || {}
end
def user_or_actor
actor = context.try(:actor)
actor.respond_to?(:id) ? actor : context.try(:user)
end
def feature_flag_name
name.tr('/', '_')
end

View file

@ -39,7 +39,7 @@
access_levels: ProjectMember.access_level_roles,
default_access_level: Gitlab::Access::MAINTAINER,
prefix: :project_access_token,
help_path: help_page_path('user/project/settings/project_access_tokens', anchor: 'limiting-scopes-of-a-project-access-token')
help_path: help_page_path('user/project/settings/project_access_tokens', anchor: 'scopes-for-a-project-access-token')
= render 'shared/access_tokens/table',
active_tokens: @active_project_access_tokens,

View file

@ -1,11 +0,0 @@
- filter = params[:two_factor] || 'everyone'
- filter_options = { 'everyone' => _('Everyone'), 'enabled' => _('Enabled'), 'disabled' => _('Disabled') }
.dropdown.inline.member-filter-2fa-dropdown{ data: { testid: 'member-filter-2fa-dropdown' } }
= dropdown_toggle(filter_options[filter], { toggle: 'dropdown', testid: 'dropdown-toggle' })
%ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable
%li.dropdown-header
= _("Filter by two-factor authentication")
- filter_options.each do |value, title|
%li
= link_to filter_group_project_member_path(two_factor: value), class: ("is-active" if filter == value) do
= title

View file

@ -0,0 +1,15 @@
# frozen_string_literal: true
class DropCiPipelinesMrMetricsFk < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
def up
with_lock_retries do
remove_foreign_key_if_exists(:merge_request_metrics, :ci_pipelines, name: "fk_rails_33ae169d48")
end
end
def down
add_concurrent_foreign_key(:merge_request_metrics, :ci_pipelines, name: "fk_rails_33ae169d48", column: :pipeline_id, target_column: :id, on_delete: "cascade")
end
end

View file

@ -0,0 +1 @@
3d011cc67fc6ac661788f2d0e3766e51d624a4248ac9dbd861a4db810d396091

View file

@ -30338,9 +30338,6 @@ ALTER TABLE ONLY container_repositories
ALTER TABLE ONLY clusters_applications_jupyter
ADD CONSTRAINT fk_rails_331f0aff78 FOREIGN KEY (oauth_application_id) REFERENCES oauth_applications(id) ON DELETE SET NULL;
ALTER TABLE ONLY merge_request_metrics
ADD CONSTRAINT fk_rails_33ae169d48 FOREIGN KEY (pipeline_id) REFERENCES ci_pipelines(id) ON DELETE CASCADE;
ALTER TABLE ONLY suggestions
ADD CONSTRAINT fk_rails_33b03a535c FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE;

View file

@ -58,7 +58,7 @@ POST projects/:id/access_tokens
|-----------|---------|----------|---------------------|
| `id` | integer or string | yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) |
| `name` | String | yes | The name of the project access token |
| `scopes` | `Array[String]` | yes | [List of scopes](../user/project/settings/project_access_tokens.md#limiting-scopes-of-a-project-access-token) |
| `scopes` | `Array[String]` | yes | [List of scopes](../user/project/settings/project_access_tokens.md#scopes-for-a-project-access-token) |
| `access_level` | Integer | no | A valid access level. Default value is 40 (Maintainer). Other allowed values are 10 (Guest), 20 (Reporter), and 30 (Developer). |
| `expires_at` | Date | no | The token expires at midnight UTC on that date |

View file

@ -886,7 +886,7 @@ Here, the apollo query is watching for changes in `graphqlResourceEtag`. If your
You can see an example of this in the pipeline status of the pipeline editor. The pipeline editor watches for changes in the latest pipeline. When the user creates a new commit, we update the pipeline query to poll for changes in the new pipeline.
```graphql
# pipeline_etag.graphql
# pipeline_etag.query.graphql
query getPipelineEtag {
pipelineEtag @client
@ -896,7 +896,7 @@ query getPipelineEtag {
```javascript
/* pipeline_editor/components/header/pipeline_status.vue */
import getPipelineEtag from '~/pipeline_editor/graphql/queries/client/pipeline_etag.graphql';
import getPipelineEtag from '~/pipeline_editor/graphql/queries/client/pipeline_etag.query.graphql';
apollo: {
pipelineEtag: {

View file

@ -42,7 +42,7 @@ Using the **Delete user and contributions** option may result
in removing more data than intended. Please see [associated records](#associated-records)
below for additional details.
### Associated Records
### Associated records
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/7393) for issues in GitLab 9.0.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/10467) for merge requests, award emoji, notes, and abuse reports in GitLab 9.1.

View file

@ -12,111 +12,88 @@ type: reference, howto
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/235765) in GitLab 13.5.
> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/342327) in GitLab 14.5. Default prefix added.
Project access tokens are similar to [personal access tokens](../../profile/personal_access_tokens.md)
except they are attached to a project rather than a user. They can be used to:
You can use a project access token to authenticate:
- Authenticate with the [GitLab API](../../../api/index.md#personalproject-access-tokens).
- Authenticate with Git using HTTP Basic Authentication. If you are asked for a username when
authenticating, you can use any non-empty value because only the token is needed.
- With the [GitLab API](../../../api/index.md#personalproject-access-tokens).
- With Git, when using HTTP Basic Authentication.
Project access tokens:
After you configure a project access token, you don't need a password when you authenticate.
Instead, you can enter any non-blank value.
- Expire on the date you define, at midnight UTC.
- Are supported for self-managed instances on Free tier and above. Free self-managed instances
should:
- Review their security and compliance policies with regards to
Project access tokens are similar to [personal access tokens](../../profile/personal_access_tokens.md),
except they are associated with a project rather than a user.
You can use project access tokens:
- On GitLab SaaS if you have the Premium license tier or higher. Personal access tokens are not available with a [trial license](https://about.gitlab.com/free-trial/).
- On self-managed instances of GitLab, with any license tier. If you have the Free tier:
- Review your security and compliance policies around
[user self-enrollment](../../admin_area/settings/sign_up_restrictions.md#disable-new-sign-ups).
- Consider [disabling project access tokens](#enable-or-disable-project-access-token-creation) to
lower potential abuse.
- Are also supported on GitLab SaaS Premium and above (excluding [trial licenses](https://about.gitlab.com/free-trial/).)
For examples of how you can use a project access token to authenticate with the API, see the
[relevant section from our API Docs](../../../api/index.md#personalproject-access-tokens).
## Create a project access token
NOTE:
For GitLab.com and self-managed instances, the default prefix is `glpat-`.
To create a project access token:
## Creating a project access token
1. On the top bar, select **Menu > Projects** and find your project.
1. On the left sidebar, select **Settings > Access Tokens**.
1. Enter a name.
1. Optional. Enter an expiry date for the token. The token will expire on that date at midnight UTC.
1. Select a role for the token.
1. Select the [desired scopes](#scopes-for-a-project-access-token).
1. Select **Create project access token**.
1. Log in to GitLab.
1. Navigate to the project you would like to create an access token for.
1. In the **Settings** menu choose **Access Tokens**.
1. Choose a name and optional expiry date for the token.
1. Choose a role for the token.
1. Choose the [desired scopes](#limiting-scopes-of-a-project-access-token).
1. Click the **Create project access token** button.
1. Save the project access token somewhere safe. Once you leave or refresh
the page, you don't have access to it again.
A project access token is displayed. Save the project access token somewhere safe. After you leave or refresh the page, you can't view it again.
## Project bot users
## Revoke a project access token
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/210181) in GitLab 13.0.
> - [Excluded from license seat use](https://gitlab.com/gitlab-org/gitlab/-/issues/223695) in GitLab 13.5.
To revoke a project access token:
Project bot users are [GitLab-created service accounts](../../../subscriptions/self_managed/index.md#billable-users) and do not count as licensed seats.
1. On the top bar, select **Menu > Projects** and find your project.
1. On the left sidebar, select **Settings > Access Tokens**.
1. Next to the project access token to revoke, select **Revoke**.
For each project access token created, a bot user is created and added to the project with
the [specified level permissions](../../permissions.md#project-members-permissions).
## Scopes for a project access token
For the bot:
The scope determines the actions you can perform when you authenticate with a project access token.
- The name is set to the name of the token.
- The username is set to `project_{project_id}_bot` for the first access token, such as `project_123_bot`.
- The email is set to `project{project_id}_bot@example.com`, for example `project123_bot@example.com`.
- For additional access tokens in the same project, the username is set to `project_{project_id}_bot{bot_count}`, for example `project_123_bot1`.
- For additional access tokens in the same project, the email is set to `project{project_id}_bot{bot_count}@example.com`, for example `project123_bot1@example.com`
API calls made with a project access token are associated with the corresponding bot user.
These bot users are included in a project's **Project information > Members** list but cannot be modified. Also, a bot
user cannot be added to any other project.
When the project access token is [revoked](#revoking-a-project-access-token), the bot user is deleted
and all records are moved to a system-wide user with the username "Ghost User". For more
information, see [Associated Records](../../profile/account/delete_account.md#associated-records).
## Revoking a project access token
At any time, you can revoke any project access token by clicking the
respective **Revoke** button in **Settings > Access Tokens**.
## Limiting scopes of a project access token
Project access tokens can be created with one or more scopes that allow various
actions that a given token can perform. The available scopes are depicted in
the following table.
| Scope | Description |
| ------------------ | ----------- |
| `api` | Grants complete read/write access to the scoped project API, including the [Package Registry](../../packages/package_registry/index.md). |
| `read_api` | Grants read access to the scoped project API, including the [Package Registry](../../packages/package_registry/index.md). |
| `read_registry` | Allows read-access (pull) to [container registry](../../packages/container_registry/index.md) images if a project is private and authorization is required. |
| `write_registry` | Allows write-access (push) to [container registry](../../packages/container_registry/index.md). |
| `read_repository` | Allows read-only access (pull) to the repository. |
| `write_repository` | Allows read-write access (pull, push) to the repository. |
| Scope | Description |
|:-------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `api` | Grants complete read and write access to the scoped project API, including the [Package Registry](../../packages/package_registry/index.md). |
| `read_api` | Grants read access to the scoped project API, including the [Package Registry](../../packages/package_registry/index.md). |
| `read_registry` | Allows read access (pull) to the [Container Registry](../../packages/container_registry/index.md) images if a project is private and authorization is required. |
| `write_registry` | Allows write access (push) to the [Container Registry](../../packages/container_registry/index.md). |
| `read_repository` | Allows read access (pull) to the repository. |
| `write_repository` | Allows read and write access (pull and push) to the repository. |
## Enable or disable project access token creation
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/287707) in GitLab 13.11.
You may enable or disable project access token creation for all projects in a group in **Group > Settings > General > Permissions, LFS, 2FA > Allow project access token creation**.
Even when creation is disabled, you can still use and revoke existing project access tokens.
This setting is available only on top-level groups.
To enable or disable project access token creation for all projects in a top-level group:
## Group access token workaround **(FREE SELF)**
1. On the top bar, select **Menu > Groups** and find your group.
1. On the left sidebar, select **Settings > General**.
1. Expand **Permissions, LFS, 2FA**.
1. Under **Permissions**, turn on or off **Allow project access token creation**.
Even when creation is disabled, you can still use and revoke existing project access tokens.
## Group access tokens **(FREE SELF)**
With group access tokens, you can use a single token to:
- Perform actions for groups.
- Manage the projects within the group.
- In [GitLab 14.2](https://gitlab.com/gitlab-org/gitlab/-/issues/330718) and later, authenticate with Git over HTTPS.
NOTE:
This section describes a workaround and is subject to change.
You cannot use the UI to create a group access token. [An issue exists](https://gitlab.com/gitlab-org/gitlab/-/issues/214045)
to add this functionality. This section describes a workaround.
Group access tokens let you use a single token to:
- Perform actions at the group level.
- Manage the projects within the group.
- In [GitLab 14.2](https://gitlab.com/gitlab-org/gitlab/-/issues/330718) and later, authenticate
with Git over HTTPS.
We don't support group access tokens in the GitLab UI, though GitLab self-managed
administrators can create them using the [Rails console](../../../administration/operations/rails_console.md).
If you are an administrator of a self-managed GitLab instance, you can create a group access token in the
[Rails console](../../../administration/operations/rails_console.md).
<div class="video-fallback">
For a demo of the group access token workaround, see <a href="https://www.youtube.com/watch?v=W2fg1P1xmU0">Demo: Group Level Access Tokens</a>.
@ -127,37 +104,71 @@ administrators can create them using the [Rails console](../../../administration
### Create a group access token
To create a group access token, run the following in a Rails console:
To create a group access token:
```ruby
admin = User.find(1) # group admin
group = Group.find(109) # the group you want to create a token for
bot = Users::CreateService.new(admin, { name: 'group_token', username: "group_#{group.id}_bot", email: "group_#{group.id}_bot@example.com", user_type: :project_bot }).execute # create the group bot user
# for further group access tokens, the username should be group_#{group.id}_bot#{bot_count}, e.g. group_109_bot2, and their email should be group_109_bot2@example.com
bot.confirm # confirm the bot
group.add_user(bot, :maintainer) # add the bot to the group at the desired access level
token = bot.personal_access_tokens.create(scopes:[:api, :write_repository], name: 'group_token') # give it a PAT
gtoken = token.token # get the token value
```
1. Run the following commands in a [Rails console](../../../administration/operations/rails_console.md):
Test if the generated group access token works:
```ruby
admin = User.find(1) # group admin
group = Group.find(109) # the group you want to create a token for
bot = Users::CreateService.new(admin, { name: 'group_token', username: "group_#{group.id}_bot", email: "group_#{group.id}_bot@example.com", user_type: :project_bot }).execute # create the group bot user
# for further group access tokens, the username should be group_#{group.id}_bot#{bot_count}, e.g. group_109_bot2, and their email should be group_109_bot2@example.com
bot.confirm # confirm the bot
group.add_user(bot, :maintainer) # add the bot to the group at the desired access level
token = bot.personal_access_tokens.create(scopes:[:api, :write_repository], name: 'group_token') # give it a PAT
gtoken = token.token # get the token value
```
1. Pass the group access token in the `PRIVATE-TOKEN` header to GitLab REST APIs. For example:
1. Test if the generated group access token works:
- [Create an epic](../../../api/epics.md#new-epic) on the group.
- [Create a project pipeline](../../../api/pipelines.md#create-a-new-pipeline)
in one of the group's projects.
- [Create an issue](../../../api/issues.md#new-issue) in one of the group's projects.
1. Use the group token to [clone a group's project](../../../gitlab-basics/start-using-git.md#clone-with-https)
using HTTPS.
1. Use the group access token in the `PRIVATE-TOKEN` header with GitLab REST APIs. For example:
- [Create an epic](../../../api/epics.md#new-epic) in the group.
- [Create a project pipeline](../../../api/pipelines.md#create-a-new-pipeline) in one of the group's projects.
- [Create an issue](../../../api/issues.md#new-issue) in one of the group's projects.
1. Use the group token to [clone a group's project](../../../gitlab-basics/start-using-git.md#clone-with-https)
using HTTPS.
### Revoke a group access token
To revoke a group access token, run the following in a Rails console:
To revoke a group access token, run the following command in a [Rails console](../../../administration/operations/rails_console.md):
```ruby
bot = User.find_by(username: 'group_109_bot') # the owner of the token you want to revoke
token = bot.personal_access_tokens.last # the token you want to revoke
token.revoke!
```
## Project bot users
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/210181) in GitLab 13.0.
> - [Excluded from license seat use](https://gitlab.com/gitlab-org/gitlab/-/issues/223695) in GitLab 13.5.
Project bot users are [GitLab-created service accounts](../../../subscriptions/self_managed/index.md#billable-users).
Each time you create a project access token, a bot user is created and added to the project.
These bot users do not count as licensed seats.
The bot users have [permissions](../../permissions.md#project-members-permissions) that correspond with the
selected role and [scope](#scopes-for-a-project-access-token) of the project access token.
- The name is set to the name of the token.
- The username is set to `project_{project_id}_bot` for the first access token. For example, `project_123_bot`.
- The email is set to `project{project_id}_bot@example.com`. For example, `project123_bot@example.com`.
- For additional access tokens in the same project, the username is set to `project_{project_id}_bot{bot_count}`. For
example, `project_123_bot1`.
- For additional access tokens in the same project, the email is set to `project{project_id}_bot{bot_count}@example.com`.
For example, `project123_bot1@example.com`.
API calls made with a project access token are associated with the corresponding bot user.
Bot users:
- Are included in a project's member list but cannot be modified.
- Cannot be added to any other project.
When the project access token is [revoked](#revoke-a-project-access-token):
- The bot user is deleted.
- All records are moved to a system-wide user with the username `Ghost User`. For more information, see
[associated records](../../profile/account/delete_account.md#associated-records).

View file

@ -159,7 +159,7 @@ class Feature
end
def log_feature_flag_states?(key)
key != :feature_flag_state_logs && Feature.enabled?(:feature_flag_state_logs, type: :ops)
Feature::Definition.log_states?(key)
end
def log_feature_flag_state(key, feature_value)

View file

@ -82,6 +82,16 @@ class Feature
attributes
end
def for_upcoming_milestone?
return false unless milestone
Gitlab::VersionInfo.parse(milestone + '.999') >= Gitlab.version_info
end
def force_log_state_changes?
attributes[:log_state_changes]
end
class << self
def paths
@paths ||= [Rails.root.join('config', 'feature_flags', '**', '*.yml')]
@ -106,6 +116,14 @@ class Feature
definitions.has_key?(key.to_sym)
end
def log_states?(key)
return false if key == :feature_flag_state_logs
return false if Feature.disabled?(:feature_flag_state_logs, type: :ops)
return false unless (feature = get(key))
feature.force_log_state_changes? || feature.for_upcoming_milestone?
end
def valid_usage!(key, type:, default_enabled:)
if definition = get(key)
definition.valid_usage!(type_in_code: type, default_enabled_in_code: default_enabled)

View file

@ -58,6 +58,7 @@ class Feature
introduced_by_url
rollout_issue_url
milestone
log_state_changes
type
group
default_enabled

View file

@ -45,3 +45,7 @@ terraform_state_versions:
- table: ci_builds
column: ci_build_id
on_delete: async_nullify
merge_request_metrics:
- table: ci_pipelines
column: pipeline_id
on_delete: async_delete

View file

@ -14142,9 +14142,6 @@ msgstr ""
msgid "Every year on %{day} at %{time} %{timezone}"
msgstr ""
msgid "Everyone"
msgstr ""
msgid "Everyone With Access"
msgstr ""
@ -15045,9 +15042,6 @@ msgstr ""
msgid "Filter by test cases that are currently open."
msgstr ""
msgid "Filter by two-factor authentication"
msgstr ""
msgid "Filter by user"
msgstr ""

View file

@ -233,6 +233,75 @@ RSpec.describe ApplicationExperiment, :experiment do
]
)
end
context "when using known context resources" do
let(:user) { build(:user, id: non_existing_record_id) }
let(:project) { build(:project, id: non_existing_record_id) }
let(:namespace) { build(:namespace, id: non_existing_record_id) }
let(:group) { build(:group, id: non_existing_record_id) }
let(:actor) { user }
let(:context) { { user: user, project: project, namespace: namespace } }
it "includes those using the gitlab standard context" do
subject.track(:action)
expect_snowplow_event(
category: 'namespaced/stub',
action: 'action',
user: user,
project: project,
namespace: namespace,
context: an_instance_of(Array)
)
end
it "falls back to using the group key" do
subject.context(namespace: nil, group: group)
subject.track(:action)
expect_snowplow_event(
category: 'namespaced/stub',
action: 'action',
user: user,
project: project,
namespace: group,
context: an_instance_of(Array)
)
end
context "with the actor key" do
it "provides it to the tracking call as the user" do
subject.context(user: nil, actor: actor)
subject.track(:action)
expect_snowplow_event(
category: 'namespaced/stub',
action: 'action',
user: actor,
project: project,
namespace: namespace,
context: an_instance_of(Array)
)
end
it "handles when it's not a user record" do
subject.context(user: nil, actor: nil)
subject.track(:action)
expect_snowplow_event(
category: 'namespaced/stub',
action: 'action',
project: project,
namespace: namespace,
context: an_instance_of(Array)
)
end
end
end
end
describe "#key_for" do

View file

@ -3,7 +3,7 @@ import { shallowMount } from '@vue/test-utils';
import waitForPromises from 'helpers/wait_for_promises';
import CiLint from '~/ci_lint/components/ci_lint.vue';
import CiLintResults from '~/pipeline_editor/components/lint/ci_lint_results.vue';
import lintCIMutation from '~/pipeline_editor/graphql/mutations/lint_ci.mutation.graphql';
import lintCIMutation from '~/pipeline_editor/graphql/mutations/client/lint_ci.mutation.graphql';
import SourceEditor from '~/vue_shared/components/source_editor.vue';
import { mockLintDataValid } from '../mock_data';

View file

@ -6,7 +6,7 @@ import dismissUserCallout from '~/graphql_shared/mutations/dismiss_user_callout.
import services from '~/ide/services';
import { query, mutate } from '~/ide/services/gql';
import { escapeFileUrl } from '~/lib/utils/url_utility';
import ciConfig from '~/pipeline_editor/graphql/queries/ci_config.graphql';
import ciConfig from '~/pipeline_editor/graphql/queries/ci_config.query.graphql';
import { projectData } from '../mock_data';
jest.mock('~/api');

View file

@ -12,7 +12,7 @@ import {
COMMIT_SUCCESS,
} from '~/pipeline_editor/constants';
import commitCreate from '~/pipeline_editor/graphql/mutations/commit_ci_file.mutation.graphql';
import updatePipelineEtag from '~/pipeline_editor/graphql/mutations/update_pipeline_etag.mutation.graphql';
import updatePipelineEtag from '~/pipeline_editor/graphql/mutations/client/update_pipeline_etag.mutation.graphql';
import {
mockCiConfigPath,

View file

@ -11,7 +11,7 @@ import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import BranchSwitcher from '~/pipeline_editor/components/file_nav/branch_switcher.vue';
import { DEFAULT_FAILURE } from '~/pipeline_editor/constants';
import getAvailableBranchesQuery from '~/pipeline_editor/graphql/queries/available_branches.graphql';
import getAvailableBranchesQuery from '~/pipeline_editor/graphql/queries/available_branches.query.graphql';
import {
mockBranchPaginationLimit,
mockDefaultBranch,

View file

@ -4,7 +4,7 @@ import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import PipelineStatus, { i18n } from '~/pipeline_editor/components/header/pipeline_status.vue';
import getPipelineQuery from '~/pipeline_editor/graphql/queries/client/pipeline.graphql';
import getPipelineQuery from '~/pipeline_editor/graphql/queries/pipeline.query.graphql';
import PipelineEditorMiniGraph from '~/pipeline_editor/components/header/pipeline_editor_mini_graph.vue';
import { mockCommitSha, mockProjectPipeline, mockProjectFullPath } from '../../mock_data';

View file

@ -9,12 +9,11 @@ import PipelineEditorTabs from '~/pipeline_editor/components/pipeline_editor_tab
import PipelineEditorEmptyState from '~/pipeline_editor/components/ui/pipeline_editor_empty_state.vue';
import PipelineEditorMessages from '~/pipeline_editor/components/ui/pipeline_editor_messages.vue';
import { COMMIT_SUCCESS, COMMIT_FAILURE, LOAD_FAILURE_UNKNOWN } from '~/pipeline_editor/constants';
import getBlobContent from '~/pipeline_editor/graphql/queries/blob_content.graphql';
import getCiConfigData from '~/pipeline_editor/graphql/queries/ci_config.graphql';
import getBlobContent from '~/pipeline_editor/graphql/queries/blob_content.query.graphql';
import getCiConfigData from '~/pipeline_editor/graphql/queries/ci_config.query.graphql';
import getTemplate from '~/pipeline_editor/graphql/queries/get_starter_template.query.graphql';
import getLatestCommitShaQuery from '~/pipeline_editor/graphql/queries/latest_commit_sha.query.graphql';
import getPipelineQuery from '~/pipeline_editor/graphql/queries/client/pipeline.graphql';
import getPipelineQuery from '~/pipeline_editor/graphql/queries/pipeline.query.graphql';
import PipelineEditorApp from '~/pipeline_editor/pipeline_editor_app.vue';
import PipelineEditorHome from '~/pipeline_editor/pipeline_editor_home.vue';

View file

@ -161,6 +161,41 @@ RSpec.describe Feature::Definition do
end
end
describe '.for_upcoming_milestone?' do
using RSpec::Parameterized::TableSyntax
let(:definition) do
Feature::Definition.new("development/enabled_feature_flag.yml",
name: :enabled_feature_flag,
type: 'development',
milestone: milestone,
default_enabled: false)
end
before do
allow(Feature::Definition).to receive(:definitions) do
{ definition.key => definition }
end
allow(Gitlab).to receive(:version_info).and_return(Gitlab::VersionInfo.parse(current_milestone))
end
subject { definition.for_upcoming_milestone? }
where(:ctx, :milestone, :current_milestone, :expected) do
'no milestone' | nil | '1.0.0' | false
'upcoming milestone - major' | '2.3' | '1.9.999' | true
'upcoming milestone - minor' | '2.3' | '2.2.999' | true
'current milestone' | '2.3' | '2.3.999' | true
'past milestone - major' | '1.9' | '2.3.999' | false
'past milestone - minor' | '2.2' | '2.3.999' | false
end
with_them do
it {is_expected.to be(expected)}
end
end
describe '.valid_usage!' do
before do
allow(described_class).to receive(:definitions) do
@ -215,7 +250,42 @@ RSpec.describe Feature::Definition do
end
end
describe '.defaul_enabled?' do
describe '.log_states?' do
using RSpec::Parameterized::TableSyntax
let(:definition) do
Feature::Definition.new("development/enabled_feature_flag.yml",
name: :enabled_feature_flag,
type: 'development',
milestone: milestone,
log_state_changes: log_state_change,
default_enabled: false)
end
before do
allow(Feature::Definition).to receive(:definitions) do
{ definition.key => definition }
end
allow(Gitlab).to receive(:version_info).and_return(Gitlab::VersionInfo.new(10, 0, 0))
end
subject { Feature::Definition.log_states?(key) }
where(:ctx, :key, :milestone, :log_state_change, :expected) do
'When flag does not exist' | :no_flag | "0.0" | true | false
'When flag is old, and logging is not forced' | :enabled_feature_flag | "0.0" | false | false
'When flag is old, but logging is forced' | :enabled_feature_flag | "0.0" | true | true
'When flag is current' | :enabled_feature_flag | "10.0" | true | true
'Flag is upcoming' | :enabled_feature_flag | "10.0" | true | true
end
with_them do
it { is_expected.to be(expected) }
end
end
describe '.default_enabled?' do
subject { described_class.default_enabled?(key) }
context 'when feature flag exist' do

View file

@ -186,6 +186,17 @@ RSpec.describe Feature, stub_feature_flags: false do
context 'logging is enabled', :request_store do
before do
allow(Feature).to receive(:log_feature_flag_states?).and_call_original
definition = Feature::Definition.new("development/enabled_feature_flag.yml",
name: :enabled_feature_flag,
type: 'development',
log_state_changes: true,
default_enabled: false)
allow(Feature::Definition).to receive(:definitions) do
{ definition.key => definition }
end
described_class.enable(:feature_flag_state_logs)
described_class.enable(:enabled_feature_flag)
described_class.enabled?(:enabled_feature_flag)
@ -513,6 +524,82 @@ RSpec.describe Feature, stub_feature_flags: false do
end
end
describe '.log_feature_flag_states?' do
let(:log_state_changes) { false }
let(:milestone) { "0.0" }
let(:flag_name) { :some_flag }
let(:definition) do
Feature::Definition.new("development/#{flag_name}.yml",
name: flag_name,
type: 'development',
milestone: milestone,
log_state_changes: log_state_changes,
default_enabled: false)
end
before do
Feature.enable(:feature_flag_state_logs)
Feature.enable(:some_flag)
allow(Feature).to receive(:log_feature_flag_states?).and_return(false)
allow(Feature).to receive(:log_feature_flag_states?).with(:feature_flag_state_logs).and_call_original
allow(Feature).to receive(:log_feature_flag_states?).with(:some_flag).and_call_original
allow(Feature::Definition).to receive(:definitions) do
{ definition.key => definition }
end
end
subject { described_class.log_feature_flag_states?(flag_name) }
context 'when flag is feature_flag_state_logs' do
let(:milestone) { "14.6" }
let(:flag_name) { :feature_flag_state_logs }
let(:log_state_changes) { true }
it { is_expected.to be_falsey }
end
context 'when flag is old' do
it { is_expected.to be_falsey }
end
context 'when flag is old while log_state_changes is not present ' do
let(:definition) do
Feature::Definition.new("development/#{flag_name}.yml",
name: flag_name,
type: 'development',
milestone: milestone,
default_enabled: false)
end
it { is_expected.to be_falsey }
end
context 'when flag is old but log_state_changes is true' do
let(:log_state_changes) { true }
it { is_expected.to be_truthy }
end
context 'when flag is new and not feature_flag_state_logs' do
let(:milestone) { "14.6" }
it { is_expected.to be_truthy }
end
context 'when milestone is nil' do
let(:definition) do
Feature::Definition.new("development/#{flag_name}.yml",
name: flag_name,
type: 'development',
default_enabled: false)
end
it { is_expected.to be_falsey }
end
end
context 'caching with stale reads from the database', :use_clean_rails_redis_caching, :request_store, :aggregate_failures do
let(:actor) { stub_feature_flag_gate('CustomActor:5') }
let(:another_actor) { stub_feature_flag_gate('CustomActor:10') }

View file

@ -98,15 +98,23 @@ RSpec.describe Gitlab::Lograge::CustomOptions do
context 'when feature flags are present', :request_store do
before do
allow(Feature::Definition).to receive(:valid_usage!).and_return(true)
allow(Feature).to receive(:log_feature_flag_states?).and_return(false)
definitions = {}
[:enabled_feature, :disabled_feature].each do |flag_name|
definitions[flag_name] = Feature::Definition.new("development/enabled_feature.yml",
name: flag_name,
type: 'development',
log_state_changes: true,
default_enabled: false)
allow(Feature).to receive(:log_feature_flag_states?).with(flag_name).and_call_original
end
allow(Feature::Definition).to receive(:definitions).and_return(definitions)
Feature.enable(:enabled_feature)
Feature.disable(:disabled_feature)
allow(Feature).to receive(:log_feature_flag_states?).with(:enabled_feature).and_call_original
allow(Feature).to receive(:log_feature_flag_states?).with(:disabled_feature).and_call_original
end
context 'and :feature_flag_log_states is enabled' do

View file

@ -48,4 +48,10 @@ RSpec.describe MergeRequest::Metrics do
end
end
end
it_behaves_like 'cleanup by a loose foreign key' do
let!(:merge_request) { create(:merge_request) }
let!(:parent) { create(:ci_pipeline, project: merge_request.target_project) }
let!(:model) { merge_request.metrics.tap { |metrics| metrics.update!(pipeline: parent) } }
end
end

View file

@ -92,4 +92,9 @@ RSpec.describe Terraform::StateVersion do
end
end
end
it_behaves_like 'cleanup by a loose foreign key' do
let!(:model) { create(:terraform_state_version) }
let!(:parent) { model.build }
end
end

View file

@ -48,11 +48,15 @@ module SnowplowHelpers
# )
def expect_snowplow_event(category:, action:, context: nil, **kwargs)
if context
kwargs[:context] = []
context.each do |c|
expect(SnowplowTracker::SelfDescribingJson).to have_received(:new)
.with(c[:schema], c[:data]).at_least(:once)
kwargs[:context] << an_instance_of(SnowplowTracker::SelfDescribingJson)
if context.is_a?(Array)
kwargs[:context] = []
context.each do |c|
expect(SnowplowTracker::SelfDescribingJson).to have_received(:new)
.with(c[:schema], c[:data]).at_least(:once)
kwargs[:context] << an_instance_of(SnowplowTracker::SelfDescribingJson)
end
else
kwargs[:context] = context
end
end

View file

@ -47,3 +47,28 @@ RSpec.shared_examples 'it has loose foreign keys' do
expect(deleted_records.status_processed.count).to be(1)
end
end
RSpec.shared_examples 'cleanup by a loose foreign key' do
let(:foreign_key_definition) do
foreign_keys_for_parent = Gitlab::Database::LooseForeignKeys.definitions_by_table[parent.class.table_name]
foreign_keys_for_parent.find { |definition| definition.from_table == model.class.table_name }
end
def find_model
model.class.find_by(id: model.id)
end
it 'deletes the model' do
parent.delete
expect(find_model).to be_present
LooseForeignKeys::ProcessDeletedRecordsService.new(connection: model.connection).execute
if foreign_key_definition.on_delete.eql?(:async_delete)
expect(find_model).not_to be_present
else
expect(find_model[foreign_key_definition.column]).to eq(nil)
end
end
end