diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-06-03 09:10:18 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-06-03 09:10:18 +0000 |
commit | e5f183140340a76754af3beabff0fcd74212a102 (patch) | |
tree | da0dcb2b596037e9ddda171d6ec63393462bae0f | |
parent | 685084aaf40a35358bd7c5135b08dc7e0d3439a7 (diff) | |
download | gitlab-ce-e5f183140340a76754af3beabff0fcd74212a102.tar.gz |
Add latest changes from gitlab-org/gitlab@master
42 files changed, 509 insertions, 529 deletions
diff --git a/.rubocop_manual_todo.yml b/.rubocop_manual_todo.yml index 7de8f7ba3f6..5992affe96a 100644 --- a/.rubocop_manual_todo.yml +++ b/.rubocop_manual_todo.yml @@ -1149,6 +1149,7 @@ RSpec/AnyInstanceOf: - 'spec/models/hooks/system_hook_spec.rb' - 'spec/models/hooks/web_hook_spec.rb' - 'spec/models/integrations/jira_spec.rb' + - 'spec/models/integrations/mattermost_slash_commands_spec.rb' - 'spec/models/issue_spec.rb' - 'spec/models/key_spec.rb' - 'spec/models/member_spec.rb' @@ -1156,7 +1157,6 @@ RSpec/AnyInstanceOf: - 'spec/models/merge_request_spec.rb' - 'spec/models/note_spec.rb' - 'spec/models/project_import_state_spec.rb' - - 'spec/models/project_services/mattermost_slash_commands_service_spec.rb' - 'spec/models/project_spec.rb' - 'spec/models/repository_spec.rb' - 'spec/models/user_spec.rb' @@ -1289,8 +1289,8 @@ RSpec/AnyInstanceOf: - 'spec/support/shared_examples/features/snippets_shared_examples.rb' - 'spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb' - 'spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb' - - 'spec/support/shared_examples/models/chat_slash_commands_shared_examples.rb' - 'spec/support/shared_examples/models/diff_note_after_commit_shared_examples.rb' + - 'spec/support/shared_examples/models/integrations/base_slash_commands_shared_examples.rb' - 'spec/support/shared_examples/models/mentionable_shared_examples.rb' - 'spec/support/shared_examples/models/with_uploads_shared_examples.rb' - 'spec/support/shared_examples/path_extraction_shared_examples.rb' @@ -1644,15 +1644,9 @@ Gitlab/NamespacedClass: - 'app/models/project_pages_metadatum.rb' - 'app/models/project_repository.rb' - 'app/models/project_repository_storage_move.rb' - - 'app/models/project_services/alerts_service.rb' - - 'app/models/project_services/alerts_service_data.rb' - - 'app/models/project_services/chat_notification_service.rb' - - 'app/models/project_services/mattermost_slash_commands_service.rb' - 'app/models/project_services/mock_monitoring_service.rb' - 'app/models/project_services/monitoring_service.rb' - 'app/models/project_services/prometheus_service.rb' - - 'app/models/project_services/slack_slash_commands_service.rb' - - 'app/models/project_services/slash_commands_service.rb' - 'app/models/project_setting.rb' - 'app/models/project_snippet.rb' - 'app/models/project_statistics.rb' diff --git a/app/assets/javascripts/cycle_analytics/components/path_navigation.vue b/app/assets/javascripts/cycle_analytics/components/path_navigation.vue index abdc546632f..c1e33f73b13 100644 --- a/app/assets/javascripts/cycle_analytics/components/path_navigation.vue +++ b/app/assets/javascripts/cycle_analytics/components/path_navigation.vue @@ -5,6 +5,7 @@ import { GlDeprecatedSkeletonLoading as GlSkeletonLoading, GlSafeHtmlDirective as SafeHtml, } from '@gitlab/ui'; +import Tracking from '~/tracking'; import { OVERVIEW_STAGE_ID } from '../constants'; export default { @@ -17,6 +18,7 @@ export default { directives: { SafeHtml, }, + mixins: [Tracking.mixin()], props: { loading: { type: Boolean, @@ -45,6 +47,14 @@ export default { hasStageCount({ stageCount = null }) { return stageCount !== null; }, + onSelectStage($event) { + this.$emit('selected', $event); + this.track('click_path_navigation', { + extra: { + stage_id: $event.id, + }, + }); + }, }, popoverOptions: { triggers: 'hover', @@ -54,7 +64,7 @@ export default { </script> <template> <gl-skeleton-loading v-if="loading" :lines="2" class="h-auto pt-2 pb-1" /> - <gl-path v-else :key="selectedStage.id" :items="stages" @selected="$emit('selected', $event)"> + <gl-path v-else :key="selectedStage.id" :items="stages" @selected="onSelectStage"> <template #default="{ pathItem, pathId }"> <gl-popover v-if="showPopover(pathItem)" diff --git a/app/helpers/gitlab_script_tag_helper.rb b/app/helpers/gitlab_script_tag_helper.rb index f784bb69dd8..467f3f7305b 100644 --- a/app/helpers/gitlab_script_tag_helper.rb +++ b/app/helpers/gitlab_script_tag_helper.rb @@ -21,12 +21,4 @@ module GitlabScriptTagHelper super end - - def preload_link_tag(source, options = {}) - # Chrome requires a nonce, see https://gitlab.com/gitlab-org/gitlab/-/issues/331810#note_584964908 - # It's likely to be a browser bug, but we need to work around it anyway - options[:nonce] = content_security_policy_nonce - - super - end end diff --git a/app/models/integrations/base_slash_commands.rb b/app/models/integrations/base_slash_commands.rb new file mode 100644 index 00000000000..eacf1184aae --- /dev/null +++ b/app/models/integrations/base_slash_commands.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +# Base class for ChatOps integrations +# This class is not meant to be used directly, but only to inherrit from. +module Integrations + class BaseSlashCommands < Integration + default_value_for :category, 'chat' + + prop_accessor :token + + has_many :chat_names, foreign_key: :service_id, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + + def valid_token?(token) + self.respond_to?(:token) && + self.token.present? && + ActiveSupport::SecurityUtils.secure_compare(token, self.token) + end + + def self.supported_events + %w() + end + + def can_test? + false + end + + def fields + [ + { type: 'text', name: 'token', placeholder: 'XXxxXXxxXXxxXXxxXXxxXXxx' } + ] + end + + def trigger(params) + return unless valid_token?(params[:token]) + + chat_user = find_chat_user(params) + user = chat_user&.user + + if user + unless user.can?(:use_slash_commands) + return Gitlab::SlashCommands::Presenters::Access.new.deactivated if user.deactivated? + + return Gitlab::SlashCommands::Presenters::Access.new.access_denied(project) + end + + Gitlab::SlashCommands::Command.new(project, chat_user, params).execute + else + url = authorize_chat_name_url(params) + Gitlab::SlashCommands::Presenters::Access.new(url).authorize + end + end + + private + + # rubocop: disable CodeReuse/ServiceClass + def find_chat_user(params) + ChatNames::FindUserService.new(self, params).execute + end + # rubocop: enable CodeReuse/ServiceClass + + # rubocop: disable CodeReuse/ServiceClass + def authorize_chat_name_url(params) + ChatNames::AuthorizeUserService.new(self, params).execute + end + # rubocop: enable CodeReuse/ServiceClass + end +end diff --git a/app/models/integrations/mattermost_slash_commands.rb b/app/models/integrations/mattermost_slash_commands.rb new file mode 100644 index 00000000000..6cd664da9e7 --- /dev/null +++ b/app/models/integrations/mattermost_slash_commands.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module Integrations + class MattermostSlashCommands < BaseSlashCommands + include Ci::TriggersHelper + + prop_accessor :token + + def can_test? + false + end + + def title + 'Mattermost slash commands' + end + + def description + "Perform common tasks with slash commands." + end + + def self.to_param + 'mattermost_slash_commands' + end + + def configure(user, params) + token = ::Mattermost::Command.new(user) + .create(command(params)) + + update(active: true, token: token) if token + rescue ::Mattermost::Error => e + [false, e.message] + end + + def list_teams(current_user) + [::Mattermost::Team.new(current_user).all, nil] + rescue ::Mattermost::Error => e + [[], e.message] + end + + def chat_responder + ::Gitlab::Chat::Responder::Mattermost + end + + private + + def command(params) + pretty_project_name = project.full_name + + params.merge( + auto_complete: true, + auto_complete_desc: "Perform common operations on: #{pretty_project_name}", + auto_complete_hint: '[help]', + description: "Perform common operations on: #{pretty_project_name}", + display_name: "GitLab / #{pretty_project_name}", + method: 'P', + username: 'GitLab') + end + end +end diff --git a/app/models/integrations/slack_slash_commands.rb b/app/models/integrations/slack_slash_commands.rb new file mode 100644 index 00000000000..ff1f806df45 --- /dev/null +++ b/app/models/integrations/slack_slash_commands.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module Integrations + class SlackSlashCommands < BaseSlashCommands + include Ci::TriggersHelper + + def title + 'Slack slash commands' + end + + def description + "Perform common operations in Slack" + end + + def self.to_param + 'slack_slash_commands' + end + + def trigger(params) + # Format messages to be Slack-compatible + super.tap do |result| + result[:text] = format(result[:text]) if result.is_a?(Hash) + end + end + + def chat_responder + ::Gitlab::Chat::Responder::Slack + end + + private + + def format(text) + ::Slack::Messenger::Util::LinkFormatter.format(text) if text + end + end +end diff --git a/app/models/postgresql/replication_slot.rb b/app/models/postgresql/replication_slot.rb index c96786423e5..e23a9546cae 100644 --- a/app/models/postgresql/replication_slot.rb +++ b/app/models/postgresql/replication_slot.rb @@ -27,7 +27,7 @@ module Postgresql # We force the use of a transaction here so the query always goes to the # primary, even when using the EE DB load balancer. - sizes = transaction { pluck(lag_function) } + sizes = transaction { pluck(Arel.sql(lag_function)) } too_great = sizes.compact.count { |size| size >= max } # If too many replicas are falling behind too much, the availability of a diff --git a/app/models/project.rb b/app/models/project.rb index 9aedd34c993..789a7074938 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -175,6 +175,7 @@ class Project < ApplicationRecord has_one :jenkins_service, class_name: 'Integrations::Jenkins' has_one :jira_service, class_name: 'Integrations::Jira' has_one :mattermost_service, class_name: 'Integrations::Mattermost' + has_one :mattermost_slash_commands_service, class_name: 'Integrations::MattermostSlashCommands' has_one :microsoft_teams_service, class_name: 'Integrations::MicrosoftTeams' has_one :mock_ci_service, class_name: 'Integrations::MockCi' has_one :packagist_service, class_name: 'Integrations::Packagist' @@ -183,12 +184,11 @@ class Project < ApplicationRecord has_one :pushover_service, class_name: 'Integrations::Pushover' has_one :redmine_service, class_name: 'Integrations::Redmine' has_one :slack_service, class_name: 'Integrations::Slack' + has_one :slack_slash_commands_service, class_name: 'Integrations::SlackSlashCommands' has_one :teamcity_service, class_name: 'Integrations::Teamcity' has_one :unify_circuit_service, class_name: 'Integrations::UnifyCircuit' has_one :webex_teams_service, class_name: 'Integrations::WebexTeams' has_one :youtrack_service, class_name: 'Integrations::Youtrack' - has_one :mattermost_slash_commands_service - has_one :slack_slash_commands_service has_one :prometheus_service, inverse_of: :project has_one :mock_monitoring_service diff --git a/app/models/project_services/mattermost_slash_commands_service.rb b/app/models/project_services/mattermost_slash_commands_service.rb deleted file mode 100644 index 5ee57aa3737..00000000000 --- a/app/models/project_services/mattermost_slash_commands_service.rb +++ /dev/null @@ -1,57 +0,0 @@ -# frozen_string_literal: true - -class MattermostSlashCommandsService < SlashCommandsService - include Ci::TriggersHelper - - prop_accessor :token - - def can_test? - false - end - - def title - 'Mattermost slash commands' - end - - def description - "Perform common tasks with slash commands." - end - - def self.to_param - 'mattermost_slash_commands' - end - - def configure(user, params) - token = ::Mattermost::Command.new(user) - .create(command(params)) - - update(active: true, token: token) if token - rescue ::Mattermost::Error => e - [false, e.message] - end - - def list_teams(current_user) - [::Mattermost::Team.new(current_user).all, nil] - rescue ::Mattermost::Error => e - [[], e.message] - end - - def chat_responder - ::Gitlab::Chat::Responder::Mattermost - end - - private - - def command(params) - pretty_project_name = project.full_name - - params.merge( - auto_complete: true, - auto_complete_desc: "Perform common operations on: #{pretty_project_name}", - auto_complete_hint: '[help]', - description: "Perform common operations on: #{pretty_project_name}", - display_name: "GitLab / #{pretty_project_name}", - method: 'P', - username: 'GitLab') - end -end diff --git a/app/models/project_services/slack_slash_commands_service.rb b/app/models/project_services/slack_slash_commands_service.rb deleted file mode 100644 index 652742c1710..00000000000 --- a/app/models/project_services/slack_slash_commands_service.rb +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true - -class SlackSlashCommandsService < SlashCommandsService - include Ci::TriggersHelper - - def title - 'Slack slash commands' - end - - def description - "Perform common operations in Slack" - end - - def self.to_param - 'slack_slash_commands' - end - - def trigger(params) - # Format messages to be Slack-compatible - super.tap do |result| - result[:text] = format(result[:text]) if result.is_a?(Hash) - end - end - - def chat_responder - ::Gitlab::Chat::Responder::Slack - end - - private - - def format(text) - ::Slack::Messenger::Util::LinkFormatter.format(text) if text - end -end diff --git a/app/models/project_services/slash_commands_service.rb b/app/models/project_services/slash_commands_service.rb deleted file mode 100644 index 37d16737052..00000000000 --- a/app/models/project_services/slash_commands_service.rb +++ /dev/null @@ -1,65 +0,0 @@ -# frozen_string_literal: true - -# Base class for Chat services -# This class is not meant to be used directly, but only to inherrit from. -class SlashCommandsService < Integration - default_value_for :category, 'chat' - - prop_accessor :token - - has_many :chat_names, foreign_key: :service_id, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent - - def valid_token?(token) - self.respond_to?(:token) && - self.token.present? && - ActiveSupport::SecurityUtils.secure_compare(token, self.token) - end - - def self.supported_events - %w() - end - - def can_test? - false - end - - def fields - [ - { type: 'text', name: 'token', placeholder: 'XXxxXXxxXXxxXXxxXXxxXXxx' } - ] - end - - def trigger(params) - return unless valid_token?(params[:token]) - - chat_user = find_chat_user(params) - user = chat_user&.user - - if user - unless user.can?(:use_slash_commands) - return Gitlab::SlashCommands::Presenters::Access.new.deactivated if user.deactivated? - - return Gitlab::SlashCommands::Presenters::Access.new.access_denied(project) - end - - Gitlab::SlashCommands::Command.new(project, chat_user, params).execute - else - url = authorize_chat_name_url(params) - Gitlab::SlashCommands::Presenters::Access.new(url).authorize - end - end - - private - - # rubocop: disable CodeReuse/ServiceClass - def find_chat_user(params) - ChatNames::FindUserService.new(self, params).execute - end - # rubocop: enable CodeReuse/ServiceClass - - # rubocop: disable CodeReuse/ServiceClass - def authorize_chat_name_url(params) - ChatNames::AuthorizeUserService.new(self, params).execute - end - # rubocop: enable CodeReuse/ServiceClass -end diff --git a/app/views/clusters/clusters/gcp/_form.html.haml b/app/views/clusters/clusters/gcp/_form.html.haml index ee2817879be..73a09f00fd6 100644 --- a/app/views/clusters/clusters/gcp/_form.html.haml +++ b/app/views/clusters/clusters/gcp/_form.html.haml @@ -62,13 +62,12 @@ %p.form-text.text-muted = s_('ClusterIntegration|Learn more about %{help_link_start_machine_type}machine types%{help_link_end} and %{help_link_start_pricing}pricing%{help_link_end}.').html_safe % { help_link_start_machine_type: help_link_start % { url: machine_type_link_url }, help_link_start_pricing: help_link_start % { url: pricing_link_url }, help_link_end: help_link_end } - - if Feature.enabled?(:create_cloud_run_clusters, clusterable, default_enabled: true) - .form-group - = provider_gcp_field.check_box :cloud_run, { label: s_('ClusterIntegration|Enable Cloud Run for Anthos'), - label_class: 'label-bold' } - .form-text.text-muted - = s_('ClusterIntegration|Uses the Cloud Run, Istio, and HTTP Load Balancing addons for this cluster.') - = link_to _('More information'), help_page_path('user/project/clusters/add_gke_clusters.md', anchor: 'cloud-run-for-anthos'), target: '_blank' + .form-group + = provider_gcp_field.check_box :cloud_run, { label: s_('ClusterIntegration|Enable Cloud Run for Anthos'), + label_class: 'label-bold' } + .form-text.text-muted + = s_('ClusterIntegration|Uses the Cloud Run, Istio, and HTTP Load Balancing addons for this cluster.') + = link_to _('More information'), help_page_path('user/project/clusters/add_gke_clusters.md', anchor: 'cloud-run-for-anthos'), target: '_blank' .form-group = field.check_box :managed, { label: s_('ClusterIntegration|GitLab-managed cluster'), diff --git a/config/feature_flags/development/cached_sidebar_open_epics_count.yml b/config/feature_flags/development/cached_sidebar_open_epics_count.yml deleted file mode 100644 index 265f3135b48..00000000000 --- a/config/feature_flags/development/cached_sidebar_open_epics_count.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: cached_sidebar_open_epics_count -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/58064 -rollout_issue_url: -milestone: '13.11' -type: development -group: group::product planning -default_enabled: true diff --git a/config/feature_flags/development/create_cloud_run_clusters.yml b/config/feature_flags/development/create_cloud_run_clusters.yml deleted file mode 100644 index 8352d6290e0..00000000000 --- a/config/feature_flags/development/create_cloud_run_clusters.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: create_cloud_run_clusters -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/19063 -rollout_issue_url: -milestone: '12.5' -type: development -group: group::configure -default_enabled: true diff --git a/doc/administration/operations/fast_ssh_key_lookup.md b/doc/administration/operations/fast_ssh_key_lookup.md index 7349052f2e6..c95842e7945 100644 --- a/doc/administration/operations/fast_ssh_key_lookup.md +++ b/doc/administration/operations/fast_ssh_key_lookup.md @@ -34,8 +34,15 @@ feature for CentOS 6, follow [the instructions on how to build and install a cus ## Fast lookup is required for Geo **(PREMIUM)** -By default, GitLab manages an `authorized_keys` file, which contains all the -public SSH keys for users allowed to access GitLab. However, to maintain a +By default, GitLab manages an `authorized_keys` file that is located in the +`git` user's home directory. For most installations, this will be located under +`/var/opt/gitlab/.ssh/authorized_keys`, but you can use the following command to locate the `authorized_keys` on your system.: + +```shell +getent passwd git | cut -d: -f6 | awk '{print $1"/.ssh/authorized_keys"}' +``` + +The `authorized_keys` file contains all the public SSH keys for users allowed to access GitLab. However, to maintain a single source of truth, [Geo](../geo/index.md) needs to be configured to perform SSH fingerprint lookups via database lookup. @@ -73,7 +80,7 @@ sudo service sshd reload ``` Confirm that SSH is working by commenting out your user's key in the `authorized_keys` -(start the line with a `#` to comment it), and attempting to pull a repository. +file (start the line with a `#` to comment it), and attempting to pull a repository. A successful pull would mean that GitLab was able to find the key in the database, since it is not present in the file anymore. diff --git a/doc/topics/autodevops/customize.md b/doc/topics/autodevops/customize.md index d544e6441ff..b9509039cb0 100644 --- a/doc/topics/autodevops/customize.md +++ b/doc/topics/autodevops/customize.md @@ -255,6 +255,27 @@ base template is migrated to use the `rules` syntax. For users who cannot migrate just yet, you can alternatively pin your templates to the [GitLab 12.10 based templates](https://gitlab.com/gitlab-org/auto-devops-v12-10). +## Use images hosted in a local Docker registry + +You can configure many Auto DevOps jobs to run in an [offline environment](../../user/application_security/offline_deployments/index.md): + +1. Copy the required Auto DevOps Docker images from Docker Hub and `registry.gitlab.com` to their local GitLab container registry. +1. After the images are hosted and available in a local registry, edit `.gitlab-ci.yml` to point to the locally-hosted images. For example: + + ```yaml + include: + - template: Auto-DevOps.gitlab-ci.yml + + variables: + REGISTRY_URL: "registry.gitlab.example" + + build: + image: "$REGISTRY_URL/docker/auto-build-image:v0.6.0" + services: + - name: "$REGISTRY_URL/greg/docker/docker:20.10.6-dind" + command: ['--tls=false', '--host=tcp://0.0.0.0:2375'] + ``` + ## PostgreSQL database support To support applications requiring a database, diff --git a/doc/user/group/epics/manage_epics.md b/doc/user/group/epics/manage_epics.md index 7bb021b4b1f..89fd32a8db1 100644 --- a/doc/user/group/epics/manage_epics.md +++ b/doc/user/group/epics/manage_epics.md @@ -385,32 +385,7 @@ To remove a child epic from a parent epic: ## Cached epic count > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/299540) in GitLab 13.11. -> - It's [deployed behind a feature flag](../../feature_flags.md), enabled by default. -> - It's enabled on GitLab.com. -> - It's recommended for production use. -> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-cached-epic-count). - -WARNING: -This feature might not be available to you. Check the **version history** note above for details. +> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/327320) in GitLab 14.0. In a group, the sidebar displays the total count of open epics and this value is cached if higher than 1000. The cached value is rounded to thousands (or millions) and updated every 24 hours. - -### Enable or disable cached epic count **(PREMIUM SELF)** - -Cached epic count in the left sidebar is under development but ready for production use. It is -deployed behind a feature flag that is **enabled by default**. -[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md) -can disable it. - -To disable it: - -```ruby -Feature.disable(:cached_sidebar_open_epics_count) -``` - -To enable it: - -```ruby -Feature.enable(:cached_sidebar_open_epics_count) -``` diff --git a/doc/user/permissions.md b/doc/user/permissions.md index 1b9a4629cb8..d69d1a4b1b3 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -10,14 +10,11 @@ Users have different abilities depending on the role they have in a particular group or project. If a user is both in a project's group and the project itself, the highest role is used. -On public and internal projects, the Guest role is not enforced. All users can: +On [public and internal projects](../api/projects.md#project-visibility-level), the Guest role +(not to be confused with [Guest user](#free-guest-users)) is not enforced. -- Create issues. -- Leave comments. -- Clone or download the project code. - -When a member leaves a team's project, all the assigned [Issues](project/issues/index.md) and [Merge Requests](project/merge_requests/index.md) -are unassigned automatically. +When a member leaves a team's project, all the assigned [issues](project/issues/index.md) and +[merge requests](project/merge_requests/index.md) are automatically unassigned. GitLab [administrators](../administration/index.md) receive all permissions. diff --git a/lib/api/helpers/services_helpers.rb b/lib/api/helpers/services_helpers.rb index 2efe6663f3f..ca13ea0789a 100644 --- a/lib/api/helpers/services_helpers.rb +++ b/lib/api/helpers/services_helpers.rb @@ -794,6 +794,7 @@ module API ::Integrations::Jenkins, ::Integrations::Jira, ::Integrations::Mattermost, + ::Integrations::MattermostSlashCommands, ::Integrations::MicrosoftTeams, ::Integrations::Packagist, ::Integrations::PipelinesEmail, @@ -801,10 +802,9 @@ module API ::Integrations::Pushover, ::Integrations::Redmine, ::Integrations::Slack, + ::Integrations::SlackSlashCommands, ::Integrations::Teamcity, ::Integrations::Youtrack, - ::MattermostSlashCommandsService, - ::SlackSlashCommandsService, ::PrometheusService ] end diff --git a/lib/gitlab/content_security_policy/config_loader.rb b/lib/gitlab/content_security_policy/config_loader.rb index 50b49071737..e42b174e085 100644 --- a/lib/gitlab/content_security_policy/config_loader.rb +++ b/lib/gitlab/content_security_policy/config_loader.rb @@ -9,7 +9,7 @@ module Gitlab def self.default_settings_hash settings_hash = { - 'enabled' => true, + 'enabled' => Rails.env.development? || Rails.env.test?, 'report_only' => false, 'directives' => { 'default_src' => "'self'", @@ -24,7 +24,7 @@ module Gitlab 'media_src' => "'self'", 'script_src' => "'strict-dynamic' 'self' 'unsafe-inline' 'unsafe-eval' https://www.google.com/recaptcha/ https://www.recaptcha.net https://apis.google.com", 'style_src' => "'self' 'unsafe-inline'", - 'worker_src' => "'self' blob: data:", + 'worker_src' => "'self'", 'object_src' => "'none'", 'report_uri' => nil } @@ -37,7 +37,6 @@ module Gitlab allow_webpack_dev_server(settings_hash) if Rails.env.development? allow_cdn(settings_hash) if ENV['GITLAB_CDN_HOST'].present? - allow_snowplow(settings_hash) if Gitlab::CurrentSettings.snowplow_enabled? settings_hash end @@ -80,11 +79,6 @@ module Gitlab append_to_directive(settings_hash, 'script_src', cdn_host) append_to_directive(settings_hash, 'style_src', cdn_host) - append_to_directive(settings_hash, 'font_src', cdn_host) - end - - def self.allow_snowplow(settings_hash) - append_to_directive(settings_hash, 'connect_src', Gitlab::CurrentSettings.snowplow_collector_hostname) end def self.append_to_directive(settings_hash, directive, text) diff --git a/lib/gitlab/integrations/sti_type.rb b/lib/gitlab/integrations/sti_type.rb index 644f6f1f804..b25b1250d01 100644 --- a/lib/gitlab/integrations/sti_type.rb +++ b/lib/gitlab/integrations/sti_type.rb @@ -6,8 +6,8 @@ module Gitlab NAMESPACED_INTEGRATIONS = Set.new(%w( Asana Assembla Bamboo Bugzilla Buildkite Campfire Confluence CustomIssueTracker Datadog Discord DroneCi EmailsOnPush Ewm ExternalWiki Flowdock HangoutsChat IssueTracker Irker - Jenkins Jira Mattermost MicrosoftTeams MockCi Packagist PipelinesEmail Pivotaltracker - Pushover Redmine Slack Teamcity UnifyCircuit Youtrack WebexTeams + Jenkins Jira Mattermost MattermostSlashCommands MicrosoftTeams MockCi Packagist PipelinesEmail Pivotaltracker + Pushover Redmine Slack SlackSlashCommands Teamcity UnifyCircuit Youtrack WebexTeams )).freeze def cast(value) diff --git a/lib/gitlab/sidekiq_middleware.rb b/lib/gitlab/sidekiq_middleware.rb index 68faaa8ab2f..30741f29563 100644 --- a/lib/gitlab/sidekiq_middleware.rb +++ b/lib/gitlab/sidekiq_middleware.rb @@ -20,6 +20,7 @@ module Gitlab chain.add ::Gitlab::SidekiqMiddleware::BatchLoader chain.add ::Labkit::Middleware::Sidekiq::Server chain.add ::Gitlab::SidekiqMiddleware::InstrumentationLogger + chain.add ::Gitlab::Database::LoadBalancing::SidekiqServerMiddleware if load_balancing_enabled? chain.add ::Gitlab::SidekiqMiddleware::AdminMode::Server chain.add ::Gitlab::SidekiqVersioning::Middleware chain.add ::Gitlab::SidekiqStatus::ServerMiddleware @@ -41,9 +42,13 @@ module Gitlab # Size limiter should be placed at the bottom, but before the metrics midleware chain.add ::Gitlab::SidekiqMiddleware::SizeLimiter::Client chain.add ::Gitlab::SidekiqMiddleware::ClientMetrics + chain.add ::Gitlab::Database::LoadBalancing::SidekiqClientMiddleware if load_balancing_enabled? end end + + def self.load_balancing_enabled? + ::Gitlab::Database::LoadBalancing.enable? + end + private_class_method :load_balancing_enabled? end end - -Gitlab::SidekiqMiddleware.singleton_class.prepend_mod_with('Gitlab::SidekiqMiddleware') diff --git a/lib/sidebars/projects/menus/security_compliance_menu.rb b/lib/sidebars/projects/menus/security_compliance_menu.rb index 6c9fb8312bd..5616b466560 100644 --- a/lib/sidebars/projects/menus/security_compliance_menu.rb +++ b/lib/sidebars/projects/menus/security_compliance_menu.rb @@ -17,7 +17,7 @@ module Sidebars override :link def link - project_security_configuration_path(context.project) + renderable_items.first&.link end override :title @@ -33,18 +33,16 @@ module Sidebars private def configuration_menu_item - strong_memoize(:configuration_menu_item) do - unless render_configuration_menu_item? - next ::Sidebars::NilMenuItem.new(item_id: :configuration) - end - - ::Sidebars::MenuItem.new( - title: _('Configuration'), - link: project_security_configuration_path(context.project), - active_routes: { path: configuration_menu_item_paths }, - item_id: :configuration - ) + unless render_configuration_menu_item? + return ::Sidebars::NilMenuItem.new(item_id: :configuration) end + + ::Sidebars::MenuItem.new( + title: _('Configuration'), + link: project_security_configuration_path(context.project), + active_routes: { path: configuration_menu_item_paths }, + item_id: :configuration + ) end def render_configuration_menu_item? diff --git a/spec/controllers/projects/mattermosts_controller_spec.rb b/spec/controllers/projects/mattermosts_controller_spec.rb index 019ae19bf72..edec8c3e9c6 100644 --- a/spec/controllers/projects/mattermosts_controller_spec.rb +++ b/spec/controllers/projects/mattermosts_controller_spec.rb @@ -13,7 +13,7 @@ RSpec.describe Projects::MattermostsController do describe 'GET #new' do before do - allow_next_instance_of(MattermostSlashCommandsService) do |instance| + allow_next_instance_of(Integrations::MattermostSlashCommands) do |instance| allow(instance).to receive(:list_teams).and_return([]) end end @@ -43,7 +43,7 @@ RSpec.describe Projects::MattermostsController do context 'no request can be made to mattermost' do it 'shows the error' do - allow_next_instance_of(MattermostSlashCommandsService) do |instance| + allow_next_instance_of(Integrations::MattermostSlashCommands) do |instance| allow(instance).to receive(:configure).and_return([false, "error message"]) end diff --git a/spec/factories/integrations.rb b/spec/factories/integrations.rb index e8ecc247dc5..71492a6faf6 100644 --- a/spec/factories/integrations.rb +++ b/spec/factories/integrations.rb @@ -167,6 +167,12 @@ FactoryBot.define do type { 'SlackService' } end + factory :slack_slash_commands_service, class: 'Integrations::SlackSlashCommands' do + project + active { true } + type { 'SlackSlashCommandsService' } + end + factory :pipelines_email_service, class: 'Integrations::PipelinesEmail' do project active { true } diff --git a/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb b/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb index 84c9b8c6b54..b2ca0424b6d 100644 --- a/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb +++ b/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb @@ -84,7 +84,7 @@ RSpec.describe 'Set up Mattermost slash commands', :js do end it 'shows an error alert with the error message if there is an error requesting teams' do - allow_any_instance_of(MattermostSlashCommandsService).to receive(:list_teams) { [[], 'test mattermost error message'] } + allow_any_instance_of(Integrations::MattermostSlashCommands).to receive(:list_teams) { [[], 'test mattermost error message'] } click_link 'Add to Mattermost' @@ -113,7 +113,7 @@ RSpec.describe 'Set up Mattermost slash commands', :js do def stub_teams(count: 0) teams = create_teams(count) - allow_any_instance_of(MattermostSlashCommandsService).to receive(:list_teams) { [teams, nil] } + allow_any_instance_of(Integrations::MattermostSlashCommands).to receive(:list_teams) { [teams, nil] } teams end diff --git a/spec/frontend/cycle_analytics/path_navigation_spec.js b/spec/frontend/cycle_analytics/path_navigation_spec.js index 182b76a1453..c6d72d3b571 100644 --- a/spec/frontend/cycle_analytics/path_navigation_spec.js +++ b/spec/frontend/cycle_analytics/path_navigation_spec.js @@ -1,11 +1,13 @@ import { GlPath, GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; +import { mockTracking, unmockTracking } from 'helpers/tracking_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import Component from '~/cycle_analytics/components/path_navigation.vue'; import { transformedProjectStagePathData, selectedStage } from './mock_data'; describe('Project PathNavigation', () => { let wrapper = null; + let trackingSpy = null; const createComponent = (props) => { return extendedWrapper( @@ -43,9 +45,11 @@ describe('Project PathNavigation', () => { beforeEach(() => { wrapper = createComponent(); + trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); }); afterEach(() => { + unmockTracking(); wrapper.destroy(); wrapper = null; }); @@ -132,5 +136,13 @@ describe('Project PathNavigation', () => { [transformedProjectStagePathData[2]], ]); }); + + it('sends tracking information', () => { + clickItemAt(0); + + expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_path_navigation', { + extra: { stage_id: selectedStage.slug }, + }); + }); }); }); diff --git a/spec/helpers/gitlab_script_tag_helper_spec.rb b/spec/helpers/gitlab_script_tag_helper_spec.rb index 35f2c0795be..37413b9b1c2 100644 --- a/spec/helpers/gitlab_script_tag_helper_spec.rb +++ b/spec/helpers/gitlab_script_tag_helper_spec.rb @@ -41,11 +41,4 @@ RSpec.describe GitlabScriptTagHelper do expect(helper.javascript_tag( '// ignored', type: 'application/javascript') { 'alert(1)' }.to_s).to eq tag_with_nonce_and_type end end - - describe '#preload_link_tag' do - it 'returns a link tag with a nonce' do - expect(helper.preload_link_tag('https://example.com/script.js').to_s) - .to eq "<link rel=\"preload\" href=\"https://example.com/script.js\" as=\"script\" type=\"text/javascript\" nonce=\"noncevalue\">" - end - end end diff --git a/spec/helpers/webpack_helper_spec.rb b/spec/helpers/webpack_helper_spec.rb index f9e2d265153..f9386c99dc3 100644 --- a/spec/helpers/webpack_helper_spec.rb +++ b/spec/helpers/webpack_helper_spec.rb @@ -15,7 +15,6 @@ RSpec.describe WebpackHelper do describe '#webpack_preload_asset_tag' do before do allow(Gitlab::Webpack::Manifest).to receive(:asset_paths).and_return([asset_path]) - allow(helper).to receive(:content_security_policy_nonce).and_return('noncevalue') end it 'preloads the resource by default' do @@ -23,7 +22,7 @@ RSpec.describe WebpackHelper do output = helper.webpack_preload_asset_tag(source) - expect(output).to eq("<link rel=\"preload\" href=\"#{asset_path}\" as=\"script\" type=\"text/javascript\" nonce=\"noncevalue\">") + expect(output).to eq("<link rel=\"preload\" href=\"#{asset_path}\" as=\"script\" type=\"text/javascript\">") end it 'prefetches the resource if explicitly asked' do diff --git a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb index 9e6bf9db907..19e52d2cf4a 100644 --- a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb +++ b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb @@ -20,9 +20,9 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do end describe '.default_settings_hash' do - it 'returns defaults for all keys' do - settings = described_class.default_settings_hash + let(:settings) { described_class.default_settings_hash } + it 'returns defaults for all keys' do expect(settings['enabled']).to be_truthy expect(settings['report_only']).to be_falsey @@ -38,32 +38,26 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do expect(directives['child_src']).to eq(directives['frame_src']) end - context 'when GITLAB_CDN_HOST is set' do + context 'when in production' do before do - stub_env('GITLAB_CDN_HOST', 'https://example.com') + allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('production')) end - it 'adds GITLAB_CDN_HOST to CSP' do - settings = described_class.default_settings_hash - directives = settings['directives'] - - expect(directives['script_src']).to eq("'strict-dynamic' 'self' 'unsafe-inline' 'unsafe-eval' https://www.google.com/recaptcha/ https://www.recaptcha.net https://apis.google.com https://example.com") - expect(directives['style_src']).to eq("'self' 'unsafe-inline' https://example.com") - expect(directives['font_src']).to eq("'self' https://example.com") + it 'is disabled' do + expect(settings['enabled']).to be_falsey end end - context 'when snowplow is configured' do + context 'when GITLAB_CDN_HOST is set' do before do - stub_application_setting(snowplow_enabled: true) - stub_application_setting(snowplow_collector_hostname: 'snowplow.example.com') + stub_env('GITLAB_CDN_HOST', 'https://example.com') end - it 'adds snowplow to CSP' do - settings = described_class.default_settings_hash + it 'adds GITLAB_CDN_HOST to CSP' do directives = settings['directives'] - expect(directives['connect_src']).to eq("'self' snowplow.example.com") + expect(directives['script_src']).to eq("'strict-dynamic' 'self' 'unsafe-inline' 'unsafe-eval' https://www.google.com/recaptcha/ https://www.recaptcha.net https://apis.google.com https://example.com") + expect(directives['style_src']).to eq("'self' 'unsafe-inline' https://example.com") end end end diff --git a/spec/lib/gitlab/sidekiq_middleware_spec.rb b/spec/lib/gitlab/sidekiq_middleware_spec.rb index 0efdef0c999..5e4e79e818e 100644 --- a/spec/lib/gitlab/sidekiq_middleware_spec.rb +++ b/spec/lib/gitlab/sidekiq_middleware_spec.rb @@ -4,215 +4,212 @@ require 'spec_helper' require 'sidekiq/testing' RSpec.describe Gitlab::SidekiqMiddleware do - before do - stub_const('TestWorker', Class.new) + let(:job_args) { [0.01] } + let(:disabled_sidekiq_middlewares) { [] } + let(:chain) { Sidekiq::Middleware::Chain.new } + let(:queue) { 'test' } + let(:enabled_sidekiq_middlewares) { all_sidekiq_middlewares - disabled_sidekiq_middlewares } + let(:worker_class) do + Class.new do + def self.name + 'TestWorker' + end - TestWorker.class_eval do - include Sidekiq::Worker include ApplicationWorker - def perform(_arg) + def perform(*args) Gitlab::SafeRequestStore['gitaly_call_actual'] = 1 Gitlab::SafeRequestStore[:gitaly_query_time] = 5 end end end - around do |example| - Sidekiq::Testing.inline! { example.run } + before do + stub_const('TestWorker', worker_class) end - let(:worker_class) { TestWorker } - let(:job_args) { [0.01] } - - # The test sets up a new server middleware stack, ensuring that the - # appropriate middlewares, as passed into server_configurator, - # are invoked. - # Additionally the test ensure that each middleware is - # 1) not failing - # 2) yielding exactly once - describe '.server_configurator' do - around do |example| - with_sidekiq_server_middleware do |chain| - described_class.server_configurator( - metrics: metrics, - arguments_logger: arguments_logger, - memory_killer: memory_killer - ).call(chain) + shared_examples "a middleware chain" do |load_balancing_enabled| + before do + allow(::Gitlab::Database::LoadBalancing).to receive(:enable?).and_return(load_balancing_enabled) + configurator.call(chain) + end - example.run + it "passes through the right middlewares", :aggregate_failures do + enabled_sidekiq_middlewares.each do |middleware| + expect_next_instances_of(middleware, 1, true) do |middleware_instance| + expect(middleware_instance).to receive(:call).with(*middleware_expected_args).once.and_call_original + end end + + expect { |b| chain.invoke(*worker_args, &b) }.to yield_control.once end + end + + shared_examples "a middleware chain for mailer" do |load_balancing_enabled| + let(:worker_class) { ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper } - let(:middleware_expected_args) { [a_kind_of(worker_class), hash_including({ 'args' => job_args }), anything] } + it_behaves_like "a middleware chain", load_balancing_enabled + end + + describe '.server_configurator' do + let(:configurator) { described_class.server_configurator } + let(:worker_args) { [worker_class.new, { 'args' => job_args }, queue] } + let(:middleware_expected_args) { [a_kind_of(worker_class), hash_including({ 'args' => job_args }), queue] } let(:all_sidekiq_middlewares) do [ - Gitlab::SidekiqMiddleware::Monitor, - Gitlab::SidekiqMiddleware::BatchLoader, - Labkit::Middleware::Sidekiq::Server, - Gitlab::SidekiqMiddleware::InstrumentationLogger, - Gitlab::SidekiqVersioning::Middleware, - Gitlab::SidekiqStatus::ServerMiddleware, - Gitlab::SidekiqMiddleware::ServerMetrics, - Gitlab::SidekiqMiddleware::ArgumentsLogger, - Gitlab::SidekiqMiddleware::MemoryKiller, - Gitlab::SidekiqMiddleware::RequestStoreMiddleware, - Gitlab::SidekiqMiddleware::ExtraDoneLogMetadata, - Gitlab::SidekiqMiddleware::WorkerContext::Server, - Gitlab::SidekiqMiddleware::AdminMode::Server, - Gitlab::SidekiqMiddleware::DuplicateJobs::Server + ::Gitlab::SidekiqMiddleware::Monitor, + ::Gitlab::SidekiqMiddleware::ServerMetrics, + ::Gitlab::SidekiqMiddleware::ArgumentsLogger, + ::Gitlab::SidekiqMiddleware::MemoryKiller, + ::Gitlab::SidekiqMiddleware::RequestStoreMiddleware, + ::Gitlab::SidekiqMiddleware::ExtraDoneLogMetadata, + ::Gitlab::SidekiqMiddleware::BatchLoader, + ::Labkit::Middleware::Sidekiq::Server, + ::Gitlab::SidekiqMiddleware::InstrumentationLogger, + ::Gitlab::Database::LoadBalancing::SidekiqServerMiddleware, + ::Gitlab::SidekiqMiddleware::AdminMode::Server, + ::Gitlab::SidekiqVersioning::Middleware, + ::Gitlab::SidekiqStatus::ServerMiddleware, + ::Gitlab::SidekiqMiddleware::WorkerContext::Server, + ::Gitlab::SidekiqMiddleware::DuplicateJobs::Server ] end - let(:enabled_sidekiq_middlewares) { all_sidekiq_middlewares - disabled_sidekiq_middlewares } + describe "server metrics" do + around do |example| + with_sidekiq_server_middleware do |chain| + described_class.server_configurator( + metrics: true, + arguments_logger: true, + memory_killer: true + ).call(chain) - shared_examples "a server middleware chain" do - it "passes through the right server middlewares" do - enabled_sidekiq_middlewares.each do |middleware| - expect_next_instance_of(middleware) do |middleware_instance| - expect(middleware_instance).to receive(:call).with(*middleware_expected_args).once.and_call_original - end + Sidekiq::Testing.inline! { example.run } end + end + let(:gitaly_histogram) { double(:gitaly_histogram) } - disabled_sidekiq_middlewares.each do |middleware| - expect(middleware).not_to receive(:new) - end + before do + allow(Gitlab::Metrics).to receive(:histogram).and_call_original + + allow(Gitlab::Metrics).to receive(:histogram) + .with(:sidekiq_jobs_gitaly_seconds, anything, anything, anything) + .and_return(gitaly_histogram) + end + + it "records correct Gitaly duration" do + expect(gitaly_histogram).to receive(:observe).with(anything, 5.0) worker_class.perform_async(*job_args) end end - shared_examples "a server middleware chain for mailer" do - let(:worker_class) { ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper } - let(:job_args) do - [ - { - "job_class" => "ActionMailer::MailDeliveryJob", - "job_id" => "a180b47c-3fd6-41b8-81e9-34da61c3400e", - "provider_job_id" => nil, - "queue_name" => "mailers", - "priority" => nil, - "arguments" => [ - "Notify", - "test_email", - "deliver_now", - { - "args" => [ - "test@example.com", - "subject", - "body" - ], - ActiveJob::Arguments.const_get('RUBY2_KEYWORDS_KEY', false) => ["args"] - } - ], - "executions" => 0, - "exception_executions" => {}, - "locale" => "en", - "timezone" => "UTC", - "enqueued_at" => "2020-07-27T07:43:31Z" - } - ] + context "all optional middlewares on" do + context "when load balancing is enabled" do + before do + allow(::Gitlab::Database::LoadBalancing).to receive_message_chain(:proxy, :load_balancer, :release_host) + end + + it_behaves_like "a middleware chain", true + it_behaves_like "a middleware chain for mailer", true end - it_behaves_like "a server middleware chain" - end + context "when load balancing is disabled" do + let(:disabled_sidekiq_middlewares) do + [ + Gitlab::Database::LoadBalancing::SidekiqServerMiddleware + ] + end - context "all optional middlewares off" do - let(:metrics) { false } - let(:arguments_logger) { false } - let(:memory_killer) { false } - let(:disabled_sidekiq_middlewares) do - [ - Gitlab::SidekiqMiddleware::ServerMetrics, - Gitlab::SidekiqMiddleware::ArgumentsLogger, - Gitlab::SidekiqMiddleware::MemoryKiller - ] + it_behaves_like "a middleware chain", false + it_behaves_like "a middleware chain for mailer", false end - - it_behaves_like "a server middleware chain" - it_behaves_like "a server middleware chain for mailer" end - context "all optional middlewares on" do - let(:metrics) { true } - let(:arguments_logger) { true } - let(:memory_killer) { true } - let(:disabled_sidekiq_middlewares) { [] } - - it_behaves_like "a server middleware chain" - it_behaves_like "a server middleware chain for mailer" + context "all optional middlewares off" do + let(:configurator) do + described_class.server_configurator( + metrics: false, + arguments_logger: false, + memory_killer: false + ) + end - context "server metrics" do - let(:gitaly_histogram) { double(:gitaly_histogram) } + context "when load balancing is enabled" do + let(:disabled_sidekiq_middlewares) do + [ + Gitlab::SidekiqMiddleware::ServerMetrics, + Gitlab::SidekiqMiddleware::ArgumentsLogger, + Gitlab::SidekiqMiddleware::MemoryKiller + ] + end before do - allow(Gitlab::Metrics).to receive(:histogram).and_call_original - - allow(Gitlab::Metrics).to receive(:histogram) - .with(:sidekiq_jobs_gitaly_seconds, anything, anything, anything) - .and_return(gitaly_histogram) + allow(::Gitlab::Database::LoadBalancing).to receive_message_chain(:proxy, :load_balancer, :release_host) end - it "records correct Gitaly duration" do - expect(gitaly_histogram).to receive(:observe).with(anything, 5.0) + it_behaves_like "a middleware chain", true + it_behaves_like "a middleware chain for mailer", true + end - worker_class.perform_async(*job_args) + context "when load balancing is disabled" do + let(:disabled_sidekiq_middlewares) do + [ + Gitlab::SidekiqMiddleware::ServerMetrics, + Gitlab::SidekiqMiddleware::ArgumentsLogger, + Gitlab::SidekiqMiddleware::MemoryKiller, + Gitlab::Database::LoadBalancing::SidekiqServerMiddleware + ] end + + it_behaves_like "a middleware chain", false + it_behaves_like "a middleware chain for mailer", false end end end - # The test sets up a new client middleware stack. The test ensures - # that each middleware is: - # 1) not failing - # 2) yielding exactly once describe '.client_configurator' do - let(:chain) { Sidekiq::Middleware::Chain.new } - let(:job) { { 'args' => job_args } } - let(:queue) { 'default' } + let(:configurator) { described_class.client_configurator } let(:redis_pool) { Sidekiq.redis_pool } - let(:middleware_expected_args) { [worker_class_arg, job, queue, redis_pool] } - let(:expected_middlewares) do + let(:middleware_expected_args) { [worker_class, hash_including({ 'args' => job_args }), queue, redis_pool] } + let(:worker_args) { [worker_class, { 'args' => job_args }, queue, redis_pool] } + let(:all_sidekiq_middlewares) do [ - ::Gitlab::SidekiqMiddleware::WorkerContext::Client, - ::Labkit::Middleware::Sidekiq::Client, - ::Gitlab::SidekiqMiddleware::DuplicateJobs::Client, - ::Gitlab::SidekiqStatus::ClientMiddleware, - ::Gitlab::SidekiqMiddleware::AdminMode::Client, - ::Gitlab::SidekiqMiddleware::SizeLimiter::Client, - ::Gitlab::SidekiqMiddleware::ClientMetrics + ::Gitlab::SidekiqMiddleware::WorkerContext::Client, + ::Labkit::Middleware::Sidekiq::Client, + ::Gitlab::SidekiqMiddleware::DuplicateJobs::Client, + ::Gitlab::SidekiqStatus::ClientMiddleware, + ::Gitlab::SidekiqMiddleware::AdminMode::Client, + ::Gitlab::SidekiqMiddleware::SizeLimiter::Client, + ::Gitlab::SidekiqMiddleware::ClientMetrics, + ::Gitlab::Database::LoadBalancing::SidekiqClientMiddleware ] end - before do - described_class.client_configurator.call(chain) - end - - shared_examples "a client middleware chain" do - # Its possible that a middleware could accidentally omit a yield call - # this will prevent the full middleware chain from being executed. - # This test ensures that this does not happen - it "invokes the chain" do - expected_middlewares do |middleware| - expect_any_instance_of(middleware).to receive(:call).with(*middleware_expected_args).once.ordered.and_call_original - end - - expect { |b| chain.invoke(worker_class_arg, job, queue, redis_pool, &b) }.to yield_control.once + context "when load balancing is disabled" do + let(:disabled_sidekiq_middlewares) do + [ + Gitlab::Database::LoadBalancing::SidekiqClientMiddleware + ] end - end - # Sidekiq documentation states that the worker class could be a string - # or a class reference. We should test for both - context "handles string worker_class values" do - let(:worker_class_arg) { worker_class.to_s } + it_behaves_like "a middleware chain", false + it_behaves_like "a middleware chain for mailer", false - it_behaves_like "a client middleware chain" - end + # Sidekiq documentation states that the worker class could be a string + # or a class reference. We should test for both + context "worker_class as string value" do + let(:worker_args) { [worker_class.to_s, { 'args' => job_args }, queue, redis_pool] } + let(:middleware_expected_args) { [worker_class.to_s, hash_including({ 'args' => job_args }), queue, redis_pool] } - context "handles string worker_class values" do - let(:worker_class_arg) { worker_class } + it_behaves_like "a middleware chain", false + it_behaves_like "a middleware chain for mailer", false + end + end - it_behaves_like "a client middleware chain" + context "when load balancing is enabled" do + it_behaves_like "a middleware chain", true + it_behaves_like "a middleware chain for mailer", true end end end diff --git a/spec/lib/sidebars/projects/menus/security_compliance_menu_spec.rb b/spec/lib/sidebars/projects/menus/security_compliance_menu_spec.rb new file mode 100644 index 00000000000..6e84beeb274 --- /dev/null +++ b/spec/lib/sidebars/projects/menus/security_compliance_menu_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Sidebars::Projects::Menus::SecurityComplianceMenu do + let_it_be(:project) { create(:project) } + + let(:user) { project.owner } + let(:show_promotions) { true } + let(:show_discover_project_security) { true } + let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project, show_promotions: show_promotions, show_discover_project_security: show_discover_project_security) } + + describe 'render?' do + subject { described_class.new(context).render? } + + context 'when user is not authenticated' do + let(:user) { nil } + + it { is_expected.to be_falsey } + end + + context 'when user is authenticated' do + context 'when the Security & Compliance is disabled' do + before do + allow(Ability).to receive(:allowed?).with(user, :access_security_and_compliance, project).and_return(false) + end + + it { is_expected.to be_falsey } + end + + context 'when the Security & Compliance is not disabled' do + it { is_expected.to be_truthy } + end + end + end +end diff --git a/spec/models/project_services/mattermost_slash_commands_service_spec.rb b/spec/models/integrations/mattermost_slash_commands_spec.rb index bbe985bfe0c..c8a6584591c 100644 --- a/spec/models/project_services/mattermost_slash_commands_service_spec.rb +++ b/spec/models/integrations/mattermost_slash_commands_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' -RSpec.describe MattermostSlashCommandsService do - it_behaves_like "chat slash commands service" +RSpec.describe Integrations::MattermostSlashCommands do + it_behaves_like Integrations::BaseSlashCommands context 'Mattermost API' do let(:project) { create(:project) } diff --git a/spec/models/project_services/slack_slash_commands_service_spec.rb b/spec/models/integrations/slack_slash_commands_spec.rb index 95c87ef01bc..a9d3c820a3c 100644 --- a/spec/models/project_services/slack_slash_commands_service_spec.rb +++ b/spec/models/integrations/slack_slash_commands_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' -RSpec.describe SlackSlashCommandsService do - it_behaves_like "chat slash commands service" +RSpec.describe Integrations::SlackSlashCommands do + it_behaves_like Integrations::BaseSlashCommands describe '#trigger' do context 'when an auth url is generated' do diff --git a/spec/models/postgresql/replication_slot_spec.rb b/spec/models/postgresql/replication_slot_spec.rb index 02a4d783b84..4bad8a3f0c0 100644 --- a/spec/models/postgresql/replication_slot_spec.rb +++ b/spec/models/postgresql/replication_slot_spec.rb @@ -24,6 +24,10 @@ RSpec.describe Postgresql::ReplicationSlot do expect(described_class).to receive(:in_use?).and_return(true) end + it 'does not raise an exception' do + expect { described_class.lag_too_great? }.not_to raise_error + end + it 'returns true when replication lag is too great' do expect(described_class) .to receive(:pluck) diff --git a/spec/support/shared_contexts/navbar_structure_context.rb b/spec/support/shared_contexts/navbar_structure_context.rb index 89c9d742033..c00b7203af6 100644 --- a/spec/support/shared_contexts/navbar_structure_context.rb +++ b/spec/support/shared_contexts/navbar_structure_context.rb @@ -18,8 +18,8 @@ RSpec.shared_context 'project navbar structure' do { nav_item: _('Security & Compliance'), nav_sub_items: [ - _('Configuration'), - (_('Audit Events') if Gitlab.ee?) + (_('Audit Events') if Gitlab.ee?), + _('Configuration') ] } end diff --git a/spec/support/shared_examples/models/chat_slash_commands_shared_examples.rb b/spec/support/shared_examples/models/integrations/base_slash_commands_shared_examples.rb index 49729afce61..128999d02fa 100644 --- a/spec/support/shared_examples/models/chat_slash_commands_shared_examples.rb +++ b/spec/support/shared_examples/models/integrations/base_slash_commands_shared_examples.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.shared_examples 'chat slash commands service' do +RSpec.shared_examples Integrations::BaseSlashCommands do describe "Associations" do it { is_expected.to respond_to :token } it { is_expected.to have_many :chat_names } diff --git a/spec/views/projects/clusters/clusters/gcp/_form.html.haml_spec.rb b/spec/views/projects/clusters/clusters/gcp/_form.html.haml_spec.rb index bf5cb6fb25d..5120998ded6 100644 --- a/spec/views/projects/clusters/clusters/gcp/_form.html.haml_spec.rb +++ b/spec/views/projects/clusters/clusters/gcp/_form.html.haml_spec.rb @@ -23,16 +23,4 @@ RSpec.describe 'clusters/clusters/gcp/_form' do expect(rendered).to have_selector("input[id='cluster_provider_gcp_attributes_cloud_run']") end end - - context 'with cloud run feature flag disabled' do - before do - stub_feature_flags(create_cloud_run_clusters: false) - end - - it 'does not have a cloud run checkbox' do - render - - expect(rendered).not_to have_selector("input[id='cluster_provider_gcp_attributes_cloud_run']") - end - end end diff --git a/workhorse/doc/operations/configuration.md b/workhorse/doc/operations/configuration.md index a78c2ac5459..8694cf1bd82 100644 --- a/workhorse/doc/operations/configuration.md +++ b/workhorse/doc/operations/configuration.md @@ -103,15 +103,11 @@ Optional fields are as follows: ``` [redis] DB = 0 -ReadTimeout = "1s" -KeepAlivePeriod = "5m" MaxIdle = 1 MaxActive = 1 ``` - `DB` is the Database to connect to. Defaults to `0` -- `ReadTimeout` is how long a redis read-command can take. Defaults to `1s` -- `KeepAlivePeriod` is how long the redis connection is to be kept alive without anything flowing through it. Defaults to `5m` - `MaxIdle` is how many idle connections can be in the redis-pool at once. Defaults to 1 - `MaxActive` is how many connections the pool can keep. Defaults to 1 diff --git a/workhorse/internal/config/config.go b/workhorse/internal/config/config.go index 84849c72744..d019a7710bf 100644 --- a/workhorse/internal/config/config.go +++ b/workhorse/internal/config/config.go @@ -70,16 +70,13 @@ type AzureCredentials struct { } type RedisConfig struct { - URL TomlURL - Sentinel []TomlURL - SentinelMaster string - Password string - DB *int - ReadTimeout *TomlDuration - WriteTimeout *TomlDuration - KeepAlivePeriod *TomlDuration - MaxIdle *int - MaxActive *int + URL TomlURL + Sentinel []TomlURL + SentinelMaster string + Password string + DB *int + MaxIdle *int + MaxActive *int } type ImageResizerConfig struct { diff --git a/workhorse/internal/redis/redis.go b/workhorse/internal/redis/redis.go index 0029a2a9e2b..b11a8184bca 100644 --- a/workhorse/internal/redis/redis.go +++ b/workhorse/internal/redis/redis.go @@ -113,21 +113,9 @@ var poolDialFunc func() (redis.Conn, error) var workerDialFunc func() (redis.Conn, error) func timeoutDialOptions(cfg *config.RedisConfig) []redis.DialOption { - readTimeout := defaultReadTimeout - writeTimeout := defaultWriteTimeout - - if cfg != nil { - if cfg.ReadTimeout != nil { - readTimeout = cfg.ReadTimeout.Duration - } - - if cfg.WriteTimeout != nil { - writeTimeout = cfg.WriteTimeout.Duration - } - } return []redis.DialOption{ - redis.DialReadTimeout(readTimeout), - redis.DialWriteTimeout(writeTimeout), + redis.DialReadTimeout(defaultReadTimeout), + redis.DialWriteTimeout(defaultWriteTimeout), } } @@ -148,47 +136,45 @@ func dialOptionsBuilder(cfg *config.RedisConfig, setTimeouts bool) []redis.DialO return dopts } -func keepAliveDialer(timeout time.Duration) func(string, string) (net.Conn, error) { - return func(network, address string) (net.Conn, error) { - addr, err := net.ResolveTCPAddr(network, address) - if err != nil { - return nil, err - } - tc, err := net.DialTCP(network, nil, addr) - if err != nil { - return nil, err - } - if err := tc.SetKeepAlive(true); err != nil { - return nil, err - } - if err := tc.SetKeepAlivePeriod(timeout); err != nil { - return nil, err - } - return tc, nil +func keepAliveDialer(network, address string) (net.Conn, error) { + addr, err := net.ResolveTCPAddr(network, address) + if err != nil { + return nil, err } + tc, err := net.DialTCP(network, nil, addr) + if err != nil { + return nil, err + } + if err := tc.SetKeepAlive(true); err != nil { + return nil, err + } + if err := tc.SetKeepAlivePeriod(defaultKeepAlivePeriod); err != nil { + return nil, err + } + return tc, nil } type redisDialerFunc func() (redis.Conn, error) -func sentinelDialer(dopts []redis.DialOption, keepAlivePeriod time.Duration) redisDialerFunc { +func sentinelDialer(dopts []redis.DialOption) redisDialerFunc { return func() (redis.Conn, error) { address, err := sntnl.MasterAddr() if err != nil { errorCounter.WithLabelValues("master", "sentinel").Inc() return nil, err } - dopts = append(dopts, redis.DialNetDial(keepAliveDialer(keepAlivePeriod))) + dopts = append(dopts, redis.DialNetDial(keepAliveDialer)) return redisDial("tcp", address, dopts...) } } -func defaultDialer(dopts []redis.DialOption, keepAlivePeriod time.Duration, url url.URL) redisDialerFunc { +func defaultDialer(dopts []redis.DialOption, url url.URL) redisDialerFunc { return func() (redis.Conn, error) { if url.Scheme == "unix" { return redisDial(url.Scheme, url.Path, dopts...) } - dopts = append(dopts, redis.DialNetDial(keepAliveDialer(keepAlivePeriod))) + dopts = append(dopts, redis.DialNetDial(keepAliveDialer)) // redis.DialURL only works with redis[s]:// URLs if url.Scheme == "redis" || url.Scheme == "rediss" { @@ -231,15 +217,11 @@ func countDialer(dialer redisDialerFunc) redisDialerFunc { // DefaultDialFunc should always used. Only exception is for unit-tests. func DefaultDialFunc(cfg *config.RedisConfig, setReadTimeout bool) func() (redis.Conn, error) { - keepAlivePeriod := defaultKeepAlivePeriod - if cfg.KeepAlivePeriod != nil { - keepAlivePeriod = cfg.KeepAlivePeriod.Duration - } dopts := dialOptionsBuilder(cfg, setReadTimeout) if sntnl != nil { - return countDialer(sentinelDialer(dopts, keepAlivePeriod)) + return countDialer(sentinelDialer(dopts)) } - return countDialer(defaultDialer(dopts, keepAlivePeriod, cfg.URL.URL)) + return countDialer(defaultDialer(dopts, cfg.URL.URL)) } // Configure redis-connection diff --git a/workhorse/internal/redis/redis_test.go b/workhorse/internal/redis/redis_test.go index f4b4120517d..eee2f99bbbf 100644 --- a/workhorse/internal/redis/redis_test.go +++ b/workhorse/internal/redis/redis_test.go @@ -96,13 +96,11 @@ func TestConfigureMinimalConfig(t *testing.T) { func TestConfigureFullConfig(t *testing.T) { i, a := 4, 10 - r := config.TomlDuration{Duration: 3} cfg := &config.RedisConfig{ - URL: config.TomlURL{}, - Password: "", - MaxIdle: &i, - MaxActive: &a, - ReadTimeout: &r, + URL: config.TomlURL{}, + Password: "", + MaxIdle: &i, + MaxActive: &a, } Configure(cfg, DefaultDialFunc) @@ -219,11 +217,7 @@ func TestDialOptionsBuildersSetTimeouts(t *testing.T) { } func TestDialOptionsBuildersSetTimeoutsConfig(t *testing.T) { - cfg := &config.RedisConfig{ - ReadTimeout: &config.TomlDuration{Duration: time.Second * time.Duration(15)}, - WriteTimeout: &config.TomlDuration{Duration: time.Second * time.Duration(15)}, - } - dopts := dialOptionsBuilder(cfg, true) + dopts := dialOptionsBuilder(nil, true) require.Equal(t, 2, len(dopts)) } |