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:
-rw-r--r--.rubocop_todo/layout/hash_alignment.yml30
-rw-r--r--app/assets/javascripts/boards/boards_util.js4
-rw-r--r--app/assets/javascripts/boards/components/board_list_header.vue2
-rw-r--r--app/assets/javascripts/boards/graphql/lists_issues.query.graphql2
-rw-r--r--app/assets/javascripts/boards/stores/mutations.js2
-rw-r--r--app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue25
-rw-r--r--app/assets/javascripts/work_items/constants.js4
-rw-r--r--app/controllers/projects/settings/packages_and_registries_controller.rb11
-rw-r--r--app/helpers/packages_helper.rb23
-rw-r--r--app/views/projects/settings/packages_and_registries/cleanup_tags.html.haml6
-rw-r--r--app/views/projects/settings/packages_and_registries/show.html.haml14
-rw-r--r--config/routes/project.rb4
-rw-r--r--doc/administration/gitaly/configure_gitaly.md105
-rw-r--r--doc/administration/gitaly/index.md7
-rw-r--r--doc/development/feature_flags/index.md2
-rw-r--r--locale/gitlab.pot5
-rw-r--r--package.json2
-rw-r--r--qa/qa/specs/features/api/1_manage/import_large_github_repo_spec.rb62
-rw-r--r--spec/features/projects/settings/registry_settings_cleanup_tags_spec.rb49
-rw-r--r--spec/frontend/boards/stores/mutations_spec.js15
-rw-r--r--spec/frontend/work_items/components/work_item_links/work_item_links_form_spec.js9
-rw-r--r--spec/requests/projects/settings/packages_and_registries_controller_spec.rb70
-rw-r--r--spec/routing/project_routing_spec.rb10
-rw-r--r--yarn.lock26
24 files changed, 311 insertions, 178 deletions
diff --git a/.rubocop_todo/layout/hash_alignment.yml b/.rubocop_todo/layout/hash_alignment.yml
index c95fa7d7267..0f440235339 100644
--- a/.rubocop_todo/layout/hash_alignment.yml
+++ b/.rubocop_todo/layout/hash_alignment.yml
@@ -2,36 +2,6 @@
# Cop supports --auto-correct.
Layout/HashAlignment:
Exclude:
- - 'ee/app/graphql/types/vulnerabilities/asset_type.rb'
- - 'ee/app/graphql/types/vulnerabilities/container_image_type.rb'
- - 'ee/app/graphql/types/vulnerabilities/link_type.rb'
- - 'ee/app/graphql/types/vulnerability/external_issue_link_type.rb'
- - 'ee/app/graphql/types/vulnerability/issue_link_type.rb'
- - 'ee/app/graphql/types/vulnerability_details/base_type.rb'
- - 'ee/app/graphql/types/vulnerability_details/boolean_type.rb'
- - 'ee/app/graphql/types/vulnerability_details/code_type.rb'
- - 'ee/app/graphql/types/vulnerability_details/commit_type.rb'
- - 'ee/app/graphql/types/vulnerability_details/diff_type.rb'
- - 'ee/app/graphql/types/vulnerability_details/file_location_type.rb'
- - 'ee/app/graphql/types/vulnerability_details/int_type.rb'
- - 'ee/app/graphql/types/vulnerability_details/list_type.rb'
- - 'ee/app/graphql/types/vulnerability_details/markdown_type.rb'
- - 'ee/app/graphql/types/vulnerability_details/module_location_type.rb'
- - 'ee/app/graphql/types/vulnerability_details/table_type.rb'
- - 'ee/app/graphql/types/vulnerability_details/text_type.rb'
- - 'ee/app/graphql/types/vulnerability_details/url_type.rb'
- - 'ee/app/graphql/types/vulnerability_location/container_scanning_type.rb'
- - 'ee/app/graphql/types/vulnerability_location/coverage_fuzzing_type.rb'
- - 'ee/app/graphql/types/vulnerability_location/dast_type.rb'
- - 'ee/app/graphql/types/vulnerability_location/dependency_scanning_type.rb'
- - 'ee/app/graphql/types/vulnerability_location/generic_type.rb'
- - 'ee/app/graphql/types/vulnerability_location/sast_type.rb'
- - 'ee/app/graphql/types/vulnerability_location/secret_detection_type.rb'
- - 'ee/app/graphql/types/vulnerability_request_response_header_type.rb'
- - 'ee/app/graphql/types/vulnerability_request_type.rb'
- - 'ee/app/graphql/types/vulnerability_response_type.rb'
- - 'ee/app/graphql/types/vulnerability_scanner_type.rb'
- - 'ee/app/graphql/types/vulnerability_type.rb'
- 'ee/app/graphql/types/vulnerable_dependency_type.rb'
- 'ee/app/graphql/types/vulnerable_kubernetes_resource_type.rb'
- 'ee/app/graphql/types/vulnerable_projects_by_grade_type.rb'
diff --git a/app/assets/javascripts/boards/boards_util.js b/app/assets/javascripts/boards/boards_util.js
index 9fca9860282..8062460f052 100644
--- a/app/assets/javascripts/boards/boards_util.js
+++ b/app/assets/javascripts/boards/boards_util.js
@@ -38,10 +38,8 @@ export function formatIssue(issue) {
export function formatListIssues(listIssues) {
const boardItems = {};
- let listItemsCount;
const listData = listIssues.nodes.reduce((map, list) => {
- listItemsCount = list.issuesCount;
let sortedIssues = list.issues.edges.map((issueNode) => ({
...issueNode.node,
}));
@@ -67,7 +65,7 @@ export function formatListIssues(listIssues) {
};
}, {});
- return { listData, boardItems, listItemsCount };
+ return { listData, boardItems };
}
export function formatListsPageInfo(lists) {
diff --git a/app/assets/javascripts/boards/components/board_list_header.vue b/app/assets/javascripts/boards/components/board_list_header.vue
index a65269de743..e3012f5b36d 100644
--- a/app/assets/javascripts/boards/components/board_list_header.vue
+++ b/app/assets/javascripts/boards/components/board_list_header.vue
@@ -117,7 +117,7 @@ export default {
return 'issues';
},
itemsTooltipLabel() {
- return n__(`%d issue`, `%d issues`, this.boardLists?.issuesCount);
+ return n__(`%d issue`, `%d issues`, this.boardList?.issuesCount);
},
chevronTooltip() {
return this.list.collapsed ? this.$options.i18n.expand : this.$options.i18n.collapse;
diff --git a/app/assets/javascripts/boards/graphql/lists_issues.query.graphql b/app/assets/javascripts/boards/graphql/lists_issues.query.graphql
index bf5329c4a8d..ae6394f9a2f 100644
--- a/app/assets/javascripts/boards/graphql/lists_issues.query.graphql
+++ b/app/assets/javascripts/boards/graphql/lists_issues.query.graphql
@@ -17,7 +17,6 @@ query BoardListsEE(
lists(id: $id, issueFilters: $filters) {
nodes {
id
- issuesCount
listType
issues(first: $first, filters: $filters, after: $after) {
edges {
@@ -41,7 +40,6 @@ query BoardListsEE(
lists(id: $id, issueFilters: $filters) {
nodes {
id
- issuesCount
listType
issues(first: $first, filters: $filters, after: $after) {
edges {
diff --git a/app/assets/javascripts/boards/stores/mutations.js b/app/assets/javascripts/boards/stores/mutations.js
index 04e7d3643e7..26a98a645b3 100644
--- a/app/assets/javascripts/boards/stores/mutations.js
+++ b/app/assets/javascripts/boards/stores/mutations.js
@@ -11,7 +11,7 @@ const updateListItemsCount = ({ state, listId, value }) => {
if (state.issuableType === issuableTypes.epic) {
Vue.set(state.boardLists, listId, { ...list, epicsCount: list.epicsCount + value });
} else {
- Vue.set(state.boardLists, listId, { ...list, issuesCount: list.issuesCount + value });
+ Vue.set(state.boardLists, listId, { ...list });
}
};
diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue
index 7b9b66a472a..8b848995d44 100644
--- a/app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue
+++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue
@@ -2,10 +2,10 @@
import { GlAlert, GlFormGroup, GlForm, GlFormCombobox, GlButton, GlFormInput } from '@gitlab/ui';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { __, s__ } from '~/locale';
-import projectWorkItemsQuery from '../../graphql/project_work_items.query.graphql';
+import projectWorkItemTypesQuery from '~/work_items/graphql/project_work_item_types.query.graphql';
import updateWorkItemMutation from '../../graphql/update_work_item.mutation.graphql';
import createWorkItemMutation from '../../graphql/create_work_item.mutation.graphql';
-import { WORK_ITEM_TYPE_IDS } from '../../constants';
+import { TASK_TYPE_NAME } from '../../constants';
export default {
components: {
@@ -35,23 +35,15 @@ export default {
},
},
apollo: {
- availableWorkItems: {
- query: projectWorkItemsQuery,
- debounce: 200,
+ workItemTypes: {
+ query: projectWorkItemTypesQuery,
variables() {
return {
- projectPath: this.projectPath,
- searchTerm: this.search?.title || this.search,
- types: ['TASK'],
+ fullPath: this.projectPath,
};
},
- skip() {
- return this.search.length === 0;
- },
update(data) {
- return data.workspace.workItems.edges
- .filter((wi) => !this.childrenIds.includes(wi.node.id))
- .map((wi) => wi.node);
+ return data.workspace?.workItemTypes?.nodes;
},
},
},
@@ -82,6 +74,9 @@ export default {
addOrCreateMethod() {
return this.childToCreateTitle ? this.createChild : this.addChild;
},
+ taskWorkItemType() {
+ return this.workItemTypes.find((type) => type.name === TASK_TYPE_NAME)?.id;
+ },
},
methods: {
getIdFromGraphQLId,
@@ -124,7 +119,7 @@ export default {
input: {
title: this.search?.title || this.search,
projectPath: this.projectPath,
- workItemTypeId: WORK_ITEM_TYPE_IDS.TASK,
+ workItemTypeId: this.taskWorkItemType,
hierarchyWidget: {
parentId: this.issuableGid,
},
diff --git a/app/assets/javascripts/work_items/constants.js b/app/assets/javascripts/work_items/constants.js
index d89978bff82..f1fc21b74f4 100644
--- a/app/assets/javascripts/work_items/constants.js
+++ b/app/assets/javascripts/work_items/constants.js
@@ -37,10 +37,6 @@ export const WORK_ITEM_STATUS_TEXT = {
OPEN: s__('WorkItem|Open'),
};
-export const WORK_ITEM_TYPE_IDS = {
- TASK: 'gid://gitlab/WorkItems::Type/5',
-};
-
export const WORK_ITEMS_TYPE_MAP = {
[WORK_ITEM_TYPE_ENUM_INCIDENT]: {
icon: `issue-type-incident`,
diff --git a/app/controllers/projects/settings/packages_and_registries_controller.rb b/app/controllers/projects/settings/packages_and_registries_controller.rb
index d3c08bef808..76c9cead360 100644
--- a/app/controllers/projects/settings/packages_and_registries_controller.rb
+++ b/app/controllers/projects/settings/packages_and_registries_controller.rb
@@ -14,11 +14,22 @@ module Projects
def show
end
+ def cleanup_tags
+ registry_settings_enabled!
+
+ @hide_search_settings = true
+ end
+
private
def packages_and_registries_settings_enabled!
render_404 unless can?(current_user, :view_package_registry_project_settings, project)
end
+
+ def registry_settings_enabled!
+ render_404 unless Gitlab.config.registry.enabled &&
+ can?(current_user, :admin_container_image, project)
+ end
end
end
end
diff --git a/app/helpers/packages_helper.rb b/app/helpers/packages_helper.rb
index ec64746d6b6..b52357bc891 100644
--- a/app/helpers/packages_helper.rb
+++ b/app/helpers/packages_helper.rb
@@ -63,4 +63,27 @@ module PackagesHelper
Gitlab.config.packages.enabled &&
Ability.allowed?(current_user, :admin_package, project)
end
+
+ def cleanup_settings_data
+ {
+ project_id: @project.id,
+ project_path: @project.full_path,
+ cadence_options: cadence_options.to_json,
+ keep_n_options: keep_n_options.to_json,
+ older_than_options: older_than_options.to_json,
+ is_admin: current_user&.admin.to_s,
+ admin_settings_path: ci_cd_admin_application_settings_path(anchor: 'js-registry-settings'),
+ enable_historic_entries: container_expiration_policies_historic_entry_enabled?.to_s,
+ help_page_path: help_page_path('user/packages/container_registry/reduce_container_registry_storage', anchor: 'cleanup-policy'),
+ show_cleanup_policy_link: show_cleanup_policy_link(@project).to_s,
+ tags_regex_help_page_path: help_page_path('user/packages/container_registry/reduce_container_registry_storage', anchor: 'regex-pattern-examples')
+ }
+ end
+
+ def settings_data
+ cleanup_settings_data.merge(
+ show_container_registry_settings: show_container_registry_settings(@project).to_s,
+ show_package_registry_settings: show_package_registry_settings(@project).to_s
+ )
+ end
end
diff --git a/app/views/projects/settings/packages_and_registries/cleanup_tags.html.haml b/app/views/projects/settings/packages_and_registries/cleanup_tags.html.haml
new file mode 100644
index 00000000000..795544b75a2
--- /dev/null
+++ b/app/views/projects/settings/packages_and_registries/cleanup_tags.html.haml
@@ -0,0 +1,6 @@
+- add_to_breadcrumbs _('Packages & Registries'), project_settings_packages_and_registries_path(@project)
+- breadcrumb_title s_('ContainerRegistry|Clean up image tags')
+- page_title s_('ContainerRegistry|Clean up image tags'), _('Packages & Registries')
+- @content_class = 'limit-container-width' unless fluid_layout
+
+#js-registry-settings-cleanup-image-tags{ data: cleanup_settings_data }
diff --git a/app/views/projects/settings/packages_and_registries/show.html.haml b/app/views/projects/settings/packages_and_registries/show.html.haml
index 1a7821d3268..d579981ebc0 100644
--- a/app/views/projects/settings/packages_and_registries/show.html.haml
+++ b/app/views/projects/settings/packages_and_registries/show.html.haml
@@ -2,16 +2,4 @@
- page_title _('Packages & Registries')
- @content_class = 'limit-container-width' unless fluid_layout
-#js-registry-settings{ data: { project_id: @project.id,
- project_path: @project.full_path,
- cadence_options: cadence_options.to_json,
- keep_n_options: keep_n_options.to_json,
- older_than_options: older_than_options.to_json,
- is_admin: current_user&.admin.to_s,
- show_container_registry_settings: show_container_registry_settings(@project).to_s,
- show_package_registry_settings: show_package_registry_settings(@project).to_s,
- admin_settings_path: ci_cd_admin_application_settings_path(anchor: 'js-registry-settings'),
- enable_historic_entries: container_expiration_policies_historic_entry_enabled?.to_s,
- help_page_path: help_page_path('user/packages/container_registry/reduce_container_registry_storage', anchor: 'cleanup-policy'),
- show_cleanup_policy_link: show_cleanup_policy_link(@project).to_s,
- tags_regex_help_page_path: help_page_path('user/packages/container_registry/reduce_container_registry_storage', anchor: 'regex-pattern-examples') } }
+#js-registry-settings{ data: settings_data }
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 63e960a9b4c..1ee962fb2a1 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -156,7 +156,9 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
- resource :packages_and_registries, only: [:show]
+ resource :packages_and_registries, only: [:show] do
+ get '/cleanup_image_tags', to: 'packages_and_registries#cleanup_tags'
+ end
end
resources :usage_quotas, only: [:index]
diff --git a/doc/administration/gitaly/configure_gitaly.md b/doc/administration/gitaly/configure_gitaly.md
index c58c1212179..5b868c274cd 100644
--- a/doc/administration/gitaly/configure_gitaly.md
+++ b/doc/administration/gitaly/configure_gitaly.md
@@ -813,26 +813,12 @@ information, see the [relevant documentation](monitoring.md#monitor-gitaly-concu
## Control groups
FLAG:
-On self-managed GitLab, by default cgroups are not available. To make it available, ask an administrator to
+On self-managed GitLab, by default repository cgroups are not available. To make it available, ask an administrator to
[enable the feature flag](../feature_flags.md) named `gitaly_run_cmds_in_cgroup`.
-Gitaly shells out to Git for many of its operations. Git can consume a lot of resources for certain operations,
-especially for large repositories.
-
Control groups (cgroups) in Linux allow limits to be imposed on how much memory and CPU can be consumed.
See the [`cgroups` Linux man page](https://man7.org/linux/man-pages/man7/cgroups.7.html) for more information.
-cgroups can be useful for protecting the system against resource exhaustion because of overcomsumption of memory and CPU.
-
-Gitaly has built-in cgroups control. When configured, Gitaly assigns Git
-processes to a cgroup based on the repository the Git command is operating in.
-Each cgroup has a memory and CPU limit. When a cgroup reaches its:
-
-- Memory limit, the kernel looks through the processes for a candidate to kill.
-- CPU limit, processes are not killed, but the processes are prevented from consuming more CPU than allowed.
-
-The main reason to configure cgroups for your GitLab installation is that it
-protects against system resource starvation due to a few large repositories or
-bad actors.
+cgroups can be useful for protecting the system against resource exhaustion because of over consumption of memory and CPU.
Some Git operations are expensive by nature. `git clone`, for instance,
spawns a `git-upload-pack` process on the server that can consume a lot of memory
@@ -840,33 +826,33 @@ for large repositories. For example, a client that keeps on cloning a
large repository over and over again. This situation could potentially use up all of the
memory on a server, causing other operations to fail for other users.
-There are many ways someone can create a repository that can consume large amounts of memory when cloned or downloaded.
+A repository can consume large amounts of memory for many reasons when cloned or downloaded.
Using cgroups allows the kernel to kill these operations before they hog up all system resources.
-### Configure cgroups in Gitaly
+Gitaly shells out to Git for many of its operations. Git can consume a lot of resources for certain operations,
+especially for large repositories.
-Two ways of configuring cgroups are available.
+Gitaly has built-in cgroups control. When configured, Gitaly assigns Git processes to a cgroup based on the repository
+the Git command is operating in. These cgroups are called repository cgroups. Each repository cgroup:
-#### Configure cgroups (new method)
+- Has a memory and CPU limit.
+- Contains the Git processes for a single repository.
+- Uses a consistent hash to ensure a Git process for a given repository always ends up in the same cgroup.
-> This method of configuring cgroups introduced in GitLab 15.1.
+When a repository cgroup reaches its:
-Gitaly creates a pool of cgroups that are isolated based on the repository used in the Git command to be placed under one of these cgroups.
+- Memory limit, the kernel looks through the processes for a candidate to kill.
+- CPU limit, processes are not killed, but the processes are prevented from consuming more CPU than allowed.
-To configure cgroups in Gitaly, add `gitaly['cgroups']` to `/etc/gitlab/gitlab.rb`.
+You configure repository cgroups for your GitLab installation to protect against system resource starvation from a few
+large repositories or bad actors.
-For example:
+### Configure repository cgroups (new method)
-```ruby
-# in /etc/gitlab/gitlab.rb
-gitaly['cgroups_mountpoint'] = "/sys/fs/cgroup"
-gitaly['cgroups_hierarchy_root'] =>"gitaly"
-gitaly['cgroups_memory_bytes'] = 64424509440, # 60gb
-gitaly['cgroups_cpu_shares'] = 1024
-gitaly['cgroups_repositories_count'] => 1000,
-gitaly['cgroups_repositories_memory_bytes'] => 32212254720 # 20gb
-gitaly['cgroups_repositories_cpu_shares'] => 512
-```
+> This method of configuring repository cgroups was introduced in GitLab 15.1.
+
+To configure repository cgroups in Gitaly using the new method, use the following settings for the new configuration method
+to `gitaly['cgroups']` in `/etc/gitlab/gitlab.rb`:
- `cgroups_mountpoint` is where the parent cgroup directory is mounted. Defaults to `/sys/fs/cgroup`.
- `cgroups_hierarchy_root` is the parent cgroup under which Gitaly creates groups, and
@@ -875,7 +861,7 @@ gitaly['cgroups_repositories_cpu_shares'] => 512
when Gitaly starts.
- `cgroups_memory_bytes` is the total memory limit that is imposed collectively on all
Git processes that Gitaly spawns. 0 implies no limit.
-- `cgroups_cpu_shares` is the cpu limit that is imposed collectively on all Git
+- `cgroups_cpu_shares` is the CPU limit that is imposed collectively on all Git
processes that Gitaly spawns. 0 implies no limit. The maximum is 1024 shares,
which represents 100% of CPU.
- `cgroups_repositories_count` is the number of cgroups in the cgroups pool. Each time a new Git
@@ -884,31 +870,29 @@ gitaly['cgroups_repositories_cpu_shares'] => 512
Git commands to these cgroups, so a Git command for a repository is
always assigned to the same cgroup.
- `cgroups_repositories_memory_bytes` is the total memory limit imposed on all Git processes contained in a repository cgroup.
- A repository cgroup is one that contains Git processes for one or more repositories.
- A consistent hash is used to assign repositories to these cgroups such that a Git process for a given repository always ends up in the same cgroup.
0 implies no limit. This value cannot exceed that of the top level `cgroups_memory_bytes`.
- `cgroups_repositories_cpu_shares` is the CPU limit that is imposed on all Git processes contained in a repository cgroup.
- A repository cgroup is one that contains Git processes for one or more repositories.
- A consistent hash is used to assign repositories to these cgroups such that a Git process for a given repository always ends up in the same cgroup.
0 implies no limit. The maximum is 1024 shares, which represents 100% of CPU.
This value cannot exceed that of the top level`cgroups_cpu_shares`.
-#### Configure cgroups (legacy method)
-
-To configure cgroups in Gitaly for GitLab versions using the legacy method, add `gitaly['cgroups']` to `/etc/gitlab/gitlab.rb`. For
-example:
+For example:
```ruby
# in /etc/gitlab/gitlab.rb
-gitaly['cgroups_count'] = 1000
gitaly['cgroups_mountpoint'] = "/sys/fs/cgroup"
-gitaly['cgroups_hierarchy_root'] = "gitaly"
-gitaly['cgroups_memory_limit'] = 32212254720
-gitaly['cgroups_memory_enabled'] = true
+gitaly['cgroups_hierarchy_root'] => "gitaly"
+gitaly['cgroups_memory_bytes'] = 64424509440, # 60gb
gitaly['cgroups_cpu_shares'] = 1024
-gitaly['cgroups_cpu_enabled'] = true
+gitaly['cgroups_repositories_count'] => 1000,
+gitaly['cgroups_repositories_memory_bytes'] => 32212254720 # 20gb
+gitaly['cgroups_repositories_cpu_shares'] => 512
```
+### Configure repository cgroups (legacy method)
+
+To configure repository cgroups in Gitaly using the legacy method, use the following settings
+in `/etc/gitlab/gitlab.rb`:
+
- `cgroups_count` is the number of cgroups created. Each time a new
command is spawned, Gitaly assigns it to one of these cgroups based
on the command line arguments of the command. A circular hashing algorithm assigns
@@ -921,7 +905,21 @@ gitaly['cgroups_cpu_enabled'] = true
- `cgroups_memory_enabled` enables or disables the memory limit on cgroups.
- `cgroups_memory_bytes` is the total memory limit each cgroup imposes on the processes added to it.
- `cgroups_cpu_enabled` enables or disables the CPU limit on cgroups.
-- `cgroups_cpu_shares` is the CPU limit each cgroup imposes on the processes added to it. The maximum is 1024 shares, which represents 100% of CPU.
+- `cgroups_cpu_shares` is the CPU limit each cgroup imposes on the processes added to it. The maximum is 1024 shares,
+ which represents 100% of CPU.
+
+For example:
+
+```ruby
+# in /etc/gitlab/gitlab.rb
+gitaly['cgroups_count'] = 1000
+gitaly['cgroups_mountpoint'] = "/sys/fs/cgroup"
+gitaly['cgroups_hierarchy_root'] = "gitaly"
+gitaly['cgroups_memory_limit'] = 32212254720
+gitaly['cgroups_memory_enabled'] = true
+gitaly['cgroups_cpu_shares'] = 1024
+gitaly['cgroups_cpu_enabled'] = true
+```
### Configuring oversubscription
@@ -930,16 +928,15 @@ In the previous example using the new configuration method:
- The top level memory limit is capped at 60gb.
- Each of the 1000 cgroups in the repositories pool is capped at 20gb.
-This is called "oversubscription". Each cgroup in the pool has a much larger capacity than 1/1000th
+This configuration leads to "oversubscription". Each cgroup in the pool has a much larger capacity than 1/1000th
of the top-level memory limit.
This strategy has two main benefits:
-- It gives the host protection from overall memory starvation (OOM), because the top-level
- cgroup's memory limit can be set to a threshold smaller than the host's
- capacity. Processes outside of that cgroup are not at risk of OOM.
+- It gives the host protection from overall memory starvation (OOM), because the memory limit of the top-level cgroup
+ can be set to a threshold smaller than the host's capacity. Processes outside of that cgroup are not at risk of OOM.
- It allows each individual cgroup in the pool to burst up to a generous upper
- bound (in this example 20 GB) that is smaller than the parent cgroup's limit,
+ bound (in this example 20 GB) that is smaller than the limit of the parent cgroup,
but substantially larger than 1/N of the parent's limit. In this example, up
to 3 child cgroups can concurrently burst up to their max. In general, all
1000 cgroups would use much less than the 20 GB.
diff --git a/doc/administration/gitaly/index.md b/doc/administration/gitaly/index.md
index 0564e160faa..82c9492ebcc 100644
--- a/doc/administration/gitaly/index.md
+++ b/doc/administration/gitaly/index.md
@@ -80,11 +80,10 @@ Gitaly Cluster does not support snapshot backups. Snapshot backups can cause iss
out of sync with the disk storage. Because of how Praefect rebuilds the replication metadata of Gitaly disk information
during a restore, we recommend using the [official backup and restore Rake tasks](../../raketasks/backup_restore.md).
-If you are unable to use this method, please contact customer support for restoration help.
+The [incremental backup method](../../raketasks/backup_gitlab.md#incremental-repository-backups)
+can be used to speed up Gitaly Cluster backups.
-We are tracking in [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/351383) improvements to the
-[official backup and restore Rake tasks](../../raketasks/backup_restore.md) to add support for incremental backups. For
-more information, see [this epic](https://gitlab.com/groups/gitlab-org/-/epics/2094).
+If you are unable to use either method, please contact customer support for restoration help.
### What to do if you are on Gitaly Cluster experiencing an issue or limitation
diff --git a/doc/development/feature_flags/index.md b/doc/development/feature_flags/index.md
index d7589681ce6..502a028f089 100644
--- a/doc/development/feature_flags/index.md
+++ b/doc/development/feature_flags/index.md
@@ -170,7 +170,7 @@ Each feature flag is defined in a separate YAML file consisting of a number of f
| `default_enabled` | yes | The default state of the feature flag. |
| `introduced_by_url` | no | The URL to the merge request that introduced the feature flag. |
| `rollout_issue_url` | no | The URL to the Issue covering the feature flag rollout. |
-| `milestone` | no | Milestone in which the feature was added. |
+| `milestone` | no | Milestone in which the feature flag was created. |
| `group` | no | The [group](https://about.gitlab.com/handbook/product/categories/#devops-stages) that owns the feature flag. |
NOTE:
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index a6ce2d51a33..7bac0f53b2f 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -403,11 +403,6 @@ msgid_plural "%d tags per image name"
msgstr[0] ""
msgstr[1] ""
-msgid "%d unassigned issue"
-msgid_plural "%d unassigned issues"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d unresolved thread"
msgid_plural "%d unresolved threads"
msgstr[0] ""
diff --git a/package.json b/package.json
index 08f704308c7..616b67858e3 100644
--- a/package.json
+++ b/package.json
@@ -248,7 +248,7 @@
"webpack-dev-server": "4.10.0",
"xhr-mock": "^2.5.1",
"yarn-check-webpack-plugin": "^1.2.0",
- "yarn-deduplicate": "^5.0.0"
+ "yarn-deduplicate": "^5.0.2"
},
"blockedDependencies": {
"bootstrap-vue": "https://docs.gitlab.com/ee/development/fe_guide/dependencies.html#bootstrapvue"
diff --git a/qa/qa/specs/features/api/1_manage/import_large_github_repo_spec.rb b/qa/qa/specs/features/api/1_manage/import_large_github_repo_spec.rb
index c47afbd23f0..de460a39ccf 100644
--- a/qa/qa/specs/features/api/1_manage/import_large_github_repo_spec.rb
+++ b/qa/qa/specs/features/api/1_manage/import_large_github_repo_spec.rb
@@ -8,9 +8,14 @@ module QA
describe 'Project import' do
let(:logger) { Runtime::Logger.logger }
let(:differ) { RSpec::Support::Differ.new(color: true) }
+ let(:gitlab_address) { QA::Runtime::Scenario.gitlab_address }
+ let(:dummy_url) { "https://example.com" }
let(:created_by_pattern) { /\*Created by: \S+\*\n\n/ }
let(:suggestion_pattern) { /suggestion:-\d+\+\d+/ }
+ let(:gh_link_pattern) { %r{https://github.com/#{github_repo}/(issues|pull)} }
+ let(:gl_link_pattern) { %r{#{gitlab_address}/#{imported_project.path_with_namespace}/-/(issues|merge_requests)} }
+ let(:event_pattern) { %r{(un)?assigned( to)? @\S+|mentioned in (issue|merge request) [!#]\d+|changed title from \*\*.*\*\* to \*\*.*\*\*} } # rubocop:disable Layout/LineLength
let(:api_client) { Runtime::API::Client.as_admin }
@@ -83,14 +88,14 @@ module QA
let(:gh_issue_comments) do
logger.debug("= Fetching issue comments =")
github_client.issues_comments(github_repo).each_with_object(Hash.new { |h, k| h[k] = [] }) do |c, hash|
- hash[c.html_url.gsub(/\#\S+/, "")] << c.body # use base html url as key
+ hash[c.html_url.gsub(/\#\S+/, "")] << c.body&.gsub(gh_link_pattern, dummy_url) # use base html url as key
end
end
let(:gh_pr_comments) do
logger.debug("= Fetching pr comments =")
github_client.pull_requests_comments(github_repo).each_with_object(Hash.new { |h, k| h[k] = [] }) do |c, hash|
- hash[c.html_url.gsub(/\#\S+/, "")] << c.body # use base html url as key
+ hash[c.html_url.gsub(/\#\S+/, "")] << c.body&.gsub(gh_link_pattern, dummy_url) # use base html url as key
end
end
@@ -135,7 +140,7 @@ module QA
target: {
name: "GitLab",
project_name: imported_project.path_with_namespace,
- address: QA::Runtime::Scenario.gitlab_address,
+ address: gitlab_address,
data: {
branches: gl_branches.length,
commits: gl_commits.length,
@@ -381,15 +386,16 @@ module QA
end
logger.debug("Fetching comments for mr '#{mr[:title]}'")
+ comments = resource
+ .comments(auto_paginate: true, attempts: 2)
+ .reject { |c| c[:system] || c[:body].match?(/^(\*\*Review:\*\*)|(\*Merged by:).*/) }
+
[mr[:iid], {
url: mr[:web_url],
title: mr[:title],
body: sanitize_description(mr[:description]) || '',
- comments: resource
- .comments(auto_paginate: true, attempts: 2)
- # remove system notes
- .reject { |c| c[:system] || c[:body].match?(/^(\*\*Review:\*\*)|(\*Merged by:).*/) }
- .map { |c| sanitize_comment(c[:body]) }
+ events: events(comments),
+ comments: non_event_comments(comments)
}]
end.to_h
end
@@ -412,24 +418,54 @@ module QA
end
logger.debug("Fetching comments for issue '#{issue[:title]}'")
+ comments = resource.comments(auto_paginate: true, attempts: 2)
+
[issue[:iid], {
url: issue[:web_url],
title: issue[:title],
body: sanitize_description(issue[:description]) || '',
- comments: resource
- .comments(auto_paginate: true, attempts: 2)
- .map { |c| sanitize_comment(c[:body]) }
+ events: events(comments),
+ comments: non_event_comments(comments)
}]
end.to_h
end
end
- # Remove added prefixes and legacy diff format from comments
+ # Fetch comments without events
+ #
+ # @param [Array] comments
+ # @return [Array]
+ def non_event_comments(comments)
+ comments
+ .reject { |c| c[:body].match?(event_pattern) }
+ .map { |c| sanitize_comment(c[:body]) }
+ end
+
+ # Events
+ #
+ # @param [Array] comments
+ # @return [Array]
+ def events(comments)
+ comments
+ .select { |c| c[:body].match?(event_pattern) }
+ .map { |c| c[:body] }
+ end
+
+ # Normalize comments and make them directly comparable
+ #
+ # * remove created by prefixes
+ # * unify suggestion format
+ # * replace github and gitlab urls - some of the links to objects get transformed to gitlab entities, some don't,
+ # update all links to example.com for now
#
# @param [String] body
# @return [String]
def sanitize_comment(body)
- body.gsub(created_by_pattern, "").gsub(suggestion_pattern, "suggestion\r")
+ body
+ .gsub(created_by_pattern, "")
+ .gsub(suggestion_pattern, "suggestion\r")
+ .gsub(gl_link_pattern, dummy_url)
+ .gsub(gh_link_pattern, dummy_url)
end
# Remove created by prefix from descripion
diff --git a/spec/features/projects/settings/registry_settings_cleanup_tags_spec.rb b/spec/features/projects/settings/registry_settings_cleanup_tags_spec.rb
new file mode 100644
index 00000000000..5a50b3de772
--- /dev/null
+++ b/spec/features/projects/settings/registry_settings_cleanup_tags_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Project > Settings > Packages & Registries > Container registry tag expiration policy' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project, reload: true) { create(:project, namespace: user.namespace) }
+
+ let(:container_registry_enabled) { true }
+ let(:container_registry_enabled_on_project) { ProjectFeature::ENABLED }
+
+ subject { visit cleanup_image_tags_project_settings_packages_and_registries_path(project) }
+
+ before do
+ project.project_feature.update!(container_registry_access_level: container_registry_enabled_on_project)
+ project.container_expiration_policy.update!(enabled: true)
+
+ sign_in(user)
+ stub_container_registry_config(enabled: container_registry_enabled)
+ end
+
+ context 'as owner', :js do
+ it 'shows available section' do
+ subject
+
+ expect(find('.breadcrumbs')).to have_content('Clean up image tags')
+ end
+ end
+
+ context 'when registry is disabled' do
+ let(:container_registry_enabled) { false }
+
+ it 'does not exists' do
+ subject
+
+ expect(page).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when container registry is disabled on project' do
+ let(:container_registry_enabled_on_project) { ProjectFeature::DISABLED }
+
+ it 'does not exists' do
+ subject
+
+ expect(page).to have_gitlab_http_status(:not_found)
+ end
+ end
+end
diff --git a/spec/frontend/boards/stores/mutations_spec.js b/spec/frontend/boards/stores/mutations_spec.js
index 7d79993a0ee..1606ca09d8f 100644
--- a/spec/frontend/boards/stores/mutations_spec.js
+++ b/spec/frontend/boards/stores/mutations_spec.js
@@ -518,17 +518,6 @@ describe('Board Store Mutations', () => {
expect(state.boardItemsByListId[payload.listId]).toEqual(listState);
});
-
- it("updates the list's items count", () => {
- expect(state.boardLists['gid://gitlab/List/1'].issuesCount).toBe(1);
-
- mutations.ADD_BOARD_ITEM_TO_LIST(state, {
- itemId: mockIssue2.id,
- listId: mockList.id,
- });
-
- expect(state.boardLists['gid://gitlab/List/1'].issuesCount).toBe(2);
- });
});
describe('REMOVE_BOARD_ITEM_FROM_LIST', () => {
@@ -536,8 +525,7 @@ describe('Board Store Mutations', () => {
setBoardsListsState();
});
- it("removes an item from a list and updates the list's items count", () => {
- expect(state.boardLists['gid://gitlab/List/1'].issuesCount).toBe(1);
+ it('removes an item from a list', () => {
expect(state.boardItemsByListId['gid://gitlab/List/1']).toContain(mockIssue.id);
mutations.REMOVE_BOARD_ITEM_FROM_LIST(state, {
@@ -546,7 +534,6 @@ describe('Board Store Mutations', () => {
});
expect(state.boardItemsByListId['gid://gitlab/List/1']).not.toContain(mockIssue.id);
- expect(state.boardLists['gid://gitlab/List/1'].issuesCount).toBe(0);
});
});
diff --git a/spec/frontend/work_items/components/work_item_links/work_item_links_form_spec.js b/spec/frontend/work_items/components/work_item_links/work_item_links_form_spec.js
index ce927a41d74..434c1db8a2c 100644
--- a/spec/frontend/work_items/components/work_item_links/work_item_links_form_spec.js
+++ b/spec/frontend/work_items/components/work_item_links/work_item_links_form_spec.js
@@ -5,12 +5,13 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import WorkItemLinksForm from '~/work_items/components/work_item_links/work_item_links_form.vue';
-import { WORK_ITEM_TYPE_IDS } from '~/work_items/constants';
import projectWorkItemsQuery from '~/work_items/graphql/project_work_items.query.graphql';
+import projectWorkItemTypesQuery from '~/work_items/graphql/project_work_item_types.query.graphql';
import createWorkItemMutation from '~/work_items/graphql/create_work_item.mutation.graphql';
import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql';
import {
availableWorkItemsResponse,
+ projectWorkItemTypesQueryResponse,
createWorkItemMutationResponse,
updateWorkItemMutationResponse,
} from '../../mock_data';
@@ -25,11 +26,13 @@ describe('WorkItemLinksForm', () => {
const createComponent = async ({
listResponse = availableWorkItemsResponse,
+ typesResponse = projectWorkItemTypesQueryResponse,
parentConfidential = false,
} = {}) => {
wrapper = shallowMountExtended(WorkItemLinksForm, {
apolloProvider: createMockApollo([
[projectWorkItemsQuery, jest.fn().mockResolvedValue(listResponse)],
+ [projectWorkItemTypesQuery, jest.fn().mockResolvedValue(typesResponse)],
[updateWorkItemMutation, updateMutationResolver],
[createWorkItemMutation, createMutationResolver],
]),
@@ -70,7 +73,7 @@ describe('WorkItemLinksForm', () => {
input: {
title: 'Create task test',
projectPath: 'project/path',
- workItemTypeId: WORK_ITEM_TYPE_IDS.TASK,
+ workItemTypeId: 'gid://gitlab/WorkItems::Type/3',
hierarchyWidget: {
parentId: 'gid://gitlab/WorkItem/1',
},
@@ -92,7 +95,7 @@ describe('WorkItemLinksForm', () => {
input: {
title: 'Create confidential task',
projectPath: 'project/path',
- workItemTypeId: WORK_ITEM_TYPE_IDS.TASK,
+ workItemTypeId: 'gid://gitlab/WorkItems::Type/3',
hierarchyWidget: {
parentId: 'gid://gitlab/WorkItem/1',
},
diff --git a/spec/requests/projects/settings/packages_and_registries_controller_spec.rb b/spec/requests/projects/settings/packages_and_registries_controller_spec.rb
new file mode 100644
index 00000000000..6d8a152c769
--- /dev/null
+++ b/spec/requests/projects/settings/packages_and_registries_controller_spec.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Projects::Settings::PackagesAndRegistriesController do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project, reload: true) { create(:project, namespace: user.namespace) }
+
+ let(:container_registry_enabled) { true }
+ let(:container_registry_enabled_on_project) { ProjectFeature::ENABLED }
+
+ before do
+ project.project_feature.update!(container_registry_access_level: container_registry_enabled_on_project)
+ project.container_expiration_policy.update!(enabled: true)
+
+ stub_container_registry_config(enabled: container_registry_enabled)
+ end
+
+ describe 'GET #cleanup_tags' do
+ subject { get cleanup_image_tags_namespace_project_settings_packages_and_registries_path(user.namespace, project) }
+
+ context 'when user is unauthorized' do
+ let_it_be(:user) { create(:user) }
+
+ before do
+ project.add_reporter(user)
+ sign_in(user)
+ subject
+ end
+
+ it 'shows 404' do
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when user is authorized' do
+ let(:user) { project.creator }
+
+ before do
+ sign_in(user)
+ subject
+ end
+
+ it 'renders content' do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template(:cleanup_tags)
+ end
+
+ context 'when registry is disabled' do
+ let(:container_registry_enabled) { false }
+
+ it 'shows 404' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when container registry is disabled on project' do
+ let(:container_registry_enabled_on_project) { ProjectFeature::DISABLED }
+
+ it 'shows 404' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index 5d560620b85..f701dd9c488 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -772,6 +772,16 @@ RSpec.describe 'project routing' do
end
end
+ describe Projects::Settings::PackagesAndRegistriesController, 'routing' do
+ it 'to #show' do
+ expect(get('/gitlab/gitlabhq/-/settings/packages_and_registries')).to route_to('projects/settings/packages_and_registries#show', namespace_id: 'gitlab', project_id: 'gitlabhq')
+ end
+
+ it 'to #cleanup_tags' do
+ expect(get('gitlab/gitlabhq/-/settings/packages_and_registries/cleanup_image_tags')).to route_to('projects/settings/packages_and_registries#cleanup_tags', namespace_id: 'gitlab', project_id: 'gitlabhq')
+ end
+ end
+
describe Projects::Settings::IntegrationsController, 'routing' do
it 'to #index' do
expect(get('/gitlab/gitlabhq/-/settings/integrations')).to route_to('projects/settings/integrations#index', namespace_id: 'gitlab', project_id: 'gitlabhq')
diff --git a/yarn.lock b/yarn.lock
index d4ce07afc64..00f4fb92105 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3549,10 +3549,10 @@ commander@^6.0.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==
-commander@^9.2.0:
- version "9.2.0"
- resolved "https://registry.yarnpkg.com/commander/-/commander-9.2.0.tgz#6e21014b2ed90d8b7c9647230d8b7a94a4a419a9"
- integrity sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w==
+commander@^9.4.0:
+ version "9.4.0"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-9.4.0.tgz#bc4a40918fefe52e22450c111ecd6b7acce6f11c"
+ integrity sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw==
commander@~9.0.0:
version "9.0.0"
@@ -10496,7 +10496,7 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
-semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.6:
+semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.6, semver@^7.3.7:
version "7.3.7"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f"
integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==
@@ -11486,7 +11486,7 @@ tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
-tslib@^2, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.0, tslib@^2.3.1, tslib@~2.4.0:
+tslib@^2, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.0, tslib@^2.4.0, tslib@~2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3"
integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==
@@ -12459,15 +12459,15 @@ yarn-check-webpack-plugin@^1.2.0:
dependencies:
chalk "^2.4.2"
-yarn-deduplicate@^5.0.0:
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/yarn-deduplicate/-/yarn-deduplicate-5.0.0.tgz#8977b9a4b1a2fd905568c3a23507b1021fa381eb"
- integrity sha512-sYA5tqBSY3m+DtEcwfMYP1G2zWq1UtWSNg2goESqiu/JXBoBF/Qh+FuTJGGjsrisxL+5yOgq/ez1Rd+KSPwzvA==
+yarn-deduplicate@^5.0.2:
+ version "5.0.2"
+ resolved "https://registry.yarnpkg.com/yarn-deduplicate/-/yarn-deduplicate-5.0.2.tgz#b56484c94d8f1163a828bf20516607f89c078675"
+ integrity sha512-pxKa+dM7DMQ4X2vYLKqGCUgtEoTtdMVk9gNoIsxsMSP0rOV51IWFcKHfRIcZjAPNgHTrxz46sKB4xr7Nte7jdw==
dependencies:
"@yarnpkg/lockfile" "^1.1.0"
- commander "^9.2.0"
- semver "^7.3.2"
- tslib "^2.3.1"
+ commander "^9.4.0"
+ semver "^7.3.7"
+ tslib "^2.4.0"
yn@3.1.1:
version "3.1.1"