Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-10-09 03:14:03 +00:00
parent 63fde89de0
commit e653e9a1c3
44 changed files with 795 additions and 471 deletions

View File

@ -1 +1 @@
02a0a2dc23f5e9503ad06ce58c6b0f22a60487fc
0105df4bc690f9a56806eb72ebec59b1c5396d6d

View File

@ -1 +1 @@
188b1e18a471cef37ddd81fdf0c643c0e1c235da
6a50d274511d46d6baa32c9ef7654b6432e3e611

View File

@ -1,160 +0,0 @@
<script>
import {
GlIcon,
GlDaterangePicker,
GlFilteredSearchToken,
GlFilteredSearchSuggestion,
GlOutsideDirective,
} from '@gitlab/ui';
import { __ } from '~/locale';
import { formatDate } from '~/lib/utils/datetime/date_format_utility';
const CUSTOM_DATE_FILTER_TYPE = 'custom-date';
export default {
directives: { Outside: GlOutsideDirective },
components: {
GlIcon,
GlDaterangePicker,
GlFilteredSearchToken,
GlFilteredSearchSuggestion,
},
props: {
active: {
type: Boolean,
required: true,
},
value: {
type: Object,
required: true,
},
config: {
type: Object,
required: true,
},
},
data() {
return {
datePickerShown: false,
};
},
computed: {
isActive() {
return this.datePickerShown || this.active;
},
computedValue() {
if (this.datePickerShown) {
return {
...this.value,
data: '',
};
}
return this.value;
},
dataSegmentInputAttributes() {
const id = 'time_range_data_segment_input';
if (this.datePickerShown) {
return {
id,
placeholder: 'YYYY-MM-DD - YYYY-MM-DD', // eslint-disable-line @gitlab/require-i18n-strings
style: 'padding-left: 23px;',
};
}
return {
id,
};
},
computedConfig() {
return {
...this.config,
options: undefined, // remove options from config to avoid default options being rendered
};
},
suggestions() {
const suggestions = this.config.options.map((option) => ({
value: option.value,
text: option.title,
}));
suggestions.push({ value: CUSTOM_DATE_FILTER_TYPE, text: __('Custom') });
return suggestions;
},
defaultStartDate() {
return new Date();
},
},
methods: {
hideDatePicker() {
this.datePickerShown = false;
},
showDatePicker() {
this.datePickerShown = true;
},
handleClickOutside() {
this.hideDatePicker();
},
handleComplete(value) {
if (value === CUSTOM_DATE_FILTER_TYPE) {
this.showDatePicker();
}
},
selectValue(inputValue, submitValue) {
let value = inputValue;
if (typeof inputValue === 'object' && inputValue.startDate && inputValue.endDate) {
const { startDate, endDate } = inputValue;
const format = 'yyyy-mm-dd';
value = `${formatDate(startDate, format)} - ${formatDate(endDate, format)}`;
}
submitValue(value);
this.hideDatePicker();
},
},
CUSTOM_DATE_FILTER_TYPE: 'custom-date',
};
</script>
<template>
<gl-filtered-search-token
:data-segment-input-attributes="dataSegmentInputAttributes"
v-bind="{ ...$props, ...$attrs }"
:view-only="datePickerShown"
:active="isActive"
:value="computedValue"
:config="computedConfig"
v-on="$listeners"
@complete="handleComplete"
>
<template #before-data-segment-input="{ submitValue }">
<gl-icon
v-if="datePickerShown"
class="gl-text-gray-500"
name="calendar"
style="margin-left: 5px; margin-right: -15px; z-index: 1; pointer-events: none"
/>
<div
v-if="datePickerShown"
v-outside="handleClickOutside"
class="gl-absolute gl-top-full gl-z-1 gl-my-2 gl-rounded-base gl-border-1 gl-border-gray-200 gl-bg-white gl-p-4 gl-shadow-x0-y2-b4-s0"
>
<gl-daterange-picker
:max-date-range="computedConfig.maxDateRange"
start-opened
:default-start-date="defaultStartDate"
:default-max-date="defaultStartDate"
@input="selectValue($event, submitValue)"
/>
</div>
</template>
<template #suggestions>
<div v-if="!datePickerShown">
<gl-filtered-search-suggestion
v-for="token in suggestions"
:key="token.value"
:value="token.value"
>
{{ token.text }}
</gl-filtered-search-suggestion>
</div>
</template>
</gl-filtered-search-token>
</template>

View File

@ -39,10 +39,14 @@ module WikiPages
# This method throws an error if internal_event_name returns an unknown event name
def increment_usage(page)
track_event(page, internal_event_name)
end
def track_event(page, event_name)
label = 'template' if page.template?
Gitlab::InternalEvents.track_event(
internal_event_name,
event_name,
user: current_user,
project: project,
namespace: group,

View File

@ -32,3 +32,5 @@ module WikiPages
end
end
end
WikiPages::CreateService.prepend_mod_with('WikiPages::CreateService')

View File

@ -29,3 +29,5 @@ module WikiPages
end
end
end
WikiPages::DestroyService.prepend_mod_with('WikiPages::DestroyService')

View File

@ -41,3 +41,5 @@ module WikiPages
end
end
end
WikiPages::UpdateService.prepend_mod_with('WikiPages::UpdateService')

View File

@ -10,6 +10,7 @@ module BulkImports
data_consistency :always # rubocop:disable SidekiqLoadBalancing/WorkerDataConsistency
feature_category :importers
sidekiq_options dead: false, retry: 6
sidekiq_options max_retries_after_interruption: 20
worker_has_external_dependencies!
worker_resource_boundary :memory
idempotent!

View File

@ -15,6 +15,7 @@ module BulkImports
data_consistency :always
feature_category :importers
sidekiq_options dead: false, retry: 6
sidekiq_options max_retries_after_interruption: 20
worker_has_external_dependencies!
deduplicate :until_executing
worker_resource_boundary :memory

View File

@ -9,6 +9,7 @@ module Import
data_consistency :delayed
feature_category :importers
sidekiq_options retry: 6, dead: false
sidekiq_options max_retries_after_interruption: 20
worker_has_external_dependencies!
PERFORM_DELAY = 1.minute

View File

@ -0,0 +1,20 @@
---
description: Wiki page in a group wiki is created
internal_events: true
action: create_group_wiki_page
identifiers:
- namespace
- user
additional_properties:
label:
description: "Is 'template' when the page is a template"
property:
description: "Name of the markup language used by the page"
product_group: knowledge
milestone: '17.5'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/167091
distributions:
- ee
tiers:
- premium
- ultimate

View File

@ -0,0 +1,20 @@
---
description: Wiki page in a group wiki is deleted
internal_events: true
action: delete_group_wiki_page
identifiers:
- namespace
- user
additional_properties:
label:
description: "Is 'template' when the page is a template"
property:
description: "Name of the markup language used by the page"
product_group: knowledge
milestone: '17.5'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/167091
distributions:
- ee
tiers:
- premium
- ultimate

View File

@ -0,0 +1,20 @@
---
description: Wiki page in a group wiki is updated
internal_events: true
action: update_group_wiki_page
identifiers:
- namespace
- user
additional_properties:
label:
description: "Is 'template' when the page is a template"
property:
description: "Name of the markup language used by the page"
product_group: knowledge
milestone: '17.5'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/167091
distributions:
- ee
tiers:
- premium
- ultimate

View File

@ -0,0 +1,20 @@
---
key_path: redis_hll_counters.count_distinct_user_id_from_create_group_wiki_page_monthly
description: Monthly count of unique users who created a wiki page in group wiki
product_group: knowledge
performance_indicator_type: []
value_type: number
status: active
milestone: '17.5'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/167091
time_frame: 28d
data_source: internal_events
data_category: optional
distribution:
- ee
tier:
- premium
- ultimate
events:
- name: create_group_wiki_page
unique: user.id

View File

@ -0,0 +1,20 @@
---
key_path: redis_hll_counters.count_distinct_user_id_from_delete_group_wiki_page_monthly
description: Monthly count of unique users who deleted a wiki page in group wiki
product_group: knowledge
performance_indicator_type: []
value_type: number
status: active
milestone: '17.5'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/167091
time_frame: 28d
data_source: internal_events
data_category: optional
distribution:
- ee
tier:
- premium
- ultimate
events:
- name: delete_group_wiki_page
unique: user.id

View File

@ -0,0 +1,20 @@
---
key_path: redis_hll_counters.count_distinct_user_id_from_update_group_wiki_page_monthly
description: Monthly count of unique users who updated a wiki page in group wiki
product_group: knowledge
performance_indicator_type: []
value_type: number
status: active
milestone: '17.5'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/167091
time_frame: 28d
data_source: internal_events
data_category: optional
distribution:
- ee
tier:
- premium
- ultimate
events:
- name: update_group_wiki_page
unique: user.id

View File

@ -0,0 +1,19 @@
---
key_path: counts.count_total_create_group_wiki_page_monthly
description: Monthly count of wiki pages created in group wikis
product_group: knowledge
performance_indicator_type: []
value_type: number
status: active
milestone: '17.5'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/167091
time_frame: 28d
data_source: internal_events
data_category: optional
distribution:
- ee
tier:
- premium
- ultimate
events:
- name: create_group_wiki_page

View File

@ -0,0 +1,19 @@
---
key_path: counts.count_total_delete_group_wiki_page_monthly
description: Monthly count of wiki pages deleted in group wikis
product_group: knowledge
performance_indicator_type: []
value_type: number
status: active
milestone: '17.5'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/167091
time_frame: 28d
data_source: internal_events
data_category: optional
distribution:
- ee
tier:
- premium
- ultimate
events:
- name: delete_group_wiki_page

View File

@ -0,0 +1,19 @@
---
key_path: counts.count_total_update_group_wiki_page_monthly
description: Monthly count of wiki pages updated in group wikis
product_group: knowledge
performance_indicator_type: []
value_type: number
status: active
milestone: '17.5'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/167091
time_frame: 28d
data_source: internal_events
data_category: optional
distribution:
- ee
tier:
- premium
- ultimate
events:
- name: update_group_wiki_page

View File

@ -0,0 +1,20 @@
---
key_path: redis_hll_counters.count_distinct_user_id_from_create_group_wiki_page_weekly
description: Weekly count of unique users who created a wiki page in group wiki
product_group: knowledge
performance_indicator_type: []
value_type: number
status: active
milestone: '17.5'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/167091
time_frame: 7d
data_source: internal_events
data_category: optional
distribution:
- ee
tier:
- premium
- ultimate
events:
- name: create_group_wiki_page
unique: user.id

View File

@ -0,0 +1,20 @@
---
key_path: redis_hll_counters.count_distinct_user_id_from_delete_group_wiki_page_weekly
description: Weekly count of unique users who deleted a wiki page in group wiki
product_group: knowledge
performance_indicator_type: []
value_type: number
status: active
milestone: '17.5'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/167091
time_frame: 7d
data_source: internal_events
data_category: optional
distribution:
- ee
tier:
- premium
- ultimate
events:
- name: delete_group_wiki_page
unique: user.id

View File

@ -0,0 +1,20 @@
---
key_path: redis_hll_counters.count_distinct_user_id_from_update_group_wiki_page_weekly
description: Weekly count of unique users who updated a wiki page in group wiki
product_group: knowledge
performance_indicator_type: []
value_type: number
status: active
milestone: '17.5'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/167091
time_frame: 7d
data_source: internal_events
data_category: optional
distribution:
- ee
tier:
- premium
- ultimate
events:
- name: update_group_wiki_page
unique: user.id

View File

@ -0,0 +1,19 @@
---
key_path: counts.count_total_create_group_wiki_page_weekly
description: Weekly count of wiki pages created in group wikis
product_group: knowledge
performance_indicator_type: []
value_type: number
status: active
milestone: '17.5'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/167091
time_frame: 7d
data_source: internal_events
data_category: optional
distribution:
- ee
tier:
- premium
- ultimate
events:
- name: create_group_wiki_page

View File

@ -0,0 +1,19 @@
---
key_path: counts.count_total_delete_group_wiki_page_weekly
description: Weekly count of wiki pages deleted in group wikis
product_group: knowledge
performance_indicator_type: []
value_type: number
status: active
milestone: '17.5'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/167091
time_frame: 7d
data_source: internal_events
data_category: optional
distribution:
- ee
tier:
- premium
- ultimate
events:
- name: delete_group_wiki_page

View File

@ -0,0 +1,19 @@
---
key_path: counts.count_total_update_group_wiki_page_weekly
description: Weekly count of wiki pages updated in group wikis
product_group: knowledge
performance_indicator_type: []
value_type: number
status: active
milestone: '17.5'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/167091
time_frame: 7d
data_source: internal_events
data_category: optional
distribution:
- ee
tier:
- premium
- ultimate
events:
- name: update_group_wiki_page

View File

@ -0,0 +1,8 @@
---
migration_job_name: FixProjectSettingsHasVulnerabilities
description: Fix the project_settings#has_vulnerabilities column data inconsistencies
feature_category: vulnerability_management
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/167933
milestone: '17.5'
queued_migration_version: 20241002155253
finalized_by: # version of the migration that finalized this BBM

View File

@ -0,0 +1,28 @@
# frozen_string_literal: true
class QueueFixProjectSettingsHasVulnerabilities < Gitlab::Database::Migration[2.2]
milestone '17.5'
restrict_gitlab_migration gitlab_schema: :gitlab_sec
MIGRATION = "FixProjectSettingsHasVulnerabilities"
DELAY_INTERVAL = 2.minutes
BATCH_SIZE = 1000
SUB_BATCH_SIZE = 100
def up
queue_batched_background_migration(
MIGRATION,
:vulnerability_reads,
:project_id,
batch_class_name: 'LooseIndexScanBatchingStrategy',
job_interval: DELAY_INTERVAL,
batch_size: BATCH_SIZE,
sub_batch_size: SUB_BATCH_SIZE
)
end
def down
delete_batched_background_migration(MIGRATION, :vulnerability_reads, :project_id, [])
end
end

View File

@ -0,0 +1 @@
ab1427627730b92e71f06a9cfe4c2615b61bc648f6b86e814c92e29572f95905

View File

@ -69,25 +69,29 @@ Enable the Advanced SAST analyzer to discover vulnerabilities in your applicatio
cross-function and cross-file taint analysis. You can then adjust its behavior by using CI/CD
variables.
### Enabling the analyzer
### Requirements
Prerequisites:
Like other GitLab SAST analyzers, the Advanced SAST analyzer requires a runner and a CI/CD pipeline; see [SAST requirements](index.md#requirements) for details.
- GitLab version 17.1 or later, if you are running a self-managed instance. (GitLab.com is ready to use.)
- The `.gitlab-ci.yml` file must include:
- The `test` stage.
On GitLab self-managed, you must also use a GitLab version that supports Advanced SAST:
To enable the Advanced SAST analyzer:
- You should use GitLab 17.4 or later if possible. GitLab 17.4 includes a new code-flow view, vulnerability deduplication, and further updates to the SAST CI/CD template.
- The [SAST CI/CD templates](index.md#stable-vs-latest-sast-templates) were updated to include Advanced SAST in the following releases:
- The stable template includes Advanced SAST in GitLab 17.3 or later.
- The latest template includes Advanced SAST in GitLab 17.2 or later. Note that you [should not mix latest and stable templates](../index.md#template-editions) in a single project.
- At a minimum, GitLab Advanced SAST requires version 17.1 or later.
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Build > Pipeline editor**.
1. If no `.gitlab-ci.yml` file exists, select **Configure pipeline**, then delete the example
content.
1. Include a SAST template (if not already done), either `Jobs/SAST.gitlab-ci.yml` or `Jobs/SAST.latest.gitlab-ci.yml`.
**Note:** The `latest` templates can receive breaking changes in any release.
1. Set the CI/CD variable `GITLAB_ADVANCED_SAST_ENABLED` to `true`.
### Enabling Advanced SAST scanning
Here is a minimal YAML file for enabling GitLab Advanced SAST:
Advanced SAST is included in the standard GitLab SAST CI/CD template, but isn't yet enabled by default.
To enable it, set the CI/CD variable `GITLAB_ADVANCED_SAST_ENABLED` to `true`.
You can set this variable in different ways depending on how you manage your CI/CD configuration.
#### Edit the CI/CD pipeline definition manually
If you've already enabled GitLab SAST scanning in your project, add a new CI/CD variable to enable GitLab SAST.
This minimal YAML file includes the [stable SAST template](index.md#stable-vs-latest-sast-templates) and enables Advanced SAST:
```yaml
include:
@ -97,6 +101,29 @@ variables:
GITLAB_ADVANCED_SAST_ENABLED: 'true'
```
#### Enforce it in a Scan Execution Policy
To enable Advanced SAST in a [Scan Execution Policy](../policies/scan_execution_policies.md), update your policy's scan action to set the CI/CD variable `GITLAB_ADVANCED_SAST_ENABLED` to `true`.
You can set this variable by:
- Selecting it from the menu in the [policy editor](../policies/scan_execution_policies.md#scan-execution-policy-editor).
- Adding it to the [`variables` object](../policies/scan_execution_policies.md#scan-action-type) in the scan action.
#### By using the pipeline editor
To enable Advanced SAST by using the pipeline editor:
1. In your project, select **Build > Pipeline editor**.
1. If no `.gitlab-ci.yml` file exists, select **Configure pipeline**, then delete the example
content.
1. Update the CI/CD configuration to:
- Include one of the GitLab-managed [SAST CI/CD templates](index.md#stable-vs-latest-sast-templates) if it is not [already included](index.md#configure-sast-in-your-cicd-yaml).
- In GitLab 17.3 or later, you should use the stable template, `Jobs/SAST.gitlab-ci.yml`.
- In GitLab 17.2, Advanced SAST is only available in the latest template, `Jobs/SAST.latest.gitlab-ci.yml`. Note that you [should not mix latest and stable templates](../index.md#template-editions) in a single project.
- In GitLab 17.1, you must manually copy the contents of the Advanced SAST job into your CI/CD pipeline definition.
- Set the CI/CD variable `GITLAB_ADVANCED_SAST_ENABLED` to `true`.
See the [minimal YAML example above](#edit-the-cicd-pipeline-definition-manually).
1. Select the **Validate** tab, then select **Validate pipeline**.
The message **Simulation completed successfully** confirms the file is valid.
@ -108,7 +135,7 @@ variables:
merge request**.
1. Review and edit the merge request according to your standard workflow, then select **Merge**.
Pipelines now include an advanced SAST job.
Pipelines now include an Advanced SAST job.
## Troubleshooting

View File

@ -244,6 +244,21 @@ of your group. They can, therefore, view information they should not be able to
Reassigning contributions to users with administrator access is disabled by default, but you can
[enable](../../../administration/settings/import_and_export_settings.md#allow-contribution-mapping-to-administrators) it.
##### Membership security considerations
Because of the GitLab permissions model, when a group or project is imported into an existing parent group, members of
the parent group are granted [inherited membership](../members/index.md#membership-types) of the imported group or project.
Selecting a user for contribution and membership reassignment who already has an
existing inherited membership of the imported group or project can affect how memberships
are reassigned to them.
GitLab does not allow a membership in a child project or group to have a lower role
than an inherited membership. If an imported membership for an assigned user has a lower role
than their existing inherited membership, the imported membership is not reassigned to the user.
This results in their membership for the imported group or project being higher than it was on the source.
#### Request reassignment in UI
Prerequisites:

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# Fixes the project_settings#has_vulnerabilities data inconsistencies
class FixProjectSettingsHasVulnerabilities < BatchedMigrationJob
feature_category :vulnerability_management
def perform; end
end
end
end
Gitlab::BackgroundMigration::FixProjectSettingsHasVulnerabilities.prepend_mod

View File

@ -5,7 +5,7 @@ module Gitlab
class SourceUserMapper
include Gitlab::ExclusiveLeaseHelpers
LRU_CACHE_SIZE = 8000
LRU_CACHE_SIZE = 100
LOCK_TTL = 15.seconds.freeze
LOCK_SLEEP = 0.3.seconds.freeze
LOCK_RETRIES = 100
@ -31,7 +31,6 @@ module Gitlab
#
# @param [String] source_user_identifier The identifier for the source user to find.
# @return [Import::SourceUser, nil] The found source user object, or `nil` if no match is found.
#
def find_source_user(source_user_identifier)
cache_from_request_store[source_user_identifier] ||= ::Import::SourceUser.uncached do
::Import::SourceUser.find_source_user(
@ -43,7 +42,8 @@ module Gitlab
end
end
def find_or_create_source_user(source_name:, source_username:, source_user_identifier:)
# Finds a source user by the provided `source_user_identifier` or creates a new one
def find_or_create_source_user(source_name:, source_username:, source_user_identifier:, cache: true)
source_user = find_source_user(source_user_identifier)
return source_user if source_user
@ -54,7 +54,9 @@ module Gitlab
source_user_identifier: source_user_identifier
)
cache_from_request_store[source_user_identifier] = source_user
cache_from_request_store[source_user_identifier] = source_user if cache
source_user
end
private

View File

@ -51,7 +51,8 @@ module Import
context.source_user_mapper.find_or_create_source_user(
source_user_identifier: source_user_id,
source_name: source_name,
source_username: source_username
source_username: source_username,
cache: false
)
end
end

View File

@ -85,11 +85,11 @@ module QA
end
def running?
`docker ps -f name=#{@name}`.include?(@name)
shell("docker ps -f name=#{@name}").include?(@name)
end
def read_file(file_path)
`docker exec #{@name} /bin/cat #{file_path}`
shell("docker exec #{@name} /bin/cat #{file_path}")
end
def restart

View File

@ -1,67 +1,179 @@
# frozen_string_literal: true
require "socket"
module QA
module Service
module DockerRun
class Smocker < Base
def initialize(name: 'smocker-server')
DEFAULT_SERVER_PORT = 8080
DEFAULT_CONFIG_PORT = 8081
private_class_method :new
class << self
# Create new instance of smocker container with random name and ports
#
# @return [QA::Service::DockerRun::Smocker]
def create
container = new
container.register!
container.wait_for_running
container
rescue StandardError => e
Runtime::Logger.error("Failed to start smocker container, logs:\n#{container.logs}")
raise e
end
# @param wait [Integer] seconds to wait for server
# @yieldparam [SmockerApi] the api object ready for interaction
def init(wait: 10)
if @container.nil?
@container = create
@api = Vendor::Smocker::SmockerApi.new(
host: @container.host_name,
public_port: @container.public_port,
admin_port: @container.admin_port
)
@api.wait_for_ready(wait: wait)
end
yield @api
end
def teardown!
@container&.remove!
@container = nil
@api = nil
end
end
def initialize
@image = 'thiht/smocker:0.18.5'
@name = name
@public_port = 8080
@admin_port = 8081
@name = "smocker-service-#{SecureRandom.hex(6)}"
super()
end
# @param wait [Integer] seconds to wait for server
# @yieldparam [SmockerApi] the api object ready for interaction
def self.init(wait: 10)
if @container.nil?
@container = new
@container.register!
@container.wait_for_running
@api = Vendor::Smocker::SmockerApi.new(
host: @container.host_name,
public_port: @container.public_port,
admin_port: @container.admin_port
)
@api.wait_for_ready(wait: wait)
end
yield @api
end
def self.teardown!
@container&.remove!
@container = nil
@api = nil
end
attr_reader :public_port, :admin_port
# Wait for container to be running
#
# @return [void]
def wait_for_running
Support::Waiter.wait_until(raise_on_failure: false, reload_page: false) do
Support::Waiter.wait_until(max_duration: 10, reload_page: false) do
running?
end
end
# Start smocker container
#
# @return [void]
def register!
command = <<~CMD.tr("\n", ' ')
docker run -d --rm
--network #{network}
--name #{name}
--publish #{public_port}:8080
--publish #{admin_port}:8081
#{image}
CMD
return if running?
shell command
command = %W[docker run -d --network #{network} --name #{name}]
# when host network is used, published ports are discarded and service in container runs as if on host
# make sure random open ports are fetched and configured for smocker server
command.push("-e", "SMOCKER_MOCK_SERVER_LISTEN_PORT=#{host_network? ? server_port : DEFAULT_SERVER_PORT}")
command.push("-e", "SMOCKER_CONFIG_LISTEN_PORT=#{host_network? ? config_port : DEFAULT_CONFIG_PORT}")
command.push("--publish-all") unless host_network?
command.push(image)
shell command.join(" ")
end
# Server port
#
# When running in contained docker network, return internal port because service is accessed using hostname:port
#
# @return [Integer]
def public_port
@public_port ||= if host_network?
server_port
elsif docker_network?
DEFAULT_SERVER_PORT
else
fetch_published_port(DEFAULT_SERVER_PORT)
end
end
# Admin port
#
# When running in contained docker network, return internal port because service is accessed using hostname:port
#
# @return [Integer]
def admin_port
@admin_port ||= if host_network?
config_port
elsif docker_network?
DEFAULT_CONFIG_PORT
else
fetch_published_port(DEFAULT_CONFIG_PORT)
end
end
private
attr_reader :name, :image
# Random open port for server
#
# @return [Integer]
def server_port
@server_port ||= random_port
end
# Random open port for server configuration
#
# @return [Integer]
def config_port
@config_port ||= random_port
end
# Host network used?
#
# @return [Boolea]
def host_network?
network == "host"
end
# Running within custom docker network
#
# @return [Boolean]
def docker_network?
host_name == "#{name}.#{network}"
end
# Fetch published container port
#
# @param [Integer] container_port
# @return [Integer]
def fetch_published_port(container_port)
port = published_ports.split("\n").find { |line| line.start_with?(container_port.to_s) }.split(':').last
raise("Could not find published #{container_port} port for container #{name}") unless port
port.to_i
end
# Published ports for smocker container
#
# @return [String]
def published_ports
@published_ports ||= shell("docker port #{name}").presence || raise(
"Unable to fetch published ports for smocker container #{name}"
)
end
# Random unassigned port
#
# @return [Integer]
def random_port
server = TCPServer.new('127.0.0.1', 0)
port = server.addr[1]
server.close
port
end
end
end
end

View File

@ -2,38 +2,52 @@
module QA
module Support
class AuditEventStreamingService < Vendor::Smocker::SmockerApi
class AuditEventStreamingService
def initialize(wait: 10, reset_on_init: true)
@mocks = File.read(EE::Runtime::Path.fixture('audit_event_streaming', 'mocks.yml'))
# We use the time of initialization to limit the results we get from the audit events API
@start = DateTime.now.iso8601
@smocker_container = Service::DockerRun::Smocker.new
@smocker_container.register!
@smocker_container.wait_for_running
super(
@smocker_container = Service::DockerRun::Smocker.create
@api = Vendor::Smocker::SmockerApi.new(
host: @smocker_container.host_name,
public_port: @smocker_container.public_port,
admin_port: @smocker_container.admin_port
)
wait_for_ready(wait: wait)
reset if reset_on_init
register(mocks)
@api.wait_for_ready(wait: wait)
@api.reset if reset_on_init
@api.register(mocks)
end
def logs
@smocker_container.logs
delegate :verify, to: :api
# Fetch smocker container logs
#
# @return [String]
def container_logs
smocker_container.logs
end
# Reset mock definitions
#
# @return [void]
def reset!
reset
register(mocks)
api.reset
api.register(mocks)
end
# Remove the Smocker Docker container
#
# @return [void]
def teardown!
@smocker_container.remove! if @smocker_container
smocker_container&.remove!
end
# Stream destination url
#
# @return [String]
def destination_url
@logs_endpoint ||= api.url('logs')
end
# Wait for the mock service to receive a request with the specified event type
@ -46,7 +60,7 @@ module QA
# @return [Hash] the request
def wait_for_event(event_type, entity_type, entity_path = nil, wait: 10, raise_on_failure: true)
event = Waiter.wait_until(max_duration: wait, sleep_interval: 1, raise_on_failure: false) do
history.find do |record|
api.history.find do |record|
body = record.request[:body]
next if body.blank?
@ -62,8 +76,8 @@ module QA
raise Repeater::WaitExceededError,
"An event with type '#{event_type}'#{" and entity_path '#{entity_path}'" if entity_path} was not received. " \
"Event history: #{stringified_history}. " \
"Audit events with entity_type '#{entity_type}': #{audit_events}"
"Event history: #{stringified_history}. " \
"Audit events with entity_type '#{entity_type}': #{audit_events}"
end
# Wait for GitLab to start streaming audit events and for the Smocker server to be ready to receive them.
@ -80,19 +94,14 @@ module QA
end
rescue Repeater::WaitExceededError
# If there is a failure this will output the logs from the smocker container (at the debug log level)
logs
container_logs
raise
end
private
# The configuration for the mocked requests and responses for events that will be verified in this test
#
# @return [String]
def mocks
@mocks ||= File.read(EE::Runtime::Path.fixture('audit_event_streaming', 'mocks.yml'))
end
attr_reader :mocks, :smocker_container, :api
end
end
end

View File

@ -1,49 +1,95 @@
# frozen_string_literal: true
# rubocop:disable RSpec/AnyInstanceOf -- allow_next_instance_of relies on gitlab/rspec
module QA
RSpec.describe Service::DockerRun::Smocker do
let(:name) { 'smocker-12345' }
let(:network) { 'thenet' }
let(:host_ip) { '1.2.3.4' }
let(:network) { "thenet" }
let(:image) { "thiht/smocker:0.18.5" }
let(:server_port) { 8080 }
let(:config_port) { 8081 }
let(:port_args) { "-e SMOCKER_MOCK_SERVER_LISTEN_PORT=#{server_port} -e SMOCKER_CONFIG_LISTEN_PORT=#{config_port}" }
let(:base_cmd_pattern) { /docker run -d --network #{network} --name smocker-service-\w+ #{port_args}/ }
subject(:smocker_container) { described_class.new(name: name) }
subject(:container) { described_class.create }
before do
# rubocop:disable RSpec/AnyInstanceOf -- allow_next_instance_of relies on gitlab/rspec
allow_any_instance_of(described_class).to receive(:shell).and_return("")
allow_any_instance_of(described_class).to receive(:network).and_return(network)
# rubocop:enable RSpec/AnyInstanceOf
allow(smocker_container).to receive(:host_ip).and_return(host_ip)
allow(Support::Waiter).to receive(:wait_until).and_return(true)
end
describe '#host_name' do
shared_examples 'returns host ip' do
it 'returns host ip' do
expect(smocker_container.host_name).to eq(host_ip)
describe "#create" do
context "with successful creation" do
it "creates new instance of smocker container" do
expect(container).to be_instance_of(described_class)
expect(container).to have_received(:shell).with(/#{base_cmd_pattern} --publish-all #{image}/)
expect(container.public_port).to eq(server_port)
expect(container.admin_port).to eq(config_port)
end
end
shared_examples 'returns name.network' do
it 'returns name.network' do
expect(smocker_container.host_name).to eq("#{name}.#{network}")
context "with failed creation" do
before do
allow(Support::Waiter).to receive(:wait_until).and_raise("error")
allow(Runtime::Logger).to receive(:error)
allow_any_instance_of(described_class).to receive(:shell)
.with(/docker logs smocker-service-\w+/)
.and_return("logs")
end
it "raises error" do
expect { container }.to raise_error("error")
expect(Runtime::Logger).to have_received(:error).with("Failed to start smocker container, logs:\nlogs")
end
end
context 'when network is not bridge or host' do
it_behaves_like 'returns name.network'
context "with host network" do
let(:network) { "host" }
let(:server_port) { 55020 }
let(:config_port) { 55021 }
let(:server) { instance_double(TCPServer, close: nil) }
before do
allow(TCPServer).to receive(:new).and_return(server)
allow(server).to receive(:addr).and_return([nil, server_port], [nil, config_port])
end
it "set random open ports for smocker service" do
expect(container).to have_received(:shell).with(/#{base_cmd_pattern} #{image}/)
expect(container.public_port).to eq(server_port)
expect(container.admin_port).to eq(config_port)
end
end
end
describe "#init" do
let(:api) { instance_double(Vendor::Smocker::SmockerApi, wait_for_ready: true) }
before do
described_class.instance_variable_set(:@container, nil)
allow(Vendor::Smocker::SmockerApi).to receive(:new).and_return(api)
allow(described_class).to receive(:create).and_return(container)
allow(container).to receive(:host_name).and_return('localhost')
allow(container).to receive(:shell).with(/docker port smocker-service-\w+/).and_return(<<~PORTS)
8080/tcp -> 0.0.0.0:55020
8081/tcp -> 0.0.0.0:55021
PORTS
end
context 'when network is bridge' do
let(:network) { 'bridge' }
it "create new instance of SmockerApi" do
described_class.init(wait: 10) { |smocker_api| expect(smocker_api).to eq(api) }
it_behaves_like 'returns host ip'
end
context 'when network is host' do
let(:network) { 'host' }
it_behaves_like 'returns host ip'
expect(Vendor::Smocker::SmockerApi).to have_received(:new).with(
host: "localhost",
public_port: 55020,
admin_port: 55021
)
expect(api).to have_received(:wait_for_ready).with(wait: 10)
end
end
end
end
# rubocop:enable RSpec/AnyInstanceOf

View File

@ -20,6 +20,8 @@ import EmptyViewer from '~/repository/components/blob_viewers/empty_viewer.vue';
import SourceViewer from '~/vue_shared/components/source_viewer/source_viewer.vue';
import blobInfoQuery from 'shared_queries/repository/blob_info.query.graphql';
import projectInfoQuery from '~/repository/queries/project_info.query.graphql';
import highlightMixin from '~/repository/mixins/highlight_mixin';
import getRefMixin from '~/repository/mixins/get_ref';
import CodeIntelligence from '~/code_navigation/components/app.vue';
import * as urlUtility from '~/lib/utils/url_utility';
import { isLoggedIn, handleLocationHash } from '~/lib/utils/common_utils';
@ -34,7 +36,6 @@ import {
projectMock,
userPermissionsMock,
propsMock,
refMock,
axiosMockResponse,
} from '../mock_data';
@ -127,7 +128,7 @@ const createComponent = async (mockData = {}, mountFn = shallowMount, mockRoute
store: createMockStore(),
apolloProvider: fakeApollo,
propsData: propsMock,
mixins: [{ data: () => ({ ref: refMock }) }],
mixins: [getRefMixin, highlightMixin],
mocks: {
$route: mockRoute,
$router: mockRouter,

View File

@ -1,178 +0,0 @@
import {
GlDaterangePicker,
GlFilteredSearchSuggestion,
GlFilteredSearchSuggestionList,
GlFilteredSearchToken,
} from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import DaterangeToken from '~/vue_shared/components/filtered_search_bar/tokens/daterange_token.vue';
import { OPERATORS_IS } from '~/vue_shared/components/filtered_search_bar/constants';
const CUSTOM_DATE = 'custom-date';
describe('DaterangeToken', () => {
let wrapper;
const defaultProps = {
active: true,
value: {
data: '',
},
config: {
operators: OPERATORS_IS,
options: [
{
value: 'last_week',
title: 'Last week',
},
{
value: 'last_month',
title: 'Last month',
},
],
maxDateRange: 7,
},
};
function createComponent(props = {}) {
return mountExtended(DaterangeToken, {
propsData: { ...defaultProps, ...props },
stubs: {
Portal: true,
},
provide: {
portalName: 'fake target',
alignSuggestions: function fakeAlignSuggestions() {},
suggestionsListClass: () => 'custom-class',
termsAsTokens: () => false,
},
});
}
const findGlFilteredSearchToken = () => wrapper.findComponent(GlFilteredSearchToken);
const findDateRangePicker = () => wrapper.findComponent(GlDaterangePicker);
const findAllSuggestions = () => wrapper.findAllComponents(GlFilteredSearchSuggestion);
const selectSuggestion = (suggestion) =>
wrapper.findComponent(GlFilteredSearchSuggestionList).vm.$emit('suggestion', suggestion);
beforeEach(() => {
wrapper = createComponent();
});
it('renders a filtered search token', () => {
expect(findGlFilteredSearchToken().exists()).toBe(true);
});
it('remove the options from the token config', () => {
expect(findGlFilteredSearchToken().props('config').options).toBeUndefined();
});
it('does not set the token as view-only', () => {
expect(findGlFilteredSearchToken().props('viewOnly')).toBe(false);
});
it('does not show the date picker by default', () => {
expect(findDateRangePicker().exists()).toBe(false);
});
it('does not re-activate the token', async () => {
await wrapper.setProps({ active: false });
expect(findGlFilteredSearchToken().props('active')).toBe(false);
});
it('does not override the value', async () => {
await wrapper.setProps({ value: { data: 'value' } });
expect(findGlFilteredSearchToken().props('value')).toEqual({ data: 'value' });
});
it('renders a list of suggestions as specified by the config', () => {
const suggestions = findAllSuggestions();
expect(suggestions.exists()).toBe(true);
expect(suggestions).toHaveLength(defaultProps.config.options.length + 1);
[...defaultProps.config.options, { value: CUSTOM_DATE, title: 'Custom' }].forEach(
(option, i) => {
expect(suggestions.at(i).props('value')).toBe(option.value);
expect(suggestions.at(i).text()).toBe(option.title);
},
);
});
it('sets maxDateRange on the datepicker', async () => {
await selectSuggestion(CUSTOM_DATE);
expect(findDateRangePicker().props('maxDateRange')).toBe(defaultProps.config.maxDateRange);
});
it('sets the dataSegmentInputAttributes', () => {
expect(findGlFilteredSearchToken().props('dataSegmentInputAttributes')).toEqual({
id: 'time_range_data_segment_input',
});
});
describe('when a default option is selected', () => {
const option = defaultProps.config.options[0].value;
beforeEach(async () => {
await selectSuggestion(option);
});
it('does not show the date picker if default option is selected', () => {
expect(findDateRangePicker().exists()).toBe(false);
});
it('sets the value', () => {
expect(findGlFilteredSearchToken().emitted().select).toEqual([[option]]);
expect(findGlFilteredSearchToken().emitted().complete).toEqual([[option]]);
});
});
describe('when custom-date option is selected', () => {
beforeEach(async () => {
await selectSuggestion(CUSTOM_DATE);
});
it('sets the token as view-only', () => {
expect(findGlFilteredSearchToken().props('viewOnly')).toBe(true);
});
it('shows the date picker', () => {
expect(findDateRangePicker().exists()).toBe(true);
const today = new Date();
expect(findDateRangePicker().props('defaultStartDate')).toEqual(today);
expect(findDateRangePicker().props('defaultMaxDate')).toEqual(today);
expect(findDateRangePicker().props('startOpened')).toBe(true);
});
it('re-activate the token while the date picker is open', async () => {
await wrapper.setProps({ active: false });
expect(findGlFilteredSearchToken().props('active')).toBe(true);
});
it('overrides the value', async () => {
await wrapper.setProps({ value: { data: 'value' } });
expect(findGlFilteredSearchToken().props('value')).toEqual({ data: '' });
});
it('sets the dataSegmentInputAttributes', () => {
expect(findGlFilteredSearchToken().props('dataSegmentInputAttributes')).toEqual({
id: 'time_range_data_segment_input',
placeholder: 'YYYY-MM-DD - YYYY-MM-DD',
style: 'padding-left: 23px;',
});
});
it('sets the date range and hides the picker upon selection', async () => {
await findDateRangePicker().vm.$emit('input', {
startDate: new Date('October 13, 2014 11:13:00'),
endDate: new Date('October 13, 2014 11:13:00'),
});
expect(findGlFilteredSearchToken().emitted().complete).toEqual([
[CUSTOM_DATE],
[`"2014-10-13 - 2014-10-13"`],
]);
expect(findGlFilteredSearchToken().emitted().select).toEqual([
[CUSTOM_DATE],
[`"2014-10-13 - 2014-10-13"`],
]);
expect(findDateRangePicker().exists()).toBe(false);
});
});
});

View File

@ -24,6 +24,7 @@ RSpec.describe Gitlab::Import::SourceUserMapper, :request_store, feature_categor
let(:source_name) { 'Pry Contributor' }
let(:source_username) { 'a_pry_contributor' }
let(:source_user_identifier) { '123456' }
let(:cache) { false }
subject(:find_or_create_source_user) do
described_class.new(
@ -33,7 +34,8 @@ RSpec.describe Gitlab::Import::SourceUserMapper, :request_store, feature_categor
).find_or_create_source_user(
source_name: source_name,
source_username: source_username,
source_user_identifier: source_user_identifier
source_user_identifier: source_user_identifier,
cache: cache
)
end
@ -203,6 +205,26 @@ RSpec.describe Gitlab::Import::SourceUserMapper, :request_store, feature_categor
expect { find_or_create_source_user }.to raise_error(ActiveRecord::RecordInvalid)
end
end
context 'when cache is true' do
let(:cache) { true }
it 'caches the created source user' do
source_user = find_or_create_source_user
expect(Gitlab::SafeRequestStore[:source_user_cache][source_user.source_user_identifier]).to eq(source_user)
end
end
context 'when cache is false' do
let(:cache) { false }
it 'does not cache the created source user' do
source_user = find_or_create_source_user
expect(Gitlab::SafeRequestStore[:source_user_cache][source_user.source_user_identifier]).to eq(nil)
end
end
end
describe '#find_source_user' do

View File

@ -0,0 +1,28 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe QueueFixProjectSettingsHasVulnerabilities, feature_category: :vulnerability_management do
let!(:batched_migration) { described_class::MIGRATION }
it 'schedules a new batched migration' do
reversible_migration do |migration|
migration.before -> {
expect(batched_migration).not_to have_scheduled_batched_migration
}
migration.after -> {
expect(batched_migration).to have_scheduled_batched_migration(
gitlab_schema: :gitlab_sec,
table_name: :vulnerability_reads,
column_name: :project_id,
batch_class_name: 'LooseIndexScanBatchingStrategy',
interval: described_class::DELAY_INTERVAL,
batch_size: described_class::BATCH_SIZE,
sub_batch_size: described_class::SUB_BATCH_SIZE
)
}
end
end
end

View File

@ -43,12 +43,27 @@ RSpec.shared_examples 'WikiPages::CreateService#execute' do |container_type|
service.execute
end
it_behaves_like 'internal event tracking' do
let(:event) { 'create_wiki_page' }
describe 'internal event tracking' do
let(:project) { container if container.is_a?(Project) }
let(:namespace) { container.is_a?(Group) ? container : container.namespace }
subject(:track_event) { service.execute }
it_behaves_like 'internal event tracking' do
let(:event) { 'create_wiki_page' }
end
context 'with group container', if: container_type == :group do
it_behaves_like 'internal event tracking' do
let(:event) { 'create_group_wiki_page' }
end
end
context 'with project container', if: container_type == :project do
it_behaves_like 'internal event not tracked' do
let(:event) { 'create_group_wiki_page' }
end
end
end
context 'when the new page is a template' do

View File

@ -15,25 +15,36 @@ RSpec.shared_examples 'WikiPages::DestroyService#execute' do |container_type|
service.execute(page)
end
it_behaves_like 'internal event tracking' do
let(:event) { 'delete_wiki_page' }
describe 'internal event tracking' do
let(:project) { container if container.is_a?(Project) }
let(:namespace) { container.is_a?(Group) ? container : container.namespace }
subject(:track_event) { service.execute(page) }
end
context 'when the deleted page is a template' do
let(:page) { create(:wiki_page, title: "#{Wiki::TEMPLATES_DIR}/foobar") }
it_behaves_like 'internal event tracking' do
let(:event) { 'delete_wiki_page' }
let(:project) { container if container.is_a?(Project) }
let(:namespace) { container.is_a?(Group) ? container : container.namespace }
let(:label) { 'template' }
let(:property) { 'markdown' }
end
subject(:track_event) { service.execute(page) }
context 'with group container', if: container_type == :group do
it_behaves_like 'internal event tracking' do
let(:event) { 'delete_group_wiki_page' }
end
end
context 'with project container', if: container_type == :project do
it_behaves_like 'internal event not tracked' do
let(:event) { 'delete_group_wiki_page' }
end
end
context 'when the deleted page is a template' do
let(:page) { create(:wiki_page, title: "#{Wiki::TEMPLATES_DIR}/foobar") }
it_behaves_like 'internal event tracking' do
let(:event) { 'delete_wiki_page' }
let(:label) { 'template' }
let(:property) { 'markdown' }
end
end
end

View File

@ -58,12 +58,27 @@ RSpec.shared_examples 'WikiPages::UpdateService#execute' do |container_type|
service.execute(page)
end
it_behaves_like 'internal event tracking' do
let(:event) { 'update_wiki_page' }
describe 'internal event tracking' do
let(:project) { container if container.is_a?(Project) }
let(:namespace) { container.is_a?(Group) ? container : container.namespace }
subject(:track_event) { service.execute(page) }
it_behaves_like 'internal event tracking' do
let(:event) { 'update_wiki_page' }
end
context 'with group container', if: container_type == :group do
it_behaves_like 'internal event tracking' do
let(:event) { 'update_group_wiki_page' }
end
end
context 'with project container', if: container_type == :project do
it_behaves_like 'internal event not tracked' do
let(:event) { 'update_group_wiki_page' }
end
end
end
context 'when the updated page is a template' do