From ead25aaac3ab8e8883af13ec2e3b5308cf61ef70 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Fri, 16 Aug 2024 09:12:07 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .../style/inline_disable_annotation.yml | 1 - GITLAB_KAS_VERSION | 2 +- Gemfile.next.checksum | 2 +- Gemfile.next.lock | 2 +- .../glql/components/presenters/table.vue | 60 ++++++++++++++++ app/assets/javascripts/glql/core/presenter.js | 3 + app/assets/javascripts/glql/utils/common.js | 10 ++- .../components/import_actions_cell.vue | 5 +- .../components/list_page/image_list_row.vue | 2 - .../components/list/package_list_row.vue | 4 +- .../components/registry/list_item.vue | 2 +- app/models/project_auto_devops.rb | 1 + app/models/user.rb | 1 + app/models/user_preference.rb | 10 +++ .../packages/maven/cached_response.rb | 2 +- config/application.rb | 8 ++- .../action_dispatch_journey_router.rb | 14 +++- ...istries_packages_maven_cached_responses.rb | 10 +++ ...09_add_dpop_enabled_to_user_preferences.rb | 9 +++ db/schema_migrations/20240813170304 | 1 + db/schema_migrations/20240814230209 | 1 + db/structure.sql | 3 +- .../backup_restore/backup_cli.md | 1 + lib/gitlab/database.rb | 19 +++-- .../observers/transaction_duration.rb | 28 +++++--- .../pagination/keyset/simple_order_builder.rb | 2 +- lib/gitlab/patch/old_redis_cache_store.rb | 70 ++++++++++++++++++ lib/gitlab/patch/redis_cache_store.rb | 36 ++++++---- lib/gitlab/redis/cluster_util.rb | 30 ++++---- .../redis/multi_store_connection_pool.rb | 2 + .../glql/components/presenters/table_spec.js | 62 ++++++++++++++++ spec/frontend/glql/core/presenter_spec.js | 22 +++--- spec/frontend/glql/utils/common_spec.js | 20 +++++- .../package_list_row_spec.js.snap | 2 +- .../package_list_row_spec.js.snap | 2 +- .../observers/transaction_duration_spec.rb | 15 +++- spec/lib/gitlab/database_spec.rb | 35 ++------- spec/models/project_auto_devops_spec.rb | 71 ++++++++----------- spec/models/user_preference_spec.rb | 28 ++++++++ spec/models/user_spec.rb | 3 + .../packages/maven/cached_response_spec.rb | 14 ++++ workhorse/go.mod | 4 +- workhorse/go.sum | 8 +-- 43 files changed, 474 insertions(+), 153 deletions(-) create mode 100644 app/assets/javascripts/glql/components/presenters/table.vue create mode 100644 db/migrate/20240813170304_allow_null_for_upstream_id_in_virtual_registries_packages_maven_cached_responses.rb create mode 100644 db/migrate/20240814230209_add_dpop_enabled_to_user_preferences.rb create mode 100644 db/schema_migrations/20240813170304 create mode 100644 db/schema_migrations/20240814230209 create mode 100644 lib/gitlab/patch/old_redis_cache_store.rb create mode 100644 spec/frontend/glql/components/presenters/table_spec.js diff --git a/.rubocop_todo/style/inline_disable_annotation.yml b/.rubocop_todo/style/inline_disable_annotation.yml index 26fce6c072d..bd46ad61844 100644 --- a/.rubocop_todo/style/inline_disable_annotation.yml +++ b/.rubocop_todo/style/inline_disable_annotation.yml @@ -1401,7 +1401,6 @@ Style/InlineDisableAnnotation: - 'ee/app/services/app_sec/dast/sites/find_or_create_service.rb' - 'ee/app/services/approval_rules/params_filtering_service.rb' - 'ee/app/services/billable_members/destroy_service.rb' - - 'ee/app/services/ci/minutes/additional_packs/base_service.rb' - 'ee/app/services/ci/minutes/additional_packs/create_service.rb' - 'ee/app/services/ci/minutes/refresh_cached_data_service.rb' - 'ee/app/services/ci/minutes/reset_usage_service.rb' diff --git a/GITLAB_KAS_VERSION b/GITLAB_KAS_VERSION index b9111c74dd2..a07908be3cc 100644 --- a/GITLAB_KAS_VERSION +++ b/GITLAB_KAS_VERSION @@ -1 +1 @@ -3d2dea2bdbf379da45781469d044f11dc16bace0 +83d81744aa7215577e8b0bef40723a787de26793 diff --git a/Gemfile.next.checksum b/Gemfile.next.checksum index 042ffc8a383..8d3db1f7e5d 100644 --- a/Gemfile.next.checksum +++ b/Gemfile.next.checksum @@ -571,7 +571,7 @@ {"name":"redis-clustering","version":"5.2.0","platform":"ruby","checksum":"685f388e0bdd81091a96cce9a46e22e727213d5fa14ebfc5111e110440e0038e"}, {"name":"redis-namespace","version":"1.11.0","platform":"ruby","checksum":"e91a1aa2b2d888b6dea1d4ab8d39e1ae6fac3426161feb9d91dd5cca598a2239"}, {"name":"redis-rack","version":"3.0.0","platform":"ruby","checksum":"abb50b82ae10ad4d11ca2e4901bfc2b98256cdafbbd95f80c86fc9e001478380"}, -{"name":"redis-store","version":"1.10.0","platform":"ruby","checksum":"f258894f9f7e82834308a3d86242294f0cff2c9db9ae66e5cb4c553a5ec8b09e"}, +{"name":"redis-store","version":"1.11.0","platform":"ruby","checksum":"edc4f3e239dcd1fdd9905584e6b1e623a84618e14436e6e8a07c70891008eda4"}, {"name":"regexp_parser","version":"2.6.0","platform":"ruby","checksum":"f163ba463a45ca2f2730e0902f2475bb0eefcd536dfc2f900a86d1e5a7d7a556"}, {"name":"regexp_property_values","version":"1.0.0","platform":"java","checksum":"5e26782b01241616855c4ee7bb8a62fce9387e484f2d3eaf04f2a0633708222e"}, {"name":"regexp_property_values","version":"1.0.0","platform":"ruby","checksum":"162499dc0bba1e66d334273a059f207a61981cc8cc69d2ca743594e7886d080f"}, diff --git a/Gemfile.next.lock b/Gemfile.next.lock index 668bb517755..f3b56d6cd00 100644 --- a/Gemfile.next.lock +++ b/Gemfile.next.lock @@ -1553,7 +1553,7 @@ GEM redis-rack (3.0.0) rack-session (>= 0.2.0) redis-store (>= 1.2, < 2) - redis-store (1.10.0) + redis-store (1.11.0) redis (>= 4, < 6) regexp_parser (2.6.0) regexp_property_values (1.0.0) diff --git a/app/assets/javascripts/glql/components/presenters/table.vue b/app/assets/javascripts/glql/components/presenters/table.vue new file mode 100644 index 00000000000..f6dc5752583 --- /dev/null +++ b/app/assets/javascripts/glql/components/presenters/table.vue @@ -0,0 +1,60 @@ + + diff --git a/app/assets/javascripts/glql/core/presenter.js b/app/assets/javascripts/glql/core/presenter.js index bbcf8fc632e..d001de87de6 100644 --- a/app/assets/javascripts/glql/core/presenter.js +++ b/app/assets/javascripts/glql/core/presenter.js @@ -3,10 +3,13 @@ import LinkPresenter from '../components/presenters/link.vue'; import TextPresenter from '../components/presenters/text.vue'; import ListPresenter from '../components/presenters/list.vue'; import NullPresenter from '../components/presenters/null.vue'; +import TablePresenter from '../components/presenters/table.vue'; const presentersByDisplayType = { list: ListPresenter, orderedList: ListPresenter, + + table: TablePresenter, }; const olProps = { listType: 'ol' }; diff --git a/app/assets/javascripts/glql/utils/common.js b/app/assets/javascripts/glql/utils/common.js index 58006820b5e..14302e39053 100644 --- a/app/assets/javascripts/glql/utils/common.js +++ b/app/assets/javascripts/glql/utils/common.js @@ -1,8 +1,7 @@ import jsYaml from 'js-yaml'; -import { uniq } from 'lodash'; +import { uniq, upperFirst, lowerCase } from 'lodash'; -export const extractGroupOrProject = () => { - const url = window.location.href; +export const extractGroupOrProject = (url = window.location.href) => { let fullPath = url .replace(window.location.origin, '') .split('/-/')[0] @@ -31,3 +30,8 @@ export const parseFrontmatter = (frontmatter, defaults = {}) => { config.display = config.display || 'list'; return config; }; + +export const toSentenceCase = (str) => { + if (str === 'id' || str === 'iid') return str.toUpperCase(); + return upperFirst(lowerCase(str)); +}; diff --git a/app/assets/javascripts/import_entities/import_groups/components/import_actions_cell.vue b/app/assets/javascripts/import_entities/import_groups/components/import_actions_cell.vue index 89b2c669618..5b4c95ee94a 100644 --- a/app/assets/javascripts/import_entities/import_groups/components/import_actions_cell.vue +++ b/app/assets/javascripts/import_entities/import_groups/components/import_actions_cell.vue @@ -27,9 +27,8 @@ export default { directives: { GlTooltip: GlTooltipDirective, }, - // eslint-disable-next-line local-rules/require-valid-help-page-path - projectCreationHelp: helpPagePath('user/group/import/index', { - anchor: 'ensure-projects-can-be-imported', + projectCreationHelp: helpPagePath('user/group/import/direct_transfer_migrations', { + anchor: 'configuration', }), props: { id: { diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue index 387232fd691..5e00b666ac3 100644 --- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue +++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue @@ -182,7 +182,6 @@ export default { @@ -191,7 +190,6 @@ export default { v-if="showBadgeProtected" v-gl-tooltip :title="$options.i18n.badgeProtectedTooltipText" - class="gl-ml-3" size="sm" variant="neutral" > diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue index 8636a40cbe5..a1794406374 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue @@ -140,10 +140,9 @@ export default { {{ packageEntity.name }} -
+
diff --git a/app/assets/javascripts/vue_shared/components/registry/list_item.vue b/app/assets/javascripts/vue_shared/components/registry/list_item.vue index ff25ec8f01e..faf628ff058 100644 --- a/app/assets/javascripts/vue_shared/components/registry/list_item.vue +++ b/app/assets/javascripts/vue_shared/components/registry/list_item.vue @@ -98,7 +98,7 @@ export default { 'left-secondary' ] " - class="gl-flex gl-min-h-6 gl-min-w-0 gl-grow gl-items-center gl-text-sm gl-text-secondary" + class="gl-flex gl-min-h-6 gl-min-w-0 gl-grow gl-items-center gl-text-sm gl-text-subtle gl-gap-3" >
diff --git a/app/models/project_auto_devops.rb b/app/models/project_auto_devops.rb index 275fe81583f..7894d4b3465 100644 --- a/app/models/project_auto_devops.rb +++ b/app/models/project_auto_devops.rb @@ -26,6 +26,7 @@ class ProjectAutoDevops < ApplicationRecord def create_gitlab_deploy_token project.deploy_tokens.create!( name: DeployToken::GITLAB_DEPLOY_TOKEN_NAME, + project_id: project_id, read_registry: true ) end diff --git a/app/models/user.rb b/app/models/user.rb index 45c36c5c44b..bb187c62b0a 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -434,6 +434,7 @@ class User < ApplicationRecord :achievements_enabled, :achievements_enabled=, :enabled_following, :enabled_following=, :home_organization, :home_organization_id, :home_organization_id=, + :dpop_enabled, :dpop_enabled=, to: :user_preference delegate :path, to: :namespace, allow_nil: true, prefix: true diff --git a/app/models/user_preference.rb b/app/models/user_preference.rb index b1c78955239..ce438fd8a54 100644 --- a/app/models/user_preference.rb +++ b/app/models/user_preference.rb @@ -44,6 +44,7 @@ class UserPreference < ApplicationRecord attribute :project_shortcut_buttons, default: true attribute :keyboard_shortcuts_enabled, default: true attribute :use_web_ide_extension_marketplace, default: false + attribute :dpop_enabled, default: false enum :visibility_pipeline_id_type, { id: 0, iid: 1 }, scopes: false @@ -149,6 +150,15 @@ class UserPreference < ApplicationRecord self.extensions_marketplace_opt_in_status = status end + def dpop_enabled=(value) + if value.nil? + default = self.class.column_defaults['dpop_enabled'] + super(default) + else + super(value) + end + end + private def user_belongs_to_home_organization diff --git a/app/models/virtual_registries/packages/maven/cached_response.rb b/app/models/virtual_registries/packages/maven/cached_response.rb index 35d5e2cec24..9a1dc029f24 100644 --- a/app/models/virtual_registries/packages/maven/cached_response.rb +++ b/app/models/virtual_registries/packages/maven/cached_response.rb @@ -22,7 +22,7 @@ module VirtualRegistries :content_type, length: { maximum: 255 } validates :downloads_count, numericality: { greater_than: 0, only_integer: true } - validates :relative_path, uniqueness: { scope: :upstream_id } + validates :relative_path, uniqueness: { scope: :upstream_id }, if: :upstream validates :file, presence: true mount_file_store_uploader ::VirtualRegistries::CachedResponseUploader diff --git a/config/application.rb b/config/application.rb index 19b2ff81082..3dc5dc49870 100644 --- a/config/application.rb +++ b/config/application.rb @@ -87,6 +87,7 @@ module Gitlab require_dependency Rails.root.join('lib/gitlab/runtime') require_dependency Rails.root.join('lib/gitlab/patch/database_config') require_dependency Rails.root.join('lib/gitlab/patch/redis_cache_store') + require_dependency Rails.root.join('lib/gitlab/patch/old_redis_cache_store') require_dependency Rails.root.join('lib/gitlab/exceptions_app') config.exceptions_app = Gitlab::ExceptionsApp.new(Gitlab.jh? ? Rails.root.join('jh/public') : Rails.public_path) @@ -535,7 +536,12 @@ module Gitlab end # Use caching across all environments - ActiveSupport::Cache::RedisCacheStore.prepend(Gitlab::Patch::RedisCacheStore) + if ::Gitlab.next_rails? + ActiveSupport::Cache::RedisCacheStore.prepend(Gitlab::Patch::RedisCacheStore) + else + ActiveSupport::Cache::RedisCacheStore.prepend(Gitlab::Patch::OldRedisCacheStore) + end + config.cache_store = :redis_cache_store, Gitlab::Redis::Cache.active_support_config config.active_job.queue_adapter = :sidekiq diff --git a/config/initializers/action_dispatch_journey_router.rb b/config/initializers/action_dispatch_journey_router.rb index 14f5ee367a6..71739dc67fc 100644 --- a/config/initializers/action_dispatch_journey_router.rb +++ b/config/initializers/action_dispatch_journey_router.rb @@ -41,7 +41,19 @@ module ActionDispatch val = match_data[i + 1] path_parameters[name.to_sym] = Utils.unescape_uri(val) if val end - [match_data, path_parameters, r] + + # This is the minimal version to support both Rails 7.0 and Rails 7.1 + # + # - https://github.com/rails/rails/blob/v7.1.3.4/actionpack/lib/action_dispatch/journey/router.rb#L131 + # + # - https://github.com/rails/rails/blob/v7.0.8.4/actionpack/lib/action_dispatch/journey/router.rb#L130 + # + # After the upgrade, this method can be more like the v7.1.3.4 version + if Gitlab.next_rails? + yield [match_data, path_parameters, r] + else + [match_data, path_parameters, r] + end end.compact! routes diff --git a/db/migrate/20240813170304_allow_null_for_upstream_id_in_virtual_registries_packages_maven_cached_responses.rb b/db/migrate/20240813170304_allow_null_for_upstream_id_in_virtual_registries_packages_maven_cached_responses.rb new file mode 100644 index 00000000000..9fa92c53c99 --- /dev/null +++ b/db/migrate/20240813170304_allow_null_for_upstream_id_in_virtual_registries_packages_maven_cached_responses.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class AllowNullForUpstreamIdInVirtualRegistriesPackagesMavenCachedResponses < Gitlab::Database::Migration[2.2] + enable_lock_retries! + milestone '17.4' + + def change + change_column_null :virtual_registries_packages_maven_cached_responses, :upstream_id, true + end +end diff --git a/db/migrate/20240814230209_add_dpop_enabled_to_user_preferences.rb b/db/migrate/20240814230209_add_dpop_enabled_to_user_preferences.rb new file mode 100644 index 00000000000..9de43b0d7a6 --- /dev/null +++ b/db/migrate/20240814230209_add_dpop_enabled_to_user_preferences.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddDpopEnabledToUserPreferences < Gitlab::Database::Migration[2.2] + milestone '17.4' + + def change + add_column :user_preferences, :dpop_enabled, :boolean, default: false, null: false, if_not_exists: true + end +end diff --git a/db/schema_migrations/20240813170304 b/db/schema_migrations/20240813170304 new file mode 100644 index 00000000000..5168ed2f4a2 --- /dev/null +++ b/db/schema_migrations/20240813170304 @@ -0,0 +1 @@ +c7671c356fd7343b8800a995810a1adee1333f57da2cede316b6d1770b0edc32 \ No newline at end of file diff --git a/db/schema_migrations/20240814230209 b/db/schema_migrations/20240814230209 new file mode 100644 index 00000000000..8195aae082f --- /dev/null +++ b/db/schema_migrations/20240814230209 @@ -0,0 +1 @@ +0e8f60bc7e3767525dccadd4e5bbc3ae6c2c1e13c8f55ed78e351caadae23b6f \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 5d9973e7251..e4a73d47def 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -19116,6 +19116,7 @@ CREATE TABLE user_preferences ( extensions_marketplace_opt_in_status smallint DEFAULT 0 NOT NULL, organization_groups_projects_sort text, organization_groups_projects_display smallint DEFAULT 0 NOT NULL, + dpop_enabled boolean DEFAULT false NOT NULL, CONSTRAINT check_1d670edc68 CHECK ((time_display_relative IS NOT NULL)), CONSTRAINT check_89bf269f41 CHECK ((char_length(diffs_deletion_color) <= 7)), CONSTRAINT check_b1306f8875 CHECK ((char_length(organization_groups_projects_sort) <= 64)), @@ -19278,7 +19279,7 @@ ALTER SEQUENCE value_stream_dashboard_counts_id_seq OWNED BY value_stream_dashbo CREATE TABLE virtual_registries_packages_maven_cached_responses ( id bigint NOT NULL, group_id bigint NOT NULL, - upstream_id bigint NOT NULL, + upstream_id bigint, upstream_checked_at timestamp with time zone DEFAULT now() NOT NULL, downloaded_at timestamp with time zone DEFAULT now() NOT NULL, created_at timestamp with time zone NOT NULL, diff --git a/doc/administration/backup_restore/backup_cli.md b/doc/administration/backup_restore/backup_cli.md index 4728ba0f7bf..d4b26878bd0 100644 --- a/doc/administration/backup_restore/backup_cli.md +++ b/doc/administration/backup_restore/backup_cli.md @@ -2,6 +2,7 @@ stage: Systems group: Geo info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments +ignore_in_report: true --- # Back up and Restore GitLab with `gitlab-backup-cli` diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb index 6fd2749c321..082d87f62ea 100644 --- a/lib/gitlab/database.rb +++ b/lib/gitlab/database.rb @@ -282,7 +282,14 @@ module Gitlab return unless connection.respond_to?(:pool) && connection.pool.respond_to?(:db_config) - connection.pool.db_config + db_config = connection.pool.db_config + db_config unless empty_config?(db_config) + end + + def self.empty_config?(db_config) + return true unless db_config + + ::Gitlab.next_rails? && db_config.is_a?(ActiveRecord::ConnectionAdapters::NullPool::NullConfig) end # At the moment, the connection can only be retrieved by @@ -353,10 +360,14 @@ module Gitlab ::Gitlab::Database::Metrics.subtransactions_increment(self.name) if transaction_type == :sub_transaction - payload = { connection: connection, transaction_type: transaction_type } - - ActiveSupport::Notifications.instrument('transaction.active_record', payload) do + if ::Gitlab.next_rails? super(**options, &block) + else + payload = { connection: connection, transaction_type: transaction_type } + + ActiveSupport::Notifications.instrument('transaction.active_record', payload) do + super(**options, &block) + end end end diff --git a/lib/gitlab/database/migrations/observers/transaction_duration.rb b/lib/gitlab/database/migrations/observers/transaction_duration.rb index 182aeb10fda..7a7b4500a0f 100644 --- a/lib/gitlab/database/migrations/observers/transaction_duration.rb +++ b/lib/gitlab/database/migrations/observers/transaction_duration.rb @@ -11,7 +11,7 @@ module Gitlab @writer = Oj::StreamWriter.new(@file, {}) @writer.push_array @subscriber = ActiveSupport::Notifications.subscribe('transaction.active_record') do |*args| - record_sql_event(*args) + record_event(*args) end end @@ -26,14 +26,26 @@ module Gitlab # no-op end - def record_sql_event(_name, started, finished, _unique_id, payload) - return if payload[:transaction_type] == :fake_transaction + private - @writer.push_value({ - start_time: started.iso8601(6), - end_time: finished.iso8601(6), - transaction_type: payload[:transaction_type] - }) + def record_event(_name, started, finished, _unique_id, payload) + if ::Gitlab.next_rails? + stack_count = payload[:connection].open_transactions + + @writer.push_value({ + start_time: started.iso8601(6), + end_time: finished.iso8601(6), + transaction_type: stack_count == 0 ? :real_transaction : :sub_transaction + }) + else + return if payload[:transaction_type] == :fake_transaction + + @writer.push_value({ + start_time: started.iso8601(6), + end_time: finished.iso8601(6), + transaction_type: payload[:transaction_type] + }) + end end end end diff --git a/lib/gitlab/pagination/keyset/simple_order_builder.rb b/lib/gitlab/pagination/keyset/simple_order_builder.rb index a0f7b81a21f..b962a6a2a1c 100644 --- a/lib/gitlab/pagination/keyset/simple_order_builder.rb +++ b/lib/gitlab/pagination/keyset/simple_order_builder.rb @@ -27,7 +27,7 @@ module Gitlab @primary_keys = if @model_class.primary_key.nil? @model_class.connection.primary_keys(@model_class.table_name) else - [@model_class.primary_key] + Array.wrap(@model_class.primary_key) end end diff --git a/lib/gitlab/patch/old_redis_cache_store.rb b/lib/gitlab/patch/old_redis_cache_store.rb new file mode 100644 index 00000000000..1aec6edfd15 --- /dev/null +++ b/lib/gitlab/patch/old_redis_cache_store.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +module Gitlab + module Patch + module OldRedisCacheStore + # We will try keep patched code explicit and matching the original signature in + # https://github.com/rails/rails/blob/v6.1.7.2/activesupport/lib/active_support/cache/redis_cache_store.rb#L361 + def read_multi_mget(*names) # rubocop:disable Style/ArgumentsForwarding -- Overridden patch + return super unless enable_rails_cache_pipeline_patch? + return super unless use_patched_mget? + + patched_read_multi_mget(*names) # rubocop:disable Style/ArgumentsForwarding -- Overridden patch + end + + # `delete_multi_entries` in Rails runs a multi-key `del` command + # patch will run pipelined single-key `del` for Redis Cluster compatibility + def delete_multi_entries(entries, **options) + return super unless enable_rails_cache_pipeline_patch? + + redis.with do |conn| + ::Gitlab::Redis::ClusterUtil.batch_del(entries, conn) + end + end + + # Copied from https://github.com/rails/rails/blob/v6.1.6.1/activesupport/lib/active_support/cache/redis_cache_store.rb + # re-implements `read_multi_mget` using a pipeline of `get`s rather than an `mget` + # + def patched_read_multi_mget(*names) + options = names.extract_options! + options = merged_options(options) + return {} if names == [] + + raw = options&.fetch(:raw, false) + + keys = names.map { |name| normalize_key(name, options) } + + values = failsafe(:patched_read_multi_mget, returning: {}) do + redis.with do |c| + ::Gitlab::Redis::ClusterUtil.batch_get(keys, c) + end + end + + names.zip(values).each_with_object({}) do |(name, value), results| + if value # rubocop:disable Style/Next -- Overridden patch + entry = deserialize_entry(value, raw: raw) + unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(name, options)) + results[name] = entry.value + end + end + end + end + + private + + def enable_rails_cache_pipeline_patch? + redis.with { |c| ::Gitlab::Redis::ClusterUtil.cluster?(c) } + end + + # MultiStore reads ONLY from the default store (no fallback), hence we can use `mget` + # if the default store is not a Redis::Cluster. We should do that as pipelining gets on a single redis is slow + def use_patched_mget? + redis.with do |conn| + next true unless conn.is_a?(Gitlab::Redis::MultiStore) + + ::Gitlab::Redis::ClusterUtil.cluster?(conn.default_store) + end + end + end + end +end diff --git a/lib/gitlab/patch/redis_cache_store.rb b/lib/gitlab/patch/redis_cache_store.rb index aaede06579a..609813fa686 100644 --- a/lib/gitlab/patch/redis_cache_store.rb +++ b/lib/gitlab/patch/redis_cache_store.rb @@ -4,12 +4,12 @@ module Gitlab module Patch module RedisCacheStore # We will try keep patched code explicit and matching the original signature in - # https://github.com/rails/rails/blob/v6.1.7.2/activesupport/lib/active_support/cache/redis_cache_store.rb#L361 - def read_multi_mget(*names) # rubocop:disable Style/ArgumentsForwarding + # https://github.com/rails/rails/blob/v7.1.3.4/activesupport/lib/active_support/cache/redis_cache_store.rb#L324 + def read_multi_entries(...) return super unless enable_rails_cache_pipeline_patch? return super unless use_patched_mget? - patched_read_multi_mget(*names) # rubocop:disable Style/ArgumentsForwarding + patched_read_multi_entries(...) end # `delete_multi_entries` in Rails runs a multi-key `del` command @@ -22,11 +22,19 @@ module Gitlab end end - # Copied from https://github.com/rails/rails/blob/v6.1.6.1/activesupport/lib/active_support/cache/redis_cache_store.rb - # re-implements `read_multi_mget` using a pipeline of `get`s rather than an `mget` - # - def patched_read_multi_mget(*names) - options = names.extract_options! + # `pipeline_entries` is used by Rails for multi-key writes + # patch will run pipelined single-key for Redis Cluster compatibility + def pipeline_entries(entries, &block) + return super unless enable_rails_cache_pipeline_patch? + + redis.with do |conn| + ::Gitlab::Redis::ClusterUtil.batch(entries, conn, &block) + end + end + + # Copied from https://github.com/rails/rails/blob/v7.1.3.4/activesupport/lib/active_support/cache/redis_cache_store.rb#L324 + # re-implements `read_multi_entries` using a pipeline of `get`s rather than an `mget` + def patched_read_multi_entries(names, **options) options = merged_options(options) return {} if names == [] @@ -34,18 +42,18 @@ module Gitlab keys = names.map { |name| normalize_key(name, options) } - values = failsafe(:patched_read_multi_mget, returning: {}) do + values = failsafe(:patched_read_multi_entries, returning: {}) do redis.with do |c| ::Gitlab::Redis::ClusterUtil.batch_get(keys, c) end end names.zip(values).each_with_object({}) do |(name, value), results| - if value # rubocop:disable Style/Next - entry = deserialize_entry(value, raw: raw) - unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(name, options)) - results[name] = entry.value - end + next unless value + + entry = deserialize_entry(value, raw: raw) + unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(name, options)) + results[name] = entry.value end end end diff --git a/lib/gitlab/redis/cluster_util.rb b/lib/gitlab/redis/cluster_util.rb index 0538fdfe834..e00c48264e4 100644 --- a/lib/gitlab/redis/cluster_util.rb +++ b/lib/gitlab/redis/cluster_util.rb @@ -18,30 +18,28 @@ module Gitlab end def batch_unlink(keys, redis) - expired_count = 0 - keys.each_slice(pipeline_batch_size) do |subset| - expired_count += redis.pipelined do |pipeline| - subset.each { |key| pipeline.unlink(key) } - end.sum - end - expired_count + batch(keys, redis) do |pipeline, subset| + subset.each { |key| pipeline.unlink(key) } + end.sum end def batch_del(keys, redis) - expired_count = 0 - keys.each_slice(pipeline_batch_size) do |subset| - expired_count += redis.pipelined do |pipeline| - subset.each { |key| pipeline.del(key) } - end.sum - end - expired_count + batch(keys, redis) do |pipeline, subset| + subset.each { |key| pipeline.del(key) } + end.sum end # Redis cluster alternative to mget def batch_get(keys, redis) - keys.each_slice(pipeline_batch_size).flat_map do |subset| + batch(keys, redis) do |pipeline, subset| + subset.map { |key| pipeline.get(key) } + end.flatten + end + + def batch(entries, redis) + entries.each_slice(pipeline_batch_size).flat_map do |subset| redis.pipelined do |pipeline| - subset.map { |key| pipeline.get(key) } + yield pipeline, subset end end end diff --git a/lib/gitlab/redis/multi_store_connection_pool.rb b/lib/gitlab/redis/multi_store_connection_pool.rb index 32f3bcee535..f5b68ff28a3 100644 --- a/lib/gitlab/redis/multi_store_connection_pool.rb +++ b/lib/gitlab/redis/multi_store_connection_pool.rb @@ -14,6 +14,8 @@ module Gitlab end end end + + alias_method :then, :with end end end diff --git a/spec/frontend/glql/components/presenters/table_spec.js b/spec/frontend/glql/components/presenters/table_spec.js new file mode 100644 index 00000000000..4639735b7c6 --- /dev/null +++ b/spec/frontend/glql/components/presenters/table_spec.js @@ -0,0 +1,62 @@ +import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper'; +import TablePresenter from '~/glql/components/presenters/table.vue'; +import TextPresenter from '~/glql/components/presenters/text.vue'; +import LinkPresenter from '~/glql/components/presenters/link.vue'; +import Presenter from '~/glql/core/presenter'; +import { MOCK_ISSUES, MOCK_FIELDS } from '../../mock_data'; + +describe('TablePresenter', () => { + let wrapper; + + const createWrapper = ({ data, config, ...moreProps }, mountFn = shallowMountExtended) => { + wrapper = mountFn(TablePresenter, { + provide: { + presenter: new Presenter().init({ data, config }), + }, + propsData: { data, config, ...moreProps }, + }); + }; + + it('renders header rows with sentence cased field names', () => { + createWrapper({ data: MOCK_ISSUES, config: { fields: MOCK_FIELDS } }); + + const headerRow = wrapper.find('thead tr'); + const headerCells = headerRow.findAll('th').wrappers.map((th) => th.text()); + + expect(headerCells).toEqual(['Title', 'Author', 'State']); + }); + + it('renders a row of items presented by appropriate presenters', () => { + createWrapper({ data: MOCK_ISSUES, config: { fields: MOCK_FIELDS } }, mountExtended); + + const tableRow1 = wrapper.findByTestId('table-row-0'); + const tableRow2 = wrapper.findByTestId('table-row-1'); + + const linkPresenters1 = tableRow1.findAllComponents(LinkPresenter); + const linkPresenters2 = tableRow2.findAllComponents(LinkPresenter); + const textPresenter1 = tableRow1.findComponent(TextPresenter); + const textPresenter2 = tableRow2.findComponent(TextPresenter); + + expect(linkPresenters1).toHaveLength(2); + expect(linkPresenters2).toHaveLength(2); + + expect(linkPresenters1.at(0).props('data')).toBe(MOCK_ISSUES.nodes[0]); + expect(linkPresenters1.at(1).props('data')).toBe(MOCK_ISSUES.nodes[0].author); + expect(linkPresenters2.at(0).props('data')).toBe(MOCK_ISSUES.nodes[1]); + expect(linkPresenters2.at(1).props('data')).toBe(MOCK_ISSUES.nodes[1].author); + + expect(textPresenter1.props('data')).toBe(MOCK_ISSUES.nodes[0].state); + expect(textPresenter2.props('data')).toBe(MOCK_ISSUES.nodes[1].state); + + const getCells = (row) => row.findAll('td').wrappers.map((td) => td.text()); + + expect(getCells(tableRow1)).toEqual(['Issue 1', 'foobar', 'opened']); + expect(getCells(tableRow2)).toEqual(['Issue 2', 'janedoe', 'closed']); + }); + + it('shows a "No data" message if the list of items provided is empty', () => { + createWrapper({ data: { nodes: [] }, config: { fields: MOCK_FIELDS } }); + + expect(wrapper.text()).toContain('No data found for this query'); + }); +}); diff --git a/spec/frontend/glql/core/presenter_spec.js b/spec/frontend/glql/core/presenter_spec.js index ca8197093b3..663676ee331 100644 --- a/spec/frontend/glql/core/presenter_spec.js +++ b/spec/frontend/glql/core/presenter_spec.js @@ -2,6 +2,7 @@ import Presenter, { componentForField } from '~/glql/core/presenter'; import LinkPresenter from '~/glql/components/presenters/link.vue'; import TextPresenter from '~/glql/components/presenters/text.vue'; import ListPresenter from '~/glql/components/presenters/list.vue'; +import TablePresenter from '~/glql/components/presenters/table.vue'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import { MOCK_FIELDS, MOCK_ISSUES } from '../mock_data'; @@ -18,12 +19,13 @@ describe('componentForField', () => { describe('Presenter', () => { it.each` - displayType | additionalProps - ${'list'} | ${{ listType: 'ul' }} - ${'orderedList'} | ${{ listType: 'ol' }} + displayType | additionalProps | PresenterComponent + ${'list'} | ${{ listType: 'ul' }} | ${ListPresenter} + ${'orderedList'} | ${{ listType: 'ol' }} | ${ListPresenter} + ${'table'} | ${{}} | ${TablePresenter} `( - 'inits a ListPresenter for displayType: $displayType with additionalProps: $additionalProps', - async ({ displayType, additionalProps }) => { + 'inits appropriate presenter component for displayType: $displayType with additionalProps: $additionalProps', + async ({ displayType, additionalProps, PresenterComponent }) => { const element = document.createElement('div'); element.innerHTML = '
assignee = currentUser()
'; @@ -32,12 +34,12 @@ describe('Presenter', () => { const { component } = await new Presenter().init({ data, config }); const wrapper = mountExtended(component); - const listPresenter = wrapper.findComponent(ListPresenter); + const presenter = wrapper.findComponent(PresenterComponent); - expect(listPresenter.exists()).toBe(true); - expect(listPresenter.props('data')).toBe(data); - expect(listPresenter.props('config')).toBe(config); - expect(listPresenter.props()).toMatchObject(additionalProps); + expect(presenter.exists()).toBe(true); + expect(presenter.props('data')).toBe(data); + expect(presenter.props('config')).toBe(config); + expect(presenter.props()).toMatchObject(additionalProps); }, ); }); diff --git a/spec/frontend/glql/utils/common_spec.js b/spec/frontend/glql/utils/common_spec.js index 0af6547c460..949afa55672 100644 --- a/spec/frontend/glql/utils/common_spec.js +++ b/spec/frontend/glql/utils/common_spec.js @@ -1,4 +1,9 @@ -import { extractGroupOrProject, parseQueryText, parseFrontmatter } from '~/glql/utils/common'; +import { + extractGroupOrProject, + parseQueryText, + parseFrontmatter, + toSentenceCase, +} from '~/glql/utils/common'; import { useMockLocationHelper } from 'helpers/mock_window_location_helper'; describe('extractGroupOrProject', () => { @@ -72,3 +77,16 @@ describe('parseFrontmatter', () => { }); }); }); + +describe('toSentenceCase', () => { + it.each` + str | expected + ${'title'} | ${'Title'} + ${'camelCasedExample'} | ${'Camel cased example'} + ${'snake_case_example'} | ${'Snake case example'} + ${'id'} | ${'ID'} + ${'iid'} | ${'IID'} + `('returns $expected for $str', ({ str, expected }) => { + expect(toSentenceCase(str)).toBe(expected); + }); +}); diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/__snapshots__/package_list_row_spec.js.snap b/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/__snapshots__/package_list_row_spec.js.snap index b31d6d783dd..7cb2d91244e 100644 --- a/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/__snapshots__/package_list_row_spec.js.snap +++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/__snapshots__/package_list_row_spec.js.snap @@ -33,7 +33,7 @@ exports[`packages_list_row renders 1`] = `
0.0 - expect(event.payload).to a_hash_including( - connection: be_a(Gitlab::Database::LoadBalancing::ConnectionProxy) - ) - end - end - - context 'within an empty transaction block' do - it 'publishes a transaction event' do - events = subscribe_events do - ApplicationRecord.transaction {} - Ci::ApplicationRecord.transaction {} - end - - expect(events.length).to be(2) - - event = events.first - expect(event).not_to be_nil - expect(event.duration).to be > 0.0 - expect(event.payload).to a_hash_including( - connection: be_a(Gitlab::Database::LoadBalancing::ConnectionProxy) - ) end end @@ -578,8 +557,12 @@ RSpec.describe Gitlab::Database, feature_category: :database do it 'publishes multiple transaction events' do events = subscribe_events do ApplicationRecord.transaction do - ApplicationRecord.transaction do - ApplicationRecord.transaction do + User.first + + ApplicationRecord.transaction(requires_new: true) do + User.first + + ApplicationRecord.transaction(requires_new: true) do User.first end end @@ -591,9 +574,6 @@ RSpec.describe Gitlab::Database, feature_category: :database do events.each do |event| expect(event).not_to be_nil expect(event.duration).to be > 0.0 - expect(event.payload).to a_hash_including( - connection: be_a(Gitlab::Database::LoadBalancing::ConnectionProxy) - ) end end end @@ -612,9 +592,6 @@ RSpec.describe Gitlab::Database, feature_category: :database do event = events.first expect(event).not_to be_nil expect(event.duration).to be > 0.0 - expect(event.payload).to a_hash_including( - connection: be_a(Gitlab::Database::LoadBalancing::ConnectionProxy) - ) end end end diff --git a/spec/models/project_auto_devops_spec.rb b/spec/models/project_auto_devops_spec.rb index d5f0b66b210..cfcdfbb0fff 100644 --- a/spec/models/project_auto_devops_spec.rb +++ b/spec/models/project_auto_devops_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ProjectAutoDevops do +RSpec.describe ProjectAutoDevops, feature_category: :auto_devops do let_it_be(:project) { build(:project) } it_behaves_like 'having unique enum values' @@ -65,69 +65,66 @@ RSpec.describe ProjectAutoDevops do describe '#create_gitlab_deploy_token' do let(:auto_devops) { build(:project_auto_devops, project: project) } + shared_examples 'does not create a gitlab deploy token' do + it do + expect { auto_devops.save! }.not_to change { project.deploy_tokens.count } + end + end + + shared_examples 'creates a gitlab deploy token' do + it do + expect { auto_devops.save! }.to change { project.deploy_tokens.count }.by(1) + + token = project.deploy_tokens.last + expect(token).to have_attributes( + name: DeployToken::GITLAB_DEPLOY_TOKEN_NAME, + read_registry: true, + project_id: project.id + ) + end + end + context 'when the project is public' do let(:project) { create(:project, :repository, :public) } - it 'does not create a gitlab deploy token' do - expect do - auto_devops.save! - end.not_to change { DeployToken.count } - end + include_examples 'does not create a gitlab deploy token' end context 'when the project is internal' do let(:project) { create(:project, :repository, :internal) } - it 'creates a gitlab deploy token' do - expect do - auto_devops.save! - end.to change { DeployToken.count }.by(1) - end + include_examples 'creates a gitlab deploy token' end context 'when the project is private' do let(:project) { create(:project, :repository, :private) } - it 'creates a gitlab deploy token' do - expect do - auto_devops.save! - end.to change { DeployToken.count }.by(1) - end + include_examples 'creates a gitlab deploy token' end context 'when autodevops is enabled at project level' do let(:project) { create(:project, :repository, :internal) } let(:auto_devops) { build(:project_auto_devops, project: project) } - it 'creates a deploy token' do - expect do - auto_devops.save! - end.to change { DeployToken.count }.by(1) - end + include_examples 'creates a gitlab deploy token' end context 'when autodevops is enabled at instance level' do let(:project) { create(:project, :repository, :internal) } let(:auto_devops) { build(:project_auto_devops, enabled: nil, project: project) } - it 'creates a deploy token' do + before do allow(Gitlab::CurrentSettings).to receive(:auto_devops_enabled?).and_return(true) - - expect do - auto_devops.save! - end.to change { DeployToken.count }.by(1) end + + include_examples 'creates a gitlab deploy token' end context 'when autodevops is disabled' do let(:project) { create(:project, :repository, :internal) } let(:auto_devops) { build(:project_auto_devops, :disabled, project: project) } - it 'does not create a deploy token' do - expect do - auto_devops.save! - end.not_to change { DeployToken.count } - end + include_examples 'does not create a gitlab deploy token' end context 'when the project already has an active gitlab-deploy-token' do @@ -135,11 +132,7 @@ RSpec.describe ProjectAutoDevops do let!(:deploy_token) { create(:deploy_token, :gitlab_deploy_token, projects: [project]) } let(:auto_devops) { build(:project_auto_devops, project: project) } - it 'does not create a deploy token' do - expect do - auto_devops.save! - end.not_to change { DeployToken.count } - end + include_examples 'does not create a gitlab deploy token' end context 'when the project already has a revoked gitlab-deploy-token' do @@ -147,11 +140,7 @@ RSpec.describe ProjectAutoDevops do let!(:deploy_token) { create(:deploy_token, :gitlab_deploy_token, :expired, projects: [project]) } let(:auto_devops) { build(:project_auto_devops, project: project) } - it 'does not create a deploy token' do - expect do - auto_devops.save! - end.not_to change { DeployToken.count } - end + include_examples 'does not create a gitlab deploy token' end end end diff --git a/spec/models/user_preference_spec.rb b/spec/models/user_preference_spec.rb index cdf165624cd..a642ed3520f 100644 --- a/spec/models/user_preference_spec.rb +++ b/spec/models/user_preference_spec.rb @@ -385,4 +385,32 @@ RSpec.describe UserPreference, feature_category: :user_profile do end end end + + describe '#dpop_enabled' do + let(:pref) { described_class.new(args) } + + context 'when no arguments are provided' do + let(:args) { {} } + + it 'is set to false by default' do + expect(pref.dpop_enabled).to eq(false) + end + end + + context 'when dpop_enabled is set to nil' do + let(:args) { { dpop_enabled: nil } } + + it 'returns default value' do + expect(pref.dpop_enabled).to eq(false) + end + end + + context 'when dpop_enabled is set to true' do + let(:args) { { dpop_enabled: true } } + + it 'returns assigned value' do + expect(pref.dpop_enabled).to eq(true) + end + end + end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index a2c58a2666f..7c69545fdcd 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -108,6 +108,9 @@ RSpec.describe User, feature_category: :user_profile do it { is_expected.to delegate_method(:home_organization_id).to(:user_preference) } it { is_expected.to delegate_method(:home_organization_id=).to(:user_preference).with_arguments(:args) } + it { is_expected.to delegate_method(:dpop_enabled).to(:user_preference) } + it { is_expected.to delegate_method(:dpop_enabled=).to(:user_preference).with_arguments(:args) } + it { is_expected.to delegate_method(:job_title).to(:user_detail).allow_nil } it { is_expected.to delegate_method(:job_title=).to(:user_detail).with_arguments(:args).allow_nil } diff --git a/spec/models/virtual_registries/packages/maven/cached_response_spec.rb b/spec/models/virtual_registries/packages/maven/cached_response_spec.rb index 107adcceb97..2c137016410 100644 --- a/spec/models/virtual_registries/packages/maven/cached_response_spec.rb +++ b/spec/models/virtual_registries/packages/maven/cached_response_spec.rb @@ -23,6 +23,20 @@ RSpec.describe VirtualRegistries::Packages::Maven::CachedResponse, type: :model, end it { is_expected.to validate_uniqueness_of(:relative_path).scoped_to(:upstream_id) } + + context 'when upstream_id is nil' do + let(:new_cached_response) { build(:virtual_registries_packages_maven_cached_response) } + + before do + cached_response.update!(upstream_id: nil) + new_cached_response.upstream = nil + end + + it 'does not validate uniqueness of relative_path' do + new_cached_response.validate + expect(new_cached_response.errors.messages_for(:relative_path)).not_to include 'has already been taken' + end + end end end diff --git a/workhorse/go.mod b/workhorse/go.mod index c49410048ca..6fc0262fd0c 100644 --- a/workhorse/go.mod +++ b/workhorse/go.mod @@ -29,10 +29,10 @@ require ( gitlab.com/gitlab-org/labkit v1.21.0 go.uber.org/goleak v1.3.0 gocloud.dev v0.38.0 - golang.org/x/image v0.18.0 + golang.org/x/image v0.19.0 golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 golang.org/x/net v0.28.0 - golang.org/x/oauth2 v0.21.0 + golang.org/x/oauth2 v0.22.0 golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d google.golang.org/grpc v1.65.0 google.golang.org/protobuf v1.34.2 diff --git a/workhorse/go.sum b/workhorse/go.sum index ba159f96684..151de010078 100644 --- a/workhorse/go.sum +++ b/workhorse/go.sum @@ -550,8 +550,8 @@ golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a/go.mod h1:AbB0pIl golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= -golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= +golang.org/x/image v0.19.0 h1:D9FX4QWkLfkeqaC62SonffIIuYdOk/UE2XKUBgRIBIQ= +golang.org/x/image v0.19.0/go.mod h1:y0zrRqlQRWQ5PXaYCOMLTW2fpsxZ8Qh9I/ohnInJEys= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -643,8 +643,8 @@ golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= -golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= +golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20170517211232-f52d1811a629/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=