Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
da92a12093
commit
375c6d54dd
|
|
@ -922,6 +922,7 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
|
|||
/doc/user/free_user_limit.md @phillipwells
|
||||
/doc/user/group/ @lciutacu
|
||||
/doc/user/group/clusters/ @phillipwells
|
||||
/doc/user/group/compliance_frameworks.md @eread
|
||||
/doc/user/group/contribution_analytics/ @lciutacu
|
||||
/doc/user/group/custom_project_templates.md @eread
|
||||
/doc/user/group/devops_adoption/ @lciutacu
|
||||
|
|
@ -931,6 +932,7 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
|
|||
/doc/user/group/issues_analytics/ @msedlakjakubowski
|
||||
/doc/user/group/iterations/ @msedlakjakubowski
|
||||
/doc/user/group/planning_hierarchy/ @msedlakjakubowski
|
||||
/doc/user/group/reporting/ @phillipwells
|
||||
/doc/user/group/repositories_analytics/ @marcel.amirault
|
||||
/doc/user/group/roadmap/ @msedlakjakubowski
|
||||
/doc/user/group/saml_sso/ @jglassman1
|
||||
|
|
@ -1017,6 +1019,7 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
|
|||
/doc/user/project/requirements/ @msedlakjakubowski
|
||||
/doc/user/project/service_desk.md @msedlakjakubowski
|
||||
/doc/user/project/settings/import_export.md @eread
|
||||
/doc/user/project/settings/import_export_troubleshooting.md @eread
|
||||
/doc/user/project/settings/index.md @lciutacu
|
||||
/doc/user/project/settings/project_access_tokens.md @jglassman1
|
||||
/doc/user/project/time_tracking.md @msedlakjakubowski
|
||||
|
|
@ -1026,7 +1029,6 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
|
|||
/doc/user/public_access.md @lciutacu
|
||||
/doc/user/reserved_names.md @lciutacu
|
||||
/doc/user/search/ @ashrafkhamis
|
||||
/doc/user/search/global_search/ @ashrafkhamis
|
||||
/doc/user/shortcuts.md @ashrafkhamis
|
||||
/doc/user/snippets.md @ashrafkhamis
|
||||
/doc/user/ssh.md @jglassman1
|
||||
|
|
|
|||
|
|
@ -573,6 +573,16 @@ ee:registry-object-storage-tls:
|
|||
GITLAB_TLS_CERTIFICATE: $QA_GITLAB_TLS_CERTIFICATE
|
||||
GITLAB_QA_OPTS: --omnibus-config registry_object_storage
|
||||
|
||||
ee:importers:
|
||||
extends: .qa
|
||||
variables:
|
||||
QA_SCENARIO: Test::Integration::Import
|
||||
QA_ALLOW_LOCAL_REQUESTS: "true"
|
||||
rules:
|
||||
- !reference [.rules:test:qa, rules]
|
||||
- if: $QA_SUITES =~ /Test::Integration::Import/
|
||||
- !reference [.rules:test:manual, rules]
|
||||
|
||||
# ==========================================
|
||||
# Post test stage
|
||||
# ==========================================
|
||||
|
|
|
|||
|
|
@ -3,7 +3,11 @@ import { GlNav, GlNavItem } from '@gitlab/ui';
|
|||
import { mapActions, mapState } from 'vuex';
|
||||
import { formatNumber } from '~/locale';
|
||||
import Tracking from '~/tracking';
|
||||
import { NAV_LINK_DEFAULT_CLASSES, NUMBER_FORMATING_OPTIONS } from '../constants';
|
||||
import {
|
||||
NAV_LINK_DEFAULT_CLASSES,
|
||||
NUMBER_FORMATING_OPTIONS,
|
||||
NAV_LINK_COUNT_DEFAULT_CLASSES,
|
||||
} from '../constants';
|
||||
|
||||
export default {
|
||||
name: 'ScopeNavigation',
|
||||
|
|
@ -20,9 +24,6 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
...mapActions(['fetchSidebarCount']),
|
||||
activeClasses(currentScope) {
|
||||
return currentScope === this.urlQuery.scope ? 'gl-font-weight-bold' : '';
|
||||
},
|
||||
showFormatedCount(count) {
|
||||
if (!count) {
|
||||
return '0';
|
||||
|
|
@ -33,14 +34,21 @@ export default {
|
|||
handleClick(scope) {
|
||||
this.track('click_menu_item', { label: `vertical_navigation_${scope}` });
|
||||
},
|
||||
linkClasses(scope) {
|
||||
linkClasses(isHighlighted) {
|
||||
return [...this.$options.NAV_LINK_DEFAULT_CLASSES, { 'gl-font-weight-bold': isHighlighted }];
|
||||
},
|
||||
countClasses(isHighlighted) {
|
||||
return [
|
||||
{ 'gl-font-weight-bold': scope === this.urlQuery.scope },
|
||||
...this.$options.NAV_LINK_DEFAULT_CLASSES,
|
||||
...this.$options.NAV_LINK_COUNT_DEFAULT_CLASSES,
|
||||
isHighlighted ? 'gl-text-gray-900' : 'gl-text-gray-500',
|
||||
];
|
||||
},
|
||||
isActive(scope, index) {
|
||||
return this.urlQuery.scope ? this.urlQuery.scope === scope : index === 0;
|
||||
},
|
||||
},
|
||||
NAV_LINK_DEFAULT_CLASSES,
|
||||
NAV_LINK_COUNT_DEFAULT_CLASSES,
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
@ -50,13 +58,13 @@ export default {
|
|||
<gl-nav-item
|
||||
v-for="(item, scope, index) in navigation"
|
||||
:key="scope"
|
||||
:link-classes="linkClasses(scope)"
|
||||
:link-classes="linkClasses(isActive(scope, index))"
|
||||
class="gl-mb-1"
|
||||
:href="item.link"
|
||||
:active="urlQuery.scope ? urlQuery.scope === scope : index === 0"
|
||||
:active="isActive(scope, index)"
|
||||
@click="handleClick(scope)"
|
||||
><span>{{ item.label }}</span
|
||||
><span v-if="item.count" class="gl-font-sm gl-font-weight-normal">
|
||||
><span v-if="item.count" :class="countClasses(isActive(scope, index))">
|
||||
{{ showFormatedCount(item.count) }}
|
||||
</span>
|
||||
</gl-nav-item>
|
||||
|
|
|
|||
|
|
@ -9,3 +9,5 @@ export const NAV_LINK_DEFAULT_CLASSES = [
|
|||
'gl-justify-content-space-between',
|
||||
'gl-text-gray-900',
|
||||
];
|
||||
|
||||
export const NAV_LINK_COUNT_DEFAULT_CLASSES = ['gl-font-sm', 'gl-font-weight-normal'];
|
||||
|
|
|
|||
|
|
@ -94,12 +94,13 @@ module Emails
|
|||
end
|
||||
end
|
||||
|
||||
def access_token_revoked_email(user, token_name)
|
||||
def access_token_revoked_email(user, token_name, source = nil)
|
||||
return unless user&.active?
|
||||
|
||||
@user = user
|
||||
@token_name = token_name
|
||||
@target_url = profile_personal_access_tokens_url
|
||||
@source = source
|
||||
|
||||
Gitlab::I18n.with_locale(@user.preferred_language) do
|
||||
mail_with_locale(to: @user.notification_email_or_default, subject: subject(_("A personal access token has been revoked")))
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Analytics::CycleAnalytics::Aggregation < ApplicationRecord
|
||||
include IgnorableColumns
|
||||
include FromUnion
|
||||
|
||||
belongs_to :group, optional: false
|
||||
|
|
@ -11,14 +10,6 @@ class Analytics::CycleAnalytics::Aggregation < ApplicationRecord
|
|||
scope :priority_order, -> (column_to_sort = :last_incremental_run_at) { order(arel_table[column_to_sort].asc.nulls_first) }
|
||||
scope :enabled, -> { where('enabled IS TRUE') }
|
||||
|
||||
# These columns were added with wrong naming convention, the columns were never used.
|
||||
ignore_column :last_full_run_processed_records, remove_with: '15.1', remove_after: '2022-05-22'
|
||||
ignore_column :last_full_run_runtimes_in_seconds, remove_with: '15.1', remove_after: '2022-05-22'
|
||||
ignore_column :last_full_run_issues_updated_at, remove_with: '15.1', remove_after: '2022-05-22'
|
||||
ignore_column :last_full_run_mrs_updated_at, remove_with: '15.1', remove_after: '2022-05-22'
|
||||
ignore_column :last_full_run_issues_id, remove_with: '15.1', remove_after: '2022-05-22'
|
||||
ignore_column :last_full_run_merge_requests_id, remove_with: '15.1', remove_after: '2022-05-22'
|
||||
|
||||
def cursor_for(mode, model)
|
||||
{
|
||||
updated_at: self["last_#{mode}_#{model.table_name}_updated_at"],
|
||||
|
|
|
|||
|
|
@ -98,10 +98,10 @@ class NotificationService
|
|||
end
|
||||
|
||||
# Notify the user when one of their personal access tokens is revoked
|
||||
def access_token_revoked(user, token_name)
|
||||
def access_token_revoked(user, token_name, source = nil)
|
||||
return unless user.can?(:receive_notifications)
|
||||
|
||||
mailer.access_token_revoked_email(user, token_name).deliver_later
|
||||
mailer.access_token_revoked_email(user, token_name, source).deliver_later
|
||||
end
|
||||
|
||||
# Notify the user when at least one of their ssh key has expired today
|
||||
|
|
|
|||
|
|
@ -4,10 +4,13 @@ module PersonalAccessTokens
|
|||
class RevokeService < BaseService
|
||||
attr_reader :token, :current_user, :group
|
||||
|
||||
def initialize(current_user = nil, token: nil, group: nil)
|
||||
VALID_SOURCES = %w[secret_detection].freeze
|
||||
|
||||
def initialize(current_user = nil, token: nil, group: nil, source: nil)
|
||||
@current_user = current_user
|
||||
@token = token
|
||||
@group = group
|
||||
@source = source
|
||||
end
|
||||
|
||||
def execute
|
||||
|
|
@ -15,7 +18,7 @@ module PersonalAccessTokens
|
|||
|
||||
if token.revoke!
|
||||
log_event
|
||||
notification_service.access_token_revoked(token.user, token.name)
|
||||
notification_service.access_token_revoked(token.user, token.name, @source)
|
||||
ServiceResponse.success(message: success_message)
|
||||
else
|
||||
ServiceResponse.error(message: error_message)
|
||||
|
|
@ -33,11 +36,24 @@ module PersonalAccessTokens
|
|||
end
|
||||
|
||||
def revocation_permitted?
|
||||
Ability.allowed?(current_user, :revoke_token, token)
|
||||
if current_user
|
||||
Ability.allowed?(current_user, :revoke_token, token)
|
||||
else
|
||||
source && VALID_SOURCES.include?(source)
|
||||
end
|
||||
end
|
||||
|
||||
def source
|
||||
current_user&.username || @source
|
||||
end
|
||||
|
||||
def log_event
|
||||
Gitlab::AppLogger.info("PAT REVOCATION: revoked_by: '#{current_user.username}', revoked_for: '#{token.user.username}', token_id: '#{token.id}'")
|
||||
Gitlab::AppLogger.info(
|
||||
class: self.class.name,
|
||||
message: "PAT Revoked",
|
||||
revoked_by: source,
|
||||
revoked_for: token.user.username,
|
||||
token_id: token.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@
|
|||
"properties": {
|
||||
"id": { "type": "string" },
|
||||
"team_name": { "type": "string" },
|
||||
"team_id": { "type": "string" },
|
||||
"team_id": { "type": "array" },
|
||||
"app_name": { "type": "string" },
|
||||
"app_id": { "type": "string" },
|
||||
"app_id_prefix": { "type": "string" },
|
||||
"app_id_prefix": { "type": "array" },
|
||||
"xcode_managed": { "type": "boolean" },
|
||||
"entitlements": { "type": "object" },
|
||||
"devices": { "type": "array" },
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
= _('Hi %{username}!') % { username: sanitize_name(@user.name) }
|
||||
%p
|
||||
= html_escape(_('A personal access token, named %{code_start}%{token_name}%{code_end}, has been revoked.')) % { code_start: '<code>'.html_safe, token_name: @token_name, code_end: '</code>'.html_safe }
|
||||
- if @source == 'secret_detection'
|
||||
= _('We found your token in a public project and have automatically revoked it to protect your account.')
|
||||
%p
|
||||
- pat_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: @target_url }
|
||||
= html_escape(_('You can check your tokens or create a new one in your %{pat_link_start}personal access tokens settings%{pat_link_end}.')) % { pat_link_start: pat_link_start, pat_link_end: '</a>'.html_safe }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
<%= _('Hi %{username}!') % { username: sanitize_name(@user.name) } %>
|
||||
|
||||
<%= _('A personal access token, named %{token_name}, has been revoked.') % { token_name: @token_name } %>
|
||||
<% if @source == 'secret_detection' %>
|
||||
|
||||
<%= _('We found your token in a public project and have automatically revoked it to protect your account.') %>
|
||||
<% end %>
|
||||
|
||||
<%= _('You can check your tokens or create a new one in your personal access tokens settings %{pat_link}.') % { pat_link: @target_url } %>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: gitlab_pat_auto_revocation
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/103713
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/382610
|
||||
milestone: '15.6'
|
||||
type: development
|
||||
group: group::static analysis
|
||||
default_enabled: false
|
||||
|
|
@ -60,3 +60,4 @@ swap:
|
|||
reporter access: the Reporter role
|
||||
reporter permission: the Reporter role
|
||||
reporter permissions: the Reporter role
|
||||
at least the Owner role: the Owner role
|
||||
|
|
|
|||
|
|
@ -110,7 +110,8 @@ identifying abstract concepts and are subject to changes as we refine the design
|
|||
|
||||
## Definition of pipeline component
|
||||
|
||||
A pipeline component is a reusable single-purpose building block that abstracts away a single pipeline configuration unit. Components are used to compose a part or entire pipeline configuration.
|
||||
A pipeline component is a reusable single-purpose building block that abstracts away a single pipeline configuration unit.
|
||||
Components are used to compose a part or entire pipeline configuration.
|
||||
It can optionally take input parameters and set output data to be adaptable and reusable in different pipeline contexts,
|
||||
while encapsulating and isolating implementation details.
|
||||
|
||||
|
|
@ -133,27 +134,145 @@ For best experience with any systems made of components it's fundamental that co
|
|||
The version identifies the exact interface and behavior of the component.
|
||||
- **Resolvable**: when a component depends on another component, this dependency must be explicit and trackable.
|
||||
|
||||
## Proposal
|
||||
## Structure of a component
|
||||
|
||||
Prerequisites to create a component:
|
||||
A pipeline component is identified by the path to a repository or directory that defines it
|
||||
and a specific version: `<component-path>@<version>`.
|
||||
|
||||
- Create a project. Description and avatar are highly recommended to improve discoverability.
|
||||
- Add a `README.md` in the top level directory that documents the component.
|
||||
What it does, how to use it, how to contribute, etc.
|
||||
This file is mandatory.
|
||||
- Add a `.gitlab-ci.yml` in the top level directory to test that the components works as expected.
|
||||
This file is highly recommended.
|
||||
For example: `gitlab-org/dast@1.0`.
|
||||
|
||||
Characteristics of a component:
|
||||
### The component path
|
||||
|
||||
- It must have a **name** to be referenced to and **description** for extra details.
|
||||
- It must specify its **type** which defines how it can be used (raw configuration to be `include`d, child pipeline workflow, job step).
|
||||
- It must define its **content** based on the type.
|
||||
- It must specify **input parameters** that it accepts. Components should depend on input parameters for dynamic values and not environment variables.
|
||||
- It can optionally define **output data** that it returns.
|
||||
- Its YAML specification should be **validated statically** (for example: using JSON schema validators).
|
||||
- It should be possible to use specific **versions** of a component by referencing official releases and SHA.
|
||||
- It should be possible to use components defined locally in the same repository.
|
||||
A component path must contain at least the metadata YAML and optionally a related `README.md` documentation file.
|
||||
|
||||
The component path can be:
|
||||
|
||||
- A path to a project: `gitlab-org/dast`. In this case the 2 files are defined in the root directory of the repository.
|
||||
- A path to a project subdirectory: `gitlab-org/dast/api-scan`. In this case the 2 files are defined in the `api-scan` directory.
|
||||
- A path to a local directory: `/path/to/component`. This path must contain the metadata YAML that defines the component.
|
||||
The path must start with `/` to indicate a full path in the repository.
|
||||
|
||||
The metadata YAML file follows the filename convention `gitlab-<component-type>.yml` where component type is one of:
|
||||
|
||||
| Component type | Context |
|
||||
| -------------- | ------- |
|
||||
| `template` | For components used under `include:` keyword |
|
||||
| `step` | For components used under `steps:` keyword |
|
||||
| `workflow` | For components used under `trigger:` keyword |
|
||||
|
||||
Based on the context where the component is used we fetch the correct YAML file.
|
||||
For example, if we are including a component `gitlab-org/dast@1.0` we expect a YAML file named `gitlab-template.yml` in the
|
||||
top level directory of `gitlab-org/dast` repository.
|
||||
|
||||
A `gitlab-<component-type>.yml` file:
|
||||
|
||||
- Must have a **name** to be referenced to and **description** for extra details.
|
||||
- Must specify its **type** in the filename, which defines how it can be used (raw configuration to be `include`d, child pipeline workflow, job step).
|
||||
- Must define its **content** based on the type.
|
||||
- Must specify **input parameters** that it accepts. Components should depend on input parameters for dynamic values and not environment variables.
|
||||
- Can optionally define **output data** that it returns.
|
||||
- Should be **validated statically** (for example: using JSON schema validators).
|
||||
|
||||
Components that are released in the catalog must have a `README.md` file in the same directory as the
|
||||
metadata YAML file. The `README.md` represents the documentation for the specific component, hence it's recommended
|
||||
even when not releasing versions in the catalog.
|
||||
|
||||
### The component version
|
||||
|
||||
The version of the component can be (in order of highest priority first):
|
||||
|
||||
1. A commit SHA - For example: `gitlab-org/dast@e3262fdd0914fa823210cdb79a8c421e2cef79d8`
|
||||
1. A released tag - For example: `gitlab-org/dast@1.0`
|
||||
1. A special moving target version that points to the most recent released tag - For example: `gitlab-org/dast@~latest`
|
||||
1. An unreleased tag - For example: `gitlab-org/dast@rc-1.0`
|
||||
1. A branch name - For example: `gitlab-org/dast@master`
|
||||
|
||||
If a tag and branch exist with the same name, the tag takes precedence over the branch.
|
||||
Similarly, if a tag is named `e3262fdd0914fa823210cdb79a8c421e2cef79d8`, a commit SHA (if exists)
|
||||
takes precedence over the tag.
|
||||
|
||||
As we want to be able to reference any revisions (even those not released), a component must be defined in a Git repository.
|
||||
|
||||
NOTE:
|
||||
When referencing a component by local path (for example `./path/to/component`), its version is implicit and matches
|
||||
the commit SHA of the current pipeline context.
|
||||
|
||||
## Components project
|
||||
|
||||
A components project is a GitLab project/repository that exclusively hosts one or more pipeline components.
|
||||
|
||||
For components projects it's highly recommended to set an appropriate avatar and project description
|
||||
to improve discoverability in the catalog.
|
||||
|
||||
### Structure of a components project
|
||||
|
||||
A project can host one or more components depending on whether the author wants to define a single component
|
||||
per project or include multiple cohesive components under the same project.
|
||||
|
||||
Let's imagine we are developing a component that runs RSpec tests for a Rails app. We create a component project
|
||||
called `myorg/rails-rspec`.
|
||||
|
||||
The following directory structure would support 1 component per project:
|
||||
|
||||
```plaintext
|
||||
.
|
||||
├── gitlab-<type>.yml
|
||||
├── README.md
|
||||
└── .gitlab-ci.yml
|
||||
```
|
||||
|
||||
The `.gitlab-ci.yml` is recommended for the project to ensure changes are verified accordingly.
|
||||
|
||||
The component is now identified by the path `myorg/rails-rspec`. In other words, this means that
|
||||
the `gitlab-<type>.yml` and `README.md` are located in the root directory of the repository.
|
||||
|
||||
The following directory structure would support multiple components per project:
|
||||
|
||||
```plaintext
|
||||
.
|
||||
├── .gitlab-ci.yml
|
||||
├── unit/
|
||||
│ ├── gitlab-workflow.yml
|
||||
│ └── README.md
|
||||
├── integration/
|
||||
│ ├── gitlab-workflow.yml
|
||||
│ └── README.md
|
||||
└── feature/
|
||||
├── gitlab-workflow.yml
|
||||
└── README.md
|
||||
```
|
||||
|
||||
In this example we are defining multiple test profiles that are executed with RSpec.
|
||||
The user could choose to use one or more of these.
|
||||
|
||||
Each of these components are identified by their path `myorg/rails-rspec/unit`, `myorg/rails-rspec/integration`
|
||||
and `myorg/rails-rspec/feature`.
|
||||
|
||||
This directory structure could also support both strategies:
|
||||
|
||||
```plaintext
|
||||
.
|
||||
├── gitlab-template.yml # myorg/rails-rspec
|
||||
├── README.md
|
||||
├── .gitlab-ci.yml
|
||||
├── unit/
|
||||
│ ├── gitlab-workflow.yml # myorg/rails-rspec/unit
|
||||
│ └── README.md
|
||||
├── integration/
|
||||
│ ├── gitlab-workflow.yml # myorg/rails-rspec/integration
|
||||
│ └── README.md
|
||||
└── feature/
|
||||
├── gitlab-workflow.yml # myorg/rails-rspec/feature
|
||||
└── README.md
|
||||
```
|
||||
|
||||
With the above structure we could have a top-level component that can be used as the
|
||||
default component. For example, `myorg/rails-rspec` could run all the test profiles together.
|
||||
However, more specific test profiles could be used separately (for example `myorg/rails-rspec/integration`).
|
||||
|
||||
NOTE:
|
||||
Any nesting more than 1 level is initially not permitted.
|
||||
This limitation encourages cohesion at project level and keeps complexity low.
|
||||
|
||||
## Limits
|
||||
|
||||
|
|
@ -188,3 +307,34 @@ Some limits we could consider adding:
|
|||
- Allow self-managed administrators to populate their self-managed catalog by importing/updating
|
||||
components from GitLab.com or from repository exports.
|
||||
- Iterate on feedback.
|
||||
|
||||
## Who
|
||||
|
||||
Proposal:
|
||||
|
||||
<!-- vale gitlab.Spelling = NO -->
|
||||
|
||||
| Role | Who
|
||||
|------------------------------|-------------------------|
|
||||
| Author | Fabio Pitino |
|
||||
| Engineering Leader | ? |
|
||||
| Product Manager | Dov Hershkovitch |
|
||||
| Architecture Evolution Coach | Kamil Trzciński, Grzegorz Bizon |
|
||||
|
||||
DRIs:
|
||||
|
||||
| Role | Who
|
||||
|------------------------------|------------------------|
|
||||
| Leadership | ? |
|
||||
| Product | Dov Hershkovitch |
|
||||
| Engineering | Fabio Pitino |
|
||||
| UX | Kevin Comoli |
|
||||
|
||||
Domain experts:
|
||||
|
||||
| Area | Who
|
||||
|------------------------------|------------------------|
|
||||
| Verify / Pipeline authoring | Avielle Wolfe |
|
||||
| Verify / Pipeline authoring | Furkan Ayhan |
|
||||
|
||||
<!-- vale gitlab.Spelling = YES -->
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@ To maximize the effectiveness of group-level protected environments,
|
|||
[group-level memberships](../../user/group/index.md) must be correctly
|
||||
configured:
|
||||
|
||||
- Operators should be given at least the Owner role
|
||||
- Operators should be given the Owner role
|
||||
for the top-level group. They can maintain CI/CD configurations for
|
||||
the higher environments (such as production) in the group-level settings page,
|
||||
which includes group-level protected environments,
|
||||
|
|
|
|||
|
|
@ -335,7 +335,7 @@ locked. Projects can only be unlocked by purchasing more storage subscription un
|
|||
|
||||
Prerequisite:
|
||||
|
||||
- You must have at least the Owner role.
|
||||
- You must have the Owner role.
|
||||
|
||||
You can purchase a storage subscription for your personal or group namespace.
|
||||
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ for the subgroups and projects where you don't want to use it.
|
|||
|
||||
Prerequisites:
|
||||
|
||||
- You must have at least the Owner role for the group.
|
||||
- You must have the Owner role for the group.
|
||||
|
||||
To enable Auto DevOps for a group:
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,12 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
# Secret Detection post-processing and revocation **(FREE SAAS)**
|
||||
|
||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/4639) in GitLab 13.6.
|
||||
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/4639) in GitLab 13.6.
|
||||
> - [Disabled by default for GitLab personal access tokens](https://gitlab.com/gitlab-org/gitlab/-/issues/371658) in GitLab 15.6 [with a flag](../../../administration/feature_flags.md) named `gitlab_pat_auto_revocation`. Available to GitLab.com only.
|
||||
|
||||
FLAG:
|
||||
By default, auto revocation of GitLab personal access tokens is not available. To opt-in on GitLab.com,
|
||||
please reach out to GitLab support.
|
||||
|
||||
GitLab supports running post-processing hooks after detecting a secret. These
|
||||
hooks can perform actions, like notifying the cloud service that issued the secret.
|
||||
|
|
@ -16,7 +21,7 @@ The cloud provider can then confirm the credentials and take remediation actions
|
|||
- Reissuing a secret.
|
||||
- Notifying the creator of the secret.
|
||||
|
||||
GitLab SaaS supports post-processing for Amazon Web Services (AWS).
|
||||
GitLab SaaS supports post-processing for [GitLab personal access tokens](../../profile/personal_access_tokens.md) and Amazon Web Services (AWS).
|
||||
Post-processing workflows vary by supported cloud providers.
|
||||
|
||||
Post-processing is limited to a project's default branch. The epic
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ If you don't want to wait, you can remove a group immediately.
|
|||
|
||||
Prerequisites:
|
||||
|
||||
- You must have at least the Owner role for a group.
|
||||
- You must have the Owner role for a group.
|
||||
- You have [marked the group for deletion](#remove-a-group).
|
||||
|
||||
To immediately remove a group marked for deletion:
|
||||
|
|
|
|||
|
|
@ -222,21 +222,26 @@ References to pull requests and issues are preserved. Each imported repository m
|
|||
[visibility level is restricted](../../public_access.md#restrict-use-of-public-or-internal-projects), in which case it
|
||||
defaults to the default project visibility.
|
||||
|
||||
### Branch protection rules
|
||||
### Branch protection rules and project settings
|
||||
|
||||
Supported GitHub branch protection rules are mapped to GitLab branch protection rules or project-wide GitLab settings when they are imported:
|
||||
When they are imported, supported GitHub branch protection rules are mapped to either:
|
||||
|
||||
- GitHub rule **Require conversation resolution before merging** for the project's default branch is mapped to the [**All threads must be resolved** GitLab setting](../../discussions/index.md#prevent-merge-unless-all-threads-are-resolved). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/371110) in GitLab 15.5.
|
||||
- GitHub rule **Require a pull request before merging** is mapped to the **No one** option in the **Allowed to push** list of the branch protection rule. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/370951) in GitLab 15.5.
|
||||
- GitHub rule **Require a pull request before merging - Require review from Code Owners** is mapped to the
|
||||
[**Code owner approval** branch protection rule](../protected_branches.md#require-code-owner-approval-on-a-protected-branch). Requires GitLab Premium or higher.
|
||||
[Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/376683) in GitLab 15.6.
|
||||
- GitHub rule **Require signed commits** for the project's default branch is mapped to the **Reject unsigned commits** GitLab push rule. Requires GitLab Premium or higher.
|
||||
[Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/370949) in GitLab 15.5.
|
||||
- GitHub rule **Allow force pushes - Everyone** is mapped to the [**Allowed to force push** branch protection rule](../protected_branches.md#allow-force-push-on-a-protected-branch). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/370943) in GitLab 15.6.
|
||||
- GitHub rule **Allow force pushes - Specify who can force push** is proposed in issue [370945](https://gitlab.com/gitlab-org/gitlab/-/issues/370945).
|
||||
- Support for GitHub rule **Require status checks to pass before merging** was proposed in issue [370948](https://gitlab.com/gitlab-org/gitlab/-/issues/370948). However, this rule cannot be translated during project import into GitLab due to technical difficulties.
|
||||
You can still create [status checks](../merge_requests/status_checks.md) in GitLab yourself.
|
||||
- GitLab branch protection rules.
|
||||
- Project-wide GitLab settings.
|
||||
|
||||
| GitHub rule | GitLab rule | Introduced in |
|
||||
|:------------------------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------------------------------------------------------------------|
|
||||
| **Require conversation resolution before merging** for the project's default branch | **All threads must be resolved** [project setting](../../discussions/index.md#prevent-merge-unless-all-threads-are-resolved) | [GitLab 15.5](https://gitlab.com/gitlab-org/gitlab/-/issues/371110) |
|
||||
| **Require a pull request before merging** | **No one** option in the **Allowed to push** list of [branch protection settings](../protected_branches.md#configure-a-protected-branch) | [GitLab 15.5](https://gitlab.com/gitlab-org/gitlab/-/issues/370951) |
|
||||
| **Require signed commits** for the project's default branch | **Reject unsigned commits** GitLab [push rule](../repository/push_rules.md#prevent-unintended-consequences) **(PREMIUM)** | [GitLab 15.5](https://gitlab.com/gitlab-org/gitlab/-/issues/370949) |
|
||||
| **Allow force pushes - Everyone** | **Allowed to force push** [branch protection setting](../protected_branches.md#allow-force-push-on-a-protected-branch) | [GitLab 15.6](https://gitlab.com/gitlab-org/gitlab/-/issues/370943) |
|
||||
| **Require a pull request before merging - Require review from Code Owners** | **Require approval from code owners** [branch protection setting](../protected_branches.md#require-code-owner-approval-on-a-protected-branch) **(PREMIUM)** | [GitLab 15.6](https://gitlab.com/gitlab-org/gitlab/-/issues/376683) |
|
||||
|
||||
Mapping GitHub rule **Require status checks to pass before merging** to
|
||||
[external status checks](../merge_requests/status_checks.md) was considered in issue
|
||||
[370948](https://gitlab.com/gitlab-org/gitlab/-/issues/370948). However, this rule is not imported during project import
|
||||
into GitLab due to technical difficulties. You can still create [external status checks](../merge_requests/status_checks.md)
|
||||
manually.
|
||||
|
||||
## Alternative way to import notes and diff notes
|
||||
|
||||
|
|
|
|||
|
|
@ -62,6 +62,41 @@ You can configure a webhook for a group or a project.
|
|||
1. Optional. Clear the **Enable SSL verification** checkbox to disable [SSL verification](index.md#manage-ssl-verification).
|
||||
1. Select **Add webhook**.
|
||||
|
||||
## Mask sensitive portions of webhook URLs
|
||||
|
||||
> Introduced in GitLab 15.6 [with a flag](../../../administration/feature_flags.md) named `webhook_form_mask_url`. Disabled by default.
|
||||
|
||||
FLAG:
|
||||
On self-managed GitLab, by default this feature is not available. To make it available, ask an administrator to [enable the feature flag](../../../administration/feature_flags.md) named `webhook_form_mask_url`. On GitLab.com, this feature is not available.
|
||||
|
||||
You can define and mask sensitive portions of webhook URLs and replace them
|
||||
with configured values any number of times when webhooks are executed.
|
||||
Sensitive portions do not get logged and are encrypted at rest in the database.
|
||||
|
||||
To mask sensitive portions of the webhook URL:
|
||||
|
||||
1. In your project or group, on the left sidebar, select **Settings > Webhooks**.
|
||||
1. In **URL**, enter the full webhook URL.
|
||||
1. Select **Mask portions of URL**.
|
||||
1. In **Sensitive portion of URL**, enter the portion you want to mask.
|
||||
1. In **How it looks in the UI**, enter the masking value.
|
||||
|
||||
To interpolate sensitive portions for each webhook, use `url_variables`.
|
||||
For example, if a webhook has the following URL:
|
||||
|
||||
```plaintext
|
||||
https://{subdomain}.example.com/{path}?key={value}
|
||||
```
|
||||
|
||||
You must define the following variables:
|
||||
|
||||
- `subdomain`
|
||||
- `path`
|
||||
- `value`
|
||||
|
||||
Variable names can contain only lowercase letters (`a-z`), numbers (`0-9`), or underscores (`_`).
|
||||
You can define URL variables directly using the REST API.
|
||||
|
||||
## Configure your webhook receiver endpoint
|
||||
|
||||
Webhook receiver endpoints should be fast and stable.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ type: reference, concepts
|
|||
disqus_identifier: 'https://docs.gitlab.com/ee/user/project/merge_requests/status_checks.html'
|
||||
---
|
||||
|
||||
# External Status Checks **(ULTIMATE)**
|
||||
# External status checks **(ULTIMATE)**
|
||||
|
||||
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3869) in GitLab 14.0, disabled behind the `:ff_external_status_checks` feature flag.
|
||||
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/320783) in GitLab 14.1.
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ overrides it.
|
|||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/221014) in GitLab 13.6.
|
||||
|
||||
Users with at least the Owner role of groups and subgroups can configure the default branch name for a group:
|
||||
Users with the Owner role of groups and subgroups can configure the default branch name for a group:
|
||||
|
||||
1. Go to the group **Settings > Repository**.
|
||||
1. Expand **Default branch**.
|
||||
|
|
|
|||
|
|
@ -270,7 +270,7 @@ You can mark a project to be deleted.
|
|||
|
||||
Prerequisite:
|
||||
|
||||
- You must have at least the Owner role for a project.
|
||||
- You must have the Owner role for a project.
|
||||
|
||||
To delete a project:
|
||||
|
||||
|
|
@ -308,7 +308,7 @@ If you don't want to wait, you can delete a project immediately.
|
|||
|
||||
Prerequisites:
|
||||
|
||||
- You must have at least the Owner role for a project.
|
||||
- You must have the Owner role for a project.
|
||||
- You have [marked the project for deletion](#delete-a-project).
|
||||
|
||||
To immediately delete a project marked for deletion:
|
||||
|
|
|
|||
|
|
@ -45552,6 +45552,9 @@ msgstr ""
|
|||
msgid "We don't have enough data to show this stage."
|
||||
msgstr ""
|
||||
|
||||
msgid "We found your token in a public project and have automatically revoked it to protect your account."
|
||||
msgstr ""
|
||||
|
||||
msgid "We have found the following errors:"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -8,12 +8,12 @@ module QA
|
|||
def perform_before_hooks
|
||||
if QA::Runtime::Env.admin_personal_access_token.present?
|
||||
QA::Resource::PersonalAccessTokenCache.set_token_for_username(QA::Runtime::User.admin_username,
|
||||
QA::Runtime::Env.admin_personal_access_token)
|
||||
QA::Runtime::Env.admin_personal_access_token)
|
||||
end
|
||||
|
||||
if QA::Runtime::Env.personal_access_token.present? && QA::Runtime::Env.user_username.present?
|
||||
QA::Resource::PersonalAccessTokenCache.set_token_for_username(QA::Runtime::Env.user_username,
|
||||
QA::Runtime::Env.personal_access_token)
|
||||
QA::Runtime::Env.personal_access_token)
|
||||
end
|
||||
|
||||
# The login page could take some time to load the first time it is visited.
|
||||
|
|
@ -22,6 +22,9 @@ module QA
|
|||
QA::Support::Retrier.retry_on_exception do
|
||||
QA::Runtime::Browser.visit(:gitlab, QA::Page::Main::Login)
|
||||
end
|
||||
return unless QA::Runtime::Env.allow_local_requests?
|
||||
|
||||
Runtime::ApplicationSettings.set_application_settings(allow_local_requests_from_web_hooks_and_services: true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ module QA
|
|||
def click_configure_it_later_button
|
||||
# TO DO: Investigate why button does not appear sometimes:
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/382698
|
||||
return unless has_element?(:configure_it_later_button)
|
||||
return unless has_element?(:configure_it_later_button, wait: 20)
|
||||
|
||||
click_element :configure_it_later_button
|
||||
wait_until(max_duration: 10, message: "Waiting for create a group page") do
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ module QA
|
|||
api_client.personal_access_token
|
||||
end
|
||||
|
||||
attribute :gitlab_address do
|
||||
attribute :source_gitlab_address do
|
||||
QA::Runtime::Scenario.gitlab_address
|
||||
end
|
||||
|
||||
|
|
@ -28,7 +28,7 @@ module QA
|
|||
|
||||
Page::Group::New.perform do |group|
|
||||
group.switch_to_import_tab
|
||||
group.connect_gitlab_instance(gitlab_address, import_access_token)
|
||||
group.connect_gitlab_instance(source_gitlab_address, import_access_token)
|
||||
end
|
||||
|
||||
Page::Group::BulkImport.perform do |import_page|
|
||||
|
|
@ -50,7 +50,7 @@ module QA
|
|||
def api_post_body
|
||||
{
|
||||
configuration: {
|
||||
url: gitlab_address,
|
||||
url: source_gitlab_address,
|
||||
access_token: import_access_token
|
||||
},
|
||||
entities: [
|
||||
|
|
@ -93,7 +93,7 @@ module QA
|
|||
|
||||
# override transformation only for /bulk_imports endpoint which doesn't have web_url in response and
|
||||
# ignore others so import_id is not overwritten incorrectly
|
||||
api_resource[:web_url] = "#{gitlab_address}/#{full_path}"
|
||||
api_resource[:web_url] = "#{source_gitlab_address}/#{full_path}"
|
||||
api_resource[:import_id] = api_resource[:id]
|
||||
api_resource
|
||||
end
|
||||
|
|
|
|||
|
|
@ -493,6 +493,10 @@ module QA
|
|||
enabled?(ENV['QA_USE_PUBLIC_IP_API'], default: false)
|
||||
end
|
||||
|
||||
def allow_local_requests?
|
||||
enabled?(ENV['QA_ALLOW_LOCAL_REQUESTS'], default: false)
|
||||
end
|
||||
|
||||
def chrome_default_download_path
|
||||
ENV['DEFAULT_CHROME_DOWNLOAD_PATH']
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
module Scenario
|
||||
module Test
|
||||
module Integration
|
||||
class Import < Test::Instance::All
|
||||
tags :import
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,70 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
RSpec.describe 'Manage', :reliable, :requires_admin, product_group: :import do
|
||||
describe 'Gitlab migration' do
|
||||
let(:import_wait_duration) { { max_duration: 300, sleep_interval: 2 } }
|
||||
let(:admin_api_client) { Runtime::API::Client.as_admin }
|
||||
let(:user) do
|
||||
Resource::User.fabricate_via_api! do |usr|
|
||||
usr.api_client = admin_api_client
|
||||
usr.hard_delete_on_api_removal = true
|
||||
end
|
||||
end
|
||||
|
||||
let(:api_client) { Runtime::API::Client.new(user: user) }
|
||||
|
||||
let(:sandbox) do
|
||||
Resource::Sandbox.fabricate_via_api! do |group|
|
||||
group.api_client = admin_api_client
|
||||
end
|
||||
end
|
||||
|
||||
let(:destination_group) do
|
||||
Resource::Group.fabricate_via_api! do |group|
|
||||
group.api_client = api_client
|
||||
group.sandbox = sandbox
|
||||
group.path = "destination-group-for-import-#{SecureRandom.hex(4)}"
|
||||
end
|
||||
end
|
||||
|
||||
let(:source_group) do
|
||||
Resource::Group.fabricate_via_api! do |group|
|
||||
group.api_client = api_client
|
||||
group.sandbox = sandbox
|
||||
group.path = "source-group-for-import-#{SecureRandom.hex(4)}"
|
||||
group.avatar = File.new('qa/fixtures/designs/tanuki.jpg', 'r')
|
||||
end
|
||||
end
|
||||
|
||||
let(:imported_group) do
|
||||
Resource::BulkImportGroup.fabricate_via_api! do |group|
|
||||
group.api_client = api_client
|
||||
group.sandbox = destination_group
|
||||
group.source_group = source_group
|
||||
end
|
||||
end
|
||||
|
||||
let(:import_failures) do
|
||||
imported_group.import_details.sum([]) { |details| details[:failures] }
|
||||
end
|
||||
|
||||
before do
|
||||
sandbox.add_member(user, Resource::Members::AccessLevel::MAINTAINER)
|
||||
end
|
||||
|
||||
after do |example|
|
||||
# Checking for failures in the test currently makes test very flaky due to catching unrelated failures
|
||||
# Log failures for easier debugging
|
||||
Runtime::Logger.warn("Import failures: #{import_failures}") if example.exception && !import_failures.empty?
|
||||
ensure
|
||||
user.remove_via_api!
|
||||
end
|
||||
RSpec.describe "Manage", :reliable, product_group: :import do
|
||||
include_context "with gitlab group migration"
|
||||
|
||||
describe "Gitlab migration" do
|
||||
context 'with subgroups and labels' do
|
||||
let(:subgroup) do
|
||||
Resource::Group.fabricate_via_api! do |group|
|
||||
group.api_client = api_client
|
||||
group.api_client = source_admin_api_client
|
||||
group.sandbox = source_group
|
||||
group.path = "subgroup-for-import-#{SecureRandom.hex(4)}"
|
||||
end
|
||||
|
|
@ -80,12 +24,12 @@ module QA
|
|||
|
||||
before do
|
||||
Resource::GroupLabel.fabricate_via_api! do |label|
|
||||
label.api_client = api_client
|
||||
label.api_client = source_admin_api_client
|
||||
label.group = source_group
|
||||
label.title = "source-group-#{SecureRandom.hex(4)}"
|
||||
end
|
||||
Resource::GroupLabel.fabricate_via_api! do |label|
|
||||
label.api_client = api_client
|
||||
label.api_client = source_admin_api_client
|
||||
label.group = subgroup
|
||||
label.title = "subgroup-#{SecureRandom.hex(4)}"
|
||||
end
|
||||
|
|
@ -112,7 +56,7 @@ module QA
|
|||
context 'with milestones and badges' do
|
||||
let(:source_milestone) do
|
||||
Resource::GroupMilestone.fabricate_via_api! do |milestone|
|
||||
milestone.api_client = api_client
|
||||
milestone.api_client = source_admin_api_client
|
||||
milestone.group = source_group
|
||||
end
|
||||
end
|
||||
|
|
@ -121,7 +65,7 @@ module QA
|
|||
source_milestone
|
||||
|
||||
Resource::GroupBadge.fabricate_via_api! do |badge|
|
||||
badge.api_client = api_client
|
||||
badge.api_client = source_admin_api_client
|
||||
badge.group = source_group
|
||||
badge.link_url = "http://example.com/badge"
|
||||
badge.image_url = "http://shields.io/badge"
|
||||
|
|
|
|||
|
|
@ -1,77 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
describe 'Manage', :requires_admin, :reliable, product_group: :import do
|
||||
describe 'Gitlab migration' do
|
||||
let!(:admin_api_client) { Runtime::API::Client.as_admin }
|
||||
let!(:user) do
|
||||
Resource::User.fabricate_via_api! do |usr|
|
||||
usr.api_client = admin_api_client
|
||||
usr.hard_delete_on_api_removal = true
|
||||
end
|
||||
end
|
||||
|
||||
let!(:api_client) { Runtime::API::Client.new(user: user) }
|
||||
let!(:personal_access_token) { api_client.personal_access_token }
|
||||
|
||||
let(:sandbox) do
|
||||
Resource::Sandbox.fabricate_via_api! do |group|
|
||||
group.api_client = admin_api_client
|
||||
end
|
||||
end
|
||||
|
||||
let(:source_group) do
|
||||
Resource::Sandbox.fabricate! do |group|
|
||||
group.api_client = api_client
|
||||
group.path = "source-group-for-import-#{SecureRandom.hex(4)}"
|
||||
end
|
||||
end
|
||||
|
||||
let(:imported_group) do
|
||||
Resource::BulkImportGroup.init do |group|
|
||||
group.api_client = api_client
|
||||
group.sandbox = sandbox
|
||||
group.source_group = source_group
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
sandbox.add_member(user, Resource::Members::AccessLevel::MAINTAINER)
|
||||
|
||||
Flow::Login.sign_in(as: user)
|
||||
|
||||
source_group
|
||||
|
||||
Page::Main::Menu.perform(&:go_to_create_group)
|
||||
Page::Group::New.perform do |group|
|
||||
group.switch_to_import_tab
|
||||
group.connect_gitlab_instance(Runtime::Scenario.gitlab_address, personal_access_token)
|
||||
end
|
||||
end
|
||||
|
||||
after do
|
||||
user.remove_via_api!
|
||||
end
|
||||
|
||||
it(
|
||||
'imports group from UI',
|
||||
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347862',
|
||||
issue_1: 'https://gitlab.com/gitlab-org/gitlab/-/issues/331252',
|
||||
issue_2: 'https://gitlab.com/gitlab-org/gitlab/-/issues/333678',
|
||||
issue_3: 'https://gitlab.com/gitlab-org/gitlab/-/issues/332351',
|
||||
except: { job: 'instance-image-slow-network' }
|
||||
) do
|
||||
Page::Group::BulkImport.perform do |import_page|
|
||||
import_page.import_group(imported_group.path, imported_group.sandbox.path)
|
||||
|
||||
expect(import_page).to have_imported_group(imported_group.path, wait: 300)
|
||||
|
||||
imported_group.reload!.visit!
|
||||
Page::Group::Show.perform do |group|
|
||||
expect(group).to have_content(imported_group.path)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
describe 'Manage', :reliable, product_group: :import do
|
||||
describe 'Gitlab migration' do
|
||||
include_context "with gitlab group migration"
|
||||
|
||||
let!(:imported_group) do
|
||||
Resource::BulkImportGroup.init do |group|
|
||||
group.api_client = api_client
|
||||
group.sandbox = target_sandbox
|
||||
group.source_group = source_group
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
Flow::Login.sign_in(as: user)
|
||||
|
||||
Page::Main::Menu.perform(&:go_to_create_group)
|
||||
Page::Group::New.perform do |group|
|
||||
group.switch_to_import_tab
|
||||
group.connect_gitlab_instance(source_gitlab_address, source_admin_api_client.personal_access_token)
|
||||
end
|
||||
end
|
||||
|
||||
it 'imports group from UI', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347862' do
|
||||
Page::Group::BulkImport.perform do |import_page|
|
||||
import_page.import_group(source_group.path, target_sandbox.path)
|
||||
|
||||
expect(import_page).to have_imported_group(imported_group.path, wait: 300)
|
||||
|
||||
imported_group.reload!.visit!
|
||||
Page::Group::Show.perform do |group|
|
||||
expect(group).to have_content(imported_group.path)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
RSpec.shared_context(
|
||||
'with gitlab group migration',
|
||||
:import,
|
||||
:orchestrated,
|
||||
requires_admin: 'creates a user via API'
|
||||
) do
|
||||
let!(:import_wait_duration) { { max_duration: 300, sleep_interval: 2 } }
|
||||
|
||||
# source instance objects
|
||||
#
|
||||
let!(:source_gitlab_address) { ENV["QA_IMPORT_SOURCE_URL"] || raise("QA_IMPORT_SOURCE_URL is required!") }
|
||||
let!(:source_admin_api_client) do
|
||||
Runtime::API::Client.new(
|
||||
source_gitlab_address,
|
||||
personal_access_token: Runtime::Env.admin_personal_access_token || raise("Admin access token missing!"),
|
||||
is_new_session: false
|
||||
)
|
||||
end
|
||||
let!(:source_admin_user) { Resource::User.fabricate_via_api! { |usr| usr.api_client = source_admin_api_client } }
|
||||
let!(:source_group) do
|
||||
Resource::Sandbox.fabricate_via_api! do |group|
|
||||
group.api_client = source_admin_api_client
|
||||
group.path = "source-group-for-import-#{SecureRandom.hex(4)}"
|
||||
group.avatar = File.new("qa/fixtures/designs/tanuki.jpg", "r")
|
||||
end
|
||||
end
|
||||
|
||||
# target instance objects
|
||||
#
|
||||
let!(:admin_api_client) { Runtime::API::Client.as_admin }
|
||||
let!(:user) { Resource::User.fabricate_via_api! { |usr| usr.api_client = admin_api_client } }
|
||||
let!(:api_client) { Runtime::API::Client.new(user: user) }
|
||||
let!(:target_sandbox) do
|
||||
Resource::Sandbox.fabricate_via_api! do |group|
|
||||
group.api_client = admin_api_client
|
||||
end
|
||||
end
|
||||
|
||||
let(:imported_group) do
|
||||
Resource::BulkImportGroup.fabricate_via_api! do |group|
|
||||
group.api_client = api_client
|
||||
group.sandbox = target_sandbox
|
||||
group.source_group = source_group
|
||||
group.source_gitlab_address = source_gitlab_address
|
||||
group.import_access_token = source_admin_api_client.personal_access_token
|
||||
end
|
||||
end
|
||||
|
||||
let(:import_failures) do
|
||||
imported_group.import_details.sum([]) { |details| details[:failures] }
|
||||
end
|
||||
|
||||
before do
|
||||
target_sandbox.add_member(user, Resource::Members::AccessLevel::OWNER)
|
||||
source_admin_user.set_public_email
|
||||
end
|
||||
|
||||
after do |example|
|
||||
# Checking for failures in the test currently makes test very flaky due to catching unrelated failures
|
||||
# Log failures for easier debugging
|
||||
Runtime::Logger.warn("Import failures: #{import_failures}") if example.exception && !import_failures.empty?
|
||||
rescue QA::Resource::Base::NoValueError
|
||||
# rescue when import did not happen at all
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -37,6 +37,7 @@ describe('ScopeNavigation', () => {
|
|||
const findGlNav = () => wrapper.findComponent(GlNav);
|
||||
const findGlNavItems = () => wrapper.findAllComponents(GlNavItem);
|
||||
const findGlNavItemActive = () => findGlNavItems().wrappers.filter((w) => w.attributes('active'));
|
||||
const findGlNavItemActiveLabel = () => findGlNavItemActive().at(0).findAll('span').at(0).text();
|
||||
const findGlNavItemActiveCount = () => findGlNavItemActive().at(0).findAll('span').at(1);
|
||||
|
||||
describe('scope navigation', () => {
|
||||
|
|
@ -64,17 +65,35 @@ describe('ScopeNavigation', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('scope navigation sets proper state', () => {
|
||||
describe('scope navigation sets proper state with url scope set', () => {
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('sets proper class to active item', () => {
|
||||
it('correct item is active', () => {
|
||||
expect(findGlNavItemActive()).toHaveLength(1);
|
||||
expect(findGlNavItemActiveLabel()).toBe('Issues');
|
||||
});
|
||||
|
||||
it('active item', () => {
|
||||
it('correct active item count', () => {
|
||||
expect(findGlNavItemActiveCount().text()).toBe('2.4K');
|
||||
});
|
||||
|
||||
it('correct active item count is highlighted', () => {
|
||||
expect(findGlNavItemActiveCount().classes('gl-text-gray-900')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('scope navigation sets proper state with NO url scope set', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
urlQuery: {},
|
||||
});
|
||||
});
|
||||
|
||||
it('correct item is active', () => {
|
||||
expect(findGlNavItems().at(0).attributes('active')).toBe('true');
|
||||
expect(findGlNavItemActiveLabel()).toBe('Projects');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -269,6 +269,38 @@ RSpec.describe Emails::Profile do
|
|||
is_expected.to have_body_text /#{token.name}/
|
||||
end
|
||||
|
||||
it 'wont include the revocation reason' do
|
||||
is_expected.not_to have_body_text %r{We found your token in a public project and have automatically revoked it to protect your account.$}
|
||||
end
|
||||
|
||||
it 'includes the email reason' do
|
||||
is_expected.to have_body_text %r{You're receiving this email because of your account on <a .*>localhost<\/a>}
|
||||
end
|
||||
end
|
||||
|
||||
context 'when source is provided' do
|
||||
subject { Notify.access_token_revoked_email(user, token.name, 'secret_detection') }
|
||||
|
||||
it_behaves_like 'an email sent from GitLab'
|
||||
it_behaves_like 'it should not have Gmail Actions links'
|
||||
it_behaves_like 'a user cannot unsubscribe through footer link'
|
||||
|
||||
it 'is sent to the user' do
|
||||
is_expected.to deliver_to user.email
|
||||
end
|
||||
|
||||
it 'has the correct subject' do
|
||||
is_expected.to have_subject /^A personal access token has been revoked$/i
|
||||
end
|
||||
|
||||
it 'provides the names of the token' do
|
||||
is_expected.to have_body_text /#{token.name}/
|
||||
end
|
||||
|
||||
it 'includes the revocation reason' do
|
||||
is_expected.to have_body_text %r{We found your token in a public project and have automatically revoked it to protect your account.$}
|
||||
end
|
||||
|
||||
it 'includes the email reason' do
|
||||
is_expected.to have_body_text %r{You're receiving this email because of your account on <a .*>localhost<\/a>}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -138,17 +138,48 @@ RSpec.describe Ci::SecureFile do
|
|||
end
|
||||
|
||||
describe '#update_metadata!' do
|
||||
it 'assigns the expected metadata when a parsable file is supplied' do
|
||||
it 'assigns the expected metadata when a parsable .cer file is supplied' do
|
||||
file = create(:ci_secure_file, name: 'file1.cer',
|
||||
file: CarrierWaveStringFile.new(fixture_file('ci_secure_files/sample.cer')))
|
||||
file.update_metadata!
|
||||
|
||||
file.reload
|
||||
|
||||
expect(file.expires_at).to eq(DateTime.parse('2022-04-26 19:20:40'))
|
||||
expect(file.metadata['id']).to eq('33669367788748363528491290218354043267')
|
||||
expect(file.metadata['issuer']['CN']).to eq('Apple Worldwide Developer Relations Certification Authority')
|
||||
expect(file.metadata['subject']['OU']).to eq('N7SYAN8PX8')
|
||||
end
|
||||
|
||||
it 'assigns the expected metadata when a parsable .p12 file is supplied' do
|
||||
file = create(:ci_secure_file, name: 'file1.p12',
|
||||
file: CarrierWaveStringFile.new(fixture_file('ci_secure_files/sample.p12')))
|
||||
file.update_metadata!
|
||||
|
||||
file.reload
|
||||
|
||||
expect(file.expires_at).to eq(DateTime.parse('2022-09-21 14:56:00'))
|
||||
expect(file.metadata['id']).to eq('75949910542696343243264405377658443914')
|
||||
expect(file.metadata['issuer']['CN']).to eq('Apple Worldwide Developer Relations Certification Authority')
|
||||
expect(file.metadata['subject']['OU']).to eq('N7SYAN8PX8')
|
||||
end
|
||||
|
||||
it 'assigns the expected metadata when a parsable .mobileprovision file is supplied' do
|
||||
file = create(:ci_secure_file, name: 'file1.mobileprovision',
|
||||
file: CarrierWaveStringFile.new(
|
||||
fixture_file('ci_secure_files/sample.mobileprovision')
|
||||
))
|
||||
file.update_metadata!
|
||||
|
||||
file.reload
|
||||
|
||||
expect(file.expires_at).to eq(DateTime.parse('2023-08-01 23:15:13'))
|
||||
expect(file.metadata['id']).to eq('6b9fcce1-b9a9-4b37-b2ce-ec4da2044abf')
|
||||
expect(file.metadata['platforms'].first).to eq('iOS')
|
||||
expect(file.metadata['app_name']).to eq('iOS Demo')
|
||||
expect(file.metadata['app_id']).to eq('match Development com.gitlab.ios-demo')
|
||||
end
|
||||
|
||||
it 'logs an error when something goes wrong with the file parsing' do
|
||||
corrupt_file = create(:ci_secure_file, name: 'file1.cer', file: CarrierWaveStringFile.new('11111111'))
|
||||
message = 'Validation failed: Metadata must be a valid json schema - not enough data.'
|
||||
|
|
|
|||
|
|
@ -361,8 +361,14 @@ RSpec.describe NotificationService, :mailer do
|
|||
|
||||
subject(:notification_service) { notification.access_token_revoked(user, pat.name) }
|
||||
|
||||
it 'sends email to the token owner' do
|
||||
expect { notification_service }.to have_enqueued_email(user, pat.name, mail: "access_token_revoked_email")
|
||||
it 'sends email to the token owner without source' do
|
||||
expect { notification_service }.to have_enqueued_email(user, pat.name, nil, mail: "access_token_revoked_email")
|
||||
end
|
||||
|
||||
it 'sends email to the token owner with source' do
|
||||
expect do
|
||||
notification.access_token_revoked(user, pat.name, 'secret_detection')
|
||||
end.to have_enqueued_email(user, pat.name, 'secret_detection', mail: "access_token_revoked_email")
|
||||
end
|
||||
|
||||
context 'when user is not allowed to receive notifications' do
|
||||
|
|
|
|||
|
|
@ -8,7 +8,12 @@ RSpec.describe PersonalAccessTokens::RevokeService do
|
|||
it { expect(service.token.revoked?).to be true }
|
||||
|
||||
it 'logs the event' do
|
||||
expect(Gitlab::AppLogger).to receive(:info).with(/PAT REVOCATION: revoked_by: '#{current_user.username}', revoked_for: '#{token.user.username}', token_id: '\d+'/)
|
||||
expect(Gitlab::AppLogger).to receive(:info).with(
|
||||
class: described_class.to_s,
|
||||
message: 'PAT Revoked',
|
||||
revoked_by: revoked_by,
|
||||
revoked_for: token.user.username,
|
||||
token_id: token.id)
|
||||
|
||||
subject
|
||||
end
|
||||
|
|
@ -29,7 +34,9 @@ RSpec.describe PersonalAccessTokens::RevokeService do
|
|||
let_it_be(:current_user) { create(:admin) }
|
||||
let_it_be(:token) { create(:personal_access_token) }
|
||||
|
||||
it_behaves_like 'a successfully revoked token'
|
||||
it_behaves_like 'a successfully revoked token' do
|
||||
let(:revoked_by) { current_user.username }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when admin mode is disabled' do
|
||||
|
|
@ -52,7 +59,38 @@ RSpec.describe PersonalAccessTokens::RevokeService do
|
|||
context 'token belongs to current_user' do
|
||||
let_it_be(:token) { create(:personal_access_token, user: current_user) }
|
||||
|
||||
it_behaves_like 'a successfully revoked token'
|
||||
it_behaves_like 'a successfully revoked token' do
|
||||
let(:revoked_by) { current_user.username }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when source' do
|
||||
let(:service) { described_class.new(nil, token: token, source: source) }
|
||||
|
||||
let_it_be(:current_user) { nil }
|
||||
|
||||
context 'when source is valid' do
|
||||
let_it_be(:source) { 'secret_detection' }
|
||||
let_it_be(:token) { create(:personal_access_token) }
|
||||
|
||||
it_behaves_like 'a successfully revoked token' do
|
||||
let(:revoked_by) { 'secret_detection' }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when source is invalid' do
|
||||
let_it_be(:source) { 'external_request' }
|
||||
let_it_be(:token) { create(:personal_access_token) }
|
||||
|
||||
it_behaves_like 'an unsuccessfully revoked token'
|
||||
end
|
||||
|
||||
context 'when source is missing' do
|
||||
let_it_be(:source) { nil }
|
||||
let_it_be(:token) { create(:personal_access_token) }
|
||||
|
||||
it_behaves_like 'an unsuccessfully revoked token'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in New Issue