diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-01-20 00:10:45 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-01-20 00:10:45 +0300 |
commit | f265a70317c69af9d955d9a4c303b0bbcb8261ee (patch) | |
tree | fc8d8bd0d82dd713faa94fcab40235667f9267c4 | |
parent | 83e4339a328c1a8c8e232d06f7d5e49c6e699fba (diff) |
Add latest changes from gitlab-org/gitlab@master
66 files changed, 1833 insertions, 154 deletions
diff --git a/.gitlab/issue_templates/Security developer workflow.md b/.gitlab/issue_templates/Security developer workflow.md index 3de004b0319..beb066cdfc4 100644 --- a/.gitlab/issue_templates/Security developer workflow.md +++ b/.gitlab/issue_templates/Security developer workflow.md @@ -1,7 +1,7 @@ <!-- # Read me first! -Create this issue under https://gitlab.com/gitlab-org/security +Create this issue under https://gitlab.com/gitlab-org/security/gitlab Set the title to: `Description of the original issue` --> diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 7a148853798..4973ad34c70 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -6a06feda7fd01961bb332afce4d7f7b4ce4a5aad +99f78e4d93d8c9ec23ef710ffde0fb4b75d786bb diff --git a/app/assets/javascripts/design_management/components/list/item.vue b/app/assets/javascripts/design_management/components/list/item.vue index fa09c7c15cc..5402fed0a9a 100644 --- a/app/assets/javascripts/design_management/components/list/item.vue +++ b/app/assets/javascripts/design_management/components/list/item.vue @@ -1,5 +1,5 @@ <script> -import { GlLoadingIcon, GlIcon, GlIntersectionObserver } from '@gitlab/ui'; +import { GlLoadingIcon, GlIcon, GlIntersectionObserver, GlTooltipDirective } from '@gitlab/ui'; import Timeago from '~/vue_shared/components/time_ago_tooltip.vue'; import { n__, __ } from '~/locale'; import { DESIGN_ROUTE_NAME } from '../../router/constants'; @@ -11,6 +11,9 @@ export default { GlIcon, Timeago, }, + directives: { + GlTooltip: GlTooltipDirective, + }, props: { id: { type: [Number, String], @@ -130,7 +133,7 @@ export default { <div class="card-body gl-p-0 gl-display-flex gl-align-items-center gl-justify-content-center gl-overflow-hidden gl-relative" > - <div v-if="icon.name" data-testid="designEvent" class="design-event gl-absolute"> + <div v-if="icon.name" data-testid="design-event" class="design-event gl-absolute"> <span :title="icon.tooltip" :aria-label="icon.tooltip"> <gl-icon :name="icon.name" @@ -153,9 +156,10 @@ export default { v-show="showImage" :src="imageLink" :alt="filename" - class="gl-display-block gl-mx-auto gl-max-w-full mh-100 design-img" + class="gl-display-block gl-mx-auto gl-max-w-full gl-max-h-full design-img" data-qa-selector="design_image" :data-qa-filename="filename" + :data-testid="`design-img-${id}`" @load="onImageLoad" @error="onImageError" /> @@ -163,9 +167,14 @@ export default { </div> <div class="card-footer gl-display-flex gl-w-full"> <div class="gl-display-flex gl-flex-direction-column str-truncated-100"> - <span class="gl-font-weight-bold str-truncated-100" data-qa-selector="design_file_name">{{ - filename - }}</span> + <span + v-gl-tooltip + class="gl-font-weight-bold str-truncated-100" + data-qa-selector="design_file_name" + :data-testid="`design-img-filename-${id}`" + :title="filename" + >{{ filename }}</span + > <span v-if="updatedAt" class="str-truncated-100"> {{ __('Updated') }} <timeago :time="updatedAt" tooltip-placement="bottom" /> </span> diff --git a/app/graphql/types/ci_configuration/sast/analyzers_entity_input_type.rb b/app/graphql/types/ci_configuration/sast/analyzers_entity_input_type.rb new file mode 100644 index 00000000000..9835a7ef208 --- /dev/null +++ b/app/graphql/types/ci_configuration/sast/analyzers_entity_input_type.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Types + module CiConfiguration + module Sast + # rubocop: disable Graphql/AuthorizeTypes + class AnalyzersEntityInputType < BaseInputObject + graphql_name 'SastCiConfigurationAnalyzersEntityInput' + description 'Represents the analyzers entity in SAST CI configuration' + + argument :name, GraphQL::STRING_TYPE, required: true, + description: 'Name of analyzer.' + + argument :enabled, GraphQL::BOOLEAN_TYPE, required: true, + description: 'State of the analyzer.' + + argument :variables, [::Types::CiConfiguration::Sast::EntityInputType], + description: 'List of variables for the analyzer.', + required: false + end + end + end +end diff --git a/app/graphql/types/ci_configuration/sast/analyzers_entity_type.rb b/app/graphql/types/ci_configuration/sast/analyzers_entity_type.rb new file mode 100644 index 00000000000..3c6202ca7e0 --- /dev/null +++ b/app/graphql/types/ci_configuration/sast/analyzers_entity_type.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Types + module CiConfiguration + module Sast + # rubocop: disable Graphql/AuthorizeTypes + class AnalyzersEntityType < BaseObject + graphql_name 'SastCiConfigurationAnalyzersEntity' + description 'Represents an analyzer entity in SAST CI configuration' + + field :name, GraphQL::STRING_TYPE, null: true, + description: 'Name of the analyzer.' + + field :label, GraphQL::STRING_TYPE, null: true, + description: 'Analyzer label used in the config UI.' + + field :enabled, GraphQL::BOOLEAN_TYPE, null: true, + description: 'Indicates whether an analyzer is enabled.' + + field :description, GraphQL::STRING_TYPE, null: true, + description: 'Analyzer description that is displayed on the form.' + + field :variables, ::Types::CiConfiguration::Sast::EntityType.connection_type, null: true, + description: 'List of supported variables.' + end + end + end +end diff --git a/app/graphql/types/ci_configuration/sast/entity_input_type.rb b/app/graphql/types/ci_configuration/sast/entity_input_type.rb new file mode 100644 index 00000000000..39b3efb3db8 --- /dev/null +++ b/app/graphql/types/ci_configuration/sast/entity_input_type.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Types + module CiConfiguration + module Sast + # rubocop: disable Graphql/AuthorizeTypes + class EntityInputType < BaseInputObject + graphql_name 'SastCiConfigurationEntityInput' + description 'Represents an entity in SAST CI configuration' + + argument :field, GraphQL::STRING_TYPE, required: true, + description: 'CI keyword of entity.' + + argument :default_value, GraphQL::STRING_TYPE, required: true, + description: 'Default value that is used if value is empty.' + + argument :value, GraphQL::STRING_TYPE, required: true, + description: 'Current value of the entity.' + end + end + end +end diff --git a/app/graphql/types/ci_configuration/sast/entity_type.rb b/app/graphql/types/ci_configuration/sast/entity_type.rb new file mode 100644 index 00000000000..eeb9025391f --- /dev/null +++ b/app/graphql/types/ci_configuration/sast/entity_type.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module Types + module CiConfiguration + module Sast + # rubocop: disable Graphql/AuthorizeTypes + class EntityType < BaseObject + graphql_name 'SastCiConfigurationEntity' + description 'Represents an entity in SAST CI configuration' + + field :field, GraphQL::STRING_TYPE, null: true, + description: 'CI keyword of entity.' + + field :label, GraphQL::STRING_TYPE, null: true, + description: 'Label for entity used in the form.' + + field :type, GraphQL::STRING_TYPE, null: true, + description: 'Type of the field value.' + + field :options, ::Types::CiConfiguration::Sast::OptionsEntityType.connection_type, null: true, + description: 'Different possible values of the field.' + + field :default_value, GraphQL::STRING_TYPE, null: true, + description: 'Default value that is used if value is empty.' + + field :description, GraphQL::STRING_TYPE, null: true, + description: 'Entity description that is displayed on the form.' + + field :value, GraphQL::STRING_TYPE, null: true, + description: 'Current value of the entity.' + + field :size, ::Types::CiConfiguration::Sast::UiComponentSizeEnum, null: true, + description: 'Size of the UI component.' + end + end + end +end diff --git a/app/graphql/types/ci_configuration/sast/input_type.rb b/app/graphql/types/ci_configuration/sast/input_type.rb new file mode 100644 index 00000000000..615436683f6 --- /dev/null +++ b/app/graphql/types/ci_configuration/sast/input_type.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Types + module CiConfiguration + module Sast + class InputType < BaseInputObject # rubocop:disable Graphql/AuthorizeTypes + graphql_name 'SastCiConfigurationInput' + description 'Represents a CI configuration of SAST' + + argument :global, [::Types::CiConfiguration::Sast::EntityInputType], + description: 'List of global entities related to SAST configuration.', + required: false + + argument :pipeline, [::Types::CiConfiguration::Sast::EntityInputType], + description: 'List of pipeline entities related to SAST configuration.', + required: false + + argument :analyzers, [::Types::CiConfiguration::Sast::AnalyzersEntityInputType], + description: 'List of analyzers and related variables for the SAST configuration.', + required: false + end + end + end +end diff --git a/app/graphql/types/ci_configuration/sast/options_entity_type.rb b/app/graphql/types/ci_configuration/sast/options_entity_type.rb new file mode 100644 index 00000000000..86d104a7fda --- /dev/null +++ b/app/graphql/types/ci_configuration/sast/options_entity_type.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Types + module CiConfiguration + module Sast + # rubocop: disable Graphql/AuthorizeTypes + class OptionsEntityType < BaseObject + graphql_name 'SastCiConfigurationOptionsEntity' + description 'Represents an entity for options in SAST CI configuration' + + field :label, GraphQL::STRING_TYPE, null: true, + description: 'Label of option entity.' + + field :value, GraphQL::STRING_TYPE, null: true, + description: 'Value of option entity.' + end + end + end +end diff --git a/app/graphql/types/ci_configuration/sast/type.rb b/app/graphql/types/ci_configuration/sast/type.rb new file mode 100644 index 00000000000..35d11584ac7 --- /dev/null +++ b/app/graphql/types/ci_configuration/sast/type.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Types + module CiConfiguration + module Sast + # rubocop: disable Graphql/AuthorizeTypes + class Type < BaseObject + graphql_name 'SastCiConfiguration' + description 'Represents a CI configuration of SAST' + + field :global, ::Types::CiConfiguration::Sast::EntityType.connection_type, null: true, + description: 'List of global entities related to SAST configuration.' + + field :pipeline, ::Types::CiConfiguration::Sast::EntityType.connection_type, null: true, + description: 'List of pipeline entities related to SAST configuration.' + + field :analyzers, ::Types::CiConfiguration::Sast::AnalyzersEntityType.connection_type, null: true, + description: 'List of analyzers entities attached to SAST configuration.' + end + end + end +end diff --git a/app/graphql/types/ci_configuration/sast/ui_component_size_enum.rb b/app/graphql/types/ci_configuration/sast/ui_component_size_enum.rb new file mode 100644 index 00000000000..3a208f9d3e4 --- /dev/null +++ b/app/graphql/types/ci_configuration/sast/ui_component_size_enum.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Types + module CiConfiguration + module Sast + class UiComponentSizeEnum < BaseEnum + graphql_name 'SastUiComponentSize' + description 'Size of UI component in SAST configuration page' + + value 'SMALL' + value 'MEDIUM' + value 'LARGE' + end + end + end +end diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb index f66d8926a9f..41a4edd20a5 100644 --- a/app/graphql/types/project_type.rb +++ b/app/graphql/types/project_type.rb @@ -16,6 +16,10 @@ module Types field :path, GraphQL::STRING_TYPE, null: false, description: 'Path of the project' + field :sast_ci_configuration, Types::CiConfiguration::Sast::Type, null: true, + calls_gitaly: true, + description: 'SAST CI configuration for the project' + field :name_with_namespace, GraphQL::STRING_TYPE, null: false, description: 'Full name of the project with its namespace' field :name, GraphQL::STRING_TYPE, null: false, @@ -359,6 +363,12 @@ module Types project.container_repositories.size end + def sast_ci_configuration + return unless Ability.allowed?(current_user, :download_code, object) + + ::Security::CiConfiguration::SastParserService.new(object).configuration + end + private def project diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index a2a3d718fda..93f6c3c59e1 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -995,13 +995,21 @@ module Ci end def has_coverage_reports? - pipeline_artifacts&.has_code_coverage? + pipeline_artifacts&.has_report?(:code_coverage) end def can_generate_coverage_reports? has_reports?(Ci::JobArtifact.coverage_reports) end + def has_codequality_reports? + pipeline_artifacts&.has_report?(:code_quality) + end + + def can_generate_codequality_reports? + has_reports?(Ci::JobArtifact.codequality_reports) + end + def test_report_summary strong_memoize(:test_report_summary) do Gitlab::Ci::Reports::TestReportSummary.new(latest_builds_report_results) diff --git a/app/models/ci/pipeline_artifact.rb b/app/models/ci/pipeline_artifact.rb index b6db8cad667..437657ade2c 100644 --- a/app/models/ci/pipeline_artifact.rb +++ b/app/models/ci/pipeline_artifact.rb @@ -30,15 +30,18 @@ module Ci update_project_statistics project_statistics_name: :pipeline_artifacts_size enum file_type: { - code_coverage: 1 + code_coverage: 1, + code_quality: 2 } - def self.has_code_coverage? - where(file_type: :code_coverage).exists? - end + class << self + def has_report?(file_type) + where(file_type: file_type).exists? + end - def self.find_with_code_coverage - find_by(file_type: :code_coverage) + def find_by_file_type(file_type) + find_by(file_type: file_type) + end end def present diff --git a/app/services/ci/generate_coverage_reports_service.rb b/app/services/ci/generate_coverage_reports_service.rb index 063fb966183..b3aa7b3091b 100644 --- a/app/services/ci/generate_coverage_reports_service.rb +++ b/app/services/ci/generate_coverage_reports_service.rb @@ -12,7 +12,7 @@ module Ci { status: :parsed, key: key(base_pipeline, head_pipeline), - data: head_pipeline.pipeline_artifacts.find_with_code_coverage.present.for_files(merge_request.new_paths) + data: head_pipeline.pipeline_artifacts.find_by_file_type(:code_coverage).present.for_files(merge_request.new_paths) } rescue => e Gitlab::ErrorTracking.track_exception(e, project_id: project.id) diff --git a/app/services/security/ci_configuration/sast_parser_service.rb b/app/services/security/ci_configuration/sast_parser_service.rb new file mode 100644 index 00000000000..5698c949cbd --- /dev/null +++ b/app/services/security/ci_configuration/sast_parser_service.rb @@ -0,0 +1,128 @@ +# frozen_string_literal: true + +module Security + module CiConfiguration + # This class parses SAST template file and .gitlab-ci.yml to populate default and current values into the JSON + # read from app/validators/json_schemas/security_ci_configuration_schemas/sast_ui_schema.json + class SastParserService < ::BaseService + include Gitlab::Utils::StrongMemoize + + SAST_UI_SCHEMA_PATH = 'app/validators/json_schemas/security_ci_configuration_schemas/sast_ui_schema.json' + + def initialize(project) + @project = project + end + + def configuration + result = Gitlab::Json.parse(File.read(Rails.root.join(SAST_UI_SCHEMA_PATH))).with_indifferent_access + populate_default_value_for(result, :global) + populate_default_value_for(result, :pipeline) + fill_current_value_with_default_for(result, :global) + fill_current_value_with_default_for(result, :pipeline) + populate_current_value_for(result, :global) + populate_current_value_for(result, :pipeline) + + fill_current_value_with_default_for_analyzers(result) + populate_current_value_for_analyzers(result) + + result + end + + private + + def sast_template_content + Gitlab::Template::GitlabCiYmlTemplate.find('SAST').content + end + + def populate_default_value_for(config, level) + set_each(config[level], key: :default_value, with: sast_template_attributes) + end + + def populate_current_value_for(config, level) + set_each(config[level], key: :value, with: gitlab_ci_yml_attributes) + end + + def fill_current_value_with_default_for(config, level) + set_each(config[level], key: :value, with: sast_template_attributes) + end + + def set_each(config_attributes, key:, with:) + config_attributes.each do |entity| + entity[key] = with[entity[:field]] if with[entity[:field]] + end + end + + def fill_current_value_with_default_for_analyzers(result) + result[:analyzers].each do |analyzer| + analyzer[:variables].each do |entity| + entity[:value] = entity[:default_value] if entity[:default_value] + end + end + end + + def populate_current_value_for_analyzers(result) + result[:analyzers].each do |analyzer| + analyzer[:enabled] = analyzer_enabled?(analyzer[:name]) + populate_current_value_for(analyzer, :variables) + end + end + + def analyzer_enabled?(analyzer_name) + # Unless explicitly listed in the excluded analyzers, consider it enabled + sast_excluded_analyzers.exclude?(analyzer_name) + end + + def sast_excluded_analyzers + strong_memoize(:sast_excluded_analyzers) do + all_analyzers = Security::CiConfiguration::SastBuildActions::SAST_DEFAULT_ANALYZERS.split(', ') rescue [] + enabled_analyzers = sast_default_analyzers.split(',').map(&:strip) rescue [] + + excluded_analyzers = gitlab_ci_yml_attributes["SAST_EXCLUDED_ANALYZERS"] || sast_template_attributes["SAST_EXCLUDED_ANALYZERS"] + excluded_analyzers = excluded_analyzers.split(',').map(&:strip) rescue [] + ((all_analyzers - enabled_analyzers) + excluded_analyzers).uniq + end + end + + def sast_default_analyzers + @sast_default_analyzers ||= gitlab_ci_yml_attributes["SAST_DEFAULT_ANALYZERS"] || sast_template_attributes["SAST_DEFAULT_ANALYZERS"] + end + + def sast_template_attributes + @sast_template_attributes ||= build_sast_attributes(sast_template_content) + end + + def gitlab_ci_yml_attributes + @gitlab_ci_yml_attributes ||= begin + config_content = @project.repository.blob_data_at(@project.repository.root_ref_sha, ci_config_file) + + return {} unless config_content + + build_sast_attributes(config_content) + end + end + + def ci_config_file + '.gitlab-ci.yml' + end + + def build_sast_attributes(content) + options = { project: @project, user: current_user, sha: @project.repository.commit.sha } + yaml_result = Gitlab::Ci::YamlProcessor.new(content, options).execute + return {} unless yaml_result.valid? + + sast_attributes = yaml_result.build_attributes(:sast) + extract_required_attributes(sast_attributes) + end + + def extract_required_attributes(attributes) + result = {} + attributes[:yaml_variables].each do |variable| + result[variable[:key]] = variable[:value] + end + + result[:stage] = attributes[:stage] + result.with_indifferent_access + end + end + end +end diff --git a/app/views/projects/default_branch/_show.html.haml b/app/views/projects/default_branch/_show.html.haml index a1c7f5027c5..4e504f4c319 100644 --- a/app/views/projects/default_branch/_show.html.haml +++ b/app/views/projects/default_branch/_show.html.haml @@ -6,7 +6,7 @@ %button.btn.js-settings-toggle = expanded ? _('Collapse') : _('Expand') %p - = _('Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one.') + = _('Set the default branch for this project. All merge requests and commits are made against this branch unless you specify a different one.') .settings-content = form_for @project, remote: true, html: { multipart: true, anchor: 'default-branch-settings' }, authenticity_token: true do |f| @@ -25,7 +25,7 @@ = f.label :autoclose_referenced_issues, class: 'form-check-label' do %strong= _("Auto-close referenced issues on default branch") .form-text.text-muted - = _("Issues referenced by merge requests and commits within the default branch will be closed automatically") + = _("When merge requests and commits in the default branch close, any issues they reference also close.") = link_to sprite_icon('question-o'), help_page_path('user/project/issues/managing_issues.md', anchor: 'disabling-automatic-issue-closing'), target: '_blank' = f.submit _('Save changes'), class: "gl-button btn btn-success" diff --git a/app/views/shared/notes/_edit_form.html.haml b/app/views/shared/notes/_edit_form.html.haml index d783fa0d777..79d42bd5f6d 100644 --- a/app/views/shared/notes/_edit_form.html.haml +++ b/app/views/shared/notes/_edit_form.html.haml @@ -9,6 +9,6 @@ .note-form-actions.clearfix .settings-message.note-edit-warning.js-finish-edit-warning = _("Finish editing this message first!") - = submit_tag _('Save comment'), class: 'btn btn-success js-comment-save-button', data: { qa_selector: 'save_comment_button' } - %button.btn.btn-cancel.note-edit-cancel{ type: 'button' } + = submit_tag _('Save comment'), class: 'gl-button btn btn-success js-comment-save-button', data: { qa_selector: 'save_comment_button' } + %button.btn.gl-button.btn-cancel.note-edit-cancel{ type: 'button' } = _("Cancel") diff --git a/app/views/shared/notes/_form.html.haml b/app/views/shared/notes/_form.html.haml index 2cf074b9d3f..6f54c54d0a9 100644 --- a/app/views/shared/notes/_form.html.haml +++ b/app/views/shared/notes/_form.html.haml @@ -38,5 +38,5 @@ .note-form-actions.clearfix = render partial: 'shared/notes/comment_button' - %a.btn.btn-cancel.js-close-discussion-note-form.hide{ role: "button", data: { cancel_text: _("Cancel") } } + %a.btn.gl-button.btn-cancel.js-close-discussion-note-form.hide{ role: "button", data: { cancel_text: _("Cancel") } } = _('Cancel') diff --git a/changelogs/unreleased/ApplyGItLabUIstylesto_edit_form-html-haml.yml b/changelogs/unreleased/ApplyGItLabUIstylesto_edit_form-html-haml.yml new file mode 100644 index 00000000000..1eeb8dfddb3 --- /dev/null +++ b/changelogs/unreleased/ApplyGItLabUIstylesto_edit_form-html-haml.yml @@ -0,0 +1,5 @@ +--- +title: Adds GitLab UI styles to button in _edit_form.html.haml +merge_request: 51156 +author: nuwe1 +type: other diff --git a/changelogs/unreleased/ApplyGitLabUIstylestobuttonin_form-html-haml.yml b/changelogs/unreleased/ApplyGitLabUIstylestobuttonin_form-html-haml.yml new file mode 100644 index 00000000000..4f86af7b884 --- /dev/null +++ b/changelogs/unreleased/ApplyGitLabUIstylestobuttonin_form-html-haml.yml @@ -0,0 +1,5 @@ +--- +title: Adds GitLab UI styles to button in _form.html.haml +merge_request: 51160 +author: nuwe1 +type: other diff --git a/changelogs/unreleased/ek-display-full-design-name-in-tooltip.yml b/changelogs/unreleased/ek-display-full-design-name-in-tooltip.yml new file mode 100644 index 00000000000..16fcd67aebb --- /dev/null +++ b/changelogs/unreleased/ek-display-full-design-name-in-tooltip.yml @@ -0,0 +1,5 @@ +--- +title: Display full design name in tooltip +merge_request: 51421 +author: +type: changed diff --git a/changelogs/unreleased/ui-text-default-branch.yml b/changelogs/unreleased/ui-text-default-branch.yml new file mode 100644 index 00000000000..7881d0f79ef --- /dev/null +++ b/changelogs/unreleased/ui-text-default-branch.yml @@ -0,0 +1,5 @@ +--- +title: Updated UI text to match style guidelines +merge_request: 51658 +author: +type: other diff --git a/config/initializers/validate_puma.rb b/config/initializers/validate_puma.rb index ac5678c4b5a..666868fdfb9 100644 --- a/config/initializers/validate_puma.rb +++ b/config/initializers/validate_puma.rb @@ -1,5 +1,13 @@ # frozen_string_literal: true if Gitlab::Runtime.puma? && ::Puma.cli_config.options[:workers].to_i == 0 + return if allow_single_mode? + raise 'Puma is only supported in Cluster-mode: workers > 0' end + +def allow_single_mode? + return false if Gitlab.com? + + Gitlab::Utils.to_boolean(ENV['PUMA_SKIP_CLUSTER_VALIDATION']) +end diff --git a/doc/.vale/gitlab/Acronyms.yml b/doc/.vale/gitlab/Acronyms.yml index 481da94c627..b6a7cb61898 100644 --- a/doc/.vale/gitlab/Acronyms.yml +++ b/doc/.vale/gitlab/Acronyms.yml @@ -157,3 +157,4 @@ exceptions: - XML - XSS - YAML + - ZIP diff --git a/doc/administration/monitoring/performance/gitlab_configuration.md b/doc/administration/monitoring/performance/gitlab_configuration.md index dd4481434a6..f4955e95692 100644 --- a/doc/administration/monitoring/performance/gitlab_configuration.md +++ b/doc/administration/monitoring/performance/gitlab_configuration.md @@ -4,22 +4,24 @@ group: Health info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# GitLab Configuration +# GitLab configuration GitLab Performance Monitoring is disabled by default. To enable it and change any of its settings: 1. Navigate to **Admin Area > Settings > Metrics and profiling** - (`/admin/application_settings/metrics_and_profiling`): - - ![GitLab Performance Monitoring Administration Settings](img/metrics_gitlab_configuration_settings.png) - -1. You must restart all GitLab processes for the changes to take effect: + (`/admin/application_settings/metrics_and_profiling`). +1. Add the necessary configuration changes. +1. Restart all GitLab for the changes to take effect: - For Omnibus GitLab installations: `sudo gitlab-ctl restart` - For installations from source: `sudo service gitlab restart` -## Pending Migrations +NOTE: +Removed [in GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/30786). Use the +[Prometheus integration](../prometheus/index.md) instead. + +## Pending migrations When any migrations are pending, the metrics are disabled until the migrations have been performed. diff --git a/doc/administration/monitoring/performance/grafana_configuration.md b/doc/administration/monitoring/performance/grafana_configuration.md index e76e81c6499..d768ecaf365 100644 --- a/doc/administration/monitoring/performance/grafana_configuration.md +++ b/doc/administration/monitoring/performance/grafana_configuration.md @@ -17,42 +17,38 @@ or Grafana supplies package repositories (Yum/Apt) for easy installation. See [Grafana installation documentation](https://grafana.com/docs/grafana/latest/installation/) for detailed steps. -Before starting Grafana for the first time, set the admin user +Before starting Grafana for the first time, set the administration user and password in `/etc/grafana/grafana.ini`. If you don't, the default password is `admin`. ## Configuration -1. Log in to Grafana as the admin user. -1. Expand the menu by clicking the Grafana logo in the top left corner. -1. Choose **Data Sources** from the menu. -1. Click **Add new** in the top bar: - ![Grafana empty data source page](img/grafana_data_source_empty.png) -1. Edit the data source to fit your needs: - ![Grafana data source configurations](img/grafana_data_source_configuration.png) -1. Click **Save**. +1. Log in to Grafana as the administration user. +1. Select **Data Sources** from the **Configuration** menu. +1. Select the **Add data source** button. +1. Select the required data source type. For example, [Prometheus](../prometheus/index.md#prometheus-as-a-grafana-data-source). +1. Complete the details for the data source and select the **Save & Test** button. + +Grafana should indicate the data source is working. ## Import Dashboards You can now import a set of default dashboards to start displaying useful information. GitLab has published a set of default -[Grafana dashboards](https://gitlab.com/gitlab-org/grafana-dashboards) to get you started. -Clone the repository, or download a ZIP file or tarball, then follow these steps to import each -JSON file individually: +[Grafana dashboards](https://gitlab.com/gitlab-org/grafana-dashboards) to get you started. To use +them: -1. Log in to Grafana as the admin user. -1. Open the dashboard dropdown menu and click **Import**: - ![Grafana dashboard dropdown](img/grafana_dashboard_dropdown.png) -1. Click **Choose file**, and browse to the location where you downloaded or - cloned the dashboard repository. Select a JSON file to import: - ![Grafana dashboard import](img/grafana_dashboard_import.png) -1. After the dashboard is imported, click the **Save dashboard** icon in the top bar: - ![Grafana save icon](img/grafana_save_icon.png) +1. Clone the repository, or download a ZIP file or tarball. +1. Follow these steps to import each JSON file individually: - If you don't save the dashboard after importing it, the dashboard is removed - when you navigate away from the page. + 1. Log in to Grafana as the administration user. + 1. Select **Manage** from the **Dashboards** menu. + 1. Select the **Import** button, then the **Upload JSON file** button. + 1. Locate the JSON file to import and select **Choose for Upload**. Select the **Import** button. + 1. After the dashboard is imported, select the **Save dashboard** icon in the top bar. -Repeat this process for each dashboard you wish to import. +If you don't save the dashboard after importing it, the dashboard is removed +when you navigate away from the page. Repeat this process for each dashboard you wish to import. Alternatively, you can import all the dashboards into your Grafana instance. For more information about this process, see the @@ -103,7 +99,7 @@ sudo mv /var/opt/gitlab/grafana/data.bak.xxxx/ /var/opt/gitlab/grafana/data/ However, you should **not** reinstate your old data _except_ under one of the following conditions: -1. If you're certain that you changed your default admin password when you enabled Grafana. +1. If you're certain that you changed your default administration password when you enabled Grafana. 1. If you run GitLab in a private network, accessed only by trusted users, and your Grafana login page has not been exposed to the internet. @@ -121,8 +117,6 @@ existing data and dashboards. For more information and further mitigation details, please refer to our [blog post on the security release](https://about.gitlab.com/releases/2019/08/12/critical-security-release-gitlab-12-dot-1-dot-6-released/). ---- - Read more on: - [Introduction to GitLab Performance Monitoring](index.md) diff --git a/doc/administration/monitoring/performance/img/grafana_dashboard_dropdown.png b/doc/administration/monitoring/performance/img/grafana_dashboard_dropdown.png Binary files differdeleted file mode 100644 index 51eef90068d..00000000000 --- a/doc/administration/monitoring/performance/img/grafana_dashboard_dropdown.png +++ /dev/null diff --git a/doc/administration/monitoring/performance/img/grafana_dashboard_import.png b/doc/administration/monitoring/performance/img/grafana_dashboard_import.png Binary files differdeleted file mode 100644 index fd639ee0eb8..00000000000 --- a/doc/administration/monitoring/performance/img/grafana_dashboard_import.png +++ /dev/null diff --git a/doc/administration/monitoring/performance/img/grafana_data_source_configuration.png b/doc/administration/monitoring/performance/img/grafana_data_source_configuration.png Binary files differdeleted file mode 100644 index a98e0ed1e7d..00000000000 --- a/doc/administration/monitoring/performance/img/grafana_data_source_configuration.png +++ /dev/null diff --git a/doc/administration/monitoring/performance/img/grafana_data_source_empty.png b/doc/administration/monitoring/performance/img/grafana_data_source_empty.png Binary files differdeleted file mode 100644 index 549ada8343e..00000000000 --- a/doc/administration/monitoring/performance/img/grafana_data_source_empty.png +++ /dev/null diff --git a/doc/administration/monitoring/performance/img/grafana_save_icon.png b/doc/administration/monitoring/performance/img/grafana_save_icon.png Binary files differdeleted file mode 100644 index 68a071f5ae2..00000000000 --- a/doc/administration/monitoring/performance/img/grafana_save_icon.png +++ /dev/null diff --git a/doc/administration/monitoring/performance/img/metrics_gitlab_configuration_settings.png b/doc/administration/monitoring/performance/img/metrics_gitlab_configuration_settings.png Binary files differdeleted file mode 100644 index 13bfd097b81..00000000000 --- a/doc/administration/monitoring/performance/img/metrics_gitlab_configuration_settings.png +++ /dev/null diff --git a/doc/administration/monitoring/prometheus/index.md b/doc/administration/monitoring/prometheus/index.md index 80ddb87e727..df462d5052d 100644 --- a/doc/administration/monitoring/prometheus/index.md +++ b/doc/administration/monitoring/prometheus/index.md @@ -6,6 +6,11 @@ info: To determine the technical writer assigned to the Stage/Group associated w # Monitoring GitLab with Prometheus +[Prometheus](https://prometheus.io) is a powerful time-series monitoring service, providing a flexible +platform for monitoring GitLab and other software products. +GitLab provides out-of-the-box monitoring with Prometheus, providing easy +access to high quality time-series monitoring of GitLab services. + > **Notes:** > > - Prometheus and the various exporters listed in this page are bundled in the @@ -16,11 +21,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w > - Prometheus and its exporters don't authenticate users, and are available > to anyone who can access them. -[Prometheus](https://prometheus.io) is a powerful time-series monitoring service, providing a flexible -platform for monitoring GitLab and other software products. -GitLab provides out of the box monitoring with Prometheus, providing easy -access to high quality time-series monitoring of GitLab services. - ## Overview Prometheus works by periodically connecting to data sources and collecting their diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md index 9e949468805..37dc03a297d 100644 --- a/doc/administration/pages/index.md +++ b/doc/administration/pages/index.md @@ -222,7 +222,7 @@ control over how the Pages daemon runs and serves content in your environment. | `api_secret_key` | Full path to file with secret key used to authenticate with the GitLab API. Auto-generated when left unset. | `artifacts_server` | Enable viewing [artifacts](../job_artifacts.md) in GitLab Pages. | `artifacts_server_timeout` | Timeout (in seconds) for a proxied request to the artifacts server. -| `artifacts_server_url` | API URL to proxy artifact requests to. Defaults to GitLab `external URL` + `/api/v4`, for example `https://gitlab.com/api/v4`. +| `artifacts_server_url` | API URL to proxy artifact requests to. Defaults to GitLab `external URL` + `/api/v4`, for example `https://gitlab.com/api/v4`. When running a [separate Pages server](#running-gitlab-pages-on-a-separate-server), this URL must point to the main GitLab server's API. | `auth_redirect_uri` | Callback URL for authenticating with GitLab. Defaults to project's subdomain of `pages_external_url` + `/auth`. | `auth_secret` | Secret key for signing authentication requests. Leave blank to pull automatically from GitLab during OAuth registration. | `dir` | Working directory for configuration and secrets files. @@ -940,6 +940,8 @@ Upgrading to an [officially supported operating system](https://about.gitlab.com This problem comes from the permissions of the GitLab Pages OAuth application. To fix it, go to **Admin > Applications > GitLab Pages** and edit the application. Under **Scopes**, ensure that the `api` scope is selected and save your changes. +When running a [separate Pages server](#running-gitlab-pages-on-a-separate-server), +this setting needs to be configured on the main GitLab server. ### Workaround in case no wildcard DNS entry can be set diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql index 146e9ad0cf7..3cbc493b2dc 100644 --- a/doc/api/graphql/reference/gitlab_schema.graphql +++ b/doc/api/graphql/reference/gitlab_schema.graphql @@ -22102,27 +22102,27 @@ Represents an analyzer entity in SAST CI configuration """ type SastCiConfigurationAnalyzersEntity { """ - Analyzer description that is displayed on the form + Analyzer description that is displayed on the form. """ description: String """ - Indicates whether an analyzer is enabled + Indicates whether an analyzer is enabled. """ enabled: Boolean """ - Analyzer label used in the config UI + Analyzer label used in the config UI. """ label: String """ - Name of the analyzer + Name of the analyzer. """ name: String """ - List of supported variables + List of supported variables. """ variables( """ @@ -22187,17 +22187,17 @@ Represents the analyzers entity in SAST CI configuration """ input SastCiConfigurationAnalyzersEntityInput { """ - State of the analyzer + State of the analyzer. """ enabled: Boolean! """ - Name of analyzer + Name of analyzer. """ name: String! """ - List of variables for the analyzer + List of variables for the analyzer. """ variables: [SastCiConfigurationEntityInput!] } @@ -22307,17 +22307,17 @@ Represents an entity in SAST CI configuration """ input SastCiConfigurationEntityInput { """ - Default value that is used if value is empty + Default value that is used if value is empty. """ defaultValue: String! """ - CI keyword of entity + CI keyword of entity. """ field: String! """ - Current value of the entity + Current value of the entity. """ value: String! } @@ -22327,17 +22327,17 @@ Represents a CI configuration of SAST """ input SastCiConfigurationInput { """ - List of analyzers and related variables for the SAST configuration + List of analyzers and related variables for the SAST configuration. """ analyzers: [SastCiConfigurationAnalyzersEntityInput!] """ - List of global entities related to SAST configuration + List of global entities related to SAST configuration. """ global: [SastCiConfigurationEntityInput!] """ - List of pipeline entities related to SAST configuration + List of pipeline entities related to SAST configuration. """ pipeline: [SastCiConfigurationEntityInput!] } diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json index f3fd31f85eb..06171385f37 100644 --- a/doc/api/graphql/reference/gitlab_schema.json +++ b/doc/api/graphql/reference/gitlab_schema.json @@ -63880,7 +63880,7 @@ "fields": [ { "name": "description", - "description": "Analyzer description that is displayed on the form", + "description": "Analyzer description that is displayed on the form.", "args": [ ], @@ -63894,7 +63894,7 @@ }, { "name": "enabled", - "description": "Indicates whether an analyzer is enabled", + "description": "Indicates whether an analyzer is enabled.", "args": [ ], @@ -63908,7 +63908,7 @@ }, { "name": "label", - "description": "Analyzer label used in the config UI", + "description": "Analyzer label used in the config UI.", "args": [ ], @@ -63922,7 +63922,7 @@ }, { "name": "name", - "description": "Name of the analyzer", + "description": "Name of the analyzer.", "args": [ ], @@ -63936,7 +63936,7 @@ }, { "name": "variables", - "description": "List of supported variables", + "description": "List of supported variables.", "args": [ { "name": "after", @@ -64115,7 +64115,7 @@ "inputFields": [ { "name": "name", - "description": "Name of analyzer", + "description": "Name of analyzer.", "type": { "kind": "NON_NULL", "name": null, @@ -64129,7 +64129,7 @@ }, { "name": "enabled", - "description": "State of the analyzer", + "description": "State of the analyzer.", "type": { "kind": "NON_NULL", "name": null, @@ -64143,7 +64143,7 @@ }, { "name": "variables", - "description": "List of variables for the analyzer", + "description": "List of variables for the analyzer.", "type": { "kind": "LIST", "name": null, @@ -64448,7 +64448,7 @@ "inputFields": [ { "name": "field", - "description": "CI keyword of entity", + "description": "CI keyword of entity.", "type": { "kind": "NON_NULL", "name": null, @@ -64462,7 +64462,7 @@ }, { "name": "defaultValue", - "description": "Default value that is used if value is empty", + "description": "Default value that is used if value is empty.", "type": { "kind": "NON_NULL", "name": null, @@ -64476,7 +64476,7 @@ }, { "name": "value", - "description": "Current value of the entity", + "description": "Current value of the entity.", "type": { "kind": "NON_NULL", "name": null, @@ -64501,7 +64501,7 @@ "inputFields": [ { "name": "global", - "description": "List of global entities related to SAST configuration", + "description": "List of global entities related to SAST configuration.", "type": { "kind": "LIST", "name": null, @@ -64519,7 +64519,7 @@ }, { "name": "pipeline", - "description": "List of pipeline entities related to SAST configuration", + "description": "List of pipeline entities related to SAST configuration.", "type": { "kind": "LIST", "name": null, @@ -64537,7 +64537,7 @@ }, { "name": "analyzers", - "description": "List of analyzers and related variables for the SAST configuration", + "description": "List of analyzers and related variables for the SAST configuration.", "type": { "kind": "LIST", "name": null, diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 2cdbbcc89a0..a16d7f70751 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -3183,11 +3183,11 @@ Represents an analyzer entity in SAST CI configuration. | Field | Type | Description | | ----- | ---- | ----------- | -| `description` | String | Analyzer description that is displayed on the form | -| `enabled` | Boolean | Indicates whether an analyzer is enabled | -| `label` | String | Analyzer label used in the config UI | -| `name` | String | Name of the analyzer | -| `variables` | SastCiConfigurationEntityConnection | List of supported variables | +| `description` | String | Analyzer description that is displayed on the form. | +| `enabled` | Boolean | Indicates whether an analyzer is enabled. | +| `label` | String | Analyzer label used in the config UI. | +| `name` | String | Name of the analyzer. | +| `variables` | SastCiConfigurationEntityConnection | List of supported variables. | ### SastCiConfigurationEntity diff --git a/doc/development/approval_rules.md b/doc/development/approval_rules.md index 542bce1cb97..4222cd17d65 100644 --- a/doc/development/approval_rules.md +++ b/doc/development/approval_rules.md @@ -4,7 +4,7 @@ group: Source Code info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Approval Rules **(STARTER)** +# Approval Rules development guide This document explains the backend design and flow of all related functionality about [merge request approval rules](../user/project/merge_requests/merge_request_approvals.md). diff --git a/doc/user/gitlab_com/index.md b/doc/user/gitlab_com/index.md index 611c1105961..770a815f8f1 100644 --- a/doc/user/gitlab_com/index.md +++ b/doc/user/gitlab_com/index.md @@ -622,13 +622,6 @@ dropped and users get To help avoid abuse, project and group imports, exports, and export downloads are rate limited. See [Project import/export rate limits](../../user/project/settings/import_export.md#rate-limits) and [Group import/export rate limits](../../user/group/settings/import_export.md#rate-limits) for details. -GitLab.com Import/Export Rate Limits are set to the default except: - -| Setting | GitLab.com | Default | -|:-------------------------------------------------|:-----------|:--------| -| Max Project Export requests per minute per user | 1 | 6 | -| Max Group Export requests per minute per user | 1 | 6 | - ### Non-configurable limits See [non-configurable limits](../../security/rate_limits.md#non-configurable-limits) for information on diff --git a/doc/user/project/settings/project_access_tokens.md b/doc/user/project/settings/project_access_tokens.md index 39de9ab9ca2..68f69f3ac8b 100644 --- a/doc/user/project/settings/project_access_tokens.md +++ b/doc/user/project/settings/project_access_tokens.md @@ -11,10 +11,8 @@ NOTE: Project access tokens are supported for self-managed instances on Core and above. They are also supported on GitLab.com Bronze and above (excluding [trial licenses](https://about.gitlab.com/free-trial/)). > - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/2587) in GitLab 13.0. -> - It was [deployed](https://gitlab.com/groups/gitlab-org/-/epics/2587) behind a feature flag, disabled by default. -> - [Became enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/218722) in GitLab 13.3. -> - It's recommended for production use. -> - [Became available on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/235765) in 13.5 for paid groups only. +> - [Became available on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/235765) in GitLab 13.5 for paid groups only. +> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/235765) in GitLab 13.5. WARNING: This feature might not be available to you. Check the **version history** note above for details. diff --git a/lib/gitlab/template/finders/global_template_finder.rb b/lib/gitlab/template/finders/global_template_finder.rb index 9b39d386674..6d2677175e6 100644 --- a/lib/gitlab/template/finders/global_template_finder.rb +++ b/lib/gitlab/template/finders/global_template_finder.rb @@ -5,9 +5,10 @@ module Gitlab module Template module Finders class GlobalTemplateFinder < BaseTemplateFinder - def initialize(base_dir, extension, categories = {}, excluded_patterns: []) + def initialize(base_dir, extension, categories = {}, include_categories_for_file = {}, excluded_patterns: []) @categories = categories @extension = extension + @include_categories_for_file = include_categories_for_file @excluded_patterns = excluded_patterns super(base_dir) @@ -47,7 +48,9 @@ module Gitlab end def select_directory(file_name) - @categories.keys.find do |category| + categories = @categories + categories.merge!(@include_categories_for_file[file_name]) if @include_categories_for_file[file_name].present? + categories.keys.find do |category| File.exist?(File.join(category_directory(category), file_name)) end end diff --git a/lib/gitlab/template/gitlab_ci_yml_template.rb b/lib/gitlab/template/gitlab_ci_yml_template.rb index c295cc75da5..01158cafc4f 100644 --- a/lib/gitlab/template/gitlab_ci_yml_template.rb +++ b/lib/gitlab/template/gitlab_ci_yml_template.rb @@ -25,6 +25,12 @@ module Gitlab } end + def include_categories_for_file + { + "SAST#{self.extension}" => { 'Security' => 'Security' } + } + end + def excluded_patterns strong_memoize(:excluded_patterns) do BASE_EXCLUDED_PATTERNS + additional_excluded_patterns @@ -41,7 +47,11 @@ module Gitlab def finder(project = nil) Gitlab::Template::Finders::GlobalTemplateFinder.new( - self.base_dir, self.extension, self.categories, excluded_patterns: self.excluded_patterns + self.base_dir, + self.extension, + self.categories, + self.include_categories_for_file, + excluded_patterns: self.excluded_patterns ) end end diff --git a/lib/security/ci_configuration/sast_build_actions.rb b/lib/security/ci_configuration/sast_build_actions.rb new file mode 100644 index 00000000000..b2d684bc1e1 --- /dev/null +++ b/lib/security/ci_configuration/sast_build_actions.rb @@ -0,0 +1,170 @@ +# frozen_string_literal: true + +module Security + module CiConfiguration + class SastBuildActions + SAST_DEFAULT_ANALYZERS = 'bandit, brakeman, eslint, flawfinder, gosec, kubesec, nodejs-scan, phpcs-security-audit, pmd-apex, security-code-scan, sobelow, spotbugs' + + def initialize(auto_devops_enabled, params, existing_gitlab_ci_content) + @auto_devops_enabled = auto_devops_enabled + @variables = variables(params) + @existing_gitlab_ci_content = existing_gitlab_ci_content || {} + @default_sast_values = default_sast_values(params) + @default_values_overwritten = false + end + + def generate + action = @existing_gitlab_ci_content.present? ? 'update' : 'create' + + update_existing_content! + + [{ action: action, file_path: '.gitlab-ci.yml', content: prepare_existing_content, default_values_overwritten: @default_values_overwritten }] + end + + private + + def variables(params) + # This early return is necessary for supporting REST API. + # Will be removed during the implementation of + # https://gitlab.com/gitlab-org/gitlab/-/issues/246737 + return params unless params['global'].present? + + collect_values(params, 'value') + end + + def default_sast_values(params) + collect_values(params, 'defaultValue') + end + + def collect_values(config, key) + global_variables = config['global']&.to_h { |k| [k['field'], k[key]] } || {} + pipeline_variables = config['pipeline']&.to_h { |k| [k['field'], k[key]] } || {} + + analyzer_variables = collect_analyzer_values(config, key) + + global_variables.merge!(pipeline_variables).merge!(analyzer_variables) + end + + def collect_analyzer_values(config, key) + analyzer_variables = analyzer_variables_for(config, key) + analyzer_variables['SAST_EXCLUDED_ANALYZERS'] = if key == 'value' + config['analyzers'] + &.reject {|a| a['enabled'] } + &.collect {|a| a['name'] } + &.sort + &.join(', ') + else + '' + end + + analyzer_variables + end + + def analyzer_variables_for(config, key) + config['analyzers'] + &.select {|a| a['enabled'] && a['variables'] } + &.flat_map {|a| a['variables'] } + &.collect {|v| [v['field'], v[key]] }.to_h + end + + def update_existing_content! + @existing_gitlab_ci_content['stages'] = set_stages + @existing_gitlab_ci_content['variables'] = set_variables(global_variables, @existing_gitlab_ci_content) + @existing_gitlab_ci_content['sast'] = set_sast_block + @existing_gitlab_ci_content['include'] = set_includes + + @existing_gitlab_ci_content.select! { |k, v| v.present? } + @existing_gitlab_ci_content['sast'].select! { |k, v| v.present? } + end + + def set_includes + includes = @existing_gitlab_ci_content['include'] || [] + includes = includes.is_a?(Array) ? includes : [includes] + includes << { 'template' => template } + includes.uniq + end + + def set_stages + existing_stages = @existing_gitlab_ci_content['stages'] || [] + base_stages = @auto_devops_enabled ? auto_devops_stages : ['test'] + (existing_stages + base_stages + [sast_stage]).uniq + end + + def auto_devops_stages + auto_devops_template = YAML.safe_load( Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps').content ) + auto_devops_template['stages'] + end + + def sast_stage + @variables['stage'].presence ? @variables['stage'] : 'test' + end + + def set_variables(variables, hash_to_update = {}) + hash_to_update['variables'] ||= {} + + variables.each do |key| + if @variables[key].present? && @variables[key].to_s != @default_sast_values[key].to_s + hash_to_update['variables'][key] = @variables[key] + @default_values_overwritten = true + else + hash_to_update['variables'].delete(key) + end + end + + hash_to_update['variables'] + end + + def set_sast_block + sast_content = @existing_gitlab_ci_content['sast'] || {} + sast_content['variables'] = set_variables(sast_variables) + sast_content['stage'] = sast_stage + sast_content.select { |k, v| v.present? } + end + + def prepare_existing_content + content = @existing_gitlab_ci_content.to_yaml + content = remove_document_delimeter(content) + + content.prepend(sast_comment) + end + + def remove_document_delimeter(content) + content.gsub(/^---\n/, '') + end + + def sast_comment + <<~YAML + # You can override the included template(s) by including variable overrides + # See https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings + # Note that environment variables can be set in several places + # See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables + YAML + end + + def template + return 'Auto-DevOps.gitlab-ci.yml' if @auto_devops_enabled + + 'Security/SAST.gitlab-ci.yml' + end + + def global_variables + %w( + SECURE_ANALYZERS_PREFIX + ) + end + + def sast_variables + %w( + SAST_ANALYZER_IMAGE_TAG + SAST_EXCLUDED_PATHS + SEARCH_MAX_DEPTH + SAST_EXCLUDED_ANALYZERS + SAST_BRAKEMAN_LEVEL + SAST_BANDIT_EXCLUDED_PATHS + SAST_FLAWFINDER_LEVEL + SAST_GOSEC_LEVEL + ) + end + end + end +end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index e47bbec804f..77fb1651295 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -15899,9 +15899,6 @@ msgstr "" msgid "Issues closed" msgstr "" -msgid "Issues referenced by merge requests and commits within the default branch will be closed automatically" -msgstr "" - msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities" msgstr "" @@ -25494,9 +25491,6 @@ msgstr "" msgid "Select target branch" msgstr "" -msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one." -msgstr "" - msgid "Select the custom project template source group." msgstr "" @@ -25779,6 +25773,9 @@ msgstr "" msgid "Set target branch to %{branch_name}." msgstr "" +msgid "Set the default branch for this project. All merge requests and commits are made against this branch unless you specify a different one." +msgstr "" + msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: %{code_open}4 mins 2 sec%{code_close}, %{code_open}2h42min%{code_close}." msgstr "" @@ -31911,6 +31908,9 @@ msgstr "" msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks." msgstr "" +msgid "When merge requests and commits in the default branch close, any issues they reference also close." +msgstr "" + msgid "When this merge request is accepted" msgid_plural "When these merge requests are accepted" msgstr[0] "" diff --git a/spec/factories/ci/pipeline_artifacts.rb b/spec/factories/ci/pipeline_artifacts.rb index fa33609dd6c..8e1a64c7d40 100644 --- a/spec/factories/ci/pipeline_artifacts.rb +++ b/spec/factories/ci/pipeline_artifacts.rb @@ -34,5 +34,15 @@ FactoryBot.define do size { file.size } end + + trait :codequality_report do + file_type { :code_quality } + size { 2.megabytes } + + after(:build) do |artifact, _evaluator| + artifact.file = fixture_file_upload( + Rails.root.join('spec/fixtures/pipeline_artifacts/code_quality.json'), 'application/json') + end + end end end diff --git a/spec/factories/ci/pipelines.rb b/spec/factories/ci/pipelines.rb index 86a8b008e48..d9ef9e6f679 100644 --- a/spec/factories/ci/pipelines.rb +++ b/spec/factories/ci/pipelines.rb @@ -163,6 +163,12 @@ FactoryBot.define do end end + trait :with_codequality_report_artifact do + after(:build) do |pipeline, evaluator| + pipeline.pipeline_artifacts << build(:ci_pipeline_artifact, :codequality_report, pipeline: pipeline, project: pipeline.project) + end + end + trait :with_terraform_reports do status { :success } diff --git a/spec/fixtures/pipeline_artifacts/code_quality.json b/spec/fixtures/pipeline_artifacts/code_quality.json new file mode 100644 index 00000000000..26b6d772a49 --- /dev/null +++ b/spec/fixtures/pipeline_artifacts/code_quality.json @@ -0,0 +1,16 @@ +{ + "files": { + "demo.rb": [ + { + "line": 5, + "description": "Method `new_array` has 8 arguments (exceeds 4 allowed). Consider refactoring.", + "severity": "major" + }, + { + "line": 5, + "description": "Avoid parameter lists longer than 5 parameters. [8/5]", + "severity": "minor" + } + ] + } +} diff --git a/spec/frontend/api/api_utils_spec.js b/spec/frontend/api/api_utils_spec.js index 3fec26f0149..04be442ef71 100644 --- a/spec/frontend/api/api_utils_spec.js +++ b/spec/frontend/api/api_utils_spec.js @@ -20,6 +20,10 @@ describe('~/api/api_utils.js', () => { ); }); + it('ensures the URL is prefixed with a /', () => { + expect(apiUtils.buildApiUrl('api/:version/projects/:id')).toEqual('/api/v7/projects/:id'); + }); + describe('when gon includes a relative_url_root property', () => { beforeEach(() => { window.gon.relative_url_root = '/relative/root'; diff --git a/spec/frontend/design_management/components/list/__snapshots__/item_spec.js.snap b/spec/frontend/design_management/components/list/__snapshots__/item_spec.js.snap index 36a2ffd19c3..8fe3e92360a 100644 --- a/spec/frontend/design_management/components/list/__snapshots__/item_spec.js.snap +++ b/spec/frontend/design_management/components/list/__snapshots__/item_spec.js.snap @@ -26,9 +26,10 @@ exports[`Design management list item component with notes renders item with mult <img alt="test" - class="gl-display-block gl-mx-auto gl-max-w-full mh-100 design-img" + class="gl-display-block gl-mx-auto gl-max-w-full gl-max-h-full design-img" data-qa-filename="test" data-qa-selector="design_image" + data-testid="design-img-1" src="" /> </gl-intersection-observer-stub> @@ -43,6 +44,8 @@ exports[`Design management list item component with notes renders item with mult <span class="gl-font-weight-bold str-truncated-100" data-qa-selector="design_file_name" + data-testid="design-img-filename-1" + title="test" > test </span> @@ -100,9 +103,10 @@ exports[`Design management list item component with notes renders item with sing <img alt="test" - class="gl-display-block gl-mx-auto gl-max-w-full mh-100 design-img" + class="gl-display-block gl-mx-auto gl-max-w-full gl-max-h-full design-img" data-qa-filename="test" data-qa-selector="design_image" + data-testid="design-img-1" src="" /> </gl-intersection-observer-stub> @@ -117,6 +121,8 @@ exports[`Design management list item component with notes renders item with sing <span class="gl-font-weight-bold str-truncated-100" data-qa-selector="design_file_name" + data-testid="design-img-filename-1" + title="test" > test </span> diff --git a/spec/frontend/design_management/components/list/item_spec.js b/spec/frontend/design_management/components/list/item_spec.js index 55c6ecbc26b..35be4d9784c 100644 --- a/spec/frontend/design_management/components/list/item_spec.js +++ b/spec/frontend/design_management/components/list/item_spec.js @@ -1,5 +1,6 @@ import { createLocalVue, shallowMount } from '@vue/test-utils'; import { GlIcon, GlLoadingIcon, GlIntersectionObserver } from '@gitlab/ui'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import VueRouter from 'vue-router'; import Item from '~/design_management/components/list/item.vue'; @@ -17,8 +18,11 @@ const DESIGN_VERSION_EVENT = { describe('Design management list item component', () => { let wrapper; + const imgId = 1; + const imgFilename = 'test'; - const findDesignEvent = () => wrapper.find('[data-testid="designEvent"]'); + const findDesignEvent = () => wrapper.findByTestId('design-event'); + const findImgFilename = (id = imgId) => wrapper.findByTestId(`design-img-filename-${id}`); const findEventIcon = () => findDesignEvent().find(GlIcon); const findLoadingIcon = () => wrapper.find(GlLoadingIcon); @@ -28,25 +32,27 @@ describe('Design management list item component', () => { isUploading = false, imageLoading = false, } = {}) { - wrapper = shallowMount(Item, { - localVue, - router, - propsData: { - id: 1, - filename: 'test', - image: 'http://via.placeholder.com/300', - isUploading, - event, - notesCount, - updatedAt: '01-01-2019', - }, - data() { - return { - imageLoading, - }; - }, - stubs: ['router-link'], - }); + wrapper = extendedWrapper( + shallowMount(Item, { + localVue, + router, + propsData: { + id: imgId, + filename: imgFilename, + image: 'http://via.placeholder.com/300', + isUploading, + event, + notesCount, + updatedAt: '01-01-2019', + }, + data() { + return { + imageLoading, + }; + }, + stubs: ['router-link'], + }), + ); } afterEach(() => { @@ -75,6 +81,10 @@ describe('Design management list item component', () => { return wrapper.vm.$nextTick(); }); + it('renders a tooltip', () => { + expect(findImgFilename().attributes('title')).toEqual(imgFilename); + }); + describe('before image is loaded', () => { it('renders loading spinner', () => { expect(wrapper.find(GlLoadingIcon)).toExist(); diff --git a/spec/graphql/types/ci_configuration/sast/analyzers_entity_input_type_spec.rb b/spec/graphql/types/ci_configuration/sast/analyzers_entity_input_type_spec.rb new file mode 100644 index 00000000000..ac18f8d53a1 --- /dev/null +++ b/spec/graphql/types/ci_configuration/sast/analyzers_entity_input_type_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ::Types::CiConfiguration::Sast::AnalyzersEntityInputType do + it { expect(described_class.graphql_name).to eq('SastCiConfigurationAnalyzersEntityInput') } + + it { expect(described_class.arguments.keys).to match_array(%w[enabled name variables]) } +end diff --git a/spec/graphql/types/ci_configuration/sast/analyzers_entity_type_spec.rb b/spec/graphql/types/ci_configuration/sast/analyzers_entity_type_spec.rb new file mode 100644 index 00000000000..27f6703b429 --- /dev/null +++ b/spec/graphql/types/ci_configuration/sast/analyzers_entity_type_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['SastCiConfigurationAnalyzersEntity'] do + let(:fields) { %i[name label enabled description variables] } + + it { expect(described_class.graphql_name).to eq('SastCiConfigurationAnalyzersEntity') } + + it { expect(described_class).to have_graphql_fields(fields) } +end diff --git a/spec/graphql/types/ci_configuration/sast/entity_input_type_spec.rb b/spec/graphql/types/ci_configuration/sast/entity_input_type_spec.rb new file mode 100644 index 00000000000..cefcf64164a --- /dev/null +++ b/spec/graphql/types/ci_configuration/sast/entity_input_type_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ::Types::CiConfiguration::Sast::EntityInputType do + it { expect(described_class.graphql_name).to eq('SastCiConfigurationEntityInput') } + + it { expect(described_class.arguments.keys).to match_array(%w[field defaultValue value]) } +end diff --git a/spec/graphql/types/ci_configuration/sast/entity_type_spec.rb b/spec/graphql/types/ci_configuration/sast/entity_type_spec.rb new file mode 100644 index 00000000000..762798670a5 --- /dev/null +++ b/spec/graphql/types/ci_configuration/sast/entity_type_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['SastCiConfigurationEntity'] do + let(:fields) { %i[field label description type options default_value value size] } + + it { expect(described_class.graphql_name).to eq('SastCiConfigurationEntity') } + + it { expect(described_class).to have_graphql_fields(fields) } +end diff --git a/spec/graphql/types/ci_configuration/sast/input_type_spec.rb b/spec/graphql/types/ci_configuration/sast/input_type_spec.rb new file mode 100644 index 00000000000..9f9d1dea98f --- /dev/null +++ b/spec/graphql/types/ci_configuration/sast/input_type_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ::Types::CiConfiguration::Sast::InputType do + it { expect(described_class.graphql_name).to eq('SastCiConfigurationInput') } + + it { expect(described_class.arguments.keys).to match_array(%w[global pipeline analyzers]) } +end diff --git a/spec/graphql/types/ci_configuration/sast/options_entity_spec.rb b/spec/graphql/types/ci_configuration/sast/options_entity_spec.rb new file mode 100644 index 00000000000..c60c8b9c84a --- /dev/null +++ b/spec/graphql/types/ci_configuration/sast/options_entity_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['SastCiConfigurationOptionsEntity'] do + let(:fields) { %i[label value] } + + it { expect(described_class.graphql_name).to eq('SastCiConfigurationOptionsEntity') } + + it { expect(described_class).to have_graphql_fields(fields) } +end diff --git a/spec/graphql/types/ci_configuration/sast/type_spec.rb b/spec/graphql/types/ci_configuration/sast/type_spec.rb new file mode 100644 index 00000000000..e7a8cd436e4 --- /dev/null +++ b/spec/graphql/types/ci_configuration/sast/type_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['SastCiConfiguration'] do + let(:fields) { %i[global pipeline analyzers] } + + it { expect(described_class.graphql_name).to eq('SastCiConfiguration') } + + it { expect(described_class).to have_graphql_fields(fields) } +end diff --git a/spec/graphql/types/ci_configuration/sast/ui_component_size_enum_spec.rb b/spec/graphql/types/ci_configuration/sast/ui_component_size_enum_spec.rb new file mode 100644 index 00000000000..23184df809f --- /dev/null +++ b/spec/graphql/types/ci_configuration/sast/ui_component_size_enum_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::CiConfiguration::Sast::UiComponentSizeEnum do + specify { expect(described_class.graphql_name).to eq('SastUiComponentSize') } + + it 'exposes all sizes of ui components' do + expect(described_class.values.keys).to include(*%w[SMALL MEDIUM LARGE]) + end +end diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb index 9d0d7a3918a..8f6a120110f 100644 --- a/spec/graphql/types/project_type_spec.rb +++ b/spec/graphql/types/project_type_spec.rb @@ -31,12 +31,171 @@ RSpec.describe GitlabSchema.types['Project'] do container_expiration_policy service_desk_enabled service_desk_address issue_status_counts terraform_states alert_management_integrations container_repositories container_repositories_count - pipeline_analytics squash_read_only + pipeline_analytics squash_read_only sast_ci_configuration ] expect(described_class).to include_graphql_fields(*expected_fields) end + describe 'sast_ci_configuration' do + let_it_be(:project) { create(:project) } + let_it_be(:user) { create(:user) } + + before do + stub_licensed_features(security_dashboard: true) + project.add_developer(user) + allow(project.repository).to receive(:blob_data_at).and_return(gitlab_ci_yml_content) + end + + include_context 'read ci configuration for sast enabled project' + + let(:query) do + %( + query { + project(fullPath: "#{project.full_path}") { + sastCiConfiguration { + global { + nodes { + type + options { + nodes { + label + value + } + } + field + label + defaultValue + value + size + } + } + pipeline { + nodes { + type + options { + nodes { + label + value + } + } + field + label + defaultValue + value + size + } + } + analyzers { + nodes { + name + label + enabled + } + } + } + } + } + ) + end + + subject { GitlabSchema.execute(query, context: { current_user: user }).as_json } + + it "returns the project's sast configuration for global variables" do + secure_analyzers_prefix = subject.dig('data', 'project', 'sastCiConfiguration', 'global', 'nodes').first + expect(secure_analyzers_prefix['type']).to eq('string') + expect(secure_analyzers_prefix['field']).to eq('SECURE_ANALYZERS_PREFIX') + expect(secure_analyzers_prefix['label']).to eq('Image prefix') + expect(secure_analyzers_prefix['defaultValue']).to eq('registry.gitlab.com/gitlab-org/security-products/analyzers') + expect(secure_analyzers_prefix['value']).to eq('registry.gitlab.com/gitlab-org/security-products/analyzers') + expect(secure_analyzers_prefix['size']).to eq('LARGE') + expect(secure_analyzers_prefix['options']).to be_nil + end + + it "returns the project's sast configuration for pipeline variables" do + pipeline_stage = subject.dig('data', 'project', 'sastCiConfiguration', 'pipeline', 'nodes').first + expect(pipeline_stage['type']).to eq('string') + expect(pipeline_stage['field']).to eq('stage') + expect(pipeline_stage['label']).to eq('Stage') + expect(pipeline_stage['defaultValue']).to eq('test') + expect(pipeline_stage['value']).to eq('test') + expect(pipeline_stage['size']).to eq('MEDIUM') + end + + it "returns the project's sast configuration for analyzer variables" do + analyzer = subject.dig('data', 'project', 'sastCiConfiguration', 'analyzers', 'nodes').first + expect(analyzer['name']).to eq('brakeman') + expect(analyzer['label']).to eq('Brakeman') + expect(analyzer['enabled']).to eq(true) + end + + context "with guest user" do + before do + project.add_guest(user) + end + + context 'when project is private' do + let(:project) { create(:project, :private, :repository) } + + it "returns no configuration" do + secure_analyzers_prefix = subject.dig('data', 'project', 'sastCiConfiguration') + expect(secure_analyzers_prefix).to be_nil + end + end + + context 'when project is public' do + let(:project) { create(:project, :public, :repository) } + + context 'when repository is accessible by everyone' do + it "returns the project's sast configuration for global variables" do + secure_analyzers_prefix = subject.dig('data', 'project', 'sastCiConfiguration', 'global', 'nodes').first + + expect(secure_analyzers_prefix['type']).to eq('string') + expect(secure_analyzers_prefix['field']).to eq('SECURE_ANALYZERS_PREFIX') + end + end + end + end + + context "with non-member user" do + before do + project.team.truncate + end + + context 'when project is private' do + let(:project) { create(:project, :private, :repository) } + + it "returns no configuration" do + secure_analyzers_prefix = subject.dig('data', 'project', 'sastCiConfiguration') + expect(secure_analyzers_prefix).to be_nil + end + end + + context 'when project is public' do + let(:project) { create(:project, :public, :repository) } + + context 'when repository is accessible by everyone' do + it "returns the project's sast configuration for global variables" do + secure_analyzers_prefix = subject.dig('data', 'project', 'sastCiConfiguration', 'global', 'nodes').first + expect(secure_analyzers_prefix['type']).to eq('string') + expect(secure_analyzers_prefix['field']).to eq('SECURE_ANALYZERS_PREFIX') + end + end + + context 'when repository is accessible only by team members' do + it "returns no configuration" do + project.project_feature.update!(merge_requests_access_level: ProjectFeature::DISABLED, + builds_access_level: ProjectFeature::DISABLED, + repository_access_level: ProjectFeature::PRIVATE) + + secure_analyzers_prefix = subject.dig('data', 'project', 'sastCiConfiguration') + expect(secure_analyzers_prefix).to be_nil + end + end + end + end + end + describe 'issue field' do subject { described_class.fields['issue'] } diff --git a/spec/initializers/validate_puma_spec.rb b/spec/initializers/validate_puma_spec.rb new file mode 100644 index 00000000000..2c02bde2bb3 --- /dev/null +++ b/spec/initializers/validate_puma_spec.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'validate puma' do + subject do + load Rails.root.join('config/initializers/validate_puma.rb') + end + + before do + stub_env('PUMA_SKIP_CLUSTER_VALIDATION', skip_validation) + stub_const('Puma', double) + allow(Gitlab::Runtime).to receive(:puma?).and_return(true) + allow(Puma).to receive_message_chain(:cli_config, :options).and_return(workers: workers) + end + + context 'for .com' do + before do + allow(Gitlab).to receive(:com?).and_return(true) + end + + context 'when worker count is 0' do + let(:workers) { 0 } + + context 'and PUMA_SKIP_CLUSTER_VALIDATION is true' do + let(:skip_validation) { true } + + specify { expect { subject }.to raise_error(String) } + end + + context 'and PUMA_SKIP_CLUSTER_VALIDATION is false' do + let(:skip_validation) { false } + + specify { expect { subject }.to raise_error(String) } + end + end + + context 'when worker count is > 0' do + let(:workers) { 2 } + + context 'and PUMA_SKIP_CLUSTER_VALIDATION is true' do + let(:skip_validation) { true } + + specify { expect { subject }.not_to raise_error } + end + + context 'and PUMA_SKIP_CLUSTER_VALIDATION is false' do + let(:skip_validation) { false } + + specify { expect { subject }.not_to raise_error } + end + end + end + + context 'for other environments' do + before do + allow(Gitlab).to receive(:com?).and_return(false) + end + + context 'when worker count is 0' do + let(:workers) { 0 } + + context 'and PUMA_SKIP_CLUSTER_VALIDATION is true' do + let(:skip_validation) { true } + + specify { expect { subject }.not_to raise_error } + end + + context 'and PUMA_SKIP_CLUSTER_VALIDATION is false' do + let(:skip_validation) { false } + + specify { expect { subject }.to raise_error(String) } + end + end + + context 'when worker count is > 0' do + let(:workers) { 2 } + + context 'and PUMA_SKIP_CLUSTER_VALIDATION is true' do + let(:skip_validation) { true } + + specify { expect { subject }.not_to raise_error } + end + + context 'and PUMA_SKIP_CLUSTER_VALIDATION is false' do + let(:skip_validation) { false } + + specify { expect { subject }.not_to raise_error } + end + end + end +end diff --git a/spec/lib/gitlab/template/finders/global_template_finder_spec.rb b/spec/lib/gitlab/template/finders/global_template_finder_spec.rb index e2751d194d3..38ec28c2b9a 100644 --- a/spec/lib/gitlab/template/finders/global_template_finder_spec.rb +++ b/spec/lib/gitlab/template/finders/global_template_finder_spec.rb @@ -15,9 +15,19 @@ RSpec.describe Gitlab::Template::Finders::GlobalTemplateFinder do FileUtils.rm_rf(base_dir) end - subject(:finder) { described_class.new(base_dir, '', { 'General' => '', 'Bar' => 'Bar' }, excluded_patterns: excluded_patterns) } + subject(:finder) do + described_class.new(base_dir, '', + { 'General' => '', 'Bar' => 'Bar' }, + include_categories_for_file, + excluded_patterns: excluded_patterns) + end let(:excluded_patterns) { [] } + let(:include_categories_for_file) do + { + "SAST" => { "Security" => "Security" } + } + end describe '.find' do context 'with a non-prefixed General template' do @@ -60,6 +70,7 @@ RSpec.describe Gitlab::Template::Finders::GlobalTemplateFinder do context 'with a prefixed template' do before do create_template!('Bar/test-template') + create_template!('Security/SAST') end it 'finds the template with a prefix' do @@ -76,6 +87,16 @@ RSpec.describe Gitlab::Template::Finders::GlobalTemplateFinder do expect { finder.find('../foo') }.to raise_error(/Invalid path/) end + context 'with include_categories_for_file being present' do + it 'finds the template with a prefix' do + expect(finder.find('SAST')).to be_present + end + + it 'does not find any template which is missing in include_categories_for_file' do + expect(finder.find('DAST')).to be_nil + end + end + context 'while listed as an exclusion' do let(:excluded_patterns) { [%r{^Bar/test-template$}] } diff --git a/spec/lib/security/ci_configuration/sast_build_actions_spec.rb b/spec/lib/security/ci_configuration/sast_build_actions_spec.rb new file mode 100644 index 00000000000..c8f9430eff9 --- /dev/null +++ b/spec/lib/security/ci_configuration/sast_build_actions_spec.rb @@ -0,0 +1,539 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Security::CiConfiguration::SastBuildActions do + let(:default_sast_values) do + { 'global' => + [ + { 'field' => 'SECURE_ANALYZERS_PREFIX', 'defaultValue' => 'registry.gitlab.com/gitlab-org/security-products/analyzers', 'value' => 'registry.gitlab.com/gitlab-org/security-products/analyzers' } + ], + 'pipeline' => + [ + { 'field' => 'stage', 'defaultValue' => 'test', 'value' => 'test' }, + { 'field' => 'SEARCH_MAX_DEPTH', 'defaultValue' => 4, 'value' => 4 }, + { 'field' => 'SAST_ANALYZER_IMAGE_TAG', 'defaultValue' => 2, 'value' => 2 }, + { 'field' => 'SAST_EXCLUDED_PATHS', 'defaultValue' => 'spec, test, tests, tmp', 'value' => 'spec, test, tests, tmp' } + ] } + end + + let(:params) do + { 'global' => + [ + { 'field' => 'SECURE_ANALYZERS_PREFIX', 'defaultValue' => 'registry.gitlab.com/gitlab-org/security-products/analyzers', 'value' => 'new_registry' } + ], + 'pipeline' => + [ + { 'field' => 'stage', 'defaultValue' => 'test', 'value' => 'security' }, + { 'field' => 'SEARCH_MAX_DEPTH', 'defaultValue' => 4, 'value' => 1 }, + { 'field' => 'SAST_ANALYZER_IMAGE_TAG', 'defaultValue' => 2, 'value' => 2 }, + { 'field' => 'SAST_EXCLUDED_PATHS', 'defaultValue' => 'spec, test, tests, tmp', 'value' => 'spec,docs' } + ] } + end + + let(:params_with_analyzer_info) do + params.merge( { 'analyzers' => + [ + { + 'name' => "bandit", + 'enabled' => false + }, + { + 'name' => "brakeman", + 'enabled' => true, + 'variables' => [ + { 'field' => "SAST_BRAKEMAN_LEVEL", + 'defaultValue' => "1", + 'value' => "2" } + ] + }, + { + 'name' => "flawfinder", + 'enabled' => true, + 'variables' => [ + { 'field' => "SAST_FLAWFINDER_LEVEL", + 'defaultValue' => "1", + 'value' => "1" } + ] + } + ] } + ) + end + + let(:params_with_all_analyzers_enabled) do + params.merge( { 'analyzers' => + [ + { + 'name' => "flawfinder", + 'enabled' => true + }, + { + 'name' => "brakeman", + 'enabled' => true + } + ] } + ) + end + + context 'with existing .gitlab-ci.yml' do + let(:auto_devops_enabled) { false } + + context 'sast has not been included' do + context 'template includes are array' do + let(:gitlab_ci_content) { existing_gitlab_ci_and_template_array_without_sast } + + subject(:result) { described_class.new(auto_devops_enabled, params, gitlab_ci_content).generate } + + it 'generates the correct YML' do + expect(result.first[:action]).to eq('update') + expect(result.first[:content]).to eq(sast_yaml_two_includes) + end + end + + context 'template include is not an array' do + let(:gitlab_ci_content) { existing_gitlab_ci_and_single_template_without_sast } + + subject(:result) { described_class.new(auto_devops_enabled, params, gitlab_ci_content).generate } + + it 'generates the correct YML' do + expect(result.first[:action]).to eq('update') + expect(result.first[:content]).to eq(sast_yaml_two_includes) + end + + it 'reports defaults have been overwritten' do + expect(result.first[:default_values_overwritten]).to eq(true) + end + end + end + + context 'sast template include is not an array' do + let(:gitlab_ci_content) { existing_gitlab_ci_and_single_template_with_sast_and_default_stage } + + subject(:result) { described_class.new(auto_devops_enabled, params, gitlab_ci_content).generate } + + it 'generates the correct YML' do + expect(result.first[:action]).to eq('update') + expect(result.first[:content]).to eq(sast_yaml_all_params) + end + end + + context 'with default values' do + let(:params) { default_sast_values } + let(:gitlab_ci_content) { existing_gitlab_ci_and_single_template_with_sast_and_default_stage } + + subject(:result) { described_class.new(auto_devops_enabled, params, gitlab_ci_content).generate } + + it 'generates the correct YML' do + expect(result.first[:content]).to eq(sast_yaml_with_no_variables_set) + end + + it 'reports defaults have not been overwritten' do + expect(result.first[:default_values_overwritten]).to eq(false) + end + + context 'analyzer section' do + let(:gitlab_ci_content) { existing_gitlab_ci_and_single_template_with_sast_and_default_stage } + + subject(:result) { described_class.new(auto_devops_enabled, params_with_analyzer_info, gitlab_ci_content).generate } + + it 'generates the correct YML' do + expect(result.first[:content]).to eq(sast_yaml_with_no_variables_set_but_analyzers) + end + + context 'analyzers are disabled' do + let(:gitlab_ci_content) { existing_gitlab_ci_and_single_template_with_sast_and_default_stage } + + subject(:result) { described_class.new(auto_devops_enabled, params_with_analyzer_info, gitlab_ci_content).generate } + + it 'writes SAST_EXCLUDED_ANALYZERS' do + stub_const('Security::CiConfiguration::SastBuildActions::SAST_DEFAULT_ANALYZERS', 'bandit, brakeman, flawfinder') + + expect(result.first[:content]).to eq(sast_yaml_with_no_variables_set_but_analyzers) + end + end + + context 'all analyzers are enabled' do + let(:gitlab_ci_content) { existing_gitlab_ci_and_single_template_with_sast_and_default_stage } + + subject(:result) { described_class.new(auto_devops_enabled, params_with_all_analyzers_enabled, gitlab_ci_content).generate } + + it 'does not write SAST_DEFAULT_ANALYZERS or SAST_EXCLUDED_ANALYZERS' do + stub_const('Security::CiConfiguration::SastBuildActions::SAST_DEFAULT_ANALYZERS', 'brakeman, flawfinder') + + expect(result.first[:content]).to eq(sast_yaml_with_no_variables_set) + end + end + end + end + + context 'with update stage and SEARCH_MAX_DEPTH and set SECURE_ANALYZERS_PREFIX to default' do + let(:params) do + { 'global' => + [ + { 'field' => 'SECURE_ANALYZERS_PREFIX', 'defaultValue' => 'registry.gitlab.com/gitlab-org/security-products/analyzers', 'value' => 'registry.gitlab.com/gitlab-org/security-products/analyzers' } + ], + 'pipeline' => + [ + { 'field' => 'stage', 'defaultValue' => 'test', 'value' => 'brand_new_stage' }, + { 'field' => 'SEARCH_MAX_DEPTH', 'defaultValue' => 4, 'value' => 5 }, + { 'field' => 'SAST_ANALYZER_IMAGE_TAG', 'defaultValue' => 2, 'value' => 2 }, + { 'field' => 'SAST_EXCLUDED_PATHS', 'defaultValue' => 'spec, test, tests, tmp', 'value' => 'spec,docs' } + ] } + end + + let(:gitlab_ci_content) { existing_gitlab_ci } + + subject(:result) { described_class.new(auto_devops_enabled, params, gitlab_ci_content).generate } + + it 'generates the correct YML' do + expect(result.first[:action]).to eq('update') + expect(result.first[:content]).to eq(sast_yaml_updated_stage) + end + end + + context 'with no existing variables' do + let(:gitlab_ci_content) { existing_gitlab_ci_with_no_variables } + + subject(:result) { described_class.new(auto_devops_enabled, params, gitlab_ci_content).generate } + + it 'generates the correct YML' do + expect(result.first[:action]).to eq('update') + expect(result.first[:content]).to eq(sast_yaml_variable_section_added) + end + end + + context 'with no existing sast config' do + let(:gitlab_ci_content) { existing_gitlab_ci_with_no_sast_section } + + subject(:result) { described_class.new(auto_devops_enabled, params, gitlab_ci_content).generate } + + it 'generates the correct YML' do + expect(result.first[:action]).to eq('update') + expect(result.first[:content]).to eq(sast_yaml_sast_section_added) + end + end + + context 'with no existing sast variables' do + let(:gitlab_ci_content) { existing_gitlab_ci_with_no_sast_variables } + + subject(:result) { described_class.new(auto_devops_enabled, params, gitlab_ci_content).generate } + + it 'generates the correct YML' do + expect(result.first[:action]).to eq('update') + expect(result.first[:content]).to eq(sast_yaml_sast_variables_section_added) + end + end + + def existing_gitlab_ci_and_template_array_without_sast + { "stages" => %w(test security), + "variables" => { "RANDOM" => "make sure this persists", "SECURE_ANALYZERS_PREFIX" => "localhost:5000/analyzers" }, + "sast" => { "variables" => { "SAST_ANALYZER_IMAGE_TAG" => 2, "SEARCH_MAX_DEPTH" => 1 }, "stage" => "security" }, + "include" => [{ "template" => "existing.yml" }] } + end + + def existing_gitlab_ci_and_single_template_with_sast_and_default_stage + { "stages" => %w(test), + "variables" => { "SECURE_ANALYZERS_PREFIX" => "localhost:5000/analyzers" }, + "sast" => { "variables" => { "SAST_ANALYZER_IMAGE_TAG" => 2, "SEARCH_MAX_DEPTH" => 1 }, "stage" => "test" }, + "include" => { "template" => "Security/SAST.gitlab-ci.yml" } } + end + + def existing_gitlab_ci_and_single_template_without_sast + { "stages" => %w(test security), + "variables" => { "RANDOM" => "make sure this persists", "SECURE_ANALYZERS_PREFIX" => "localhost:5000/analyzers" }, + "sast" => { "variables" => { "SAST_ANALYZER_IMAGE_TAG" => 2, "SEARCH_MAX_DEPTH" => 1 }, "stage" => "security" }, + "include" => { "template" => "existing.yml" } } + end + + def existing_gitlab_ci_with_no_variables + { "stages" => %w(test security), + "sast" => { "variables" => { "SAST_ANALYZER_IMAGE_TAG" => 2, "SEARCH_MAX_DEPTH" => 1 }, "stage" => "security" }, + "include" => [{ "template" => "Security/SAST.gitlab-ci.yml" }] } + end + + def existing_gitlab_ci_with_no_sast_section + { "stages" => %w(test security), + "variables" => { "RANDOM" => "make sure this persists", "SECURE_ANALYZERS_PREFIX" => "localhost:5000/analyzers" }, + "include" => [{ "template" => "Security/SAST.gitlab-ci.yml" }] } + end + + def existing_gitlab_ci_with_no_sast_variables + { "stages" => %w(test security), + "variables" => { "RANDOM" => "make sure this persists", "SECURE_ANALYZERS_PREFIX" => "localhost:5000/analyzers" }, + "sast" => { "stage" => "security" }, + "include" => [{ "template" => "Security/SAST.gitlab-ci.yml" }] } + end + + def existing_gitlab_ci + { "stages" => %w(test security), + "variables" => { "RANDOM" => "make sure this persists", "SECURE_ANALYZERS_PREFIX" => "bad_prefix" }, + "sast" => { "variables" => { "SAST_ANALYZER_IMAGE_TAG" => 2, "SEARCH_MAX_DEPTH" => 1 }, "stage" => "security" }, + "include" => [{ "template" => "Security/SAST.gitlab-ci.yml" }] } + end + end + + context 'with no .gitlab-ci.yml' do + let(:gitlab_ci_content) { nil } + + context 'autodevops disabled' do + let(:auto_devops_enabled) { false } + + context 'with one empty parameter' do + let(:params) do + { 'global' => + [ + { 'field' => 'SECURE_ANALYZERS_PREFIX', 'defaultValue' => 'registry.gitlab.com/gitlab-org/security-products/analyzers', 'value' => '' } + ] } + end + + subject(:result) { described_class.new(auto_devops_enabled, params, gitlab_ci_content).generate } + + it 'generates the correct YML' do + expect(result.first[:content]).to eq(sast_yaml_with_no_variables_set) + end + end + + context 'with all parameters' do + subject(:result) { described_class.new(auto_devops_enabled, params, gitlab_ci_content).generate } + + it 'generates the correct YML' do + expect(result.first[:content]).to eq(sast_yaml_all_params) + end + end + end + + context 'with autodevops enabled' do + let(:auto_devops_enabled) { true } + + subject(:result) { described_class.new(auto_devops_enabled, params, gitlab_ci_content).generate } + + before do + allow_next_instance_of(described_class) do |sast_build_actions| + allow(sast_build_actions).to receive(:auto_devops_stages).and_return(fast_auto_devops_stages) + end + end + + it 'generates the correct YML' do + expect(result.first[:content]).to eq(auto_devops_with_custom_stage) + end + end + end + + describe 'Security::CiConfiguration::SastBuildActions::SAST_DEFAULT_ANALYZERS' do + subject(:variable) {Security::CiConfiguration::SastBuildActions::SAST_DEFAULT_ANALYZERS} + + it 'is sorted alphabetically' do + sorted_variable = Security::CiConfiguration::SastBuildActions::SAST_DEFAULT_ANALYZERS + .split(',') + .map(&:strip) + .sort + .join(', ') + + expect(variable).to eq(sorted_variable) + end + end + + # stubbing this method allows this spec file to use fast_spec_helper + def fast_auto_devops_stages + auto_devops_template = YAML.safe_load( File.read('lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml') ) + auto_devops_template['stages'] + end + + def sast_yaml_with_no_variables_set_but_analyzers + <<-CI_YML.strip_heredoc + # You can override the included template(s) by including variable overrides + # See https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings + # Note that environment variables can be set in several places + # See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables + stages: + - test + sast: + variables: + SAST_EXCLUDED_ANALYZERS: bandit + SAST_BRAKEMAN_LEVEL: '2' + stage: test + include: + - template: Security/SAST.gitlab-ci.yml + CI_YML + end + + def sast_yaml_with_no_variables_set + <<-CI_YML.strip_heredoc + # You can override the included template(s) by including variable overrides + # See https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings + # Note that environment variables can be set in several places + # See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables + stages: + - test + sast: + stage: test + include: + - template: Security/SAST.gitlab-ci.yml + CI_YML + end + + def sast_yaml_all_params + <<-CI_YML.strip_heredoc + # You can override the included template(s) by including variable overrides + # See https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings + # Note that environment variables can be set in several places + # See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables + stages: + - test + - security + variables: + SECURE_ANALYZERS_PREFIX: new_registry + sast: + variables: + SAST_EXCLUDED_PATHS: spec,docs + SEARCH_MAX_DEPTH: 1 + stage: security + include: + - template: Security/SAST.gitlab-ci.yml + CI_YML + end + + def auto_devops_with_custom_stage + <<-CI_YML.strip_heredoc + # You can override the included template(s) by including variable overrides + # See https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings + # Note that environment variables can be set in several places + # See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables + stages: + - build + - test + - deploy + - review + - dast + - staging + - canary + - production + - incremental rollout 10% + - incremental rollout 25% + - incremental rollout 50% + - incremental rollout 100% + - performance + - cleanup + - security + variables: + SECURE_ANALYZERS_PREFIX: new_registry + sast: + variables: + SAST_EXCLUDED_PATHS: spec,docs + SEARCH_MAX_DEPTH: 1 + stage: security + include: + - template: Auto-DevOps.gitlab-ci.yml + CI_YML + end + + def sast_yaml_two_includes + <<-CI_YML.strip_heredoc + # You can override the included template(s) by including variable overrides + # See https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings + # Note that environment variables can be set in several places + # See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables + stages: + - test + - security + variables: + RANDOM: make sure this persists + SECURE_ANALYZERS_PREFIX: new_registry + sast: + variables: + SAST_EXCLUDED_PATHS: spec,docs + SEARCH_MAX_DEPTH: 1 + stage: security + include: + - template: existing.yml + - template: Security/SAST.gitlab-ci.yml + CI_YML + end + + def sast_yaml_variable_section_added + <<-CI_YML.strip_heredoc + # You can override the included template(s) by including variable overrides + # See https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings + # Note that environment variables can be set in several places + # See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables + stages: + - test + - security + sast: + variables: + SAST_EXCLUDED_PATHS: spec,docs + SEARCH_MAX_DEPTH: 1 + stage: security + include: + - template: Security/SAST.gitlab-ci.yml + variables: + SECURE_ANALYZERS_PREFIX: new_registry + CI_YML + end + + def sast_yaml_sast_section_added + <<-CI_YML.strip_heredoc + # You can override the included template(s) by including variable overrides + # See https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings + # Note that environment variables can be set in several places + # See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables + stages: + - test + - security + variables: + RANDOM: make sure this persists + SECURE_ANALYZERS_PREFIX: new_registry + include: + - template: Security/SAST.gitlab-ci.yml + sast: + variables: + SAST_EXCLUDED_PATHS: spec,docs + SEARCH_MAX_DEPTH: 1 + stage: security + CI_YML + end + + def sast_yaml_sast_variables_section_added + <<-CI_YML.strip_heredoc + # You can override the included template(s) by including variable overrides + # See https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings + # Note that environment variables can be set in several places + # See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables + stages: + - test + - security + variables: + RANDOM: make sure this persists + SECURE_ANALYZERS_PREFIX: new_registry + sast: + stage: security + variables: + SAST_EXCLUDED_PATHS: spec,docs + SEARCH_MAX_DEPTH: 1 + include: + - template: Security/SAST.gitlab-ci.yml + CI_YML + end + + def sast_yaml_updated_stage + <<-CI_YML.strip_heredoc + # You can override the included template(s) by including variable overrides + # See https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings + # Note that environment variables can be set in several places + # See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables + stages: + - test + - security + - brand_new_stage + variables: + RANDOM: make sure this persists + sast: + variables: + SAST_EXCLUDED_PATHS: spec,docs + SEARCH_MAX_DEPTH: 5 + stage: brand_new_stage + include: + - template: Security/SAST.gitlab-ci.yml + CI_YML + end +end diff --git a/spec/models/ci/pipeline_artifact_spec.rb b/spec/models/ci/pipeline_artifact_spec.rb index 8cbace845a9..7e6196f1403 100644 --- a/spec/models/ci/pipeline_artifact_spec.rb +++ b/spec/models/ci/pipeline_artifact_spec.rb @@ -76,38 +76,98 @@ RSpec.describe Ci::PipelineArtifact, type: :model do end end - describe '.has_code_coverage?' do - subject { Ci::PipelineArtifact.has_code_coverage? } + describe '.has_report?' do + subject(:pipeline_artifact) { Ci::PipelineArtifact.has_report?(file_type) } - context 'when pipeline artifact has a code coverage' do - let!(:pipeline_artifact) { create(:ci_pipeline_artifact) } + context 'when file_type is code_coverage' do + let(:file_type) { :code_coverage } + + context 'when pipeline artifact has a coverage report' do + let!(:pipeline_artifact) { create(:ci_pipeline_artifact) } + + it 'returns true' do + expect(pipeline_artifact).to be_truthy + end + end + + context 'when pipeline artifact does not have a coverage report' do + it 'returns false' do + expect(pipeline_artifact).to be_falsey + end + end + end + + context 'when file_type is code_quality' do + let(:file_type) { :code_quality } + + context 'when pipeline artifact has a quality report' do + let!(:pipeline_artifact) { create(:ci_pipeline_artifact, :codequality_report) } - it 'returns true' do - expect(subject).to be_truthy + it 'returns true' do + expect(pipeline_artifact).to be_truthy + end + end + + context 'when pipeline artifact does not have a quality report' do + it 'returns false' do + expect(pipeline_artifact).to be_falsey + end end end - context 'when pipeline artifact does not have a code coverage' do + context 'when file_type is nil' do + let(:file_type) { nil } + it 'returns false' do - expect(subject).to be_falsey + expect(pipeline_artifact).to be_falsey end end end - describe '.find_with_code_coverage' do - subject { Ci::PipelineArtifact.find_with_code_coverage } + describe '.find_by_file_type' do + subject(:pipeline_artifact) { Ci::PipelineArtifact.find_by_file_type(file_type) } - context 'when pipeline artifact has a coverage report' do - let!(:coverage_report) { create(:ci_pipeline_artifact) } + context 'when file_type is code_coverage' do + let(:file_type) { :code_coverage } + + context 'when pipeline artifact has a coverage report' do + let!(:coverage_report) { create(:ci_pipeline_artifact) } + + it 'returns a pipeline artifact with a coverage report' do + expect(pipeline_artifact.file_type).to eq('code_coverage') + end + end + + context 'when pipeline artifact does not have a coverage report' do + it 'returns nil' do + expect(pipeline_artifact).to be_nil + end + end + end + + context 'when file_type is code_quality' do + let(:file_type) { :code_quality } + + context 'when pipeline artifact has a quality report' do + let!(:coverage_report) { create(:ci_pipeline_artifact, :codequality_report) } - it 'returns a pipeline artifact with a code coverage' do - expect(subject.file_type).to eq('code_coverage') + it 'returns a pipeline artifact with a quality report' do + expect(pipeline_artifact.file_type).to eq('code_quality') + end + end + + context 'when pipeline artifact does not have a quality report' do + it 'returns nil' do + expect(pipeline_artifact).to be_nil + end end end - context 'when pipeline artifact does not have a coverage report' do + context 'when file_type is nil' do + let(:file_type) { nil } + it 'returns nil' do - expect(subject).to be_nil + expect(pipeline_artifact).to be_nil end end end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index e7943a99877..af6d7ab4250 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -3489,6 +3489,54 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end end + describe '#has_codequality_reports?' do + subject { pipeline.has_codequality_reports? } + + context 'when pipeline has a codequality artifact' do + let(:pipeline) { create(:ci_pipeline, :with_codequality_report_artifact, :running, project: project) } + + it { expect(subject).to be_truthy } + end + + context 'when pipeline does not have a codequality artifact' do + let(:pipeline) { create(:ci_pipeline, :success, project: project) } + + it { expect(subject).to be_falsey } + end + end + + describe '#can_generate_codequality_reports?' do + subject { pipeline.can_generate_codequality_reports? } + + context 'when pipeline has builds with codequality reports' do + before do + create(:ci_build, :codequality_reports, pipeline: pipeline, project: project) + end + + context 'when pipeline status is running' do + let(:pipeline) { create(:ci_pipeline, :running, project: project) } + + it { expect(subject).to be_falsey } + end + + context 'when pipeline status is success' do + let(:pipeline) { create(:ci_pipeline, :success, project: project) } + + it { expect(subject).to be_truthy } + end + end + + context 'when pipeline does not have builds with codequality reports' do + before do + create(:ci_build, :artifacts, pipeline: pipeline, project: project) + end + + let(:pipeline) { create(:ci_pipeline, :success, project: project) } + + it { expect(subject).to be_falsey } + end + end + describe '#test_report_summary' do subject { pipeline.test_report_summary } diff --git a/spec/services/security/ci_configuration/sast_parser_service_spec.rb b/spec/services/security/ci_configuration/sast_parser_service_spec.rb new file mode 100644 index 00000000000..21490f993c7 --- /dev/null +++ b/spec/services/security/ci_configuration/sast_parser_service_spec.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Security::CiConfiguration::SastParserService do + describe '#configuration' do + include_context 'read ci configuration for sast enabled project' + + let(:configuration) { described_class.new(project).configuration } + let(:secure_analyzers_prefix) { configuration['global'][0] } + let(:sast_excluded_paths) { configuration['global'][1] } + let(:sast_analyzer_image_tag) { configuration['global'][2] } + let(:sast_pipeline_stage) { configuration['pipeline'][0] } + let(:sast_search_max_depth) { configuration['pipeline'][1] } + let(:brakeman) { configuration['analyzers'][0] } + let(:bandit) { configuration['analyzers'][1] } + let(:sast_brakeman_level) { brakeman['variables'][0] } + + it 'parses the configuration for SAST' do + expect(secure_analyzers_prefix['default_value']).to eql('registry.gitlab.com/gitlab-org/security-products/analyzers') + expect(sast_excluded_paths['default_value']).to eql('spec, test, tests, tmp') + expect(sast_analyzer_image_tag['default_value']).to eql('2') + expect(sast_pipeline_stage['default_value']).to eql('test') + expect(sast_search_max_depth['default_value']).to eql('4') + expect(brakeman['enabled']).to be(true) + expect(sast_brakeman_level['default_value']).to eql('1') + end + + context 'while populating current values of the entities' do + context 'when .gitlab-ci.yml is present' do + it 'populates the current values from the file' do + allow(project.repository).to receive(:blob_data_at).and_return(gitlab_ci_yml_content) + expect(secure_analyzers_prefix['value']).to eql('registry.gitlab.com/gitlab-org/security-products/analyzers2') + expect(sast_excluded_paths['value']).to eql('spec, executables') + expect(sast_analyzer_image_tag['value']).to eql('2') + expect(sast_pipeline_stage['value']).to eql('our_custom_security_stage') + expect(sast_search_max_depth['value']).to eql('8') + expect(brakeman['enabled']).to be(false) + expect(bandit['enabled']).to be(true) + expect(sast_brakeman_level['value']).to eql('2') + end + + context 'SAST_DEFAULT_ANALYZERS is set' do + it 'enables analyzers correctly' do + allow(project.repository).to receive(:blob_data_at).and_return(gitlab_ci_yml_default_analyzers_content) + + expect(brakeman['enabled']).to be(false) + expect(bandit['enabled']).to be(true) + end + end + + context 'SAST_EXCLUDED_ANALYZERS is set' do + it 'enables analyzers correctly' do + allow(project.repository).to receive(:blob_data_at).and_return(gitlab_ci_yml_excluded_analyzers_content) + + expect(brakeman['enabled']).to be(false) + expect(bandit['enabled']).to be(true) + end + end + end + + context 'when .gitlab-ci.yml is absent' do + it 'populates the current values with the default values' do + allow(project.repository).to receive(:blob_data_at).and_return(nil) + expect(secure_analyzers_prefix['value']).to eql('registry.gitlab.com/gitlab-org/security-products/analyzers') + expect(sast_excluded_paths['value']).to eql('spec, test, tests, tmp') + expect(sast_analyzer_image_tag['value']).to eql('2') + expect(sast_pipeline_stage['value']).to eql('test') + expect(sast_search_max_depth['value']).to eql('4') + expect(brakeman['enabled']).to be(true) + expect(sast_brakeman_level['value']).to eql('1') + end + end + end + end +end |