Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-02-07 09:08:04 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-02-07 09:08:04 +0300
commit95a6825e19809cae0cee779c0ca3667b233a58f4 (patch)
treee5cb19ea02021cf67be33cfc30a5c4f59ccf10d5
parentfcfafe81d1f1aa442c5a5c93cd27b5f5b798cb90 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/invite_members/components/invite_members_modal.vue6
-rw-r--r--app/assets/javascripts/invite_members/components/invite_modal_base.vue1
-rw-r--r--app/assets/javascripts/invite_members/init_invite_members_modal.js3
-rw-r--r--app/helpers/issuables_helper.rb2
-rw-r--r--app/views/groups/_invite_members_modal.html.haml2
-rw-r--r--doc/api/graphql/reference/index.md17
-rw-r--r--doc/architecture/blueprints/gitlab_agent_deployments/index.md403
-rw-r--r--doc/ci/yaml/index.md2
-rw-r--r--lib/api/helpers/members_helpers.rb4
-rw-r--r--lib/api/invitations.rb2
-rw-r--r--lib/gitlab/gitaly_client/operation_service.rb111
-rw-r--r--locale/gitlab.pot6
-rw-r--r--qa/Gemfile2
-rw-r--r--qa/Gemfile.lock4
-rw-r--r--spec/controllers/groups_controller_spec.rb15
-rw-r--r--spec/frontend/invite_members/components/invite_members_modal_spec.js17
-rw-r--r--spec/lib/gitlab/gitaly_client/operation_service_spec.rb52
-rw-r--r--spec/models/group_spec.rb16
18 files changed, 531 insertions, 134 deletions
diff --git a/app/assets/javascripts/invite_members/components/invite_members_modal.vue b/app/assets/javascripts/invite_members/components/invite_members_modal.vue
index 41f1389400a..eff9bf33cc4 100644
--- a/app/assets/javascripts/invite_members/components/invite_members_modal.vue
+++ b/app/assets/javascripts/invite_members/components/invite_members_modal.vue
@@ -107,6 +107,11 @@ export default {
required: false,
default: () => ({}),
},
+ activeTrialDataset: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
reloadPageOnSubmit: {
type: Boolean,
required: false,
@@ -405,6 +410,7 @@ export default {
:new-users-to-invite="newUsersToInvite"
:root-group-id="rootId"
:users-limit-dataset="usersLimitDataset"
+ :active-trial-dataset="activeTrialDataset"
:full-path="fullPath"
@reset="resetFields"
@submit="sendInvite"
diff --git a/app/assets/javascripts/invite_members/components/invite_modal_base.vue b/app/assets/javascripts/invite_members/components/invite_modal_base.vue
index a5e0db8179a..20859422e2c 100644
--- a/app/assets/javascripts/invite_members/components/invite_modal_base.vue
+++ b/app/assets/javascripts/invite_members/components/invite_modal_base.vue
@@ -296,6 +296,7 @@ export default {
</div>
<slot name="alert"></slot>
+ <slot name="active-trial-alert"></slot>
<gl-form-group
:label="labelSearchField"
diff --git a/app/assets/javascripts/invite_members/init_invite_members_modal.js b/app/assets/javascripts/invite_members/init_invite_members_modal.js
index 842ab07f368..4f539cd8756 100644
--- a/app/assets/javascripts/invite_members/init_invite_members_modal.js
+++ b/app/assets/javascripts/invite_members/init_invite_members_modal.js
@@ -41,6 +41,9 @@ export default (function initInviteMembersModal() {
usersLimitDataset: convertObjectPropsToCamelCase(
JSON.parse(el.dataset.usersLimitDataset || '{}'),
),
+ activeTrialDataset: convertObjectPropsToCamelCase(
+ JSON.parse(el.dataset.activeTrialDataset || '{}'),
+ ),
reloadPageOnSubmit: parseBoolean(el.dataset.reloadPageOnSubmit),
},
}),
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index cec506405b7..62023c6da74 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -308,7 +308,7 @@ module IssuablesHelper
end
def issuables_count_for_state(issuable_type, state)
- Gitlab::IssuablesCountForState.new(finder, store_in_redis_cache: true)[state]
+ Gitlab::IssuablesCountForState.new(finder, fast_fail: true, store_in_redis_cache: true)[state]
end
def close_issuable_path(issuable)
diff --git a/app/views/groups/_invite_members_modal.html.haml b/app/views/groups/_invite_members_modal.html.haml
index bf0e8b627fd..f0fd9026b30 100644
--- a/app/views/groups/_invite_members_modal.html.haml
+++ b/app/views/groups/_invite_members_modal.html.haml
@@ -1,6 +1,6 @@
- return unless can_admin_group_member?(group)
.js-invite-members-modal{ data: { is_project: 'false',
- access_levels: GroupMember.access_level_roles.to_json,
+ access_levels: group.access_level_roles.to_json,
reload_page_on_submit: local_assigns.fetch(:reload_page_on_submit, false).to_s,
help_link: help_page_url('user/permissions') }.merge(common_invite_modal_dataset(group)).merge(users_filter_data(group)) }
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index a4a74c171d9..0b5d73afacd 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -10928,7 +10928,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="boardepicancestorsmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter epics by milestone title, computed from epic's issues. |
| <a id="boardepicancestorsmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. |
| <a id="boardepicancestorsnot"></a>`not` | [`NegatedEpicFilterInput`](#negatedepicfilterinput) | Negated epic arguments. |
-| <a id="boardepicancestorsor"></a>`or` | [`UnionedEpicFilterInput`](#unionedepicfilterinput) | List of arguments with inclusive OR. |
+| <a id="boardepicancestorsor"></a>`or` **{warning-solid}** | [`UnionedEpicFilterInput`](#unionedepicfilterinput) | **Introduced** in 15.9. This feature is in Alpha. It can be changed or removed at any time. List of arguments with inclusive OR. Ignored unless `or_issuable_queries` flag is enabled. |
| <a id="boardepicancestorssearch"></a>`search` | [`String`](#string) | Search query for title or description. |
| <a id="boardepicancestorssort"></a>`sort` | [`EpicSort`](#epicsort) | List epics by sort order. |
| <a id="boardepicancestorsstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. |
@@ -10967,7 +10967,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="boardepicchildrenmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter epics by milestone title, computed from epic's issues. |
| <a id="boardepicchildrenmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. |
| <a id="boardepicchildrennot"></a>`not` | [`NegatedEpicFilterInput`](#negatedepicfilterinput) | Negated epic arguments. |
-| <a id="boardepicchildrenor"></a>`or` | [`UnionedEpicFilterInput`](#unionedepicfilterinput) | List of arguments with inclusive OR. |
+| <a id="boardepicchildrenor"></a>`or` **{warning-solid}** | [`UnionedEpicFilterInput`](#unionedepicfilterinput) | **Introduced** in 15.9. This feature is in Alpha. It can be changed or removed at any time. List of arguments with inclusive OR. Ignored unless `or_issuable_queries` flag is enabled. |
| <a id="boardepicchildrensearch"></a>`search` | [`String`](#string) | Search query for title or description. |
| <a id="boardepicchildrensort"></a>`sort` | [`EpicSort`](#epicsort) | List epics by sort order. |
| <a id="boardepicchildrenstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. |
@@ -13018,7 +13018,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="epicancestorsmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter epics by milestone title, computed from epic's issues. |
| <a id="epicancestorsmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. |
| <a id="epicancestorsnot"></a>`not` | [`NegatedEpicFilterInput`](#negatedepicfilterinput) | Negated epic arguments. |
-| <a id="epicancestorsor"></a>`or` | [`UnionedEpicFilterInput`](#unionedepicfilterinput) | List of arguments with inclusive OR. |
+| <a id="epicancestorsor"></a>`or` **{warning-solid}** | [`UnionedEpicFilterInput`](#unionedepicfilterinput) | **Introduced** in 15.9. This feature is in Alpha. It can be changed or removed at any time. List of arguments with inclusive OR. Ignored unless `or_issuable_queries` flag is enabled. |
| <a id="epicancestorssearch"></a>`search` | [`String`](#string) | Search query for title or description. |
| <a id="epicancestorssort"></a>`sort` | [`EpicSort`](#epicsort) | List epics by sort order. |
| <a id="epicancestorsstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. |
@@ -13057,7 +13057,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="epicchildrenmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter epics by milestone title, computed from epic's issues. |
| <a id="epicchildrenmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. |
| <a id="epicchildrennot"></a>`not` | [`NegatedEpicFilterInput`](#negatedepicfilterinput) | Negated epic arguments. |
-| <a id="epicchildrenor"></a>`or` | [`UnionedEpicFilterInput`](#unionedepicfilterinput) | List of arguments with inclusive OR. |
+| <a id="epicchildrenor"></a>`or` **{warning-solid}** | [`UnionedEpicFilterInput`](#unionedepicfilterinput) | **Introduced** in 15.9. This feature is in Alpha. It can be changed or removed at any time. List of arguments with inclusive OR. Ignored unless `or_issuable_queries` flag is enabled. |
| <a id="epicchildrensearch"></a>`search` | [`String`](#string) | Search query for title or description. |
| <a id="epicchildrensort"></a>`sort` | [`EpicSort`](#epicsort) | List epics by sort order. |
| <a id="epicchildrenstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. |
@@ -14077,7 +14077,7 @@ Returns [`Epic`](#epic).
| <a id="groupepicmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter epics by milestone title, computed from epic's issues. |
| <a id="groupepicmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. |
| <a id="groupepicnot"></a>`not` | [`NegatedEpicFilterInput`](#negatedepicfilterinput) | Negated epic arguments. |
-| <a id="groupepicor"></a>`or` | [`UnionedEpicFilterInput`](#unionedepicfilterinput) | List of arguments with inclusive OR. |
+| <a id="groupepicor"></a>`or` **{warning-solid}** | [`UnionedEpicFilterInput`](#unionedepicfilterinput) | **Introduced** in 15.9. This feature is in Alpha. It can be changed or removed at any time. List of arguments with inclusive OR. Ignored unless `or_issuable_queries` flag is enabled. |
| <a id="groupepicsearch"></a>`search` | [`String`](#string) | Search query for title or description. |
| <a id="groupepicsort"></a>`sort` | [`EpicSort`](#epicsort) | List epics by sort order. |
| <a id="groupepicstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. |
@@ -14128,7 +14128,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="groupepicsmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter epics by milestone title, computed from epic's issues. |
| <a id="groupepicsmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. |
| <a id="groupepicsnot"></a>`not` | [`NegatedEpicFilterInput`](#negatedepicfilterinput) | Negated epic arguments. |
-| <a id="groupepicsor"></a>`or` | [`UnionedEpicFilterInput`](#unionedepicfilterinput) | List of arguments with inclusive OR. |
+| <a id="groupepicsor"></a>`or` **{warning-solid}** | [`UnionedEpicFilterInput`](#unionedepicfilterinput) | **Introduced** in 15.9. This feature is in Alpha. It can be changed or removed at any time. List of arguments with inclusive OR. Ignored unless `or_issuable_queries` flag is enabled. |
| <a id="groupepicssearch"></a>`search` | [`String`](#string) | Search query for title or description. |
| <a id="groupepicssort"></a>`sort` | [`EpicSort`](#epicsort) | List epics by sort order. |
| <a id="groupepicsstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. |
@@ -25289,7 +25289,7 @@ Values for ordering deployments by a specific field.
| <a id="epicfilterslabelname"></a>`labelName` | [`[String]`](#string) | Filter by label name. |
| <a id="epicfiltersmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values "NONE" and "ANY" are supported. |
| <a id="epicfiltersnot"></a>`not` | [`NegatedEpicBoardIssueInput`](#negatedepicboardissueinput) | Negated epic arguments. |
-| <a id="epicfiltersor"></a>`or` | [`UnionedEpicFilterInput`](#unionedepicfilterinput) | List of arguments with inclusive OR. |
+| <a id="epicfiltersor"></a>`or` | [`UnionedEpicFilterInput`](#unionedepicfilterinput) | List of arguments with inclusive OR. Ignored unless `or_issuable_queries` flag is enabled. |
| <a id="epicfilterssearch"></a>`search` | [`String`](#string) | Search query for epic title or description. |
### `EpicTreeNodeFieldsInputType`
@@ -25570,7 +25570,8 @@ A time-frame defined as a closed inclusive range of two dates.
| Name | Type | Description |
| ---- | ---- | ----------- |
-| <a id="unionedepicfilterinputlabelname"></a>`labelName` | [`[String!]`](#string) | Filters epics that have at least one of the given labels. Ignored unless `or_issuable_queries` flag is enabled. |
+| <a id="unionedepicfilterinputauthorusername"></a>`authorUsername` | [`[String!]`](#string) | Filters epics that are authored by one of the given users. |
+| <a id="unionedepicfilterinputlabelname"></a>`labelName` | [`[String!]`](#string) | Filters epics that have at least one of the given labels. |
### `UnionedIssueFilterInput`
diff --git a/doc/architecture/blueprints/gitlab_agent_deployments/index.md b/doc/architecture/blueprints/gitlab_agent_deployments/index.md
new file mode 100644
index 00000000000..96e361d7531
--- /dev/null
+++ b/doc/architecture/blueprints/gitlab_agent_deployments/index.md
@@ -0,0 +1,403 @@
+---
+status: proposed
+creation-date: "2022-11-23"
+authors: [ "@shinya.maeda" ]
+coach: "@DylanGriffith"
+approvers: [ "@nagyv-gitlab", "@cbalane", "@hustewart", "@hfyngvason" ]
+owning-stage: "~devops::release"
+participating-stages: [Configure, Release]
+---
+
+# View and manage resources deployed by GitLab Agent For Kuberenetes
+
+## Summary
+
+As part of the [GitLab Kubernetes Dashboard](https://gitlab.com/groups/gitlab-org/-/epics/2493) epic,
+users want to view and manage their resources deployed by GitLab Agent For Kuberenetes.
+Users should be able to interact with the resources through GitLab UI, such as Environment Index/Details page.
+
+This blueprint describes how the association is established and how these domain models interact with each other.
+
+## Motivation
+
+### Goals
+
+- The proposed architecture can be used in [GitLab Kubernetes Dashboard](https://gitlab.com/groups/gitlab-org/-/epics/2493).
+- The proposed architecture can be used in [Organization-level Environment dashboard](https://gitlab.com/gitlab-org/gitlab/-/issues/241506).
+- The cluster resources and events can be visualized per [GitLab Environment](../../../ci/environments/index.md).
+ An environment-specific view scoped to the resources managed either directly or indirectly by a deployment commit.
+- Support both [GitOps mode](../../../user/clusters/agent/gitops.md#gitops-configuration-reference) and [CI Access mode](../../../user/clusters/agent/ci_cd_workflow.md#authorize-the-agent).
+ - NOTE: At the moment, we focus on the solution for CI Access mode. GitOps mode will have significant architectural changes _outside of_ this blueprint,
+ such as [Flux switching](https://gitlab.com/gitlab-org/gitlab/-/issues/357947) and [Manifest projects outside of the Agent configuration project](https://gitlab.com/groups/gitlab-org/-/epics/7704). In order to derisk potential rework, we'll revisit the GitOps mode after these upstream changes have been settled.
+
+### Non-Goals
+
+- The design details of [GitLab Kubernetes Dashboard](https://gitlab.com/groups/gitlab-org/-/epics/2493) and [Organization-level Environment dashboard](https://gitlab.com/gitlab-org/gitlab/-/issues/241506).
+- Support Environment/Deployment features that rely on GitLab CI/CD pipelines, such as [Protected Environments](../../../ci/environments/protected_environments.md), [Deployment Approvals](../../../ci/environments/deployment_approvals.md), [Deployment safety](../../../ci/environments/deployment_safety.md), and [Environment rollback](../../../ci/environments/index.md#environment-rollback). These features are already available in CI Access mode, however, it's not available in GitOps mode.
+
+## Proposal
+
+### Overview
+
+- GitLab Environment and Agent-managed Resource Group have 1-to-1 relationship.
+- Agent-managed Resource Group tracks all resources produced by the connected [agent](../../../user/clusters/agent/index.md). This includes not only resources written in manifest files but also subsequently generated resources (e.g. `Pod`s created by `Deployment` manifest file).
+- Agent-managed Resource Group renders dependency graph, such as `Deployment` => `ReplicaSet` => `Pod`. This is for providing ArgoCD-style resource view.
+- Agent-managed Resource Group has the Resource Health status that represents a summary of resource statuses, such as `Healthy`, `Progressing` or `Degraded`.
+
+```mermaid
+flowchart LR
+ subgraph Kubernetes["Kubernetes"]
+ subgraph ResourceGroupProduction["ResourceGroup"]
+ direction LR
+ ResourceGroupProductionService(["Service"])
+ ResourceGroupProductionDeployment(["Deployment"])
+ ResourceGroupProductionPod1(["Pod1"])
+ ResourceGroupProductionPod2(["Pod2"])
+ end
+ subgraph ResourceGroupStaging["ResourceGroup"]
+ direction LR
+ ResourceGroupStagingService(["Service"])
+ ResourceGroupStagingDeployment(["Deployment"])
+ ResourceGroupStagingPod1(["Pod1"])
+ ResourceGroupStagingPod2(["Pod2"])
+ end
+ end
+
+ subgraph GitLab
+ subgraph Organization
+ subgraph Project
+ environment1["production environment"]
+ environment2["staging environment"]
+ end
+ end
+ end
+
+ environment1 --- ResourceGroupProduction
+ environment2 --- ResourceGroupStaging
+ ResourceGroupProductionService -.- ResourceGroupProductionDeployment
+ ResourceGroupProductionDeployment -.- ResourceGroupProductionPod1
+ ResourceGroupProductionDeployment -.- ResourceGroupProductionPod2
+ ResourceGroupStagingService -.- ResourceGroupStagingDeployment
+ ResourceGroupStagingDeployment -.- ResourceGroupStagingPod1
+ ResourceGroupStagingDeployment -.- ResourceGroupStagingPod2
+```
+
+### Existing components and relationships
+
+- [GitLab Project](../../../user/project/working_with_projects.md) and GitLab Environment have 1-to-many relationship.
+- GitLab Project and Agent have 1-to-many _direct_ relationship. Only one project can own a specific agent.
+- [GitOps mode](../../../user/clusters/agent/gitops.md#gitops-configuration-reference)
+ - GitLab Project and Agent do _NOT_ have many-to-many _indirect_ relationship yet. This will be supported in [Manifest projects outside of the Agent configuration project](https://gitlab.com/groups/gitlab-org/-/epics/7704).
+ - Agent and Agent-managed Resource Group have 1-to-1 relationship. Inventory IDs are used to group Kubernetes resources. This might be changed in [Flux switching](https://gitlab.com/gitlab-org/gitlab/-/issues/357947).
+- [CI Access mode](../../../user/clusters/agent/ci_cd_workflow.md#authorize-the-agent)
+ - GitLab Project and Agent have many-to-many _indirect_ relationship. The project owning the agent can [share the access with the other proejcts](../../../user/clusters/agent/ci_cd_workflow.md#authorize-the-agent-to-access-projects-in-your-groups). (NOTE: Technically, only running jobs inside the project are allowed to access the cluster due to job-token authentication.)
+ - Agent and Agent-managed Resource Group do _NOT_ have relationships yet.
+
+### Issues
+
+- Agent-managed Resource Group should have environment ID as the foreign key, which must be unique across resource groups.
+- Agent-managed Resource Group should have parameters how to group resources in the associated cluster, for example, `namespace`, `lable` and `inventory-id` (GitOps mode only) can passed as parameters.
+- Agent-managed Resource Group should be able to fetch all relevant resources, including both default resource kinds and other [Custom Resources](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/).
+- Agent-managed Resource Group should be aware of dependency graph.
+- Agent-managed Resource Group should be able to compute Resource Health status from the associated resources.
+
+### Example: Pull-based deployment (GitOps mode)
+
+NOTE:
+At the moment, we focus on the solution for CI Access mode. GitOps mode will have significant architectural changes _outside of_ this blueprint,
+such as [Flux switching](https://gitlab.com/gitlab-org/gitlab/-/issues/357947) and [Manifest projects outside of the Agent configuration project](https://gitlab.com/groups/gitlab-org/-/epics/7704). In order to derisk potential rework, we'll revisit the GitOps mode after these upstream changes have been settled.
+
+### Example: Push-based deployment (CI access mode)
+
+This is an example of how the architecture works in push-based deployment.
+The feature is documented [here](../../../user/clusters/agent/ci_cd_workflow.md) as CI access mode.
+
+```mermaid
+flowchart LR
+ subgraph ProductionKubernetes["Production Kubernetes"]
+ subgraph ResourceGroupProductionFrontend["ResourceGroup"]
+ direction LR
+ ResourceGroupProductionFrontendService(["Service"])
+ ResourceGroupProductionFrontendDeployment(["Deployment"])
+ ResourceGroupProductionFrontendPod1(["Pod1"])
+ ResourceGroupProductionFrontendPod2(["Pod2"])
+ end
+ subgraph ResourceGroupProductionBackend["ResourceGroup"]
+ direction LR
+ ResourceGroupProductionBackendService(["Service"])
+ ResourceGroupProductionBackendDeployment(["Deployment"])
+ ResourceGroupProductionBackendPod1(["Pod1"])
+ ResourceGroupProductionBackendPod2(["Pod2"])
+ end
+ subgraph ResourceGroupProductionPrometheus["ResourceGroup"]
+ direction LR
+ ResourceGroupProductionPrometheusService(["Service"])
+ ResourceGroupProductionPrometheusDeployment(["Deployment"])
+ ResourceGroupProductionPrometheusPod1(["Pod1"])
+ ResourceGroupProductionPrometheusPod2(["Pod2"])
+ end
+ end
+
+ subgraph GitLab
+ subgraph Organization
+ subgraph OperationGroup
+ subgraph AgentManagementProject
+ AgentManagementAgentProduction["Production agent"]
+ AgentManagementManifestFiles["Kubernetes Manifest Files"]
+ AgentManagementEnvironmentProductionPrometheus["production prometheus environment"]
+ AgentManagementPipelines["CI/CD pipelines"]
+ end
+ end
+ subgraph DevelopmentGroup
+ subgraph FrontendAppProject
+ FrontendAppCode["VueJS"]
+ FrontendDockerfile["Dockerfile"]
+ end
+ subgraph BackendAppProject
+ BackendAppCode["Golang"]
+ BackendDockerfile["Dockerfile"]
+ end
+ subgraph DeploymentProject
+ DeploymentManifestFiles["Kubernetes Manifest Files"]
+ DeploymentPipelines["CI/CD pipelines"]
+ DeploymentEnvironmentProductionFrontend["production frontend environment"]
+ DeploymentEnvironmentProductionBackend["production backend environment"]
+ end
+ end
+ end
+ end
+
+ DeploymentEnvironmentProductionFrontend --- ResourceGroupProductionFrontend
+ DeploymentEnvironmentProductionBackend --- ResourceGroupProductionBackend
+ AgentManagementEnvironmentProductionPrometheus --- ResourceGroupProductionPrometheus
+ ResourceGroupProductionFrontendService -.- ResourceGroupProductionFrontendDeployment
+ ResourceGroupProductionFrontendDeployment -.- ResourceGroupProductionFrontendPod1
+ ResourceGroupProductionFrontendDeployment -.- ResourceGroupProductionFrontendPod2
+ ResourceGroupProductionBackendService -.- ResourceGroupProductionBackendDeployment
+ ResourceGroupProductionBackendDeployment -.- ResourceGroupProductionBackendPod1
+ ResourceGroupProductionBackendDeployment -.- ResourceGroupProductionBackendPod2
+ ResourceGroupProductionPrometheusService -.- ResourceGroupProductionPrometheusDeployment
+ ResourceGroupProductionPrometheusDeployment -.- ResourceGroupProductionPrometheusPod1
+ ResourceGroupProductionPrometheusDeployment -.- ResourceGroupProductionPrometheusPod2
+ AgentManagementAgentProduction -- Shared with --- DeploymentProject
+ DeploymentPipelines -- "Deploy" --> ResourceGroupProductionFrontend
+ DeploymentPipelines -- "Deploy" --> ResourceGroupProductionBackend
+ AgentManagementPipelines -- "Deploy" --> ResourceGroupProductionPrometheus
+```
+
+### Further details
+
+#### Multi-Project Deployment Pipelines
+
+The microservice project setup can be improved by [Multi-Project Deployment Pipelines](https://gitlab.com/groups/gitlab-org/-/epics/8483):
+
+- Deployment Project can behave as the shared deployment engine for any upstream application projects and environments.
+- Environments can be created within the application projects. It gives more visibility of environments for developers.
+- Deployment Project can be managed under Operator group. More segregation of duties.
+- Users don't need to setup [RBAC to restrict CI/CD jobs](../../../user/clusters/agent/ci_cd_workflow.md#restrict-project-and-group-access-by-using-impersonation).
+- This is especitially helpful for [dynamic environments](../../../ci/environments/index.md#create-a-dynamic-environment), such as Review Apps.
+
+```mermaid
+flowchart LR
+ subgraph ProductionKubernetes["Production Kubernetes"]
+ subgraph ResourceGroupProductionFrontend["ResourceGroup"]
+ direction LR
+ ResourceGroupProductionFrontendService(["Service"])
+ ResourceGroupProductionFrontendDeployment(["Deployment"])
+ ResourceGroupProductionFrontendPod1(["Pod1"])
+ ResourceGroupProductionFrontendPod2(["Pod2"])
+ end
+ subgraph ResourceGroupProductionBackend["ResourceGroup"]
+ direction LR
+ ResourceGroupProductionBackendService(["Service"])
+ ResourceGroupProductionBackendDeployment(["Deployment"])
+ ResourceGroupProductionBackendPod1(["Pod1"])
+ ResourceGroupProductionBackendPod2(["Pod2"])
+ end
+ subgraph ResourceGroupProductionPrometheus["ResourceGroup"]
+ direction LR
+ ResourceGroupProductionPrometheusService(["Service"])
+ ResourceGroupProductionPrometheusDeployment(["Deployment"])
+ ResourceGroupProductionPrometheusPod1(["Pod1"])
+ ResourceGroupProductionPrometheusPod2(["Pod2"])
+ end
+ end
+
+ subgraph GitLab
+ subgraph Organization
+ subgraph OperationGroup
+ subgraph DeploymentProject
+ DeploymentAgentProduction["Production agent"]
+ DeploymentManifestFiles["Kubernetes Manifest Files"]
+ DeploymentEnvironmentProductionPrometheus["production prometheus environment"]
+ DeploymentPipelines["CI/CD pipelines"]
+ end
+ end
+ subgraph DevelopmentGroup
+ subgraph FrontendAppProject
+ FrontendDeploymentPipelines["CI/CD pipelines"]
+ FrontendEnvironmentProduction["production environment"]
+ end
+ subgraph BackendAppProject
+ BackendDeploymentPipelines["CI/CD pipelines"]
+ BackendEnvironmentProduction["production environment"]
+ end
+ end
+ end
+ end
+
+ FrontendEnvironmentProduction --- ResourceGroupProductionFrontend
+ BackendEnvironmentProduction --- ResourceGroupProductionBackend
+ DeploymentEnvironmentProductionPrometheus --- ResourceGroupProductionPrometheus
+ ResourceGroupProductionFrontendService -.- ResourceGroupProductionFrontendDeployment
+ ResourceGroupProductionFrontendDeployment -.- ResourceGroupProductionFrontendPod1
+ ResourceGroupProductionFrontendDeployment -.- ResourceGroupProductionFrontendPod2
+ ResourceGroupProductionBackendService -.- ResourceGroupProductionBackendDeployment
+ ResourceGroupProductionBackendDeployment -.- ResourceGroupProductionBackendPod1
+ ResourceGroupProductionBackendDeployment -.- ResourceGroupProductionBackendPod2
+ ResourceGroupProductionPrometheusService -.- ResourceGroupProductionPrometheusDeployment
+ ResourceGroupProductionPrometheusDeployment -.- ResourceGroupProductionPrometheusPod1
+ ResourceGroupProductionPrometheusDeployment -.- ResourceGroupProductionPrometheusPod2
+ FrontendDeploymentPipelines -- "Trigger downstream pipeline" --> DeploymentProject
+ BackendDeploymentPipelines -- "Trigger downstream pipeline" --> DeploymentProject
+ DeploymentPipelines -- "Deploy" --> ResourceGroupProductionFrontend
+ DeploymentPipelines -- "Deploy" --> ResourceGroupProductionBackend
+```
+
+#### View all Agent-managed Resource Groups on production environment
+
+At the group-level, we can accumulate all environments match a specific tier, for example,
+listing all environments with `production` tier from subsequent projects.
+This is useful to see the entire Agent-managed Resource Groups on production environment.
+The following diagram examplifies the relationship between GitLab group and Kubernetes resources:
+
+```mermaid
+flowchart LR
+ subgraph Kubernetes["Kubernetes"]
+ subgraph ResourceGroupProduction["ResourceGroup"]
+ direction LR
+ ResourceGroupProductionService(["Service"])
+ ResourceGroupProductionDeployment(["Deployment"])
+ ResourceGroupProductionPod1(["Pod1"])
+ ResourceGroupProductionPod2(["Pod2"])
+ end
+ subgraph ResourceGroupStaging["ResourceGroup"]
+ direction LR
+ ResourceGroupStagingService(["Service"])
+ ResourceGroupStagingDeployment(["Deployment"])
+ ResourceGroupStagingPod1(["Pod1"])
+ ResourceGroupStagingPod2(["Pod2"])
+ end
+ end
+
+ subgraph GitLab
+ subgraph Organization
+ OrganizationProduction["All resources on production"]
+ subgraph Frontend project
+ FrontendEnvironmentProduction["production environment"]
+ end
+ subgraph Backend project
+ BackendEnvironmentProduction["production environment"]
+ end
+ end
+ end
+
+ FrontendEnvironmentProduction --- ResourceGroupProduction
+ BackendEnvironmentProduction --- ResourceGroupStaging
+ ResourceGroupProductionService -.- ResourceGroupProductionDeployment
+ ResourceGroupProductionDeployment -.- ResourceGroupProductionPod1
+ ResourceGroupProductionDeployment -.- ResourceGroupProductionPod2
+ ResourceGroupStagingService -.- ResourceGroupStagingDeployment
+ ResourceGroupStagingDeployment -.- ResourceGroupStagingPod1
+ ResourceGroupStagingDeployment -.- ResourceGroupStagingPod2
+ OrganizationProduction --- FrontendEnvironmentProduction
+ OrganizationProduction --- BackendEnvironmentProduction
+```
+
+A few notes:
+
+- In the future, we'd have more granular filters for resource search.
+ For example, there are two environments `production/us-region` and `production/eu-region` in each project
+ and show only resources in US region at the group-level.
+ This could be achivable by query filtering in PostgreSQL or label/namespace filtering in Kubernetes.
+- Please see [Add dynamically populated organization-level environments page](https://gitlab.com/gitlab-org/gitlab/-/issues/241506) for more information.
+
+## Design and implementation details
+
+NOTE:
+The following solution might be only applicable for CI Access mode. GitOps mode will have significant architectural changes _outside of_ this blueprint,
+such as [Flux switching](https://gitlab.com/gitlab-org/gitlab/-/issues/357947) and [Manifest projects outside of the Agent configuration project](https://gitlab.com/groups/gitlab-org/-/epics/7704). In order to derisk potential rework, we'll revisit the GitOps mode after these upstream changes have been settled.
+
+### Associate Environment with Agent
+
+As a preliminary step, we allow users to explicitly define "which deployment job" uses "which agent" and deploy to "which namespace". The following keywords are supported in `.gitlab-ci.yml`.
+
+- `environment:kubernetes:agent` ... Define which agent the deployment job uses. It can select the appropriate context from the `KUBE_CONFIG`.
+- `environment:kubernetes:namespace` ... Define which namespace the deployment job deploys to. It injects `KUBE_NAMESPACE` predefined variable into the job. This keyword already [exists](../../../ci/yaml/index.md#environmentkubernetes).
+
+Here is an example of `.gitlab-ci.yml`.
+
+```yaml
+deploy-production:
+ environment:
+ name: production
+ kubernetes:
+ agent: path/to/agent/repository:agent-name
+ namespace: default
+ script:
+ - helm --context="$KUBE_CONTEXT" --namespace="$KUBE_NAMESPACE" upgrade --install
+```
+
+When a deployment job is created, GitLab persists the relationship of specified agent, namespace and deployment job. If the CI job is NOT authorized to access the agent (Please refer [`Clusters::Agents::FilterAuthorizationsService`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/services/clusters/agents/filter_authorizations_service.rb) for more details), this relationship aren't recorded. This process happens in [`Deployments::CreateForBuildService`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/services/deployments/create_for_build_service.rb). The database table scheme is:
+
+```plaintext
+agent_deployments:
+ - deployment_id (bigint/FK/NOT NULL/Unique)
+ - agent_id (bigint/FK/NOT NULL)
+ - kubernetes_namespace (character varying(255)/NOT NULL)
+```
+
+To idenfity an associated agent for a specific environment, `environment.last_deployment.agent` can be used in Rails.
+
+### Fetch resources through `user_access`
+
+When user visits an environment page, GitLab frontend fetches an environment via GraphQL. Frontend additionally fetches the associated agent-ID and namespace through deployment relationship, which being tracked by the `agent_deployments` table.
+
+Here is an example of GraphQL query:
+
+```graphql
+{
+ project(fullPath: "group/project") {
+ id
+ environment(name: "<environment-name>") {
+ slug
+ lastDeployment(status: SUCCESS) {
+ agent {
+ id
+ kubernetesNamespace
+ }
+ }
+ }
+ }
+}
+```
+
+GitLab frontend authenticate/authorize the user access with [browser cookie](https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent/-/blob/master/doc/kubernetes_user_access.md#browser-cookie-on-gitlab-frontend). If the access is forbidden, frontend shows an error message that `You don't have access to an agent that deployed to this environment. Please contact agent administrator if you are allowed in "user_access" in agent config file. See <troubleshooting-doc-link>`.
+
+After the user gained access to the agent, GitLab frontend fetches available API Resource list in the Kubernetes and fetches the resources with the following parameters:
+
+- `namespace` ... `#{environment.lastDeployment.agent.kubernetesNamespace}`
+- `labels`
+ - `app.gitlab.com/project_id=#{project.id}` _AND_
+ - `app.gitlab.com/environment_slug: #{environment.slug}`
+
+If no resources are found, this is likely that the users have not embedded these lables into their resources. In this case, frontend shows an warning message `There are no resources found for the environment. Do resources have GitLab preserved labels? See <troubleshooting-doc-link>`.
+
+### Dependency graph
+
+- GitLab frontend uses [Owner References](https://kubernetes.io/docs/concepts/overview/working-with-objects/owners-dependents/) to idenfity the dependencies between resources. These are embedded in resources as `metadata.ownerReferences` field.
+- For the resoruces that don't have owner references, we can use [Well-Known Labels, Annotations and Taints](https://kubernetes.io/docs/reference/labels-annotations-taints/) as complement. e.g. `EndpointSlice` doesn't have `metadata.ownerReferences`, but has `kubernetes.io/service-name` as a reference to the parent `Service` resource.
+
+### Health status of resources
+
+- GitLab frontend computes the status summary from the fetched resources. Something similar to ArgoCD's [Resource Health](https://argo-cd.readthedocs.io/en/stable/operator-manual/health/) e.g. `Healthy`, `Progressing`, `Degraded` and `Suspended`. The formula is TBD.
diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md
index 8f02b502693..dfe4ac7446e 100644
--- a/doc/ci/yaml/index.md
+++ b/doc/ci/yaml/index.md
@@ -1711,7 +1711,7 @@ Use the `action` keyword to specify how the job interacts with the environment.
|:----------|:----------------|
| `start` | Default value. Indicates that the job starts the environment. The deployment is created after the job starts. |
| `prepare` | Indicates that the job is only preparing the environment. It does not trigger deployments. [Read more about preparing environments](../environments/index.md#access-an-environment-for-preparation-or-verification-purposes). |
-| `stop` | Indicates that the job stops a deployment. For more detail, read [Stop an environment](../environments/index.md#stop-an-environment). |
+| `stop` | Indicates that the job stops an environment. [Read more about stopping an environment](../environments/index.md#stop-an-environment). |
| `verify` | Indicates that the job is only verifying the environment. It does not trigger deployments. [Read more about verifying environments](../environments/index.md#access-an-environment-for-preparation-or-verification-purposes). |
| `access` | Indicates that the job is only accessing the environment. It does not trigger deployments. [Read more about accessing environments](../environments/index.md#access-an-environment-for-preparation-or-verification-purposes). |
diff --git a/lib/api/helpers/members_helpers.rb b/lib/api/helpers/members_helpers.rb
index b0ea4388d9b..a406a62344a 100644
--- a/lib/api/helpers/members_helpers.rb
+++ b/lib/api/helpers/members_helpers.rb
@@ -98,6 +98,10 @@ module API
user_id.present?
end
+ def self.member_access_levels
+ Gitlab::Access.all_values
+ end
+
private
def member_already_exists?(source, user_id)
diff --git a/lib/api/invitations.rb b/lib/api/invitations.rb
index ec563e6a5c5..828f4b419ef 100644
--- a/lib/api/invitations.rb
+++ b/lib/api/invitations.rb
@@ -21,7 +21,7 @@ module API
tags %w[invitations]
end
params do
- requires :access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'A valid access level (defaults: `30`, developer access level)'
+ requires :access_level, type: Integer, values: ::API::Helpers::MembersHelpers.member_access_levels, desc: 'A valid access level (defaults: `30`, developer access level)'
optional :email, type: Array[String], email_or_email_list: true, coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The email address to invite, or multiple emails separated by comma'
optional :user_id, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The user ID of the new member or multiple IDs separated by commas.'
optional :expires_at, type: DateTime, desc: 'Date string in the format YEAR-MONTH-DAY'
diff --git a/lib/gitlab/gitaly_client/operation_service.rb b/lib/gitlab/gitaly_client/operation_service.rb
index 66f70ed9dc6..6865e76d4bb 100644
--- a/lib/gitlab/gitaly_client/operation_service.rb
+++ b/lib/gitlab/gitaly_client/operation_service.rb
@@ -40,11 +40,6 @@ module Gitlab
)
response = gitaly_client_call(@repository.storage, :operation_service, :user_create_tag, request, timeout: GitalyClient.long_timeout)
- if pre_receive_error = response.pre_receive_error.presence
- raise Gitlab::Git::PreReceiveError, pre_receive_error
- elsif response.exists
- raise Gitlab::Git::Repository::TagExistsError
- end
Gitlab::Git::Tag.new(@repository, response.tag)
rescue GRPC::BadStatus => e
@@ -79,10 +74,6 @@ module Gitlab
response = gitaly_client_call(@repository.storage, :operation_service,
:user_create_branch, request, timeout: GitalyClient.long_timeout)
- if response.pre_receive_error.present?
- raise Gitlab::Git::PreReceiveError, response.pre_receive_error
- end
-
branch = response.branch
return unless branch
@@ -128,12 +119,8 @@ module Gitlab
user: Gitlab::Git::User.from_gitlab(user).to_gitaly
)
- response = gitaly_client_call(@repository.storage, :operation_service,
- :user_delete_branch, request, timeout: GitalyClient.long_timeout)
-
- if pre_receive_error = response.pre_receive_error.presence
- raise Gitlab::Git::PreReceiveError, pre_receive_error
- end
+ gitaly_client_call(@repository.storage, :operation_service,
+ :user_delete_branch, request, timeout: GitalyClient.long_timeout)
rescue GRPC::BadStatus => e
detailed_error = GitalyClient.decode_detailed_error(e)
@@ -246,25 +233,54 @@ module Gitlab
end
def user_cherry_pick(user:, commit:, branch_name:, message:, start_branch_name:, start_repository:, dry_run: false)
- call_cherry_pick_or_revert(:cherry_pick,
- user: user,
- commit: commit,
- branch_name: branch_name,
- message: message,
- start_branch_name: start_branch_name,
- start_repository: start_repository,
- dry_run: dry_run)
+ response = call_cherry_pick_or_revert(:cherry_pick,
+ user: user,
+ commit: commit,
+ branch_name: branch_name,
+ message: message,
+ start_branch_name: start_branch_name,
+ start_repository: start_repository,
+ dry_run: dry_run)
+
+ Gitlab::Git::OperationService::BranchUpdate.from_gitaly(response.branch_update)
+ rescue GRPC::BadStatus => e
+ detailed_error = GitalyClient.decode_detailed_error(e)
+
+ case detailed_error&.error
+ when :access_check
+ access_check_error = detailed_error.access_check
+ # These messages were returned from internal/allowed API calls
+ raise Gitlab::Git::PreReceiveError.new(fallback_message: access_check_error.error_message)
+ when :cherry_pick_conflict
+ raise Gitlab::Git::Repository::CreateTreeError, 'CONFLICT'
+ when :changes_already_applied
+ raise Gitlab::Git::Repository::CreateTreeError, 'EMPTY'
+ when :target_branch_diverged
+ raise Gitlab::Git::CommitError, 'branch diverged'
+ else
+ raise e
+ end
end
def user_revert(user:, commit:, branch_name:, message:, start_branch_name:, start_repository:, dry_run: false)
- call_cherry_pick_or_revert(:revert,
- user: user,
- commit: commit,
- branch_name: branch_name,
- message: message,
- start_branch_name: start_branch_name,
- start_repository: start_repository,
- dry_run: dry_run)
+ response = call_cherry_pick_or_revert(:revert,
+ user: user,
+ commit: commit,
+ branch_name: branch_name,
+ message: message,
+ start_branch_name: start_branch_name,
+ start_repository: start_repository,
+ dry_run: dry_run)
+
+ if response.pre_receive_error.presence
+ raise Gitlab::Git::PreReceiveError, response.pre_receive_error
+ elsif response.commit_error.presence
+ raise Gitlab::Git::CommitError, response.commit_error
+ elsif response.create_tree_error.presence
+ raise Gitlab::Git::Repository::CreateTreeError, response.create_tree_error_code
+ end
+
+ Gitlab::Git::OperationService::BranchUpdate.from_gitaly(response.branch_update)
end
def rebase(user, rebase_id, branch:, branch_sha:, remote_repository:, remote_branch:, push_options: [])
@@ -520,7 +536,7 @@ module Gitlab
dry_run: dry_run
)
- response = gitaly_client_call(
+ gitaly_client_call(
@repository.storage,
:operation_service,
:"user_#{rpc}",
@@ -528,37 +544,6 @@ module Gitlab
remote_storage: start_repository.storage,
timeout: GitalyClient.long_timeout
)
-
- handle_cherry_pick_or_revert_response(response)
- rescue GRPC::BadStatus => e
- detailed_error = GitalyClient.decode_detailed_error(e)
-
- case detailed_error&.error
- when :access_check
- access_check_error = detailed_error.access_check
- # These messages were returned from internal/allowed API calls
- raise Gitlab::Git::PreReceiveError.new(fallback_message: access_check_error.error_message)
- when :cherry_pick_conflict
- raise Gitlab::Git::Repository::CreateTreeError, 'CONFLICT'
- when :changes_already_applied
- raise Gitlab::Git::Repository::CreateTreeError, 'EMPTY'
- when :target_branch_diverged
- raise Gitlab::Git::CommitError, 'branch diverged'
- else
- raise e
- end
- end
-
- def handle_cherry_pick_or_revert_response(response)
- if response.pre_receive_error.presence
- raise Gitlab::Git::PreReceiveError, response.pre_receive_error
- elsif response.commit_error.presence
- raise Gitlab::Git::CommitError, response.commit_error
- elsif response.create_tree_error.presence
- raise Gitlab::Git::Repository::CreateTreeError, response.create_tree_error_code
- end
-
- Gitlab::Git::OperationService::BranchUpdate.from_gitaly(response.branch_update)
end
# rubocop:disable Metrics/ParameterLists
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 4fd72d43b90..2f3678cce1a 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -23068,6 +23068,9 @@ msgstr ""
msgid "InviteMembersModal|Access expiration date (optional)"
msgstr ""
+msgid "InviteMembersModal|Add unlimited members with your trial"
+msgstr ""
+
msgid "InviteMembersModal|Cancel"
msgstr ""
@@ -23083,6 +23086,9 @@ msgstr ""
msgid "InviteMembersModal|Create issues for your new team member to work on (optional)"
msgstr ""
+msgid "InviteMembersModal|During your trial, you can invite as many members to %{groupName} as you like. When the trial ends, you'll have a maximum of %{dashboardLimit} members on the Free tier. To get more members, %{linkStart}upgrade to a paid plan%{linkEnd}."
+msgstr ""
+
msgid "InviteMembersModal|Explore paid plans"
msgstr ""
diff --git a/qa/Gemfile b/qa/Gemfile
index db4dc9c7802..0e3f22cb69f 100644
--- a/qa/Gemfile
+++ b/qa/Gemfile
@@ -16,7 +16,7 @@ gem 'rspec-retry', '~> 0.6.2', require: 'rspec/retry'
gem 'rspec_junit_formatter', '~> 0.6.0'
gem 'faker', '~> 3.1', '>= 3.1.1'
gem 'knapsack', '~> 4.0'
-gem 'parallel_tests', '~> 4.1'
+gem 'parallel_tests', '~> 4.2'
gem 'rotp', '~> 6.2.2'
gem 'parallel', '~> 1.22', '>= 1.22.1'
gem 'rainbow', '~> 3.1.1'
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index 6a2af4c6353..cc9e4adf15e 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -189,7 +189,7 @@ GEM
oj (3.13.23)
os (1.1.4)
parallel (1.22.1)
- parallel_tests (4.1.0)
+ parallel_tests (4.2.0)
parallel
parser (3.1.3.0)
ast (~> 2.4.1)
@@ -323,7 +323,7 @@ DEPENDENCIES
nokogiri (~> 1.14, >= 1.14.1)
octokit (~> 6.0.1)
parallel (~> 1.22, >= 1.22.1)
- parallel_tests (~> 4.1)
+ parallel_tests (~> 4.2)
pry-byebug (~> 3.10.1)
rainbow (~> 3.1.1)
rake (~> 13, >= 13.0.6)
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
index 91a4336b800..fa46a4aab2d 100644
--- a/spec/controllers/groups_controller_spec.rb
+++ b/spec/controllers/groups_controller_spec.rb
@@ -562,6 +562,21 @@ RSpec.describe GroupsController, factory_default: :keep, feature_category: :code
expect(response.body).to have_content('Open 2 Merged 0 Closed 0 All 2')
expect(response.body).not_to have_content('Open Merged Closed All')
end
+
+ context 'when MergeRequestsFinder raises an exception' do
+ before do
+ allow_next_instance_of(MergeRequestsFinder) do |instance|
+ allow(instance).to receive(:count_by_state).and_raise(ActiveRecord::QueryCanceled)
+ end
+ end
+
+ it 'does not display MR counts in nav' do
+ get :merge_requests, params: { id: group.to_param }
+
+ expect(response.body).to have_content('Open Merged Closed All')
+ expect(response.body).not_to have_content('Open 0 Merged 0 Closed 0 All 0')
+ end
+ end
end
context 'when an ActiveRecord::QueryCanceled is raised' do
diff --git a/spec/frontend/invite_members/components/invite_members_modal_spec.js b/spec/frontend/invite_members/components/invite_members_modal_spec.js
index b6b34e1063b..8493de3ca00 100644
--- a/spec/frontend/invite_members/components/invite_members_modal_spec.js
+++ b/spec/frontend/invite_members/components/invite_members_modal_spec.js
@@ -66,6 +66,7 @@ describe('InviteMembersModal', () => {
},
propsData: {
usersLimitDataset: {},
+ activeTrialDataset: {},
fullPath: 'project',
...propsData,
...props,
@@ -83,12 +84,20 @@ describe('InviteMembersModal', () => {
});
};
- const createInviteMembersToProjectWrapper = (usersLimitDataset = {}, stubs = {}) => {
- createComponent({ usersLimitDataset, isProject: true }, stubs);
+ const createInviteMembersToProjectWrapper = (
+ usersLimitDataset = {},
+ activeTrialDataset = {},
+ stubs = {},
+ ) => {
+ createComponent({ usersLimitDataset, activeTrialDataset, isProject: true }, stubs);
};
- const createInviteMembersToGroupWrapper = (usersLimitDataset = {}, stubs = {}) => {
- createComponent({ usersLimitDataset, isProject: false }, stubs);
+ const createInviteMembersToGroupWrapper = (
+ usersLimitDataset = {},
+ activeTrialDataset = {},
+ stubs = {},
+ ) => {
+ createComponent({ usersLimitDataset, activeTrialDataset, isProject: false }, stubs);
};
beforeEach(() => {
diff --git a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
index 82d5d0f292b..443efd9828f 100644
--- a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
@@ -42,21 +42,6 @@ RSpec.describe Gitlab::GitalyClient::OperationService do
expect(subject.dereferenced_target).to eq(commit)
end
- context "when pre_receive_error is present" do
- let(:response) do
- Gitaly::UserCreateBranchResponse.new(pre_receive_error: "GitLab: something failed")
- end
-
- it "throws a PreReceive exception" do
- expect_any_instance_of(Gitaly::OperationService::Stub)
- .to receive(:user_create_branch).with(request, kind_of(Hash))
- .and_return(response)
-
- expect { subject }.to raise_error(
- Gitlab::Git::PreReceiveError, "something failed")
- end
- end
-
context 'with structured errors' do
context 'with CustomHookError' do
let(:stdout) { nil }
@@ -232,21 +217,6 @@ RSpec.describe Gitlab::GitalyClient::OperationService do
subject
end
- context "when pre_receive_error is present" do
- let(:response) do
- Gitaly::UserDeleteBranchResponse.new(pre_receive_error: "GitLab: something failed")
- end
-
- it "throws a PreReceive exception" do
- expect_any_instance_of(Gitaly::OperationService::Stub)
- .to receive(:user_delete_branch).with(request, kind_of(Hash))
- .and_return(response)
-
- expect { subject }.to raise_error(
- Gitlab::Git::PreReceiveError, "something failed")
- end
- end
-
context 'with a custom hook error' do
let(:stdout) { nil }
let(:stderr) { nil }
@@ -574,16 +544,6 @@ RSpec.describe Gitlab::GitalyClient::OperationService do
)
end
- context 'when errors are not raised but returned in the response' do
- before do
- expect_any_instance_of(Gitaly::OperationService::Stub)
- .to receive(:user_cherry_pick).with(kind_of(Gitaly::UserCherryPickRequest), kind_of(Hash))
- .and_return(response)
- end
-
- it_behaves_like 'cherry pick and revert errors'
- end
-
context 'when AccessCheckError is raised' do
let(:raised_error) do
new_detailed_error(
@@ -1149,18 +1109,6 @@ RSpec.describe Gitlab::GitalyClient::OperationService do
end
end
- context 'with pre-receive error' do
- before do
- expect_any_instance_of(Gitaly::OperationService::Stub)
- .to receive(:user_create_tag)
- .and_return(Gitaly::UserCreateTagResponse.new(pre_receive_error: "GitLab: something failed"))
- end
-
- it 'raises a PreReceiveError' do
- expect { add_tag }.to raise_error(Gitlab::Git::PreReceiveError, "something failed")
- end
- end
-
context 'with internal error' do
before do
expect_any_instance_of(Gitaly::OperationService::Stub)
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 4c665d6ea28..384d3d5c82e 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -2898,6 +2898,22 @@ RSpec.describe Group, feature_category: :subgroups do
end
end
+ describe "#access_level_roles" do
+ let(:group) { create(:group) }
+
+ it "returns the correct roles" do
+ expect(group.access_level_roles).to eq(
+ {
+ 'Guest' => 10,
+ 'Reporter' => 20,
+ 'Developer' => 30,
+ 'Maintainer' => 40,
+ 'Owner' => 50
+ }
+ )
+ end
+ end
+
describe '#membership_locked?' do
it 'returns false' do
expect(build(:group)).not_to be_membership_locked