Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-08-23 12:10:04 +00:00
parent a95a884707
commit d67a86595f
30 changed files with 757 additions and 267 deletions

View file

@ -1 +1 @@
de019fc19eeb8bc6a65a6dbd8bf236669c777815
2ed9a2c78ec556eb8d64e03203c864355ea5a128

View file

@ -59,7 +59,7 @@ class Groups::RunnersController < Groups::ApplicationController
private
def runner
@runner ||= Ci::RunnersFinder.new(current_user: current_user, group: @group, params: {}).execute
@runner ||= Ci::RunnersFinder.new(current_user: current_user, params: { group: @group }).execute
.except(:limit, :offset)
.find(params[:id])
end

View file

@ -17,7 +17,7 @@ module Groups
NUMBER_OF_RUNNERS_PER_PAGE = 4
def show
runners_finder = Ci::RunnersFinder.new(current_user: current_user, group: @group, params: params)
runners_finder = Ci::RunnersFinder.new(current_user: current_user, params: params.merge({ group: @group }))
# We need all runners for count
@all_group_runners = runners_finder.execute.except(:limit, :offset)
@group_runners = runners_finder.execute.page(params[:page]).per(NUMBER_OF_RUNNERS_PER_PAGE)

View file

@ -7,9 +7,9 @@ module Ci
ALLOWED_SORTS = %w[contacted_asc contacted_desc created_at_asc created_at_desc created_date].freeze
DEFAULT_SORT = 'created_at_desc'
def initialize(current_user:, group: nil, params:)
def initialize(current_user:, params:)
@params = params
@group = group
@group = params.delete(:group)
@current_user = current_user
end
@ -48,10 +48,16 @@ module Ci
def group_runners
raise Gitlab::Access::AccessDeniedError unless can?(@current_user, :admin_group, @group)
# Getting all runners from the group itself and all its descendants
@runners = case @params[:membership]
when :direct
Ci::Runner.belonging_to_group(@group.id)
when :descendants, nil
# Getting all runners from the group itself and all its descendant groups/projects
descendant_projects = Project.for_group_and_its_subgroups(@group)
@runners = Ci::Runner.belonging_to_group_or_project(@group.self_and_descendants, descendant_projects)
Ci::Runner.belonging_to_group_or_project(@group.self_and_descendants, descendant_projects)
else
raise ArgumentError, 'Invalid membership filter'
end
end
def filter_by_status!

View file

@ -0,0 +1,26 @@
# frozen_string_literal: true
module Resolvers
module Ci
class GroupRunnersResolver < RunnersResolver
type Types::Ci::RunnerType.connection_type, null: true
argument :membership, ::Types::Ci::RunnerMembershipFilterEnum,
required: false,
default_value: :descendants,
description: 'Control which runners to include in the results.'
protected
def runners_finder_params(params)
super(params).merge(membership: params[:membership])
end
def parent_param
raise 'Expected group missing' unless parent.is_a?(Group)
{ group: parent }
end
end
end
end

View file

@ -34,7 +34,7 @@ module Resolvers
.execute)
end
private
protected
def runners_finder_params(params)
{
@ -47,6 +47,19 @@ module Resolvers
tag_name: node_selection&.selects?(:tag_list)
}
}.compact
.merge(parent_param)
end
def parent_param
return {} unless parent
raise "Unexpected parent type: #{parent.class}"
end
private
def parent
object.respond_to?(:sync) ? object.sync : object
end
end
end

View file

@ -0,0 +1,18 @@
# frozen_string_literal: true
module Types
module Ci
class RunnerMembershipFilterEnum < BaseEnum
graphql_name 'RunnerMembershipFilter'
description 'Values for filtering runners in namespaces.'
value 'DIRECT',
description: "Include runners that have a direct relationship.",
value: :direct
value 'DESCENDANTS',
description: "Include runners that have either a direct relationship or a relationship with descendants. These can be project runners or group runners (in the case where group is queried).",
value: :descendants
end
end
end

View file

@ -155,6 +155,12 @@ module Types
complexity: 5,
resolver: Resolvers::GroupsResolver
field :runners, Types::Ci::RunnerType.connection_type,
null: true,
resolver: Resolvers::Ci::GroupRunnersResolver,
description: "Find runners visible to the current user.",
feature_flag: :runner_graphql_query
def avatar_url
object.avatar_url(only_path: false)
end

View file

@ -16,7 +16,6 @@ module Ci
"ci-config-path": project.ci_config_path_or_default,
"ci-examples-help-page-path" => help_page_path('ci/examples/index'),
"ci-help-page-path" => help_page_path('ci/index'),
"commit-sha" => commit_sha,
"default-branch" => project.default_branch_or_main,
"empty-state-illustration-path" => image_path('illustrations/empty-state/empty-dag-md.svg'),
"initial-branch-name" => initial_branch,

View file

@ -1,8 +1,8 @@
---
name: dast_meta_tag_validation
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/67945
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/337711
milestone: '14.2'
name: create_vulnerabilities_via_api
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68158
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/338694
milestone: '14.3'
type: development
group: group::dynamic analysis
default_enabled: true
group: group::threat insights
default_enabled: false

View file

@ -1,8 +0,0 @@
---
name: dast_runner_site_validation
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61649
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/331082
milestone: '14.0'
type: development
group: group::dynamic analysis
default_enabled: true

View file

@ -0,0 +1,115 @@
# frozen_string_literal: true
class BackfillStageEventHash < ActiveRecord::Migration[6.1]
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
BATCH_SIZE = 100
EVENT_ID_IDENTIFIER_MAPPING = {
1 => :issue_created,
2 => :issue_first_mentioned_in_commit,
3 => :issue_closed,
4 => :issue_first_added_to_board,
5 => :issue_first_associated_with_milestone,
7 => :issue_last_edited,
8 => :issue_label_added,
9 => :issue_label_removed,
10 => :issue_deployed_to_production,
100 => :merge_request_created,
101 => :merge_request_first_deployed_to_production,
102 => :merge_request_last_build_finished,
103 => :merge_request_last_build_started,
104 => :merge_request_merged,
105 => :merge_request_closed,
106 => :merge_request_last_edited,
107 => :merge_request_label_added,
108 => :merge_request_label_removed,
109 => :merge_request_first_commit_at,
1000 => :code_stage_start,
1001 => :issue_stage_end,
1002 => :plan_stage_start
}.freeze
LABEL_BASED_EVENTS = Set.new([8, 9, 107, 108]).freeze
class GroupStage < ActiveRecord::Base
include EachBatch
self.table_name = 'analytics_cycle_analytics_group_stages'
end
class ProjectStage < ActiveRecord::Base
include EachBatch
self.table_name = 'analytics_cycle_analytics_project_stages'
end
class StageEventHash < ActiveRecord::Base
self.table_name = 'analytics_cycle_analytics_stage_event_hashes'
end
def up
GroupStage.reset_column_information
ProjectStage.reset_column_information
StageEventHash.reset_column_information
update_stage_table(GroupStage)
update_stage_table(ProjectStage)
add_not_null_constraint :analytics_cycle_analytics_group_stages, :stage_event_hash_id
add_not_null_constraint :analytics_cycle_analytics_project_stages, :stage_event_hash_id
end
def down
remove_not_null_constraint :analytics_cycle_analytics_group_stages, :stage_event_hash_id
remove_not_null_constraint :analytics_cycle_analytics_project_stages, :stage_event_hash_id
end
private
def update_stage_table(klass)
klass.each_batch(of: BATCH_SIZE) do |relation|
klass.transaction do
records = relation.where(stage_event_hash_id: nil).lock!.to_a # prevent concurrent modification (unlikely to happen)
records = delete_invalid_records(records)
next if records.empty?
hashes_by_stage = records.to_h { |stage| [stage, calculate_stage_events_hash(stage)] }
hashes = hashes_by_stage.values.uniq
StageEventHash.insert_all(hashes.map { |hash| { hash_sha256: hash } })
stage_event_hashes_by_hash = StageEventHash.where(hash_sha256: hashes).index_by(&:hash_sha256)
records.each do |stage|
stage.update!(stage_event_hash_id: stage_event_hashes_by_hash[hashes_by_stage[stage]].id)
end
end
end
end
def calculate_stage_events_hash(stage)
start_event_hash = calculate_event_hash(stage.start_event_identifier, stage.start_event_label_id)
end_event_hash = calculate_event_hash(stage.end_event_identifier, stage.end_event_label_id)
Digest::SHA256.hexdigest("#{start_event_hash}-#{end_event_hash}")
end
def calculate_event_hash(event_identifier, label_id = nil)
str = EVENT_ID_IDENTIFIER_MAPPING.fetch(event_identifier).to_s
str << "-#{label_id}" if LABEL_BASED_EVENTS.include?(event_identifier)
Digest::SHA256.hexdigest(str)
end
# Invalid records are safe to delete, since they are not working properly anyway
def delete_invalid_records(records)
to_be_deleted = records.select do |record|
EVENT_ID_IDENTIFIER_MAPPING[record.start_event_identifier].nil? ||
EVENT_ID_IDENTIFIER_MAPPING[record.end_event_identifier].nil?
end
to_be_deleted.each(&:delete)
records - to_be_deleted
end
end

View file

@ -0,0 +1 @@
97d968bba0eb2bf6faa19de8a3e4fe93dc03a623b623dc802ab0fe0a4afb0370

View file

@ -9102,7 +9102,8 @@ CREATE TABLE analytics_cycle_analytics_group_stages (
custom boolean DEFAULT true NOT NULL,
name character varying(255) NOT NULL,
group_value_stream_id bigint NOT NULL,
stage_event_hash_id bigint
stage_event_hash_id bigint,
CONSTRAINT check_e6bd4271b5 CHECK ((stage_event_hash_id IS NOT NULL))
);
CREATE SEQUENCE analytics_cycle_analytics_group_stages_id_seq
@ -9146,7 +9147,8 @@ CREATE TABLE analytics_cycle_analytics_project_stages (
custom boolean DEFAULT true NOT NULL,
name character varying(255) NOT NULL,
project_value_stream_id bigint NOT NULL,
stage_event_hash_id bigint
stage_event_hash_id bigint,
CONSTRAINT check_8f6019de1e CHECK ((stage_event_hash_id IS NOT NULL))
);
CREATE SEQUENCE analytics_cycle_analytics_project_stages_id_seq

View file

@ -4459,6 +4459,39 @@ Input type: `VulnerabilityConfirmInput`
| <a id="mutationvulnerabilityconfirmerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationvulnerabilityconfirmvulnerability"></a>`vulnerability` | [`Vulnerability`](#vulnerability) | The vulnerability after state change. |
### `Mutation.vulnerabilityCreate`
Input type: `VulnerabilityCreateInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationvulnerabilitycreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationvulnerabilitycreateconfidence"></a>`confidence` | [`VulnerabilityConfidence`](#vulnerabilityconfidence) | Confidence of the vulnerability (defaults to `unknown`). |
| <a id="mutationvulnerabilitycreateconfirmedat"></a>`confirmedAt` | [`Time`](#time) | Timestamp of when the vulnerability state changed to confirmed (defaults to creation time if status is `confirmed`). |
| <a id="mutationvulnerabilitycreatedescription"></a>`description` | [`String!`](#string) | Description of the vulnerability. |
| <a id="mutationvulnerabilitycreatedetectedat"></a>`detectedAt` | [`Time`](#time) | Timestamp of when the vulnerability was first detected (defaults to creation time). |
| <a id="mutationvulnerabilitycreatedismissedat"></a>`dismissedAt` | [`Time`](#time) | Timestamp of when the vulnerability state changed to dismissed (defaults to creation time if status is `dismissed`). |
| <a id="mutationvulnerabilitycreateidentifiers"></a>`identifiers` | [`[VulnerabilityIdentifierInput!]!`](#vulnerabilityidentifierinput) | Array of CVE or CWE identifiers for the vulnerability. |
| <a id="mutationvulnerabilitycreatemessage"></a>`message` | [`String`](#string) | Additional information about the vulnerability. |
| <a id="mutationvulnerabilitycreateproject"></a>`project` | [`ProjectID!`](#projectid) | ID of the project to attach the vulnerability to. |
| <a id="mutationvulnerabilitycreateresolvedat"></a>`resolvedAt` | [`Time`](#time) | Timestamp of when the vulnerability state changed to resolved (defaults to creation time if status is `resolved`). |
| <a id="mutationvulnerabilitycreatescannername"></a>`scannerName` | [`String!`](#string) | Name of the security scanner used to discover the vulnerability. |
| <a id="mutationvulnerabilitycreatescannertype"></a>`scannerType` | [`SecurityScannerType!`](#securityscannertype) | Type of the security scanner used to discover the vulnerability. |
| <a id="mutationvulnerabilitycreateseverity"></a>`severity` | [`VulnerabilitySeverity`](#vulnerabilityseverity) | Severity of the vulnerability (defaults to `unknown`). |
| <a id="mutationvulnerabilitycreatesolution"></a>`solution` | [`String`](#string) | How to fix this vulnerability. |
| <a id="mutationvulnerabilitycreatestate"></a>`state` | [`VulnerabilityState`](#vulnerabilitystate) | State of the vulnerability (defaults to `detected`). |
| <a id="mutationvulnerabilitycreatetitle"></a>`title` | [`String!`](#string) | Title of the vulnerability. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationvulnerabilitycreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationvulnerabilitycreateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationvulnerabilitycreatevulnerability"></a>`vulnerability` | [`Vulnerability`](#vulnerability) | Vulnerability created. |
### `Mutation.vulnerabilityDismiss`
Input type: `VulnerabilityDismissInput`
@ -10004,6 +10037,27 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="groupprojectssearch"></a>`search` | [`String`](#string) | Search project with most similar names or paths. |
| <a id="groupprojectssort"></a>`sort` | [`NamespaceProjectSort`](#namespaceprojectsort) | Sort projects by this criteria. |
##### `Group.runners`
Find runners visible to the current user. Available only when feature flag `runner_graphql_query` is enabled. This flag is enabled by default.
Returns [`CiRunnerConnection`](#cirunnerconnection).
This field returns a [connection](#connections). It accepts the
four standard [pagination arguments](#connection-pagination-arguments):
`before: String`, `after: String`, `first: Int`, `last: Int`.
###### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="grouprunnersmembership"></a>`membership` | [`RunnerMembershipFilter`](#runnermembershipfilter) | Control which runners to include in the results. |
| <a id="grouprunnerssearch"></a>`search` | [`String`](#string) | Filter by full token or partial text in description field. |
| <a id="grouprunnerssort"></a>`sort` | [`CiRunnerSort`](#cirunnersort) | Sort order of results. |
| <a id="grouprunnersstatus"></a>`status` | [`CiRunnerStatus`](#cirunnerstatus) | Filter runners by status. |
| <a id="grouprunnerstaglist"></a>`tagList` | [`[String!]`](#string) | Filter by tags associated with the runner (comma-separated or array). |
| <a id="grouprunnerstype"></a>`type` | [`CiRunnerType`](#cirunnertype) | Filter runners by type. |
##### `Group.timelogs`
Time logged on issues and merge requests in the group and its subgroups.
@ -13261,6 +13315,7 @@ Represents summary of a security report.
| <a id="securityreportsummarycoveragefuzzing"></a>`coverageFuzzing` | [`SecurityReportSummarySection`](#securityreportsummarysection) | Aggregated counts for the `coverage_fuzzing` scan. |
| <a id="securityreportsummarydast"></a>`dast` | [`SecurityReportSummarySection`](#securityreportsummarysection) | Aggregated counts for the `dast` scan. |
| <a id="securityreportsummarydependencyscanning"></a>`dependencyScanning` | [`SecurityReportSummarySection`](#securityreportsummarysection) | Aggregated counts for the `dependency_scanning` scan. |
| <a id="securityreportsummarygeneric"></a>`generic` | [`SecurityReportSummarySection`](#securityreportsummarysection) | Aggregated counts for the `generic` scan. |
| <a id="securityreportsummarysast"></a>`sast` | [`SecurityReportSummarySection`](#securityreportsummarysection) | Aggregated counts for the `sast` scan. |
| <a id="securityreportsummarysecretdetection"></a>`secretDetection` | [`SecurityReportSummarySection`](#securityreportsummarysection) | Aggregated counts for the `secret_detection` scan. |
@ -14141,7 +14196,7 @@ Represents a vulnerability.
| <a id="vulnerabilitynotes"></a>`notes` | [`NoteConnection!`](#noteconnection) | All notes on this noteable. (see [Connections](#connections)) |
| <a id="vulnerabilityprimaryidentifier"></a>`primaryIdentifier` | [`VulnerabilityIdentifier`](#vulnerabilityidentifier) | Primary identifier of the vulnerability. |
| <a id="vulnerabilityproject"></a>`project` | [`Project`](#project) | The project on which the vulnerability was found. |
| <a id="vulnerabilityreporttype"></a>`reportType` | [`VulnerabilityReportType`](#vulnerabilityreporttype) | Type of the security report that found the vulnerability (SAST, DEPENDENCY_SCANNING, CONTAINER_SCANNING, DAST, SECRET_DETECTION, COVERAGE_FUZZING, API_FUZZING, CLUSTER_IMAGE_SCANNING). `Scan Type` in the UI. |
| <a id="vulnerabilityreporttype"></a>`reportType` | [`VulnerabilityReportType`](#vulnerabilityreporttype) | Type of the security report that found the vulnerability (SAST, DEPENDENCY_SCANNING, CONTAINER_SCANNING, DAST, SECRET_DETECTION, COVERAGE_FUZZING, API_FUZZING, CLUSTER_IMAGE_SCANNING, GENERIC). `Scan Type` in the UI. |
| <a id="vulnerabilityresolvedat"></a>`resolvedAt` | [`Time`](#time) | Timestamp of when the vulnerability state was changed to resolved. |
| <a id="vulnerabilityresolvedby"></a>`resolvedBy` | [`UserCore`](#usercore) | The user that resolved the vulnerability. |
| <a id="vulnerabilityresolvedondefaultbranch"></a>`resolvedOnDefaultBranch` | [`Boolean!`](#boolean) | Indicates whether the vulnerability is fixed on the default branch or not. |
@ -14435,6 +14490,16 @@ Represents the location of a vulnerability found by a dependency security scan.
| <a id="vulnerabilitylocationdependencyscanningdependency"></a>`dependency` | [`VulnerableDependency`](#vulnerabledependency) | Dependency containing the vulnerability. |
| <a id="vulnerabilitylocationdependencyscanningfile"></a>`file` | [`String`](#string) | Path to the vulnerable file. |
### `VulnerabilityLocationGeneric`
Represents the location of a vulnerability found by a generic scanner.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="vulnerabilitylocationgenericdescription"></a>`description` | [`String`](#string) | Free-form description of where the vulnerability is located. |
### `VulnerabilityLocationSast`
Represents the location of a vulnerability found by a SAST scan.
@ -15626,6 +15691,15 @@ Status of a requirement based on last test report.
| <a id="requirementstatusfiltermissing"></a>`MISSING` | Requirements without any test report. |
| <a id="requirementstatusfilterpassed"></a>`PASSED` | |
### `RunnerMembershipFilter`
Values for filtering runners in namespaces.
| Value | Description |
| ----- | ----------- |
| <a id="runnermembershipfilterdescendants"></a>`DESCENDANTS` | Include runners that have either a direct relationship or a relationship with descendants. These can be project runners or group runners (in the case where group is queried). |
| <a id="runnermembershipfilterdirect"></a>`DIRECT` | Include runners that have a direct relationship. |
### `SastUiComponentSize`
Size of UI component in SAST configuration page.
@ -15872,6 +15946,20 @@ Possible states of a user.
| <a id="visibilityscopesenumprivate"></a>`private` | The snippet is visible only to the snippet creator. |
| <a id="visibilityscopesenumpublic"></a>`public` | The snippet can be accessed without any authentication. |
### `VulnerabilityConfidence`
Confidence that a given vulnerability is present in the codebase.
| Value | Description |
| ----- | ----------- |
| <a id="vulnerabilityconfidenceconfirmed"></a>`CONFIRMED` | |
| <a id="vulnerabilityconfidenceexperimental"></a>`EXPERIMENTAL` | |
| <a id="vulnerabilityconfidencehigh"></a>`HIGH` | |
| <a id="vulnerabilityconfidenceignore"></a>`IGNORE` | |
| <a id="vulnerabilityconfidencelow"></a>`LOW` | |
| <a id="vulnerabilityconfidencemedium"></a>`MEDIUM` | |
| <a id="vulnerabilityconfidenceunknown"></a>`UNKNOWN` | |
### `VulnerabilityDismissalReason`
The dismissal reason of the Vulnerability.
@ -15933,6 +16021,7 @@ The type of the security scan that found the vulnerability.
| <a id="vulnerabilityreporttypecoverage_fuzzing"></a>`COVERAGE_FUZZING` | |
| <a id="vulnerabilityreporttypedast"></a>`DAST` | |
| <a id="vulnerabilityreporttypedependency_scanning"></a>`DEPENDENCY_SCANNING` | |
| <a id="vulnerabilityreporttypegeneric"></a>`GENERIC` | |
| <a id="vulnerabilityreporttypesast"></a>`SAST` | |
| <a id="vulnerabilityreporttypesecret_detection"></a>`SECRET_DETECTION` | |
@ -16573,6 +16662,7 @@ One of:
- [`VulnerabilityLocationCoverageFuzzing`](#vulnerabilitylocationcoveragefuzzing)
- [`VulnerabilityLocationDast`](#vulnerabilitylocationdast)
- [`VulnerabilityLocationDependencyScanning`](#vulnerabilitylocationdependencyscanning)
- [`VulnerabilityLocationGeneric`](#vulnerabilitylocationgeneric)
- [`VulnerabilityLocationSast`](#vulnerabilitylocationsast)
- [`VulnerabilityLocationSecretDetection`](#vulnerabilitylocationsecretdetection)
@ -17351,3 +17441,14 @@ A time-frame defined as a closed inclusive range of two dates.
| <a id="updatediffimagepositioninputwidth"></a>`width` | [`Int`](#int) | Total width of the image. |
| <a id="updatediffimagepositioninputx"></a>`x` | [`Int`](#int) | X position of the note. |
| <a id="updatediffimagepositioninputy"></a>`y` | [`Int`](#int) | Y position of the note. |
### `VulnerabilityIdentifierInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="vulnerabilityidentifierinputexternalid"></a>`externalId` | [`String`](#string) | External ID of the vulnerability identifier. |
| <a id="vulnerabilityidentifierinputexternaltype"></a>`externalType` | [`String`](#string) | External type of the vulnerability identifier. |
| <a id="vulnerabilityidentifierinputname"></a>`name` | [`String!`](#string) | Name of the vulnerability identifier. |
| <a id="vulnerabilityidentifierinputurl"></a>`url` | [`String!`](#string) | URL of the vulnerability identifier. |

View file

@ -1049,11 +1049,7 @@ When an API site type is selected, a [host override](#host-override) is used to
#### Site profile validation
> - Site profile validation [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/233020) in GitLab 13.8.
> - Meta tag validation [enabled on GitLab.com](https://gitlab.com/groups/gitlab-org/-/epics/6460) in GitLab 14.2 and is ready for production use.
> - Meta tag validation [enabled with `dast_meta_tag_validation flag` flag](https://gitlab.com/gitlab-org/gitlab/-/issues/337711) for self-managed GitLab in GitLab 14.2 and is ready for production use.
FLAG:
On self-managed GitLab, by default this feature is available. To hide the feature, ask an administrator to [disable the `dast_meta_tag_validation` flag](../../../administration/feature_flags.md). On GitLab.com, this feature is available but can be configured by GitLab.com administrators only.
> - Meta tag validation [introduced](https://gitlab.com/groups/gitlab-org/-/epics/6460) in GitLab 14.2.
Site profile validation reduces the risk of running an active scan against the wrong website. A site
must be validated before an active scan can run against it. The site validation methods are as

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View file

@ -194,14 +194,19 @@ merge request would introduce one of the following security issues:
When the Vulnerability-Check merge request rule is enabled, additional merge request approval
is required when the latest security report in a merge request:
- Contains a vulnerability of `high`, `critical`, or `unknown` severity that is not present in the
- Contains vulnerabilities that are not present in the
target branch. Note that approval is still required for dismissed vulnerabilities.
- Contains vulnerabilities with severity levels (for example, `high`, `critical`, or `unknown`)
matching the rule's severity levels.
- Contains a vulnerability count higher than the rule allows.
- Is not generated during pipeline execution.
An approval is optional when the security report:
- Contains no new vulnerabilities when compared to the target branch.
- Contains only new vulnerabilities of `low` or `medium` severity.
- Contains only vulnerabilities with severity levels (for example, `low`, `medium`) **NOT** matching
the rule's severity levels.
- Contains a vulnerability count equal to or less than what the rule allows.
When the License-Check merge request rule is enabled, additional approval is required if a merge
request contains a denied license. For more details, see [Enabling license approvals within a project](../compliance/license_compliance/index.md#enabling-license-approvals-within-a-project).
@ -219,16 +224,19 @@ Follow these steps to enable `Vulnerability-Check`:
1. Go to your project and select **Settings > General**.
1. Expand **Merge request approvals**.
1. Select **Enable** or **Edit**.
1. Add or change the **Rule name** to `Vulnerability-Check` (case sensitive).
1. Set the **No. of approvals required** to greater than zero.
1. Set the **Security scanners** that the rule applies to.
1. Select the **Target branch**.
1. Set the **Vulnerabilities allowed** to the number of vulnerabilities allowed before the rule is
triggered.
1. Set the **Severity levels** to the severity levels that the rule applies to.
1. Set the **Approvals required** to the number of approvals that the rule requires.
1. Select the users or groups to provide approval.
1. Select **Add approval rule**.
Once this group is added to your project, the approval rule is enabled for all merge requests.
Any code changes cause the approvals required to reset.
![Vulnerability Check Approver Rule](img/vulnerability-check_v13_4.png)
![Vulnerability Check Approver Rule](img/vulnerability-check_v14_2.png)
## Using private Maven repositories

View file

@ -6,9 +6,53 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Infrastructure management **(FREE)**
GitLab provides you with great solutions to help you manage your
infrastructure:
With the rise of DevOps and SRE approaches, infrastructure management becomes codified,
automatable, and software development best practices gain their place around infrastructure
management too. On one hand, the daily tasks of classical operations people changed
and are more similar to traditional software development. On the other hand, software engineers
are more likely to control their whole DevOps lifecycle, including deployments and delivery.
- [Infrastructure as Code and GitOps](iac/index.md)
- [Kubernetes clusters](../project/clusters/index.md)
- [Runbooks](../project/clusters/runbooks/index.md)
GitLab offers various features to speed up and simplify your infrastructure management practices.
## Generic infrastructure management
GitLab has deep integrations with Terraform to run your infrastructure as code pipelines
and support your processes. Terraform is considered the standard in cloud infrastructure provisioning.
The various GitLab integrations help you:
- Get started quickly without any setup.
- Collaborate around infrastructure changes in merge requests the same as you might
with code changes.
- Scale using a module registry.
Read more about the [Infrastructure as Code features](iac/index.md), including:
- [The GitLab Managed Terraform State](terraform_state.md).
- [The Terraform MR widget](mr_integration.md).
- [The Terraform module registry](../packages/terraform_module_registry/index.md).
## Integrated Kubernetes management
GitLab has special integrations with Kubernetes to help you deploy, manage and troubleshoot
third-party or custom applications in Kubernetes clusters. Auto DevOps provides a full
DevSecOps pipeline by default targeted at Kubernetes based deployments. To support
all the GitLab features, GitLab offers a cluster management project for easy onboarding.
The deploy boards provide quick insights into your cluster, including pod logs tailing.
The recommended approach to connect to a cluster is using [the GitLab Kubernetes Agent](../clusters/agent/index.md).
Read more about [the Kubernetes cluster support and integrations](../project/clusters/index.md), including:
- Certificate-based integration for [projects](../project/clusters/index.md),
[groups](../group/clusters/index.md), or [instances](../instance/clusters/index.md).
- [Agent-based integration](../clusters/agent/index.md). **(PREMIUM)**
- The [Kubernetes Agent Server](../../administration/clusters/kas.md) is [available on GitLab.com](../clusters/agent/index.md#set-up-the-kubernetes-agent-server)
at `wss://kas.gitlab.com`. **(PREMIUM)**
- [Agent-based access from GitLab CI/CD](../clusters/agent/ci_cd_tunnel.md).
## Runbooks in GitLab
Runbooks are a collection of documented procedures that explain how to carry out a task,
such as starting, stopping, debugging, or troubleshooting a system.
Read more about [how executable runbooks work in GitLab](../project/clusters/runbooks/index.md).

View file

@ -29472,7 +29472,7 @@ msgstr ""
msgid "Security report is out of date. Run %{newPipelineLinkStart}a new pipeline%{newPipelineLinkEnd} for the target branch (%{targetBranchName})"
msgstr ""
msgid "SecurityApprovals|A merge request approval is required when a security report contains a new vulnerability of high, critical, or unknown severity."
msgid "SecurityApprovals|A merge request approval is required when a security report contains a new vulnerability."
msgstr ""
msgid "SecurityApprovals|A merge request approval is required when test coverage declines."
@ -29508,7 +29508,7 @@ msgstr ""
msgid "SecurityApprovals|Requires approval for decreases in test coverage. %{linkStart}More information%{linkEnd}"
msgstr ""
msgid "SecurityApprovals|Requires approval for vulnerabilities of Critical, High, or Unknown severity. %{linkStart}Learn more.%{linkEnd}"
msgid "SecurityApprovals|Requires approval for vulnerabilities. %{linkStart}Learn more.%{linkEnd}"
msgstr ""
msgid "SecurityApprovals|Test coverage must be enabled. %{linkStart}Learn more%{linkEnd}."
@ -37184,9 +37184,6 @@ msgstr ""
msgid "Vulnerability|Request/Response"
msgstr ""
msgid "Vulnerability|Scanner"
msgstr ""
msgid "Vulnerability|Scanner Provider"
msgstr ""
@ -37199,6 +37196,9 @@ msgstr ""
msgid "Vulnerability|The unmodified response is the original response that had no mutations done to the request"
msgstr ""
msgid "Vulnerability|Tool"
msgstr ""
msgid "Vulnerability|Unmodified Response"
msgstr ""

View file

@ -18,12 +18,14 @@ module QA
:iid,
:assignee_ids,
:labels,
:title
:title,
:description
def initialize
@assignee_ids = []
@labels = []
@title = "Issue title #{SecureRandom.hex(8)}"
@description = "Issue description #{SecureRandom.hex(8)}"
end
def fabricate!
@ -34,7 +36,7 @@ module QA
Page::Project::Issue::New.perform do |new_page|
new_page.fill_title(@title)
new_page.choose_template(@template) if @template
new_page.fill_description(@description) if @description
new_page.fill_description(@description) if @description && !@template
new_page.choose_milestone(@milestone) if @milestone
new_page.create_new_issue
end
@ -64,6 +66,7 @@ module QA
}.tap do |hash|
hash[:milestone_id] = @milestone.id if @milestone
hash[:weight] = @weight if @weight
hash[:description] = @description if @description
end
end

View file

@ -8,6 +8,10 @@ module QA
extend self
extend Support::Api
RETRY_MAX_ITERATION = 10
RETRY_SLEEP_INTERVAL = 12
INSERT_RECALL_THRESHOLD = RETRY_MAX_ITERATION * RETRY_SLEEP_INTERVAL
ElasticSearchServerError = Class.new(RuntimeError)
def assert_elasticsearch_responding
@ -85,7 +89,7 @@ module QA
private
def find_target_in_scope(scope, search_term)
QA::Support::Retrier.retry_until(max_attempts: 10, sleep_interval: 10, raise_on_failure: true, retry_on_exception: true) do
QA::Support::Retrier.retry_until(max_attempts: RETRY_MAX_ITERATION, sleep_interval: RETRY_SLEEP_INTERVAL, raise_on_failure: true, retry_on_exception: true) do
result = search(scope, search_term)
result && result.any? { |record| yield record }
end

View file

@ -133,7 +133,7 @@ module QA
it 'imports large Github repo via api' do
start = Time.now
imported_project # import the project
Runtime::Logger.info("Importing project '#{imported_project.full_path}'") # import the project and log path
fetch_github_objects # fetch all objects right after import has started
import_status = lambda do
@ -221,32 +221,39 @@ module QA
# @return [void]
def verify_mrs_or_issues(type)
msg = ->(title) { "expected #{type} with title '#{title}' to have" }
expected = type == 'mr' ? mrs : gl_issues
actual = type == 'mr' ? gh_prs : gh_issues
# Compare length to have easy to read overview how many objects are missing
expect(expected.length).to(
eq(actual.length),
"Expected to contain same amount of #{type}s. Expected: #{expected.length}, actual: #{actual.length}"
)
expected = type == 'mr' ? mrs : gl_issues
actual = type == 'mr' ? gh_prs : gh_issues
count_msg = "Expected to contain same amount of #{type}s. Gitlab: #{expected.length}, Github: #{actual.length}"
expect(expected.length).to eq(actual.length), count_msg
logger.debug("= Comparing #{type}s =")
actual.each do |title, actual_item|
print "." # indicate that it is still going but don't spam the output with newlines
expected_item = expected[title]
# Print title in the error message to see which object is missing
expect(expected_item).to be_truthy, "#{msg.call(title)} been imported"
next unless expected_item
expect(expected_item[:body]).to(
include(actual_item[:body]),
"#{msg.call(title)} same description. diff:\n#{differ.diff(expected_item[:body], actual_item[:body])}"
)
expect(expected_item[:comments].length).to(
eq(actual_item[:comments].length),
"#{msg.call(title)} same amount of comments"
)
expect(expected_item[:comments]).to match_array(actual_item[:comments])
# Print difference in the description
expected_body = expected_item[:body]
actual_body = actual_item[:body]
body_msg = <<~MSG
#{msg.call(title)} same description. diff:\n#{differ.diff(expected_item[:body], actual_item[:body])}
MSG
expect(expected_body).to include(actual_body), body_msg
# Print amount difference first
expected_comments = expected_item[:comments]
actual_comments = actual_item[:comments]
comment_count_msg = <<~MSG
#{msg.call(title)} same amount of comments. Gitlab: #{expected_comments.length}, Github: #{actual_comments.length}
MSG
expect(expected_comments.length).to eq(actual_comments.length), comment_count_msg
expect(expected_comments).to match_array(actual_comments)
end
puts # print newline after last print to make output pretty
end

View file

@ -18,6 +18,13 @@ RSpec.describe Ci::RunnersFinder do
end
end
context 'with nil group' do
it 'returns all runners' do
expect(Ci::Runner).to receive(:with_tags).and_call_original
expect(described_class.new(current_user: admin, params: { group: nil }).execute).to match_array [runner1, runner2]
end
end
context 'with preload param set to :tag_name true' do
it 'requests tags' do
expect(Ci::Runner).to receive(:with_tags).and_call_original
@ -158,6 +165,7 @@ RSpec.describe Ci::RunnersFinder do
let_it_be(:project_4) { create(:project, group: sub_group_2) }
let_it_be(:project_5) { create(:project, group: sub_group_3) }
let_it_be(:project_6) { create(:project, group: sub_group_4) }
let_it_be(:runner_instance) { create(:ci_runner, :instance, contacted_at: 13.minutes.ago) }
let_it_be(:runner_group) { create(:ci_runner, :group, contacted_at: 12.minutes.ago) }
let_it_be(:runner_sub_group_1) { create(:ci_runner, :group, active: false, contacted_at: 11.minutes.ago) }
let_it_be(:runner_sub_group_2) { create(:ci_runner, :group, contacted_at: 10.minutes.ago) }
@ -171,7 +179,10 @@ RSpec.describe Ci::RunnersFinder do
let_it_be(:runner_project_6) { create(:ci_runner, :project, contacted_at: 2.minutes.ago, projects: [project_5])}
let_it_be(:runner_project_7) { create(:ci_runner, :project, contacted_at: 1.minute.ago, projects: [project_6])}
let(:params) { {} }
let(:target_group) { nil }
let(:membership) { nil }
let(:extra_params) { {} }
let(:params) { { group: target_group, membership: membership }.merge(extra_params).reject { |_, v| v.nil? } }
before do
group.runners << runner_group
@ -182,14 +193,9 @@ RSpec.describe Ci::RunnersFinder do
end
describe '#execute' do
subject { described_class.new(current_user: user, group: group, params: params).execute }
subject { described_class.new(current_user: user, params: params).execute }
context 'with user as group owner' do
before do
group.add_owner(user)
end
context 'passing no params' do
shared_examples 'membership equal to :descendants' do
it 'returns all descendant runners' do
expect(subject).to eq([runner_project_7, runner_project_6, runner_project_5,
runner_project_4, runner_project_3, runner_project_2,
@ -198,8 +204,51 @@ RSpec.describe Ci::RunnersFinder do
end
end
context 'with user as group owner' do
before do
group.add_owner(user)
end
context 'with :group as target group' do
let(:target_group) { group }
context 'passing no params' do
it_behaves_like 'membership equal to :descendants'
end
context 'with :descendants membership' do
let(:membership) { :descendants }
it_behaves_like 'membership equal to :descendants'
end
context 'with :direct membership' do
let(:membership) { :direct }
it 'returns runners belonging to group' do
expect(subject).to eq([runner_group])
end
end
context 'with unknown membership' do
let(:membership) { :unsupported }
it 'raises an error' do
expect { subject }.to raise_error(ArgumentError, 'Invalid membership filter')
end
end
context 'with nil group' do
let(:target_group) { nil }
it 'returns no runners' do
# Query should run against all runners, however since user is not admin, query returns no results
expect(subject).to eq([])
end
end
context 'with sort param' do
let(:params) { { sort: 'contacted_asc' } }
let(:extra_params) { { sort: 'contacted_asc' } }
it 'sorts by specified attribute' do
expect(subject).to eq([runner_group, runner_sub_group_1, runner_sub_group_2,
@ -211,7 +260,7 @@ RSpec.describe Ci::RunnersFinder do
context 'filtering' do
context 'by search term' do
let(:params) { { search: 'runner_project_search' } }
let(:extra_params) { { search: 'runner_project_search' } }
it 'returns correct runner' do
expect(subject).to eq([runner_project_3])
@ -219,7 +268,7 @@ RSpec.describe Ci::RunnersFinder do
end
context 'by status' do
let(:params) { { status_status: 'paused' } }
let(:extra_params) { { status_status: 'paused' } }
it 'returns correct runner' do
expect(subject).to eq([runner_sub_group_1])
@ -227,7 +276,7 @@ RSpec.describe Ci::RunnersFinder do
end
context 'by tag_name' do
let(:params) { { tag_name: %w[runner_tag] } }
let(:extra_params) { { tag_name: %w[runner_tag] } }
it 'returns correct runner' do
expect(subject).to eq([runner_project_5])
@ -235,7 +284,7 @@ RSpec.describe Ci::RunnersFinder do
end
context 'by runner type' do
let(:params) { { type_type: 'project_type' } }
let(:extra_params) { { type_type: 'project_type' } }
it 'returns correct runners' do
expect(subject).to eq([runner_project_7, runner_project_6,
@ -245,6 +294,7 @@ RSpec.describe Ci::RunnersFinder do
end
end
end
end
context 'when user is not group owner' do
where(:user_permission) do
@ -278,7 +328,7 @@ RSpec.describe Ci::RunnersFinder do
end
describe '#sort_key' do
subject { described_class.new(current_user: user, group: group, params: params).sort_key }
subject { described_class.new(current_user: user, params: params.merge(group: group)).sort_key }
context 'without params' do
it 'returns created_at_desc' do
@ -287,7 +337,7 @@ RSpec.describe Ci::RunnersFinder do
end
context 'with params' do
let(:params) { { sort: 'contacted_asc' } }
let(:extra_params) { { sort: 'contacted_asc' } }
it 'returns contacted_asc' do
expect(subject).to eq('contacted_asc')

View file

@ -0,0 +1,94 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Resolvers::Ci::GroupRunnersResolver do
include GraphqlHelpers
describe '#resolve' do
subject { resolve(described_class, obj: obj, ctx: { current_user: user }, args: args) }
include_context 'runners resolver setup'
let(:obj) { group }
let(:args) { {} }
# First, we can do a couple of basic real tests to verify common cases. That ensures that the code works.
context 'when user cannot see runners' do
it 'returns no runners' do
expect(subject.items.to_a).to eq([])
end
end
context 'with user as group owner' do
before do
group.add_owner(user)
end
it 'returns all the runners' do
expect(subject.items.to_a).to contain_exactly(inactive_project_runner, offline_project_runner, group_runner, subgroup_runner)
end
context 'with membership direct' do
let(:args) { { membership: :direct } }
it 'returns only direct runners' do
expect(subject.items.to_a).to contain_exactly(group_runner)
end
end
end
# Then, we can check specific edge cases for this resolver
context 'with obj set to nil' do
let(:obj) { nil }
it 'raises an error' do
expect { subject }.to raise_error('Expected group missing')
end
end
context 'with obj not set to group' do
let(:obj) { build(:project) }
it 'raises an error' do
expect { subject }.to raise_error('Expected group missing')
end
end
# Here we have a mocked part. We assume that all possible edge cases are covered in RunnersFinder spec. So we don't need to test them twice.
# Only thing we can do is to verify that args from the resolver is correctly transformed to params of the Finder and we return the Finder's result back.
describe 'Allowed query arguments' do
let(:finder) { instance_double(::Ci::RunnersFinder) }
let(:args) do
{
status: 'active',
type: :group_type,
tag_list: ['active_runner'],
search: 'abc',
sort: :contacted_asc,
membership: :descendants
}
end
let(:expected_params) do
{
status_status: 'active',
type_type: :group_type,
tag_name: ['active_runner'],
preload: { tag_name: nil },
search: 'abc',
sort: 'contacted_asc',
membership: :descendants,
group: group
}
end
it 'calls RunnersFinder with expected arguments' do
allow(::Ci::RunnersFinder).to receive(:new).with(current_user: user, params: expected_params).once.and_return(finder)
allow(finder).to receive(:execute).once.and_return([:execute_return_value])
expect(subject.items.to_a).to eq([:execute_return_value])
end
end
end
end

View file

@ -5,185 +5,70 @@ require 'spec_helper'
RSpec.describe Resolvers::Ci::RunnersResolver do
include GraphqlHelpers
let_it_be(:user) { create_default(:user, :admin) }
let_it_be(:group) { create(:group, :public) }
let_it_be(:project) { create(:project, :repository, :public) }
let_it_be(:inactive_project_runner) do
create(:ci_runner, :project, projects: [project], description: 'inactive project runner', token: 'abcdef', active: false, contacted_at: 1.minute.ago, tag_list: %w(project_runner))
end
let_it_be(:offline_project_runner) do
create(:ci_runner, :project, projects: [project], description: 'offline project runner', token: 'defghi', contacted_at: 1.day.ago, tag_list: %w(project_runner active_runner))
end
let_it_be(:group_runner) { create(:ci_runner, :group, groups: [group], token: 'mnopqr', description: 'group runner', contacted_at: 1.second.ago) }
let_it_be(:instance_runner) { create(:ci_runner, :instance, description: 'shared runner', token: 'stuvxz', contacted_at: 2.minutes.ago, tag_list: %w(instance_runner active_runner)) }
describe '#resolve' do
subject { resolve(described_class, ctx: { current_user: user }, args: args).items.to_a }
let(:obj) { nil }
let(:args) { {} }
let(:args) do
{}
end
subject { resolve(described_class, obj: obj, ctx: { current_user: user }, args: args) }
context 'when the user cannot see runners' do
let(:user) { create(:user) }
include_context 'runners resolver setup'
# First, we can do a couple of basic real tests to verify common cases. That ensures that the code works.
context 'when user cannot see runners' do
let(:user) { build(:user) }
it 'returns no runners' do
is_expected.to be_empty
expect(subject.items.to_a).to eq([])
end
end
context 'without sort' do
context 'when user can see runners' do
let(:obj) { nil }
it 'returns all the runners' do
is_expected.to contain_exactly(inactive_project_runner, offline_project_runner, group_runner, instance_runner)
expect(subject.items.to_a).to contain_exactly(inactive_project_runner, offline_project_runner, group_runner, subgroup_runner, instance_runner)
end
end
context 'with a sort argument' do
context "set to :contacted_asc" do
# Then, we can check specific edge cases for this resolver
context 'with obj not set to nil' do
let(:obj) { build(:project) }
it 'raises an error' do
expect { subject }.to raise_error(a_string_including('Unexpected parent type'))
end
end
# Here we have a mocked part. We assume that all possible edge cases are covered in RunnersFinder spec. So we don't need to test them twice.
# Only thing we can do is to verify that args from the resolver is correctly transformed to params of the Finder and we return the Finder's result back.
describe 'Allowed query arguments' do
let(:finder) { instance_double(::Ci::RunnersFinder) }
let(:args) do
{ sort: :contacted_asc }
{
status: 'active',
type: :instance_type,
tag_list: ['active_runner'],
search: 'abc',
sort: :contacted_asc
}
end
it { is_expected.to eq([offline_project_runner, instance_runner, inactive_project_runner, group_runner]) }
let(:expected_params) do
{
status_status: 'active',
type_type: :instance_type,
tag_name: ['active_runner'],
preload: { tag_name: nil },
search: 'abc',
sort: 'contacted_asc'
}
end
context "set to :contacted_desc" do
let(:args) do
{ sort: :contacted_desc }
end
it 'calls RunnersFinder with expected arguments' do
allow(::Ci::RunnersFinder).to receive(:new).with(current_user: user, params: expected_params).once.and_return(finder)
allow(finder).to receive(:execute).once.and_return([:execute_return_value])
it { is_expected.to eq([offline_project_runner, instance_runner, inactive_project_runner, group_runner].reverse) }
end
context "set to :created_at_desc" do
let(:args) do
{ sort: :created_at_desc }
end
it { is_expected.to eq([instance_runner, group_runner, offline_project_runner, inactive_project_runner]) }
end
context "set to :created_at_asc" do
let(:args) do
{ sort: :created_at_asc }
end
it { is_expected.to eq([instance_runner, group_runner, offline_project_runner, inactive_project_runner].reverse) }
end
end
context 'when type is filtered' do
let(:args) do
{ type: runner_type.to_s }
end
context 'to instance runners' do
let(:runner_type) { :instance_type }
it 'returns the instance runner' do
is_expected.to contain_exactly(instance_runner)
end
end
context 'to group runners' do
let(:runner_type) { :group_type }
it 'returns the group runner' do
is_expected.to contain_exactly(group_runner)
end
end
context 'to project runners' do
let(:runner_type) { :project_type }
it 'returns the project runner' do
is_expected.to contain_exactly(inactive_project_runner, offline_project_runner)
end
end
end
context 'when status is filtered' do
let(:args) do
{ status: runner_status.to_s }
end
context 'to active runners' do
let(:runner_status) { :active }
it 'returns the instance and group runners' do
is_expected.to contain_exactly(offline_project_runner, group_runner, instance_runner)
end
end
context 'to offline runners' do
let(:runner_status) { :offline }
it 'returns the offline project runner' do
is_expected.to contain_exactly(offline_project_runner)
end
end
end
context 'when tag list is filtered' do
let(:args) do
{ tag_list: tag_list }
end
context 'with "project_runner" tag' do
let(:tag_list) { ['project_runner'] }
it 'returns the project_runner runners' do
is_expected.to contain_exactly(offline_project_runner, inactive_project_runner)
end
end
context 'with "project_runner" and "active_runner" tags as comma-separated string' do
let(:tag_list) { ['project_runner,active_runner'] }
it 'returns the offline_project_runner runner' do
is_expected.to contain_exactly(offline_project_runner)
end
end
context 'with "active_runner" and "instance_runner" tags as array' do
let(:tag_list) { %w[instance_runner active_runner] }
it 'returns the offline_project_runner runner' do
is_expected.to contain_exactly(instance_runner)
end
end
end
context 'when text is filtered' do
let(:args) do
{ search: search_term }
end
context 'to "project"' do
let(:search_term) { 'project' }
it 'returns both project runners' do
is_expected.to contain_exactly(inactive_project_runner, offline_project_runner)
end
end
context 'to "group"' do
let(:search_term) { 'group' }
it 'returns group runner' do
is_expected.to contain_exactly(group_runner)
end
end
context 'to "defghi"' do
let(:search_term) { 'defghi' }
it 'returns runners containing term in token' do
is_expected.to contain_exactly(offline_project_runner)
end
expect(subject.items.to_a).to eq([:execute_return_value])
end
end
end

View file

@ -42,7 +42,6 @@ RSpec.describe Ci::PipelineEditorHelper do
"ci-config-path": project.ci_config_path_or_default,
"ci-examples-help-page-path" => help_page_path('ci/examples/index'),
"ci-help-page-path" => help_page_path('ci/index'),
"commit-sha" => project.commit.sha,
"default-branch" => project.default_branch_or_main,
"empty-state-illustration-path" => 'foo',
"initial-branch-name" => nil,
@ -69,7 +68,6 @@ RSpec.describe Ci::PipelineEditorHelper do
"ci-config-path": project.ci_config_path_or_default,
"ci-examples-help-page-path" => help_page_path('ci/examples/index'),
"ci-help-page-path" => help_page_path('ci/index'),
"commit-sha" => '',
"default-branch" => project.default_branch_or_main,
"empty-state-illustration-path" => 'foo',
"initial-branch-name" => nil,
@ -97,10 +95,7 @@ RSpec.describe Ci::PipelineEditorHelper do
end
it 'returns correct values' do
latest_feature_sha = project.repository.commit('feature').sha
expect(pipeline_editor_data['initial-branch-name']).to eq('feature')
expect(pipeline_editor_data['commit-sha']).to eq(latest_feature_sha)
end
end
end

View file

@ -0,0 +1,103 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe BackfillStageEventHash, schema: 20210730103808 do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:labels) { table(:labels) }
let(:group_stages) { table(:analytics_cycle_analytics_group_stages) }
let(:project_stages) { table(:analytics_cycle_analytics_project_stages) }
let(:group_value_streams) { table(:analytics_cycle_analytics_group_value_streams) }
let(:project_value_streams) { table(:analytics_cycle_analytics_project_value_streams) }
let(:stage_event_hashes) { table(:analytics_cycle_analytics_stage_event_hashes) }
let(:issue_created) { 1 }
let(:issue_closed) { 3 }
let(:issue_label_removed) { 9 }
let(:unknown_stage_event) { -1 }
let(:namespace) { namespaces.create!(name: 'ns', path: 'ns', type: 'Group') }
let(:project) { projects.create!(name: 'project', path: 'project', namespace_id: namespace.id) }
let(:group_label) { labels.create!(title: 'label', type: 'GroupLabel', group_id: namespace.id) }
let(:group_value_stream) { group_value_streams.create!(name: 'group vs', group_id: namespace.id) }
let(:project_value_stream) { project_value_streams.create!(name: 'project vs', project_id: project.id) }
let(:group_stage_1) do
group_stages.create!(
name: 'stage 1',
group_id: namespace.id,
start_event_identifier: issue_created,
end_event_identifier: issue_closed,
group_value_stream_id: group_value_stream.id
)
end
let(:group_stage_2) do
group_stages.create!(
name: 'stage 2',
group_id: namespace.id,
start_event_identifier: issue_created,
end_event_identifier: issue_label_removed,
end_event_label_id: group_label.id,
group_value_stream_id: group_value_stream.id
)
end
let(:project_stage_1) do
project_stages.create!(
name: 'stage 1',
project_id: project.id,
start_event_identifier: issue_created,
end_event_identifier: issue_closed,
project_value_stream_id: project_value_stream.id
)
end
let(:invalid_group_stage) do
group_stages.create!(
name: 'stage 3',
group_id: namespace.id,
start_event_identifier: issue_created,
end_event_identifier: unknown_stage_event,
group_value_stream_id: group_value_stream.id
)
end
describe '#up' do
it 'populates stage_event_hash_id column' do
group_stage_1
group_stage_2
project_stage_1
migrate!
group_stage_1.reload
group_stage_2.reload
project_stage_1.reload
expect(group_stage_1.stage_event_hash_id).not_to be_nil
expect(group_stage_2.stage_event_hash_id).not_to be_nil
expect(project_stage_1.stage_event_hash_id).not_to be_nil
expect(stage_event_hashes.count).to eq(2) # group_stage_1 and project_stage_1 has the same hash
end
it 'runs without problem without stages' do
expect { migrate! }.not_to raise_error
end
context 'when invalid event identifier is discovered' do
it 'removes the stage' do
group_stage_1
invalid_group_stage
expect { migrate! }.not_to change { group_stage_1 }
expect(group_stages.find_by_id(invalid_group_stage.id)).to eq(nil)
end
end
end
end

View file

@ -0,0 +1,22 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.shared_context 'runners resolver setup' do
let_it_be(:user) { create_default(:user, :admin) }
let_it_be(:group) { create(:group, :public) }
let_it_be(:subgroup) { create(:group, :public, parent: group) }
let_it_be(:project) { create(:project, :public, group: group) }
let_it_be(:inactive_project_runner) do
create(:ci_runner, :project, projects: [project], description: 'inactive project runner', token: 'abcdef', active: false, contacted_at: 1.minute.ago, tag_list: %w(project_runner))
end
let_it_be(:offline_project_runner) do
create(:ci_runner, :project, projects: [project], description: 'offline project runner', token: 'defghi', contacted_at: 1.day.ago, tag_list: %w(project_runner active_runner))
end
let_it_be(:group_runner) { create(:ci_runner, :group, groups: [group], token: 'mnopqr', description: 'group runner', contacted_at: 2.seconds.ago) }
let_it_be(:subgroup_runner) { create(:ci_runner, :group, groups: [subgroup], token: 'mnopqr', description: 'subgroup runner', contacted_at: 1.second.ago) }
let_it_be(:instance_runner) { create(:ci_runner, :instance, description: 'shared runner', token: 'stuvxz', contacted_at: 2.minutes.ago, tag_list: %w(instance_runner active_runner)) }
end