diff --git a/app/assets/javascripts/projects/terraform_notification/components/terraform_notification.vue b/app/assets/javascripts/projects/terraform_notification/components/terraform_notification.vue index 2290d6a078f..668cc10c454 100644 --- a/app/assets/javascripts/projects/terraform_notification/components/terraform_notification.vue +++ b/app/assets/javascripts/projects/terraform_notification/components/terraform_notification.vue @@ -1,9 +1,9 @@ diff --git a/app/assets/javascripts/projects/terraform_notification/index.js b/app/assets/javascripts/projects/terraform_notification/index.js index 0a273247930..362e71ed902 100644 --- a/app/assets/javascripts/projects/terraform_notification/index.js +++ b/app/assets/javascripts/projects/terraform_notification/index.js @@ -1,12 +1,18 @@ import Vue from 'vue'; -import { parseBoolean, getCookie } from '~/lib/utils/common_utils'; +import VueApollo from 'vue-apollo'; +import createDefaultClient from '~/lib/graphql'; import TerraformNotification from './components/terraform_notification.vue'; +Vue.use(VueApollo); + +const apolloProvider = new VueApollo({ + defaultClient: createDefaultClient(), +}); + export default () => { const el = document.querySelector('.js-terraform-notification'); - const bannerDismissedKey = 'terraform_notification_dismissed'; - if (!el || parseBoolean(getCookie(bannerDismissedKey))) { + if (!el) { return false; } @@ -14,9 +20,9 @@ export default () => { return new Vue({ el, + apolloProvider, provide: { terraformImagePath, - bannerDismissedKey, }, render: (createElement) => createElement(TerraformNotification), }); diff --git a/app/assets/javascripts/vue_merge_request_widget/index.js b/app/assets/javascripts/vue_merge_request_widget/index.js index b10e0e2bc88..f5dbcec7dbe 100644 --- a/app/assets/javascripts/vue_merge_request_widget/index.js +++ b/app/assets/javascripts/vue_merge_request_widget/index.js @@ -6,6 +6,7 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; import MrWidgetOptions from 'ee_else_ce/vue_merge_request_widget/mr_widget_options.vue'; import createDefaultClient from '~/lib/graphql'; +import { parseBoolean } from '~/lib/utils/common_utils'; import Translate from '../vue_shared/translate'; Vue.use(Translate); @@ -31,6 +32,8 @@ export default () => { provide: { artifactsEndpoint: gl.mrWidgetData.artifacts_endpoint, artifactsEndpointPlaceholder: gl.mrWidgetData.artifacts_endpoint_placeholder, + falsePositiveDocUrl: gl.mrWidgetData.false_positive_doc_url, + canViewFalsePositive: parseBoolean(gl.mrWidgetData.can_view_false_positive), }, ...MrWidgetOptions, apolloProvider, diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index 2fff0a69a26..feac7bbc363 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -148,6 +148,7 @@ module Clusters scope :with_management_project, -> { where.not(management_project: nil) } scope :for_project_namespace, -> (namespace_id) { joins(:projects).where(projects: { namespace_id: namespace_id }) } + scope :with_name, -> (name) { where(name: name) } # with_application_prometheus scope is deprecated, and scheduled for removal # in %14.0. See https://gitlab.com/groups/gitlab-org/-/epics/4280 diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb index 7f5f87e3e36..7ec614b048c 100644 --- a/app/models/clusters/platforms/kubernetes.rb +++ b/app/models/clusters/platforms/kubernetes.rb @@ -137,6 +137,14 @@ module Clusters kubeclient.patch_ingress(ingress.name, data, namespace) end + def kubeconfig(namespace) + to_kubeconfig( + url: api_url, + namespace: namespace, + token: token, + ca_pem: ca_pem) + end + private def default_namespace(project, environment_name:) @@ -154,14 +162,6 @@ module Clusters ).execute end - def kubeconfig(namespace) - to_kubeconfig( - url: api_url, - namespace: namespace, - token: token, - ca_pem: ca_pem) - end - def read_pods(namespace) kubeclient.get_pods(namespace: namespace).as_json rescue Kubeclient::ResourceNotFoundError diff --git a/app/views/projects/merge_requests/_widget.html.haml b/app/views/projects/merge_requests/_widget.html.haml index 5f2cb1cfcc4..47a0d05fc65 100644 --- a/app/views/projects/merge_requests/_widget.html.haml +++ b/app/views/projects/merge_requests/_widget.html.haml @@ -18,5 +18,7 @@ window.gl.mrWidgetData.approvals_help_path = '#{help_page_path("user/project/merge_requests/merge_request_approvals")}'; window.gl.mrWidgetData.pipelines_empty_svg_path = '#{image_path('illustrations/pipelines_empty.svg')}'; window.gl.mrWidgetData.codequality_help_path = '#{help_page_path("user/project/merge_requests/code_quality", anchor: "code-quality-reports")}'; + window.gl.mrWidgetData.false_positive_doc_url = '#{help_page_path('user/application_security/vulnerabilities/index')}'; + window.gl.mrWidgetData.can_view_false_positive = '#{(Feature.enabled?(:vulnerability_flags, default_enabled: :yaml) && @merge_request.project.licensed_feature_available?(:sast_fp_reduction)).to_s}'; #js-vue-mr-widget.mr-widget diff --git a/config/feature_flags/development/vulnerability_flags.yml b/config/feature_flags/development/vulnerability_flags.yml new file mode 100644 index 00000000000..8e78dc0f611 --- /dev/null +++ b/config/feature_flags/development/vulnerability_flags.yml @@ -0,0 +1,8 @@ +--- +name: vulnerability_flags +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/66775 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/340203 +milestone: '14.2' +type: development +group: group::static analysis +default_enabled: false diff --git a/config/initializers/action_cable.rb b/config/initializers/action_cable.rb index 5530e7d64a2..16d29f5910f 100644 --- a/config/initializers/action_cable.rb +++ b/config/initializers/action_cable.rb @@ -17,3 +17,6 @@ ActionCable::SubscriptionAdapter::Redis.redis_connector = lambda do |config| ::Redis.new(args) end + +Gitlab::ActionCable::RequestStoreCallbacks.install +Gitlab::Database::LoadBalancing::ActionCableCallbacks.install if Gitlab::Database::LoadBalancing.enable? diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 161c1d3cd92..7deadc6b2e6 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -12088,7 +12088,7 @@ Represents vulnerability finding of a security report on the pipeline. | ---- | ---- | ----------- | | `confidence` | [`String`](#string) | Type of the security report that found the vulnerability. | | `description` | [`String`](#string) | Description of the vulnerability finding. | -| `falsePositive` | [`Boolean`](#boolean) | Indicates whether the vulnerability is a false positive. Available only when feature flag `vulnerability_flags` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice. | +| `falsePositive` | [`Boolean`](#boolean) | Indicates whether the vulnerability is a false positive. | | `identifiers` | [`[VulnerabilityIdentifier!]!`](#vulnerabilityidentifier) | Identifiers of the vulnerabilit finding. | | `location` | [`VulnerabilityLocation`](#vulnerabilitylocation) | Location metadata for the vulnerability. Its fields depend on the type of security scan that found the vulnerability. | | `name` | [`String`](#string) | Name of the vulnerability finding. | @@ -14452,7 +14452,7 @@ Represents a vulnerability. | `dismissedAt` | [`Time`](#time) | Timestamp of when the vulnerability state was changed to dismissed. | | `dismissedBy` | [`UserCore`](#usercore) | User that dismissed the vulnerability. | | `externalIssueLinks` | [`VulnerabilityExternalIssueLinkConnection!`](#vulnerabilityexternalissuelinkconnection) | List of external issue links related to the vulnerability. (see [Connections](#connections)) | -| `falsePositive` | [`Boolean`](#boolean) | Indicates whether the vulnerability is a false positive. Available only when feature flag `vulnerability_flags` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice. | +| `falsePositive` | [`Boolean`](#boolean) | Indicates whether the vulnerability is a false positive. | | `hasSolutions` | [`Boolean`](#boolean) | Indicates whether there is a solution available for this vulnerability. | | `id` | [`ID!`](#id) | GraphQL ID of the vulnerability. | | `identifiers` | [`[VulnerabilityIdentifier!]!`](#vulnerabilityidentifier) | Identifiers of the vulnerability. | diff --git a/doc/api/labels.md b/doc/api/labels.md index 1606df03afb..a8cb56f1573 100644 --- a/doc/api/labels.md +++ b/doc/api/labels.md @@ -9,7 +9,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w Interact with [labels](../user/project/labels.md) using the REST API. NOTE: -The `description_html` - was added to response JSON in [GitLab 12.7](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/21413). +The `description_html` - was [added](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/21413) to response JSON in GitLab 12.7. ## List labels @@ -24,7 +24,7 @@ GET /projects/:id/labels | Attribute | Type | Required | Description | | --------- | ------- | -------- | --------------------- | | `id` | integer/string | yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user | -| `with_counts` | boolean | no | Whether or not to include issue and merge request counts. Defaults to `false`. _([Introduced in GitLab 12.2](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/31543))_ | +| `with_counts` | boolean | no | Whether or not to include issue and merge request counts. Defaults to `false`. _([Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/31543) in GitLab 12.2)_ | | `include_ancestor_groups` | boolean | no | Include ancestor groups. Defaults to `true`. | | `search` | string | no | Keyword to filter labels by. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/259024) in GitLab 13.6 | diff --git a/doc/ci/pipelines/parent_child_pipelines.md b/doc/ci/pipelines/parent_child_pipelines.md index 080227c6fc6..71f778d81b3 100644 --- a/doc/ci/pipelines/parent_child_pipelines.md +++ b/doc/ci/pipelines/parent_child_pipelines.md @@ -61,7 +61,8 @@ microservice_a: include: path/to/microservice_a.yml ``` -You can include multiple files when composing a child pipeline: +You can include multiple files when defining a child pipeline. The child pipeline's +configuration is composed of all configuration files merged together: ```yaml microservice_a: diff --git a/doc/ci/runners/build_cloud/macos/setup.md b/doc/ci/runners/build_cloud/macos/setup.md deleted file mode 100644 index 87fcd207aa0..00000000000 --- a/doc/ci/runners/build_cloud/macos/setup.md +++ /dev/null @@ -1,178 +0,0 @@ ---- -stage: Verify -group: Runner -info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments ---- - -# Set up macOS runners - -To run a CI/CD job on a macOS runner, complete the following steps in order. - -When you're done, GitLab Runner will be running on your macOS machine -and an individual runner will be ready to process jobs. - -- Change the system shell to Bash. -- Install Homebrew, rbenv, and GitLab Runner. -- Configure rbenv and install Ruby. -- Install Xcode. -- Register a runner. -- Configure CI/CD. - -## Prerequisites - -Before you begin: - -- Install a recent version of macOS. This guide was developed on 11.4. -- Ensure you have terminal or SSH access to the machine. - -## Change the system shell to Bash - -Newer versions of macOS ship with Zsh as the default shell. -You must change it to Bash. - -1. Connect to your machine and determine the default shell: - - ```shell - echo $shell - ``` - -1. If the result is not `/bin/bash`, change the shell by running: - - ```shell - chsh -s /bin/bash - ``` - -1. Enter your password. -1. Restart your terminal or reconnect by using SSH. -1. Run `echo $SHELL` again. The result should be `/bin/bash`. - -## Install Homebrew, rbenv, and GitLab Runner - -The runner needs certain environment options to connect to the machine and run a job. - -1. Install the [Homebrew package manager](https://brew.sh/): - - ```shell - /bin/bash -c "$(curl "https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh")" - ``` - -1. Set up [`rbenv`](https://github.com/rbenv/rbenv), which is a Ruby version manager, and GitLab Runner: - - ```shell - brew install rbenv gitlab-runner - brew services start gitlab-runner - ``` - -## Configure rbenv and install Ruby - -Now configure rbenv and install Ruby. - -1. Add rbenv to the Bash environment: - - ```shell - echo 'if which rbenv > /dev/null; then eval "$(rbenv init -)"; fi' >> ~/.bash_profile - source ~/.bash_profile - ``` - -1. Install Ruby 2.74 and set it as the machine's global default: - - ```shell - rbenv install 2.7.4 - rbenv global 2.7.4 - ``` - -## Install Xcode - -Now install and configure Xcode. - -1. Go to one of these locations and install Xcode: - - - The Apple App Store. - - The [Apple Developer Portal](https://developer.apple.com/download/all/?q=xcode). - - [`xcode-install`](https://github.com/xcpretty/xcode-install). This project aims to make it easier to download various - Apple dependencies from the command line. - -1. Agree to the license and install the recommended additional components. - You can do this by opening Xcode and following the prompts, or by running the following command in the terminal: - - ```shell - sudo xcodebuild -runFirstLaunch - ``` - -1. Update the active developer directory so that Xcode loads the proper command line tools during your build: - - ```shell - sudo xcode-select -s /Applications/Xcode.app/Contents/Developer - ``` - -### Register a runner - -Now register a runner to start picking up your CI/CD jobs. - -1. In GitLab, on the top bar, select **Menu > Projects** or **Menu > Group** to find your project or group. -1. On the left sidebar, select **Settings > CI/CD**. -1. Expand **Runners**. -1. Note the URL and registration token. -1. In a terminal, start the interactive setup: - - ```shell - gitlab-runner register - ``` - -1. Enter the GitLab URL. -1. Enter the registration token. -1. Enter a description for the runner. - You will use the description to identify the runner in GitLab, and the name is associated with jobs executed on this instance. - -1. Enter tags, which direct specific jobs to specific instances. You will use these tags later to ensure macOS jobs - run on this macOS machine. In this example, enter: - - ```shell - macos - ``` - -1. Type `shell` to select the shell [executor](https://docs.gitlab.com/runner/executors/). - -A success message is displayed: - -```shell -> Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded! -``` - -To view the runner, go to **Settings > CI/CD** and expand **Runners**. - -### Configure CI/CD - -In your GitLab project, configure CI/CD and start a build. You can use this sample `.gitlab-ci.yml` file. -Notice the tags match the tags you used to register the runner. - -```yaml -stages: - - build - - test - -variables: - LANG: "en_US.UTF-8" - -before_script: - - gem install bundler - - bundle install - - gem install cocoapods - - pod install - -build: - stage: build - script: - - bundle exec fastlane build - tags: - - macos - -test: - stage: test - script: - - bundle exec fastlane test - tags: - - macos -``` - -The macOS runner should now build your project. diff --git a/doc/development/documentation/styleguide/word_list.md b/doc/development/documentation/styleguide/word_list.md index 6df221fe609..40a2fb480d1 100644 --- a/doc/development/documentation/styleguide/word_list.md +++ b/doc/development/documentation/styleguide/word_list.md @@ -209,7 +209,7 @@ Try to avoid. Be as specific as you can. Do not use **and so on** as a replaceme ## expand -Use instead of **open** when you are talking about expanding or collapsing a section in the UI. +Use **expand** instead of **open** when you are talking about expanding or collapsing a section in the UI. ## field diff --git a/doc/install/requirements.md b/doc/install/requirements.md index e8ea456e022..c5b120aa69e 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -256,6 +256,12 @@ of [legacy Rugged code](../administration/gitaly/index.md#direct-access-to-git-i higher, due to how [Ruby MRI multi-threading](https://en.wikipedia.org/wiki/Global_interpreter_lock) works. +### Puma per worker maximum memory + +By default, each Puma worker will be limited to 1024 MB of memory. +This setting [can be adjusted](../administration/operations/puma.md#puma-worker-killer) and should be considered +if you need to increase the number of Puma workers. + ## Redis and Sidekiq Redis stores all user sessions and the background task queue. diff --git a/doc/user/application_security/policies/index.md b/doc/user/application_security/policies/index.md index 6f0167c64d2..07494c91bb2 100644 --- a/doc/user/application_security/policies/index.md +++ b/doc/user/application_security/policies/index.md @@ -287,6 +287,16 @@ This rule enforces the defined actions and schedules a scan on the provided date | `type` | `string` | `schedule` | The rule's type. | | `branches` | `array` of `string` | `*` or the branch's name | The branch the given policy applies to (supports wildcard). | | `cadence` | `string` | CRON expression (for example, `0 0 * * *`) | A whitespace-separated string containing five fields that represents the scheduled time. | +| `clusters` | `object` | | The cluster where the given policy will enforce running selected scans (only for `container_scanning`/`cluster_image_scanning` scans). The key of the object is the name of the Kubernetes cluster configured for your project in GitLab. In the optionally provided value of the object, you can precisely select Kubernetes resources that will be scanned. | + +#### `cluster` schema + +| Field | Type | Possible values | Description | +|--------------|---------------------|--------------------------|-------------| +| `containers` | `array` of `string` | | The container name that will be scanned (only the first value is currently supported). | +| `resources` | `array` of `string` | | The resource name that will be scanned (only the first value is currently supported). | +| `namespaces` | `array` of `string` | | The namespace that will be scanned (only the first value is currently supported). | +| `kinds` | `array` of `string` | `deployment`/`daemonset` | The resource kind that should be scanned (only the first value is currently supported). | ### `scan` action type @@ -315,6 +325,9 @@ Note the following: - A secret detection scan runs in `normal` mode when executed as part of a pipeline, and in [`historic`](../secret_detection/index.md#full-history-secret-scan) mode when executed as part of a scheduled scan. +- A container scanning and cluster image scanning scans configured for the `pipeline` rule type will ignore the cluster defined in the `clusters` object. + They will use predefined CI/CD variables defined for your project. Cluster selection with the `clusters` object is supported for the `schedule` rule type. + Cluster with name provided in `clusters` object must be created and configured for the project. To be able to successfully perform the `container_scanning`/`cluster_image_scanning` scans for the cluster you must follow instructions for the [Cluster Image Scanning feature](../cluster_image_scanning/index.md#prerequisites). Here's an example: @@ -345,8 +358,8 @@ scan_execution_policy: scanner_profile: Scanner Profile C site_profile: Site Profile D - scan: secret_detection -- name: Enforce Secret Detection in every default branch pipeline - description: This policy enforces pipeline configuration to have a job with Secret Detection scan for the default branch +- name: Enforce Secret Detection and Container Scanning in every default branch pipeline + description: This policy enforces pipeline configuration to have a job with Secret Detection and Container Scanning scans for the default branch enabled: true rules: - type: pipeline @@ -354,7 +367,25 @@ scan_execution_policy: - main actions: - scan: secret_detection -``` + - scan: container_scanning +- name: Enforce Cluster Image Scanning on production-cluster every 24h + description: This policy enforces Cluster Image Scanning scan to run every 24 hours + enabled: true + rules: + - type: schedule + cadence: '15 3 * * *' + clusters: + production-cluster: + containers: + - database + resources: + - production-application + namespaces: + - production-namespace + kinds: + - deployment + actions: + - scan: cluster_image_scanning In this example: @@ -362,7 +393,9 @@ In this example: `release/v1.2.1`), DAST scans run with `Scanner Profile A` and `Site Profile B`. - DAST and secret detection scans run every 10 minutes. The DAST scan runs with `Scanner Profile C` and `Site Profile D`. -- Secret detection scans run for every pipeline executed on the `main` branch. +- Secret detection and container scanning scans run for every pipeline executed on the `main` branch. +- Cluster Image Scanning scan runs every 24h. The scan runs on the `production-cluster` cluster and fetches vulnerabilities + from the container with the name `database` configured for deployment with the name `production-application` in the `production-namepsace` namespace. ## Roadmap diff --git a/lib/gitlab/action_cable/request_store_callbacks.rb b/lib/gitlab/action_cable/request_store_callbacks.rb new file mode 100644 index 00000000000..a9f30b0fc10 --- /dev/null +++ b/lib/gitlab/action_cable/request_store_callbacks.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Gitlab + module ActionCable + module RequestStoreCallbacks + def self.install + ::ActionCable::Server::Worker.set_callback :work, :around, &wrapper + ::ActionCable::Channel::Base.set_callback :subscribe, :around, &wrapper + ::ActionCable::Channel::Base.set_callback :unsubscribe, :around, &wrapper + end + + def self.wrapper + lambda do |_, inner| + ::Gitlab::WithRequestStore.with_request_store do + inner.call + end + end + end + end + end +end diff --git a/lib/gitlab/database/load_balancing/action_cable_callbacks.rb b/lib/gitlab/database/load_balancing/action_cable_callbacks.rb new file mode 100644 index 00000000000..4feba989a0a --- /dev/null +++ b/lib/gitlab/database/load_balancing/action_cable_callbacks.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module Gitlab + module Database + module LoadBalancing + module ActionCableCallbacks + def self.install + ::ActionCable::Server::Worker.set_callback :work, :around, &wrapper + ::ActionCable::Channel::Base.set_callback :subscribe, :around, &wrapper + ::ActionCable::Channel::Base.set_callback :unsubscribe, :around, &wrapper + end + + def self.wrapper + lambda do |_, inner| + ::Gitlab::Database::LoadBalancing::Session.current.use_primary! + + inner.call + ensure + ::Gitlab::Database::LoadBalancing.proxy.load_balancer.release_host + ::Gitlab::Database::LoadBalancing::Session.clear_session + end + end + end + end + end +end diff --git a/lib/gitlab/database/load_balancing/configuration.rb b/lib/gitlab/database/load_balancing/configuration.rb index 6bd94bad5cf..c874825c98a 100644 --- a/lib/gitlab/database/load_balancing/configuration.rb +++ b/lib/gitlab/database/load_balancing/configuration.rb @@ -11,7 +11,7 @@ module Gitlab # Creates a configuration object for the given ActiveRecord model. def self.for_model(model) - cfg = model.connection_db_config.configuration_hash + cfg = model.connection_db_config.configuration_hash.deep_symbolize_keys lb_cfg = cfg[:load_balancing] || {} config = new(model) @@ -35,7 +35,7 @@ module Gitlab config.hosts = hosts end - discover = (lb_cfg[:discover] || {}).symbolize_keys + discover = lb_cfg[:discover] || {} # We iterate over the known/default keys so we don't end up with # random keys in our configuration hash. diff --git a/locale/gitlab.pot b/locale/gitlab.pot index f4944017380..765f7e67a79 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -37377,6 +37377,9 @@ msgstr "" msgid "Vulnerability|Evidence" msgstr "" +msgid "Vulnerability|False positive detected" +msgstr "" + msgid "Vulnerability|File" msgstr "" @@ -37419,6 +37422,9 @@ msgstr "" msgid "Vulnerability|Status" msgstr "" +msgid "Vulnerability|The scanner determined this vulnerability to be a false positive. Verify the evaluation before changing its status. %{linkStart}Learn more about false positive detection.%{linkEnd}" +msgstr "" + msgid "Vulnerability|The unmodified response is the original response that had no mutations done to the request" msgstr "" diff --git a/package.json b/package.json index f5f1ffbb881..b113ef88117 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "@gitlab/favicon-overlay": "2.0.0", "@gitlab/svgs": "1.211.0", "@gitlab/tributejs": "1.0.0", - "@gitlab/ui": "32.9.1", + "@gitlab/ui": "32.10.0", "@gitlab/visual-review-tools": "1.6.1", "@rails/actioncable": "6.1.3-2", "@rails/ujs": "6.1.3-2", diff --git a/spec/frontend/projects/terraform_notification/terraform_notification_spec.js b/spec/frontend/projects/terraform_notification/terraform_notification_spec.js index 630d0ffae54..6576ce70d60 100644 --- a/spec/frontend/projects/terraform_notification/terraform_notification_spec.js +++ b/spec/frontend/projects/terraform_notification/terraform_notification_spec.js @@ -1,7 +1,7 @@ import { GlBanner } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; -import { mockTracking, unmockTracking } from 'helpers/tracking_helper'; -import { setCookie, parseBoolean } from '~/lib/utils/common_utils'; +import { makeMockUserCalloutDismisser } from 'helpers/mock_user_callout_dismisser'; +import { mockTracking } from 'helpers/tracking_helper'; import TerraformNotification from '~/projects/terraform_notification/components/terraform_notification.vue'; import { EVENT_LABEL, @@ -9,64 +9,77 @@ import { CLICK_EVENT, } from '~/projects/terraform_notification/constants'; -jest.mock('~/lib/utils/common_utils'); - const terraformImagePath = '/path/to/image'; -const bannerDismissedKey = 'terraform_notification_dismissed'; describe('TerraformNotificationBanner', () => { let wrapper; let trackingSpy; + let userCalloutDismissSpy; const provideData = { terraformImagePath, - bannerDismissedKey, }; const findBanner = () => wrapper.findComponent(GlBanner); - beforeEach(() => { + const createComponent = ({ shouldShowCallout = true } = {}) => { + userCalloutDismissSpy = jest.fn(); + wrapper = shallowMount(TerraformNotification, { provide: provideData, - stubs: { GlBanner }, + stubs: { + GlBanner, + UserCalloutDismisser: makeMockUserCalloutDismisser({ + dismiss: userCalloutDismissSpy, + shouldShowCallout, + }), + }, }); + }; + + beforeEach(() => { + createComponent(); trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); }); afterEach(() => { wrapper.destroy(); - parseBoolean.mockReturnValue(false); - unmockTracking(); }); - describe('when the dismiss cookie is not set', () => { + describe('when user has already dismissed the banner', () => { + beforeEach(() => { + createComponent({ + shouldShowCallout: false, + }); + }); + it('should not render the banner', () => { + expect(findBanner().exists()).toBe(false); + }); + }); + + describe("when user hasn't yet dismissed the banner", () => { it('should render the banner', () => { expect(findBanner().exists()).toBe(true); }); }); describe('when close button is clicked', () => { - beforeEach(async () => { - await findBanner().vm.$emit('close'); + beforeEach(() => { + wrapper.vm.$refs.calloutDismisser.dismiss = userCalloutDismissSpy; + findBanner().vm.$emit('close'); }); - - it('should set the cookie with the bannerDismissedKey', () => { - expect(setCookie).toHaveBeenCalledWith(bannerDismissedKey, true); - }); - it('should send the dismiss event', () => { expect(trackingSpy).toHaveBeenCalledWith(undefined, DISMISS_EVENT, { label: EVENT_LABEL, }); }); - - it('should remove the banner', () => { - expect(findBanner().exists()).toBe(false); + it('should call the dismiss callback', () => { + expect(userCalloutDismissSpy).toHaveBeenCalledTimes(1); }); }); describe('when docs link is clicked', () => { - beforeEach(async () => { - await findBanner().vm.$emit('primary'); + beforeEach(() => { + findBanner().vm.$emit('primary'); }); it('should send button click event', () => { diff --git a/spec/lib/gitlab/action_cable/request_store_callbacks_spec.rb b/spec/lib/gitlab/action_cable/request_store_callbacks_spec.rb new file mode 100644 index 00000000000..e25b51c6513 --- /dev/null +++ b/spec/lib/gitlab/action_cable/request_store_callbacks_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +RSpec.describe Gitlab::ActionCable::RequestStoreCallbacks do + describe '.wrapper' do + it 'enables RequestStore in the inner block' do + expect(RequestStore.active?).to eq(false) + + described_class.wrapper.call( + nil, + lambda do + expect(RequestStore.active?).to eq(true) + end + ) + + expect(RequestStore.active?).to eq(false) + end + end +end diff --git a/spec/lib/gitlab/database/load_balancing/action_cable_callbacks_spec.rb b/spec/lib/gitlab/database/load_balancing/action_cable_callbacks_spec.rb new file mode 100644 index 00000000000..89ad0017753 --- /dev/null +++ b/spec/lib/gitlab/database/load_balancing/action_cable_callbacks_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +RSpec.describe Gitlab::Database::LoadBalancing::ActionCableCallbacks, :request_store do + describe '.wrapper' do + it 'uses primary and then releases the connection and clears the session' do + expect(Gitlab::Database::LoadBalancing).to receive_message_chain(:proxy, :load_balancer, :release_host) + expect(Gitlab::Database::LoadBalancing::Session).to receive(:clear_session) + + described_class.wrapper.call( + nil, + lambda do + expect(Gitlab::Database::LoadBalancing::Session.current.use_primary?).to eq(true) + end + ) + end + + context 'with an exception' do + it 'releases the connection and clears the session' do + expect(Gitlab::Database::LoadBalancing).to receive_message_chain(:proxy, :load_balancer, :release_host) + expect(Gitlab::Database::LoadBalancing::Session).to receive(:clear_session) + + expect do + described_class.wrapper.call(nil, lambda { raise 'test_exception' }) + end.to raise_error('test_exception') + end + end + end +end diff --git a/spec/lib/gitlab/database/load_balancing/configuration_spec.rb b/spec/lib/gitlab/database/load_balancing/configuration_spec.rb index d4843d873bb..4102c96d509 100644 --- a/spec/lib/gitlab/database/load_balancing/configuration_spec.rb +++ b/spec/lib/gitlab/database/load_balancing/configuration_spec.rb @@ -69,6 +69,42 @@ RSpec.describe Gitlab::Database::LoadBalancing::Configuration do expect(config.pool_size).to eq(4) end end + + context 'when the load balancing configuration uses strings as the keys' do + let(:configuration_hash) do + { + pool: 4, + load_balancing: { + 'max_replication_difference' => 1, + 'max_replication_lag_time' => 2, + 'replica_check_interval' => 3, + 'hosts' => %w[foo bar], + 'discover' => { + 'record' => 'foo.example.com' + } + } + } + end + + it 'uses the custom configuration settings' do + config = described_class.for_model(model) + + expect(config.hosts).to eq(%w[foo bar]) + expect(config.max_replication_difference).to eq(1) + expect(config.max_replication_lag_time).to eq(2.0) + expect(config.replica_check_interval).to eq(3.0) + expect(config.service_discovery).to eq( + nameserver: 'localhost', + port: 8600, + record: 'foo.example.com', + record_type: 'A', + interval: 60, + disconnect_timeout: 120, + use_tcp: false + ) + expect(config.pool_size).to eq(4) + end + end end describe '#load_balancing_enabled?' do diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb index 278e200b05c..4dc47a7efc1 100644 --- a/spec/models/clusters/cluster_spec.rb +++ b/spec/models/clusters/cluster_spec.rb @@ -268,6 +268,16 @@ RSpec.describe Clusters::Cluster, :use_clean_rails_memory_store_caching do it { is_expected.to contain_exactly(cluster) } end + describe '.with_name' do + subject { described_class.with_name(name) } + + let(:name) { 'this-cluster' } + let!(:cluster) { create(:cluster, :project, name: name) } + let!(:another_cluster) { create(:cluster, :project) } + + it { is_expected.to contain_exactly(cluster) } + end + describe 'validations' do subject { cluster.valid? } diff --git a/yarn.lock b/yarn.lock index 19f3c5aeee1..009c3cfdadb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -974,10 +974,10 @@ resolved "https://registry.yarnpkg.com/@gitlab/tributejs/-/tributejs-1.0.0.tgz#672befa222aeffc83e7d799b0500a7a4418e59b8" integrity sha512-nmKw1+hB6MHvlmPz63yPwVs1qQkycHwsKgxpEbzmky16Y6mL4EJMk3w1b8QlOAF/AIAzjCERPhe/R4MJiohbZw== -"@gitlab/ui@32.9.1": - version "32.9.1" - resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-32.9.1.tgz#a02a21c772cd1b9bb7f929764d587082724f8d4c" - integrity sha512-Cx9BGC5KJ1KPphRC+J90GPxDATTf3s1y01VMgXngXPB2ZJMl75aue3swBOsAmgre91NL4RUqiG+TJMmMCs3xuA== +"@gitlab/ui@32.10.0": + version "32.10.0" + resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-32.10.0.tgz#7c6ff39d2dc0b770237289dedf2eee46a618021b" + integrity sha512-Kr4sDomMmxOr/PgQsfJ45HCHdTjbb9UISYxwtugAGKJKha/zHmpSEtZFu0R1cLhnVUqS0Y+FSgkq2nAw3NuxIA== dependencies: "@babel/standalone" "^7.0.0" bootstrap-vue "2.18.1"