diff options
112 files changed, 7877 insertions, 483 deletions
diff --git a/.rubocop_todo/style/bare_percent_literals.yml b/.rubocop_todo/style/bare_percent_literals.yml deleted file mode 100644 index 554dc64c39c..00000000000 --- a/.rubocop_todo/style/bare_percent_literals.yml +++ /dev/null @@ -1,87 +0,0 @@ ---- -# Cop supports --autocorrect. -Style/BarePercentLiterals: - Exclude: - - 'ee/spec/features/projects/environments/environments_spec.rb' - - 'ee/spec/helpers/subscriptions_helper_spec.rb' - - 'ee/spec/lib/banzai/filter/references/iteration_reference_filter_spec.rb' - - 'ee/spec/lib/gitlab/analytics/cycle_analytics/request_params_spec.rb' - - 'ee/spec/lib/gitlab/status_page/filter/image_filter_spec.rb' - - 'ee/spec/requests/api/ci/jobs_spec.rb' - - 'ee/spec/services/geo/container_repository_sync_spec.rb' - - 'qa/qa/ee/page/dashboard/projects.rb' - - 'qa/qa/ee/page/group/settings/general.rb' - - 'qa/qa/ee/page/project/issue/show.rb' - - 'qa/qa/ee/page/project/job/show.rb' - - 'qa/qa/ee/page/project/packages/index.rb' - - 'qa/qa/ee/page/project/pipeline/show.rb' - - 'qa/qa/ee/page/project/show.rb' - - 'qa/qa/ee/page/project/snippet/index.rb' - - 'qa/qa/ee/page/project/wiki/show.rb' - - 'qa/qa/page/element.rb' - - 'qa/qa/page/file/form.rb' - - 'qa/qa/page/project/web_ide/edit.rb' - - 'qa/qa/resource/events/project.rb' - - 'qa/qa/resource/personal_access_token_cache.rb' - - 'qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb' - - 'qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_with_image_pull_policy_spec.rb' - - 'qa/qa/specs/features/ee/browser_ui/3_create/repository/push_rules_spec.rb' - - 'qa/spec/runtime/feature_spec.rb' - - 'spec/controllers/import/fogbugz_controller_spec.rb' - - 'spec/controllers/projects/artifacts_controller_spec.rb' - - 'spec/controllers/projects/design_management/designs/raw_images_controller_spec.rb' - - 'spec/controllers/projects/design_management/designs/resized_image_controller_spec.rb' - - 'spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb' - - 'spec/features/projects/artifacts/user_downloads_artifacts_spec.rb' - - 'spec/features/projects/badges/coverage_spec.rb' - - 'spec/features/projects/badges/pipeline_badge_spec.rb' - - 'spec/features/projects/issuable_templates_spec.rb' - - 'spec/features/projects/jobs_spec.rb' - - 'spec/features/projects/pipelines/pipeline_spec.rb' - - 'spec/features/projects/pipelines/pipelines_spec.rb' - - 'spec/features/uploads/user_uploads_avatar_to_group_spec.rb' - - 'spec/features/uploads/user_uploads_avatar_to_profile_spec.rb' - - 'spec/graphql/resolvers/echo_resolver_spec.rb' - - 'spec/helpers/markup_helper_spec.rb' - - 'spec/lib/banzai/filter/autolink_filter_spec.rb' - - 'spec/lib/banzai/filter/external_link_filter_spec.rb' - - 'spec/lib/banzai/filter/image_link_filter_spec.rb' - - 'spec/lib/banzai/filter/references/label_reference_filter_spec.rb' - - 'spec/lib/banzai/filter/references/milestone_reference_filter_spec.rb' - - 'spec/lib/banzai/pipeline/full_pipeline_spec.rb' - - 'spec/lib/banzai/pipeline/incident_management/timeline_event_pipeline_spec.rb' - - 'spec/lib/banzai/pipeline/plain_markdown_pipeline_spec.rb' - - 'spec/lib/banzai/reference_parser/issue_parser_spec.rb' - - 'spec/lib/banzai/reference_parser/merge_request_parser_spec.rb' - - 'spec/lib/gitlab/diff/highlight_spec.rb' - - 'spec/lib/gitlab/error_tracking/stack_trace_highlight_decorator_spec.rb' - - 'spec/lib/gitlab/gfm/reference_rewriter_spec.rb' - - 'spec/lib/gitlab/highlight_spec.rb' - - 'spec/lib/gitlab/pagination/gitaly_keyset_pager_spec.rb' - - 'spec/lib/gitlab/pagination/offset_pagination_spec.rb' - - 'spec/lib/gitlab/prometheus/query_variables_spec.rb' - - 'spec/lib/gitlab/reference_extractor_spec.rb' - - 'spec/lib/gitlab/url_sanitizer_spec.rb' - - 'spec/mailers/emails/releases_spec.rb' - - 'spec/mailers/emails/service_desk_spec.rb' - - 'spec/models/deployment_spec.rb' - - 'spec/models/incident_management/timeline_event_spec.rb' - - 'spec/models/integrations/drone_ci_spec.rb' - - 'spec/models/integrations/teamcity_spec.rb' - - 'spec/models/project_label_spec.rb' - - 'spec/presenters/snippet_blob_presenter_spec.rb' - - 'spec/requests/api/ci/job_artifacts_spec.rb' - - 'spec/requests/api/deployments_spec.rb' - - 'spec/requests/api/graphql/mutations/snippets/destroy_spec.rb' - - 'spec/requests/api/graphql/project/incident_management/timeline_events_spec.rb' - - 'spec/requests/projects/packages/package_files_controller_spec.rb' - - 'spec/rubocop/cop/gitlab/mark_used_feature_flags_spec.rb' - - 'spec/services/prometheus/proxy_variable_substitution_service_spec.rb' - - 'spec/support/banzai/reference_filter_shared_examples.rb' - - 'spec/support/helpers/graphql_helpers.rb' - - 'spec/support/shared_examples/controllers/repository_lfs_file_load_shared_examples.rb' - - 'spec/support/shared_examples/features/resolving_discussions_in_issues_shared_examples.rb' - - 'spec/support/shared_examples/graphql/label_fields.rb' - - 'spec/support/shared_examples/lib/banzai/filters/sanitization_filter_shared_examples.rb' - - 'spec/support_specs/matchers/exceed_query_limit_helpers_spec.rb' - - 'spec/views/layouts/_head.html.haml_spec.rb' @@ -447,13 +447,17 @@ group :development, :test, :omnibus do gem 'license_finder', '~> 7.0', require: false end +# Gems required in various pipelines +group :development, :test, :monorepo do + gem 'gitlab-rspec', path: 'gems/gitlab-rspec' +end + group :test do gem 'fuubar', '~> 2.2.0' gem 'rspec-retry', '~> 0.6.2' gem 'rspec_profiling', '~> 0.0.6' gem 'rspec-benchmark', '~> 0.6.0' gem 'rspec-parameterized', '~> 1.0', require: false - gem 'gitlab-rspec', path: 'gems/gitlab-rspec' gem 'capybara', '~> 3.39', '>= 3.39.1' gem 'capybara-screenshot', '~> 1.0.26' diff --git a/app/assets/javascripts/api/user_api.js b/app/assets/javascripts/api/user_api.js index 17ad1a0b31d..c056b42b5b6 100644 --- a/app/assets/javascripts/api/user_api.js +++ b/app/assets/javascripts/api/user_api.js @@ -11,6 +11,7 @@ const USER_POST_STATUS_PATH = '/api/:version/user/status'; const USER_FOLLOW_PATH = '/api/:version/users/:id/follow'; const USER_UNFOLLOW_PATH = '/api/:version/users/:id/unfollow'; const USER_FOLLOWERS_PATH = '/api/:version/users/:id/followers'; +const USER_FOLLOWING_PATH = '/api/:version/users/:id/following'; const USER_ASSOCIATIONS_COUNT_PATH = '/api/:version/users/:id/associations_count'; export function getUsers(query, options) { @@ -82,6 +83,16 @@ export function getUserFollowers(userId, params) { }); } +export function getUserFollowing(userId, params) { + const url = buildApiUrl(USER_FOLLOWING_PATH).replace(':id', encodeURIComponent(userId)); + return axios.get(url, { + params: { + per_page: DEFAULT_PER_PAGE, + ...params, + }, + }); +} + export function associationsCount(userId) { const url = buildApiUrl(USER_ASSOCIATIONS_COUNT_PATH).replace(':id', encodeURIComponent(userId)); return axios.get(url); diff --git a/app/assets/javascripts/notes/components/discussion_counter.vue b/app/assets/javascripts/notes/components/discussion_counter.vue index ba5ffc60917..cff1043c258 100644 --- a/app/assets/javascripts/notes/components/discussion_counter.vue +++ b/app/assets/javascripts/notes/components/discussion_counter.vue @@ -1,13 +1,6 @@ <script> -import { - GlTooltipDirective, - GlButton, - GlButtonGroup, - GlDropdown, - GlDropdownItem, - GlIcon, -} from '@gitlab/ui'; -import { mapGetters, mapActions } from 'vuex'; +import { GlButton, GlButtonGroup, GlDisclosureDropdown, GlTooltipDirective } from '@gitlab/ui'; +import { mapActions, mapGetters } from 'vuex'; import { throttle } from 'lodash'; import { __ } from '~/locale'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; @@ -18,11 +11,9 @@ export default { GlTooltip: GlTooltipDirective, }, components: { + GlDisclosureDropdown, GlButton, GlButtonGroup, - GlDropdown, - GlDropdownItem, - GlIcon, }, mixins: [glFeatureFlagsMixin(), discussionNavigation], props: { @@ -56,6 +47,29 @@ export default { resolveAllDiscussionsIssuePath() { return this.getNoteableData.create_issue_to_resolve_discussions_path; }, + threadOptions() { + const options = [ + { + text: this.toggleThreadsLabel, + action: this.handleExpandDiscussions, + extraAttrs: { + 'data-testid': 'toggle-all-discussions-btn', + }, + }, + ]; + + if (this.resolveAllDiscussionsIssuePath && !this.allResolved) { + options.push({ + text: __('Resolve all with new issue'), + href: this.resolveAllDiscussionsIssuePath, + extraAttrs: { + 'data-testid': 'resolve-all-with-issue-link', + }, + }); + } + + return options; + }, }, methods: { ...mapActions(['setExpandDiscussions']), @@ -86,32 +100,25 @@ export default { > <template v-if="allResolved"> {{ __('All threads resolved!') }} - <gl-dropdown - v-gl-tooltip:discussionCounter.hover.bottom + <gl-disclosure-dropdown + v-gl-tooltip:discussionCounter.hover.top + icon="ellipsis_v" size="small" category="tertiary" - right + placement="right" + no-caret :title="__('Thread options')" :aria-label="__('Thread options')" toggle-class="btn-icon" class="gl-pt-0! gl-px-2 gl-h-full gl-ml-2" - > - <template #button-content> - <gl-icon name="ellipsis_v" class="mr-0" /> - </template> - <gl-dropdown-item - data-testid="toggle-all-discussions-btn" - @click="handleExpandDiscussions" - > - {{ toggleThreadsLabel }} - </gl-dropdown-item> - </gl-dropdown> + :items="threadOptions" + /> </template> <template v-else> {{ n__('%d unresolved thread', '%d unresolved threads', unresolvedDiscussionsCount) }} <gl-button-group class="gl-ml-3"> <gl-button - v-gl-tooltip:discussionCounter.hover.bottom + v-gl-tooltip:discussionCounter.hover.top :title="__('Go to previous unresolved thread')" :aria-label="__('Go to previous unresolved thread')" class="discussion-previous-btn gl-rounded-base! gl-px-2!" @@ -123,7 +130,7 @@ export default { @click="jumpPrevious" /> <gl-button - v-gl-tooltip:discussionCounter.hover.bottom + v-gl-tooltip:discussionCounter.hover.top :title="__('Go to next unresolved thread')" :aria-label="__('Go to next unresolved thread')" class="discussion-next-btn gl-rounded-base! gl-px-2!" @@ -134,32 +141,19 @@ export default { category="tertiary" @click="jumpNext" /> - <gl-dropdown - v-gl-tooltip:discussionCounter.hover.bottom + <gl-disclosure-dropdown + v-gl-tooltip:discussionCounter.hover.top + icon="ellipsis_v" size="small" category="tertiary" - right + placement="right" + no-caret :title="__('Thread options')" :aria-label="__('Thread options')" toggle-class="btn-icon" class="gl-pt-0! gl-px-2" - > - <template #button-content> - <gl-icon name="ellipsis_v" class="mr-0" /> - </template> - <gl-dropdown-item - data-testid="toggle-all-discussions-btn" - @click="handleExpandDiscussions" - > - {{ toggleThreadsLabel }} - </gl-dropdown-item> - <gl-dropdown-item - v-if="resolveAllDiscussionsIssuePath && !allResolved" - :href="resolveAllDiscussionsIssuePath" - > - {{ __('Resolve all with new issue') }} - </gl-dropdown-item> - </gl-dropdown> + :items="threadOptions" + /> </gl-button-group> </template> </div> diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/version_row.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/version_row.vue index 37a6fe75f15..ca2516810cf 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/version_row.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/version_row.vue @@ -1,7 +1,6 @@ <script> import { - GlDropdown, - GlDropdownItem, + GlDisclosureDropdown, GlFormCheckbox, GlIcon, GlLink, @@ -25,8 +24,7 @@ import { export default { name: 'PackageVersionRow', components: { - GlDropdown, - GlDropdownItem, + GlDisclosureDropdown, GlFormCheckbox, GlIcon, GlLink, @@ -61,6 +59,18 @@ export default { errorStatusRow() { return this.packageEntity?.status === PACKAGE_ERROR_STATUS; }, + dropdownItems() { + return [ + { + text: this.$options.i18n.deletePackage, + action: () => this.$emit('delete'), + extraAttrs: { + class: 'gl-text-red-500!', + 'data-testid': 'action-delete', + }, + }, + ]; + }, }, i18n: { deletePackage: DELETE_PACKAGE_TEXT, @@ -129,18 +139,15 @@ export default { </template> <template v-if="packageEntity.canDestroy" #right-action> - <gl-dropdown + <gl-disclosure-dropdown data-testid="delete-dropdown" icon="ellipsis_v" - :text="$options.i18n.moreActions" - :text-sr-only="true" + :items="dropdownItems" + :toggle-text="$options.i18n.moreActions" category="tertiary" + text-sr-only no-caret - > - <gl-dropdown-item data-testid="action-delete" variant="danger" @click="$emit('delete')">{{ - $options.i18n.deletePackage - }}</gl-dropdown-item> - </gl-dropdown> + /> </template> </list-item> </template> diff --git a/app/assets/javascripts/profile/components/following_tab.vue b/app/assets/javascripts/profile/components/following_tab.vue index 8ee878e3dcc..27c16ee5b46 100644 --- a/app/assets/javascripts/profile/components/following_tab.vue +++ b/app/assets/javascripts/profile/components/following_tab.vue @@ -1,16 +1,60 @@ <script> import { GlBadge, GlTab } from '@gitlab/ui'; import { s__ } from '~/locale'; +import { getUserFollowing } from '~/rest_api'; +import { createAlert } from '~/alert'; +import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils'; +import Follow from './follow.vue'; export default { i18n: { title: s__('UserProfile|Following'), + errorMessage: s__( + 'UserProfile|An error occurred loading the following. Please refresh the page to try again.', + ), }, components: { GlBadge, GlTab, + Follow, + }, + inject: ['followeesCount', 'userId'], + data() { + return { + following: [], + loading: true, + totalItems: 0, + page: 1, + }; + }, + watch: { + page: { + async handler() { + this.loading = true; + + try { + const { data: following, headers } = await getUserFollowing(this.userId, { + page: this.page, + }); + + const { total } = parseIntPagination(normalizeHeaders(headers)); + + this.following = following; + this.totalItems = total; + } catch (error) { + createAlert({ message: this.$options.i18n.errorMessage, error, captureError: true }); + } finally { + this.loading = false; + } + }, + immediate: true, + }, + }, + methods: { + onPaginationInput(page) { + this.page = page; + }, }, - inject: ['followeesCount'], }; </script> @@ -20,5 +64,12 @@ export default { <span>{{ $options.i18n.title }}</span> <gl-badge size="sm" class="gl-ml-2">{{ followeesCount }}</gl-badge> </template> + <follow + :users="following" + :loading="loading" + :page="page" + :total-items="totalItems" + @pagination-input="onPaginationInput" + /> </gl-tab> </template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue index 52cdafd4717..083ce4f87af 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue @@ -511,13 +511,13 @@ export default { }, i18n: { mergeCommitTemplateHintText: s__( - 'mrWidget|To change this default message, edit the template for merge commit messages. %{linkStart}Learn more.%{linkEnd}', + 'mrWidget|To change this default message, edit the template for merge commit messages. %{linkStart}Learn more%{linkEnd}.', ), squashCommitTemplateHintText: s__( - 'mrWidget|To change this default message, edit the template for squash commit messages. %{linkStart}Learn more.%{linkEnd}', + 'mrWidget|To change this default message, edit the template for squash commit messages. %{linkStart}Learn more%{linkEnd}.', ), mergeAndSquashCommitTemplatesHintText: s__( - 'mrWidget|To change these default messages, edit the templates for both the merge and squash commit messages. %{linkStart}Learn more.%{linkEnd}', + 'mrWidget|To change these default messages, edit the templates for both the merge and squash commit messages. %{linkStart}Learn more%{linkEnd}.', ), sourceDivergedFromTargetText: s__('mrWidget|The source branch is %{link} the target branch'), divergedCommits: (count) => n__('%d commit behind', '%d commits behind', count), @@ -619,9 +619,8 @@ export default { :href="commitTemplateHelpPage" class="inline-link" target="_blank" + >{{ content }}</gl-link > - {{ content }} - </gl-link> </template> </gl-sprintf> </p> diff --git a/app/models/ci/build_trace_chunk.rb b/app/models/ci/build_trace_chunk.rb index 03b59b19ef1..e90c480a8cb 100644 --- a/app/models/ci/build_trace_chunk.rb +++ b/app/models/ci/build_trace_chunk.rb @@ -242,7 +242,7 @@ module Ci ## # We need to so persist data then save a new store identifier before we # remove data from the previous store to make this operation - # trasnaction-safe. `unsafe_set_data! calls `save!` because of this + # transaction-safe. `unsafe_set_data! calls `save!` because of this # reason. # # TODO consider using callbacks and state machine to remove old data diff --git a/app/models/clusters/concerns/prometheus_client.rb b/app/models/clusters/concerns/prometheus_client.rb index 10cb307addd..d2f69b813aa 100644 --- a/app/models/clusters/concerns/prometheus_client.rb +++ b/app/models/clusters/concerns/prometheus_client.rb @@ -29,7 +29,7 @@ module Clusters rescue Kubeclient::HttpError, Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::ENETUNREACH # If users have mistakenly set parameters or removed the depended clusters, # `proxy_url` could raise an exception because gitlab can not communicate with the cluster. - # Since `PrometheusAdapter#can_query?` is eargely loaded on environement pages in gitlab, + # Since `PrometheusAdapter#can_query?` is eargely loaded on environment pages in gitlab, # we need to silence the exceptions end diff --git a/app/models/note.rb b/app/models/note.rb index 09ff7ad3979..2ac40357625 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -756,7 +756,7 @@ class Note < ApplicationRecord Ability.users_that_can_read_internal_notes(users, resource_parent).pluck(:id) end - # Method necesary while we transition into the new format for task system notes + # Method necessary while we transition into the new format for task system notes # TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/369923 def note return super unless system? && for_issue? && super&.match?(ISSUE_TASK_SYSTEM_NOTE_PATTERN) diff --git a/config/feature_flags/development/adapt_deprecated_approvals.yml b/config/feature_flags/development/adapt_deprecated_approvals.yml deleted file mode 100644 index 0f2ba559033..00000000000 --- a/config/feature_flags/development/adapt_deprecated_approvals.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: adapt_deprecated_approvals -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/118036 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/409686 -milestone: '16.0' -type: development -group: group::source code -default_enabled: true diff --git a/db/migrate/20230612074428_add_name_to_external_audit_event_destination.rb b/db/migrate/20230612074428_add_name_to_external_audit_event_destination.rb new file mode 100644 index 00000000000..4fe61c4caad --- /dev/null +++ b/db/migrate/20230612074428_add_name_to_external_audit_event_destination.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class AddNameToExternalAuditEventDestination < Gitlab::Database::Migration[2.1] + # rubocop:disable Migration/AddLimitToTextColumns + # text limit is added in a 20230612091526_add_text_limit_to_external_audit_event_destination_name.rb migration + def change + add_column :audit_events_external_audit_event_destinations, :name, :text + end + + # rubocop:enable Migration/AddLimitToTextColumns +end diff --git a/db/migrate/20230612091526_add_text_limit_to_external_audit_event_destination_name.rb b/db/migrate/20230612091526_add_text_limit_to_external_audit_event_destination_name.rb new file mode 100644 index 00000000000..d469c7cc964 --- /dev/null +++ b/db/migrate/20230612091526_add_text_limit_to_external_audit_event_destination_name.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class AddTextLimitToExternalAuditEventDestinationName < Gitlab::Database::Migration[2.1] + disable_ddl_transaction! + + def up + add_text_limit :audit_events_external_audit_event_destinations, :name, 72 + end + + def down + remove_text_limit :audit_events_external_audit_event_destinations, :name + end +end diff --git a/db/migrate/20230612091747_add_name_to_instance_audit_event_destination.rb b/db/migrate/20230612091747_add_name_to_instance_audit_event_destination.rb new file mode 100644 index 00000000000..7a99654bd66 --- /dev/null +++ b/db/migrate/20230612091747_add_name_to_instance_audit_event_destination.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class AddNameToInstanceAuditEventDestination < Gitlab::Database::Migration[2.1] + # rubocop:disable Migration/AddLimitToTextColumns + # text limit is added in a 20230612091910_add_text_limit_to_instance_audit_event_destination_name.rb migration + def change + add_column :audit_events_instance_external_audit_event_destinations, :name, :text + end + + # rubocop:enable Migration/AddLimitToTextColumns +end diff --git a/db/migrate/20230612091910_add_text_limit_to_instance_audit_event_destination_name.rb b/db/migrate/20230612091910_add_text_limit_to_instance_audit_event_destination_name.rb new file mode 100644 index 00000000000..0bdfd4baff7 --- /dev/null +++ b/db/migrate/20230612091910_add_text_limit_to_instance_audit_event_destination_name.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class AddTextLimitToInstanceAuditEventDestinationName < Gitlab::Database::Migration[2.1] + disable_ddl_transaction! + + def up + add_text_limit :audit_events_instance_external_audit_event_destinations, :name, 72 + end + + def down + remove_text_limit :audit_events_instance_external_audit_event_destinations, :name + end +end diff --git a/db/migrate/20230615074515_add_index_to_audit_event_external_destination.rb b/db/migrate/20230615074515_add_index_to_audit_event_external_destination.rb new file mode 100644 index 00000000000..b1846c8abed --- /dev/null +++ b/db/migrate/20230615074515_add_index_to_audit_event_external_destination.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class AddIndexToAuditEventExternalDestination < Gitlab::Database::Migration[2.1] + disable_ddl_transaction! + + INDEX_NAME = 'unique_external_audit_event_destination_namespace_id_and_name' + + def up + add_concurrent_index :audit_events_external_audit_event_destinations, [:namespace_id, :name], unique: true, + name: INDEX_NAME + end + + def down + remove_concurrent_index_by_name :audit_events_external_audit_event_destinations, INDEX_NAME + end +end diff --git a/db/migrate/20230615074544_add_index_to_instance_audit_event_destination.rb b/db/migrate/20230615074544_add_index_to_instance_audit_event_destination.rb new file mode 100644 index 00000000000..4e14bb537a3 --- /dev/null +++ b/db/migrate/20230615074544_add_index_to_instance_audit_event_destination.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class AddIndexToInstanceAuditEventDestination < Gitlab::Database::Migration[2.1] + disable_ddl_transaction! + + INDEX_NAME = 'unique_instance_audit_event_destination_name' + + def up + add_concurrent_index :audit_events_instance_external_audit_event_destinations, :name, unique: true, name: INDEX_NAME + end + + def down + remove_concurrent_index_by_name :audit_events_instance_external_audit_event_destinations, INDEX_NAME + end +end diff --git a/db/schema_migrations/20230612074428 b/db/schema_migrations/20230612074428 new file mode 100644 index 00000000000..a763ffafa46 --- /dev/null +++ b/db/schema_migrations/20230612074428 @@ -0,0 +1 @@ +65df1d9bfc31a41210d9a18a63786d35bde456d0fd94520be186e8eb06214d7e
\ No newline at end of file diff --git a/db/schema_migrations/20230612091526 b/db/schema_migrations/20230612091526 new file mode 100644 index 00000000000..04fa44523f9 --- /dev/null +++ b/db/schema_migrations/20230612091526 @@ -0,0 +1 @@ +f03f81f50c0777502aeb1040f5e2805b296d77979760156f5e2e1e184792d503
\ No newline at end of file diff --git a/db/schema_migrations/20230612091747 b/db/schema_migrations/20230612091747 new file mode 100644 index 00000000000..fbc9998118a --- /dev/null +++ b/db/schema_migrations/20230612091747 @@ -0,0 +1 @@ +ca928191459eeeeed6c10b444053eedb5e7236e94403a36ee8aa86a448d352c6
\ No newline at end of file diff --git a/db/schema_migrations/20230612091910 b/db/schema_migrations/20230612091910 new file mode 100644 index 00000000000..8446746db36 --- /dev/null +++ b/db/schema_migrations/20230612091910 @@ -0,0 +1 @@ +6e534b91092c00e405dd621ea4d05b732b075007563f776b1fb88087f7edeaf9
\ No newline at end of file diff --git a/db/schema_migrations/20230615074515 b/db/schema_migrations/20230615074515 new file mode 100644 index 00000000000..22f8ca20d74 --- /dev/null +++ b/db/schema_migrations/20230615074515 @@ -0,0 +1 @@ +bfbb49d6b6b263be1fc669807ceb5f2e6cedb0e68221cb17a9e4fe5c2bedb439
\ No newline at end of file diff --git a/db/schema_migrations/20230615074544 b/db/schema_migrations/20230615074544 new file mode 100644 index 00000000000..f9079d090a8 --- /dev/null +++ b/db/schema_migrations/20230615074544 @@ -0,0 +1 @@ +45c76efa0091f7b63ce476cd7d6909d3c11aec2d96e51087f44060d5192b76a5
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 77b32db0eda..a43c8fa1238 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -12120,8 +12120,10 @@ CREATE TABLE audit_events_external_audit_event_destinations ( created_at timestamp with time zone NOT NULL, updated_at timestamp with time zone NOT NULL, verification_token text, + name text, CONSTRAINT check_2feafb9daf CHECK ((char_length(destination_url) <= 255)), - CONSTRAINT check_8ec80a7d06 CHECK ((char_length(verification_token) <= 24)) + CONSTRAINT check_8ec80a7d06 CHECK ((char_length(verification_token) <= 24)), + CONSTRAINT check_c52ff8e90e CHECK ((char_length(name) <= 72)) ); CREATE SEQUENCE audit_events_external_audit_event_destinations_id_seq @@ -12173,6 +12175,8 @@ CREATE TABLE audit_events_instance_external_audit_event_destinations ( destination_url text NOT NULL, encrypted_verification_token bytea NOT NULL, encrypted_verification_token_iv bytea NOT NULL, + name text, + CONSTRAINT check_433fbb3305 CHECK ((char_length(name) <= 72)), CONSTRAINT check_4dc67167ce CHECK ((char_length(destination_url) <= 255)) ); @@ -33580,6 +33584,8 @@ CREATE UNIQUE INDEX uniq_pkgs_debian_project_distributions_project_id_and_suite CREATE UNIQUE INDEX unique_ci_builds_token_encrypted_and_partition_id ON ci_builds USING btree (token_encrypted, partition_id) WHERE (token_encrypted IS NOT NULL); +CREATE UNIQUE INDEX unique_external_audit_event_destination_namespace_id_and_name ON audit_events_external_audit_event_destinations USING btree (namespace_id, name); + CREATE UNIQUE INDEX unique_google_cloud_logging_configurations_on_namespace_id ON audit_events_google_cloud_logging_configurations USING btree (namespace_id, google_project_id_name, log_id_name); CREATE UNIQUE INDEX unique_idx_namespaces_storage_limit_exclusions_on_namespace_id ON namespaces_storage_limit_exclusions USING btree (namespace_id); @@ -33590,6 +33596,8 @@ CREATE UNIQUE INDEX unique_index_for_project_pages_unique_domain ON project_sett CREATE UNIQUE INDEX unique_index_on_system_note_metadata_id ON resource_link_events USING btree (system_note_metadata_id); +CREATE UNIQUE INDEX unique_instance_audit_event_destination_namespace_id_and_name ON audit_events_instance_external_audit_event_destinations USING btree (name); + CREATE UNIQUE INDEX unique_merge_request_diff_llm_summaries_on_mr_diff_id ON merge_request_diff_llm_summaries USING btree (merge_request_diff_id); CREATE UNIQUE INDEX unique_merge_request_metrics_by_merge_request_id ON merge_request_metrics USING btree (merge_request_id); diff --git a/doc/policy/experiment-beta-support.md b/doc/policy/experiment-beta-support.md index 59490ac3d68..8e87f4e4b2b 100644 --- a/doc/policy/experiment-beta-support.md +++ b/doc/policy/experiment-beta-support.md @@ -12,7 +12,7 @@ All other features are considered to be Generally Available (GA). ## Experiment -Support is not provided for features listed as "Experimental" or "Alpha" or any similar designation. Issues regarding such features should be opened in the GitLab issue tracker. +Support is not provided for features listed as "Experimental" or "Alpha" or any similar designation. Issues regarding such features should be opened in the GitLab issue tracker. Teams should release features as GA from the start unless there are strong reasons to release them as Experiment or Beta versions first. - Not ready for production use. - No support available. @@ -20,10 +20,12 @@ Support is not provided for features listed as "Experimental" or "Alpha" or any - Can be removed at any time. - Data loss may occur. - Documentation may not exist or just be in a blog format. -- [User interface reflects Experiment status](https://design.gitlab.com/usability/feature-management#highlighting-feature-versions). -- User experience incomplete, might be just quick action access. -- Behind a feature flag that is on by default. -- Behind a toggle that is off by default. +- Offer an easy way to choose to opt-in to experimental features with minimal friction. For example, needing to flip a feature flag is too much friction, but a group or project-level setting that is in the UI is not. +- Link out to the [GitLab Testing Agreement](https://about.gitlab.com/handbook/legal/testing-agreement/) in the opt-in. +- Documentation reflects that the feature is subject to the [GitLab Testing Agreement](https://about.gitlab.com/handbook/legal/testing-agreement/). +- [UI reflects experiment status](https://design.gitlab.com/usability/feature-management#highlighting-feature-versions). +- Feedback issue to engage with team. +- UX not finalized, might be just quick action access. - Not announced in a release post. - Can be promoted in the user interface through [discovery moments](https://design.gitlab.com/usability/feature-management#discovery-moments), if needed. - Feedback issue to engage with team. @@ -54,13 +56,13 @@ Generally Available features means that they passed the [Production Readiness Re - Fully documented and supported. - User experience complete and in line with GitLab design standards. -## Never internal +## Provide earlier access -Features are never internal (GitLab team-members) only. +Give users the ability to opt into experimental features when there is enough value. +Where possible, release an experimental feature externally instead of only testing internally or waiting for the feature to be in a Beta state. Our [mission is "everyone can contribute"](https://about.gitlab.com/company/mission/), and that is only possible if people outside the company can try a feature. We get higher quality (more diverse) feedback if people from different organizations try something. -We've also learned that internal only as a state slows us down more than it speeds us up. -Release the experiment instead of testing internally or waiting for the feature to be in a Beta state. +We've learned that keeping features internal only for extended periods of time slows us down unnecessarily. The experimental features are only shown when people/organizations opt-in to experiments, we are allowed to make mistakes here and literally experiment. ## All features are in production diff --git a/lib/api/files.rb b/lib/api/files.rb index 45e935d7ea2..c140cec658d 100644 --- a/lib/api/files.rb +++ b/lib/api/files.rb @@ -49,7 +49,14 @@ module API end def content_sha - cache_client.fetch("blob_content_sha256:#{user_project.full_path}:#{@blob.id}") do + cache_client.fetch( + "blob_content_sha256:#{user_project.full_path}:#{@blob.id}", + nil, + { + cache_identifier: 'API::Files#content_sha', + backing_resource: :gitaly + } + ) do @blob.load_all_data! Digest::SHA256.hexdigest(@blob.data) @@ -57,10 +64,8 @@ module API end def cache_client - Gitlab::Cache::Client.build_with_metadata( - cache_identifier: 'API::Files#content_sha', - feature_category: :source_code_management, - backing_resource: :gitaly + @cache_client ||= Gitlab::Cache::Client.new( + Gitlab::Cache::Metrics.new(Gitlab::Cache::Metadata.new(feature_category: :source_code_management)) ) end diff --git a/lib/gitlab/cache/client.rb b/lib/gitlab/cache/client.rb index 37d6cac8d43..7c783461522 100644 --- a/lib/gitlab/cache/client.rb +++ b/lib/gitlab/cache/client.rb @@ -6,60 +6,38 @@ module Gitlab class Client DEFAULT_BACKING_RESOURCE = :unknown - # Build Cache client with the metadata support - # - # @param cache_identifier [String] defines the location of the cache definition - # Example: "ProtectedBranches::CacheService#fetch" - # @param feature_category [Symbol] name of the feature category (from config/feature_categories.yml) - # @param backing_resource [Symbol] most affected resource by cache generation (full list: VALID_BACKING_RESOURCES) - # @return [Gitlab::Cache::Client] - def self.build_with_metadata( - cache_identifier:, - feature_category:, - backing_resource: DEFAULT_BACKING_RESOURCE - ) - new(Metadata.new( - cache_identifier: cache_identifier, - feature_category: feature_category, - backing_resource: backing_resource - )) - end - - def initialize(metadata, backend: Rails.cache) - @metadata = metadata - @metrics = Metrics.new(metadata) + def initialize(metrics, backend: Rails.cache) + @metrics = metrics @backend = backend end - def read(name) - read_result = backend.read(name) + def read(name, options = nil, labels = {}) + read_result = backend.read(name, options) if read_result.nil? - metrics.increment_cache_miss + metrics.increment_cache_miss(labels) else - metrics.increment_cache_hit + metrics.increment_cache_hit(labels) end read_result end - def fetch(name, options = nil, &block) - read_result = read(name) + def fetch(name, options = nil, labels = {}, &block) + read_result = read(name, options, labels) return read_result unless block || read_result backend.fetch(name, options) do - metrics.observe_cache_generation(&block) + metrics.observe_cache_generation(labels, &block) end end delegate :write, :exist?, :delete, to: :backend - attr_reader :metadata, :metrics - private - attr_reader :backend + attr_reader :metrics, :backend end end end diff --git a/lib/gitlab/cache/metadata.rb b/lib/gitlab/cache/metadata.rb index de35b332300..7251eb11740 100644 --- a/lib/gitlab/cache/metadata.rb +++ b/lib/gitlab/cache/metadata.rb @@ -12,8 +12,8 @@ module Gitlab # @param backing_resource [Symbol] most affected resource by cache generation (full list: VALID_BACKING_RESOURCES) # @return [Gitlab::Cache::Metadata] def initialize( - cache_identifier:, feature_category:, + cache_identifier: nil, backing_resource: Client::DEFAULT_BACKING_RESOURCE ) @cache_identifier = cache_identifier diff --git a/lib/gitlab/cache/metrics.rb b/lib/gitlab/cache/metrics.rb index d9c80f076b9..26a1f346f13 100644 --- a/lib/gitlab/cache/metrics.rb +++ b/lib/gitlab/cache/metrics.rb @@ -12,14 +12,14 @@ module Gitlab # Increase cache hit counter # - def increment_cache_hit - counter.increment(labels.merge(cache_hit: true)) + def increment_cache_hit(labels = {}) + counter.increment(base_labels.merge(labels, cache_hit: true)) end # Increase cache miss counter # - def increment_cache_miss - counter.increment(labels.merge(cache_hit: false)) + def increment_cache_miss(labels = {}) + counter.increment(base_labels.merge(labels, cache_hit: false)) end # Measure the duration of cacheable action @@ -29,12 +29,12 @@ module Gitlab # cacheable_action # end # - def observe_cache_generation(&block) + def observe_cache_generation(labels = {}, &block) real_start = Gitlab::Metrics::System.monotonic_time value = yield - histogram.observe({}, Gitlab::Metrics::System.monotonic_time - real_start) + histogram.observe(base_labels.merge(labels), Gitlab::Metrics::System.monotonic_time - real_start) value end @@ -44,20 +44,24 @@ module Gitlab attr_reader :cache_metadata def counter - @counter ||= Gitlab::Metrics.counter(:redis_hit_miss_operations_total, "Hit/miss Redis cache counter") + @counter ||= Gitlab::Metrics.counter( + :redis_hit_miss_operations_total, + "Hit/miss Redis cache counter", + base_labels + ) end def histogram @histogram ||= Gitlab::Metrics.histogram( :redis_cache_generation_duration_seconds, 'Duration of Redis cache generation', - labels, + base_labels, DEFAULT_BUCKETS ) end - def labels - @labels ||= { + def base_labels + @base_labels ||= { cache_identifier: cache_metadata.cache_identifier, feature_category: cache_metadata.feature_category, backing_resource: cache_metadata.backing_resource diff --git a/lib/gitlab/ci/parsers/security/validators/schema_validator.rb b/lib/gitlab/ci/parsers/security/validators/schema_validator.rb index 92d9d170575..e39482481c7 100644 --- a/lib/gitlab/ci/parsers/security/validators/schema_validator.rb +++ b/lib/gitlab/ci/parsers/security/validators/schema_validator.rb @@ -7,14 +7,14 @@ module Gitlab module Validators class SchemaValidator SUPPORTED_VERSIONS = { - cluster_image_scanning: %w[15.0.0 15.0.1 15.0.2 15.0.4 15.0.6], - container_scanning: %w[15.0.0 15.0.1 15.0.2 15.0.4 15.0.6], - coverage_fuzzing: %w[15.0.0 15.0.1 15.0.2 15.0.4 15.0.6], - dast: %w[15.0.0 15.0.1 15.0.2 15.0.4 15.0.6], - api_fuzzing: %w[15.0.0 15.0.1 15.0.2 15.0.4 15.0.6], - dependency_scanning: %w[15.0.0 15.0.1 15.0.2 15.0.4 15.0.6], - sast: %w[15.0.0 15.0.1 15.0.2 15.0.4 15.0.6], - secret_detection: %w[15.0.0 15.0.1 15.0.2 15.0.4 15.0.6] + cluster_image_scanning: %w[15.0.0 15.0.1 15.0.2 15.0.4 15.0.5 15.0.6], + container_scanning: %w[15.0.0 15.0.1 15.0.2 15.0.4 15.0.5 15.0.6], + coverage_fuzzing: %w[15.0.0 15.0.1 15.0.2 15.0.4 15.0.5 15.0.6], + dast: %w[15.0.0 15.0.1 15.0.2 15.0.4 15.0.5 15.0.6], + api_fuzzing: %w[15.0.0 15.0.1 15.0.2 15.0.4 15.0.5 15.0.6], + dependency_scanning: %w[15.0.0 15.0.1 15.0.2 15.0.4 15.0.5 15.0.6], + sast: %w[15.0.0 15.0.1 15.0.2 15.0.4 15.0.5 15.0.6], + secret_detection: %w[15.0.0 15.0.1 15.0.2 15.0.4 15.0.5 15.0.6] }.freeze VERSIONS_TO_REMOVE_IN_17_0 = %w[].freeze diff --git a/lib/gitlab/ci/parsers/security/validators/schemas/15.0.5/cluster-image-scanning-report-format.json b/lib/gitlab/ci/parsers/security/validators/schemas/15.0.5/cluster-image-scanning-report-format.json new file mode 100644 index 00000000000..5563acbe232 --- /dev/null +++ b/lib/gitlab/ci/parsers/security/validators/schemas/15.0.5/cluster-image-scanning-report-format.json @@ -0,0 +1,1035 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/raw/master/dist/cluster-image-scanning-report-format.json", + "title": "Report format for GitLab Cluster Image Scanning", + "description": "This schema provides the the report format for Cluster Image Scanning (https://docs.gitlab.com/ee/user/application_security/cluster_image_scanning/).", + "definitions": { + "detail_type": { + "oneOf": [ + { + "$ref": "#/definitions/named_list" + }, + { + "$ref": "#/definitions/list" + }, + { + "$ref": "#/definitions/table" + }, + { + "$ref": "#/definitions/text" + }, + { + "$ref": "#/definitions/url" + }, + { + "$ref": "#/definitions/code" + }, + { + "$ref": "#/definitions/value" + }, + { + "$ref": "#/definitions/diff" + }, + { + "$ref": "#/definitions/markdown" + }, + { + "$ref": "#/definitions/commit" + }, + { + "$ref": "#/definitions/file_location" + }, + { + "$ref": "#/definitions/module_location" + } + ] + }, + "text_value": { + "type": "string" + }, + "named_field": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "$ref": "#/definitions/text_value", + "type": "string", + "minLength": 1 + }, + "description": { + "$ref": "#/definitions/text_value" + } + } + }, + "named_list": { + "type": "object", + "description": "An object with named and typed fields", + "required": [ + "type", + "items" + ], + "properties": { + "type": { + "const": "named-list" + }, + "items": { + "type": "object", + "patternProperties": { + "^.*$": { + "allOf": [ + { + "$ref": "#/definitions/named_field" + }, + { + "$ref": "#/definitions/detail_type" + } + ] + } + } + } + } + }, + "list": { + "type": "object", + "description": "A list of typed fields", + "required": [ + "type", + "items" + ], + "properties": { + "type": { + "const": "list" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + } + } + }, + "table": { + "type": "object", + "description": "A table of typed fields", + "required": [ + "type", + "rows" + ], + "properties": { + "type": { + "const": "table" + }, + "header": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + }, + "rows": { + "type": "array", + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + } + } + } + }, + "text": { + "type": "object", + "description": "Raw text", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "text" + }, + "value": { + "$ref": "#/definitions/text_value" + } + } + }, + "url": { + "type": "object", + "description": "A single URL", + "required": [ + "type", + "href" + ], + "properties": { + "type": { + "const": "url" + }, + "text": { + "$ref": "#/definitions/text_value" + }, + "href": { + "type": "string", + "minLength": 1, + "examples": [ + "http://mysite.com" + ] + } + } + }, + "code": { + "type": "object", + "description": "A codeblock", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "code" + }, + "value": { + "type": "string" + }, + "lang": { + "type": "string", + "description": "A programming language" + } + } + }, + "value": { + "type": "object", + "description": "A field that can store a range of types of value", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "value" + }, + "value": { + "type": [ + "number", + "string", + "boolean" + ] + } + } + }, + "diff": { + "type": "object", + "description": "A diff", + "required": [ + "type", + "before", + "after" + ], + "properties": { + "type": { + "const": "diff" + }, + "before": { + "type": "string" + }, + "after": { + "type": "string" + } + } + }, + "markdown": { + "type": "object", + "description": "GitLab flavoured markdown, see https://docs.gitlab.com/ee/user/markdown.html", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "markdown" + }, + "value": { + "$ref": "#/definitions/text_value", + "examples": [ + "Here is markdown `inline code` #1 [test](gitlab.com)\n\n![GitLab Logo](https://about.gitlab.com/images/press/logo/preview/gitlab-logo-white-preview.png)" + ] + } + } + }, + "commit": { + "type": "object", + "description": "A commit/tag/branch within the GitLab project", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "commit" + }, + "value": { + "type": "string", + "description": "The commit SHA", + "minLength": 1 + } + } + }, + "file_location": { + "type": "object", + "description": "A location within a file in the project", + "required": [ + "type", + "file_name", + "line_start" + ], + "properties": { + "type": { + "const": "file-location" + }, + "file_name": { + "type": "string", + "minLength": 1 + }, + "line_start": { + "type": "integer" + }, + "line_end": { + "type": "integer" + } + } + }, + "module_location": { + "type": "object", + "description": "A location within a binary module of the form module+relative_offset", + "required": [ + "type", + "module_name", + "offset" + ], + "properties": { + "type": { + "const": "module-location" + }, + "module_name": { + "type": "string", + "minLength": 1, + "examples": [ + "compiled_binary" + ] + }, + "offset": { + "type": "integer", + "examples": [ + 100 + ] + } + } + } + }, + "self": { + "version": "15.0.5" + }, + "type": "object", + "required": [ + "scan", + "version", + "vulnerabilities" + ], + "additionalProperties": true, + "properties": { + "scan": { + "type": "object", + "required": [ + "analyzer", + "end_time", + "scanner", + "start_time", + "status", + "type" + ], + "properties": { + "end_time": { + "type": "string", + "description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan finished.", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$", + "examples": [ + "2020-01-28T03:26:02" + ] + }, + "messages": { + "type": "array", + "items": { + "type": "object", + "description": "Communication intended for the initiator of a scan.", + "required": [ + "level", + "value" + ], + "properties": { + "level": { + "type": "string", + "description": "Describes the severity of the communication. Use info to communicate normal scan behaviour; warn to communicate a potentially recoverable problem, or a partial error; fatal to communicate an issue that causes the scan to halt.", + "enum": [ + "info", + "warn", + "fatal" + ], + "examples": [ + "info" + ] + }, + "value": { + "type": "string", + "description": "The message to communicate.", + "minLength": 1, + "examples": [ + "Permission denied, scanning aborted" + ] + } + } + } + }, + "options": { + "type": "array", + "items": { + "type": "object", + "description": "A configuration option used for this scan.", + "required": [ + "name", + "value" + ], + "properties": { + "name": { + "type": "string", + "description": "The configuration option name.", + "maxLength": 255, + "minLength": 1, + "examples": [ + "DAST_FF_ENABLE_BAS", + "DOCKER_TLS_CERTDIR", + "DS_MAX_DEPTH", + "SECURE_LOG_LEVEL" + ] + }, + "source": { + "type": "string", + "description": "The source of this option.", + "enum": [ + "argument", + "file", + "env_variable", + "other" + ] + }, + "value": { + "type": [ + "boolean", + "integer", + "null", + "string" + ], + "description": "The value used for this scan.", + "examples": [ + true, + 2, + null, + "fatal", + "" + ] + } + } + } + }, + "analyzer": { + "type": "object", + "description": "Object defining the analyzer used to perform the scan. Analyzers typically delegate to an underlying scanner to run the scan.", + "required": [ + "id", + "name", + "version", + "vendor" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique id that identifies the analyzer.", + "minLength": 1, + "examples": [ + "gitlab-dast" + ] + }, + "name": { + "type": "string", + "description": "A human readable value that identifies the analyzer, not required to be unique.", + "minLength": 1, + "examples": [ + "GitLab DAST" + ] + }, + "url": { + "type": "string", + "pattern": "^https?://.+", + "description": "A link to more information about the analyzer.", + "examples": [ + "https://docs.gitlab.com/ee/user/application_security/dast" + ] + }, + "vendor": { + "description": "The vendor/maintainer of the analyzer.", + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the vendor.", + "minLength": 1, + "examples": [ + "GitLab" + ] + } + } + }, + "version": { + "type": "string", + "description": "The version of the analyzer.", + "minLength": 1, + "examples": [ + "1.0.2" + ] + } + } + }, + "scanner": { + "type": "object", + "description": "Object defining the scanner used to perform the scan.", + "required": [ + "id", + "name", + "version", + "vendor" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique id that identifies the scanner.", + "minLength": 1, + "examples": [ + "my-sast-scanner" + ] + }, + "name": { + "type": "string", + "description": "A human readable value that identifies the scanner, not required to be unique.", + "minLength": 1, + "examples": [ + "My SAST Scanner" + ] + }, + "url": { + "type": "string", + "description": "A link to more information about the scanner.", + "examples": [ + "https://scanner.url" + ] + }, + "version": { + "type": "string", + "description": "The version of the scanner.", + "minLength": 1, + "examples": [ + "1.0.2" + ] + }, + "vendor": { + "description": "The vendor/maintainer of the scanner.", + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the vendor.", + "minLength": 1, + "examples": [ + "GitLab" + ] + } + } + } + } + }, + "start_time": { + "type": "string", + "description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan started.", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$", + "examples": [ + "2020-02-14T16:01:59" + ] + }, + "status": { + "type": "string", + "description": "Result of the scan.", + "enum": [ + "success", + "failure" + ] + }, + "type": { + "type": "string", + "description": "Type of the scan.", + "enum": [ + "cluster_image_scanning" + ] + }, + "primary_identifiers": { + "type": "array", + "description": "An unordered array containing an exhaustive list of primary identifiers for which the analyzer may return results", + "items": { + "type": "object", + "required": [ + "type", + "name", + "value" + ], + "properties": { + "type": { + "type": "string", + "description": "for example, cve, cwe, osvdb, usn, or an analyzer-dependent type such as gemnasium).", + "minLength": 1 + }, + "name": { + "type": "string", + "description": "Human-readable name of the identifier.", + "minLength": 1 + }, + "url": { + "type": "string", + "description": "URL of the identifier's documentation.", + "pattern": "^https?://.+" + }, + "value": { + "type": "string", + "description": "Value of the identifier, for matching purpose.", + "minLength": 1 + } + } + } + } + } + }, + "schema": { + "type": "string", + "description": "URI pointing to the validating security report schema.", + "pattern": "^https?://.+" + }, + "version": { + "type": "string", + "description": "The version of the schema to which the JSON report conforms.", + "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" + }, + "vulnerabilities": { + "type": "array", + "description": "Array of vulnerability objects.", + "items": { + "type": "object", + "description": "Describes the vulnerability using GitLab Flavored Markdown", + "required": [ + "id", + "identifiers", + "location" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "description": "Unique identifier of the vulnerability. This is recommended to be a UUID.", + "examples": [ + "642735a5-1425-428d-8d4e-3c854885a3c9" + ] + }, + "name": { + "type": "string", + "maxLength": 255, + "description": "The name of the vulnerability. This must not include the finding's specific information." + }, + "description": { + "type": "string", + "maxLength": 1048576, + "description": "A long text section describing the vulnerability more fully." + }, + "severity": { + "type": "string", + "description": "How much the vulnerability impacts the software. Possible values are Info, Unknown, Low, Medium, High, or Critical. Note that some analyzers may not report all these possible values.", + "enum": [ + "Info", + "Unknown", + "Low", + "Medium", + "High", + "Critical" + ] + }, + "solution": { + "type": "string", + "maxLength": 7000, + "description": "Explanation of how to fix the vulnerability." + }, + "identifiers": { + "type": "array", + "minItems": 1, + "description": "An ordered array of references that identify a vulnerability on internal or external databases. The first identifier is the Primary Identifier, which has special meaning.", + "items": { + "type": "object", + "required": [ + "type", + "name", + "value" + ], + "properties": { + "type": { + "type": "string", + "description": "for example, cve, cwe, osvdb, usn, or an analyzer-dependent type such as gemnasium).", + "minLength": 1 + }, + "name": { + "type": "string", + "description": "Human-readable name of the identifier.", + "minLength": 1 + }, + "url": { + "type": "string", + "description": "URL of the identifier's documentation.", + "pattern": "^https?://.+" + }, + "value": { + "type": "string", + "description": "Value of the identifier, for matching purpose.", + "minLength": 1 + } + } + } + }, + "links": { + "type": "array", + "description": "An array of references to external documentation or articles that describe the vulnerability.", + "items": { + "type": "object", + "required": [ + "url" + ], + "properties": { + "name": { + "type": "string", + "description": "Name of the vulnerability details link." + }, + "url": { + "type": "string", + "description": "URL of the vulnerability details document.", + "pattern": "^https?://.+" + } + } + } + }, + "details": { + "$ref": "#/definitions/named_list/properties/items" + }, + "tracking": { + "type": "object", + "description": "Describes how this vulnerability should be tracked as the project changes.", + "oneOf": [ + { + "description": "Declares that a series of items should be tracked using source-specific tracking methods.", + "required": [ + "items" + ], + "properties": { + "type": { + "const": "source" + }, + "items": { + "type": "array", + "items": { + "description": "An item that should be tracked using source-specific tracking methods.", + "type": "object", + "required": [ + "signatures" + ], + "properties": { + "file": { + "type": "string", + "description": "Path to the file where the vulnerability is located." + }, + "start_line": { + "type": "number", + "description": "The first line of the file that includes the vulnerability." + }, + "end_line": { + "type": "number", + "description": "The last line of the file that includes the vulnerability." + }, + "signatures": { + "type": "array", + "description": "An array of calculated tracking signatures for this tracking item.", + "minItems": 1, + "items": { + "description": "A calculated tracking signature value and metadata.", + "type": "object", + "required": [ + "algorithm", + "value" + ], + "properties": { + "algorithm": { + "type": "string", + "description": "The algorithm used to generate the signature." + }, + "value": { + "type": "string", + "description": "The result of this signature algorithm." + } + } + } + } + } + } + } + } + } + ], + "properties": { + "type": { + "type": "string", + "description": "Each tracking type must declare its own type." + } + } + }, + "flags": { + "description": "Flags that can be attached to vulnerabilities.", + "type": "array", + "items": { + "type": "object", + "description": "Informational flags identified and assigned to a vulnerability.", + "required": [ + "type", + "origin", + "description" + ], + "properties": { + "type": { + "type": "string", + "minLength": 1, + "description": "Result of the scan.", + "enum": [ + "flagged-as-likely-false-positive" + ] + }, + "origin": { + "minLength": 1, + "description": "Tool that issued the flag.", + "type": "string" + }, + "description": { + "minLength": 1, + "description": "What the flag is about.", + "type": "string" + } + } + } + }, + "location": { + "type": "object", + "description": "Identifies the vulnerability's location.", + "required": [ + "dependency", + "image", + "kubernetes_resource" + ], + "properties": { + "dependency": { + "type": "object", + "description": "Describes the dependency of a project where the vulnerability is located.", + "required": [ + "package", + "version" + ], + "properties": { + "package": { + "type": "object", + "description": "Provides information on the package where the vulnerability is located.", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "Name of the package where the vulnerability is located." + } + } + }, + "version": { + "type": "string", + "description": "Version of the vulnerable package." + }, + "iid": { + "description": "ID that identifies the dependency in the scope of a dependency file.", + "type": "number" + }, + "direct": { + "type": "boolean", + "description": "Tells whether this is a direct, top-level dependency of the scanned project." + }, + "dependency_path": { + "type": "array", + "description": "Ancestors of the dependency, starting from a direct project dependency, and ending with an immediate parent of the dependency. The dependency itself is excluded from the path. Direct dependencies have no path.", + "items": { + "type": "object", + "required": [ + "iid" + ], + "properties": { + "iid": { + "type": "number", + "description": "ID that is unique in the scope of a parent object, and specific to the resource type." + } + } + } + } + } + }, + "operating_system": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "The operating system that contains the vulnerable package." + }, + "image": { + "type": "string", + "minLength": 1, + "description": "The analyzed Docker image.", + "examples": [ + "index.docker.io/library/nginx:1.21" + ] + }, + "kubernetes_resource": { + "type": "object", + "description": "The specific Kubernetes resource that was scanned.", + "required": [ + "namespace", + "kind", + "name", + "container_name" + ], + "properties": { + "namespace": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "The Kubernetes namespace the resource that had its image scanned.", + "examples": [ + "default", + "staging", + "production" + ] + }, + "kind": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "The Kubernetes kind the resource that had its image scanned.", + "examples": [ + "Deployment", + "DaemonSet" + ] + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "The name of the resource that had its image scanned.", + "examples": [ + "nginx-ingress" + ] + }, + "container_name": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "The name of the container that had its image scanned.", + "examples": [ + "nginx" + ] + }, + "agent_id": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "The GitLab ID of the Kubernetes Agent which performed the scan.", + "examples": [ + "1234" + ] + }, + "cluster_id": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "The GitLab ID of the Kubernetes cluster when using cluster integration.", + "examples": [ + "1234" + ] + } + } + } + } + } + } + } + }, + "remediations": { + "type": "array", + "description": "An array of objects containing information on available remediations, along with patch diffs to apply.", + "items": { + "type": "object", + "required": [ + "fixes", + "summary", + "diff" + ], + "properties": { + "fixes": { + "type": "array", + "description": "An array of strings that represent references to vulnerabilities fixed by this remediation.", + "items": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "description": "Unique identifier of the vulnerability. This is recommended to be a UUID.", + "examples": [ + "642735a5-1425-428d-8d4e-3c854885a3c9" + ] + } + } + } + }, + "summary": { + "type": "string", + "minLength": 1, + "description": "An overview of how the vulnerabilities were fixed." + }, + "diff": { + "type": "string", + "minLength": 1, + "description": "A base64-encoded remediation code diff, compatible with git apply." + } + } + } + } + } +} diff --git a/lib/gitlab/ci/parsers/security/validators/schemas/15.0.5/container-scanning-report-format.json b/lib/gitlab/ci/parsers/security/validators/schemas/15.0.5/container-scanning-report-format.json new file mode 100644 index 00000000000..820811100ea --- /dev/null +++ b/lib/gitlab/ci/parsers/security/validators/schemas/15.0.5/container-scanning-report-format.json @@ -0,0 +1,967 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/raw/master/dist/container-scanning-report-format.json", + "title": "Report format for GitLab Container Scanning", + "description": "This schema provides the the report format for Container Scanning (https://docs.gitlab.com/ee/user/application_security/container_scanning).", + "definitions": { + "detail_type": { + "oneOf": [ + { + "$ref": "#/definitions/named_list" + }, + { + "$ref": "#/definitions/list" + }, + { + "$ref": "#/definitions/table" + }, + { + "$ref": "#/definitions/text" + }, + { + "$ref": "#/definitions/url" + }, + { + "$ref": "#/definitions/code" + }, + { + "$ref": "#/definitions/value" + }, + { + "$ref": "#/definitions/diff" + }, + { + "$ref": "#/definitions/markdown" + }, + { + "$ref": "#/definitions/commit" + }, + { + "$ref": "#/definitions/file_location" + }, + { + "$ref": "#/definitions/module_location" + } + ] + }, + "text_value": { + "type": "string" + }, + "named_field": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "$ref": "#/definitions/text_value", + "type": "string", + "minLength": 1 + }, + "description": { + "$ref": "#/definitions/text_value" + } + } + }, + "named_list": { + "type": "object", + "description": "An object with named and typed fields", + "required": [ + "type", + "items" + ], + "properties": { + "type": { + "const": "named-list" + }, + "items": { + "type": "object", + "patternProperties": { + "^.*$": { + "allOf": [ + { + "$ref": "#/definitions/named_field" + }, + { + "$ref": "#/definitions/detail_type" + } + ] + } + } + } + } + }, + "list": { + "type": "object", + "description": "A list of typed fields", + "required": [ + "type", + "items" + ], + "properties": { + "type": { + "const": "list" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + } + } + }, + "table": { + "type": "object", + "description": "A table of typed fields", + "required": [ + "type", + "rows" + ], + "properties": { + "type": { + "const": "table" + }, + "header": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + }, + "rows": { + "type": "array", + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + } + } + } + }, + "text": { + "type": "object", + "description": "Raw text", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "text" + }, + "value": { + "$ref": "#/definitions/text_value" + } + } + }, + "url": { + "type": "object", + "description": "A single URL", + "required": [ + "type", + "href" + ], + "properties": { + "type": { + "const": "url" + }, + "text": { + "$ref": "#/definitions/text_value" + }, + "href": { + "type": "string", + "minLength": 1, + "examples": [ + "http://mysite.com" + ] + } + } + }, + "code": { + "type": "object", + "description": "A codeblock", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "code" + }, + "value": { + "type": "string" + }, + "lang": { + "type": "string", + "description": "A programming language" + } + } + }, + "value": { + "type": "object", + "description": "A field that can store a range of types of value", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "value" + }, + "value": { + "type": [ + "number", + "string", + "boolean" + ] + } + } + }, + "diff": { + "type": "object", + "description": "A diff", + "required": [ + "type", + "before", + "after" + ], + "properties": { + "type": { + "const": "diff" + }, + "before": { + "type": "string" + }, + "after": { + "type": "string" + } + } + }, + "markdown": { + "type": "object", + "description": "GitLab flavoured markdown, see https://docs.gitlab.com/ee/user/markdown.html", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "markdown" + }, + "value": { + "$ref": "#/definitions/text_value", + "examples": [ + "Here is markdown `inline code` #1 [test](gitlab.com)\n\n![GitLab Logo](https://about.gitlab.com/images/press/logo/preview/gitlab-logo-white-preview.png)" + ] + } + } + }, + "commit": { + "type": "object", + "description": "A commit/tag/branch within the GitLab project", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "commit" + }, + "value": { + "type": "string", + "description": "The commit SHA", + "minLength": 1 + } + } + }, + "file_location": { + "type": "object", + "description": "A location within a file in the project", + "required": [ + "type", + "file_name", + "line_start" + ], + "properties": { + "type": { + "const": "file-location" + }, + "file_name": { + "type": "string", + "minLength": 1 + }, + "line_start": { + "type": "integer" + }, + "line_end": { + "type": "integer" + } + } + }, + "module_location": { + "type": "object", + "description": "A location within a binary module of the form module+relative_offset", + "required": [ + "type", + "module_name", + "offset" + ], + "properties": { + "type": { + "const": "module-location" + }, + "module_name": { + "type": "string", + "minLength": 1, + "examples": [ + "compiled_binary" + ] + }, + "offset": { + "type": "integer", + "examples": [ + 100 + ] + } + } + } + }, + "self": { + "version": "15.0.5" + }, + "type": "object", + "required": [ + "scan", + "version", + "vulnerabilities" + ], + "additionalProperties": true, + "properties": { + "scan": { + "type": "object", + "required": [ + "analyzer", + "end_time", + "scanner", + "start_time", + "status", + "type" + ], + "properties": { + "end_time": { + "type": "string", + "description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan finished.", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$", + "examples": [ + "2020-01-28T03:26:02" + ] + }, + "messages": { + "type": "array", + "items": { + "type": "object", + "description": "Communication intended for the initiator of a scan.", + "required": [ + "level", + "value" + ], + "properties": { + "level": { + "type": "string", + "description": "Describes the severity of the communication. Use info to communicate normal scan behaviour; warn to communicate a potentially recoverable problem, or a partial error; fatal to communicate an issue that causes the scan to halt.", + "enum": [ + "info", + "warn", + "fatal" + ], + "examples": [ + "info" + ] + }, + "value": { + "type": "string", + "description": "The message to communicate.", + "minLength": 1, + "examples": [ + "Permission denied, scanning aborted" + ] + } + } + } + }, + "options": { + "type": "array", + "items": { + "type": "object", + "description": "A configuration option used for this scan.", + "required": [ + "name", + "value" + ], + "properties": { + "name": { + "type": "string", + "description": "The configuration option name.", + "maxLength": 255, + "minLength": 1, + "examples": [ + "DAST_FF_ENABLE_BAS", + "DOCKER_TLS_CERTDIR", + "DS_MAX_DEPTH", + "SECURE_LOG_LEVEL" + ] + }, + "source": { + "type": "string", + "description": "The source of this option.", + "enum": [ + "argument", + "file", + "env_variable", + "other" + ] + }, + "value": { + "type": [ + "boolean", + "integer", + "null", + "string" + ], + "description": "The value used for this scan.", + "examples": [ + true, + 2, + null, + "fatal", + "" + ] + } + } + } + }, + "analyzer": { + "type": "object", + "description": "Object defining the analyzer used to perform the scan. Analyzers typically delegate to an underlying scanner to run the scan.", + "required": [ + "id", + "name", + "version", + "vendor" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique id that identifies the analyzer.", + "minLength": 1, + "examples": [ + "gitlab-dast" + ] + }, + "name": { + "type": "string", + "description": "A human readable value that identifies the analyzer, not required to be unique.", + "minLength": 1, + "examples": [ + "GitLab DAST" + ] + }, + "url": { + "type": "string", + "pattern": "^https?://.+", + "description": "A link to more information about the analyzer.", + "examples": [ + "https://docs.gitlab.com/ee/user/application_security/dast" + ] + }, + "vendor": { + "description": "The vendor/maintainer of the analyzer.", + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the vendor.", + "minLength": 1, + "examples": [ + "GitLab" + ] + } + } + }, + "version": { + "type": "string", + "description": "The version of the analyzer.", + "minLength": 1, + "examples": [ + "1.0.2" + ] + } + } + }, + "scanner": { + "type": "object", + "description": "Object defining the scanner used to perform the scan.", + "required": [ + "id", + "name", + "version", + "vendor" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique id that identifies the scanner.", + "minLength": 1, + "examples": [ + "my-sast-scanner" + ] + }, + "name": { + "type": "string", + "description": "A human readable value that identifies the scanner, not required to be unique.", + "minLength": 1, + "examples": [ + "My SAST Scanner" + ] + }, + "url": { + "type": "string", + "description": "A link to more information about the scanner.", + "examples": [ + "https://scanner.url" + ] + }, + "version": { + "type": "string", + "description": "The version of the scanner.", + "minLength": 1, + "examples": [ + "1.0.2" + ] + }, + "vendor": { + "description": "The vendor/maintainer of the scanner.", + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the vendor.", + "minLength": 1, + "examples": [ + "GitLab" + ] + } + } + } + } + }, + "start_time": { + "type": "string", + "description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan started.", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$", + "examples": [ + "2020-02-14T16:01:59" + ] + }, + "status": { + "type": "string", + "description": "Result of the scan.", + "enum": [ + "success", + "failure" + ] + }, + "type": { + "type": "string", + "description": "Type of the scan.", + "enum": [ + "container_scanning" + ] + }, + "primary_identifiers": { + "type": "array", + "description": "An unordered array containing an exhaustive list of primary identifiers for which the analyzer may return results", + "items": { + "type": "object", + "required": [ + "type", + "name", + "value" + ], + "properties": { + "type": { + "type": "string", + "description": "for example, cve, cwe, osvdb, usn, or an analyzer-dependent type such as gemnasium).", + "minLength": 1 + }, + "name": { + "type": "string", + "description": "Human-readable name of the identifier.", + "minLength": 1 + }, + "url": { + "type": "string", + "description": "URL of the identifier's documentation.", + "pattern": "^https?://.+" + }, + "value": { + "type": "string", + "description": "Value of the identifier, for matching purpose.", + "minLength": 1 + } + } + } + } + } + }, + "schema": { + "type": "string", + "description": "URI pointing to the validating security report schema.", + "pattern": "^https?://.+" + }, + "version": { + "type": "string", + "description": "The version of the schema to which the JSON report conforms.", + "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" + }, + "vulnerabilities": { + "type": "array", + "description": "Array of vulnerability objects.", + "items": { + "type": "object", + "description": "Describes the vulnerability using GitLab Flavored Markdown", + "required": [ + "id", + "identifiers", + "location" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "description": "Unique identifier of the vulnerability. This is recommended to be a UUID.", + "examples": [ + "642735a5-1425-428d-8d4e-3c854885a3c9" + ] + }, + "name": { + "type": "string", + "maxLength": 255, + "description": "The name of the vulnerability. This must not include the finding's specific information." + }, + "description": { + "type": "string", + "maxLength": 1048576, + "description": "A long text section describing the vulnerability more fully." + }, + "severity": { + "type": "string", + "description": "How much the vulnerability impacts the software. Possible values are Info, Unknown, Low, Medium, High, or Critical. Note that some analyzers may not report all these possible values.", + "enum": [ + "Info", + "Unknown", + "Low", + "Medium", + "High", + "Critical" + ] + }, + "solution": { + "type": "string", + "maxLength": 7000, + "description": "Explanation of how to fix the vulnerability." + }, + "identifiers": { + "type": "array", + "minItems": 1, + "description": "An ordered array of references that identify a vulnerability on internal or external databases. The first identifier is the Primary Identifier, which has special meaning.", + "items": { + "type": "object", + "required": [ + "type", + "name", + "value" + ], + "properties": { + "type": { + "type": "string", + "description": "for example, cve, cwe, osvdb, usn, or an analyzer-dependent type such as gemnasium).", + "minLength": 1 + }, + "name": { + "type": "string", + "description": "Human-readable name of the identifier.", + "minLength": 1 + }, + "url": { + "type": "string", + "description": "URL of the identifier's documentation.", + "pattern": "^https?://.+" + }, + "value": { + "type": "string", + "description": "Value of the identifier, for matching purpose.", + "minLength": 1 + } + } + } + }, + "links": { + "type": "array", + "description": "An array of references to external documentation or articles that describe the vulnerability.", + "items": { + "type": "object", + "required": [ + "url" + ], + "properties": { + "name": { + "type": "string", + "description": "Name of the vulnerability details link." + }, + "url": { + "type": "string", + "description": "URL of the vulnerability details document.", + "pattern": "^https?://.+" + } + } + } + }, + "details": { + "$ref": "#/definitions/named_list/properties/items" + }, + "tracking": { + "type": "object", + "description": "Describes how this vulnerability should be tracked as the project changes.", + "oneOf": [ + { + "description": "Declares that a series of items should be tracked using source-specific tracking methods.", + "required": [ + "items" + ], + "properties": { + "type": { + "const": "source" + }, + "items": { + "type": "array", + "items": { + "description": "An item that should be tracked using source-specific tracking methods.", + "type": "object", + "required": [ + "signatures" + ], + "properties": { + "file": { + "type": "string", + "description": "Path to the file where the vulnerability is located." + }, + "start_line": { + "type": "number", + "description": "The first line of the file that includes the vulnerability." + }, + "end_line": { + "type": "number", + "description": "The last line of the file that includes the vulnerability." + }, + "signatures": { + "type": "array", + "description": "An array of calculated tracking signatures for this tracking item.", + "minItems": 1, + "items": { + "description": "A calculated tracking signature value and metadata.", + "type": "object", + "required": [ + "algorithm", + "value" + ], + "properties": { + "algorithm": { + "type": "string", + "description": "The algorithm used to generate the signature." + }, + "value": { + "type": "string", + "description": "The result of this signature algorithm." + } + } + } + } + } + } + } + } + } + ], + "properties": { + "type": { + "type": "string", + "description": "Each tracking type must declare its own type." + } + } + }, + "flags": { + "description": "Flags that can be attached to vulnerabilities.", + "type": "array", + "items": { + "type": "object", + "description": "Informational flags identified and assigned to a vulnerability.", + "required": [ + "type", + "origin", + "description" + ], + "properties": { + "type": { + "type": "string", + "minLength": 1, + "description": "Result of the scan.", + "enum": [ + "flagged-as-likely-false-positive" + ] + }, + "origin": { + "minLength": 1, + "description": "Tool that issued the flag.", + "type": "string" + }, + "description": { + "minLength": 1, + "description": "What the flag is about.", + "type": "string" + } + } + } + }, + "location": { + "type": "object", + "description": "Identifies the vulnerability's location.", + "required": [ + "dependency", + "operating_system", + "image" + ], + "properties": { + "dependency": { + "type": "object", + "description": "Describes the dependency of a project where the vulnerability is located.", + "required": [ + "package", + "version" + ], + "properties": { + "package": { + "type": "object", + "description": "Provides information on the package where the vulnerability is located.", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "Name of the package where the vulnerability is located." + } + } + }, + "version": { + "type": "string", + "description": "Version of the vulnerable package." + }, + "iid": { + "description": "ID that identifies the dependency in the scope of a dependency file.", + "type": "number" + }, + "direct": { + "type": "boolean", + "description": "Tells whether this is a direct, top-level dependency of the scanned project." + }, + "dependency_path": { + "type": "array", + "description": "Ancestors of the dependency, starting from a direct project dependency, and ending with an immediate parent of the dependency. The dependency itself is excluded from the path. Direct dependencies have no path.", + "items": { + "type": "object", + "required": [ + "iid" + ], + "properties": { + "iid": { + "type": "number", + "description": "ID that is unique in the scope of a parent object, and specific to the resource type." + } + } + } + } + } + }, + "operating_system": { + "type": "string", + "minLength": 1, + "description": "The operating system that contains the vulnerable package." + }, + "image": { + "type": "string", + "minLength": 1, + "description": "The analyzed Docker image." + }, + "default_branch_image": { + "type": "string", + "maxLength": 255, + "description": "The name of the image on the default branch." + } + } + } + } + } + }, + "remediations": { + "type": "array", + "description": "An array of objects containing information on available remediations, along with patch diffs to apply.", + "items": { + "type": "object", + "required": [ + "fixes", + "summary", + "diff" + ], + "properties": { + "fixes": { + "type": "array", + "description": "An array of strings that represent references to vulnerabilities fixed by this remediation.", + "items": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "description": "Unique identifier of the vulnerability. This is recommended to be a UUID.", + "examples": [ + "642735a5-1425-428d-8d4e-3c854885a3c9" + ] + } + } + } + }, + "summary": { + "type": "string", + "minLength": 1, + "description": "An overview of how the vulnerabilities were fixed." + }, + "diff": { + "type": "string", + "minLength": 1, + "description": "A base64-encoded remediation code diff, compatible with git apply." + } + } + } + } + } +} diff --git a/lib/gitlab/ci/parsers/security/validators/schemas/15.0.5/coverage-fuzzing-report-format.json b/lib/gitlab/ci/parsers/security/validators/schemas/15.0.5/coverage-fuzzing-report-format.json new file mode 100644 index 00000000000..63e39395772 --- /dev/null +++ b/lib/gitlab/ci/parsers/security/validators/schemas/15.0.5/coverage-fuzzing-report-format.json @@ -0,0 +1,925 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/raw/master/dist/coverage-fuzzing-report-format.json", + "title": "Report format for GitLab Fuzz Testing", + "description": "This schema provides the report format for Coverage Guided Fuzz Testing (https://docs.gitlab.com/ee/user/application_security/coverage_fuzzing).", + "definitions": { + "detail_type": { + "oneOf": [ + { + "$ref": "#/definitions/named_list" + }, + { + "$ref": "#/definitions/list" + }, + { + "$ref": "#/definitions/table" + }, + { + "$ref": "#/definitions/text" + }, + { + "$ref": "#/definitions/url" + }, + { + "$ref": "#/definitions/code" + }, + { + "$ref": "#/definitions/value" + }, + { + "$ref": "#/definitions/diff" + }, + { + "$ref": "#/definitions/markdown" + }, + { + "$ref": "#/definitions/commit" + }, + { + "$ref": "#/definitions/file_location" + }, + { + "$ref": "#/definitions/module_location" + } + ] + }, + "text_value": { + "type": "string" + }, + "named_field": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "$ref": "#/definitions/text_value", + "type": "string", + "minLength": 1 + }, + "description": { + "$ref": "#/definitions/text_value" + } + } + }, + "named_list": { + "type": "object", + "description": "An object with named and typed fields", + "required": [ + "type", + "items" + ], + "properties": { + "type": { + "const": "named-list" + }, + "items": { + "type": "object", + "patternProperties": { + "^.*$": { + "allOf": [ + { + "$ref": "#/definitions/named_field" + }, + { + "$ref": "#/definitions/detail_type" + } + ] + } + } + } + } + }, + "list": { + "type": "object", + "description": "A list of typed fields", + "required": [ + "type", + "items" + ], + "properties": { + "type": { + "const": "list" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + } + } + }, + "table": { + "type": "object", + "description": "A table of typed fields", + "required": [ + "type", + "rows" + ], + "properties": { + "type": { + "const": "table" + }, + "header": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + }, + "rows": { + "type": "array", + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + } + } + } + }, + "text": { + "type": "object", + "description": "Raw text", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "text" + }, + "value": { + "$ref": "#/definitions/text_value" + } + } + }, + "url": { + "type": "object", + "description": "A single URL", + "required": [ + "type", + "href" + ], + "properties": { + "type": { + "const": "url" + }, + "text": { + "$ref": "#/definitions/text_value" + }, + "href": { + "type": "string", + "minLength": 1, + "examples": [ + "http://mysite.com" + ] + } + } + }, + "code": { + "type": "object", + "description": "A codeblock", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "code" + }, + "value": { + "type": "string" + }, + "lang": { + "type": "string", + "description": "A programming language" + } + } + }, + "value": { + "type": "object", + "description": "A field that can store a range of types of value", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "value" + }, + "value": { + "type": [ + "number", + "string", + "boolean" + ] + } + } + }, + "diff": { + "type": "object", + "description": "A diff", + "required": [ + "type", + "before", + "after" + ], + "properties": { + "type": { + "const": "diff" + }, + "before": { + "type": "string" + }, + "after": { + "type": "string" + } + } + }, + "markdown": { + "type": "object", + "description": "GitLab flavoured markdown, see https://docs.gitlab.com/ee/user/markdown.html", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "markdown" + }, + "value": { + "$ref": "#/definitions/text_value", + "examples": [ + "Here is markdown `inline code` #1 [test](gitlab.com)\n\n![GitLab Logo](https://about.gitlab.com/images/press/logo/preview/gitlab-logo-white-preview.png)" + ] + } + } + }, + "commit": { + "type": "object", + "description": "A commit/tag/branch within the GitLab project", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "commit" + }, + "value": { + "type": "string", + "description": "The commit SHA", + "minLength": 1 + } + } + }, + "file_location": { + "type": "object", + "description": "A location within a file in the project", + "required": [ + "type", + "file_name", + "line_start" + ], + "properties": { + "type": { + "const": "file-location" + }, + "file_name": { + "type": "string", + "minLength": 1 + }, + "line_start": { + "type": "integer" + }, + "line_end": { + "type": "integer" + } + } + }, + "module_location": { + "type": "object", + "description": "A location within a binary module of the form module+relative_offset", + "required": [ + "type", + "module_name", + "offset" + ], + "properties": { + "type": { + "const": "module-location" + }, + "module_name": { + "type": "string", + "minLength": 1, + "examples": [ + "compiled_binary" + ] + }, + "offset": { + "type": "integer", + "examples": [ + 100 + ] + } + } + } + }, + "self": { + "version": "15.0.5" + }, + "type": "object", + "required": [ + "scan", + "version", + "vulnerabilities" + ], + "additionalProperties": true, + "properties": { + "scan": { + "type": "object", + "required": [ + "analyzer", + "end_time", + "scanner", + "start_time", + "status", + "type" + ], + "properties": { + "end_time": { + "type": "string", + "description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan finished.", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$", + "examples": [ + "2020-01-28T03:26:02" + ] + }, + "messages": { + "type": "array", + "items": { + "type": "object", + "description": "Communication intended for the initiator of a scan.", + "required": [ + "level", + "value" + ], + "properties": { + "level": { + "type": "string", + "description": "Describes the severity of the communication. Use info to communicate normal scan behaviour; warn to communicate a potentially recoverable problem, or a partial error; fatal to communicate an issue that causes the scan to halt.", + "enum": [ + "info", + "warn", + "fatal" + ], + "examples": [ + "info" + ] + }, + "value": { + "type": "string", + "description": "The message to communicate.", + "minLength": 1, + "examples": [ + "Permission denied, scanning aborted" + ] + } + } + } + }, + "options": { + "type": "array", + "items": { + "type": "object", + "description": "A configuration option used for this scan.", + "required": [ + "name", + "value" + ], + "properties": { + "name": { + "type": "string", + "description": "The configuration option name.", + "maxLength": 255, + "minLength": 1, + "examples": [ + "DAST_FF_ENABLE_BAS", + "DOCKER_TLS_CERTDIR", + "DS_MAX_DEPTH", + "SECURE_LOG_LEVEL" + ] + }, + "source": { + "type": "string", + "description": "The source of this option.", + "enum": [ + "argument", + "file", + "env_variable", + "other" + ] + }, + "value": { + "type": [ + "boolean", + "integer", + "null", + "string" + ], + "description": "The value used for this scan.", + "examples": [ + true, + 2, + null, + "fatal", + "" + ] + } + } + } + }, + "analyzer": { + "type": "object", + "description": "Object defining the analyzer used to perform the scan. Analyzers typically delegate to an underlying scanner to run the scan.", + "required": [ + "id", + "name", + "version", + "vendor" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique id that identifies the analyzer.", + "minLength": 1, + "examples": [ + "gitlab-dast" + ] + }, + "name": { + "type": "string", + "description": "A human readable value that identifies the analyzer, not required to be unique.", + "minLength": 1, + "examples": [ + "GitLab DAST" + ] + }, + "url": { + "type": "string", + "pattern": "^https?://.+", + "description": "A link to more information about the analyzer.", + "examples": [ + "https://docs.gitlab.com/ee/user/application_security/dast" + ] + }, + "vendor": { + "description": "The vendor/maintainer of the analyzer.", + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the vendor.", + "minLength": 1, + "examples": [ + "GitLab" + ] + } + } + }, + "version": { + "type": "string", + "description": "The version of the analyzer.", + "minLength": 1, + "examples": [ + "1.0.2" + ] + } + } + }, + "scanner": { + "type": "object", + "description": "Object defining the scanner used to perform the scan.", + "required": [ + "id", + "name", + "version", + "vendor" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique id that identifies the scanner.", + "minLength": 1, + "examples": [ + "my-sast-scanner" + ] + }, + "name": { + "type": "string", + "description": "A human readable value that identifies the scanner, not required to be unique.", + "minLength": 1, + "examples": [ + "My SAST Scanner" + ] + }, + "url": { + "type": "string", + "description": "A link to more information about the scanner.", + "examples": [ + "https://scanner.url" + ] + }, + "version": { + "type": "string", + "description": "The version of the scanner.", + "minLength": 1, + "examples": [ + "1.0.2" + ] + }, + "vendor": { + "description": "The vendor/maintainer of the scanner.", + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the vendor.", + "minLength": 1, + "examples": [ + "GitLab" + ] + } + } + } + } + }, + "start_time": { + "type": "string", + "description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan started.", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$", + "examples": [ + "2020-02-14T16:01:59" + ] + }, + "status": { + "type": "string", + "description": "Result of the scan.", + "enum": [ + "success", + "failure" + ] + }, + "type": { + "type": "string", + "description": "Type of the scan.", + "enum": [ + "coverage_fuzzing" + ] + }, + "primary_identifiers": { + "type": "array", + "description": "An unordered array containing an exhaustive list of primary identifiers for which the analyzer may return results", + "items": { + "type": "object", + "required": [ + "type", + "name", + "value" + ], + "properties": { + "type": { + "type": "string", + "description": "for example, cve, cwe, osvdb, usn, or an analyzer-dependent type such as gemnasium).", + "minLength": 1 + }, + "name": { + "type": "string", + "description": "Human-readable name of the identifier.", + "minLength": 1 + }, + "url": { + "type": "string", + "description": "URL of the identifier's documentation.", + "pattern": "^https?://.+" + }, + "value": { + "type": "string", + "description": "Value of the identifier, for matching purpose.", + "minLength": 1 + } + } + } + } + } + }, + "schema": { + "type": "string", + "description": "URI pointing to the validating security report schema.", + "pattern": "^https?://.+" + }, + "version": { + "type": "string", + "description": "The version of the schema to which the JSON report conforms.", + "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" + }, + "vulnerabilities": { + "type": "array", + "description": "Array of vulnerability objects.", + "items": { + "type": "object", + "description": "Describes the vulnerability using GitLab Flavored Markdown", + "required": [ + "id", + "identifiers", + "location" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "description": "Unique identifier of the vulnerability. This is recommended to be a UUID.", + "examples": [ + "642735a5-1425-428d-8d4e-3c854885a3c9" + ] + }, + "name": { + "type": "string", + "maxLength": 255, + "description": "The name of the vulnerability. This must not include the finding's specific information." + }, + "description": { + "type": "string", + "maxLength": 1048576, + "description": "A long text section describing the vulnerability more fully." + }, + "severity": { + "type": "string", + "description": "How much the vulnerability impacts the software. Possible values are Info, Unknown, Low, Medium, High, or Critical. Note that some analyzers may not report all these possible values.", + "enum": [ + "Info", + "Unknown", + "Low", + "Medium", + "High", + "Critical" + ] + }, + "solution": { + "type": "string", + "maxLength": 7000, + "description": "Explanation of how to fix the vulnerability." + }, + "identifiers": { + "type": "array", + "minItems": 1, + "description": "An ordered array of references that identify a vulnerability on internal or external databases. The first identifier is the Primary Identifier, which has special meaning.", + "items": { + "type": "object", + "required": [ + "type", + "name", + "value" + ], + "properties": { + "type": { + "type": "string", + "description": "for example, cve, cwe, osvdb, usn, or an analyzer-dependent type such as gemnasium).", + "minLength": 1 + }, + "name": { + "type": "string", + "description": "Human-readable name of the identifier.", + "minLength": 1 + }, + "url": { + "type": "string", + "description": "URL of the identifier's documentation.", + "pattern": "^https?://.+" + }, + "value": { + "type": "string", + "description": "Value of the identifier, for matching purpose.", + "minLength": 1 + } + } + } + }, + "links": { + "type": "array", + "description": "An array of references to external documentation or articles that describe the vulnerability.", + "items": { + "type": "object", + "required": [ + "url" + ], + "properties": { + "name": { + "type": "string", + "description": "Name of the vulnerability details link." + }, + "url": { + "type": "string", + "description": "URL of the vulnerability details document.", + "pattern": "^https?://.+" + } + } + } + }, + "details": { + "$ref": "#/definitions/named_list/properties/items" + }, + "tracking": { + "type": "object", + "description": "Describes how this vulnerability should be tracked as the project changes.", + "oneOf": [ + { + "description": "Declares that a series of items should be tracked using source-specific tracking methods.", + "required": [ + "items" + ], + "properties": { + "type": { + "const": "source" + }, + "items": { + "type": "array", + "items": { + "description": "An item that should be tracked using source-specific tracking methods.", + "type": "object", + "required": [ + "signatures" + ], + "properties": { + "file": { + "type": "string", + "description": "Path to the file where the vulnerability is located." + }, + "start_line": { + "type": "number", + "description": "The first line of the file that includes the vulnerability." + }, + "end_line": { + "type": "number", + "description": "The last line of the file that includes the vulnerability." + }, + "signatures": { + "type": "array", + "description": "An array of calculated tracking signatures for this tracking item.", + "minItems": 1, + "items": { + "description": "A calculated tracking signature value and metadata.", + "type": "object", + "required": [ + "algorithm", + "value" + ], + "properties": { + "algorithm": { + "type": "string", + "description": "The algorithm used to generate the signature." + }, + "value": { + "type": "string", + "description": "The result of this signature algorithm." + } + } + } + } + } + } + } + } + } + ], + "properties": { + "type": { + "type": "string", + "description": "Each tracking type must declare its own type." + } + } + }, + "flags": { + "description": "Flags that can be attached to vulnerabilities.", + "type": "array", + "items": { + "type": "object", + "description": "Informational flags identified and assigned to a vulnerability.", + "required": [ + "type", + "origin", + "description" + ], + "properties": { + "type": { + "type": "string", + "minLength": 1, + "description": "Result of the scan.", + "enum": [ + "flagged-as-likely-false-positive" + ] + }, + "origin": { + "minLength": 1, + "description": "Tool that issued the flag.", + "type": "string" + }, + "description": { + "minLength": 1, + "description": "What the flag is about.", + "type": "string" + } + } + } + }, + "location": { + "description": "The location of the error", + "type": "object", + "properties": { + "crash_address": { + "type": "string", + "description": "The relative address in memory were the crash occurred.", + "examples": [ + "0xabababab" + ] + }, + "stacktrace_snippet": { + "type": "string", + "description": "The stack trace recorded during fuzzing resulting the crash.", + "examples": [ + "func_a+0xabcd\nfunc_b+0xabcc" + ] + }, + "crash_state": { + "type": "string", + "description": "Minimised and normalized crash stack-trace (called crash_state).", + "examples": [ + "func_a+0xa\nfunc_b+0xb\nfunc_c+0xc" + ] + }, + "crash_type": { + "type": "string", + "description": "Type of the crash.", + "examples": [ + "Heap-Buffer-overflow", + "Division-by-zero" + ] + } + } + } + } + } + }, + "remediations": { + "type": "array", + "description": "An array of objects containing information on available remediations, along with patch diffs to apply.", + "items": { + "type": "object", + "required": [ + "fixes", + "summary", + "diff" + ], + "properties": { + "fixes": { + "type": "array", + "description": "An array of strings that represent references to vulnerabilities fixed by this remediation.", + "items": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "description": "Unique identifier of the vulnerability. This is recommended to be a UUID.", + "examples": [ + "642735a5-1425-428d-8d4e-3c854885a3c9" + ] + } + } + } + }, + "summary": { + "type": "string", + "minLength": 1, + "description": "An overview of how the vulnerabilities were fixed." + }, + "diff": { + "type": "string", + "minLength": 1, + "description": "A base64-encoded remediation code diff, compatible with git apply." + } + } + } + } + } +} diff --git a/lib/gitlab/ci/parsers/security/validators/schemas/15.0.5/dast-report-format.json b/lib/gitlab/ci/parsers/security/validators/schemas/15.0.5/dast-report-format.json new file mode 100644 index 00000000000..86e62558a39 --- /dev/null +++ b/lib/gitlab/ci/parsers/security/validators/schemas/15.0.5/dast-report-format.json @@ -0,0 +1,1330 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/raw/master/dist/dast-report-format.json", + "title": "Report format for GitLab DAST", + "description": "This schema provides the the report format for Dynamic Application Security Testing (https://docs.gitlab.com/ee/user/application_security/dast).", + "definitions": { + "detail_type": { + "oneOf": [ + { + "$ref": "#/definitions/named_list" + }, + { + "$ref": "#/definitions/list" + }, + { + "$ref": "#/definitions/table" + }, + { + "$ref": "#/definitions/text" + }, + { + "$ref": "#/definitions/url" + }, + { + "$ref": "#/definitions/code" + }, + { + "$ref": "#/definitions/value" + }, + { + "$ref": "#/definitions/diff" + }, + { + "$ref": "#/definitions/markdown" + }, + { + "$ref": "#/definitions/commit" + }, + { + "$ref": "#/definitions/file_location" + }, + { + "$ref": "#/definitions/module_location" + } + ] + }, + "text_value": { + "type": "string" + }, + "named_field": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "$ref": "#/definitions/text_value", + "type": "string", + "minLength": 1 + }, + "description": { + "$ref": "#/definitions/text_value" + } + } + }, + "named_list": { + "type": "object", + "description": "An object with named and typed fields", + "required": [ + "type", + "items" + ], + "properties": { + "type": { + "const": "named-list" + }, + "items": { + "type": "object", + "patternProperties": { + "^.*$": { + "allOf": [ + { + "$ref": "#/definitions/named_field" + }, + { + "$ref": "#/definitions/detail_type" + } + ] + } + } + } + } + }, + "list": { + "type": "object", + "description": "A list of typed fields", + "required": [ + "type", + "items" + ], + "properties": { + "type": { + "const": "list" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + } + } + }, + "table": { + "type": "object", + "description": "A table of typed fields", + "required": [ + "type", + "rows" + ], + "properties": { + "type": { + "const": "table" + }, + "header": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + }, + "rows": { + "type": "array", + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + } + } + } + }, + "text": { + "type": "object", + "description": "Raw text", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "text" + }, + "value": { + "$ref": "#/definitions/text_value" + } + } + }, + "url": { + "type": "object", + "description": "A single URL", + "required": [ + "type", + "href" + ], + "properties": { + "type": { + "const": "url" + }, + "text": { + "$ref": "#/definitions/text_value" + }, + "href": { + "type": "string", + "minLength": 1, + "examples": [ + "http://mysite.com" + ] + } + } + }, + "code": { + "type": "object", + "description": "A codeblock", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "code" + }, + "value": { + "type": "string" + }, + "lang": { + "type": "string", + "description": "A programming language" + } + } + }, + "value": { + "type": "object", + "description": "A field that can store a range of types of value", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "value" + }, + "value": { + "type": [ + "number", + "string", + "boolean" + ] + } + } + }, + "diff": { + "type": "object", + "description": "A diff", + "required": [ + "type", + "before", + "after" + ], + "properties": { + "type": { + "const": "diff" + }, + "before": { + "type": "string" + }, + "after": { + "type": "string" + } + } + }, + "markdown": { + "type": "object", + "description": "GitLab flavoured markdown, see https://docs.gitlab.com/ee/user/markdown.html", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "markdown" + }, + "value": { + "$ref": "#/definitions/text_value", + "examples": [ + "Here is markdown `inline code` #1 [test](gitlab.com)\n\n![GitLab Logo](https://about.gitlab.com/images/press/logo/preview/gitlab-logo-white-preview.png)" + ] + } + } + }, + "commit": { + "type": "object", + "description": "A commit/tag/branch within the GitLab project", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "commit" + }, + "value": { + "type": "string", + "description": "The commit SHA", + "minLength": 1 + } + } + }, + "file_location": { + "type": "object", + "description": "A location within a file in the project", + "required": [ + "type", + "file_name", + "line_start" + ], + "properties": { + "type": { + "const": "file-location" + }, + "file_name": { + "type": "string", + "minLength": 1 + }, + "line_start": { + "type": "integer" + }, + "line_end": { + "type": "integer" + } + } + }, + "module_location": { + "type": "object", + "description": "A location within a binary module of the form module+relative_offset", + "required": [ + "type", + "module_name", + "offset" + ], + "properties": { + "type": { + "const": "module-location" + }, + "module_name": { + "type": "string", + "minLength": 1, + "examples": [ + "compiled_binary" + ] + }, + "offset": { + "type": "integer", + "examples": [ + 100 + ] + } + } + } + }, + "self": { + "version": "15.0.5" + }, + "type": "object", + "required": [ + "scan", + "version", + "vulnerabilities" + ], + "additionalProperties": true, + "properties": { + "scan": { + "type": "object", + "required": [ + "analyzer", + "end_time", + "scanned_resources", + "scanner", + "start_time", + "status", + "type" + ], + "properties": { + "end_time": { + "type": "string", + "description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan finished.", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$", + "examples": [ + "2020-01-28T03:26:02" + ] + }, + "messages": { + "type": "array", + "items": { + "type": "object", + "description": "Communication intended for the initiator of a scan.", + "required": [ + "level", + "value" + ], + "properties": { + "level": { + "type": "string", + "description": "Describes the severity of the communication. Use info to communicate normal scan behaviour; warn to communicate a potentially recoverable problem, or a partial error; fatal to communicate an issue that causes the scan to halt.", + "enum": [ + "info", + "warn", + "fatal" + ], + "examples": [ + "info" + ] + }, + "value": { + "type": "string", + "description": "The message to communicate.", + "minLength": 1, + "examples": [ + "Permission denied, scanning aborted" + ] + } + } + } + }, + "options": { + "type": "array", + "items": { + "type": "object", + "description": "A configuration option used for this scan.", + "required": [ + "name", + "value" + ], + "properties": { + "name": { + "type": "string", + "description": "The configuration option name.", + "maxLength": 255, + "minLength": 1, + "examples": [ + "DAST_FF_ENABLE_BAS", + "DOCKER_TLS_CERTDIR", + "DS_MAX_DEPTH", + "SECURE_LOG_LEVEL" + ] + }, + "source": { + "type": "string", + "description": "The source of this option.", + "enum": [ + "argument", + "file", + "env_variable", + "other" + ] + }, + "value": { + "type": [ + "boolean", + "integer", + "null", + "string" + ], + "description": "The value used for this scan.", + "examples": [ + true, + 2, + null, + "fatal", + "" + ] + } + } + } + }, + "analyzer": { + "type": "object", + "description": "Object defining the analyzer used to perform the scan. Analyzers typically delegate to an underlying scanner to run the scan.", + "required": [ + "id", + "name", + "version", + "vendor" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique id that identifies the analyzer.", + "minLength": 1, + "examples": [ + "gitlab-dast" + ] + }, + "name": { + "type": "string", + "description": "A human readable value that identifies the analyzer, not required to be unique.", + "minLength": 1, + "examples": [ + "GitLab DAST" + ] + }, + "url": { + "type": "string", + "pattern": "^https?://.+", + "description": "A link to more information about the analyzer.", + "examples": [ + "https://docs.gitlab.com/ee/user/application_security/dast" + ] + }, + "vendor": { + "description": "The vendor/maintainer of the analyzer.", + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the vendor.", + "minLength": 1, + "examples": [ + "GitLab" + ] + } + } + }, + "version": { + "type": "string", + "description": "The version of the analyzer.", + "minLength": 1, + "examples": [ + "1.0.2" + ] + } + } + }, + "scanner": { + "type": "object", + "description": "Object defining the scanner used to perform the scan.", + "required": [ + "id", + "name", + "version", + "vendor" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique id that identifies the scanner.", + "minLength": 1, + "examples": [ + "my-sast-scanner" + ] + }, + "name": { + "type": "string", + "description": "A human readable value that identifies the scanner, not required to be unique.", + "minLength": 1, + "examples": [ + "My SAST Scanner" + ] + }, + "url": { + "type": "string", + "description": "A link to more information about the scanner.", + "examples": [ + "https://scanner.url" + ] + }, + "version": { + "type": "string", + "description": "The version of the scanner.", + "minLength": 1, + "examples": [ + "1.0.2" + ] + }, + "vendor": { + "description": "The vendor/maintainer of the scanner.", + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the vendor.", + "minLength": 1, + "examples": [ + "GitLab" + ] + } + } + } + } + }, + "start_time": { + "type": "string", + "description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan started.", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$", + "examples": [ + "2020-02-14T16:01:59" + ] + }, + "status": { + "type": "string", + "description": "Result of the scan.", + "enum": [ + "success", + "failure" + ] + }, + "type": { + "type": "string", + "description": "Type of the scan.", + "enum": [ + "dast", + "api_fuzzing" + ] + }, + "primary_identifiers": { + "type": "array", + "description": "An unordered array containing an exhaustive list of primary identifiers for which the analyzer may return results", + "items": { + "type": "object", + "required": [ + "type", + "name", + "value" + ], + "properties": { + "type": { + "type": "string", + "description": "for example, cve, cwe, osvdb, usn, or an analyzer-dependent type such as gemnasium).", + "minLength": 1 + }, + "name": { + "type": "string", + "description": "Human-readable name of the identifier.", + "minLength": 1 + }, + "url": { + "type": "string", + "description": "URL of the identifier's documentation.", + "pattern": "^https?://.+" + }, + "value": { + "type": "string", + "description": "Value of the identifier, for matching purpose.", + "minLength": 1 + } + } + } + }, + "scanned_resources": { + "type": "array", + "description": "The attack surface scanned by DAST.", + "items": { + "type": "object", + "required": [ + "method", + "url", + "type" + ], + "properties": { + "method": { + "type": "string", + "minLength": 1, + "description": "HTTP method of the scanned resource.", + "examples": [ + "GET", + "POST", + "HEAD" + ] + }, + "url": { + "type": "string", + "minLength": 1, + "description": "URL of the scanned resource.", + "examples": [ + "http://my.site.com/a-page" + ] + }, + "type": { + "type": "string", + "minLength": 1, + "description": "Type of the scanned resource, for DAST, this must be 'url'.", + "examples": [ + "url" + ] + } + } + } + } + } + }, + "schema": { + "type": "string", + "description": "URI pointing to the validating security report schema.", + "pattern": "^https?://.+" + }, + "version": { + "type": "string", + "description": "The version of the schema to which the JSON report conforms.", + "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" + }, + "vulnerabilities": { + "type": "array", + "description": "Array of vulnerability objects.", + "items": { + "type": "object", + "description": "Describes the vulnerability using GitLab Flavored Markdown", + "required": [ + "id", + "identifiers", + "location" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "description": "Unique identifier of the vulnerability. This is recommended to be a UUID.", + "examples": [ + "642735a5-1425-428d-8d4e-3c854885a3c9" + ] + }, + "name": { + "type": "string", + "maxLength": 255, + "description": "The name of the vulnerability. This must not include the finding's specific information." + }, + "description": { + "type": "string", + "maxLength": 1048576, + "description": "A long text section describing the vulnerability more fully." + }, + "severity": { + "type": "string", + "description": "How much the vulnerability impacts the software. Possible values are Info, Unknown, Low, Medium, High, or Critical. Note that some analyzers may not report all these possible values.", + "enum": [ + "Info", + "Unknown", + "Low", + "Medium", + "High", + "Critical" + ] + }, + "solution": { + "type": "string", + "maxLength": 7000, + "description": "Explanation of how to fix the vulnerability." + }, + "identifiers": { + "type": "array", + "minItems": 1, + "description": "An ordered array of references that identify a vulnerability on internal or external databases. The first identifier is the Primary Identifier, which has special meaning.", + "items": { + "type": "object", + "required": [ + "type", + "name", + "value" + ], + "properties": { + "type": { + "type": "string", + "description": "for example, cve, cwe, osvdb, usn, or an analyzer-dependent type such as gemnasium).", + "minLength": 1 + }, + "name": { + "type": "string", + "description": "Human-readable name of the identifier.", + "minLength": 1 + }, + "url": { + "type": "string", + "description": "URL of the identifier's documentation.", + "pattern": "^https?://.+" + }, + "value": { + "type": "string", + "description": "Value of the identifier, for matching purpose.", + "minLength": 1 + } + } + } + }, + "links": { + "type": "array", + "description": "An array of references to external documentation or articles that describe the vulnerability.", + "items": { + "type": "object", + "required": [ + "url" + ], + "properties": { + "name": { + "type": "string", + "description": "Name of the vulnerability details link." + }, + "url": { + "type": "string", + "description": "URL of the vulnerability details document.", + "pattern": "^https?://.+" + } + } + } + }, + "details": { + "$ref": "#/definitions/named_list/properties/items" + }, + "tracking": { + "type": "object", + "description": "Describes how this vulnerability should be tracked as the project changes.", + "oneOf": [ + { + "description": "Declares that a series of items should be tracked using source-specific tracking methods.", + "required": [ + "items" + ], + "properties": { + "type": { + "const": "source" + }, + "items": { + "type": "array", + "items": { + "description": "An item that should be tracked using source-specific tracking methods.", + "type": "object", + "required": [ + "signatures" + ], + "properties": { + "file": { + "type": "string", + "description": "Path to the file where the vulnerability is located." + }, + "start_line": { + "type": "number", + "description": "The first line of the file that includes the vulnerability." + }, + "end_line": { + "type": "number", + "description": "The last line of the file that includes the vulnerability." + }, + "signatures": { + "type": "array", + "description": "An array of calculated tracking signatures for this tracking item.", + "minItems": 1, + "items": { + "description": "A calculated tracking signature value and metadata.", + "type": "object", + "required": [ + "algorithm", + "value" + ], + "properties": { + "algorithm": { + "type": "string", + "description": "The algorithm used to generate the signature." + }, + "value": { + "type": "string", + "description": "The result of this signature algorithm." + } + } + } + } + } + } + } + } + } + ], + "properties": { + "type": { + "type": "string", + "description": "Each tracking type must declare its own type." + } + } + }, + "flags": { + "description": "Flags that can be attached to vulnerabilities.", + "type": "array", + "items": { + "type": "object", + "description": "Informational flags identified and assigned to a vulnerability.", + "required": [ + "type", + "origin", + "description" + ], + "properties": { + "type": { + "type": "string", + "minLength": 1, + "description": "Result of the scan.", + "enum": [ + "flagged-as-likely-false-positive" + ] + }, + "origin": { + "minLength": 1, + "description": "Tool that issued the flag.", + "type": "string" + }, + "description": { + "minLength": 1, + "description": "What the flag is about.", + "type": "string" + } + } + } + }, + "evidence": { + "type": "object", + "properties": { + "source": { + "type": "object", + "description": "Source of evidence", + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "description": "Unique source identifier", + "examples": [ + "assert:LogAnalysis", + "assert:StatusCode" + ] + }, + "name": { + "type": "string", + "minLength": 1, + "description": "Source display name", + "examples": [ + "Log Analysis", + "Status Code" + ] + }, + "url": { + "type": "string", + "description": "Link to additional information", + "examples": [ + "https://docs.gitlab.com/ee/development/integrations/secure.html" + ] + } + } + }, + "summary": { + "type": "string", + "description": "Human readable string containing evidence of the vulnerability.", + "examples": [ + "Credit card 4111111111111111 found", + "Server leaked information nginx/1.17.6" + ] + }, + "request": { + "type": "object", + "description": "An HTTP request.", + "required": [ + "headers", + "method", + "url" + ], + "properties": { + "headers": { + "type": "array", + "description": "HTTP headers present on the request.", + "items": { + "type": "object", + "required": [ + "name", + "value" + ], + "properties": { + "name": { + "type": "string", + "minLength": 1, + "description": "Name of the HTTP header.", + "examples": [ + "Accept", + "Content-Length", + "Content-Type" + ] + }, + "value": { + "type": "string", + "description": "Value of the HTTP header.", + "examples": [ + "*/*", + "560", + "application/json; charset=utf-8" + ] + } + } + } + }, + "method": { + "type": "string", + "minLength": 1, + "description": "HTTP method used in the request.", + "examples": [ + "GET", + "POST" + ] + }, + "url": { + "type": "string", + "minLength": 1, + "description": "URL of the request.", + "examples": [ + "http://my.site.com/vulnerable-endpoint?show-credit-card" + ] + }, + "body": { + "type": "string", + "description": "Body of the request for display purposes. Body must be suitable for display (not binary), and truncated to a reasonable size.", + "examples": [ + "user=jsmith&first=%27&last=smith" + ] + } + } + }, + "response": { + "type": "object", + "description": "An HTTP response.", + "required": [ + "headers", + "reason_phrase", + "status_code" + ], + "properties": { + "headers": { + "type": "array", + "description": "HTTP headers present on the request.", + "items": { + "type": "object", + "required": [ + "name", + "value" + ], + "properties": { + "name": { + "type": "string", + "minLength": 1, + "description": "Name of the HTTP header.", + "examples": [ + "Accept", + "Content-Length", + "Content-Type" + ] + }, + "value": { + "type": "string", + "description": "Value of the HTTP header.", + "examples": [ + "*/*", + "560", + "application/json; charset=utf-8" + ] + } + } + } + }, + "reason_phrase": { + "type": "string", + "description": "HTTP reason phrase of the response.", + "examples": [ + "OK", + "Internal Server Error" + ] + }, + "status_code": { + "type": "integer", + "description": "HTTP status code of the response.", + "examples": [ + 200, + 500 + ] + }, + "body": { + "type": "string", + "description": "Body of the response for display purposes. Body must be suitable for display (not binary), and truncated to a reasonable size.", + "examples": [ + "{\"user_id\": 2}" + ] + } + } + }, + "supporting_messages": { + "type": "array", + "description": "Array of supporting http messages.", + "items": { + "type": "object", + "description": "A supporting http message.", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "minLength": 1, + "description": "Message display name.", + "examples": [ + "Unmodified", + "Recorded" + ] + }, + "request": { + "type": "object", + "description": "An HTTP request.", + "required": [ + "headers", + "method", + "url" + ], + "properties": { + "headers": { + "type": "array", + "description": "HTTP headers present on the request.", + "items": { + "type": "object", + "required": [ + "name", + "value" + ], + "properties": { + "name": { + "type": "string", + "minLength": 1, + "description": "Name of the HTTP header.", + "examples": [ + "Accept", + "Content-Length", + "Content-Type" + ] + }, + "value": { + "type": "string", + "description": "Value of the HTTP header.", + "examples": [ + "*/*", + "560", + "application/json; charset=utf-8" + ] + } + } + } + }, + "method": { + "type": "string", + "minLength": 1, + "description": "HTTP method used in the request.", + "examples": [ + "GET", + "POST" + ] + }, + "url": { + "type": "string", + "minLength": 1, + "description": "URL of the request.", + "examples": [ + "http://my.site.com/vulnerable-endpoint?show-credit-card" + ] + }, + "body": { + "type": "string", + "description": "Body of the request for display purposes. Body must be suitable for display (not binary), and truncated to a reasonable size.", + "examples": [ + "user=jsmith&first=%27&last=smith" + ] + } + } + }, + "response": { + "type": "object", + "description": "An HTTP response.", + "required": [ + "headers", + "reason_phrase", + "status_code" + ], + "properties": { + "headers": { + "type": "array", + "description": "HTTP headers present on the request.", + "items": { + "type": "object", + "required": [ + "name", + "value" + ], + "properties": { + "name": { + "type": "string", + "minLength": 1, + "description": "Name of the HTTP header.", + "examples": [ + "Accept", + "Content-Length", + "Content-Type" + ] + }, + "value": { + "type": "string", + "description": "Value of the HTTP header.", + "examples": [ + "*/*", + "560", + "application/json; charset=utf-8" + ] + } + } + } + }, + "reason_phrase": { + "type": "string", + "description": "HTTP reason phrase of the response.", + "examples": [ + "OK", + "Internal Server Error" + ] + }, + "status_code": { + "type": "integer", + "description": "HTTP status code of the response.", + "examples": [ + 200, + 500 + ] + }, + "body": { + "type": "string", + "description": "Body of the response for display purposes. Body must be suitable for display (not binary), and truncated to a reasonable size.", + "examples": [ + "{\"user_id\": 2}" + ] + } + } + } + } + } + } + } + }, + "location": { + "type": "object", + "description": "Identifies the vulnerability's location.", + "properties": { + "hostname": { + "type": "string", + "description": "The protocol, domain, and port of the application where the vulnerability was found." + }, + "method": { + "type": "string", + "description": "The HTTP method that was used to request the URL where the vulnerability was found." + }, + "param": { + "type": "string", + "description": "A value provided by a vulnerability rule related to the found vulnerability. Examples include a header value, or a parameter used in a HTTP POST." + }, + "path": { + "type": "string", + "description": "The path of the URL where the vulnerability was found. Typically, this would start with a forward slash." + } + } + }, + "assets": { + "type": "array", + "description": "Array of build assets associated with vulnerability.", + "items": { + "type": "object", + "description": "Describes an asset associated with vulnerability.", + "required": [ + "type", + "name", + "url" + ], + "properties": { + "type": { + "type": "string", + "description": "The type of asset", + "enum": [ + "http_session", + "postman" + ] + }, + "name": { + "type": "string", + "minLength": 1, + "description": "Display name for asset", + "examples": [ + "HTTP Messages", + "Postman Collection" + ] + }, + "url": { + "type": "string", + "minLength": 1, + "description": "Link to asset in build artifacts", + "examples": [ + "https://gitlab.com/gitlab-org/security-products/dast/-/jobs/626397001/artifacts/file//output/zap_session.data" + ] + } + } + } + } + } + } + }, + "remediations": { + "type": "array", + "description": "An array of objects containing information on available remediations, along with patch diffs to apply.", + "items": { + "type": "object", + "required": [ + "fixes", + "summary", + "diff" + ], + "properties": { + "fixes": { + "type": "array", + "description": "An array of strings that represent references to vulnerabilities fixed by this remediation.", + "items": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "description": "Unique identifier of the vulnerability. This is recommended to be a UUID.", + "examples": [ + "642735a5-1425-428d-8d4e-3c854885a3c9" + ] + } + } + } + }, + "summary": { + "type": "string", + "minLength": 1, + "description": "An overview of how the vulnerabilities were fixed." + }, + "diff": { + "type": "string", + "minLength": 1, + "description": "A base64-encoded remediation code diff, compatible with git apply." + } + } + } + } + } +} diff --git a/lib/gitlab/ci/parsers/security/validators/schemas/15.0.5/dependency-scanning-report-format.json b/lib/gitlab/ci/parsers/security/validators/schemas/15.0.5/dependency-scanning-report-format.json new file mode 100644 index 00000000000..c08cbcffc5b --- /dev/null +++ b/lib/gitlab/ci/parsers/security/validators/schemas/15.0.5/dependency-scanning-report-format.json @@ -0,0 +1,1033 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/raw/master/dist/dependency-scanning-report-format.json", + "title": "Report format for GitLab Dependency Scanning", + "description": "This schema provides the the report format for Dependency Scanning analyzers (https://docs.gitlab.com/ee/user/application_security/dependency_scanning).", + "definitions": { + "detail_type": { + "oneOf": [ + { + "$ref": "#/definitions/named_list" + }, + { + "$ref": "#/definitions/list" + }, + { + "$ref": "#/definitions/table" + }, + { + "$ref": "#/definitions/text" + }, + { + "$ref": "#/definitions/url" + }, + { + "$ref": "#/definitions/code" + }, + { + "$ref": "#/definitions/value" + }, + { + "$ref": "#/definitions/diff" + }, + { + "$ref": "#/definitions/markdown" + }, + { + "$ref": "#/definitions/commit" + }, + { + "$ref": "#/definitions/file_location" + }, + { + "$ref": "#/definitions/module_location" + } + ] + }, + "text_value": { + "type": "string" + }, + "named_field": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "$ref": "#/definitions/text_value", + "type": "string", + "minLength": 1 + }, + "description": { + "$ref": "#/definitions/text_value" + } + } + }, + "named_list": { + "type": "object", + "description": "An object with named and typed fields", + "required": [ + "type", + "items" + ], + "properties": { + "type": { + "const": "named-list" + }, + "items": { + "type": "object", + "patternProperties": { + "^.*$": { + "allOf": [ + { + "$ref": "#/definitions/named_field" + }, + { + "$ref": "#/definitions/detail_type" + } + ] + } + } + } + } + }, + "list": { + "type": "object", + "description": "A list of typed fields", + "required": [ + "type", + "items" + ], + "properties": { + "type": { + "const": "list" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + } + } + }, + "table": { + "type": "object", + "description": "A table of typed fields", + "required": [ + "type", + "rows" + ], + "properties": { + "type": { + "const": "table" + }, + "header": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + }, + "rows": { + "type": "array", + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + } + } + } + }, + "text": { + "type": "object", + "description": "Raw text", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "text" + }, + "value": { + "$ref": "#/definitions/text_value" + } + } + }, + "url": { + "type": "object", + "description": "A single URL", + "required": [ + "type", + "href" + ], + "properties": { + "type": { + "const": "url" + }, + "text": { + "$ref": "#/definitions/text_value" + }, + "href": { + "type": "string", + "minLength": 1, + "examples": [ + "http://mysite.com" + ] + } + } + }, + "code": { + "type": "object", + "description": "A codeblock", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "code" + }, + "value": { + "type": "string" + }, + "lang": { + "type": "string", + "description": "A programming language" + } + } + }, + "value": { + "type": "object", + "description": "A field that can store a range of types of value", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "value" + }, + "value": { + "type": [ + "number", + "string", + "boolean" + ] + } + } + }, + "diff": { + "type": "object", + "description": "A diff", + "required": [ + "type", + "before", + "after" + ], + "properties": { + "type": { + "const": "diff" + }, + "before": { + "type": "string" + }, + "after": { + "type": "string" + } + } + }, + "markdown": { + "type": "object", + "description": "GitLab flavoured markdown, see https://docs.gitlab.com/ee/user/markdown.html", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "markdown" + }, + "value": { + "$ref": "#/definitions/text_value", + "examples": [ + "Here is markdown `inline code` #1 [test](gitlab.com)\n\n![GitLab Logo](https://about.gitlab.com/images/press/logo/preview/gitlab-logo-white-preview.png)" + ] + } + } + }, + "commit": { + "type": "object", + "description": "A commit/tag/branch within the GitLab project", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "commit" + }, + "value": { + "type": "string", + "description": "The commit SHA", + "minLength": 1 + } + } + }, + "file_location": { + "type": "object", + "description": "A location within a file in the project", + "required": [ + "type", + "file_name", + "line_start" + ], + "properties": { + "type": { + "const": "file-location" + }, + "file_name": { + "type": "string", + "minLength": 1 + }, + "line_start": { + "type": "integer" + }, + "line_end": { + "type": "integer" + } + } + }, + "module_location": { + "type": "object", + "description": "A location within a binary module of the form module+relative_offset", + "required": [ + "type", + "module_name", + "offset" + ], + "properties": { + "type": { + "const": "module-location" + }, + "module_name": { + "type": "string", + "minLength": 1, + "examples": [ + "compiled_binary" + ] + }, + "offset": { + "type": "integer", + "examples": [ + 100 + ] + } + } + } + }, + "self": { + "version": "15.0.5" + }, + "type": "object", + "required": [ + "dependency_files", + "scan", + "version", + "vulnerabilities" + ], + "additionalProperties": true, + "properties": { + "scan": { + "type": "object", + "required": [ + "analyzer", + "end_time", + "scanner", + "start_time", + "status", + "type" + ], + "properties": { + "end_time": { + "type": "string", + "description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan finished.", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$", + "examples": [ + "2020-01-28T03:26:02" + ] + }, + "messages": { + "type": "array", + "items": { + "type": "object", + "description": "Communication intended for the initiator of a scan.", + "required": [ + "level", + "value" + ], + "properties": { + "level": { + "type": "string", + "description": "Describes the severity of the communication. Use info to communicate normal scan behaviour; warn to communicate a potentially recoverable problem, or a partial error; fatal to communicate an issue that causes the scan to halt.", + "enum": [ + "info", + "warn", + "fatal" + ], + "examples": [ + "info" + ] + }, + "value": { + "type": "string", + "description": "The message to communicate.", + "minLength": 1, + "examples": [ + "Permission denied, scanning aborted" + ] + } + } + } + }, + "options": { + "type": "array", + "items": { + "type": "object", + "description": "A configuration option used for this scan.", + "required": [ + "name", + "value" + ], + "properties": { + "name": { + "type": "string", + "description": "The configuration option name.", + "maxLength": 255, + "minLength": 1, + "examples": [ + "DAST_FF_ENABLE_BAS", + "DOCKER_TLS_CERTDIR", + "DS_MAX_DEPTH", + "SECURE_LOG_LEVEL" + ] + }, + "source": { + "type": "string", + "description": "The source of this option.", + "enum": [ + "argument", + "file", + "env_variable", + "other" + ] + }, + "value": { + "type": [ + "boolean", + "integer", + "null", + "string" + ], + "description": "The value used for this scan.", + "examples": [ + true, + 2, + null, + "fatal", + "" + ] + } + } + } + }, + "analyzer": { + "type": "object", + "description": "Object defining the analyzer used to perform the scan. Analyzers typically delegate to an underlying scanner to run the scan.", + "required": [ + "id", + "name", + "version", + "vendor" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique id that identifies the analyzer.", + "minLength": 1, + "examples": [ + "gitlab-dast" + ] + }, + "name": { + "type": "string", + "description": "A human readable value that identifies the analyzer, not required to be unique.", + "minLength": 1, + "examples": [ + "GitLab DAST" + ] + }, + "url": { + "type": "string", + "pattern": "^https?://.+", + "description": "A link to more information about the analyzer.", + "examples": [ + "https://docs.gitlab.com/ee/user/application_security/dast" + ] + }, + "vendor": { + "description": "The vendor/maintainer of the analyzer.", + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the vendor.", + "minLength": 1, + "examples": [ + "GitLab" + ] + } + } + }, + "version": { + "type": "string", + "description": "The version of the analyzer.", + "minLength": 1, + "examples": [ + "1.0.2" + ] + } + } + }, + "scanner": { + "type": "object", + "description": "Object defining the scanner used to perform the scan.", + "required": [ + "id", + "name", + "version", + "vendor" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique id that identifies the scanner.", + "minLength": 1, + "examples": [ + "my-sast-scanner" + ] + }, + "name": { + "type": "string", + "description": "A human readable value that identifies the scanner, not required to be unique.", + "minLength": 1, + "examples": [ + "My SAST Scanner" + ] + }, + "url": { + "type": "string", + "description": "A link to more information about the scanner.", + "examples": [ + "https://scanner.url" + ] + }, + "version": { + "type": "string", + "description": "The version of the scanner.", + "minLength": 1, + "examples": [ + "1.0.2" + ] + }, + "vendor": { + "description": "The vendor/maintainer of the scanner.", + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the vendor.", + "minLength": 1, + "examples": [ + "GitLab" + ] + } + } + } + } + }, + "start_time": { + "type": "string", + "description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan started.", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$", + "examples": [ + "2020-02-14T16:01:59" + ] + }, + "status": { + "type": "string", + "description": "Result of the scan.", + "enum": [ + "success", + "failure" + ] + }, + "type": { + "type": "string", + "description": "Type of the scan.", + "enum": [ + "dependency_scanning" + ] + }, + "primary_identifiers": { + "type": "array", + "description": "An unordered array containing an exhaustive list of primary identifiers for which the analyzer may return results", + "items": { + "type": "object", + "required": [ + "type", + "name", + "value" + ], + "properties": { + "type": { + "type": "string", + "description": "for example, cve, cwe, osvdb, usn, or an analyzer-dependent type such as gemnasium).", + "minLength": 1 + }, + "name": { + "type": "string", + "description": "Human-readable name of the identifier.", + "minLength": 1 + }, + "url": { + "type": "string", + "description": "URL of the identifier's documentation.", + "pattern": "^https?://.+" + }, + "value": { + "type": "string", + "description": "Value of the identifier, for matching purpose.", + "minLength": 1 + } + } + } + } + } + }, + "schema": { + "type": "string", + "description": "URI pointing to the validating security report schema.", + "pattern": "^https?://.+" + }, + "version": { + "type": "string", + "description": "The version of the schema to which the JSON report conforms.", + "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" + }, + "vulnerabilities": { + "type": "array", + "description": "Array of vulnerability objects.", + "items": { + "type": "object", + "description": "Describes the vulnerability using GitLab Flavored Markdown", + "required": [ + "id", + "identifiers", + "location" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "description": "Unique identifier of the vulnerability. This is recommended to be a UUID.", + "examples": [ + "642735a5-1425-428d-8d4e-3c854885a3c9" + ] + }, + "name": { + "type": "string", + "maxLength": 255, + "description": "The name of the vulnerability. This must not include the finding's specific information." + }, + "description": { + "type": "string", + "maxLength": 1048576, + "description": "A long text section describing the vulnerability more fully." + }, + "severity": { + "type": "string", + "description": "How much the vulnerability impacts the software. Possible values are Info, Unknown, Low, Medium, High, or Critical. Note that some analyzers may not report all these possible values.", + "enum": [ + "Info", + "Unknown", + "Low", + "Medium", + "High", + "Critical" + ] + }, + "solution": { + "type": "string", + "maxLength": 7000, + "description": "Explanation of how to fix the vulnerability." + }, + "identifiers": { + "type": "array", + "minItems": 1, + "description": "An ordered array of references that identify a vulnerability on internal or external databases. The first identifier is the Primary Identifier, which has special meaning.", + "items": { + "type": "object", + "required": [ + "type", + "name", + "value" + ], + "properties": { + "type": { + "type": "string", + "description": "for example, cve, cwe, osvdb, usn, or an analyzer-dependent type such as gemnasium).", + "minLength": 1 + }, + "name": { + "type": "string", + "description": "Human-readable name of the identifier.", + "minLength": 1 + }, + "url": { + "type": "string", + "description": "URL of the identifier's documentation.", + "pattern": "^https?://.+" + }, + "value": { + "type": "string", + "description": "Value of the identifier, for matching purpose.", + "minLength": 1 + } + } + } + }, + "links": { + "type": "array", + "description": "An array of references to external documentation or articles that describe the vulnerability.", + "items": { + "type": "object", + "required": [ + "url" + ], + "properties": { + "name": { + "type": "string", + "description": "Name of the vulnerability details link." + }, + "url": { + "type": "string", + "description": "URL of the vulnerability details document.", + "pattern": "^https?://.+" + } + } + } + }, + "details": { + "$ref": "#/definitions/named_list/properties/items" + }, + "tracking": { + "type": "object", + "description": "Describes how this vulnerability should be tracked as the project changes.", + "oneOf": [ + { + "description": "Declares that a series of items should be tracked using source-specific tracking methods.", + "required": [ + "items" + ], + "properties": { + "type": { + "const": "source" + }, + "items": { + "type": "array", + "items": { + "description": "An item that should be tracked using source-specific tracking methods.", + "type": "object", + "required": [ + "signatures" + ], + "properties": { + "file": { + "type": "string", + "description": "Path to the file where the vulnerability is located." + }, + "start_line": { + "type": "number", + "description": "The first line of the file that includes the vulnerability." + }, + "end_line": { + "type": "number", + "description": "The last line of the file that includes the vulnerability." + }, + "signatures": { + "type": "array", + "description": "An array of calculated tracking signatures for this tracking item.", + "minItems": 1, + "items": { + "description": "A calculated tracking signature value and metadata.", + "type": "object", + "required": [ + "algorithm", + "value" + ], + "properties": { + "algorithm": { + "type": "string", + "description": "The algorithm used to generate the signature." + }, + "value": { + "type": "string", + "description": "The result of this signature algorithm." + } + } + } + } + } + } + } + } + } + ], + "properties": { + "type": { + "type": "string", + "description": "Each tracking type must declare its own type." + } + } + }, + "flags": { + "description": "Flags that can be attached to vulnerabilities.", + "type": "array", + "items": { + "type": "object", + "description": "Informational flags identified and assigned to a vulnerability.", + "required": [ + "type", + "origin", + "description" + ], + "properties": { + "type": { + "type": "string", + "minLength": 1, + "description": "Result of the scan.", + "enum": [ + "flagged-as-likely-false-positive" + ] + }, + "origin": { + "minLength": 1, + "description": "Tool that issued the flag.", + "type": "string" + }, + "description": { + "minLength": 1, + "description": "What the flag is about.", + "type": "string" + } + } + } + }, + "location": { + "type": "object", + "description": "Identifies the vulnerability's location.", + "required": [ + "file", + "dependency" + ], + "properties": { + "file": { + "type": "string", + "minLength": 1, + "description": "Path to the manifest or lock file where the dependency is declared (such as yarn.lock)." + }, + "dependency": { + "type": "object", + "description": "Describes the dependency of a project where the vulnerability is located.", + "required": [ + "package", + "version" + ], + "properties": { + "package": { + "type": "object", + "description": "Provides information on the package where the vulnerability is located.", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "Name of the package where the vulnerability is located." + } + } + }, + "version": { + "type": "string", + "description": "Version of the vulnerable package." + }, + "iid": { + "description": "ID that identifies the dependency in the scope of a dependency file.", + "type": "number" + }, + "direct": { + "type": "boolean", + "description": "Tells whether this is a direct, top-level dependency of the scanned project." + }, + "dependency_path": { + "type": "array", + "description": "Ancestors of the dependency, starting from a direct project dependency, and ending with an immediate parent of the dependency. The dependency itself is excluded from the path. Direct dependencies have no path.", + "items": { + "type": "object", + "required": [ + "iid" + ], + "properties": { + "iid": { + "type": "number", + "description": "ID that is unique in the scope of a parent object, and specific to the resource type." + } + } + } + } + } + } + } + } + } + } + }, + "remediations": { + "type": "array", + "description": "An array of objects containing information on available remediations, along with patch diffs to apply.", + "items": { + "type": "object", + "required": [ + "fixes", + "summary", + "diff" + ], + "properties": { + "fixes": { + "type": "array", + "description": "An array of strings that represent references to vulnerabilities fixed by this remediation.", + "items": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "description": "Unique identifier of the vulnerability. This is recommended to be a UUID.", + "examples": [ + "642735a5-1425-428d-8d4e-3c854885a3c9" + ] + } + } + } + }, + "summary": { + "type": "string", + "minLength": 1, + "description": "An overview of how the vulnerabilities were fixed." + }, + "diff": { + "type": "string", + "minLength": 1, + "description": "A base64-encoded remediation code diff, compatible with git apply." + } + } + } + }, + "dependency_files": { + "type": "array", + "description": "List of dependency files identified in the project.", + "items": { + "type": "object", + "required": [ + "path", + "package_manager", + "dependencies" + ], + "properties": { + "path": { + "type": "string", + "minLength": 1 + }, + "package_manager": { + "type": "string", + "minLength": 1 + }, + "dependencies": { + "type": "array", + "items": { + "type": "object", + "description": "Describes the dependency of a project where the vulnerability is located.", + "required": [ + "package", + "version" + ], + "properties": { + "package": { + "type": "object", + "description": "Provides information on the package where the vulnerability is located.", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "Name of the package where the vulnerability is located." + } + } + }, + "version": { + "type": "string", + "description": "Version of the vulnerable package." + }, + "iid": { + "description": "ID that identifies the dependency in the scope of a dependency file.", + "type": "number" + }, + "direct": { + "type": "boolean", + "description": "Tells whether this is a direct, top-level dependency of the scanned project." + }, + "dependency_path": { + "type": "array", + "description": "Ancestors of the dependency, starting from a direct project dependency, and ending with an immediate parent of the dependency. The dependency itself is excluded from the path. Direct dependencies have no path.", + "items": { + "type": "object", + "required": [ + "iid" + ], + "properties": { + "iid": { + "type": "number", + "description": "ID that is unique in the scope of a parent object, and specific to the resource type." + } + } + } + } + } + } + } + } + } + } + } +} diff --git a/lib/gitlab/ci/parsers/security/validators/schemas/15.0.5/sast-report-format.json b/lib/gitlab/ci/parsers/security/validators/schemas/15.0.5/sast-report-format.json new file mode 100644 index 00000000000..f1869950d20 --- /dev/null +++ b/lib/gitlab/ci/parsers/security/validators/schemas/15.0.5/sast-report-format.json @@ -0,0 +1,920 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/raw/master/dist/sast-report-format.json", + "title": "Report format for GitLab SAST", + "description": "This schema provides the report format for Static Application Security Testing analyzers (https://docs.gitlab.com/ee/user/application_security/sast).", + "definitions": { + "detail_type": { + "oneOf": [ + { + "$ref": "#/definitions/named_list" + }, + { + "$ref": "#/definitions/list" + }, + { + "$ref": "#/definitions/table" + }, + { + "$ref": "#/definitions/text" + }, + { + "$ref": "#/definitions/url" + }, + { + "$ref": "#/definitions/code" + }, + { + "$ref": "#/definitions/value" + }, + { + "$ref": "#/definitions/diff" + }, + { + "$ref": "#/definitions/markdown" + }, + { + "$ref": "#/definitions/commit" + }, + { + "$ref": "#/definitions/file_location" + }, + { + "$ref": "#/definitions/module_location" + } + ] + }, + "text_value": { + "type": "string" + }, + "named_field": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "$ref": "#/definitions/text_value", + "type": "string", + "minLength": 1 + }, + "description": { + "$ref": "#/definitions/text_value" + } + } + }, + "named_list": { + "type": "object", + "description": "An object with named and typed fields", + "required": [ + "type", + "items" + ], + "properties": { + "type": { + "const": "named-list" + }, + "items": { + "type": "object", + "patternProperties": { + "^.*$": { + "allOf": [ + { + "$ref": "#/definitions/named_field" + }, + { + "$ref": "#/definitions/detail_type" + } + ] + } + } + } + } + }, + "list": { + "type": "object", + "description": "A list of typed fields", + "required": [ + "type", + "items" + ], + "properties": { + "type": { + "const": "list" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + } + } + }, + "table": { + "type": "object", + "description": "A table of typed fields", + "required": [ + "type", + "rows" + ], + "properties": { + "type": { + "const": "table" + }, + "header": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + }, + "rows": { + "type": "array", + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + } + } + } + }, + "text": { + "type": "object", + "description": "Raw text", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "text" + }, + "value": { + "$ref": "#/definitions/text_value" + } + } + }, + "url": { + "type": "object", + "description": "A single URL", + "required": [ + "type", + "href" + ], + "properties": { + "type": { + "const": "url" + }, + "text": { + "$ref": "#/definitions/text_value" + }, + "href": { + "type": "string", + "minLength": 1, + "examples": [ + "http://mysite.com" + ] + } + } + }, + "code": { + "type": "object", + "description": "A codeblock", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "code" + }, + "value": { + "type": "string" + }, + "lang": { + "type": "string", + "description": "A programming language" + } + } + }, + "value": { + "type": "object", + "description": "A field that can store a range of types of value", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "value" + }, + "value": { + "type": [ + "number", + "string", + "boolean" + ] + } + } + }, + "diff": { + "type": "object", + "description": "A diff", + "required": [ + "type", + "before", + "after" + ], + "properties": { + "type": { + "const": "diff" + }, + "before": { + "type": "string" + }, + "after": { + "type": "string" + } + } + }, + "markdown": { + "type": "object", + "description": "GitLab flavoured markdown, see https://docs.gitlab.com/ee/user/markdown.html", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "markdown" + }, + "value": { + "$ref": "#/definitions/text_value", + "examples": [ + "Here is markdown `inline code` #1 [test](gitlab.com)\n\n![GitLab Logo](https://about.gitlab.com/images/press/logo/preview/gitlab-logo-white-preview.png)" + ] + } + } + }, + "commit": { + "type": "object", + "description": "A commit/tag/branch within the GitLab project", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "commit" + }, + "value": { + "type": "string", + "description": "The commit SHA", + "minLength": 1 + } + } + }, + "file_location": { + "type": "object", + "description": "A location within a file in the project", + "required": [ + "type", + "file_name", + "line_start" + ], + "properties": { + "type": { + "const": "file-location" + }, + "file_name": { + "type": "string", + "minLength": 1 + }, + "line_start": { + "type": "integer" + }, + "line_end": { + "type": "integer" + } + } + }, + "module_location": { + "type": "object", + "description": "A location within a binary module of the form module+relative_offset", + "required": [ + "type", + "module_name", + "offset" + ], + "properties": { + "type": { + "const": "module-location" + }, + "module_name": { + "type": "string", + "minLength": 1, + "examples": [ + "compiled_binary" + ] + }, + "offset": { + "type": "integer", + "examples": [ + 100 + ] + } + } + } + }, + "self": { + "version": "15.0.5" + }, + "type": "object", + "required": [ + "scan", + "version", + "vulnerabilities" + ], + "additionalProperties": true, + "properties": { + "scan": { + "type": "object", + "required": [ + "analyzer", + "end_time", + "scanner", + "start_time", + "status", + "type" + ], + "properties": { + "end_time": { + "type": "string", + "description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan finished.", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$", + "examples": [ + "2020-01-28T03:26:02" + ] + }, + "messages": { + "type": "array", + "items": { + "type": "object", + "description": "Communication intended for the initiator of a scan.", + "required": [ + "level", + "value" + ], + "properties": { + "level": { + "type": "string", + "description": "Describes the severity of the communication. Use info to communicate normal scan behaviour; warn to communicate a potentially recoverable problem, or a partial error; fatal to communicate an issue that causes the scan to halt.", + "enum": [ + "info", + "warn", + "fatal" + ], + "examples": [ + "info" + ] + }, + "value": { + "type": "string", + "description": "The message to communicate.", + "minLength": 1, + "examples": [ + "Permission denied, scanning aborted" + ] + } + } + } + }, + "options": { + "type": "array", + "items": { + "type": "object", + "description": "A configuration option used for this scan.", + "required": [ + "name", + "value" + ], + "properties": { + "name": { + "type": "string", + "description": "The configuration option name.", + "maxLength": 255, + "minLength": 1, + "examples": [ + "DAST_FF_ENABLE_BAS", + "DOCKER_TLS_CERTDIR", + "DS_MAX_DEPTH", + "SECURE_LOG_LEVEL" + ] + }, + "source": { + "type": "string", + "description": "The source of this option.", + "enum": [ + "argument", + "file", + "env_variable", + "other" + ] + }, + "value": { + "type": [ + "boolean", + "integer", + "null", + "string" + ], + "description": "The value used for this scan.", + "examples": [ + true, + 2, + null, + "fatal", + "" + ] + } + } + } + }, + "analyzer": { + "type": "object", + "description": "Object defining the analyzer used to perform the scan. Analyzers typically delegate to an underlying scanner to run the scan.", + "required": [ + "id", + "name", + "version", + "vendor" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique id that identifies the analyzer.", + "minLength": 1, + "examples": [ + "gitlab-dast" + ] + }, + "name": { + "type": "string", + "description": "A human readable value that identifies the analyzer, not required to be unique.", + "minLength": 1, + "examples": [ + "GitLab DAST" + ] + }, + "url": { + "type": "string", + "pattern": "^https?://.+", + "description": "A link to more information about the analyzer.", + "examples": [ + "https://docs.gitlab.com/ee/user/application_security/dast" + ] + }, + "vendor": { + "description": "The vendor/maintainer of the analyzer.", + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the vendor.", + "minLength": 1, + "examples": [ + "GitLab" + ] + } + } + }, + "version": { + "type": "string", + "description": "The version of the analyzer.", + "minLength": 1, + "examples": [ + "1.0.2" + ] + } + } + }, + "scanner": { + "type": "object", + "description": "Object defining the scanner used to perform the scan.", + "required": [ + "id", + "name", + "version", + "vendor" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique id that identifies the scanner.", + "minLength": 1, + "examples": [ + "my-sast-scanner" + ] + }, + "name": { + "type": "string", + "description": "A human readable value that identifies the scanner, not required to be unique.", + "minLength": 1, + "examples": [ + "My SAST Scanner" + ] + }, + "url": { + "type": "string", + "description": "A link to more information about the scanner.", + "examples": [ + "https://scanner.url" + ] + }, + "version": { + "type": "string", + "description": "The version of the scanner.", + "minLength": 1, + "examples": [ + "1.0.2" + ] + }, + "vendor": { + "description": "The vendor/maintainer of the scanner.", + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the vendor.", + "minLength": 1, + "examples": [ + "GitLab" + ] + } + } + } + } + }, + "start_time": { + "type": "string", + "description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan started.", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$", + "examples": [ + "2020-02-14T16:01:59" + ] + }, + "status": { + "type": "string", + "description": "Result of the scan.", + "enum": [ + "success", + "failure" + ] + }, + "type": { + "type": "string", + "description": "Type of the scan.", + "enum": [ + "sast" + ] + }, + "primary_identifiers": { + "type": "array", + "description": "An unordered array containing an exhaustive list of primary identifiers for which the analyzer may return results", + "items": { + "type": "object", + "required": [ + "type", + "name", + "value" + ], + "properties": { + "type": { + "type": "string", + "description": "for example, cve, cwe, osvdb, usn, or an analyzer-dependent type such as gemnasium).", + "minLength": 1 + }, + "name": { + "type": "string", + "description": "Human-readable name of the identifier.", + "minLength": 1 + }, + "url": { + "type": "string", + "description": "URL of the identifier's documentation.", + "pattern": "^https?://.+" + }, + "value": { + "type": "string", + "description": "Value of the identifier, for matching purpose.", + "minLength": 1 + } + } + } + } + } + }, + "schema": { + "type": "string", + "description": "URI pointing to the validating security report schema.", + "pattern": "^https?://.+" + }, + "version": { + "type": "string", + "description": "The version of the schema to which the JSON report conforms.", + "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" + }, + "vulnerabilities": { + "type": "array", + "description": "Array of vulnerability objects.", + "items": { + "type": "object", + "description": "Describes the vulnerability using GitLab Flavored Markdown", + "required": [ + "id", + "identifiers", + "location" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "description": "Unique identifier of the vulnerability. This is recommended to be a UUID.", + "examples": [ + "642735a5-1425-428d-8d4e-3c854885a3c9" + ] + }, + "name": { + "type": "string", + "maxLength": 255, + "description": "The name of the vulnerability. This must not include the finding's specific information." + }, + "description": { + "type": "string", + "maxLength": 1048576, + "description": "A long text section describing the vulnerability more fully." + }, + "severity": { + "type": "string", + "description": "How much the vulnerability impacts the software. Possible values are Info, Unknown, Low, Medium, High, or Critical. Note that some analyzers may not report all these possible values.", + "enum": [ + "Info", + "Unknown", + "Low", + "Medium", + "High", + "Critical" + ] + }, + "solution": { + "type": "string", + "maxLength": 7000, + "description": "Explanation of how to fix the vulnerability." + }, + "identifiers": { + "type": "array", + "minItems": 1, + "description": "An ordered array of references that identify a vulnerability on internal or external databases. The first identifier is the Primary Identifier, which has special meaning.", + "items": { + "type": "object", + "required": [ + "type", + "name", + "value" + ], + "properties": { + "type": { + "type": "string", + "description": "for example, cve, cwe, osvdb, usn, or an analyzer-dependent type such as gemnasium).", + "minLength": 1 + }, + "name": { + "type": "string", + "description": "Human-readable name of the identifier.", + "minLength": 1 + }, + "url": { + "type": "string", + "description": "URL of the identifier's documentation.", + "pattern": "^https?://.+" + }, + "value": { + "type": "string", + "description": "Value of the identifier, for matching purpose.", + "minLength": 1 + } + } + } + }, + "links": { + "type": "array", + "description": "An array of references to external documentation or articles that describe the vulnerability.", + "items": { + "type": "object", + "required": [ + "url" + ], + "properties": { + "name": { + "type": "string", + "description": "Name of the vulnerability details link." + }, + "url": { + "type": "string", + "description": "URL of the vulnerability details document.", + "pattern": "^https?://.+" + } + } + } + }, + "details": { + "$ref": "#/definitions/named_list/properties/items" + }, + "tracking": { + "type": "object", + "description": "Describes how this vulnerability should be tracked as the project changes.", + "oneOf": [ + { + "description": "Declares that a series of items should be tracked using source-specific tracking methods.", + "required": [ + "items" + ], + "properties": { + "type": { + "const": "source" + }, + "items": { + "type": "array", + "items": { + "description": "An item that should be tracked using source-specific tracking methods.", + "type": "object", + "required": [ + "signatures" + ], + "properties": { + "file": { + "type": "string", + "description": "Path to the file where the vulnerability is located." + }, + "start_line": { + "type": "number", + "description": "The first line of the file that includes the vulnerability." + }, + "end_line": { + "type": "number", + "description": "The last line of the file that includes the vulnerability." + }, + "signatures": { + "type": "array", + "description": "An array of calculated tracking signatures for this tracking item.", + "minItems": 1, + "items": { + "description": "A calculated tracking signature value and metadata.", + "type": "object", + "required": [ + "algorithm", + "value" + ], + "properties": { + "algorithm": { + "type": "string", + "description": "The algorithm used to generate the signature." + }, + "value": { + "type": "string", + "description": "The result of this signature algorithm." + } + } + } + } + } + } + } + } + } + ], + "properties": { + "type": { + "type": "string", + "description": "Each tracking type must declare its own type." + } + } + }, + "flags": { + "description": "Flags that can be attached to vulnerabilities.", + "type": "array", + "items": { + "type": "object", + "description": "Informational flags identified and assigned to a vulnerability.", + "required": [ + "type", + "origin", + "description" + ], + "properties": { + "type": { + "type": "string", + "minLength": 1, + "description": "Result of the scan.", + "enum": [ + "flagged-as-likely-false-positive" + ] + }, + "origin": { + "minLength": 1, + "description": "Tool that issued the flag.", + "type": "string" + }, + "description": { + "minLength": 1, + "description": "What the flag is about.", + "type": "string" + } + } + } + }, + "location": { + "type": "object", + "description": "Identifies the vulnerability's location.", + "properties": { + "file": { + "type": "string", + "description": "Path to the file where the vulnerability is located." + }, + "start_line": { + "type": "number", + "description": "The first line of the code affected by the vulnerability." + }, + "end_line": { + "type": "number", + "description": "The last line of the code affected by the vulnerability." + }, + "class": { + "type": "string", + "description": "Provides the name of the class where the vulnerability is located." + }, + "method": { + "type": "string", + "description": "Provides the name of the method where the vulnerability is located." + } + } + }, + "raw_source_code_extract": { + "type": "string", + "description": "Provides an unsanitized excerpt of the affected source code." + } + } + } + }, + "remediations": { + "type": "array", + "description": "An array of objects containing information on available remediations, along with patch diffs to apply.", + "items": { + "type": "object", + "required": [ + "fixes", + "summary", + "diff" + ], + "properties": { + "fixes": { + "type": "array", + "description": "An array of strings that represent references to vulnerabilities fixed by this remediation.", + "items": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "description": "Unique identifier of the vulnerability. This is recommended to be a UUID.", + "examples": [ + "642735a5-1425-428d-8d4e-3c854885a3c9" + ] + } + } + } + }, + "summary": { + "type": "string", + "minLength": 1, + "description": "An overview of how the vulnerabilities were fixed." + }, + "diff": { + "type": "string", + "minLength": 1, + "description": "A base64-encoded remediation code diff, compatible with git apply." + } + } + } + } + } +} diff --git a/lib/gitlab/ci/parsers/security/validators/schemas/15.0.5/secret-detection-report-format.json b/lib/gitlab/ci/parsers/security/validators/schemas/15.0.5/secret-detection-report-format.json new file mode 100644 index 00000000000..e9bfd6186a3 --- /dev/null +++ b/lib/gitlab/ci/parsers/security/validators/schemas/15.0.5/secret-detection-report-format.json @@ -0,0 +1,944 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/raw/master/dist/secret-detection-report-format.json", + "title": "Report format for GitLab Secret Detection", + "description": "This schema provides the the report format for the Secret Detection analyzer (https://docs.gitlab.com/ee/user/application_security/secret_detection)", + "definitions": { + "detail_type": { + "oneOf": [ + { + "$ref": "#/definitions/named_list" + }, + { + "$ref": "#/definitions/list" + }, + { + "$ref": "#/definitions/table" + }, + { + "$ref": "#/definitions/text" + }, + { + "$ref": "#/definitions/url" + }, + { + "$ref": "#/definitions/code" + }, + { + "$ref": "#/definitions/value" + }, + { + "$ref": "#/definitions/diff" + }, + { + "$ref": "#/definitions/markdown" + }, + { + "$ref": "#/definitions/commit" + }, + { + "$ref": "#/definitions/file_location" + }, + { + "$ref": "#/definitions/module_location" + } + ] + }, + "text_value": { + "type": "string" + }, + "named_field": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "$ref": "#/definitions/text_value", + "type": "string", + "minLength": 1 + }, + "description": { + "$ref": "#/definitions/text_value" + } + } + }, + "named_list": { + "type": "object", + "description": "An object with named and typed fields", + "required": [ + "type", + "items" + ], + "properties": { + "type": { + "const": "named-list" + }, + "items": { + "type": "object", + "patternProperties": { + "^.*$": { + "allOf": [ + { + "$ref": "#/definitions/named_field" + }, + { + "$ref": "#/definitions/detail_type" + } + ] + } + } + } + } + }, + "list": { + "type": "object", + "description": "A list of typed fields", + "required": [ + "type", + "items" + ], + "properties": { + "type": { + "const": "list" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + } + } + }, + "table": { + "type": "object", + "description": "A table of typed fields", + "required": [ + "type", + "rows" + ], + "properties": { + "type": { + "const": "table" + }, + "header": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + }, + "rows": { + "type": "array", + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + } + } + } + }, + "text": { + "type": "object", + "description": "Raw text", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "text" + }, + "value": { + "$ref": "#/definitions/text_value" + } + } + }, + "url": { + "type": "object", + "description": "A single URL", + "required": [ + "type", + "href" + ], + "properties": { + "type": { + "const": "url" + }, + "text": { + "$ref": "#/definitions/text_value" + }, + "href": { + "type": "string", + "minLength": 1, + "examples": [ + "http://mysite.com" + ] + } + } + }, + "code": { + "type": "object", + "description": "A codeblock", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "code" + }, + "value": { + "type": "string" + }, + "lang": { + "type": "string", + "description": "A programming language" + } + } + }, + "value": { + "type": "object", + "description": "A field that can store a range of types of value", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "value" + }, + "value": { + "type": [ + "number", + "string", + "boolean" + ] + } + } + }, + "diff": { + "type": "object", + "description": "A diff", + "required": [ + "type", + "before", + "after" + ], + "properties": { + "type": { + "const": "diff" + }, + "before": { + "type": "string" + }, + "after": { + "type": "string" + } + } + }, + "markdown": { + "type": "object", + "description": "GitLab flavoured markdown, see https://docs.gitlab.com/ee/user/markdown.html", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "markdown" + }, + "value": { + "$ref": "#/definitions/text_value", + "examples": [ + "Here is markdown `inline code` #1 [test](gitlab.com)\n\n![GitLab Logo](https://about.gitlab.com/images/press/logo/preview/gitlab-logo-white-preview.png)" + ] + } + } + }, + "commit": { + "type": "object", + "description": "A commit/tag/branch within the GitLab project", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "commit" + }, + "value": { + "type": "string", + "description": "The commit SHA", + "minLength": 1 + } + } + }, + "file_location": { + "type": "object", + "description": "A location within a file in the project", + "required": [ + "type", + "file_name", + "line_start" + ], + "properties": { + "type": { + "const": "file-location" + }, + "file_name": { + "type": "string", + "minLength": 1 + }, + "line_start": { + "type": "integer" + }, + "line_end": { + "type": "integer" + } + } + }, + "module_location": { + "type": "object", + "description": "A location within a binary module of the form module+relative_offset", + "required": [ + "type", + "module_name", + "offset" + ], + "properties": { + "type": { + "const": "module-location" + }, + "module_name": { + "type": "string", + "minLength": 1, + "examples": [ + "compiled_binary" + ] + }, + "offset": { + "type": "integer", + "examples": [ + 100 + ] + } + } + } + }, + "self": { + "version": "15.0.5" + }, + "type": "object", + "required": [ + "scan", + "version", + "vulnerabilities" + ], + "additionalProperties": true, + "properties": { + "scan": { + "type": "object", + "required": [ + "analyzer", + "end_time", + "scanner", + "start_time", + "status", + "type" + ], + "properties": { + "end_time": { + "type": "string", + "description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan finished.", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$", + "examples": [ + "2020-01-28T03:26:02" + ] + }, + "messages": { + "type": "array", + "items": { + "type": "object", + "description": "Communication intended for the initiator of a scan.", + "required": [ + "level", + "value" + ], + "properties": { + "level": { + "type": "string", + "description": "Describes the severity of the communication. Use info to communicate normal scan behaviour; warn to communicate a potentially recoverable problem, or a partial error; fatal to communicate an issue that causes the scan to halt.", + "enum": [ + "info", + "warn", + "fatal" + ], + "examples": [ + "info" + ] + }, + "value": { + "type": "string", + "description": "The message to communicate.", + "minLength": 1, + "examples": [ + "Permission denied, scanning aborted" + ] + } + } + } + }, + "options": { + "type": "array", + "items": { + "type": "object", + "description": "A configuration option used for this scan.", + "required": [ + "name", + "value" + ], + "properties": { + "name": { + "type": "string", + "description": "The configuration option name.", + "maxLength": 255, + "minLength": 1, + "examples": [ + "DAST_FF_ENABLE_BAS", + "DOCKER_TLS_CERTDIR", + "DS_MAX_DEPTH", + "SECURE_LOG_LEVEL" + ] + }, + "source": { + "type": "string", + "description": "The source of this option.", + "enum": [ + "argument", + "file", + "env_variable", + "other" + ] + }, + "value": { + "type": [ + "boolean", + "integer", + "null", + "string" + ], + "description": "The value used for this scan.", + "examples": [ + true, + 2, + null, + "fatal", + "" + ] + } + } + } + }, + "analyzer": { + "type": "object", + "description": "Object defining the analyzer used to perform the scan. Analyzers typically delegate to an underlying scanner to run the scan.", + "required": [ + "id", + "name", + "version", + "vendor" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique id that identifies the analyzer.", + "minLength": 1, + "examples": [ + "gitlab-dast" + ] + }, + "name": { + "type": "string", + "description": "A human readable value that identifies the analyzer, not required to be unique.", + "minLength": 1, + "examples": [ + "GitLab DAST" + ] + }, + "url": { + "type": "string", + "pattern": "^https?://.+", + "description": "A link to more information about the analyzer.", + "examples": [ + "https://docs.gitlab.com/ee/user/application_security/dast" + ] + }, + "vendor": { + "description": "The vendor/maintainer of the analyzer.", + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the vendor.", + "minLength": 1, + "examples": [ + "GitLab" + ] + } + } + }, + "version": { + "type": "string", + "description": "The version of the analyzer.", + "minLength": 1, + "examples": [ + "1.0.2" + ] + } + } + }, + "scanner": { + "type": "object", + "description": "Object defining the scanner used to perform the scan.", + "required": [ + "id", + "name", + "version", + "vendor" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique id that identifies the scanner.", + "minLength": 1, + "examples": [ + "my-sast-scanner" + ] + }, + "name": { + "type": "string", + "description": "A human readable value that identifies the scanner, not required to be unique.", + "minLength": 1, + "examples": [ + "My SAST Scanner" + ] + }, + "url": { + "type": "string", + "description": "A link to more information about the scanner.", + "examples": [ + "https://scanner.url" + ] + }, + "version": { + "type": "string", + "description": "The version of the scanner.", + "minLength": 1, + "examples": [ + "1.0.2" + ] + }, + "vendor": { + "description": "The vendor/maintainer of the scanner.", + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the vendor.", + "minLength": 1, + "examples": [ + "GitLab" + ] + } + } + } + } + }, + "start_time": { + "type": "string", + "description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan started.", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$", + "examples": [ + "2020-02-14T16:01:59" + ] + }, + "status": { + "type": "string", + "description": "Result of the scan.", + "enum": [ + "success", + "failure" + ] + }, + "type": { + "type": "string", + "description": "Type of the scan.", + "enum": [ + "secret_detection" + ] + }, + "primary_identifiers": { + "type": "array", + "description": "An unordered array containing an exhaustive list of primary identifiers for which the analyzer may return results", + "items": { + "type": "object", + "required": [ + "type", + "name", + "value" + ], + "properties": { + "type": { + "type": "string", + "description": "for example, cve, cwe, osvdb, usn, or an analyzer-dependent type such as gemnasium).", + "minLength": 1 + }, + "name": { + "type": "string", + "description": "Human-readable name of the identifier.", + "minLength": 1 + }, + "url": { + "type": "string", + "description": "URL of the identifier's documentation.", + "pattern": "^https?://.+" + }, + "value": { + "type": "string", + "description": "Value of the identifier, for matching purpose.", + "minLength": 1 + } + } + } + } + } + }, + "schema": { + "type": "string", + "description": "URI pointing to the validating security report schema.", + "pattern": "^https?://.+" + }, + "version": { + "type": "string", + "description": "The version of the schema to which the JSON report conforms.", + "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" + }, + "vulnerabilities": { + "type": "array", + "description": "Array of vulnerability objects.", + "items": { + "type": "object", + "description": "Describes the vulnerability using GitLab Flavored Markdown", + "required": [ + "id", + "identifiers", + "location" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "description": "Unique identifier of the vulnerability. This is recommended to be a UUID.", + "examples": [ + "642735a5-1425-428d-8d4e-3c854885a3c9" + ] + }, + "name": { + "type": "string", + "maxLength": 255, + "description": "The name of the vulnerability. This must not include the finding's specific information." + }, + "description": { + "type": "string", + "maxLength": 1048576, + "description": "A long text section describing the vulnerability more fully." + }, + "severity": { + "type": "string", + "description": "How much the vulnerability impacts the software. Possible values are Info, Unknown, Low, Medium, High, or Critical. Note that some analyzers may not report all these possible values.", + "enum": [ + "Info", + "Unknown", + "Low", + "Medium", + "High", + "Critical" + ] + }, + "solution": { + "type": "string", + "maxLength": 7000, + "description": "Explanation of how to fix the vulnerability." + }, + "identifiers": { + "type": "array", + "minItems": 1, + "description": "An ordered array of references that identify a vulnerability on internal or external databases. The first identifier is the Primary Identifier, which has special meaning.", + "items": { + "type": "object", + "required": [ + "type", + "name", + "value" + ], + "properties": { + "type": { + "type": "string", + "description": "for example, cve, cwe, osvdb, usn, or an analyzer-dependent type such as gemnasium).", + "minLength": 1 + }, + "name": { + "type": "string", + "description": "Human-readable name of the identifier.", + "minLength": 1 + }, + "url": { + "type": "string", + "description": "URL of the identifier's documentation.", + "pattern": "^https?://.+" + }, + "value": { + "type": "string", + "description": "Value of the identifier, for matching purpose.", + "minLength": 1 + } + } + } + }, + "links": { + "type": "array", + "description": "An array of references to external documentation or articles that describe the vulnerability.", + "items": { + "type": "object", + "required": [ + "url" + ], + "properties": { + "name": { + "type": "string", + "description": "Name of the vulnerability details link." + }, + "url": { + "type": "string", + "description": "URL of the vulnerability details document.", + "pattern": "^https?://.+" + } + } + } + }, + "details": { + "$ref": "#/definitions/named_list/properties/items" + }, + "tracking": { + "type": "object", + "description": "Describes how this vulnerability should be tracked as the project changes.", + "oneOf": [ + { + "description": "Declares that a series of items should be tracked using source-specific tracking methods.", + "required": [ + "items" + ], + "properties": { + "type": { + "const": "source" + }, + "items": { + "type": "array", + "items": { + "description": "An item that should be tracked using source-specific tracking methods.", + "type": "object", + "required": [ + "signatures" + ], + "properties": { + "file": { + "type": "string", + "description": "Path to the file where the vulnerability is located." + }, + "start_line": { + "type": "number", + "description": "The first line of the file that includes the vulnerability." + }, + "end_line": { + "type": "number", + "description": "The last line of the file that includes the vulnerability." + }, + "signatures": { + "type": "array", + "description": "An array of calculated tracking signatures for this tracking item.", + "minItems": 1, + "items": { + "description": "A calculated tracking signature value and metadata.", + "type": "object", + "required": [ + "algorithm", + "value" + ], + "properties": { + "algorithm": { + "type": "string", + "description": "The algorithm used to generate the signature." + }, + "value": { + "type": "string", + "description": "The result of this signature algorithm." + } + } + } + } + } + } + } + } + } + ], + "properties": { + "type": { + "type": "string", + "description": "Each tracking type must declare its own type." + } + } + }, + "flags": { + "description": "Flags that can be attached to vulnerabilities.", + "type": "array", + "items": { + "type": "object", + "description": "Informational flags identified and assigned to a vulnerability.", + "required": [ + "type", + "origin", + "description" + ], + "properties": { + "type": { + "type": "string", + "minLength": 1, + "description": "Result of the scan.", + "enum": [ + "flagged-as-likely-false-positive" + ] + }, + "origin": { + "minLength": 1, + "description": "Tool that issued the flag.", + "type": "string" + }, + "description": { + "minLength": 1, + "description": "What the flag is about.", + "type": "string" + } + } + } + }, + "location": { + "required": [ + "commit" + ], + "type": "object", + "properties": { + "file": { + "type": "string", + "description": "Path to the file where the vulnerability is located" + }, + "commit": { + "type": "object", + "description": "Represents the commit in which the vulnerability was detected", + "required": [ + "sha" + ], + "properties": { + "author": { + "type": "string" + }, + "date": { + "type": "string" + }, + "message": { + "type": "string" + }, + "sha": { + "type": "string", + "minLength": 1 + } + } + }, + "start_line": { + "type": "number", + "description": "The first line of the code affected by the vulnerability" + }, + "end_line": { + "type": "number", + "description": "The last line of the code affected by the vulnerability" + }, + "class": { + "type": "string", + "description": "Provides the name of the class where the vulnerability is located" + }, + "method": { + "type": "string", + "description": "Provides the name of the method where the vulnerability is located" + } + } + }, + "raw_source_code_extract": { + "type": "string", + "description": "Provides an unsanitized excerpt of the affected source code." + } + } + } + }, + "remediations": { + "type": "array", + "description": "An array of objects containing information on available remediations, along with patch diffs to apply.", + "items": { + "type": "object", + "required": [ + "fixes", + "summary", + "diff" + ], + "properties": { + "fixes": { + "type": "array", + "description": "An array of strings that represent references to vulnerabilities fixed by this remediation.", + "items": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "description": "Unique identifier of the vulnerability. This is recommended to be a UUID.", + "examples": [ + "642735a5-1425-428d-8d4e-3c854885a3c9" + ] + } + } + } + }, + "summary": { + "type": "string", + "minLength": 1, + "description": "An overview of how the vulnerabilities were fixed." + }, + "diff": { + "type": "string", + "minLength": 1, + "description": "A base64-encoded remediation code diff, compatible with git apply." + } + } + } + } + } +} diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 3cd30a587d8..59732a7fbc4 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -49746,6 +49746,9 @@ msgstr "" msgid "UserProfile|An error occurred loading the followers. Please refresh the page to try again." msgstr "" +msgid "UserProfile|An error occurred loading the following. Please refresh the page to try again." +msgstr "" + msgid "UserProfile|An error occurred loading the personal projects. Please refresh the page to try again." msgstr "" @@ -54927,13 +54930,13 @@ msgstr "" msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated." msgstr "" -msgid "mrWidget|To change these default messages, edit the templates for both the merge and squash commit messages. %{linkStart}Learn more.%{linkEnd}" +msgid "mrWidget|To change these default messages, edit the templates for both the merge and squash commit messages. %{linkStart}Learn more%{linkEnd}." msgstr "" -msgid "mrWidget|To change this default message, edit the template for merge commit messages. %{linkStart}Learn more.%{linkEnd}" +msgid "mrWidget|To change this default message, edit the template for merge commit messages. %{linkStart}Learn more%{linkEnd}." msgstr "" -msgid "mrWidget|To change this default message, edit the template for squash commit messages. %{linkStart}Learn more.%{linkEnd}" +msgid "mrWidget|To change this default message, edit the template for squash commit messages. %{linkStart}Learn more%{linkEnd}." msgstr "" msgid "mrWidget|What is a merge train?" diff --git a/qa/qa/page/file/form.rb b/qa/qa/page/file/form.rb index d0113d66dec..06b75f42e4d 100644 --- a/qa/qa/page/file/form.rb +++ b/qa/qa/page/file/form.rb @@ -46,7 +46,7 @@ module QA when 'LICENSE' click_element :license_dropdown else - raise %Q(Unsupported template_type "#{template_type}". Please confirm that it is a valid option.) + raise %(Unsupported template_type "#{template_type}". Please confirm that it is a valid option.) end filter_and_select template end diff --git a/qa/qa/page/project/web_ide/edit.rb b/qa/qa/page/project/web_ide/edit.rb index 8432edff046..19802f846d8 100644 --- a/qa/qa/page/project/web_ide/edit.rb +++ b/qa/qa/page/project/web_ide/edit.rb @@ -178,7 +178,7 @@ module QA within_element(:template_list_content) do click_on file_name rescue Capybara::ElementNotFound - raise ElementNotFound, %Q(Couldn't find file template named "#{file_name}". Please confirm that it is a valid option.) + raise ElementNotFound, %(Couldn't find file template named "#{file_name}". Please confirm that it is a valid option.) end # Wait for the modal to fade out too @@ -192,7 +192,7 @@ module QA begin click_on template rescue Capybara::ElementNotFound - raise ElementNotFound, %Q(Couldn't find template "#{template}" for #{file_name}. Please confirm that it exists in the list of templates.) + raise ElementNotFound, %(Couldn't find template "#{template}" for #{file_name}. Please confirm that it exists in the list of templates.) end end end diff --git a/qa/qa/resource/events/project.rb b/qa/qa/resource/events/project.rb index c1bd921c3cf..693fdd6168f 100644 --- a/qa/qa/resource/events/project.rb +++ b/qa/qa/resource/events/project.rb @@ -7,21 +7,21 @@ module QA include Events::Base def push_events(commit_message) - QA::Runtime::Logger.info(%Q[#{self.class.name} - wait for and fetch push events"]) + QA::Runtime::Logger.info(%[#{self.class.name} - wait for and fetch push events"]) fetch_events do events(action: 'pushed').select { |event| event.dig(:push_data, :commit_title) == commit_message } end end def wait_for_merge(title) - QA::Runtime::Logger.info(%Q[#{self.class.name} - wait_for_merge with title "#{title}"]) + QA::Runtime::Logger.info(%[#{self.class.name} - wait_for_merge with title "#{title}"]) wait_for_event do events(action: 'accepted', target_type: 'merge_request').any? { |event| event[:target_title] == title } end end def wait_for_push(commit_message) - QA::Runtime::Logger.info(%Q[#{self.class.name} - wait_for_push with commit message "#{commit_message}"]) + QA::Runtime::Logger.info(%[#{self.class.name} - wait_for_push with commit message "#{commit_message}"]) wait_for_event do events(action: 'pushed').any? { |event| event.dig(:push_data, :commit_title) == commit_message } end @@ -32,7 +32,7 @@ module QA end def wait_for_push_new_branch(branch_name = self.default_branch) - QA::Runtime::Logger.info(%Q[#{self.class.name} - wait_for_push_new_branch with branch_name "#{branch_name}"]) + QA::Runtime::Logger.info(%[#{self.class.name} - wait_for_push_new_branch with branch_name "#{branch_name}"]) wait_for_event do events(action: 'pushed').any? { |event| event.dig(:push_data, :ref) == branch_name } end diff --git a/qa/qa/resource/personal_access_token_cache.rb b/qa/qa/resource/personal_access_token_cache.rb index 0874618201a..078df8e6779 100644 --- a/qa/qa/resource/personal_access_token_cache.rb +++ b/qa/qa/resource/personal_access_token_cache.rb @@ -9,9 +9,9 @@ module QA token = @personal_access_tokens[username] log_message = if token - %Q[Retrieved cached token for username: #{username}, last six chars of token:#{token[-6..]}] + %[Retrieved cached token for username: #{username}, last six chars of token:#{token[-6..]}] else - %Q[No cached token found for username: #{username}] + %[No cached token found for username: #{username}] end QA::Runtime::Logger.info(log_message) @@ -20,7 +20,7 @@ module QA end def self.set_token_for_username(username, token) - QA::Runtime::Logger.info(%Q[Caching token for username: #{username}, last six chars of token:#{token[-6..]}]) + QA::Runtime::Logger.info(%[Caching token for username: #{username}, last six chars of token:#{token[-6..]}]) @personal_access_tokens[username] = token end end diff --git a/qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb index 4e9d74a5117..1a53fda4cdb 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb @@ -38,7 +38,7 @@ module QA def mailhog_json Support::Retrier.retry_until(sleep_interval: 1) do - Runtime::Logger.debug(%Q[retrieving "#{QA::Runtime::MailHog.api_messages_url}"]) + Runtime::Logger.debug(%[retrieving "#{QA::Runtime::MailHog.api_messages_url}"]) mailhog_response = get QA::Runtime::MailHog.api_messages_url @@ -47,8 +47,8 @@ module QA subjects = mailhog_data.dig('items') .map { |item| mailhog_item_subject(item) } - Runtime::Logger.debug(%Q[Total number of emails: #{total}]) - Runtime::Logger.debug(%Q[Subjects:\n#{subjects.join("\n")}]) + Runtime::Logger.debug(%[Total number of emails: #{total}]) + Runtime::Logger.debug(%[Subjects:\n#{subjects.join("\n")}]) # Expect at least two invitation messages: group and project mailhog_data if mailhog_project_message_count(subjects) >= 1 diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_with_image_pull_policy_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_with_image_pull_policy_spec.rb index c693a57605e..1abe1df5964 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_with_image_pull_policy_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_with_image_pull_policy_spec.rb @@ -112,7 +112,7 @@ module QA QA::Service::Shellout.shell("docker cp #{runner_name}:/etc/gitlab-runner/config.toml #{tempdir.path}") File.open(tempdir.path, 'a') do |f| - f << %Q[ allowed_pull_policies = #{allowed_policies}\n] + f << %[ allowed_pull_policies = #{allowed_policies}\n] end QA::Service::Shellout.shell("docker cp #{tempdir.path} #{runner_name}:/etc/gitlab-runner/config.toml") diff --git a/qa/qa/specs/features/browser_ui/5_package/infrastructure_registry/terraform_module_registry_spec.rb b/qa/qa/specs/features/browser_ui/5_package/infrastructure_registry/terraform_module_registry_spec.rb index 312c5a13a61..7742695ac41 100644 --- a/qa/qa/specs/features/browser_ui/5_package/infrastructure_registry/terraform_module_registry_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/infrastructure_registry/terraform_module_registry_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Package', product_group: :package_registry do + RSpec.describe 'Package', :requires_admin, product_group: :package_registry do describe 'Terraform Module Registry' do include Runtime::Fixtures @@ -25,6 +25,7 @@ module QA end before do + # Remove 'requires_admin' if below method is removed QA::Support::Helpers::ImportSource.enable('git') Flow::Login.sign_in diff --git a/qa/spec/runtime/feature_spec.rb b/qa/spec/runtime/feature_spec.rb index 72ba915d99b..351856de1bd 100644 --- a/qa/spec/runtime/feature_spec.rb +++ b/qa/spec/runtime/feature_spec.rb @@ -59,7 +59,7 @@ RSpec.describe QA::Runtime::Feature do .and_return(request) expect(described_class) .to receive(:get) - .and_return(Struct.new(:code, :body).new(200, %Q([{ "name": "a_flag", "state": "conditional", "gates": #{gates} }]))) + .and_return(Struct.new(:code, :body).new(200, %([{ "name": "a_flag", "state": "conditional", "gates": #{gates} }]))) expect(described_class.enabled?(feature_flag, scope => actor)).to be true end diff --git a/spec/controllers/import/fogbugz_controller_spec.rb b/spec/controllers/import/fogbugz_controller_spec.rb index 3b099ba2613..273dfd6a9c7 100644 --- a/spec/controllers/import/fogbugz_controller_spec.rb +++ b/spec/controllers/import/fogbugz_controller_spec.rb @@ -17,7 +17,7 @@ RSpec.describe Import::FogbugzController, feature_category: :importers do end describe 'POST #callback' do - let(:xml_response) { %Q(<?xml version=\"1.0\" encoding=\"UTF-8\"?><response><token><![CDATA[#{token}]]></token></response>) } + let(:xml_response) { %(<?xml version=\"1.0\" encoding=\"UTF-8\"?><response><token><![CDATA[#{token}]]></token></response>) } before do stub_request(:post, "https://example.com/api.asp").to_return(status: 200, body: xml_response, headers: {}) diff --git a/spec/controllers/projects/artifacts_controller_spec.rb b/spec/controllers/projects/artifacts_controller_spec.rb index c7b74b5cf68..44615506e5d 100644 --- a/spec/controllers/projects/artifacts_controller_spec.rb +++ b/spec/controllers/projects/artifacts_controller_spec.rb @@ -104,7 +104,7 @@ RSpec.describe Projects::ArtifactsController, feature_category: :build_artifacts download_artifact - expect(response.headers['Content-Disposition']).to eq(%Q(attachment; filename="#{filename}"; filename*=UTF-8''#{filename})) + expect(response.headers['Content-Disposition']).to eq(%(attachment; filename="#{filename}"; filename*=UTF-8''#{filename})) end end @@ -135,7 +135,7 @@ RSpec.describe Projects::ArtifactsController, feature_category: :build_artifacts download_artifact(file_type: 'archive') expect(response).to have_gitlab_http_status(:ok) - expect(response.headers['Content-Disposition']).to eq(%Q(attachment; filename="#{filename}"; filename*=UTF-8''#{filename})) + expect(response.headers['Content-Disposition']).to eq(%(attachment; filename="#{filename}"; filename*=UTF-8''#{filename})) end end end @@ -168,7 +168,7 @@ RSpec.describe Projects::ArtifactsController, feature_category: :build_artifacts download_artifact(file_type: file_type) - expect(response.headers['Content-Disposition']).to eq(%Q(attachment; filename="#{filename}"; filename*=UTF-8''#{filename})) + expect(response.headers['Content-Disposition']).to eq(%(attachment; filename="#{filename}"; filename*=UTF-8''#{filename})) end end @@ -264,7 +264,7 @@ RSpec.describe Projects::ArtifactsController, feature_category: :build_artifacts expect(response).to have_gitlab_http_status(:ok) expect(response.headers['Content-Disposition']) - .to eq(%Q(attachment; filename="#{filename}"; filename*=UTF-8''#{filename})) + .to eq(%(attachment; filename="#{filename}"; filename*=UTF-8''#{filename})) end end end diff --git a/spec/controllers/projects/design_management/designs/raw_images_controller_spec.rb b/spec/controllers/projects/design_management/designs/raw_images_controller_spec.rb index a7f3212a6f9..eaef455837f 100644 --- a/spec/controllers/projects/design_management/designs/raw_images_controller_spec.rb +++ b/spec/controllers/projects/design_management/designs/raw_images_controller_spec.rb @@ -129,7 +129,7 @@ RSpec.describe Projects::DesignManagement::Designs::RawImagesController do it 'serves files with `Content-Disposition: attachment`' do subject - expect(response.header['Content-Disposition']).to eq(%Q(attachment; filename=\"#{filename}\"; filename*=UTF-8''#{filename})) + expect(response.header['Content-Disposition']).to eq(%(attachment; filename=\"#{filename}\"; filename*=UTF-8''#{filename})) end it 'sets appropriate caching headers' do diff --git a/spec/controllers/projects/design_management/designs/resized_image_controller_spec.rb b/spec/controllers/projects/design_management/designs/resized_image_controller_spec.rb index 1bb5112681c..5ed670a4f0b 100644 --- a/spec/controllers/projects/design_management/designs/resized_image_controller_spec.rb +++ b/spec/controllers/projects/design_management/designs/resized_image_controller_spec.rb @@ -51,7 +51,7 @@ RSpec.describe Projects::DesignManagement::Designs::ResizedImageController, feat it 'sets Content-Disposition as attachment' do filename = design.filename - expect(response.header['Content-Disposition']).to eq(%Q(attachment; filename=\"#{filename}\"; filename*=UTF-8''#{filename})) + expect(response.header['Content-Disposition']).to eq(%(attachment; filename=\"#{filename}\"; filename*=UTF-8''#{filename})) end it 'serves files with Workhorse' do diff --git a/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb b/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb index 7f6a044a575..d35f037247d 100644 --- a/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb +++ b/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb @@ -12,9 +12,9 @@ RSpec.describe 'Resolving all open threads in a merge request from an issue', :j url = new_project_issue_path(project, merge_request_to_resolve_discussions_of: merge_request.iid) if title.empty? - %Q{a[href="#{url}"]} + %{a[href="#{url}"]} else - %Q{a[title="#{title}"][href="#{url}"]} + %{a[title="#{title}"][href="#{url}"]} end end @@ -30,7 +30,7 @@ RSpec.describe 'Resolving all open threads in a merge request from an issue', :j end it 'shows a button to resolve all threads by creating a new issue' do - find('.discussions-counter .dropdown-toggle').click + find('.discussions-counter .gl-new-dropdown-toggle').click within('.discussions-counter') do expect(page).to have_link(_("Resolve all with new issue"), href: new_project_issue_path(project, merge_request_to_resolve_discussions_of: merge_request.iid)) @@ -49,7 +49,7 @@ RSpec.describe 'Resolving all open threads in a merge request from an issue', :j context 'creating an issue for threads' do before do - find('.discussions-counter .dropdown-toggle').click + find('.discussions-counter .gl-new-dropdown-toggle').click find(resolve_all_discussions_link_selector).click end @@ -65,7 +65,7 @@ RSpec.describe 'Resolving all open threads in a merge request from an issue', :j before do project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED) visit project_merge_request_path(project, merge_request) - find('.discussions-counter .dropdown-toggle').click + find('.discussions-counter .gl-new-dropdown-toggle').click end it 'does not show a link to create a new issue' do diff --git a/spec/features/projects/artifacts/user_downloads_artifacts_spec.rb b/spec/features/projects/artifacts/user_downloads_artifacts_spec.rb index 48dcb95e09b..eaf57c566e8 100644 --- a/spec/features/projects/artifacts/user_downloads_artifacts_spec.rb +++ b/spec/features/projects/artifacts/user_downloads_artifacts_spec.rb @@ -9,7 +9,7 @@ RSpec.describe "User downloads artifacts", feature_category: :build_artifacts do shared_examples "downloading" do it "downloads the zip" do - expect(page.response_headers['Content-Disposition']).to eq(%Q{attachment; filename="#{job.artifacts_file.filename}"; filename*=UTF-8''#{job.artifacts_file.filename}}) + expect(page.response_headers['Content-Disposition']).to eq(%{attachment; filename="#{job.artifacts_file.filename}"; filename*=UTF-8''#{job.artifacts_file.filename}}) expect(page.response_headers['Content-Transfer-Encoding']).to eq("binary") expect(page.response_headers['Content-Type']).to eq("application/zip") expect(page.source.b).to eq(job.artifacts_file.file.read.b) diff --git a/spec/features/projects/badges/coverage_spec.rb b/spec/features/projects/badges/coverage_spec.rb index 3c8b17607ca..38a7bdf65c3 100644 --- a/spec/features/projects/badges/coverage_spec.rb +++ b/spec/features/projects/badges/coverage_spec.rb @@ -197,7 +197,7 @@ RSpec.describe 'test coverage badge', feature_category: :code_testing do def expect_coverage_badge(coverage) svg = Nokogiri::XML.parse(page.body) expect(page.response_headers['Content-Type']).to include('image/svg+xml') - expect(svg.at(%Q{text:contains("#{coverage}")})).to be_truthy + expect(svg.at(%{text:contains("#{coverage}")})).to be_truthy end def expect_coverage_badge_color(color) diff --git a/spec/features/projects/badges/pipeline_badge_spec.rb b/spec/features/projects/badges/pipeline_badge_spec.rb index c0f5d0ffead..94772c9fc1e 100644 --- a/spec/features/projects/badges/pipeline_badge_spec.rb +++ b/spec/features/projects/badges/pipeline_badge_spec.rb @@ -75,7 +75,7 @@ RSpec.describe 'Pipeline Badge', feature_category: :continuous_integration do def expect_badge(status) svg = Nokogiri::XML.parse(page.body) expect(page.response_headers['Content-Type']).to include('image/svg+xml') - expect(svg.at(%Q{text:contains("#{status}")})).to be_truthy + expect(svg.at(%{text:contains("#{status}")})).to be_truthy end end end diff --git a/spec/features/projects/issuable_templates_spec.rb b/spec/features/projects/issuable_templates_spec.rb index 72695680809..bb43d019701 100644 --- a/spec/features/projects/issuable_templates_spec.rb +++ b/spec/features/projects/issuable_templates_spec.rb @@ -18,7 +18,7 @@ RSpec.describe 'issuable templates', :js, feature_category: :groups_and_projects context 'user creates an issue using templates' do let(:template_content) { 'this is a test "bug" template' } - let(:longtemplate_content) { %Q(this\n\n\n\n\nis\n\n\n\n\na\n\n\n\n\nbug\n\n\n\n\ntemplate) } + let(:longtemplate_content) { %(this\n\n\n\n\nis\n\n\n\n\na\n\n\n\n\nbug\n\n\n\n\ntemplate) } let(:issue) { create(:issue, author: user, assignees: [user], project: project) } let(:description_addition) { ' appending to description' } diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index fcd07d33535..c203e644280 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -306,7 +306,7 @@ RSpec.describe 'Jobs', :clean_gitlab_redis_shared_state, feature_category: :grou artifact_request = requests.find { |req| req.url.include?('artifacts/download') } - expect(artifact_request.response_headers['Content-Disposition']).to eq(%Q{attachment; filename="#{job.artifacts_file.filename}"; filename*=UTF-8''#{job.artifacts_file.filename}}) + expect(artifact_request.response_headers['Content-Disposition']).to eq(%{attachment; filename="#{job.artifacts_file.filename}"; filename*=UTF-8''#{job.artifacts_file.filename}}) expect(artifact_request.response_headers['Content-Transfer-Encoding']).to eq("binary") expect(artifact_request.response_headers['Content-Type']).to eq("image/gif") expect(artifact_request.body).to eq(job.artifacts_file.file.read.b) diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index abc9e3d30fc..6d96d77519c 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -1358,7 +1358,7 @@ RSpec.describe 'Pipeline', :js, feature_category: :groups_and_projects do page.within(all('.well-segment')[1]) do expect(page).to have_selector( - %Q{span[title="#{pipeline.yaml_errors}"]}) + %{span[title="#{pipeline.yaml_errors}"]}) end end @@ -1371,7 +1371,7 @@ RSpec.describe 'Pipeline', :js, feature_category: :groups_and_projects do page.within(all('.well-segment')[1]) do expect(page).to have_selector( - %Q{span[title="#{pipeline.present.failure_reason}"]}) + %{span[title="#{pipeline.present.failure_reason}"]}) end end diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index 441f39e6999..cbaad4e069b 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -259,7 +259,7 @@ RSpec.describe 'Pipelines', :js, feature_category: :groups_and_projects do it 'contains badge with tooltip which contains error' do expect(pipeline).to have_yaml_errors expect(page).to have_selector( - %Q{span[title="#{pipeline.yaml_errors}"]}) + %{span[title="#{pipeline.yaml_errors}"]}) end it 'contains badge that indicates failure reason' do @@ -269,7 +269,7 @@ RSpec.describe 'Pipelines', :js, feature_category: :groups_and_projects do it 'contains badge with tooltip which contains failure reason' do expect(pipeline.failure_reason?).to eq true expect(page).to have_selector( - %Q{span[title="#{pipeline.present.failure_reason}"]}) + %{span[title="#{pipeline.present.failure_reason}"]}) end end @@ -656,7 +656,7 @@ RSpec.describe 'Pipelines', :js, feature_category: :groups_and_projects do # header expect(page).to have_text("##{pipeline.id}") - expect(page).to have_selector(%Q(img[src="#{pipeline.user.avatar_url}"])) + expect(page).to have_selector(%(img[src="#{pipeline.user.avatar_url}"])) expect(page).to have_link(pipeline.user.name, href: user_path(pipeline.user)) # stages diff --git a/spec/features/uploads/user_uploads_avatar_to_group_spec.rb b/spec/features/uploads/user_uploads_avatar_to_group_spec.rb index 02e98905662..2872446ed6b 100644 --- a/spec/features/uploads/user_uploads_avatar_to_group_spec.rb +++ b/spec/features/uploads/user_uploads_avatar_to_group_spec.rb @@ -22,7 +22,7 @@ RSpec.describe 'User uploads avatar to group', feature_category: :user_profile d visit group_path(group) - expect(page).to have_selector(%Q(img[data-src$="/uploads/-/system/group/avatar/#{group.id}/dk.png"])) + expect(page).to have_selector(%(img[data-src$="/uploads/-/system/group/avatar/#{group.id}/dk.png"])) # Cheating here to verify something that isn't user-facing, but is important expect(group.reload.avatar.file).to exist diff --git a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb index 03b072ea417..cc296259b80 100644 --- a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb +++ b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb @@ -18,7 +18,7 @@ RSpec.describe 'User uploads avatar to profile', feature_category: :user_profile visit user_path(user) - expect(page).to have_selector(%Q(img[src$="/uploads/-/system/user/avatar/#{user.id}/dk.png?width=96"])) + expect(page).to have_selector(%(img[src$="/uploads/-/system/user/avatar/#{user.id}/dk.png?width=96"])) # Cheating here to verify something that isn't user-facing, but is important expect(user.reload.avatar.file).to exist diff --git a/spec/frontend/api/user_api_spec.js b/spec/frontend/api/user_api_spec.js index b2ecfeb8394..a6e08e1cf4b 100644 --- a/spec/frontend/api/user_api_spec.js +++ b/spec/frontend/api/user_api_spec.js @@ -2,6 +2,7 @@ import MockAdapter from 'axios-mock-adapter'; import projects from 'test_fixtures/api/users/projects/get.json'; import followers from 'test_fixtures/api/users/followers/get.json'; +import following from 'test_fixtures/api/users/following/get.json'; import { followUser, unfollowUser, @@ -9,6 +10,7 @@ import { updateUserStatus, getUserProjects, getUserFollowers, + getUserFollowing, } from '~/api/user_api'; import axios from '~/lib/utils/axios_utils'; import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; @@ -131,4 +133,23 @@ describe('~/api/user_api', () => { expect(axiosMock.history.get[0].params).toEqual({ ...params, per_page: DEFAULT_PER_PAGE }); }); }); + + describe('getUserFollowing', () => { + it('calls correct URL and returns expected response', async () => { + const MOCK_USER_ID = 1; + const MOCK_PAGE = 2; + + const expectedUrl = `/api/v4/users/${MOCK_USER_ID}/following`; + const expectedResponse = { data: following }; + const params = { page: MOCK_PAGE }; + + axiosMock.onGet(expectedUrl).replyOnce(HTTP_STATUS_OK, expectedResponse); + + await expect(getUserFollowing(MOCK_USER_ID, params)).resolves.toEqual( + expect.objectContaining({ data: expectedResponse }), + ); + expect(axiosMock.history.get[0].url).toBe(expectedUrl); + expect(axiosMock.history.get[0].params).toEqual({ ...params, per_page: DEFAULT_PER_PAGE }); + }); + }); }); diff --git a/spec/frontend/fixtures/users.rb b/spec/frontend/fixtures/users.rb index 89bffea7e4c..800a9af194e 100644 --- a/spec/frontend/fixtures/users.rb +++ b/spec/frontend/fixtures/users.rb @@ -7,7 +7,8 @@ RSpec.describe 'Users (JavaScript fixtures)', feature_category: :user_profile do include ApiHelpers let_it_be(:followers) { create_list(:user, 5) } - let_it_be(:user) { create(:user, followers: followers) } + let_it_be(:followees) { create_list(:user, 5) } + let_it_be(:user) { create(:user, followers: followers, followees: followees) } describe API::Users, '(JavaScript fixtures)', type: :request do it 'api/users/followers/get.json' do @@ -15,6 +16,12 @@ RSpec.describe 'Users (JavaScript fixtures)', feature_category: :user_profile do expect(response).to be_successful end + + it 'api/users/following/get.json' do + get api("/users/#{user.id}/following", user) + + expect(response).to be_successful + end end describe UsersController, '(JavaScript fixtures)', type: :controller do diff --git a/spec/frontend/notes/components/discussion_counter_spec.js b/spec/frontend/notes/components/discussion_counter_spec.js index ac677841ee1..e52dd87f784 100644 --- a/spec/frontend/notes/components/discussion_counter_spec.js +++ b/spec/frontend/notes/components/discussion_counter_spec.js @@ -1,11 +1,11 @@ -import { GlDropdownItem } from '@gitlab/ui'; +import { GlDisclosureDropdown, GlDisclosureDropdownItem } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; import Vue, { nextTick } from 'vue'; import Vuex from 'vuex'; import DiscussionCounter from '~/notes/components/discussion_counter.vue'; import notesModule from '~/notes/stores/modules'; import * as types from '~/notes/stores/mutation_types'; -import { noteableDataMock, discussionMock, notesDataMock, userDataMock } from '../mock_data'; +import { discussionMock, noteableDataMock, notesDataMock, userDataMock } from '../mock_data'; describe('DiscussionCounter component', () => { let store; @@ -101,9 +101,24 @@ describe('DiscussionCounter component', () => { `('renders correctly if $title', async ({ resolved, groupLength }) => { updateStore({ resolvable: true, resolved }); wrapper = mount(DiscussionCounter, { store, propsData: { blocksMerge: true } }); - await wrapper.find('.dropdown-toggle').trigger('click'); + await wrapper.findComponent(GlDisclosureDropdown).trigger('click'); - expect(wrapper.findAllComponents(GlDropdownItem)).toHaveLength(groupLength); + expect(wrapper.findAllComponents(GlDisclosureDropdownItem)).toHaveLength(groupLength); + }); + + describe('resolve all with new issue link', () => { + it('has correct href prop', async () => { + updateStore({ resolvable: true }); + wrapper = mount(DiscussionCounter, { store, propsData: { blocksMerge: true } }); + + const resolveDiscussionsPath = + store.getters.getNoteableData.create_issue_to_resolve_discussions_path; + + await wrapper.findComponent(GlDisclosureDropdown).trigger('click'); + const resolveAllLink = wrapper.find('[data-testid="resolve-all-with-issue-link"]'); + + expect(resolveAllLink.attributes('href')).toBe(resolveDiscussionsPath); + }); }); }); @@ -114,7 +129,7 @@ describe('DiscussionCounter component', () => { store.commit(types.ADD_OR_UPDATE_DISCUSSIONS, [discussion]); store.dispatch('updateResolvableDiscussionsCounts'); wrapper = mount(DiscussionCounter, { store, propsData: { blocksMerge: true } }); - await wrapper.find('.dropdown-toggle').trigger('click'); + await wrapper.findComponent(GlDisclosureDropdown).trigger('click'); toggleAllButton = wrapper.find('[data-testid="toggle-all-discussions-btn"]'); }; diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/version_row_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/version_row_spec.js index f7c8e909ff6..bc7203f73c9 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/version_row_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/details/version_row_spec.js @@ -1,5 +1,13 @@ -import { GlDropdownItem, GlFormCheckbox, GlIcon, GlLink, GlSprintf, GlTruncate } from '@gitlab/ui'; -import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import { + GlDisclosureDropdown, + GlDisclosureDropdownItem, + GlFormCheckbox, + GlIcon, + GlLink, + GlSprintf, + GlTruncate, +} from '@gitlab/ui'; +import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import ListItem from '~/vue_shared/components/registry/list_item.vue'; import PackageTags from '~/packages_and_registries/shared/components/package_tags.vue'; @@ -24,10 +32,16 @@ describe('VersionRow', () => { const findPackageName = () => wrapper.findComponent(GlTruncate); const findWarningIcon = () => wrapper.findComponent(GlIcon); const findBulkDeleteAction = () => wrapper.findComponent(GlFormCheckbox); - const findDeleteDropdownItem = () => wrapper.findComponent(GlDropdownItem); + const findDeleteDropdownItem = () => wrapper.findComponent(GlDisclosureDropdownItem); - function createComponent({ packageEntity = packageVersion, selected = false } = {}) { - wrapper = shallowMountExtended(VersionRow, { + function createComponent(options = {}) { + const { + mountFn = shallowMountExtended, + packageEntity = packageVersion, + selected = false, + } = options; + + wrapper = mountFn(VersionRow, { propsData: { packageEntity, selected, @@ -35,6 +49,7 @@ describe('VersionRow', () => { stubs: { GlSprintf, GlTruncate, + GlDisclosureDropdown, }, directives: { GlTooltip: createMockDirective('gl-tooltip'), @@ -100,9 +115,7 @@ describe('VersionRow', () => { }); it('renders checkbox in selected state if selected', () => { - createComponent({ - selected: true, - }); + createComponent({ selected: true }); expect(findBulkDeleteAction().attributes('checked')).toBe('true'); expect(findListItem().props('selected')).toBe(true); @@ -116,19 +129,16 @@ describe('VersionRow', () => { expect(findDeleteDropdownItem().exists()).toBe(false); }); - it('exists and has the correct props', () => { + it('exists', () => { createComponent(); expect(findDeleteDropdownItem().exists()).toBe(true); - expect(findDeleteDropdownItem().attributes()).toMatchObject({ - variant: 'danger', - }); }); - it('emits the delete event when the delete button is clicked', () => { - createComponent(); + it('emits the delete event when the delete button is clicked', async () => { + createComponent({ mountFn: mountExtended }); - findDeleteDropdownItem().vm.$emit('click'); + await findDeleteDropdownItem().find('button').trigger('click'); expect(wrapper.emitted('delete')).toHaveLength(1); }); diff --git a/spec/frontend/profile/components/following_tab_spec.js b/spec/frontend/profile/components/following_tab_spec.js index c0583cf4877..1eadb2c7388 100644 --- a/spec/frontend/profile/components/following_tab_spec.js +++ b/spec/frontend/profile/components/following_tab_spec.js @@ -1,32 +1,112 @@ import { GlBadge, GlTab } from '@gitlab/ui'; - +import { shallowMount } from '@vue/test-utils'; +import following from 'test_fixtures/api/users/following/get.json'; import { s__ } from '~/locale'; import FollowingTab from '~/profile/components/following_tab.vue'; -import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import Follow from '~/profile/components/follow.vue'; +import { getUserFollowing } from '~/rest_api'; +import { createAlert } from '~/alert'; +import waitForPromises from 'helpers/wait_for_promises'; + +const MOCK_FOLLOWEES_COUNT = 2; +const MOCK_TOTAL_FOLLOWING = 6; +const MOCK_PAGE = 1; + +jest.mock('~/rest_api'); +jest.mock('~/alert'); describe('FollowingTab', () => { let wrapper; const createComponent = () => { - wrapper = shallowMountExtended(FollowingTab, { + wrapper = shallowMount(FollowingTab, { provide: { - followeesCount: 3, + followeesCount: MOCK_FOLLOWEES_COUNT, + userId: 1, + }, + stubs: { + GlTab, }, }); }; - it('renders `GlTab` and sets title', () => { - createComponent(); + const findGlBadge = () => wrapper.findComponent(GlBadge); + const findFollow = () => wrapper.findComponent(Follow); + + describe('when API request is loading', () => { + beforeEach(() => { + getUserFollowing.mockReturnValueOnce(new Promise(() => {})); + createComponent(); + }); + + it('renders `Follow` component and sets `loading` prop to `true`', () => { + expect(findFollow().props('loading')).toBe(true); + }); + }); + + describe('when API request is successful', () => { + beforeEach(() => { + getUserFollowing.mockResolvedValueOnce({ + data: following, + headers: { 'X-TOTAL': `${MOCK_TOTAL_FOLLOWING}` }, + }); + createComponent(); + }); + + it('renders `GlTab` and sets title', () => { + expect(wrapper.findComponent(GlTab).text()).toContain(s__('UserProfile|Following')); + }); + + it('renders `GlBadge`, sets size and content', () => { + expect(findGlBadge().props('size')).toBe('sm'); + expect(findGlBadge().text()).toBe(`${MOCK_FOLLOWEES_COUNT}`); + }); + + it('renders `Follow` component and passes correct props', () => { + expect(findFollow().props()).toMatchObject({ + users: following, + loading: false, + page: MOCK_PAGE, + totalItems: MOCK_TOTAL_FOLLOWING, + }); + }); + + describe('when `Follow` component emits `pagination-input` event', () => { + it('calls API and updates `users` and `page` props', async () => { + const NEXT_PAGE = MOCK_PAGE + 1; + const NEXT_PAGE_FOLLOWING = [{ id: 999, name: 'page 2 following' }]; - expect(wrapper.findComponent(GlTab).element.textContent).toContain( - s__('UserProfile|Following'), - ); + getUserFollowing.mockResolvedValueOnce({ + data: NEXT_PAGE_FOLLOWING, + headers: { 'X-TOTAL': `${MOCK_TOTAL_FOLLOWING}` }, + }); + + findFollow().vm.$emit('pagination-input', NEXT_PAGE); + + await waitForPromises(); + + expect(findFollow().props()).toMatchObject({ + users: NEXT_PAGE_FOLLOWING, + loading: false, + page: NEXT_PAGE, + totalItems: MOCK_TOTAL_FOLLOWING, + }); + }); + }); }); - it('renders `GlBadge`, sets size and content', () => { - createComponent(); + describe('when API request is not successful', () => { + beforeEach(() => { + getUserFollowing.mockRejectedValueOnce(new Error()); + createComponent(); + }); - expect(wrapper.findComponent(GlBadge).attributes('size')).toBe('sm'); - expect(wrapper.findComponent(GlBadge).element.textContent).toBe('3'); + it('shows error alert', () => { + expect(createAlert).toHaveBeenCalledWith({ + message: FollowingTab.i18n.errorMessage, + error: new Error(), + captureError: true, + }); + }); }); }); diff --git a/spec/graphql/resolvers/echo_resolver_spec.rb b/spec/graphql/resolvers/echo_resolver_spec.rb index 59a121ac7de..02ec7327f74 100644 --- a/spec/graphql/resolvers/echo_resolver_spec.rb +++ b/spec/graphql/resolvers/echo_resolver_spec.rb @@ -14,7 +14,7 @@ RSpec.describe Resolvers::EchoResolver do describe '#resolve' do it 'echoes text and username' do - expect(resolve_echo(text)).to eq %Q("#{current_user.username}" says: #{text}) + expect(resolve_echo(text)).to eq %("#{current_user.username}" says: #{text}) end it 'echoes text and nil as username' do diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb index a9f99f29f6d..562d6683d97 100644 --- a/spec/helpers/markup_helper_spec.rb +++ b/spec/helpers/markup_helper_spec.rb @@ -278,7 +278,7 @@ RSpec.describe MarkupHelper do it 'ignores reference links when they are the entire body' do text = issues[0].to_reference act = helper.link_to_markdown(text, '/foo') - expect(act).to eq %Q(<a href="/foo">#{issues[0].to_reference}</a>) + expect(act).to eq %(<a href="/foo">#{issues[0].to_reference}</a>) end it 'replaces commit message with emoji to link' do diff --git a/spec/lib/banzai/filter/autolink_filter_spec.rb b/spec/lib/banzai/filter/autolink_filter_spec.rb index 2c75377ec42..c8b5a9ffa0b 100644 --- a/spec/lib/banzai/filter/autolink_filter_spec.rb +++ b/spec/lib/banzai/filter/autolink_filter_spec.rb @@ -169,7 +169,7 @@ RSpec.describe Banzai::Filter::AutolinkFilter, feature_category: :team_planning it 'removes one closing punctuation mark when the punctuation in the link is unbalanced' do complicated_link = "(#{link}(a'b[c'd]))'" - expected_complicated_link = %Q{(<a href="#{link}(a'b[c'd]))">#{link}(a'b[c'd]))</a>'} + expected_complicated_link = %{(<a href="#{link}(a'b[c'd]))">#{link}(a'b[c'd]))</a>'} actual = unescape(filter(complicated_link).to_html) expect(actual).to eq(Rinku.auto_link(complicated_link)) @@ -178,7 +178,7 @@ RSpec.describe Banzai::Filter::AutolinkFilter, feature_category: :team_planning it 'does not double-encode HTML entities' do encoded_link = "#{link}?foo=bar&baz=quux" - expected_encoded_link = %Q{<a href="#{encoded_link}">#{encoded_link}</a>} + expected_encoded_link = %{<a href="#{encoded_link}">#{encoded_link}</a>} actual = unescape(filter(encoded_link).to_html) expect(actual).to eq(Rinku.auto_link(encoded_link)) diff --git a/spec/lib/banzai/filter/external_link_filter_spec.rb b/spec/lib/banzai/filter/external_link_filter_spec.rb index de259342998..300b8601dcb 100644 --- a/spec/lib/banzai/filter/external_link_filter_spec.rb +++ b/spec/lib/banzai/filter/external_link_filter_spec.rb @@ -34,7 +34,7 @@ RSpec.describe Banzai::Filter::ExternalLinkFilter, feature_category: :team_plann it 'skips internal links' do internal = Gitlab.config.gitlab.url - exp = act = %Q(<a href="#{internal}/sign_in">Login</a>) + exp = act = %(<a href="#{internal}/sign_in">Login</a>) expect(filter(act).to_html).to eq exp end @@ -90,7 +90,7 @@ RSpec.describe Banzai::Filter::ExternalLinkFilter, feature_category: :team_plann context 'with an impersonated username' do let(:internal) { Gitlab.config.gitlab.url } - let(:doc) { filter %Q(<a href="https://#{internal}@example.com" target="_blank">Reverse Tabnabbing</a>) } + let(:doc) { filter %(<a href="https://#{internal}@example.com" target="_blank">Reverse Tabnabbing</a>) } it_behaves_like 'an external link with rel attribute' end @@ -112,8 +112,8 @@ RSpec.describe Banzai::Filter::ExternalLinkFilter, feature_category: :team_plann it 'skips internal links' do internal_link = Gitlab.config.gitlab.url + "/sign_in" url = internal_link.gsub(/\Ahttp/, 'HtTp') - act = %Q(<a href="#{url}">Login</a>) - exp = %Q(<a href="#{internal_link}">Login</a>) + act = %(<a href="#{url}">Login</a>) + exp = %(<a href="#{internal_link}">Login</a>) expect(filter(act).to_html).to eq(exp) end @@ -131,7 +131,7 @@ RSpec.describe Banzai::Filter::ExternalLinkFilter, feature_category: :team_plann context 'links with RTLO character' do # In rendered text this looks like "http://example.com/evilexe.mp3" - let(:doc) { filter %Q(<a href="http://example.com/evil%E2%80%AE3pm.exe">http://example.com/evil\u202E3pm.exe</a>) } + let(:doc) { filter %(<a href="http://example.com/evil%E2%80%AE3pm.exe">http://example.com/evil\u202E3pm.exe</a>) } it_behaves_like 'an external link with rel attribute' @@ -142,7 +142,7 @@ RSpec.describe Banzai::Filter::ExternalLinkFilter, feature_category: :team_plann end it 'does not mangle the link text' do - doc = filter %Q(<a href="http://example.com">One<span>and</span>\u202Eexe.mp3</a>) + doc = filter %(<a href="http://example.com">One<span>and</span>\u202Eexe.mp3</a>) expect(doc.to_html).to include('One<span>and</span>%E2%80%AEexe.mp3</a>') end diff --git a/spec/lib/banzai/filter/image_link_filter_spec.rb b/spec/lib/banzai/filter/image_link_filter_spec.rb index 2d496c447e1..5238f05f4bd 100644 --- a/spec/lib/banzai/filter/image_link_filter_spec.rb +++ b/spec/lib/banzai/filter/image_link_filter_spec.rb @@ -9,8 +9,8 @@ RSpec.describe Banzai::Filter::ImageLinkFilter, feature_category: :team_planning let(:context) { {} } def image(path, alt: nil, data_src: nil) - alt_tag = alt ? %Q{alt="#{alt}"} : "" - data_src_tag = data_src ? %Q{data-src="#{data_src}"} : "" + alt_tag = alt ? %{alt="#{alt}"} : "" + data_src_tag = data_src ? %{data-src="#{data_src}"} : "" %(<img src="#{path}" #{alt_tag} #{data_src_tag} />) end @@ -22,7 +22,7 @@ RSpec.describe Banzai::Filter::ImageLinkFilter, feature_category: :team_planning end it 'does not wrap a duplicate link' do - doc = filter(%Q(<a href="/whatever">#{image(path)}</a>), context) + doc = filter(%(<a href="/whatever">#{image(path)}</a>), context) expect(doc.to_html).to match %r{^<a href="/whatever"><img[^>]*></a>$} end @@ -34,7 +34,7 @@ RSpec.describe Banzai::Filter::ImageLinkFilter, feature_category: :team_planning end it 'works with inline images' do - doc = filter(%Q(<p>test #{image(path)} inline</p>), context) + doc = filter(%(<p>test #{image(path)} inline</p>), context) expect(doc.to_html).to match %r{^<p>test <a[^>]*><img[^>]*></a> inline</p>$} end diff --git a/spec/lib/banzai/filter/references/label_reference_filter_spec.rb b/spec/lib/banzai/filter/references/label_reference_filter_spec.rb index f8d223c6611..91b051d71ec 100644 --- a/spec/lib/banzai/filter/references/label_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/references/label_reference_filter_spec.rb @@ -344,7 +344,7 @@ RSpec.describe Banzai::Filter::References::LabelReferenceFilter, feature_categor end describe 'referencing a label in a link href' do - let(:reference) { %Q{<a href="#{label.to_reference}">Label</a>} } + let(:reference) { %{<a href="#{label.to_reference}">Label</a>} } it 'links to a valid reference' do doc = reference_filter("See #{reference}") diff --git a/spec/lib/banzai/filter/references/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/references/milestone_reference_filter_spec.rb index ecd5d1368c9..7caa6efff66 100644 --- a/spec/lib/banzai/filter/references/milestone_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/references/milestone_reference_filter_spec.rb @@ -132,7 +132,7 @@ RSpec.describe Banzai::Filter::References::MilestoneReferenceFilter, feature_cat shared_examples 'referencing a milestone in a link href' do let(:unquoted_reference) { "#{Milestone.reference_prefix}#{milestone.name}" } - let(:link_reference) { %Q{<a href="#{unquoted_reference}">Milestone</a>} } + let(:link_reference) { %{<a href="#{unquoted_reference}">Milestone</a>} } before do milestone.update!(name: 'gfm') @@ -169,7 +169,7 @@ RSpec.describe Banzai::Filter::References::MilestoneReferenceFilter, feature_cat shared_examples 'linking to a milestone as the entire link' do let(:unquoted_reference) { "#{Milestone.reference_prefix}#{milestone.name}" } let(:link) { urls.milestone_url(milestone) } - let(:link_reference) { %Q{<a href="#{link}">#{link}</a>} } + let(:link_reference) { %{<a href="#{link}">#{link}</a>} } it 'replaces the link text with the milestone reference' do doc = reference_filter("See #{link}") diff --git a/spec/lib/banzai/pipeline/full_pipeline_spec.rb b/spec/lib/banzai/pipeline/full_pipeline_spec.rb index 5d56035f6df..6ef03b58f67 100644 --- a/spec/lib/banzai/pipeline/full_pipeline_spec.rb +++ b/spec/lib/banzai/pipeline/full_pipeline_spec.rb @@ -28,7 +28,7 @@ RSpec.describe Banzai::Pipeline::FullPipeline, feature_category: :team_planning end it 'escapes the data-original attribute on a reference' do - markdown = %Q{[">bad things](#{issue.to_reference})} + markdown = %{[">bad things](#{issue.to_reference})} result = described_class.to_html(markdown, project: project) expect(result).to include(%{data-original='\"&gt;bad things'}) end diff --git a/spec/lib/banzai/pipeline/incident_management/timeline_event_pipeline_spec.rb b/spec/lib/banzai/pipeline/incident_management/timeline_event_pipeline_spec.rb index 12a6be6bc18..9e79be4333a 100644 --- a/spec/lib/banzai/pipeline/incident_management/timeline_event_pipeline_spec.rb +++ b/spec/lib/banzai/pipeline/incident_management/timeline_event_pipeline_spec.rb @@ -73,7 +73,7 @@ RSpec.describe Banzai::Pipeline::IncidentManagement::TimelineEventPipeline do context 'when markdown contains labels' do let(:label) { create(:label, project: project, title: 'backend') } - let(:markdown) { %Q(~"#{label.name}" ~unknown) } + let(:markdown) { %(~"#{label.name}" ~unknown) } it 'replaces existing label to a link' do # rubocop:disable Layout/LineLength diff --git a/spec/lib/banzai/pipeline/plain_markdown_pipeline_spec.rb b/spec/lib/banzai/pipeline/plain_markdown_pipeline_spec.rb index 8ff0fa3ae1e..ae01939605e 100644 --- a/spec/lib/banzai/pipeline/plain_markdown_pipeline_spec.rb +++ b/spec/lib/banzai/pipeline/plain_markdown_pipeline_spec.rb @@ -64,9 +64,9 @@ RSpec.describe Banzai::Pipeline::PlainMarkdownPipeline, feature_category: :team_ describe 'backslash escapes are untouched in code blocks, code spans, autolinks, or raw HTML' do where(:markdown, :expected) do %q(`` \@\! ``) | %q(<code>\@\!</code>) - %q( \@\!) | %Q(<code>\\@\\!\n</code>) - %Q(~~~\n\\@\\!\n~~~) | %Q(<code>\\@\\!\n</code>) - %q($1+\$2$) | %q(<code data-math-style="inline">1+\\$2</code>) + %q( \@\!) | %(<code>\\@\\!\n</code>) + %(~~~\n\\@\\!\n~~~) | %(<code>\\@\\!\n</code>) + %q($1+\$2$) | %q(<code data-math-style="inline">1+\\$2</code>) %q(<http://example.com?find=\@>) | %q(<a href="http://example.com?find=%5C@">http://example.com?find=\@</a>) %q[<a href="/bar\@)">] | %q[<a href="/bar\@)">] end @@ -77,15 +77,15 @@ RSpec.describe Banzai::Pipeline::PlainMarkdownPipeline, feature_category: :team_ end describe 'work in all other contexts, including URLs and link titles, link references, and info strings in fenced code blocks' do - let(:markdown) { %Q(``` foo\\@bar\nfoo\n```) } + let(:markdown) { %(``` foo\\@bar\nfoo\n```) } it 'renders correct html' do - correct_html_included(markdown, %Q(<pre lang="foo@bar"><code>foo\n</code></pre>)) + correct_html_included(markdown, %(<pre lang="foo@bar"><code>foo\n</code></pre>)) end where(:markdown, :expected) do - %q![foo](/bar\@ "\@title")! | %q(<a href="/bar@" title="@title">foo</a>) - %Q![foo]\n\n[foo]: /bar\\@ "\\@title"! | %q(<a href="/bar@" title="@title">foo</a>) + %q![foo](/bar\@ "\@title")! | %q(<a href="/bar@" title="@title">foo</a>) + %![foo]\n\n[foo]: /bar\\@ "\\@title"! | %q(<a href="/bar@" title="@title">foo</a>) end with_them do diff --git a/spec/lib/banzai/reference_parser/issue_parser_spec.rb b/spec/lib/banzai/reference_parser/issue_parser_spec.rb index 2efdb928b6f..072df6a23aa 100644 --- a/spec/lib/banzai/reference_parser/issue_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/issue_parser_spec.rb @@ -136,7 +136,7 @@ RSpec.describe Banzai::ReferenceParser::IssueParser, feature_category: :team_pla end def issue_link(issue) - Nokogiri::HTML.fragment(%Q{<a data-issue="#{issue.id}"></a>}).children[0] + Nokogiri::HTML.fragment(%{<a data-issue="#{issue.id}"></a>}).children[0] end before do diff --git a/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb b/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb index eead5019217..bab535b67bf 100644 --- a/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb @@ -61,7 +61,7 @@ RSpec.describe Banzai::ReferenceParser::MergeRequestParser, feature_category: :c end def merge_request_link(merge_request) - Nokogiri::HTML.fragment(%Q{<a data-project="#{merge_request.project_id}" data-merge-request="#{merge_request.id}"></a>}).children[0] + Nokogiri::HTML.fragment(%{<a data-project="#{merge_request.project_id}" data-merge-request="#{merge_request.id}"></a>}).children[0] end before do diff --git a/spec/lib/gitlab/cache/client_spec.rb b/spec/lib/gitlab/cache/client_spec.rb index 638fed1a905..6543381b86a 100644 --- a/spec/lib/gitlab/cache/client_spec.rb +++ b/spec/lib/gitlab/cache/client_spec.rb @@ -3,52 +3,32 @@ require 'spec_helper' RSpec.describe Gitlab::Cache::Client, feature_category: :source_code_management do - subject(:client) { described_class.new(metadata, backend: backend) } + subject(:client) { described_class.new(metrics, backend: backend) } + let(:metrics) { Gitlab::Cache::Metrics.new(metadata) } let(:backend) { Rails.cache } - let(:metadata) do - Gitlab::Cache::Metadata.new( - cache_identifier: cache_identifier, - feature_category: feature_category, - backing_resource: backing_resource - ) - end let(:cache_identifier) { 'MyClass#cache' } let(:feature_category) { :source_code_management } let(:backing_resource) { :cpu } - let(:metadata_mock) do + let(:metadata) do Gitlab::Cache::Metadata.new( cache_identifier: cache_identifier, - feature_category: feature_category + feature_category: feature_category, + backing_resource: backing_resource ) end - let(:metrics_mock) { Gitlab::Cache::Metrics.new(metadata_mock) } - - describe '.build_with_metadata' do - it 'builds a cache client with metrics support' do - attributes = { - cache_identifier: cache_identifier, - feature_category: feature_category, - backing_resource: backing_resource - } - - instance = described_class.build_with_metadata(**attributes) - - expect(instance).to be_a(described_class) - expect(instance.metadata).to have_attributes(**attributes) - end + let(:labels) do + { + feature_category: :audit_events + } end describe 'Methods', :use_clean_rails_memory_store_caching do let(:expected_key) { 'key' } - before do - allow(Gitlab::Cache::Metrics).to receive(:new).and_return(metrics_mock) - end - describe '#read' do context 'when key does not exist' do it 'returns nil' do @@ -56,9 +36,9 @@ RSpec.describe Gitlab::Cache::Client, feature_category: :source_code_management end it 'increments cache miss' do - expect(metrics_mock).to receive(:increment_cache_miss) + expect(metrics).to receive(:increment_cache_miss).with(labels).and_call_original - client.read('key') + expect(client.read('key', nil, labels)).to be_nil end end @@ -72,9 +52,9 @@ RSpec.describe Gitlab::Cache::Client, feature_category: :source_code_management end it 'increments cache hit' do - expect(metrics_mock).to receive(:increment_cache_hit) + expect(metrics).to receive(:increment_cache_hit).with(labels) - client.read('key') + expect(client.read('key', nil, labels)).to eq('value') end end end @@ -125,13 +105,13 @@ RSpec.describe Gitlab::Cache::Client, feature_category: :source_code_management end it 'increments a cache hit' do - expect(metrics_mock).to receive(:increment_cache_hit) + expect(metrics).to receive(:increment_cache_hit).with(labels) - client.fetch('key') + expect(client.fetch('key', nil, labels)).to eq('value') end it 'does not measure the cache generation time' do - expect(metrics_mock).not_to receive(:observe_cache_generation) + expect(metrics).not_to receive(:observe_cache_generation) client.fetch('key') { 'new-value' } end @@ -145,15 +125,15 @@ RSpec.describe Gitlab::Cache::Client, feature_category: :source_code_management end it 'increments a cache miss' do - expect(metrics_mock).to receive(:increment_cache_miss) + expect(metrics).to receive(:increment_cache_miss).with(labels) - client.fetch('key') + expect(client.fetch('key', nil, labels) { 'value' }).to eq('value') end it 'measures the cache generation time' do - expect(metrics_mock).to receive(:observe_cache_generation) + expect(metrics).to receive(:observe_cache_generation).with(labels).and_call_original - client.fetch('key') { 'value' } + expect(client.fetch('key', nil, labels) { 'value' }).to eq('value') end end end diff --git a/spec/lib/gitlab/cache/metadata_spec.rb b/spec/lib/gitlab/cache/metadata_spec.rb index d2b79fb8b08..29c30d526ad 100644 --- a/spec/lib/gitlab/cache/metadata_spec.rb +++ b/spec/lib/gitlab/cache/metadata_spec.rb @@ -19,11 +19,11 @@ RSpec.describe Gitlab::Cache::Metadata, feature_category: :source_code_managemen context 'when optional arguments are not set' do it 'sets default value for them' do attributes = described_class.new( - cache_identifier: cache_identifier, feature_category: feature_category ) expect(attributes.backing_resource).to eq(:unknown) + expect(attributes.cache_identifier).to be_nil end end diff --git a/spec/lib/gitlab/cache/metrics_spec.rb b/spec/lib/gitlab/cache/metrics_spec.rb index 76ec0dbfa0b..c46533371d3 100644 --- a/spec/lib/gitlab/cache/metrics_spec.rb +++ b/spec/lib/gitlab/cache/metrics_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Cache::Metrics do +RSpec.describe Gitlab::Cache::Metrics, feature_category: :source_code_management do subject(:metrics) { described_class.new(metadata) } let(:metadata) do @@ -23,12 +23,19 @@ RSpec.describe Gitlab::Cache::Metrics do allow(Gitlab::Metrics).to receive(:counter) .with( :redis_hit_miss_operations_total, - 'Hit/miss Redis cache counter' + 'Hit/miss Redis cache counter', + { + cache_identifier: cache_identifier, + feature_category: feature_category, + backing_resource: backing_resource + } ).and_return(counter_mock) end describe '#increment_cache_hit' do - subject { metrics.increment_cache_hit } + subject { metrics.increment_cache_hit(labels) } + + let(:labels) { {} } it 'increments number of hits' do expect(counter_mock) @@ -44,10 +51,31 @@ RSpec.describe Gitlab::Cache::Metrics do subject end + + context 'when labels redefine defaults' do + let(:labels) { { backing_resource: :gitaly } } + + it 'increments number of hits' do + expect(counter_mock) + .to receive(:increment) + .with( + { + backing_resource: :gitaly, + cache_identifier: cache_identifier, + feature_category: feature_category, + cache_hit: true + } + ).once + + subject + end + end end describe '#increment_cache_miss' do - subject { metrics.increment_cache_miss } + subject { metrics.increment_cache_miss(labels) } + + let(:labels) { {} } it 'increments number of misses' do expect(counter_mock) @@ -63,22 +91,40 @@ RSpec.describe Gitlab::Cache::Metrics do subject end + + context 'when labels redefine defaults' do + let(:labels) { { backing_resource: :gitaly } } + + it 'increments number of misses' do + expect(counter_mock) + .to receive(:increment) + .with( + { + backing_resource: :gitaly, + cache_identifier: cache_identifier, + feature_category: feature_category, + cache_hit: false + } + ).once + + subject + end + end end describe '#observe_cache_generation' do subject do - metrics.observe_cache_generation { action } + metrics.observe_cache_generation(labels) { action } end let(:action) { 'action' } let(:histogram_mock) { instance_double(Prometheus::Client::Histogram) } + let(:labels) { {} } before do allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(100.0, 500.0) - end - it 'updates histogram metric' do - expect(Gitlab::Metrics).to receive(:histogram).with( + allow(Gitlab::Metrics).to receive(:histogram).with( :redis_cache_generation_duration_seconds, 'Duration of Redis cache generation', { @@ -88,10 +134,36 @@ RSpec.describe Gitlab::Cache::Metrics do }, [0, 1, 5] ).and_return(histogram_mock) + end - expect(histogram_mock).to receive(:observe).with({}, 400.0) + it 'updates histogram metric' do + expect(histogram_mock).to receive(:observe).with( + { + cache_identifier: cache_identifier, + feature_category: feature_category, + backing_resource: backing_resource + }, + 400.0 + ) is_expected.to eq(action) end + + context 'when labels redefine defaults' do + let(:labels) { { backing_resource: :gitaly } } + + it 'updates histogram metric' do + expect(histogram_mock).to receive(:observe).with( + { + cache_identifier: cache_identifier, + feature_category: feature_category, + backing_resource: :gitaly + }, + 400.0 + ) + + is_expected.to eq(action) + end + end end end diff --git a/spec/lib/gitlab/diff/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb index 233dddbdad7..e39c15c8fd7 100644 --- a/spec/lib/gitlab/diff/highlight_spec.rb +++ b/spec/lib/gitlab/diff/highlight_spec.rb @@ -38,19 +38,19 @@ RSpec.describe Gitlab::Diff::Highlight, feature_category: :source_code_managemen end it 'highlights and marks unchanged lines' do - code = %Q{ <span id="LC7" class="line" lang="ruby"> <span class="k">def</span> <span class="nf">popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span></span>\n} + code = %{ <span id="LC7" class="line" lang="ruby"> <span class="k">def</span> <span class="nf">popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span></span>\n} expect(subject[2].rich_text).to eq(code) end it 'highlights and marks removed lines' do - code = %Q{-<span id="LC9" class="line" lang="ruby"> <span class="k">raise</span> <span class="s2">"System commands must be given as an array of strings"</span></span>\n} + code = %{-<span id="LC9" class="line" lang="ruby"> <span class="k">raise</span> <span class="s2">"System commands must be given as an array of strings"</span></span>\n} expect(subject[4].rich_text).to eq(code) end it 'highlights and marks added lines' do - code = %Q{+<span id="LC9" class="line" lang="ruby"> <span class="k">raise</span> <span class="no"><span class="idiff left addition">RuntimeError</span></span><span class="p"><span class="idiff addition">,</span></span><span class="idiff right addition"> </span><span class="s2">"System commands must be given as an array of strings"</span></span>\n} + code = %{+<span id="LC9" class="line" lang="ruby"> <span class="k">raise</span> <span class="no"><span class="idiff left addition">RuntimeError</span></span><span class="p"><span class="idiff addition">,</span></span><span class="idiff right addition"> </span><span class="s2">"System commands must be given as an array of strings"</span></span>\n} expect(subject[5].rich_text).to eq(code) end @@ -135,7 +135,7 @@ RSpec.describe Gitlab::Diff::Highlight, feature_category: :source_code_managemen it 'blobs are highlighted as plain text without loading all data' do expect(diff_file.blob).not_to receive(:load_all_data!) - expect(subject[2].rich_text).to eq(%Q{ <span id="LC7" class="line" lang=""> def popen(cmd, path=nil)</span>\n}) + expect(subject[2].rich_text).to eq(%{ <span id="LC7" class="line" lang=""> def popen(cmd, path=nil)</span>\n}) expect(subject[2].rich_text).to be_html_safe end end diff --git a/spec/lib/gitlab/error_tracking/stack_trace_highlight_decorator_spec.rb b/spec/lib/gitlab/error_tracking/stack_trace_highlight_decorator_spec.rb index 73ebee49169..a854adca32b 100644 --- a/spec/lib/gitlab/error_tracking/stack_trace_highlight_decorator_spec.rb +++ b/spec/lib/gitlab/error_tracking/stack_trace_highlight_decorator_spec.rb @@ -28,7 +28,7 @@ RSpec.describe Gitlab::ErrorTracking::StackTraceHighlightDecorator do [11, '<span id="LC1" class="line" lang="ruby"><span class="k">class</span> <span class="nc">HelloWorld</span></span>'], [12, '<span id="LC1" class="line" lang="ruby"> <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">message</span></span>'], [13, '<span id="LC1" class="line" lang="ruby"> <span class="vi">@name</span> <span class="o">=</span> <span class="s1">\'World\'</span></span>'], - [14, %Q[<span id="LC1" class="line" lang="ruby"> <span class="nb">puts</span> <span class="s2">"Hello </span><span class="si">\#{</span><span class="vi">@name</span><span class="si">}</span><span class="s2">"</span></span>]], + [14, %[<span id="LC1" class="line" lang="ruby"> <span class="nb">puts</span> <span class="s2">"Hello </span><span class="si">\#{</span><span class="vi">@name</span><span class="si">}</span><span class="s2">"</span></span>]], [15, '<span id="LC1" class="line" lang="ruby"> <span class="k">end</span></span>'], [16, '<span id="LC1" class="line" lang="ruby"><span class="k">end</span></span>'] ] diff --git a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb index 8bb649e78e0..6de7cab9c42 100644 --- a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb +++ b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb @@ -78,13 +78,13 @@ RSpec.describe Gitlab::Gfm::ReferenceRewriter do context 'label referenced by id' do let(:text) { '#1 and ~123' } - it { is_expected.to eq %Q{#{old_project_ref}#1 and #{old_project_ref}~123} } + it { is_expected.to eq %{#{old_project_ref}#1 and #{old_project_ref}~123} } end context 'label referenced by text' do let(:text) { '#1 and ~"test"' } - it { is_expected.to eq %Q{#{old_project_ref}#1 and #{old_project_ref}~123} } + it { is_expected.to eq %{#{old_project_ref}#1 and #{old_project_ref}~123} } end end @@ -99,13 +99,13 @@ RSpec.describe Gitlab::Gfm::ReferenceRewriter do context 'label referenced by id' do let(:text) { '#1 and ~321' } - it { is_expected.to eq %Q{#{old_project_ref}#1 and #{old_project_ref}~321} } + it { is_expected.to eq %{#{old_project_ref}#1 and #{old_project_ref}~321} } end context 'label referenced by text' do let(:text) { '#1 and ~"group label"' } - it { is_expected.to eq %Q{#{old_project_ref}#1 and #{old_project_ref}~321} } + it { is_expected.to eq %{#{old_project_ref}#1 and #{old_project_ref}~321} } end end end @@ -149,7 +149,7 @@ RSpec.describe Gitlab::Gfm::ReferenceRewriter do let(:text) { 'milestone: %"9.0"' } - it { is_expected.to eq %Q[milestone: #{old_project_ref}%"9.0"] } + it { is_expected.to eq %[milestone: #{old_project_ref}%"9.0"] } end context 'when referring to group milestone' do diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb index d7ae6ed06a4..173131b1d5c 100644 --- a/spec/lib/gitlab/highlight_spec.rb +++ b/spec/lib/gitlab/highlight_spec.rb @@ -34,7 +34,7 @@ RSpec.describe Gitlab::Highlight do end it 'highlights' do - expected = %Q[<span id="LC1" class="line" lang="common_lisp"><span class="p">(</span><span class="nb">make-pathname</span> <span class="ss">:defaults</span> <span class="nv">name</span></span> + expected = %[<span id="LC1" class="line" lang="common_lisp"><span class="p">(</span><span class="nb">make-pathname</span> <span class="ss">:defaults</span> <span class="nv">name</span></span> <span id="LC2" class="line" lang="common_lisp"><span class="ss">:type</span> <span class="s">"assem"</span><span class="p">)</span></span>] expect(described_class.highlight(file_name, content)).to eq(expected) diff --git a/spec/lib/gitlab/pagination/gitaly_keyset_pager_spec.rb b/spec/lib/gitlab/pagination/gitaly_keyset_pager_spec.rb index b5ed583b1f1..2e87c582040 100644 --- a/spec/lib/gitlab/pagination/gitaly_keyset_pager_spec.rb +++ b/spec/lib/gitlab/pagination/gitaly_keyset_pager_spec.rb @@ -106,7 +106,7 @@ RSpec.describe Gitlab::Pagination::GitalyKeysetPager do context 'when next page could be available' do let(:branches) { [branch1, branch2] } - let(:expected_next_page_link) { %Q(<#{incoming_api_projects_url}?#{query.merge(page_token: branch2.name).to_query}>; rel="next") } + let(:expected_next_page_link) { %(<#{incoming_api_projects_url}?#{query.merge(page_token: branch2.name).to_query}>; rel="next") } it 'uses keyset pagination and adds link headers' do expect(request_context).to receive(:header).with('Link', expected_next_page_link) diff --git a/spec/lib/gitlab/pagination/offset_pagination_spec.rb b/spec/lib/gitlab/pagination/offset_pagination_spec.rb index dc32f471756..836b3cb55d6 100644 --- a/spec/lib/gitlab/pagination/offset_pagination_spec.rb +++ b/spec/lib/gitlab/pagination/offset_pagination_spec.rb @@ -44,9 +44,9 @@ RSpec.describe Gitlab::Pagination::OffsetPagination do expect_header('X-Prev-Page', '') expect_header('Link', anything) do |_key, val| - expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) - expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="last")) - expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="next")) + expect(val).to include(%(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) + expect(val).to include(%(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="last")) + expect(val).to include(%(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="next")) expect(val).not_to include('rel="prev"') end @@ -91,8 +91,8 @@ RSpec.describe Gitlab::Pagination::OffsetPagination do expect_header('X-Prev-Page', '') expect_header('Link', anything) do |_key, val| - expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) - expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="next")) + expect(val).to include(%(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) + expect(val).to include(%(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="next")) expect(val).not_to include('rel="last"') expect(val).not_to include('rel="prev"') end @@ -113,8 +113,8 @@ RSpec.describe Gitlab::Pagination::OffsetPagination do expect_header('X-Prev-Page', '') expect_header('Link', anything) do |_key, val| - expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) - expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="next")) + expect(val).to include(%(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) + expect(val).to include(%(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="next")) expect(val).not_to include('rel="last"') expect(val).not_to include('rel="prev"') end @@ -242,9 +242,9 @@ RSpec.describe Gitlab::Pagination::OffsetPagination do expect_header('X-Prev-Page', '1') expect_header('Link', anything) do |_key, val| - expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) - expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="last")) - expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="prev")) + expect(val).to include(%(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) + expect(val).to include(%(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="last")) + expect(val).to include(%(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="prev")) expect(val).not_to include('rel="next"') end @@ -291,8 +291,8 @@ RSpec.describe Gitlab::Pagination::OffsetPagination do expect_header('X-Prev-Page', '') expect_header('Link', anything) do |_key, val| - expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) - expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="last")) + expect(val).to include(%(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) + expect(val).to include(%(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="last")) expect(val).not_to include('rel="prev"') expect(val).not_to include('rel="next"') expect(val).not_to include('page=0') diff --git a/spec/lib/gitlab/prometheus/query_variables_spec.rb b/spec/lib/gitlab/prometheus/query_variables_spec.rb index d9cac3e1064..d0947eef2d9 100644 --- a/spec/lib/gitlab/prometheus/query_variables_spec.rb +++ b/spec/lib/gitlab/prometheus/query_variables_spec.rb @@ -20,7 +20,7 @@ RSpec.describe Gitlab::Prometheus::QueryVariables do it do is_expected.to include(environment_filter: - %Q[container_name!="POD",environment="#{slug}"]) + %[container_name!="POD",environment="#{slug}"]) end context 'without deployment platform' do diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb index 62fcb4821fc..37db13b76b9 100644 --- a/spec/lib/gitlab/reference_extractor_spec.rb +++ b/spec/lib/gitlab/reference_extractor_spec.rb @@ -31,7 +31,7 @@ RSpec.describe Gitlab::ReferenceExtractor do project.add_reporter(@u_foo) project.add_reporter(@u_bar) - subject.analyze(%Q{ + subject.analyze(%{ Inline code: `@foo` Code block: diff --git a/spec/lib/gitlab/url_sanitizer_spec.rb b/spec/lib/gitlab/url_sanitizer_spec.rb index c02cbef8328..5f76c1de5b1 100644 --- a/spec/lib/gitlab/url_sanitizer_spec.rb +++ b/spec/lib/gitlab/url_sanitizer_spec.rb @@ -8,7 +8,7 @@ RSpec.describe Gitlab::UrlSanitizer do describe '.sanitize' do def sanitize_url(url) # We want to try with multi-line content because is how error messages are formatted - described_class.sanitize(%Q{ + described_class.sanitize(%{ remote: Not Found fatal: repository `#{url}` not found }) diff --git a/spec/mailers/emails/releases_spec.rb b/spec/mailers/emails/releases_spec.rb index e8ca9533256..4c92762e624 100644 --- a/spec/mailers/emails/releases_spec.rb +++ b/spec/mailers/emails/releases_spec.rb @@ -57,7 +57,7 @@ RSpec.describe Emails::Releases do let(:release) { create(:release, project: project, description: "Attachment: [Test file](#{upload_path})") } it 'renders absolute links' do - is_expected.to have_body_text(%Q(<a href="#{project.web_url}#{upload_path}" data-canonical-src="#{upload_path}" data-link="true" class="gfm">Test file</a>)) + is_expected.to have_body_text(%(<a href="#{project.web_url}#{upload_path}" data-canonical-src="#{upload_path}" data-link="true" class="gfm">Test file</a>)) end end end diff --git a/spec/mailers/emails/service_desk_spec.rb b/spec/mailers/emails/service_desk_spec.rb index 22b910b3dae..8c0efe3f480 100644 --- a/spec/mailers/emails/service_desk_spec.rb +++ b/spec/mailers/emails/service_desk_spec.rb @@ -340,8 +340,8 @@ RSpec.describe Emails::ServiceDesk, feature_category: :service_desk do end end - let_it_be(:expected_html) { %Q(a new comment with <a href="#{project.web_url}#{upload_path}" data-canonical-src="#{upload_path}" data-link="true" class="gfm">#{filename}</a>) } - let_it_be(:expected_template_html) { %Q(some text #{expected_html}) } + let_it_be(:expected_html) { %(a new comment with <a href="#{project.web_url}#{upload_path}" data-canonical-src="#{upload_path}" data-link="true" class="gfm">#{filename}</a>) } + let_it_be(:expected_template_html) { %(some text #{expected_html}) } it_behaves_like 'a service desk notification email' it_behaves_like 'a service desk notification email with template content', 'new_note' @@ -357,7 +357,7 @@ RSpec.describe Emails::ServiceDesk, feature_category: :service_desk do end context 'when upload name is not changed in markdown' do - let_it_be(:expected_template_html) { %Q(some text a new comment with <strong>#{filename}</strong>) } + let_it_be(:expected_template_html) { %(some text a new comment with <strong>#{filename}</strong>) } it_behaves_like 'a service desk notification email', 1 it_behaves_like 'a service desk notification email with template content', 'new_note', 1 @@ -366,9 +366,9 @@ RSpec.describe Emails::ServiceDesk, feature_category: :service_desk do context 'when upload name is changed in markdown' do let_it_be(:upload_name_in_markdown) { 'Custom name' } let_it_be(:note) { create(:note_on_issue, noteable: issue, project: project, note: "a new comment with [#{upload_name_in_markdown}](#{upload_path})") } - let_it_be(:expected_text) { %Q(a new comment with [#{upload_name_in_markdown}](#{upload_path})) } - let_it_be(:expected_html) { %Q(a new comment with <strong>#{upload_name_in_markdown} (#{filename})</strong>) } - let_it_be(:expected_template_html) { %Q(some text #{expected_html}) } + let_it_be(:expected_text) { %(a new comment with [#{upload_name_in_markdown}](#{upload_path})) } + let_it_be(:expected_html) { %(a new comment with <strong>#{upload_name_in_markdown} (#{filename})</strong>) } + let_it_be(:expected_template_html) { %(some text #{expected_html}) } it_behaves_like 'a service desk notification email', 1 it_behaves_like 'a service desk notification email with template content', 'new_note', 1 @@ -392,16 +392,16 @@ RSpec.describe Emails::ServiceDesk, feature_category: :service_desk do let_it_be(:upload_1) { create(:upload, :issuable_upload, :with_file, model: note.project, path: path_1, secret: secret_1) } - let_it_be(:expected_html) { %Q(a new comment with <strong>#{filename}</strong> <strong>#{filename_1}</strong>) } - let_it_be(:expected_template_html) { %Q(some text #{expected_html}) } + let_it_be(:expected_html) { %(a new comment with <strong>#{filename}</strong> <strong>#{filename_1}</strong>) } + let_it_be(:expected_template_html) { %(some text #{expected_html}) } it_behaves_like 'a service desk notification email', 2 it_behaves_like 'a service desk notification email with template content', 'new_note', 2 end context 'when not all uploads processed correct' do - let_it_be(:expected_html) { %Q(a new comment with <strong>#{filename}</strong> <a href="#{project.web_url}#{upload_path_1}" data-canonical-src="#{upload_path_1}" data-link="true" class="gfm">#{filename_1}</a>) } - let_it_be(:expected_template_html) { %Q(some text #{expected_html}) } + let_it_be(:expected_html) { %(a new comment with <strong>#{filename}</strong> <a href="#{project.web_url}#{upload_path_1}" data-canonical-src="#{upload_path_1}" data-link="true" class="gfm">#{filename_1}</a>) } + let_it_be(:expected_template_html) { %(some text #{expected_html}) } it_behaves_like 'a service desk notification email', 1 it_behaves_like 'a service desk notification email with template content', 'new_note', 1 @@ -417,7 +417,7 @@ RSpec.describe Emails::ServiceDesk, feature_category: :service_desk do expect(Gitlab::ErrorTracking).to receive(:track_exception).with(StandardError, project_id: note.project_id) end - let_it_be(:expected_template_html) { %Q(some text a new comment with <a href="#{project.web_url}#{upload_path}" data-canonical-src="#{upload_path}" data-link="true" class="gfm">#{filename}</a>) } + let_it_be(:expected_template_html) { %(some text a new comment with <a href="#{project.web_url}#{upload_path}" data-canonical-src="#{upload_path}" data-link="true" class="gfm">#{filename}</a>) } it_behaves_like 'a service desk notification email with template content', 'new_note' end @@ -430,7 +430,7 @@ RSpec.describe Emails::ServiceDesk, feature_category: :service_desk do expect(Gitlab::ErrorTracking).to receive(:track_exception).with(StandardError, project_id: note.project_id) end - let_it_be(:expected_template_html) { %Q(some text a new comment with <a href="#{project.web_url}#{upload_path}" data-canonical-src="#{upload_path}" data-link="true" class="gfm">#{filename}</a>) } + let_it_be(:expected_template_html) { %(some text a new comment with <a href="#{project.web_url}#{upload_path}" data-canonical-src="#{upload_path}" data-link="true" class="gfm">#{filename}</a>) } it_behaves_like 'a service desk notification email with template content', 'new_note' end diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb index ede96d79656..9c8ac45c405 100644 --- a/spec/models/deployment_spec.rb +++ b/spec/models/deployment_spec.rb @@ -1285,7 +1285,7 @@ RSpec.describe Deployment, feature_category: :continuous_delivery do let(:build_status) { :created } it_behaves_like 'gracefully handling error' do - let(:error_message) { %Q{Status cannot transition via \"create\"} } + let(:error_message) { %{Status cannot transition via \"create\"} } end end @@ -1315,7 +1315,7 @@ RSpec.describe Deployment, feature_category: :continuous_delivery do let(:build_status) { :created } it_behaves_like 'gracefully handling error' do - let(:error_message) { %Q{Status cannot transition via \"create\"} } + let(:error_message) { %{Status cannot transition via \"create\"} } end end @@ -1323,7 +1323,7 @@ RSpec.describe Deployment, feature_category: :continuous_delivery do let(:build_status) { :running } it_behaves_like 'gracefully handling error' do - let(:error_message) { %Q{Status cannot transition via \"run\"} } + let(:error_message) { %{Status cannot transition via \"run\"} } end end diff --git a/spec/models/incident_management/timeline_event_spec.rb b/spec/models/incident_management/timeline_event_spec.rb index 036f5affb87..40adf46e7d5 100644 --- a/spec/models/incident_management/timeline_event_spec.rb +++ b/spec/models/incident_management/timeline_event_spec.rb @@ -76,7 +76,7 @@ RSpec.describe IncidentManagement::TimelineEvent do end let(:expected_note_html) do - %Q(<p>note <strong>bold</strong> <em>italic</em> <code>code</code> #{expected_image_html} #{expected_emoji_html}</p>) + %(<p>note <strong>bold</strong> <em>italic</em> <code>code</code> #{expected_image_html} #{expected_emoji_html}</p>) end # rubocop:enable Layout/LineLength diff --git a/spec/models/integrations/drone_ci_spec.rb b/spec/models/integrations/drone_ci_spec.rb index c46face9702..2cb86fb680a 100644 --- a/spec/models/integrations/drone_ci_spec.rb +++ b/spec/models/integrations/drone_ci_spec.rb @@ -184,7 +184,7 @@ RSpec.describe Integrations::DroneCi, :use_clean_rails_memory_store_caching do "success" => "success" }.each do |drone_status, our_status| it "sets commit status to #{our_status.inspect} when returned status is #{drone_status.inspect}" do - stub_request(body: %Q({"status":"#{drone_status}"})) + stub_request(body: %({"status":"#{drone_status}"})) is_expected.to eq(our_status) end diff --git a/spec/models/integrations/teamcity_spec.rb b/spec/models/integrations/teamcity_spec.rb index e32088a2f79..c4c7202fae0 100644 --- a/spec/models/integrations/teamcity_spec.rb +++ b/spec/models/integrations/teamcity_spec.rb @@ -307,7 +307,7 @@ RSpec.describe Integrations::Teamcity, :use_clean_rails_memory_store_caching do def stub_post_to_build_queue(branch:) teamcity_full_url = "#{teamcity_url}/httpAuth/app/rest/buildQueue" - body ||= %Q(<build branchName=\"#{branch}\"><buildType id=\"foo\"/></build>) + body ||= %(<build branchName=\"#{branch}\"><buildType id=\"foo\"/></build>) auth = %w(mic password) stub_full_request(teamcity_full_url, method: :post).with( @@ -322,7 +322,7 @@ RSpec.describe Integrations::Teamcity, :use_clean_rails_memory_store_caching do def stub_request(status: 200, body: nil, build_status: 'success') auth = %w(mic password) - body ||= %Q({"build":{"status":"#{build_status}","id":"666"}}) + body ||= %({"build":{"status":"#{build_status}","id":"666"}}) stub_full_request(teamcity_full_url).with(basic_auth: auth).to_return( status: status, diff --git a/spec/models/project_label_spec.rb b/spec/models/project_label_spec.rb index ba9ea759c6a..62839f5fb4f 100644 --- a/spec/models/project_label_spec.rb +++ b/spec/models/project_label_spec.rb @@ -107,14 +107,14 @@ RSpec.describe ProjectLabel do context 'using name' do it 'returns cross reference with label name' do expect(label.to_reference(project, format: :name)) - .to eq %Q(#{label.project.full_path}~"#{label.name}") + .to eq %(#{label.project.full_path}~"#{label.name}") end end context 'using id' do it 'returns cross reference with label id' do expect(label.to_reference(project, format: :id)) - .to eq %Q(#{label.project.full_path}~#{label.id}) + .to eq %(#{label.project.full_path}~#{label.id}) end end end diff --git a/spec/presenters/snippet_blob_presenter_spec.rb b/spec/presenters/snippet_blob_presenter_spec.rb index d7f56c30b5e..cdd02241fbf 100644 --- a/spec/presenters/snippet_blob_presenter_spec.rb +++ b/spec/presenters/snippet_blob_presenter_spec.rb @@ -46,7 +46,7 @@ RSpec.describe SnippetBlobPresenter do let(:file) { 'test.ipynb' } it 'returns rich notebook content' do - expect(subject.strip).to eq %Q(<div class="file-content" data-endpoint="#{data_endpoint_url}" data-relative-raw-path="#{data_raw_dir}" id="js-notebook-viewer"></div>) + expect(subject.strip).to eq %(<div class="file-content" data-endpoint="#{data_endpoint_url}" data-relative-raw-path="#{data_raw_dir}" id="js-notebook-viewer"></div>) end end @@ -54,7 +54,7 @@ RSpec.describe SnippetBlobPresenter do let(:file) { 'openapi.yml' } it 'returns rich openapi content' do - expect(subject).to eq %Q(<div class="file-content" data-endpoint="#{data_endpoint_url}" id="js-openapi-viewer"></div>\n) + expect(subject).to eq %(<div class="file-content" data-endpoint="#{data_endpoint_url}" id="js-openapi-viewer"></div>\n) end end diff --git a/spec/requests/api/ci/job_artifacts_spec.rb b/spec/requests/api/ci/job_artifacts_spec.rb index 7cea744cdb9..6f4e7fd66ed 100644 --- a/spec/requests/api/ci/job_artifacts_spec.rb +++ b/spec/requests/api/ci/job_artifacts_spec.rb @@ -541,7 +541,7 @@ RSpec.describe API::Ci::JobArtifacts, feature_category: :build_artifacts do let(:download_headers) do { 'Content-Transfer-Encoding' => 'binary', 'Content-Disposition' => - %Q(attachment; filename="#{job_with_artifacts.artifacts_file.filename}"; filename*=UTF-8''#{job.artifacts_file.filename}) } + %(attachment; filename="#{job_with_artifacts.artifacts_file.filename}"; filename*=UTF-8''#{job.artifacts_file.filename}) } end it { expect(response).to have_gitlab_http_status(:ok) } diff --git a/spec/requests/api/deployments_spec.rb b/spec/requests/api/deployments_spec.rb index d7056adfcb6..82ac2eed83d 100644 --- a/spec/requests/api/deployments_spec.rb +++ b/spec/requests/api/deployments_spec.rb @@ -424,7 +424,7 @@ RSpec.describe API::Deployments, feature_category: :continuous_delivery do ) expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response['message']['status']).to include(%Q{cannot transition via \"run\"}) + expect(json_response['message']['status']).to include(%{cannot transition via \"run\"}) end it 'links merge requests when the deployment status changes to success', :sidekiq_inline do diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb index ed84e3e5f48..ea341703301 100644 --- a/spec/requests/api/files_spec.rb +++ b/spec/requests/api/files_spec.rb @@ -141,11 +141,9 @@ RSpec.describe API::Files, feature_category: :source_code_management do it 'caches sha256 of the content', :use_clean_rails_redis_caching do head api(route(file_path), current_user, **options), params: params - expect(Gitlab::Cache::Client).to receive(:build_with_metadata).with( - cache_identifier: 'API::Files#content_sha', - feature_category: :source_code_management, - backing_resource: :gitaly - ).and_call_original + expect_next_instance_of(Gitlab::Cache::Client) do |instance| + expect(instance).to receive(:fetch).with(anything, nil, { cache_identifier: 'API::Files#content_sha', backing_resource: :gitaly }).and_call_original + end expect(Rails.cache.fetch("blob_content_sha256:#{project.full_path}:#{response.headers['X-Gitlab-Blob-Id']}")) .to eq(content_sha256) diff --git a/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb b/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb index 09e884d9412..c3f818b6627 100644 --- a/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb +++ b/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb @@ -53,7 +53,7 @@ RSpec.describe 'Destroying a Snippet', feature_category: :source_code_management let!(:snippet_gid) { project.to_gid.to_s } it 'returns an error' do - err_message = %Q["#{snippet_gid}" does not represent an instance of Snippet] + err_message = %["#{snippet_gid}" does not represent an instance of Snippet] post_graphql_mutation(mutation, current_user: current_user) diff --git a/spec/requests/api/graphql/project/incident_management/timeline_events_spec.rb b/spec/requests/api/graphql/project/incident_management/timeline_events_spec.rb index 7587b227d9f..dd383226e17 100644 --- a/spec/requests/api/graphql/project/incident_management/timeline_events_spec.rb +++ b/spec/requests/api/graphql/project/incident_management/timeline_events_spec.rb @@ -15,7 +15,7 @@ RSpec.describe 'getting incident timeline events', feature_category: :incident_m let_it_be(:promoted_from_note) { create(:note, project: project, noteable: incident) } let_it_be(:issue_url) { project_issue_url(private_project, issue) } let_it_be(:issue_ref) { "#{private_project.full_path}##{issue.iid}" } - let_it_be(:issue_link) { %Q(<a href="#{issue_url}">#{issue_url}</a>) } + let_it_be(:issue_link) { %(<a href="#{issue_url}">#{issue_url}</a>) } let_it_be(:timeline_event) do create( diff --git a/spec/requests/projects/packages/package_files_controller_spec.rb b/spec/requests/projects/packages/package_files_controller_spec.rb index e5849be9f13..4f1793b831d 100644 --- a/spec/requests/projects/packages/package_files_controller_spec.rb +++ b/spec/requests/projects/packages/package_files_controller_spec.rb @@ -22,7 +22,7 @@ RSpec.describe Projects::Packages::PackageFilesController, feature_category: :pa subject expect(response.headers['Content-Disposition']) - .to eq(%Q(attachment; filename="#{filename}"; filename*=UTF-8''#{filename})) + .to eq(%(attachment; filename="#{filename}"; filename*=UTF-8''#{filename})) end it_behaves_like 'bumping the package last downloaded at field' diff --git a/spec/rubocop/cop/gitlab/mark_used_feature_flags_spec.rb b/spec/rubocop/cop/gitlab/mark_used_feature_flags_spec.rb index 96ff01108c3..4b7ea6b72e5 100644 --- a/spec/rubocop/cop/gitlab/mark_used_feature_flags_spec.rb +++ b/spec/rubocop/cop/gitlab/mark_used_feature_flags_spec.rb @@ -48,35 +48,35 @@ RSpec.describe RuboCop::Cop::Gitlab::MarkUsedFeatureFlags do ].each do |feature_flag_method| context "#{feature_flag_method} method" do context 'a string feature flag' do - include_examples 'sets flag as used', %Q|#{feature_flag_method}("foo")|, 'foo' + include_examples 'sets flag as used', %|#{feature_flag_method}("foo")|, 'foo' end context 'a symbol feature flag' do - include_examples 'sets flag as used', %Q|#{feature_flag_method}(:foo)|, 'foo' + include_examples 'sets flag as used', %|#{feature_flag_method}(:foo)|, 'foo' end context 'an interpolated string feature flag with a string prefix' do - include_examples 'sets flag as used', %Q|#{feature_flag_method}("foo_\#{bar}")|, %w[foo_hello foo_world] + include_examples 'sets flag as used', %|#{feature_flag_method}("foo_\#{bar}")|, %w[foo_hello foo_world] end context 'an interpolated symbol feature flag with a string prefix' do - include_examples 'sets flag as used', %Q|#{feature_flag_method}(:"foo_\#{bar}")|, %w[foo_hello foo_world] + include_examples 'sets flag as used', %|#{feature_flag_method}(:"foo_\#{bar}")|, %w[foo_hello foo_world] end context 'a string with a "/" in it' do - include_examples 'sets flag as used', %Q|#{feature_flag_method}("bar/baz")|, 'bar_baz' + include_examples 'sets flag as used', %|#{feature_flag_method}("bar/baz")|, 'bar_baz' end context 'an interpolated string feature flag with a string prefix and suffix' do - include_examples 'does not set any flags as used', %Q|#{feature_flag_method}(:"foo_\#{bar}_baz")| + include_examples 'does not set any flags as used', %|#{feature_flag_method}(:"foo_\#{bar}_baz")| end context 'a dynamic string feature flag as a variable' do - include_examples 'does not set any flags as used', %Q|#{feature_flag_method}(a_variable, an_arg)| + include_examples 'does not set any flags as used', %|#{feature_flag_method}(a_variable, an_arg)| end context 'an integer feature flag' do - include_examples 'does not set any flags as used', %Q|#{feature_flag_method}(123)| + include_examples 'does not set any flags as used', %|#{feature_flag_method}(123)| end end end @@ -87,31 +87,31 @@ RSpec.describe RuboCop::Cop::Gitlab::MarkUsedFeatureFlags do ].each do |feature_flag_method| context "#{feature_flag_method} method" do context 'a string feature flag' do - include_examples 'sets flag as used', %Q|#{feature_flag_method}("foo")|, 'gitaly_foo' + include_examples 'sets flag as used', %|#{feature_flag_method}("foo")|, 'gitaly_foo' end context 'a symbol feature flag' do - include_examples 'sets flag as used', %Q|#{feature_flag_method}(:foo)|, 'gitaly_foo' + include_examples 'sets flag as used', %|#{feature_flag_method}(:foo)|, 'gitaly_foo' end context 'an interpolated string feature flag with a string prefix' do - include_examples 'sets flag as used', %Q|#{feature_flag_method}("foo_\#{bar}")|, %w[foo_hello foo_world] + include_examples 'sets flag as used', %|#{feature_flag_method}("foo_\#{bar}")|, %w[foo_hello foo_world] end context 'an interpolated symbol feature flag with a string prefix' do - include_examples 'sets flag as used', %Q|#{feature_flag_method}(:"foo_\#{bar}")|, %w[foo_hello foo_world] + include_examples 'sets flag as used', %|#{feature_flag_method}(:"foo_\#{bar}")|, %w[foo_hello foo_world] end context 'an interpolated string feature flag with a string prefix and suffix' do - include_examples 'does not set any flags as used', %Q|#{feature_flag_method}(:"foo_\#{bar}_baz")| + include_examples 'does not set any flags as used', %|#{feature_flag_method}(:"foo_\#{bar}_baz")| end context 'a dynamic string feature flag as a variable' do - include_examples 'does not set any flags as used', %Q|#{feature_flag_method}(a_variable, an_arg)| + include_examples 'does not set any flags as used', %|#{feature_flag_method}(a_variable, an_arg)| end context 'an integer feature flag' do - include_examples 'does not set any flags as used', %Q|#{feature_flag_method}(123)| + include_examples 'does not set any flags as used', %|#{feature_flag_method}(123)| end end end @@ -126,15 +126,15 @@ RSpec.describe RuboCop::Cop::Gitlab::MarkUsedFeatureFlags do end context 'an interpolated string feature flag with a string prefix' do - include_examples 'sets flag as used', %Q|experiment("foo_\#{bar}")|, %w[foo_hello foo_world] + include_examples 'sets flag as used', %|experiment("foo_\#{bar}")|, %w[foo_hello foo_world] end context 'an interpolated symbol feature flag with a string prefix' do - include_examples 'sets flag as used', %Q|experiment(:"foo_\#{bar}")|, %w[foo_hello foo_world] + include_examples 'sets flag as used', %|experiment(:"foo_\#{bar}")|, %w[foo_hello foo_world] end context 'an interpolated string feature flag with a string prefix and suffix' do - include_examples 'does not set any flags as used', %Q|experiment(:"foo_\#{bar}_baz")| + include_examples 'does not set any flags as used', %|experiment(:"foo_\#{bar}_baz")| end context 'a dynamic string feature flag as a variable' do @@ -151,31 +151,31 @@ RSpec.describe RuboCop::Cop::Gitlab::MarkUsedFeatureFlags do ].each do |feature_flag_method| context "#{feature_flag_method} method" do context 'a string feature flag' do - include_examples 'sets flag as used', %Q|#{feature_flag_method}(arg, "baz")|, 'baz' + include_examples 'sets flag as used', %|#{feature_flag_method}(arg, "baz")|, 'baz' end context 'a symbol feature flag' do - include_examples 'sets flag as used', %Q|#{feature_flag_method}(arg, :baz)|, 'baz' + include_examples 'sets flag as used', %|#{feature_flag_method}(arg, :baz)|, 'baz' end context 'an interpolated string feature flag with a string prefix' do - include_examples 'sets flag as used', %Q|#{feature_flag_method}(arg, "foo_\#{bar}")|, %w[foo_hello foo_world] + include_examples 'sets flag as used', %|#{feature_flag_method}(arg, "foo_\#{bar}")|, %w[foo_hello foo_world] end context 'an interpolated symbol feature flag with a string prefix' do - include_examples 'sets flag as used', %Q|#{feature_flag_method}(arg, :"foo_\#{bar}")|, %w[foo_hello foo_world] + include_examples 'sets flag as used', %|#{feature_flag_method}(arg, :"foo_\#{bar}")|, %w[foo_hello foo_world] end context 'an interpolated string feature flag with a string prefix and suffix' do - include_examples 'does not set any flags as used', %Q|#{feature_flag_method}(arg, :"foo_\#{bar}_baz")| + include_examples 'does not set any flags as used', %|#{feature_flag_method}(arg, :"foo_\#{bar}_baz")| end context 'a dynamic string feature flag as a variable' do - include_examples 'does not set any flags as used', %Q|#{feature_flag_method}(a_variable, an_arg)| + include_examples 'does not set any flags as used', %|#{feature_flag_method}(a_variable, an_arg)| end context 'an integer feature flag' do - include_examples 'does not set any flags as used', %Q|#{feature_flag_method}(arg, 123)| + include_examples 'does not set any flags as used', %|#{feature_flag_method}(arg, 123)| end end end diff --git a/spec/services/prometheus/proxy_variable_substitution_service_spec.rb b/spec/services/prometheus/proxy_variable_substitution_service_spec.rb index fbee4b9c7d7..a5395eed1b4 100644 --- a/spec/services/prometheus/proxy_variable_substitution_service_spec.rb +++ b/spec/services/prometheus/proxy_variable_substitution_service_spec.rb @@ -53,7 +53,7 @@ RSpec.describe Prometheus::ProxyVariableSubstitutionService, feature_category: : end it_behaves_like 'success' do - let(:expected_query) { %Q[up{environment="#{environment.slug}"}] } + let(:expected_query) { %[up{environment="#{environment.slug}"}] } end end end diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb index a9ad853b028..62e05129fb2 100644 --- a/spec/support/helpers/graphql_helpers.rb +++ b/spec/support/helpers/graphql_helpers.rb @@ -473,7 +473,7 @@ module GraphqlHelpers end def with_signature(variables, query) - %Q[query(#{variables.map(&:sig).join(', ')}) #{wrap_query(query)}] + %[query(#{variables.map(&:sig).join(', ')}) #{wrap_query(query)}] end def var(type) diff --git a/spec/support/shared_examples/controllers/repository_lfs_file_load_shared_examples.rb b/spec/support/shared_examples/controllers/repository_lfs_file_load_shared_examples.rb index 9cf35325202..ba3b08751da 100644 --- a/spec/support/shared_examples/controllers/repository_lfs_file_load_shared_examples.rb +++ b/spec/support/shared_examples/controllers/repository_lfs_file_load_shared_examples.rb @@ -75,7 +75,7 @@ RSpec.shared_examples 'a controller that can serve LFS files' do |options = {}| file_uri = URI.parse(response.location) params = CGI.parse(file_uri.query) - expect(params["response-content-disposition"].first).to eq(%Q(attachment; filename="#{filename}"; filename*=UTF-8''#{filename})) + expect(params["response-content-disposition"].first).to eq(%(attachment; filename="#{filename}"; filename*=UTF-8''#{filename})) end end end diff --git a/spec/support/shared_examples/features/resolving_discussions_in_issues_shared_examples.rb b/spec/support/shared_examples/features/resolving_discussions_in_issues_shared_examples.rb index 337b3f3cbd0..7e3b507c1ba 100644 --- a/spec/support/shared_examples/features/resolving_discussions_in_issues_shared_examples.rb +++ b/spec/support/shared_examples/features/resolving_discussions_in_issues_shared_examples.rb @@ -24,6 +24,6 @@ RSpec.shared_examples 'creating an issue for a thread' do expect(discussion.resolved?).to eq(true) # Issue title inludes MR title - expect(page).to have_content(%Q(Follow-up from "#{merge_request.title}")) + expect(page).to have_content(%(Follow-up from "#{merge_request.title}")) end end diff --git a/spec/support/shared_examples/graphql/label_fields.rb b/spec/support/shared_examples/graphql/label_fields.rb index 030a2feafcd..809c801de62 100644 --- a/spec/support/shared_examples/graphql/label_fields.rb +++ b/spec/support/shared_examples/graphql/label_fields.rb @@ -93,7 +93,7 @@ RSpec.shared_examples 'querying a GraphQL type with labels' do describe 'performance' do def query_for(*labels) selections = labels.map do |label| - %Q[#{label.title.gsub(/:+/, '_')}: label(title: "#{label.title}") { description }] + %[#{label.title.gsub(/:+/, '_')}: label(title: "#{label.title}") { description }] end make_query(selections) diff --git a/spec/support/shared_examples/lib/banzai/filters/sanitization_filter_shared_examples.rb b/spec/support/shared_examples/lib/banzai/filters/sanitization_filter_shared_examples.rb index 47655f86558..99d0bc287a3 100644 --- a/spec/support/shared_examples/lib/banzai/filters/sanitization_filter_shared_examples.rb +++ b/spec/support/shared_examples/lib/banzai/filters/sanitization_filter_shared_examples.rb @@ -110,7 +110,7 @@ RSpec.shared_examples 'XSS prevention' do }, 'protocol-based JS injection: Unicode' => { - input: %Q(<a href="\u0001java\u0003script:alert('XSS')">foo</a>), + input: %(<a href="\u0001java\u0003script:alert('XSS')">foo</a>), output: '<a>foo</a>' }, diff --git a/spec/support_specs/matchers/exceed_query_limit_helpers_spec.rb b/spec/support_specs/matchers/exceed_query_limit_helpers_spec.rb index 0e79e32b78a..19581064626 100644 --- a/spec/support_specs/matchers/exceed_query_limit_helpers_spec.rb +++ b/spec/support_specs/matchers/exceed_query_limit_helpers_spec.rb @@ -156,8 +156,8 @@ RSpec.describe ExceedQueryLimitHelpers do expect(test_matcher.count_queries(recorder)).to eq({ 'SELECT "schema_migrations".* FROM "schema_migrations"' => { - %Q[WHERE "schema_migrations"."version" = 'foo\nbar\nbaz' LIMIT 1] => 2, - %Q[WHERE "schema_migrations"."version" = 'foo\nbiz\nbaz' LIMIT 1] => 1 + %[WHERE "schema_migrations"."version" = 'foo\nbar\nbaz' LIMIT 1] => 2, + %[WHERE "schema_migrations"."version" = 'foo\nbiz\nbaz' LIMIT 1] => 1 } }) end diff --git a/spec/views/layouts/_head.html.haml_spec.rb b/spec/views/layouts/_head.html.haml_spec.rb index a44c69748e5..9aec8f2d759 100644 --- a/spec/views/layouts/_head.html.haml_spec.rb +++ b/spec/views/layouts/_head.html.haml_spec.rb @@ -44,13 +44,13 @@ RSpec.describe 'layouts/_head' do it 'adds a link dns-prefetch tag' do render - expect(rendered).to match(%Q(<link href="#{asset_host}" rel="dns-prefetch">)) + expect(rendered).to match(%(<link href="#{asset_host}" rel="dns-prefetch">)) end it 'adds a link preconnect tag' do render - expect(rendered).to match(%Q(<link crossorigin="" href="#{asset_host}" rel="preconnect">)) + expect(rendered).to match(%(<link crossorigin="" href="#{asset_host}" rel="preconnect">)) end end @@ -82,7 +82,7 @@ RSpec.describe 'layouts/_head' do it 'adds a link preconnect tag' do render - expect(rendered).to match(%Q(<link crossorigin="" href="#{snowplow_collector_hostname}" rel="preconnect">)) + expect(rendered).to match(%(<link crossorigin="" href="#{snowplow_collector_hostname}" rel="preconnect">)) end end |