Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-08-01 15:10:12 +00:00
parent 5e77663f2e
commit 4212e9c2d4
83 changed files with 769 additions and 925 deletions

View File

@ -601,7 +601,7 @@ gem 'ed25519', '~> 1.3.0'
# Error Tracking OpenAPI client
# See https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/development/rake_tasks.md#update-openapi-client-for-error-tracking-feature
gem 'error_tracking_open_api', path: 'vendor/gems/error_tracking_open_api'
gem 'error_tracking_open_api', path: 'gems/error_tracking_open_api'
# Vulnerability advisories
gem 'cvss-suite', '~> 3.0.1', require: 'cvss_suite'

View File

@ -12,6 +12,12 @@ PATH
addressable (~> 2.8)
json (~> 2.6.3)
PATH
remote: gems/error_tracking_open_api
specs:
error_tracking_open_api (1.0.0)
typhoeus (~> 1.0, >= 1.0.1)
PATH
remote: gems/gitlab-rspec
specs:
@ -84,12 +90,6 @@ PATH
devise (~> 4.0)
devise-two-factor (~> 4.0)
PATH
remote: vendor/gems/error_tracking_open_api
specs:
error_tracking_open_api (1.0.0)
typhoeus (~> 1.0, >= 1.0.1)
PATH
remote: vendor/gems/mail-smtp_pool
specs:

View File

@ -1098,6 +1098,73 @@
}
]
},
"parallel": {
"description": "Splits up a single job into multiple that run in parallel. Provides `CI_NODE_INDEX` and `CI_NODE_TOTAL` environment variables to the jobs.",
"oneOf": [
{
"type": "integer",
"description": "Creates N instances of the job that run in parallel.",
"default": 0,
"minimum": 2,
"maximum": 200
},
{
"type": "object",
"properties": {
"matrix": {
"type": "array",
"description": "Defines different variables for jobs that are running in parallel.",
"items": {
"type": "object",
"description": "Defines the variables for a specific job.",
"additionalProperties": {
"type": [
"string",
"number",
"array"
]
}
},
"maxItems": 200
}
},
"additionalProperties": false,
"required": [
"matrix"
]
}
]
},
"parallel_matrix": {
"description": "Use the `needs:parallel:matrix` keyword to specify parallelized jobs needed to be completed for the job to run. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#needsparallelmatrix)",
"oneOf": [
{
"type": "object",
"properties": {
"matrix": {
"type": "array",
"description": "Defines different variables for jobs that are running in parallel.",
"items": {
"type": "object",
"description": "Defines the variables for a specific job.",
"additionalProperties": {
"type": [
"string",
"number",
"array"
]
}
},
"maxItems": 200
}
},
"additionalProperties": false,
"required": [
"matrix"
]
}
]
},
"when": {
"markdownDescription": "Describes the conditions for when to run the job. Defaults to 'on_success'. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#when).",
"default": "on_success",
@ -1518,6 +1585,9 @@
},
"optional": {
"type": "boolean"
},
"parallel": {
"$ref": "#/definitions/parallel_matrix"
}
},
"required": [
@ -1536,6 +1606,9 @@
},
"artifacts": {
"type": "boolean"
},
"parallel": {
"$ref": "#/definitions/parallel_matrix"
}
},
"required": [
@ -1558,6 +1631,9 @@
},
"artifacts": {
"type": "boolean"
},
"parallel": {
"$ref": "#/definitions/parallel_matrix"
}
},
"required": [
@ -1771,41 +1847,7 @@
"$ref": "#/definitions/retry"
},
"parallel": {
"description": "Parallel will split up a single job into several, and provide `CI_NODE_INDEX` and `CI_NODE_TOTAL` environment variables for the running jobs.",
"oneOf": [
{
"type": "integer",
"description": "Creates N instances of the same job that run in parallel.",
"default": 0,
"minimum": 2,
"maximum": 200
},
{
"type": "object",
"properties": {
"matrix": {
"type": "array",
"description": "Defines different variables for jobs that are running in parallel.",
"items": {
"type": "object",
"description": "Defines environment variables for specific job.",
"additionalProperties": {
"type": [
"string",
"number",
"array"
]
}
},
"maxItems": 200
}
},
"additionalProperties": false,
"required": [
"matrix"
]
}
]
"$ref": "#/definitions/parallel"
},
"interruptible": {
"$ref": "#/definitions/interruptible"

View File

@ -15,66 +15,6 @@ module Admin
def project_missing_pipeline_yaml?(project)
project.repository&.gitlab_ci_yml.blank?
end
def code_suggestions_description
link_start = code_suggestions_link_start(code_suggestions_docs_url)
# rubocop:disable Layout/LineLength
# rubocop:disable Style/FormatString
s_('CodeSuggestionsSM|Enable Code Suggestions for users of this instance. %{link_start}What are Code Suggestions?%{link_end}')
.html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
# rubocop:enable Style/FormatString
# rubocop:enable Layout/LineLength
end
def code_suggestions_token_explanation
link_start = code_suggestions_link_start(code_suggestions_pat_docs_url)
# rubocop:disable Layout/LineLength
# rubocop:disable Style/FormatString
s_('CodeSuggestionsSM|On GitLab.com, create a token. This token is required to use Code Suggestions on your self-managed instance. %{link_start}How do I create a token?%{link_end}')
.html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
# rubocop:enable Style/FormatString
# rubocop:enable Layout/LineLength
end
def code_suggestions_agreement
terms_link_start = code_suggestions_link_start(code_suggestions_agreement_url)
ai_docs_link_start = code_suggestions_link_start(code_suggestions_ai_docs_url)
# rubocop:disable Layout/LineLength
# rubocop:disable Style/FormatString
s_('CodeSuggestionsSM|By enabling this feature, you agree to the %{terms_link_start}GitLab Testing Agreement%{link_end} and acknowledge that GitLab will send data from the instance, including personal data, to our %{ai_docs_link_start}AI providers%{link_end} to provide this feature.')
.html_safe % { terms_link_start: terms_link_start, ai_docs_link_start: ai_docs_link_start, link_end: '</a>'.html_safe }
# rubocop:enable Style/FormatString
# rubocop:enable Layout/LineLength
end
private
# rubocop:disable Gitlab/DocUrl
# We want to link SaaS docs for flexibility for every URL related to Code Suggestions on Self Managed.
# We expect to update docs often during the Beta and we want to point user to the most up to date information.
def code_suggestions_docs_url
'https://docs.gitlab.com/ee/user/project/repository/code_suggestions.html'
end
def code_suggestions_agreement_url
'https://about.gitlab.com/handbook/legal/testing-agreement/'
end
def code_suggestions_ai_docs_url
'https://docs.gitlab.com/ee/user/ai_features.html#third-party-services'
end
def code_suggestions_pat_docs_url
'https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#create-a-personal-access-token'
end
# rubocop:enable Gitlab/DocUrl
def code_suggestions_link_start(url)
"<a href=\"#{url}\" target=\"_blank\" rel=\"noopener noreferrer\">".html_safe
end
end
end
end

View File

@ -218,7 +218,6 @@ module ApplicationSettingsHelper
:admin_mode,
:after_sign_out_path,
:after_sign_up_text,
:ai_access_token,
:akismet_api_key,
:akismet_enabled,
:allow_local_requests_from_hooks_and_services,
@ -310,7 +309,6 @@ module ApplicationSettingsHelper
:inactive_projects_delete_after_months,
:inactive_projects_min_size_mb,
:inactive_projects_send_warning_email_after_months,
:instance_level_code_suggestions_enabled,
:invisible_captcha_enabled,
:jira_connect_application_key,
:jira_connect_public_key_storage_enabled,

View File

@ -726,10 +726,6 @@ class ApplicationSetting < MainClusterwide::ApplicationRecord
allow_nil: false,
inclusion: { in: [true, false], message: N_('must be a boolean value') }
validates :ai_access_token,
presence: { message: N_("is required to enable Code Suggestions") },
if: :instance_level_code_suggestions_enabled
validates :package_registry_allow_anyone_to_pull_option,
inclusion: { in: [true, false], message: N_('must be a boolean value') }
@ -968,5 +964,4 @@ class ApplicationSetting < MainClusterwide::ApplicationRecord
end
end
ApplicationSetting.prepend(ApplicationSettingMaskedAttrs)
ApplicationSetting.prepend_mod_with('ApplicationSetting')

View File

@ -1,14 +0,0 @@
# frozen_string_literal: true
# Similar to MASK_PASSWORD mechanism we do for EE, see:
# https://gitlab.com/gitlab-org/gitlab/-/blob/463bb1f855d71fadef931bd50f1692ee04f211a8/ee/app/models/ee/application_setting.rb#L15
# but for non-EE attributes.
module ApplicationSettingMaskedAttrs
MASK = '*****'
def ai_access_token=(value)
return if value == MASK
super
end
end

View File

@ -1,53 +0,0 @@
# frozen_string_literal: true
# Searches a projects repository for a metrics dashboard and formats the output.
# Expects any custom dashboards will be located in `.gitlab/dashboards`
# Use Gitlab::Metrics::Dashboard::Finder to retrive dashboards.
module Metrics
module Dashboard
class CustomDashboardService < ::Metrics::Dashboard::BaseService
class << self
def valid_params?(params)
params[:dashboard_path].present?
end
def all_dashboard_paths(project)
project.repository.user_defined_metrics_dashboard_paths
.map do |filepath|
{
path: filepath,
display_name: name_for_path(filepath),
default: false,
system_dashboard: false,
out_of_the_box_dashboard: out_of_the_box_dashboard?
}
end
end
# Grabs the filepath after the base directory.
def name_for_path(filepath)
filepath.delete_prefix("#{Gitlab::Metrics::Dashboard::RepoDashboardFinder::DASHBOARD_ROOT}/")
end
end
private
# Searches the project repo for a custom-defined dashboard.
def get_raw_dashboard
yml = Gitlab::Metrics::Dashboard::RepoDashboardFinder.read_dashboard(project, dashboard_path)
load_yaml(yml)
end
def cache_key
"project_#{project.id}_metrics_dashboard_#{dashboard_path}"
end
def sequence
[
::Gitlab::Metrics::Dashboard::Stages::CustomDashboardMetricsInserter
] + super
end
end
end
end

View File

@ -1,127 +0,0 @@
# frozen_string_literal: true
# Updates the content of a specified dashboard in .yml file inside `.gitlab/dashboards`
module Metrics
module Dashboard
class UpdateDashboardService < ::BaseService
include Stepable
ALLOWED_FILE_TYPE = '.yml'
USER_DASHBOARDS_DIR = ::Gitlab::Metrics::Dashboard::RepoDashboardFinder::DASHBOARD_ROOT
steps :check_push_authorized,
:check_branch_name,
:check_file_type,
:update_file,
:create_merge_request
def execute
execute_steps
end
private
def check_push_authorized(result)
return error(_('You are not allowed to push into this branch. Create another branch or open a merge request.'), :forbidden) unless push_authorized?
success(result)
end
def check_branch_name(result)
return error(_('There was an error updating the dashboard, branch name is invalid.'), :bad_request) unless valid_branch_name?
return error(_('There was an error updating the dashboard, branch named: %{branch} already exists.') % { branch: params[:branch] }, :bad_request) unless new_or_default_branch?
success(result)
end
def check_file_type(result)
return error(_('The file name should have a .yml extension'), :bad_request) unless target_file_type_valid?
success(result)
end
def update_file(result)
file_update_response = ::Files::UpdateService.new(project, current_user, dashboard_attrs).execute
if file_update_response[:status] == :success
success(result.merge(file_update_response, http_status: :created, dashboard: dashboard_details))
else
error(file_update_response[:message], :bad_request)
end
end
def create_merge_request(result)
return success(result) if project.default_branch == branch
merge_request_params = {
source_branch: branch,
target_branch: project.default_branch,
title: params[:commit_message]
}
merge_request = ::MergeRequests::CreateService.new(project: project, current_user: current_user, params: merge_request_params).execute
if merge_request.persisted?
success(result.merge(merge_request: Gitlab::UrlBuilder.build(merge_request)))
else
error(merge_request.errors.full_messages.join(','), :bad_request)
end
end
def push_authorized?
Gitlab::UserAccess.new(current_user, container: project).can_push_to_branch?(branch)
end
def valid_branch_name?
Gitlab::GitRefValidator.validate(branch)
end
def new_or_default_branch?
!repository.branch_exists?(branch) || project.default_branch == branch
end
def target_file_type_valid?
File.extname(params[:file_name]) == ALLOWED_FILE_TYPE
end
def dashboard_attrs
{
commit_message: params[:commit_message],
file_path: update_dashboard_path,
file_content: update_dashboard_content,
encoding: 'text',
branch_name: branch,
start_branch: repository.branch_exists?(branch) ? branch : project.default_branch
}
end
def update_dashboard_path
File.join(USER_DASHBOARDS_DIR, file_name)
end
def file_name
@file_name ||= File.basename(CGI.unescape(params[:file_name]))
end
def branch
@branch ||= params[:branch]
end
def update_dashboard_content
::PerformanceMonitoring::PrometheusDashboard.from_json(params[:file_content]).to_yaml
end
def repository
@repository ||= project.repository
end
def dashboard_details
{
path: update_dashboard_path,
display_name: ::Metrics::Dashboard::CustomDashboardService.name_for_path(update_dashboard_path),
default: false,
system_dashboard: false
}
end
end
end
end

View File

@ -1,31 +0,0 @@
- return if Gitlab.org_or_com?
- expanded = integration_expanded?('ai_access')
- token_is_present = @application_setting.ai_access_token.present?
- token_label = token_is_present ? s_('CodeSuggestionsSM|Enter new personal access token') : s_('CodeSuggestionsSM|Personal access token')
- token_value = token_is_present ? ApplicationSettingMaskedAttrs::MASK : ''
%section.settings.no-animate#js-ai-access-settings{ class: ('expanded' if expanded) }
.settings-header
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= s_('CodeSuggestionsSM|Code Suggestions')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded ? _('Collapse') : _('Expand')
%p
= code_suggestions_description
.settings-content
= gitlab_ui_form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-ai-access-settings'), html: { class: 'fieldset-form', id: 'ai-access-settings' } do |f|
= form_errors(@application_setting)
%fieldset
.form-group
= f.gitlab_ui_checkbox_component :instance_level_code_suggestions_enabled,
s_('CodeSuggestionsSM|Enable Code Suggestions for this instance %{beta}').html_safe % { beta: gl_badge_tag(_('Beta'), variant: :neutral, size: :sm) },
help_text: code_suggestions_agreement
= f.label :ai_access_token, token_label, class: 'label-bold'
= f.password_field :ai_access_token, value: token_value, autocomplete: 'on', class: 'form-control gl-form-input', aria: { describedby: 'code_suggestions_token_explanation' }
%p.form-text.text-muted{ id: 'code_suggestions_token_explanation' }
= code_suggestions_token_explanation
= f.submit _('Save changes'), pajamas_button: true

View File

@ -0,0 +1,8 @@
---
name: ci_needs_parallel_matrix
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/118839
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/416479
milestone: '16.3'
type: development
group: group::pipeline authoring
default_enabled: false

View File

@ -6973,10 +6973,6 @@ Input type: `UserSetNamespaceCommitEmailInput`
### `Mutation.vulnerabilitiesDismiss`
WARNING:
**Introduced** in 16.2.
This feature is an Experiment. It can be changed or removed at any time.
Input type: `VulnerabilitiesDismissInput`
#### Arguments
@ -6994,7 +6990,7 @@ Input type: `VulnerabilitiesDismissInput`
| ---- | ---- | ----------- |
| <a id="mutationvulnerabilitiesdismissclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationvulnerabilitiesdismisserrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationvulnerabilitiesdismissvulnerabilities"></a>`vulnerabilities` **{warning-solid}** | [`[Vulnerability!]!`](#vulnerability) | **Deprecated:** This feature is an Experiment. It can be changed or removed at any time. Introduced in 16.2. |
| <a id="mutationvulnerabilitiesdismissvulnerabilities"></a>`vulnerabilities` | [`[Vulnerability!]!`](#vulnerability) | Vulnerabilities after state change. |
### `Mutation.vulnerabilityConfirm`

View File

@ -832,6 +832,83 @@ deploy:
Quotes around the `dependencies` entry are required.
## Specify a parallelized job using needs with multiple parallelized jobs
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/254821) in GitLab 16.3 [with a flag](../../administration/feature_flags.md) named `ci_needs_parallel_matrix`. Disabled by default.
You can use variables defined in [`needs:parallel:matrix`](../yaml/index.md#needsparallelmatrix) with multiple parallelized jobs.
For example:
```yaml
linux:build:
stage: build
script: echo "Building linux..."
parallel:
matrix:
- PROVIDER: aws
STACK:
- monitoring
- app1
- app2
mac:build:
stage: build
script: echo "Building mac..."
parallel:
matrix:
- PROVIDER: [gcp, vultr]
STACK: [data, processing]
linux:rspec:
stage: test
needs:
- job: linux:build
parallel:
matrix:
- PROVIDER: aws
- STACK: app1
script: echo "Running rspec on linux..."
mac:rspec:
stage: test
needs:
- job: mac:build
parallel:
matrix:
- PROVIDER: [gcp, vultr]
- STACK: [data]
script: echo "Running rspec on mac..."
production:
stage: deploy
script: echo "Running production..."
environment: production
```
This example generates several jobs. The parallel jobs each have different values
for `PROVIDER` and `STACK`.
- 3 parallel `linux:build` jobs:
- `linux:build: [aws, monitoring]`
- `linux:build: [aws, app1]`
- `linux:build: [aws, app2]`
- 4 parallel `mac:build` jobs:
- `mac:build: [gcp, data]`
- `mac:build: [gcp, processing]`
- `mac:build: [vultr, data]`
- `mac:build: [vultr, processing]`
- A `linux:rspec` job.
- A `production` job.
The jobs have three paths of execution:
- Linux path: The `linux:rspec` job runs as soon as the `linux:build: [aws, app1]`
job finishes, without waiting for `mac:build` to finish.
- macOS path: The `mac:rspec` job runs as soon as the `mac:build: [gcp, data]` and
`mac:build: [vultr, data]` jobs finish, without waiting for `linux:build` to finish.
- The `production` job runs as soon as all previous jobs finish.
## Use predefined CI/CD variables to run jobs only in specific pipeline types
You can use [predefined CI/CD variables](../variables/predefined_variables.md) to choose

View File

@ -2389,6 +2389,8 @@ This example creates four paths of execution:
it depends on all jobs created in parallel, not just one job. It also downloads
artifacts from all the parallel jobs by default. If the artifacts have the same
name, they overwrite each other and only the last one downloaded is saved.
- To have `needs` refer to a subset of parallelized jobs (and not all of the parallelized jobs),
use the [`needs:parallel:matrix`](#needsparallelmatrix) keyword.
- In [GitLab 14.1 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/30632) you
can refer to jobs in the same stage as the job you are configuring. This feature is
enabled on GitLab.com and ready for production use. On self-managed [GitLab 14.2 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/30632)
@ -2679,6 +2681,61 @@ upstream_status:
- If you add the `job` keyword to `needs:pipeline`, the job no longer mirrors the
pipeline status. The behavior changes to [`needs:pipeline:job`](#needspipelinejob).
#### `needs:parallel:matrix`
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/254821) in GitLab 16.3 [with a flag](../../administration/feature_flags.md) named `ci_needs_parallel_matrix`. Disabled by default.
Jobs can use [`parallel:matrix`](#parallelmatrix) to run a job multiple times in parallel in a single pipeline,
but with different variable values for each instance of the job.
Use `needs:parallel:matrix` to execute jobs out-of-order depending on parallelized jobs.
**Keyword type**: Job keyword. You can use it only as part of a job. Must be used with `needs:job`.
**Possible inputs**: An array of hashes of variables:
- The variables and values must be selected from the variables and values defined in the `parallel:matrix` job.
**Example of `needs:parallel:matrix`**:
```yaml
linux:build:
stage: build
script: echo "Building linux..."
parallel:
matrix:
- PROVIDER: aws
STACK:
- monitoring
- app1
- app2
linux:rspec:
stage: test
needs:
- job: linux:build
parallel:
matrix:
- PROVIDER: aws
- STACK: app1
script: echo "Running rspec on linux..."
```
The above example generates the following jobs:
```plaintext
linux:build: [aws, monitoring]
linux:build: [aws, app1]
linux:build: [aws, app2]
linux:rspec
```
The `linux:rspec` job runs as soon as the `linux:build: [aws, app1]` job finishes.
**Related topics**:
- [Specify a parallelized job using needs with multiple parallelized jobs](../jobs/job_control.md#specify-a-parallelized-job-using-needs-with-multiple-parallelized-jobs).
### `only` / `except`
NOTE:

View File

@ -526,7 +526,7 @@ NOTE:
This Rake task needs `docker` to be installed.
To update generated code for OpenAPI client located in
`vendor/gems/error_tracking_open_api` run the following commands:
`gems/error_tracking_open_api` run the following commands:
```shell
# Run rake task
@ -535,7 +535,7 @@ bundle exec rake gems:error_tracking_open_api:generate
# Review and test the changes
# Commit the changes
git commit -m 'Update ErrorTrackingOpenAPI from OpenAPI definition' vendor/gems/error_tracking_open_api
git commit -m 'Update ErrorTrackingOpenAPI from OpenAPI definition' gems/error_tracking_open_api
```
## Update banned SSH keys

View File

@ -1,4 +1,4 @@
# Generated by `rake gems:error_tracking_open_api:generate` on 2023-06-08
# Generated by `rake gems:error_tracking_open_api:generate` on 2023-07-13
See https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/development/rake_tasks.md#update-openapi-client-for-error-tracking-feature

View File

@ -29,7 +29,7 @@ Gem::Specification.new do |s|
s.add_runtime_dependency 'typhoeus', '~> 1.0', '>= 1.0.1'
s.add_development_dependency 'rspec', '~> 3.6', '>= 3.6.0'
s.files = Dir.glob("lib/**/*")
s.test_files = []

View File

@ -42,11 +42,15 @@ module Gitlab
end
class JobHash < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Validatable
include ::Gitlab::Config::Entry::Attributable
include ::Gitlab::Config::Entry::Configurable
ALLOWED_KEYS = %i[job artifacts optional].freeze
attributes :job, :artifacts, :optional
ALLOWED_KEYS = %i[job artifacts optional parallel].freeze
attributes :job, :artifacts, :optional, :parallel
entry :parallel, Entry::Product::Parallel,
description: 'Parallel needs configuration for this job',
inherit: true
validations do
validates :config, presence: true
@ -61,9 +65,15 @@ module Gitlab
end
def value
{ name: job,
result = {
name: job,
artifacts: artifacts || artifacts.nil?,
optional: !!optional }
optional: !!optional
}
result[:parallel] = parallel_value if Feature.enabled?(:ci_needs_parallel_matrix) && has_parallel?
result
end
end

View File

@ -21,6 +21,10 @@ module Gitlab
if config.is_a?(Hash) && config.empty?
errors.add(:config, 'can not be an empty Hash')
end
if Feature.enabled?(:ci_needs_parallel_matrix) && number_parallel_build?
errors.add(:config, 'cannot use "parallel: <number>".')
end
end
validate on: :composed do
@ -47,6 +51,14 @@ module Gitlab
end
end
def number_parallel_build?
if config.is_a?(Array)
config.any? { |need_values| need_values.is_a?(Hash) && need_values[:parallel].is_a?(Numeric) }
elsif config.is_a?(Hash)
config[:parallel].is_a?(Numeric)
end
end
def composable_class
Entry::Need
end

View File

@ -44,6 +44,10 @@ module Gitlab
job_need_name = job_need[:name].to_sym
if all_jobs = parallelized_jobs[job_need_name]
if Feature.enabled?(:ci_needs_parallel_matrix) && job_need.key?(:parallel)
all_jobs = parallelize_job_config(job_need_name, job_need.delete(:parallel))
end
all_jobs.map { |job| job_need.merge(name: job.name) }
else
job_need

View File

@ -63,7 +63,7 @@ module Gitlab
# display_name: String,
# default: Boolean }]
def find_all_paths(project)
dashboards = user_facing_dashboard_services.flat_map do |service|
dashboards = PREDEFINED_DASHBOARD_LIST.flat_map do |service|
service.all_dashboard_paths(project)
end
@ -72,18 +72,10 @@ module Gitlab
private
def user_facing_dashboard_services
PREDEFINED_DASHBOARD_LIST + [project_service]
end
def system_service
::Metrics::Dashboard::SystemDashboardService
end
def project_service
::Metrics::Dashboard::CustomDashboardService
end
def service_for(options)
Gitlab::Metrics::Dashboard::ServiceSelector.call(options)
end

View File

@ -16,8 +16,7 @@ module Gitlab
::Metrics::Dashboard::GitlabAlertEmbedService,
::Metrics::Dashboard::CustomMetricEmbedService,
::Metrics::Dashboard::DefaultEmbedService,
::Metrics::Dashboard::SystemDashboardService,
::Metrics::Dashboard::CustomDashboardService
::Metrics::Dashboard::SystemDashboardService
].freeze
# Returns a class which inherits from the BaseService

View File

@ -29,7 +29,7 @@ namespace :gems do
end
def root_directory
File.expand_path('../../vendor/gems', __dir__)
File.expand_path('../../gems', __dir__)
end
def generate_gem(vendor_gem_dir:, api_url:, gem_name:, module_name:, docker_image:)
@ -53,14 +53,18 @@ namespace :gems do
write_file(gem_dir / 'LICENSE', license)
write_file(gem_dir / "#{gem_name}.gemspec") do |content|
replace_string(content, 'Unlicense', 'MIT')
replace_string(content, /.*add_development_dependency 'rspec'.*/, '')
replace_string(content, /(\.files\s*=).*/, '\1 Dir.glob("lib/**/*")')
replace_string(content, /(\.test_files\s*=).*/, '\1 []')
end
# This is gem is supposed to be generated. No developer should change code.
remove_entry_secure(gem_dir / 'Gemfile')
# The generated code doesn't align well with `gitlab-styles` configuration.
remove_entry_secure(gem_dir / '.rubocop.yml')
remove_entry_secure(gem_dir / '.travis.yml')
remove_entry_secure(gem_dir / 'git_push.sh')
# The RSpec examples are stubs and have no value.
remove_entry_secure(gem_dir / 'spec')
remove_entry_secure(gem_dir / '.rspec')
end
@ -78,14 +82,16 @@ namespace :gems do
end
def readme_banner(task)
# rubocop:disable Rails/TimeZone
<<~BANNER
# Generated by `rake #{task.name}` on #{Time.now.strftime('%Y-%m-%d')}
# #{generated_by(task)}
See https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/development/rake_tasks.md#update-openapi-client-for-error-tracking-feature
BANNER
# rubocop:enable Rails/TimeZone
end
def generated_by(task)
"Generated by `rake #{task.name}` on #{Time.now.strftime('%Y-%m-%d')}" # rubocop:disable Rails/TimeZone
end
def license

View File

@ -1966,6 +1966,9 @@ msgstr ""
msgid "AI|Generate issue description"
msgstr ""
msgid "AI|GitLab Duo is %{transition} an answer"
msgstr ""
msgid "AI|Helpful"
msgstr ""
@ -2035,6 +2038,18 @@ msgstr ""
msgid "AI|You can ask AI for more information."
msgstr ""
msgid "AI|finding"
msgstr ""
msgid "AI|generating"
msgstr ""
msgid "AI|producing"
msgstr ""
msgid "AI|working on"
msgstr ""
msgid "API"
msgstr ""
@ -46594,9 +46609,6 @@ msgstr ""
msgid "The file has been successfully deleted."
msgstr ""
msgid "The file name should have a .yml extension"
msgstr ""
msgid "The finding is not a vulnerability because it is part of a test or is test data."
msgstr ""
@ -47294,12 +47306,6 @@ msgstr ""
msgid "There was an error updating the Maintenance Mode Settings"
msgstr ""
msgid "There was an error updating the dashboard, branch name is invalid."
msgstr ""
msgid "There was an error updating the dashboard, branch named: %{branch} already exists."
msgstr ""
msgid "There was an error when reseting email token."
msgstr ""
@ -53194,9 +53200,6 @@ msgstr ""
msgid "You are not allowed to log in using password"
msgstr ""
msgid "You are not allowed to push into this branch. Create another branch or open a merge request."
msgstr ""
msgid "You are not allowed to reject a user"
msgstr ""

View File

@ -35,6 +35,7 @@ import IdTokensYaml from './yaml_tests/positive_tests/id_tokens.yml';
import HooksYaml from './yaml_tests/positive_tests/hooks.yml';
import SecretsYaml from './yaml_tests/positive_tests/secrets.yml';
import ServicesYaml from './yaml_tests/positive_tests/services.yml';
import NeedsParallelMatrixYaml from './yaml_tests/positive_tests/needs_parallel_matrix.yml';
// YAML NEGATIVE TEST
import ArtifactsNegativeYaml from './yaml_tests/negative_tests/artifacts.yml';
@ -56,6 +57,9 @@ import IdTokensNegativeYaml from './yaml_tests/negative_tests/id_tokens.yml';
import HooksNegative from './yaml_tests/negative_tests/hooks.yml';
import SecretsNegativeYaml from './yaml_tests/negative_tests/secrets.yml';
import ServicesNegativeYaml from './yaml_tests/negative_tests/services.yml';
import NeedsParallelMatrixNumericYaml from './yaml_tests/negative_tests/needs/parallel_matrix/numeric.yml';
import NeedsParallelMatrixWrongParallelValueYaml from './yaml_tests/negative_tests/needs/parallel_matrix/wrong_parallel_value.yml';
import NeedsParallelMatrixWrongMatrixValueYaml from './yaml_tests/negative_tests/needs/parallel_matrix/wrong_matrix_value.yml';
const ajv = new Ajv({
strictTypes: false,
@ -96,6 +100,7 @@ describe('positive tests', () => {
IdTokensYaml,
ServicesYaml,
SecretsYaml,
NeedsParallelMatrixYaml,
}),
)('schema validates %s', (_, input) => {
// We construct a new "JSON" from each main key that is inside a
@ -136,6 +141,9 @@ describe('negative tests', () => {
ProjectPathIncludeTailSlashYaml,
SecretsNegativeYaml,
ServicesNegativeYaml,
NeedsParallelMatrixNumericYaml,
NeedsParallelMatrixWrongParallelValueYaml,
NeedsParallelMatrixWrongMatrixValueYaml,
}),
)('schema validates %s', (_, input) => {
// We construct a new "JSON" from each main key that is inside a

View File

@ -0,0 +1,6 @@
# invalid needs:parallel:matrix where parallel has a numeric value
job_with_needs_parallel_matrix:
script: exit 0
needs:
- job: some_job_with_parallel_matrix
parallel: 10

View File

@ -0,0 +1,30 @@
# invalid needs:parallel:matrix where matrix value is incorrect
job_with_needs_parallel_matrix:
script: exit 0
needs:
- job: some_job_with_parallel_matrix
parallel:
matrix: 10
job_with_needs_parallel_matrix_2:
script: exit 0
needs:
- job: some_job_with_parallel_matrix
parallel:
matrix: "string"
job_with_needs_parallel_matrix_3:
script: exit 0
needs:
- job: some_job_with_parallel_matrix
parallel:
matrix: [a1, a2]
job_with_needs_parallel_matrix_4:
script: exit 0
needs:
- job: some_job_with_parallel_matrix
parallel:
matrix:
VAR_1: 1
VAR_2: 2

View File

@ -0,0 +1,27 @@
# invalid needs:parallel:matrix where parallel value is incorrect
job_with_needs_parallel_matrix:
script: exit 0
needs:
- job: some_job_with_parallel_matrix
parallel:
not_matrix:
- VAR_1: [a1, a2]
VAR_2: [b1, b2]
job_with_needs_parallel_matrix_2:
script: exit 0
needs:
- job: some_job_with_parallel_matrix
parallel: [a1, a2]
job_with_needs_parallel_matrix_3:
script: exit 0
needs:
- job: some_job_with_parallel_matrix
parallel: "matrix"
job_with_needs_parallel_matrix_4:
script: exit 0
needs:
- job: some_job_with_parallel_matrix
parallel: object

View File

@ -0,0 +1,32 @@
# valid needs:parallel:matrix
job_with_needs_parallel_matrix:
script: exit 0
needs:
- job: job_with_parallel_matrix
parallel:
matrix:
- VAR_1: [a]
VAR_2: [d]
job_with_needs_parallel_matrix_2:
script: exit 0
needs:
- job: job_with_parallel_matrix
parallel:
matrix:
- VAR_1: a
VAR_2: d
job_with_needs_parallel_matrix_3:
script: exit 0
needs:
- job: job_with_parallel_matrix
parallel:
matrix:
- VAR_1: ["a", b]
VAR_2: d
- job: job_with_parallel_matrix_2
parallel:
matrix:
- VAR_1: [a, "b", c]
VAR_5: [d, "e"]

View File

@ -31,24 +31,4 @@ RSpec.describe Admin::ApplicationSettings::SettingsHelper do
})
end
end
describe 'Code Suggestions for Self-Managed instances', feature_category: :code_suggestions do
describe '#code_suggestions_description' do
subject { helper.code_suggestions_description }
it { is_expected.to include 'https://docs.gitlab.com/ee/user/project/repository/code_suggestions.html' }
end
describe '#code_suggestions_token_explanation' do
subject { helper.code_suggestions_token_explanation }
it { is_expected.to include 'https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#create-a-personal-access-token' }
end
describe '#code_suggestions_agreement' do
subject { helper.code_suggestions_agreement }
it { is_expected.to include 'https://about.gitlab.com/handbook/legal/testing-agreement/' }
end
end
end

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe ::Gitlab::Ci::Config::Entry::Need do
RSpec.describe ::Gitlab::Ci::Config::Entry::Need, feature_category: :pipeline_composition do
subject(:need) { described_class.new(config) }
shared_examples 'job type' do
@ -219,6 +219,81 @@ RSpec.describe ::Gitlab::Ci::Config::Entry::Need do
it_behaves_like 'job type'
end
context 'when parallel:matrix has a value' do
before do
need.compose!
end
context 'and it is a string value' do
let(:config) do
{ job: 'job_name', parallel: { matrix: [{ platform: 'p1', stack: 's1' }] } }
end
describe '#valid?' do
it { is_expected.to be_valid }
end
describe '#value' do
it 'returns job needs configuration' do
expect(need.value).to eq(
name: 'job_name',
artifacts: true,
optional: false,
parallel: { matrix: [{ "platform" => ['p1'], "stack" => ['s1'] }] }
)
end
end
it_behaves_like 'job type'
end
context 'and it is an array value' do
let(:config) do
{ job: 'job_name', parallel: { matrix: [{ platform: %w[p1 p2], stack: %w[s1 s2] }] } }
end
describe '#valid?' do
it { is_expected.to be_valid }
end
describe '#value' do
it 'returns job needs configuration' do
expect(need.value).to eq(
name: 'job_name',
artifacts: true,
optional: false,
parallel: { matrix: [{ 'platform' => %w[p1 p2], 'stack' => %w[s1 s2] }] }
)
end
end
it_behaves_like 'job type'
end
context 'and it is a both an array and string value' do
let(:config) do
{ job: 'job_name', parallel: { matrix: [{ platform: %w[p1 p2], stack: 's1' }] } }
end
describe '#valid?' do
it { is_expected.to be_valid }
end
describe '#value' do
it 'returns job needs configuration' do
expect(need.value).to eq(
name: 'job_name',
artifacts: true,
optional: false,
parallel: { matrix: [{ 'platform' => %w[p1 p2], 'stack' => ['s1'] }] }
)
end
end
it_behaves_like 'job type'
end
end
end
context 'with cross pipeline artifacts needs' do

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe ::Gitlab::Ci::Config::Entry::Needs do
RSpec.describe ::Gitlab::Ci::Config::Entry::Needs, feature_category: :pipeline_composition do
subject(:needs) { described_class.new(config) }
before do
@ -67,6 +67,141 @@ RSpec.describe ::Gitlab::Ci::Config::Entry::Needs do
end
end
context 'when needs value is a hash' do
context 'with a job value' do
let(:config) do
{ job: 'job_name' }
end
describe '#valid?' do
it { is_expected.to be_valid }
end
end
context 'with a parallel value that is a numeric value' do
let(:config) do
{ job: 'job_name', parallel: 2 }
end
describe '#valid?' do
it { is_expected.not_to be_valid }
end
describe '#errors' do
it 'returns errors about number values being invalid for needs:parallel' do
expect(needs.errors).to match_array(["needs config cannot use \"parallel: <number>\"."])
end
end
end
end
context 'when needs:parallel value is incorrect' do
context 'with a keyword that is not "matrix"' do
let(:config) do
[
{ job: 'job_name', parallel: { not_matrix: [{ one: 'aaa', two: 'bbb' }] } }
]
end
describe '#valid?' do
it { is_expected.not_to be_valid }
end
describe '#errors' do
it 'returns errors about incorrect matrix keyword' do
expect(needs.errors).to match_array([
'need:parallel config contains unknown keys: not_matrix',
'need:parallel config missing required keys: matrix'
])
end
end
end
context 'with a number value' do
let(:config) { [{ job: 'job_name', parallel: 2 }] }
describe '#valid?' do
it { is_expected.not_to be_valid }
end
describe '#errors' do
it 'returns errors about number values being invalid for needs:parallel' do
expect(needs.errors).to match_array(["needs config cannot use \"parallel: <number>\"."])
end
end
end
end
context 'when needs:parallel:matrix value is empty' do
let(:config) { [{ job: 'job_name', parallel: { matrix: {} } }] }
describe '#valid?' do
it { is_expected.not_to be_valid }
end
describe '#errors' do
it 'returns error about incorrect type' do
expect(needs.errors).to contain_exactly(
'need:parallel:matrix config should be an array of hashes')
end
end
end
context 'when needs:parallel:matrix value is incorrect' do
let(:config) { [{ job: 'job_name', parallel: { matrix: 'aaa' } }] }
describe '#valid?' do
it { is_expected.not_to be_valid }
end
describe '#errors' do
it 'returns error about incorrect type' do
expect(needs.errors).to contain_exactly(
'need:parallel:matrix config should be an array of hashes')
end
end
end
context 'when needs:parallel:matrix value is correct' do
context 'with a simple config' do
let(:config) do
[
{ job: 'job_name', parallel: { matrix: [{ A: 'a1', B: 'b1' }] } }
]
end
describe '#valid?' do
it { is_expected.to be_valid }
end
end
context 'with a complex config' do
let(:config) do
[
{
job: 'job_name1',
artifacts: true,
parallel: { matrix: [{ A: %w[a1 a2], B: %w[b1 b2 b3], C: %w[c1 c2] }] }
},
{
job: 'job_name2',
parallel: {
matrix: [
{ A: %w[a1 a2], D: %w[d1 d2] },
{ E: %w[e1 e2], F: ['f1'] },
{ C: %w[c1 c2 c3], G: %w[g1 g2], H: ['h1'] }
]
}
}
]
end
describe '#valid?' do
it { is_expected.to be_valid }
end
end
end
context 'with too many cross pipeline dependencies' do
let(:limit) { described_class::NEEDS_CROSS_PIPELINE_DEPENDENCIES_LIMIT }

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'fast_spec_helper'
require 'spec_helper'
RSpec.describe Gitlab::Ci::Config::Normalizer do
let(:job_name) { :rspec }
@ -103,6 +103,50 @@ RSpec.describe Gitlab::Ci::Config::Normalizer do
end
end
shared_examples 'needs:parallel:matrix' do
let(:expanded_needs_parallel_job_attributes) do
expanded_needs_parallel_job_names.map do |job_name|
{ name: job_name }
end
end
context 'when job has needs:parallel:matrix on parallelized jobs' do
let(:config) do
{
job_name => job_config,
other_job: {
script: 'echo 1',
needs: {
job: [
{ name: job_name.to_s, parallel: needs_parallel_config }
]
}
}
}
end
it 'parallelizes and only keeps needs specified by needs:parallel:matrix' do
expect(subject.dig(:other_job, :needs, :job)).to eq(expanded_needs_parallel_job_attributes)
end
context 'when FF `ci_needs_parallel_matrix` is disabled' do
before do
stub_feature_flags(ci_needs_parallel_matrix: false)
end
let(:expanded_job_attributes) do
expanded_job_names.map do |job_name|
{ name: job_name, parallel: needs_parallel_config }
end
end
it 'keeps all parallelized jobs' do
expect(subject.dig(:other_job, :needs, :job)).to eq(expanded_job_attributes)
end
end
end
end
context 'with parallel config as integer' do
let(:variables_config) { {} }
let(:parallel_config) { 5 }
@ -167,7 +211,7 @@ RSpec.describe Gitlab::Ci::Config::Normalizer do
it_behaves_like 'parallel needs'
end
context 'with parallel matrix config' do
context 'with a simple parallel matrix config' do
let(:variables_config) do
{
USER_VARIABLE: 'user value'
@ -192,6 +236,19 @@ RSpec.describe Gitlab::Ci::Config::Normalizer do
]
end
let(:needs_parallel_config) do
{
matrix: [
{
VAR_1: ['A'],
VAR_2: ['C']
}
]
}
end
let(:expanded_needs_parallel_job_names) { ['rspec: [A, C]'] }
it 'does not have original job' do
is_expected.not_to include(job_name)
end
@ -228,6 +285,66 @@ RSpec.describe Gitlab::Ci::Config::Normalizer do
it_behaves_like 'parallel dependencies'
it_behaves_like 'parallel needs'
it_behaves_like 'needs:parallel:matrix'
end
context 'with a complex parallel matrix config' do
let(:variables_config) { {} }
let(:parallel_config) do
{
matrix: [
{
PLATFORM: ['centos'],
STACK: %w[ruby python java],
DB: %w[postgresql mysql]
},
{
PLATFORM: ['ubuntu'],
PROVIDER: %w[aws gcp]
}
]
}
end
let(:needs_parallel_config) do
{
matrix: [
{
PLATFORM: ['centos'],
STACK: %w[ruby python],
DB: ['postgresql']
},
{
PLATFORM: ['ubuntu'],
PROVIDER: ['aws']
}
]
}
end
let(:expanded_needs_parallel_job_names) do
[
'rspec: [centos, ruby, postgresql]',
'rspec: [centos, python, postgresql]',
'rspec: [ubuntu, aws]'
]
end
let(:expanded_job_names) do
[
'rspec: [centos, ruby, postgresql]',
'rspec: [centos, ruby, mysql]',
'rspec: [centos, python, postgresql]',
'rspec: [centos, python, mysql]',
'rspec: [centos, java, postgresql]',
'rspec: [centos, java, mysql]',
'rspec: [ubuntu, aws]',
'rspec: [ubuntu, gcp]'
]
end
it_behaves_like 'parallel needs'
it_behaves_like 'needs:parallel:matrix'
end
context 'when parallel config does not matches a factory' do

View File

@ -2675,6 +2675,42 @@ module Gitlab
it_behaves_like 'returns errors', 'jobs:test1 dependencies should be an array of strings'
end
context 'needs with parallel:matrix' do
let(:config) do
{
build1: {
stage: 'build',
script: 'build',
parallel: { matrix: [{ 'PROVIDER': ['aws'], 'STACK': %w[monitoring app1 app2] }] }
},
test1: {
stage: 'test',
script: 'test',
needs: [{ job: 'build1', parallel: { matrix: [{ 'PROVIDER': ['aws'], 'STACK': ['app1'] }] } }]
}
}
end
it "does create jobs with valid specification" do
expect(subject.builds.size).to eq(4)
expect(subject.builds[3]).to eq(
stage: "test",
stage_idx: 2,
name: "test1",
only: { refs: %w[branches tags] },
options: { script: ["test"] },
needs_attributes: [
{ name: "build1: [aws, app1]", artifacts: true, optional: false }
],
when: "on_success",
allow_failure: false,
job_variables: [],
root_variables_inheritance: true,
scheduling_type: :dag
)
end
end
end
context 'with when/rules' do

View File

@ -17,20 +17,12 @@ RSpec.describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store
let(:dashboard_path) { '.gitlab/dashboards/test.yml' }
let(:service_call) { described_class.find(project, user, environment: environment, dashboard_path: dashboard_path) }
it_behaves_like 'misconfigured dashboard service response', :not_found
context 'when the dashboard exists' do
let(:project) { project_with_dashboard(dashboard_path) }
it_behaves_like 'valid dashboard service response'
end
context 'when the dashboard is configured incorrectly' do
let(:project) { project_with_dashboard(dashboard_path, {}) }
it_behaves_like 'misconfigured dashboard service response', :unprocessable_entity
end
context 'when the system dashboard is specified' do
let(:dashboard_path) { system_dashboard_path }
@ -103,14 +95,6 @@ RSpec.describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store
it { is_expected.to eq dashboard }
end
context 'when an existing project dashboard is specified' do
let(:dashboard) { load_sample_dashboard }
let(:params) { { dashboard_path: '.gitlab/dashboards/test.yml' } }
let(:project) { project_with_dashboard(params[:dashboard_path]) }
it { is_expected.to eq dashboard }
end
end
describe '.find_all_paths' do
@ -120,37 +104,5 @@ RSpec.describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store
it 'includes OOTB dashboards by default' do
expect(all_dashboard_paths).to eq([system_dashboard])
end
context 'when the project contains dashboards' do
let(:dashboard_content) { fixture_file('lib/gitlab/metrics/dashboard/sample_dashboard.yml') }
let(:project) { project_with_dashboards(dashboards) }
let(:dashboards) do
{
'.gitlab/dashboards/metrics.yml' => dashboard_content,
'.gitlab/dashboards/better_metrics.yml' => dashboard_content
}
end
it 'includes OOTB and project dashboards' do
project_dashboard1 = {
path: '.gitlab/dashboards/metrics.yml',
display_name: 'metrics.yml',
default: false,
system_dashboard: false,
out_of_the_box_dashboard: false
}
project_dashboard2 = {
path: '.gitlab/dashboards/better_metrics.yml',
display_name: 'better_metrics.yml',
default: false,
system_dashboard: false,
out_of_the_box_dashboard: false
}
expect(all_dashboard_paths).to eq([project_dashboard2, project_dashboard1, system_dashboard])
end
end
end
end

View File

@ -13,10 +13,6 @@ RSpec.describe Gitlab::Metrics::Dashboard::ServiceSelector do
it { is_expected.to be Metrics::Dashboard::SystemDashboardService }
context 'when just the dashboard path is provided' do
let(:arguments) { { dashboard_path: '.gitlab/dashboards/test.yml' } }
it { is_expected.to be Metrics::Dashboard::CustomDashboardService }
context 'when the path is for the system dashboard' do
let(:arguments) { { dashboard_path: system_dashboard_path } }

View File

@ -1687,29 +1687,4 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
expect(setting.personal_access_tokens_disabled?).to eq(false)
end
end
describe '#ai_access_token' do
context 'when `instance_level_code_suggestions_enabled` is true' do
before do
setting.instance_level_code_suggestions_enabled = true
end
it { is_expected.not_to allow_value(nil).for(:ai_access_token) }
end
context 'when `instance_level_code_suggestions_enabled` is false' do
before do
setting.instance_level_code_suggestions_enabled = false
end
it { is_expected.to allow_value(nil).for(:ai_access_token) }
end
it 'does not modify the token if it is unchanged in the form' do
setting.ai_access_token = 'foo'
setting.ai_access_token = ApplicationSettingMaskedAttrs::MASK
expect(setting.ai_access_token).to eq('foo')
end
end
end

View File

@ -229,16 +229,6 @@ RSpec.describe PerformanceMonitoring::PrometheusDashboard do
allow(Gitlab::Metrics::Dashboard::Finder).to receive(:find_raw).with(project, dashboard_path: path).and_call_original
end
context 'when schema is valid' do
let(:dashboard_schema) { YAML.safe_load(fixture_file('lib/gitlab/metrics/dashboard/sample_dashboard.yml')) }
it 'returns empty array' do
expect(described_class).to receive(:from_json).with(dashboard_schema)
expect(schema_validation_warnings).to eq []
end
end
context 'when schema is invalid' do
let(:dashboard_schema) { YAML.safe_load(fixture_file('lib/gitlab/metrics/dashboard/dashboard_missing_panel_groups.yml')) }
@ -250,28 +240,5 @@ RSpec.describe PerformanceMonitoring::PrometheusDashboard do
expect(described_class.new.schema_validation_warnings).to eq ['test: test error']
end
end
context 'when YAML has wrong syntax' do
let(:project) { create(:project, :repository, :custom_repo, files: { path => fixture_file('lib/gitlab/metrics/dashboard/broken_yml_syntax.yml') }) }
subject(:schema_validation_warnings) { described_class.new(path: path, environment: environment).schema_validation_warnings }
it 'returns array with errors messages' do
expect(described_class).not_to receive(:from_json)
expect(schema_validation_warnings).to eq ['Invalid yaml']
end
end
end
describe '#to_yaml' do
subject { prometheus_dashboard.to_yaml }
let(:prometheus_dashboard) { described_class.from_json(json_content) }
let(:expected_yaml) do
"---\npanel_groups:\n- panels:\n - metrics:\n - id: metric_of_ages\n unit: count\n label: Metric of Ages\n query: \n query_range: http_requests_total\n type: area-chart\n title: Chart Title\n y_label: Y-Axis\n weight: \n group: Group Title\n priority: \ndashboard: Dashboard Title\n"
end
it { is_expected.to eq(expected_yaml) }
end
end

View File

@ -25,36 +25,6 @@ RSpec.describe API::Metrics::UserStarredDashboards, feature_category: :metrics d
end
context 'with correct permissions' do
context 'with valid parameters' do
context 'dashboard_path as url param url escaped' do
it 'creates a new user starred metrics dashboard', :aggregate_failures do
post api(url, user), params: params
expect(response).to have_gitlab_http_status(:created)
expect(json_response['project_id']).to eq(project.id)
expect(json_response['user_id']).to eq(user.id)
expect(json_response['dashboard_path']).to eq(dashboard)
end
end
context 'dashboard_path in request body unescaped' do
let(:params) do
{
dashboard_path: dashboard
}
end
it 'creates a new user starred metrics dashboard', :aggregate_failures do
post api(url, user), params: params
expect(response).to have_gitlab_http_status(:created)
expect(json_response['project_id']).to eq(project.id)
expect(json_response['user_id']).to eq(user.id)
expect(json_response['dashboard_path']).to eq(dashboard)
end
end
end
context 'with invalid parameters' do
it 'returns error message' do
post api(url, user), params: { dashboard_path: '' }

View File

@ -143,19 +143,5 @@ RSpec.describe Metrics::Dashboard::Annotations::CreateService, feature_category:
it_behaves_like 'annotation creation failure'
end
end
context 'incorrect dashboard_path' do
let(:cluster) { create(:cluster, :project) }
let(:environment) { nil }
let(:dashboard_path) { 'something_incorrect.yml' }
context 'with maintainer user' do
before do
cluster.project.add_maintainer(user)
end
it_behaves_like 'prevented annotation creation', 'Dashboard with requested path can not be found'
end
end
end
end

View File

@ -1,167 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Metrics::Dashboard::CustomDashboardService, :use_clean_rails_memory_store_caching,
feature_category: :metrics do
include MetricsDashboardHelpers
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:environment) { create(:environment, project: project) }
let(:dashboard_path) { '.gitlab/dashboards/test.yml' }
let(:service_params) { [project, user, { environment: environment, dashboard_path: dashboard_path }] }
subject { described_class.new(*service_params) }
before do
project.add_maintainer(user) if user
end
describe '#raw_dashboard' do
let(:project) { project_with_dashboard(dashboard_path) }
it_behaves_like '#raw_dashboard raises error if dashboard loading fails'
end
describe '#get_dashboard' do
let(:service_call) { subject.get_dashboard }
context 'when the dashboard does not exist' do
it_behaves_like 'misconfigured dashboard service response', :not_found
it 'does not update gitlab_metrics_dashboard_processing_time_ms metric', :prometheus do
service_call
metric = subject.send(:processing_time_metric)
labels = subject.send(:processing_time_metric_labels)
expect(metric.get(labels)).to eq(0)
end
end
it_behaves_like 'raises error for users with insufficient permissions'
context 'when the dashboard exists' do
let(:project) { project_with_dashboard(dashboard_path) }
it_behaves_like 'valid dashboard service response'
it_behaves_like 'updates gitlab_metrics_dashboard_processing_time_ms metric'
it 'caches the unprocessed dashboard for subsequent calls' do
expect_any_instance_of(described_class)
.to receive(:get_raw_dashboard)
.once
.and_call_original
described_class.new(*service_params).get_dashboard
described_class.new(*service_params).get_dashboard
end
it 'tracks panel type' do
allow(::Gitlab::Tracking).to receive(:event).and_call_original
described_class.new(*service_params).get_dashboard
expect(::Gitlab::Tracking).to have_received(:event)
.with('MetricsDashboard::Chart', 'chart_rendered', { label: 'area-chart' })
.at_least(:once)
end
context 'with metric in database' do
let!(:prometheus_metric) do
create(:prometheus_metric, project: project, identifier: 'metric_a1', group: 'custom')
end
it 'includes metric_id' do
dashboard = described_class.new(*service_params).get_dashboard
metric_id = dashboard[:dashboard][:panel_groups].find { |panel_group| panel_group[:group] == 'Group A' }
.fetch(:panels).find { |panel| panel[:title] == 'Super Chart A1' }
.fetch(:metrics).find { |metric| metric[:id] == 'metric_a1' }
.fetch(:metric_id)
expect(metric_id).to eq(prometheus_metric.id)
end
end
context 'and the dashboard is then deleted' do
it 'does not return the previously cached dashboard' do
described_class.new(*service_params).get_dashboard
delete_project_dashboard(project, user, dashboard_path)
expect_any_instance_of(described_class)
.to receive(:get_raw_dashboard)
.once
.and_call_original
described_class.new(*service_params).get_dashboard
end
end
end
context 'when the dashboard is configured incorrectly' do
let(:project) { project_with_dashboard(dashboard_path, {}) }
it_behaves_like 'misconfigured dashboard service response', :unprocessable_entity
end
end
describe '.all_dashboard_paths' do
let(:all_dashboards) { described_class.all_dashboard_paths(project) }
context 'when there are no project dashboards' do
it 'returns an empty array' do
expect(all_dashboards).to be_empty
end
end
context 'when there are project dashboards available' do
let(:dashboard_path) { '.gitlab/dashboards/test.yml' }
let(:project) { project_with_dashboard(dashboard_path) }
it 'returns the dashboard attributes' do
expect(all_dashboards).to eq(
[{
path: dashboard_path,
display_name: 'test.yml',
default: false,
system_dashboard: false,
out_of_the_box_dashboard: false
}]
)
end
it 'caches repo file list' do
expect(Gitlab::Metrics::Dashboard::RepoDashboardFinder).to receive(:list_dashboards)
.with(project)
.once
.and_call_original
described_class.all_dashboard_paths(project)
described_class.all_dashboard_paths(project)
end
end
end
describe '.valid_params?' do
let(:params) { { dashboard_path: '.gitlab/dashboard/test.yml' } }
subject { described_class.valid_params?(params) }
it { is_expected.to be_truthy }
context 'missing dashboard_path' do
let(:params) { {} }
it { is_expected.to be_falsey }
end
context 'empty dashboard_path' do
let(:params) { { dashboard_path: '' } }
it { is_expected.to be_falsey }
end
end
end

View File

@ -1,159 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Metrics::Dashboard::UpdateDashboardService, :use_clean_rails_memory_store_caching, feature_category: :metrics do
include MetricsDashboardHelpers
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:environment) { create(:environment, project: project) }
describe '#execute' do
subject(:service_call) { described_class.new(project, user, params).execute }
let(:commit_message) { 'test' }
let(:branch) { 'dashboard_new_branch' }
let(:dashboard) { 'config/prometheus/common_metrics.yml' }
let(:file_name) { 'custom_dashboard.yml' }
let(:file_content_hash) { YAML.safe_load(File.read(dashboard)) }
let(:params) do
{
file_name: file_name,
file_content: file_content_hash,
commit_message: commit_message,
branch: branch
}
end
context 'user does not have push right to repository' do
it_behaves_like 'misconfigured dashboard service response with stepable', :forbidden, 'You are not allowed to push into this branch. Create another branch or open a merge request.'
end
context 'with rights to push to the repository' do
before do
project.add_maintainer(user)
end
context 'path traversal attack attempt' do
context 'with a yml extension' do
let(:file_name) { 'config/prometheus/../database.yml' }
it_behaves_like 'misconfigured dashboard service response with stepable', :bad_request, "A file with this name doesn't exist"
end
context 'without a yml extension' do
let(:file_name) { '../../..../etc/passwd' }
it_behaves_like 'misconfigured dashboard service response with stepable', :bad_request, 'The file name should have a .yml extension'
end
end
context 'valid parameters' do
it_behaves_like 'valid dashboard update process'
end
context 'selected branch already exists' do
let(:branch) { 'existing_branch' }
before do
project.repository.add_branch(user, branch, 'master')
end
it_behaves_like 'misconfigured dashboard service response with stepable', :bad_request, 'There was an error updating the dashboard, branch named: existing_branch already exists.'
end
context 'Files::UpdateService success' do
let(:merge_request) { project.merge_requests.last }
before do
allow(::Files::UpdateService).to receive(:new).and_return(double(execute: { status: :success }))
end
it 'returns success', :aggregate_failures do
dashboard_details = {
path: '.gitlab/dashboards/custom_dashboard.yml',
display_name: 'custom_dashboard.yml',
default: false,
system_dashboard: false
}
expect(service_call[:status]).to be :success
expect(service_call[:http_status]).to be :created
expect(service_call[:dashboard]).to match dashboard_details
expect(service_call[:merge_request]).to eq(Gitlab::UrlBuilder.build(merge_request))
end
context 'when the merge request does not succeed' do
let(:error_message) { 'There was an error' }
let(:merge_request) do
build(:merge_request, target_project: project, source_project: project, author: user)
end
before do
merge_request.errors.add(:base, error_message)
allow_next_instance_of(::MergeRequests::CreateService) do |mr|
allow(mr).to receive(:execute).and_return(merge_request)
end
end
it 'returns an appropriate message and status code', :aggregate_failures do
result = service_call
expect(result.keys).to contain_exactly(:message, :http_status, :status, :last_step)
expect(result[:status]).to eq(:error)
expect(result[:http_status]).to eq(:bad_request)
expect(result[:message]).to eq(error_message)
end
end
context 'with escaped characters in file name' do
let(:file_name) { "custom_dashboard%26copy.yml" }
it 'escapes the special characters', :aggregate_failures do
dashboard_details = {
path: '.gitlab/dashboards/custom_dashboard&copy.yml',
display_name: 'custom_dashboard&copy.yml',
default: false,
system_dashboard: false
}
expect(service_call[:status]).to be :success
expect(service_call[:http_status]).to be :created
expect(service_call[:dashboard]).to match dashboard_details
end
end
context 'when pushing to the default branch' do
let(:branch) { 'master' }
it 'does not create a merge request', :aggregate_failures do
dashboard_details = {
path: '.gitlab/dashboards/custom_dashboard.yml',
display_name: 'custom_dashboard.yml',
default: false,
system_dashboard: false
}
expect(::MergeRequests::CreateService).not_to receive(:new)
expect(service_call.keys).to contain_exactly(:dashboard, :http_status, :status)
expect(service_call[:status]).to be :success
expect(service_call[:http_status]).to be :created
expect(service_call[:dashboard]).to match dashboard_details
end
end
end
context 'Files::UpdateService fails' do
before do
allow(::Files::UpdateService).to receive(:new).and_return(double(execute: { status: :error }))
end
it 'returns error' do
expect(service_call[:status]).to be :error
end
end
end
end
end

View File

@ -1,38 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'admin/application_settings/_ai_access.html.haml', feature_category: :code_suggestions do
let_it_be(:admin) { build_stubbed(:admin) }
let(:page) { Capybara::Node::Simple.new(rendered) }
before do
allow(::Gitlab).to receive(:org_or_com?).and_return(false) # Will not render partial for .com or .org
assign(:application_setting, application_setting)
allow(view).to receive(:current_user) { admin }
allow(view).to receive(:expanded).and_return(true)
end
context 'when ai_access_token is not set' do
let(:application_setting) { build(:application_setting) }
it 'renders an empty password field' do
render
expect(rendered).to have_field('Personal access token', type: 'password')
expect(page.find_field('Personal access token').value).to be_blank
end
end
context 'when ai_access_token is set' do
let(:application_setting) do
build(:application_setting, ai_access_token: 'ai_access_token',
instance_level_code_suggestions_enabled: true)
end
it 'renders masked password field' do
render
expect(rendered).to have_field('Enter new personal access token', type: 'password')
expect(page.find_field('Enter new personal access token').value).to eq(ApplicationSettingMaskedAttrs::MASK)
end
end
end

View File

@ -114,29 +114,31 @@ RSpec.describe 'admin/application_settings/general.html.haml' do
end
end
describe 'instance-level code suggestions settings', feature_category: :code_suggestions do
# for the licensed tests, refer to ee/spec/views/admin/application_settings/general.html.haml_spec.rb
describe 'instance-level code suggestions settings', :without_license, feature_category: :code_suggestions do
before do
allow(::Gitlab).to receive(:org_or_com?).and_return(gitlab_org_or_com?)
render
end
context 'when on .com or .org' do
let(:gitlab_org_or_com?) { true }
shared_examples 'does not render the form' do
it 'does not render the form' do
expect(rendered).not_to have_field('application_setting_instance_level_code_suggestions_enabled')
expect(rendered).not_to have_field('application_setting_ai_access_token')
end
end
context 'when on .com or .org' do
let(:gitlab_org_or_com?) { true }
it_behaves_like 'does not render the form'
end
context 'when not on .com and not on .org' do
let(:gitlab_org_or_com?) { false }
it 'renders the form' do
expect(rendered).to have_field('application_setting_instance_level_code_suggestions_enabled')
expect(rendered).to have_field('application_setting_ai_access_token')
end
it_behaves_like 'does not render the form'
end
end
end