diff --git a/app/assets/javascripts/token_access/constants.js b/app/assets/javascripts/token_access/constants.js
index 80c013b2ed4..7fb9187b78d 100644
--- a/app/assets/javascripts/token_access/constants.js
+++ b/app/assets/javascripts/token_access/constants.js
@@ -148,3 +148,5 @@ export const JOB_TOKEN_POLICIES = keyBy(
export const JOB_TOKEN_FORM_ADD_GROUP_OR_PROJECT = 'JOB_TOKEN_FORM_ADD_GROUP_OR_PROJECT';
export const JOB_TOKEN_FORM_AUTOPOPULATE_AUTH_LOG = 'JOB_TOKEN_FORM_AUTOPOPULATE_AUTH_LOG';
+export const JOB_TOKEN_REMOVE_AUTOPOPULATED_ENTRIES_MODAL =
+ 'JOB_TOKEN_REMOVE_AUTOPOPULATED_ENTRIES_MODAL';
diff --git a/app/assets/javascripts/token_access/graphql/mutations/remove_autopopulated_entries.mutation.graphql b/app/assets/javascripts/token_access/graphql/mutations/remove_autopopulated_entries.mutation.graphql
new file mode 100644
index 00000000000..4e73e54ef4f
--- /dev/null
+++ b/app/assets/javascripts/token_access/graphql/mutations/remove_autopopulated_entries.mutation.graphql
@@ -0,0 +1,6 @@
+mutation CiJobTokenScopeClearAllowlistAutopopulations($projectPath: ID!) {
+ ciJobTokenScopeClearAllowlistAutopopulations(input: { projectPath: $projectPath }) {
+ status
+ errors
+ }
+}
diff --git a/app/assets/javascripts/work_items/graphql/list/get_work_items.query.graphql b/app/assets/javascripts/work_items/graphql/list/get_work_items.query.graphql
index ec785d9d639..5a76a0f8c0a 100644
--- a/app/assets/javascripts/work_items/graphql/list/get_work_items.query.graphql
+++ b/app/assets/javascripts/work_items/graphql/list/get_work_items.query.graphql
@@ -65,6 +65,7 @@ query getWorkItems(
webPath
}
closedAt
+ userDiscussionsCount
confidential
createdAt
iid
@@ -126,6 +127,7 @@ query getWorkItems(
webPath
}
closedAt
+ userDiscussionsCount
confidential
createdAt
iid
diff --git a/app/assets/stylesheets/page_bundles/search.scss b/app/assets/stylesheets/page_bundles/search.scss
index 9e32f9af3d7..7ea2e4682a9 100644
--- a/app/assets/stylesheets/page_bundles/search.scss
+++ b/app/assets/stylesheets/page_bundles/search.scss
@@ -386,3 +386,7 @@ input[type='search'] {
}
}
/* stylelint-enable property-no-vendor-prefix */
+
+.description.term p:last-child {
+ @apply gl-m-0;
+}
diff --git a/app/views/search/results/_milestone.html.haml b/app/views/search/results/_milestone.html.haml
index 25e55225602..fb3d6a3fb6c 100644
--- a/app/views/search/results/_milestone.html.haml
+++ b/app/views/search/results/_milestone.html.haml
@@ -3,11 +3,11 @@
- position = index + 1
.search-result-row
- = link_to project_milestone_path(milestone.project, milestone), class: 'gl-font-bold gl-text-default', data: { event_tracking: 'click_search_result', event_label: @scope, event_value: position } do
+ = link_to project_milestone_path(milestone.project, milestone), class: 'gl-font-bold', data: { event_tracking: 'click_search_result', event_label: @scope, event_value: position } do
%span.term.str-truncated= simple_search_highlight_and_truncate(milestone.title, @search_term)
- if milestone.project_milestone?
- .gl-mt-2= gl_badge_tag milestone.project.full_name, { variant: :muted }, { class: 'gl-whitespace-normal gl-text-left' }
+ .gl-mt-2.gl-text-subtle.gl-whitespace-normal.gl-text-left.gl-text-sm= milestone.project.full_name
- if milestone.description.present?
.description.term
diff --git a/config/feature_flags/beta/virtual_registry_maven_cleanup_new_worker_class.yml b/config/feature_flags/beta/virtual_registry_maven_cleanup_new_worker_class.yml
index 33ac5ebfe31..42bf61e437c 100644
--- a/config/feature_flags/beta/virtual_registry_maven_cleanup_new_worker_class.yml
+++ b/config/feature_flags/beta/virtual_registry_maven_cleanup_new_worker_class.yml
@@ -6,4 +6,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/513796
milestone: '17.9'
group: group::package registry
type: beta
-default_enabled: false
+default_enabled: true
diff --git a/config/feature_flags/development/use_typhoeus_elasticsearch_adapter.yml b/config/feature_flags/development/use_typhoeus_elasticsearch_adapter.yml
deleted file mode 100644
index ac762395cd2..00000000000
--- a/config/feature_flags/development/use_typhoeus_elasticsearch_adapter.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: use_typhoeus_elasticsearch_adapter
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/76879
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/348607
-milestone: '14.7'
-type: development
-group: group::global search
-default_enabled: false
diff --git a/doc/administration/troubleshooting/postgresql.md b/doc/administration/troubleshooting/postgresql.md
index c8403113caf..6d0c0d605a7 100644
--- a/doc/administration/troubleshooting/postgresql.md
+++ b/doc/administration/troubleshooting/postgresql.md
@@ -115,7 +115,7 @@ This section is for links to information elsewhere in the GitLab documentation.
- Including [troubleshooting](../postgresql/replication_and_failover_troubleshooting.md)
`gitlab-ctl patroni check-leader` and PgBouncer errors.
-- [Developer database documentation](../../development/feature_development.md#database-guides),
+- [Developer database documentation](../../development/database/_index.md),
some of which is absolutely not for production use. Including:
- Understanding EXPLAIN plans.
diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md
index e51710aa45e..817e7a087bc 100644
--- a/doc/ci/docker/using_docker_build.md
+++ b/doc/ci/docker/using_docker_build.md
@@ -944,3 +944,60 @@ The opposite configuration (`docker:24.0.5-dind` service and Docker Engine on th
19.06.x or older) works without problems. For the best strategy, you should to frequently test and update
job environment versions to the newest. This brings new features, improved security and - for this specific
case - makes the upgrade on the underlying Docker Engine on the runner's host transparent for the job.
+
+### Error: `failed to verify certificate: x509: certificate signed by unknown authority`
+
+This error can appear when Docker commands like `docker build` or `docker pull` are executed in a Docker-in-Docker
+environment where custom or private certificates are used (for example, Zscaler certificates):
+
+```plaintext
+error pulling image configuration: download failed after attempts=6: tls: failed to verify certificate: x509: certificate signed by unknown authority
+```
+
+This error occurs because Docker commands in a Docker-in-Docker environment
+use two separate containers:
+
+- The **build container** runs the Docker client (`/usr/bin/docker`) and executes your job's script commands.
+- The **service container** (often named `svc`) runs the Docker daemon that processes most Docker commands.
+
+When your organization uses custom certificates, both containers need these certificates.
+Without proper certificate configuration in both containers, Docker operations that connect to external
+registries or services will fail with certificate errors.
+
+To resolve this issue:
+
+1. Store your root certificate as a [CI/CD variable](../variables/_index.md#define-a-cicd-variable-in-the-ui) named `CA_CERTIFICATE`.
+ The certificate should be in this format:
+
+ ```plaintext
+ -----BEGIN CERTIFICATE-----
+ (certificate content)
+ -----END CERTIFICATE-----
+ ```
+
+1. Configure your pipeline to install the certificate in the service container before starting the Docker daemon. For example:
+
+ ```yaml
+ image_build:
+ stage: build
+ image:
+ name: docker:19.03
+ variables:
+ DOCKER_HOST: tcp://localhost:2375
+ DOCKER_TLS_CERTDIR: ""
+ CA_CERTIFICATE: "$CA_CERTIFICATE"
+ services:
+ - name: docker:19.03-dind
+ command:
+ - /bin/sh
+ - -c
+ - |
+ echo "$CA_CERTIFICATE" > /usr/local/share/ca-certificates/custom-ca.crt && \
+ update-ca-certificates && \
+ dockerd-entrypoint.sh || exit
+ script:
+ - docker info
+ - docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD $DOCKER_REGISTRY
+ - docker build -t "${DOCKER_REGISTRY}/my-app:${CI_COMMIT_REF_NAME}" .
+ - docker push "${DOCKER_REGISTRY}/my-app:${CI_COMMIT_REF_NAME}"
+ ```
diff --git a/doc/development/data_retention_policies.md b/doc/development/data_retention_policies.md
new file mode 100644
index 00000000000..25c2c5cedb6
--- /dev/null
+++ b/doc/development/data_retention_policies.md
@@ -0,0 +1,71 @@
+---
+stage: none
+group: unassigned
+info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/ee/development/development_processes.html#development-guidelines-review.
+---
+
+
+# Data Retention Guidelines for Feature Development
+
+## Overview
+
+Data retention is a critical aspect of feature development at GitLab. As we build and maintain features, we must consider the lifecycle of the data we collect and store. This document outlines the guidelines for incorporating data retention considerations into feature development from the outset.
+
+## Why data retention matters
+
+- **System performance**: Time-based data organization enables better query optimization and efficient data access patterns, leading to faster response times and improved system scalability.
+- **Infrastructure cost**: Strategic storage management through data lifecycle policies reduces infrastructure costs for primary storage, backups, and disaster recovery systems.
+- **Engineering efficiency**: Designing features with data retention in mind from the start makes development faster and more reliable by establishing clear data lifecycles, reducing technical debt and faster data migrations.
+
+## Guidelines for feature development
+
+### 1. Early planning
+
+When designing new features, consider data retention requirements during the initial planning phase:
+
+- Document the types of data being persisted. Is this user-facing data?
+ Is it generated internally to make processing more efficient?
+ Is it derived/cache data?
+- Identify the business purpose and required retention period for each data type.
+- Define the product justification and customer usage pattern of older data.
+ How do people interact with older data as opposed to newer data?
+ How does the value change over time?
+- Consider regulatory requirements that might affect data retention (such as Personally Identifiable Information).
+- Plan for data removal or archival mechanisms.
+
+### 2. Design for data lifecycle
+
+Features should be designed with the understanding that data is not permanent:
+
+- Avoid assumptions about infinite data availability.
+- Implement graceful handling of missing or archived data.
+- Design user interfaces to clearly communicate data availability periods.
+- Design data structures for longer-term storage that is optimized to be viewed in a longer-term context.
+- Consider implementing "time to live" (TTL) mechanisms where appropriate, especially for derived/cache data
+ that can be gracefully reproduced on-demand.
+
+### 3. Documentation recommendations
+
+Each feature implementation must include:
+
+- Clear documentation of data retention periods (on GitLab.com and default values, if any)
+ and business reasoning/justification
+- Description of data removal/archival mechanisms.
+- Impact analysis of data removal on dependent features.
+
+## Implementation checklist
+
+Before submitting a merge request for a new feature:
+
+- [ ] Document data retention requirements.
+- [ ] Design data models with data removal in mind.
+- [ ] Implement data removal/archival mechanisms.
+- [ ] Test feature behavior with missing/archived data.
+- [ ] Include retention periods in user documentation.
+- [ ] Consider impact on dependent features.
+- [ ] Consider impact on backups/restores and export/import.
+- [ ] Consider impact on replication (eg Geo).
+
+## Related links
+
+- [Large tables limitations](../development/database/large_tables_limitations.md)
diff --git a/doc/development/database/large_tables_limitations.md b/doc/development/database/large_tables_limitations.md
index 4c9ac00acc5..b038d1ecba3 100644
--- a/doc/development/database/large_tables_limitations.md
+++ b/doc/development/database/large_tables_limitations.md
@@ -122,3 +122,4 @@ Disadvantages
- [Database size limits](https://handbook.gitlab.com/handbook/engineering/architecture/design-documents/database_size_limits/#solutions)
- [Adding database indexes](adding_database_indexes.md)
- [Database layout and access patterns](layout_and_access_patterns.md#data-model-trade-offs)
+- [Data retention guidelines for feature development](../data_retention_policies.md)
diff --git a/doc/development/feature_development.md b/doc/development/feature_development.md
index 910eb236e73..609ef25c317 100644
--- a/doc/development/feature_development.md
+++ b/doc/development/feature_development.md
@@ -137,9 +137,11 @@ The following integration guides are internal. Some integrations require access
- [JSON guidelines](json.md) for how to handle JSON in a performant manner.
- [GraphQL API optimizations](api_graphql_styleguide.md#optimizations) for how to optimize GraphQL code.
-## Database guides
+## Data stores guides
-See [database guidelines](database/_index.md).
+- [Database guidelines](database/_index.md).
+- [Data retention policies](data_retention_policies.md)
+- [Gitaly guidelines](gitaly.md)
## Testing guides
diff --git a/doc/integration/exact_code_search/zoekt.md b/doc/integration/exact_code_search/zoekt.md
index f0e280f1d3c..97879b926ac 100644
--- a/doc/integration/exact_code_search/zoekt.md
+++ b/doc/integration/exact_code_search/zoekt.md
@@ -65,6 +65,21 @@ To enable [exact code search](../../user/search/exact_code_search.md) in GitLab:
1. Select the **Enable indexing for exact code search** and **Enable exact code search** checkboxes.
1. Select **Save changes**.
+## Check indexing status
+
+Prerequisites:
+
+- You must have administrator access to the instance.
+
+Indexing performance depends on the CPU and memory limits on the Zoekt indexer nodes.
+To check indexing status, in the Rails console, run the following command:
+
+```ruby
+Search::Zoekt::Index.group(:state).count
+Search::Zoekt::Repository.group(:state).count
+Search::Zoekt::Task.group(:state).count
+```
+
## Delete offline nodes automatically
Prerequisites:
@@ -97,6 +112,13 @@ To index all root namespaces automatically:
1. Select the **Index root namespaces automatically** checkbox.
1. Select **Save changes**.
+When you enable this setting, GitLab creates indexing tasks for all projects in:
+
+- All groups and subgroups
+- Any new root namespace
+
+After a project is indexed, GitLab creates only incremental indexing when a repository change is detected.
+
When you disable this setting:
- Existing root namespaces remain indexed.
diff --git a/doc/user/group/import/direct_transfer_migrations.md b/doc/user/group/import/direct_transfer_migrations.md
index ba87c3d1379..e70f4459eaa 100644
--- a/doc/user/group/import/direct_transfer_migrations.md
+++ b/doc/user/group/import/direct_transfer_migrations.md
@@ -35,10 +35,12 @@ If there are any problems, you can:
Before migrating by using direct transfer, see the following prerequisites.
-### Network
+### Network and storage space
- The network connection between instances or GitLab.com must support HTTPS.
- Firewalls must not block the connection between the source and destination GitLab instances.
+- The source and destination GitLab instances must have enough free space in the `/tmp` directory
+ to create and extract archives of transferred projects and groups.
### Versions
diff --git a/doc/user/group/manage.md b/doc/user/group/manage.md
index 8cd715d4bcc..ff8270e2965 100644
--- a/doc/user/group/manage.md
+++ b/doc/user/group/manage.md
@@ -184,7 +184,7 @@ To disable diff previews for all projects in a group:
{{< history >}}
-- Notifications to inherited group members [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/463016) in GitLab 17.7 [with a flag](../../administration/feature_flags.md) named `pat_expiry_inherited_members_notification`. Disabled by default.
+- Notifications to inherited group members [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/463016) in GitLab 17.7 [with a flag](../../administration/feature_flags.md) named `extended_expiry_webhook_execution_setting`. Disabled by default.
{{< /history >}}
diff --git a/doc/user/group/saml_sso/_index.md b/doc/user/group/saml_sso/_index.md
index 5413652eec1..9002ab69302 100644
--- a/doc/user/group/saml_sso/_index.md
+++ b/doc/user/group/saml_sso/_index.md
@@ -351,7 +351,7 @@ are then redirected to sign in through the identity provider.
GitLab.com uses the SAML **NameID** to identify users. The **NameID** is:
- A required field in the SAML response.
-- Case sensitive.
+- Case-insensitive.
The **NameID** must:
diff --git a/doc/user/project/import/_index.md b/doc/user/project/import/_index.md
index 8f6d1ed941d..b93f49e05c3 100644
--- a/doc/user/project/import/_index.md
+++ b/doc/user/project/import/_index.md
@@ -274,10 +274,13 @@ Users with the Owner role for a top-level group can reassign contributions and m
from placeholder users to existing active (non-bot) users.
On the destination instance, users with the Owner role for a top-level group can:
-- Request users to accept reassignment of contributions and membership [in the UI](#request-reassignment-in-ui).
- The reassignment process starts only after the selected user [accepts the reassignment request](#accept-contribution-reassignment),
- which is sent to them by email.
-- Choose not to reassign contributions and memberships, and [keep them with placeholder users](#keep-as-placeholder).
+- Request users to review reassignment of contributions and memberships [in the UI](#request-reassignment-in-ui)
+ or [through a CSV file](#request-reassignment-by-using-a-csv-file).
+ For a large number of placeholder users, you should use a CSV file.
+ In both cases, users receive a request by email to accept or reject the reassignment.
+ The reassignment starts only after the selected user
+ [accepts the reassignment request](#accept-contribution-reassignment).
+- Choose not to reassign contributions and memberships and [keep them assigned to placeholder users](#keep-as-placeholder).
All the contributions initially assigned to a single placeholder user can only be reassigned to a single active regular
user on the destination instance. The contributions assigned to a single placeholder user cannot be split among multiple
@@ -353,6 +356,56 @@ Contributions of only one placeholder user can be reassigned to an active non-bo
Before a user accepts the reassignment, you can [cancel the request](#cancel-reassignment-request).
+#### Request reassignment by using a CSV file
+
+{{< history >}}
+
+- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/455901) in GitLab 17.10 [with a flag](../../../administration/feature_flags.md) named `importer_user_mapping_reassignment_csv`. Disabled by default.
+
+{{< /history >}}
+
+{{< alert type="flag" >}}
+
+The availability of this feature is controlled by a feature flag.
+For more information, see the history.
+This feature is available for testing, but not ready for production use.
+
+{{< /alert >}}
+
+Prerequisites:
+
+- You must have the Owner role for the group.
+
+For a large number of placeholder users, you might want to
+reassign contributions and memberships by using a CSV file.
+You can download a prefilled CSV template with the following information.
+For example:
+
+| Source host | Import type | Source user identifier | Source user name | Source username |
+|----------------------|-------------|------------------------|------------------|-----------------|
+| `gitlab.example.com` | `gitlab` | `alice` | `Alice Coder` | `a.coer` |
+
+Do not update **Source host**, **Import type**, or **Source user identifier**.
+This information locates the corresponding database record
+after you've uploaded the completed CSV file.
+**Source user name** and **Source username** identify the source user
+and are not used after you've uploaded the CSV file.
+
+To request reassignment of contributions and memberships by using a CSV file:
+
+1. On the left sidebar, select **Search or go to** and find your group.
+1. Select **Manage > Members**.
+1. Select the **Placeholders** tab.
+1. Select **Reassign with CSV**.
+1. Download the prefilled CSV template.
+1. In **GitLab username** or **GitLab public email**, enter the username or email address
+ of the GitLab user on the destination instance.
+ You can use only public email addresses for reassignment.
+1. Upload the completed CSV file.
+1. Select **Reassign**.
+
+Users receive an email to review and accept any contributions reassigned to them.
+
#### Keep as placeholder
You might not want to reassign contributions and memberships to users on the destination instance. For example, you
diff --git a/doc/user/project/settings/_index.md b/doc/user/project/settings/_index.md
index 736dafabac8..1e5e684828a 100644
--- a/doc/user/project/settings/_index.md
+++ b/doc/user/project/settings/_index.md
@@ -170,7 +170,7 @@ To set this default:
{{< history >}}
-- [Added](https://gitlab.com/gitlab-org/gitlab/-/issues/463016) 60 day and 30 days triggers to project and group access tokens webhooks in GitLab 17.9 [with a flag](../../../administration/feature_flags.md) named `pat_expiry_inherited_members_notification`. Disabled by default.
+- [Added](https://gitlab.com/gitlab-org/gitlab/-/issues/463016) 60 day and 30 days triggers to project and group access tokens webhooks in GitLab 17.9 [with a flag](../../../administration/feature_flags.md) named `extended_expiry_webhook_execution_setting`. Disabled by default.
{{< /history >}}
diff --git a/doc/user/workspace/configuration.md b/doc/user/workspace/configuration.md
index 6a62808aac0..f96b888d8a2 100644
--- a/doc/user/workspace/configuration.md
+++ b/doc/user/workspace/configuration.md
@@ -112,57 +112,64 @@ For more information, see [`image_pull_secrets`](settings.md#image_pull_secrets)
{{< /history >}}
-Prerequisites:
-
-- Ensure the container images used in the devfile support [arbitrary user IDs](_index.md#arbitrary-user-ids).
- Sudo access for a workspace does not mean that the container image used
- in a [devfile](_index.md#devfile) can run with a user ID of `0`.
-
-A development environment often requires sudo permissions to
-install, configure, and use dependencies during runtime.
-You can configure secure sudo access for a workspace with:
+Development environments often require sudo permissions to install, configure, and use dependencies
+during runtime. You can configure sudo access for a workspace with:
- [Sysbox](#with-sysbox)
- [Kata Containers](#with-kata-containers)
- [User namespaces](#with-user-namespaces)
+Prerequisites:
+
+- Your container images must support [arbitrary user IDs](_index.md#arbitrary-user-ids).
+ Even with sudo access configured, container images used in a [devfile](_index.md#devfile)
+ cannot run with a user ID of `0`.
+
### With Sysbox
[Sysbox](https://github.com/nestybox/sysbox) is a container runtime that improves container isolation
and enables containers to run the same workloads as virtual machines.
-To configure sudo access for a workspace with Sysbox:
+To configure sudo access with Sysbox:
-1. In the Kubernetes cluster, [install Sysbox](https://github.com/nestybox/sysbox#installation).
-1. In the GitLab agent for workspaces:
- - Set [`default_runtime_class`](settings.md#default_runtime_class) to the runtime class
- of Sysbox (for example, `sysbox-runc`).
- - Set [`allow_privilege_escalation`](settings.md#allow_privilege_escalation) to `true`.
- - Set [`annotations`](settings.md#annotations) to `{"io.kubernetes.cri-o.userns-mode": "auto:size=65536"}`.
+1. In your Kubernetes cluster, [install Sysbox](https://github.com/nestybox/sysbox#installation).
+1. Configure the GitLab agent for workspaces:
+
+ - Set the default runtime class. In [`default_runtime_class`](settings.md#default_runtime_class),
+ enter the runtime class for Sysbox. For example, `sysbox-runc`.
+ - Enable privilege escalation.
+ Set [`allow_privilege_escalation`](settings.md#allow_privilege_escalation) to `true`.
+ - Configure the annotations required by Sysbox. Set [`annotations`](settings.md#annotations) to
+ `{"io.kubernetes.cri-o.userns-mode": "auto:size=65536"}`.
### With Kata Containers
-[Kata Containers](https://github.com/kata-containers/kata-containers) is a standard implementation of lightweight
-virtual machines that perform like containers but provide the workload isolation and security of virtual machines.
+[Kata Containers](https://github.com/kata-containers/kata-containers) is a standard implementation
+of lightweight virtual machines that perform like containers but provide the workload isolation and
+security of virtual machines.
-To configure sudo access for a workspace with Kata Containers:
+To configure sudo access with Kata Containers:
-1. In the Kubernetes cluster, [install Kata Containers](https://github.com/kata-containers/kata-containers/tree/main/docs/install).
-1. In the GitLab agent for workspaces:
- - Set [`default_runtime_class`](settings.md#default_runtime_class) to one of the runtime classes
- of Kata Containers (for example, `kata-qemu`).
- - Set [`allow_privilege_escalation`](settings.md#allow_privilege_escalation) to `true`.
+1. In your Kubernetes cluster, [install Kata Containers](https://github.com/kata-containers/kata-containers/tree/main/docs/install).
+1. Configure the GitLab agent for workspaces:
+
+ - Set the default runtime class. In [`default_runtime_class`](settings.md#default_runtime_class),
+ enter the runtime class for Kata Containers. For example, `kata-qemu`.
+ - Enable privilege escalation.
+ Set [`allow_privilege_escalation`](settings.md#allow_privilege_escalation) to `true`.
### With user namespaces
-[User namespaces](https://kubernetes.io/docs/concepts/workloads/pods/user-namespaces/) isolate the user
-running inside the container from the user on the host.
+[User namespaces](https://kubernetes.io/docs/concepts/workloads/pods/user-namespaces/) isolate
+container users from host users.
-To configure sudo access for a workspace with user namespaces:
+To configure sudo access with user namespaces:
-1. In the Kubernetes cluster, [configure user namespaces](https://kubernetes.io/blog/2024/04/22/userns-beta/).
-1. In the GitLab agent for workspaces, set [`use_kubernetes_user_namespaces`](settings.md#use_kubernetes_user_namespaces)
- and [`allow_privilege_escalation`](settings.md#allow_privilege_escalation) to `true`.
+1. In your Kubernetes cluster, [configure user namespaces](https://kubernetes.io/blog/2024/04/22/userns-beta/).
+1. Configure the GitLab agent for workspaces:
+
+ - Set [`use_kubernetes_user_namespaces`](settings.md#use_kubernetes_user_namespaces) to `true`.
+ - Set [`allow_privilege_escalation`](settings.md#allow_privilege_escalation) to `true`.
## Build and run containers in a workspace
diff --git a/gems/gitlab-active-context/lib/active_context/databases/elasticsearch/client.rb b/gems/gitlab-active-context/lib/active_context/databases/elasticsearch/client.rb
index d885a6751f9..f1f0dbdb1b6 100644
--- a/gems/gitlab-active-context/lib/active_context/databases/elasticsearch/client.rb
+++ b/gems/gitlab-active-context/lib/active_context/databases/elasticsearch/client.rb
@@ -31,7 +31,7 @@ module ActiveContext
def elasticsearch_config
{
- adapter: :net_http,
+ adapter: :typhoeus,
urls: options[:url],
transport_options: {
request: {
diff --git a/gems/gitlab-active-context/lib/active_context/databases/opensearch/client.rb b/gems/gitlab-active-context/lib/active_context/databases/opensearch/client.rb
index aaedc14dbaa..fc7fca9b6c0 100644
--- a/gems/gitlab-active-context/lib/active_context/databases/opensearch/client.rb
+++ b/gems/gitlab-active-context/lib/active_context/databases/opensearch/client.rb
@@ -49,7 +49,7 @@ module ActiveContext
def opensearch_config
{
- adapter: :net_http,
+ adapter: :typhoeus,
urls: options[:url],
transport_options: {
request: {
diff --git a/gems/gitlab-active-context/spec/lib/active_context/databases/elasticsearch/client_spec.rb b/gems/gitlab-active-context/spec/lib/active_context/databases/elasticsearch/client_spec.rb
index 52d31479046..bca4a44a60f 100644
--- a/gems/gitlab-active-context/spec/lib/active_context/databases/elasticsearch/client_spec.rb
+++ b/gems/gitlab-active-context/spec/lib/active_context/databases/elasticsearch/client_spec.rb
@@ -37,7 +37,7 @@ RSpec.describe ActiveContext::Databases::Elasticsearch::Client do
it 'includes all expected keys with correct values' do
expect(elasticsearch_config).to include(
- adapter: :net_http,
+ adapter: :typhoeus,
urls: 'http://localhost:9200',
transport_options: {
request: {
diff --git a/gems/gitlab-active-context/spec/lib/active_context/databases/opensearch/client_spec.rb b/gems/gitlab-active-context/spec/lib/active_context/databases/opensearch/client_spec.rb
index ee81655a295..23a4e2a15fe 100644
--- a/gems/gitlab-active-context/spec/lib/active_context/databases/opensearch/client_spec.rb
+++ b/gems/gitlab-active-context/spec/lib/active_context/databases/opensearch/client_spec.rb
@@ -32,6 +32,21 @@ RSpec.describe ActiveContext::Databases::Opensearch::Client do
end
end
+ describe '#opensearch_config' do
+ it 'returns correct configuration hash' do
+ config = client.send(:opensearch_config)
+
+ expect(config).to include(
+ urls: options[:url],
+ randomize_hosts: true
+ )
+ expect(config[:transport_options][:request]).to include(
+ timeout: options[:client_request_timeout],
+ open_timeout: described_class::OPEN_TIMEOUT
+ )
+ end
+ end
+
describe '#aws_credentials' do
context 'when static credentials are provided' do
let(:options) do
diff --git a/lib/gitlab/ci/trace.rb b/lib/gitlab/ci/trace.rb
index 846987a0ac8..c8c632b9ee2 100644
--- a/lib/gitlab/ci/trace.rb
+++ b/lib/gitlab/ci/trace.rb
@@ -245,9 +245,7 @@ module Gitlab
end
def ensure_directory
- unless Dir.exist?(default_directory)
- FileUtils.mkdir_p(default_directory)
- end
+ FileUtils.mkdir_p(default_directory)
end
def current_path
diff --git a/lib/gitlab/database/migrations/test_batched_background_runner.rb b/lib/gitlab/database/migrations/test_batched_background_runner.rb
index d092448eafa..8c66836b436 100644
--- a/lib/gitlab/database/migrations/test_batched_background_runner.rb
+++ b/lib/gitlab/database/migrations/test_batched_background_runner.rb
@@ -194,7 +194,7 @@ module Gitlab
def export_migration_details(migration_name, attributes)
directory = result_dir.join(migration_name)
- FileUtils.mkdir_p(directory) unless Dir.exist?(directory)
+ FileUtils.mkdir_p(directory)
File.write(directory.join(MIGRATION_DETAILS_FILE_NAME), attributes.to_json)
end
diff --git a/lib/gitlab/gpg.rb b/lib/gitlab/gpg.rb
index 5a4c375d872..fdcc7edc4bc 100644
--- a/lib/gitlab/gpg.rb
+++ b/lib/gitlab/gpg.rb
@@ -131,7 +131,7 @@ module Gitlab
# 15 tries will never complete within the maximum time with exponential
# backoff. So our limit is the runtime, not the number of tries.
Retriable.retriable(max_elapsed_time: cleanup_time, base_interval: 0.1, tries: 15) do
- FileUtils.remove_entry(tmp_dir) if File.exist?(tmp_dir)
+ FileUtils.rm_rf(tmp_dir)
end
rescue StandardError => e
raise CleanupError, e
diff --git a/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy.rb b/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy.rb
index dabb0cb08e2..95e04a77a2b 100644
--- a/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy.rb
+++ b/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy.rb
@@ -65,7 +65,7 @@ module Gitlab
end
def ensure_lock_files_path!
- FileUtils.mkdir_p(lock_files_path) unless Dir.exist?(lock_files_path)
+ FileUtils.mkdir_p(lock_files_path)
end
def lock_files_path
diff --git a/lib/gitlab/import_export/recursive_merge_folders.rb b/lib/gitlab/import_export/recursive_merge_folders.rb
index e6eba60db93..7292d9ba08e 100644
--- a/lib/gitlab/import_export/recursive_merge_folders.rb
+++ b/lib/gitlab/import_export/recursive_merge_folders.rb
@@ -60,7 +60,7 @@ module Gitlab
next if Gitlab::Utils::FileInfo.linked?(source_child)
if File.directory?(source_child)
- FileUtils.mkdir_p(target_child, mode: DEFAULT_DIR_MODE) unless File.exist?(target_child)
+ FileUtils.mkdir_p(target_child, mode: DEFAULT_DIR_MODE)
recursive_merge(source_child, target_child)
else
FileUtils.mv(source_child, target_child)
diff --git a/lib/gitlab/memory/upload_and_cleanup_reports.rb b/lib/gitlab/memory/upload_and_cleanup_reports.rb
index 27d94df478c..010c7ff0c1f 100644
--- a/lib/gitlab/memory/upload_and_cleanup_reports.rb
+++ b/lib/gitlab/memory/upload_and_cleanup_reports.rb
@@ -41,7 +41,7 @@ module Gitlab
end
def cleanup!(path)
- File.unlink(path) if File.exist?(path)
+ FileUtils.rm_f(path)
rescue Errno::ENOENT
# Path does not exist: Ignore. We already check `File.exist?`. Rescue to be extra safe.
end
diff --git a/lib/tasks/gitlab/update_templates.rake b/lib/tasks/gitlab/update_templates.rake
index 51b4707c9b1..7b48cdbed63 100644
--- a/lib/tasks/gitlab/update_templates.rake
+++ b/lib/tasks/gitlab/update_templates.rake
@@ -143,7 +143,7 @@ namespace :gitlab do
end
def clone_repository(url, directory)
- FileUtils.rm_rf(directory) if Dir.exist?(directory)
+ FileUtils.rm_rf(directory)
system("git clone #{url} --depth=1 --branch=master #{directory}")
end
diff --git a/lib/tasks/tanuki_emoji.rake b/lib/tasks/tanuki_emoji.rake
index 0e759261f20..2ed2bc35428 100644
--- a/lib/tasks/tanuki_emoji.rake
+++ b/lib/tasks/tanuki_emoji.rake
@@ -80,7 +80,7 @@ namespace :tanuki_emoji do
puts "Importing emojis into: #{emoji_dir} ..."
# Re-create the assets folder and copy emojis renaming them to use name instead of unicode hex
- FileUtils.rm_rf(emoji_dir) if Dir.exist?(emoji_dir)
+ FileUtils.rm_rf(emoji_dir)
FileUtils.mkdir_p(emoji_dir, mode: 0700)
TanukiEmoji.index.all.find_each do |emoji|
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index e687feddbc5..6bf076b32c3 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -9078,6 +9078,9 @@ msgstr ""
msgid "Batch size"
msgstr ""
+msgid "Batch size of namespaces for initial indexing"
+msgstr ""
+
msgid "Batched Job|Background migrations"
msgstr ""
@@ -11240,6 +11243,9 @@ msgstr ""
msgid "CICD|An error occurred while adding the authentication log entries. Please try again."
msgstr ""
+msgid "CICD|An error occurred while removing the auto-added log entries. Please try again."
+msgstr ""
+
msgid "CICD|Are you sure you want to remove %{namespace} from the job token allowlist?"
msgstr ""
@@ -11255,6 +11261,9 @@ msgstr ""
msgid "CICD|Authentication log entries were successfully added to the allowlist."
msgstr ""
+msgid "CICD|Authentication log entries were successfully removed from the allowlist."
+msgstr ""
+
msgid "CICD|Authorized groups and projects"
msgstr ""
@@ -11354,9 +11363,18 @@ msgstr ""
msgid "CICD|Prevent CI/CD job tokens from this project from being used to access other projects unless the other project is added to the allowlist. It is a security risk to disable this feature, because unauthorized projects might attempt to retrieve an active token and access the API. %{linkStart}Learn more%{linkEnd}."
msgstr ""
+msgid "CICD|Remove all auto-added allowlist entries"
+msgstr ""
+
msgid "CICD|Remove group or project"
msgstr ""
+msgid "CICD|Removing auto-added allowlist entries. Please wait while the action completes."
+msgstr ""
+
+msgid "CICD|Removing these entries could cause authentication failures or disrupt pipelines."
+msgstr ""
+
msgid "CICD|Select the groups and projects authorized to use a CI/CD job token to authenticate requests to this project. %{linkStart}Learn more%{linkEnd}."
msgstr ""
@@ -11387,6 +11405,9 @@ msgstr ""
msgid "CICD|There was a problem fetching authorization logs count."
msgstr ""
+msgid "CICD|This action removes all groups and projects that were auto-added from the authentication log."
+msgstr ""
+
msgid "CICD|Unprotected branches will not have access to the cache from protected branches."
msgstr ""
@@ -47964,6 +47985,9 @@ msgstr ""
msgid "Remove email participants"
msgstr ""
+msgid "Remove entries"
+msgstr ""
+
msgid "Remove favicon"
msgstr ""
diff --git a/package.json b/package.json
index c4fbdc2ad44..f542bc17668 100644
--- a/package.json
+++ b/package.json
@@ -63,7 +63,7 @@
"@gitlab/fonts": "^1.3.0",
"@gitlab/query-language-rust": "0.4.0",
"@gitlab/svgs": "3.123.0",
- "@gitlab/ui": "108.4.1",
+ "@gitlab/ui": "108.6.0",
"@gitlab/vue-router-vue3": "npm:vue-router@4.5.0",
"@gitlab/vuex-vue3": "npm:vuex@4.1.0",
"@gitlab/web-ide": "^0.0.1-dev-20250211142744",
diff --git a/scripts/cells/application-settings-analysis.rb b/scripts/cells/application-settings-analysis.rb
index fdbad40282c..731172e50c9 100755
--- a/scripts/cells/application-settings-analysis.rb
+++ b/scripts/cells/application-settings-analysis.rb
@@ -278,6 +278,7 @@ class ApplicationSettingsAnalysis
vertex_ai_project
web_ide_oauth_application_id
zoekt_cpu_to_tasks_ratio
+ zoekt_rollout_batch_size
zoekt_indexing_enabled
zoekt_search_enabled
zoekt_settings
diff --git a/spec/frontend/token_access/autopopulate_allowlist_modal_spec.js b/spec/frontend/token_access/autopopulate_allowlist_modal_spec.js
index 2a3a59f53d2..e0eac4f7236 100644
--- a/spec/frontend/token_access/autopopulate_allowlist_modal_spec.js
+++ b/spec/frontend/token_access/autopopulate_allowlist_modal_spec.js
@@ -87,12 +87,29 @@ describe('AutopopulateAllowlistModal component', () => {
);
});
- // TODO: Test for help link
- // See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181294
it('renders help link', () => {
expect(findLink().text()).toBe('What is the compaction algorithm?');
+ expect(findLink().attributes('href')).toBe(
+ '/help/ci/jobs/ci_job_token#auto-populate-a-projects-allowlist',
+ );
});
});
+
+ it.each`
+ modalEvent | emittedEvent
+ ${'canceled'} | ${'hide'}
+ ${'hidden'} | ${'hide'}
+ ${'secondary'} | ${'hide'}
+ `(
+ 'emits the $emittedEvent event when $modalEvent event is triggered',
+ ({ modalEvent, emittedEvent }) => {
+ expect(wrapper.emitted(emittedEvent)).toBeUndefined();
+
+ findModal().vm.$emit(modalEvent);
+
+ expect(wrapper.emitted(emittedEvent)).toHaveLength(1);
+ },
+ );
});
describe('when mutation is running', () => {
diff --git a/spec/frontend/token_access/inbound_token_access_spec.js b/spec/frontend/token_access/inbound_token_access_spec.js
index 95ed442b1e1..b3c9aabcc8c 100644
--- a/spec/frontend/token_access/inbound_token_access_spec.js
+++ b/spec/frontend/token_access/inbound_token_access_spec.js
@@ -1,4 +1,9 @@
-import { GlAlert, GlLoadingIcon, GlFormRadioGroup } from '@gitlab/ui';
+import {
+ GlAlert,
+ GlDisclosureDropdown,
+ GlDisclosureDropdownItem,
+ GlFormRadioGroup,
+} from '@gitlab/ui';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
@@ -12,6 +17,7 @@ import {
} from '~/token_access/constants';
import AutopopulateAllowlistModal from '~/token_access/components/autopopulate_allowlist_modal.vue';
import NamespaceForm from '~/token_access/components/namespace_form.vue';
+import RemoveAutopopulatedEntriesModal from '~/token_access/components/remove_autopopulated_entries_modal.vue';
import inboundRemoveGroupCIJobTokenScopeMutation from '~/token_access/graphql/mutations/inbound_remove_group_ci_job_token_scope.mutation.graphql';
import inboundRemoveProjectCIJobTokenScopeMutation from '~/token_access/graphql/mutations/inbound_remove_project_ci_job_token_scope.mutation.graphql';
import inboundUpdateCIJobTokenScopeMutation from '~/token_access/graphql/mutations/inbound_update_ci_job_token_scope.mutation.graphql';
@@ -19,6 +25,7 @@ import inboundGetCIJobTokenScopeQuery from '~/token_access/graphql/queries/inbou
import inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery from '~/token_access/graphql/queries/inbound_get_groups_and_projects_with_ci_job_token_scope.query.graphql';
import getAuthLogCountQuery from '~/token_access/graphql/queries/get_auth_log_count.query.graphql';
import getCiJobTokenScopeAllowlistQuery from '~/token_access/graphql/queries/get_ci_job_token_scope_allowlist.query.graphql';
+import removeAutopopulatedEntriesMutation from '~/token_access/graphql/mutations/remove_autopopulated_entries.mutation.graphql';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import ConfirmActionModal from '~/vue_shared/components/confirm_action_modal.vue';
import TokenAccessTable from '~/token_access/components/token_access_table.vue';
@@ -31,6 +38,7 @@ import {
inboundRemoveNamespaceSuccess,
inboundUpdateScopeSuccessResponse,
mockAuthLogsCountResponse,
+ mockRemoveAutopopulatedEntriesResponse,
} from './mock_data';
const projectPath = 'root/my-repo';
@@ -63,13 +71,22 @@ describe('TokenAccess component', () => {
const inboundUpdateScopeSuccessResponseHandler = jest
.fn()
.mockResolvedValue(inboundUpdateScopeSuccessResponse);
+ const removeAutopopulatedEntriesMutationHandler = jest
+ .fn()
+ .mockResolvedValue(mockRemoveAutopopulatedEntriesResponse());
+ const removeAutopopulatedEntriesMutationErrorHandler = jest
+ .fn()
+ .mockResolvedValue(mockRemoveAutopopulatedEntriesResponse({ errorMessage: message }));
const failureHandler = jest.fn().mockRejectedValue(error);
const mockToastShow = jest.fn();
const findAutopopulateAllowlistModal = () => wrapper.findComponent(AutopopulateAllowlistModal);
+ const findAutopopulationAlert = () => wrapper.findByTestId('autopopulation-alert');
+ const findAllowlistOptions = () => wrapper.findComponent(GlDisclosureDropdown);
+ const findAllowlistOption = (index) =>
+ wrapper.findAllComponents(GlDisclosureDropdownItem).at(index).find('button');
const findFormSelector = () => wrapper.findByTestId('form-selector');
const findRadioGroup = () => wrapper.findComponent(GlFormRadioGroup);
- const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findToggleFormBtn = () => wrapper.findByTestId('crud-form-toggle');
const findTokenDisabledAlert = () => wrapper.findComponent(GlAlert);
const findNamespaceForm = () => wrapper.findComponent(NamespaceForm);
@@ -78,6 +95,8 @@ describe('TokenAccess component', () => {
const findGroupCount = () => wrapper.findByTestId('group-count');
const findProjectCount = () => wrapper.findByTestId('project-count');
const findConfirmActionModal = () => wrapper.findComponent(ConfirmActionModal);
+ const findRemoveAutopopulatedEntriesModal = () =>
+ wrapper.findComponent(RemoveAutopopulatedEntriesModal);
const findTokenAccessTable = () => wrapper.findComponent(TokenAccessTable);
const createComponent = (
@@ -88,6 +107,7 @@ describe('TokenAccess component', () => {
enforceAllowlist = false,
projectAllowlistLimit = 2,
stubs = {},
+ isLoading = false,
} = {},
) => {
wrapper = shallowMountExtended(InboundTokenAccess, {
@@ -110,24 +130,30 @@ describe('TokenAccess component', () => {
},
});
- return waitForPromises();
+ if (!isLoading) {
+ return waitForPromises();
+ }
+
+ return Promise.resolve();
};
describe('loading state', () => {
it('shows loading state while waiting on query to resolve', async () => {
- createComponent([
- [inboundGetCIJobTokenScopeQuery, inboundJobTokenScopeEnabledResponseHandler],
+ createComponent(
[
- inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery,
- inboundGroupsAndProjectsWithScopeResponseHandler,
+ [inboundGetCIJobTokenScopeQuery, inboundJobTokenScopeEnabledResponseHandler],
+ [
+ inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery,
+ inboundGroupsAndProjectsWithScopeResponseHandler,
+ ],
],
- ]);
+ { isLoading: true },
+ );
- expect(findLoadingIcon().exists()).toBe(true);
+ await nextTick();
- await waitForPromises();
-
- expect(findLoadingIcon().exists()).toBe(false);
+ expect(findTokenAccessTable().props('loading')).toBe(true);
+ expect(findTokenAccessTable().props('loadingMessage')).toBe('');
});
});
@@ -448,8 +474,12 @@ describe('TokenAccess component', () => {
inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery,
inboundGroupsAndProjectsWithScopeResponseHandler,
],
+ [removeAutopopulatedEntriesMutation, removeAutopopulatedEntriesMutationHandler],
],
- { authenticationLogsMigrationForAllowlist: true, stubs: { CrudComponent } },
+ {
+ authenticationLogsMigrationForAllowlist: true,
+ stubs: { CrudComponent, GlDisclosureDropdown, GlDisclosureDropdownItem },
+ },
),
);
@@ -496,6 +526,132 @@ describe('TokenAccess component', () => {
expect(findFormSelector().props('selected')).toBe(null);
});
});
+
+ describe('remove autopopulated entries', () => {
+ const triggerRemoveEntries = () => {
+ findAllowlistOption(0).trigger('click');
+ findRemoveAutopopulatedEntriesModal().vm.$emit('remove-entries');
+ };
+
+ it('additional actions are available in the disclosure dropdown', () => {
+ expect(findAllowlistOptions().exists()).toBe(true);
+ });
+
+ it('"Remove only entries auto-added" renders the remove autopopulated entries modal', async () => {
+ expect(findRemoveAutopopulatedEntriesModal().props('showModal')).toBe(false);
+
+ findAllowlistOption(0).trigger('click');
+ await nextTick();
+
+ expect(findRemoveAutopopulatedEntriesModal().props('showModal')).toBe(true);
+ });
+
+ it('shows loading state while remove autopopulated entries mutation is processing', async () => {
+ expect(findCountLoadingIcon().exists()).toBe(false);
+ expect(findTokenAccessTable().props('loading')).toBe(false);
+
+ triggerRemoveEntries();
+
+ await nextTick();
+
+ expect(findCountLoadingIcon().exists()).toBe(true);
+ expect(findTokenAccessTable().props('loading')).toBe(true);
+ expect(findTokenAccessTable().props('loadingMessage')).toBe(
+ 'Removing auto-added allowlist entries. Please wait while the action completes.',
+ );
+ });
+
+ it('calls the remove autopopulated entries mutation and refetches allowlist', async () => {
+ expect(removeAutopopulatedEntriesMutationHandler).toHaveBeenCalledTimes(0);
+ expect(inboundGroupsAndProjectsWithScopeResponseHandler).toHaveBeenCalledTimes(1);
+
+ triggerRemoveEntries();
+ await waitForPromises();
+ await nextTick();
+
+ expect(removeAutopopulatedEntriesMutationHandler).toHaveBeenCalledTimes(1);
+ expect(inboundGroupsAndProjectsWithScopeResponseHandler).toHaveBeenCalledTimes(2);
+ });
+
+ it('shows toast message when mutation is successful', async () => {
+ triggerRemoveEntries();
+ await waitForPromises();
+ await nextTick();
+
+ expect(mockToastShow).toHaveBeenCalledWith(
+ 'Authentication log entries were successfully removed from the allowlist.',
+ );
+ });
+
+ it('shows error alert when mutation returns an error', async () => {
+ createComponent(
+ [
+ [inboundGetCIJobTokenScopeQuery, inboundJobTokenScopeEnabledResponseHandler],
+ [
+ inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery,
+ inboundGroupsAndProjectsWithScopeResponseHandler,
+ ],
+ [removeAutopopulatedEntriesMutation, removeAutopopulatedEntriesMutationErrorHandler],
+ ],
+ {
+ authenticationLogsMigrationForAllowlist: true,
+ stubs: { CrudComponent, GlDisclosureDropdown, GlDisclosureDropdownItem },
+ },
+ );
+
+ expect(findAutopopulationAlert().exists()).toBe(false);
+
+ triggerRemoveEntries();
+ await waitForPromises();
+ await nextTick();
+
+ expect(findAutopopulationAlert().text()).toBe('An error occurred');
+ });
+
+ it('shows error alert when mutation fails', async () => {
+ createComponent(
+ [
+ [inboundGetCIJobTokenScopeQuery, inboundJobTokenScopeEnabledResponseHandler],
+ [
+ inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery,
+ inboundGroupsAndProjectsWithScopeResponseHandler,
+ ],
+ [removeAutopopulatedEntriesMutation, failureHandler],
+ ],
+ {
+ authenticationLogsMigrationForAllowlist: true,
+ stubs: { CrudComponent, GlDisclosureDropdown, GlDisclosureDropdownItem },
+ },
+ );
+
+ expect(findAutopopulationAlert().exists()).toBe(false);
+
+ triggerRemoveEntries();
+ await waitForPromises();
+ await nextTick();
+
+ expect(findAutopopulationAlert().text()).toBe(
+ 'An error occurred while removing the auto-added log entries. Please try again.',
+ );
+ });
+
+ it('modal can be re-opened again after it closes', async () => {
+ findAllowlistOption(0).trigger('click');
+ await nextTick();
+
+ expect(findRemoveAutopopulatedEntriesModal().props('showModal')).toBe(true);
+
+ findRemoveAutopopulatedEntriesModal().vm.$emit('hide');
+ await nextTick();
+
+ expect(findRemoveAutopopulatedEntriesModal().props('showModal')).toBe(false);
+
+ findAllowlistOption(0).trigger('click');
+ await nextTick();
+
+ expect(findRemoveAutopopulatedEntriesModal().props('showModal')).toBe(true);
+ });
+ });
});
describe.each`
diff --git a/spec/frontend/token_access/mock_data.js b/spec/frontend/token_access/mock_data.js
index 5bb8ed560a6..b7fd38afb6a 100644
--- a/spec/frontend/token_access/mock_data.js
+++ b/spec/frontend/token_access/mock_data.js
@@ -323,3 +323,13 @@ export const mockAutopopulateAllowlistError = {
},
},
};
+
+export const mockRemoveAutopopulatedEntriesResponse = ({ errorMessage } = {}) => ({
+ data: {
+ ciJobTokenScopeClearAllowlistAutopopulations: {
+ status: 'complete',
+ errors: errorMessage ? [{ message: errorMessage }] : [],
+ __typename: 'CiJobTokenScopeClearAllowlistAutopopulationsPayload',
+ },
+ },
+});
diff --git a/spec/frontend/token_access/remove_autopopulated_entries_modal_spec.js b/spec/frontend/token_access/remove_autopopulated_entries_modal_spec.js
new file mode 100644
index 00000000000..486df61705a
--- /dev/null
+++ b/spec/frontend/token_access/remove_autopopulated_entries_modal_spec.js
@@ -0,0 +1,63 @@
+import { GlModal } from '@gitlab/ui';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import RemoveAutopopulatedEntriesModal from '~/token_access/components/remove_autopopulated_entries_modal.vue';
+
+const projectName = 'My project';
+const fullPath = 'root/my-repo';
+
+Vue.use(VueApollo);
+
+describe('RemoveAutopopulatedEntriesModal component', () => {
+ let wrapper;
+
+ const findModal = () => wrapper.findComponent(GlModal);
+
+ const createComponent = ({ props } = {}) => {
+ wrapper = shallowMountExtended(RemoveAutopopulatedEntriesModal, {
+ provide: {
+ fullPath,
+ },
+ propsData: {
+ projectName,
+ showModal: true,
+ ...props,
+ },
+ });
+ };
+
+ describe('template', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it.each`
+ modalEvent | emittedEvent
+ ${'canceled'} | ${'hide'}
+ ${'hidden'} | ${'hide'}
+ ${'secondary'} | ${'hide'}
+ `(
+ 'emits the $emittedEvent event when $modalEvent event is triggered',
+ ({ modalEvent, emittedEvent }) => {
+ expect(wrapper.emitted(emittedEvent)).toBeUndefined();
+
+ findModal().vm.$emit(modalEvent);
+
+ expect(wrapper.emitted(emittedEvent)).toHaveLength(1);
+ },
+ );
+ });
+
+ describe('when clicking on the primary button', () => {
+ it('emits the remove-entries event', () => {
+ createComponent();
+
+ expect(wrapper.emitted('remove-entries')).toBeUndefined();
+
+ findModal().vm.$emit('primary', { preventDefault: jest.fn() });
+
+ expect(wrapper.emitted('remove-entries')).toHaveLength(1);
+ });
+ });
+});
diff --git a/spec/frontend/token_access/token_access_table_spec.js b/spec/frontend/token_access/token_access_table_spec.js
index 9278d05c7d1..e97ae3720d5 100644
--- a/spec/frontend/token_access/token_access_table_spec.js
+++ b/spec/frontend/token_access/token_access_table_spec.js
@@ -22,6 +22,7 @@ describe('Token access table', () => {
const findName = () => wrapper.findByTestId('token-access-name');
const findPolicies = () => findAllTableRows().at(0).findAll('td').at(1);
const findAutopopulatedIcon = () => wrapper.findByTestId('autopopulated-icon');
+ const findLoadingMessage = () => wrapper.findByTestId('loading-message');
describe.each`
type | items
@@ -86,6 +87,17 @@ describe('Token access table', () => {
createComponent({ items: mockGroups, loading: true });
expect(findTable().findComponent(GlLoadingIcon).props('size')).toBe('md');
+ expect(findLoadingMessage().exists()).toBe(false);
+ });
+
+ it('shows loading message when available', () => {
+ createComponent({
+ items: mockGroups,
+ loading: true,
+ loadingMessage: 'Removing auto-populated entries...',
+ });
+
+ expect(findLoadingMessage().text()).toBe('Removing auto-populated entries...');
});
});
diff --git a/spec/frontend/work_items/mock_data.js b/spec/frontend/work_items/mock_data.js
index 5314a421494..74abfc87a96 100644
--- a/spec/frontend/work_items/mock_data.js
+++ b/spec/frontend/work_items/mock_data.js
@@ -5347,6 +5347,7 @@ export const groupWorkItemsQueryResponse = {
title: 'a group level work item',
updatedAt: '',
webUrl: 'web/url',
+ userDiscussionsCount: 0,
widgets: [
{
__typename: 'WorkItemWidgetAssignees',
diff --git a/spec/lib/gitlab/gpg_spec.rb b/spec/lib/gitlab/gpg_spec.rb
index e64555f1079..1299b772827 100644
--- a/spec/lib/gitlab/gpg_spec.rb
+++ b/spec/lib/gitlab/gpg_spec.rb
@@ -172,7 +172,7 @@ RSpec.describe Gitlab::Gpg do
it 'does not fail if the homedir was deleted while running' do
expect do
described_class.using_tmp_keychain do
- FileUtils.remove_entry(described_class.current_home_dir)
+ FileUtils.rm_rf(described_class.current_home_dir)
end
end.not_to raise_error
end
@@ -204,8 +204,8 @@ RSpec.describe Gitlab::Gpg do
end
before do
- # Stub all the other calls for `remove_entry`
- allow(FileUtils).to receive(:remove_entry).with(any_args).and_call_original
+ # Stub all the other calls for `rm_rf`
+ allow(FileUtils).to receive(:rm_rf).with(any_args).and_call_original
end
it "tries for #{seconds} or 15 times" do
@@ -216,15 +216,15 @@ RSpec.describe Gitlab::Gpg do
it 'tries at least 2 times to remove the tmp dir before raising', :aggregate_failures do
expect(Retriable).to receive(:sleep).at_least(:twice)
- expect(FileUtils).to receive(:remove_entry).with(tmp_dir).at_least(:twice).and_raise('Deletion failed')
+ expect(FileUtils).to receive(:rm_rf).with(tmp_dir).at_least(:twice).and_raise('Deletion failed')
expect { described_class.using_tmp_keychain {} }.to raise_error(described_class::CleanupError)
end
it 'does not attempt multiple times when the deletion succeeds' do
expect(Retriable).to receive(:sleep).once
- expect(FileUtils).to receive(:remove_entry).with(tmp_dir).once.and_raise('Deletion failed')
- expect(FileUtils).to receive(:remove_entry).with(tmp_dir).and_call_original
+ expect(FileUtils).to receive(:rm_rf).with(tmp_dir).once.and_raise('Deletion failed')
+ expect(FileUtils).to receive(:rm_rf).with(tmp_dir).and_call_original
expect { described_class.using_tmp_keychain {} }.not_to raise_error
diff --git a/spec/tasks/gitlab/update_templates_rake_spec.rb b/spec/tasks/gitlab/update_templates_rake_spec.rb
index 18ef20fbc78..1b41d81adb5 100644
--- a/spec/tasks/gitlab/update_templates_rake_spec.rb
+++ b/spec/tasks/gitlab/update_templates_rake_spec.rb
@@ -2,38 +2,68 @@
require 'spec_helper'
-RSpec.describe 'gitlab:update_project_templates rake task', :silence_stdout, feature_category: :importers do
- let!(:tmpdir) { Dir.mktmpdir }
- let(:template) { Gitlab::ProjectTemplate.find(:rails) }
-
+RSpec.describe 'gitlab:update_templates rake task', :silence_stdout, feature_category: :importers do
before do
Rake.application.rake_require 'tasks/gitlab/update_templates'
- admin = create(:admin)
- create(:key, user: admin)
+ end
- allow(Gitlab::ProjectTemplate)
- .to receive(:archive_directory)
- .and_return(Pathname.new(tmpdir))
+ describe '.update_project_templates' do
+ let!(:tmpdir) { Dir.mktmpdir }
+ let(:template) { Gitlab::ProjectTemplate.find(:rails) }
- # Gitlab::HTTP resolves the domain to an IP prior to WebMock taking effect, hence the wildcard
- stub_request(:get, %r{^https://.*/api/v4/projects/#{template.uri_encoded_project_path}/repository/commits\?page=1&per_page=1})
- .to_return(
- status: 200,
- body: [{ id: '67812735b83cb42710f22dc98d73d42c8bf4d907' }].to_json,
- headers: { 'Content-Type' => 'application/json' }
+ before do
+ admin = create(:admin)
+ create(:key, user: admin)
+
+ allow(Gitlab::ProjectTemplate)
+ .to receive(:archive_directory)
+ .and_return(Pathname.new(tmpdir))
+
+ # Gitlab::HTTP resolves the domain to an IP prior to WebMock taking effect, hence the wildcard
+ stub_request(:get, %r{^https://.*/api/v4/projects/#{template.uri_encoded_project_path}/repository/commits\?page=1&per_page=1})
+ .to_return(
+ status: 200,
+ body: [{ id: '67812735b83cb42710f22dc98d73d42c8bf4d907' }].to_json,
+ headers: { 'Content-Type' => 'application/json' }
+ )
+ end
+
+ after do
+ FileUtils.rm_rf(tmpdir)
+ end
+
+ it 'updates valid project templates' do
+ expect(Gitlab::TaskHelpers).to receive(:run_command!).with(anything).exactly(6).times.and_call_original
+ expect(Gitlab::TaskHelpers).to receive(:run_command!).with(%w[git push -u origin master])
+
+ expect { run_rake_task('gitlab:update_project_templates', [template.name]) }
+ .to change { Dir.entries(tmpdir) }
+ .by(["#{template.name}.tar.gz"])
+ end
+ end
+
+ describe '#clone_repository' do
+ let(:repo_url) { "https://test.com/example/text.git" }
+ let(:template) do
+ Struct.new(:repo_url, :cleanup_regex).new(
+ repo_url,
+ /(\.{1,2}|LICENSE|Global|\.test)\z/
)
- end
+ end
- after do
- FileUtils.rm_rf(tmpdir)
- end
+ let(:dir) { Rails.root.join('vendor/text').to_s }
+ let(:kernel_system_call_params) { "git clone #{repo_url} --depth=1 --branch=master #{dir}" }
- it 'updates valid project templates' do
- expect(Gitlab::TaskHelpers).to receive(:run_command!).with(anything).exactly(6).times.and_call_original
- expect(Gitlab::TaskHelpers).to receive(:run_command!).with(%w[git push -u origin master])
+ before do
+ stub_const('TEMPLATE_DATA', [template])
+ allow(main_object).to receive(:system).with(kernel_system_call_params).and_return(false)
+ allow(FileUtils).to receive(:rm_rf).and_call_original
+ end
- expect { run_rake_task('gitlab:update_project_templates', [template.name]) }
- .to change { Dir.entries(tmpdir) }
- .by(["#{template.name}.tar.gz"])
+ it 'calls FileUtils.rm_rf to remove directory' do
+ expect(FileUtils).to receive(:rm_rf).with(dir)
+
+ run_rake_task('gitlab:update_templates')
+ end
end
end
diff --git a/yarn.lock b/yarn.lock
index 25aa949cdf7..3be1c5c0d30 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1436,10 +1436,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.123.0.tgz#1fa3b1a709755ff7c8ef67e18c0442101655ebf0"
integrity sha512-yjVn+utOTIKk8d9JlvGo6EgJ4TQ+CKpe3RddflAqtsQqQuL/2MlVdtaUePybxYzWIaumFuh5LouQ6BrWyw1niQ==
-"@gitlab/ui@108.4.1":
- version "108.4.1"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-108.4.1.tgz#d45dbcc3a9f568694f53eebb795544157ddbf44f"
- integrity sha512-o3mcR/AQ5tkKd3mMClMvsJIHhLWzU6n1L0m3bp4Yr4zaNtRG3c8gEGF7MR6pMhlnyb5r/FQk2wigxHaUuOSblQ==
+"@gitlab/ui@108.6.0":
+ version "108.6.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-108.6.0.tgz#55c87776b1f247250dc33ab7921e27c415675842"
+ integrity sha512-azX2X0Qzs9Jt1ZruGlFWa9JAJuCO8Bix+uWMMWYqCQYCk4HLOT2t7jI0Kt8vUTwweA/IsmT6VPID2aKxFeJR0g==
dependencies:
"@floating-ui/dom" "1.4.3"
echarts "^5.3.2"