diff options
34 files changed, 565 insertions, 58 deletions
diff --git a/.gitlab/ci/qa.gitlab-ci.yml b/.gitlab/ci/qa.gitlab-ci.yml index 6f06746c49f..dae96808a61 100644 --- a/.gitlab/ci/qa.gitlab-ci.yml +++ b/.gitlab/ci/qa.gitlab-ci.yml @@ -128,6 +128,8 @@ e2e:test-on-gdk: # In MRs we assume the last scheduled master pipeline built the image already. - job: build-qa-on-gdk-master-image optional: true + variables: + QA_RUN_TYPE: e2e-test-on-gdk # Setting it here so that all the child pipeline reporting jobs inherit this variable allow_failure: true trigger: strategy: depend diff --git a/.gitlab/ci/test-on-gdk/main.gitlab-ci.yml b/.gitlab/ci/test-on-gdk/main.gitlab-ci.yml index d51e255fb95..10eca154b31 100644 --- a/.gitlab/ci/test-on-gdk/main.gitlab-ci.yml +++ b/.gitlab/ci/test-on-gdk/main.gitlab-ci.yml @@ -3,6 +3,27 @@ default: include: - local: .gitlab/ci/package-and-test/rules.gitlab-ci.yml + - local: .gitlab/ci/package-and-test/variables.gitlab-ci.yml + - project: 'gitlab-org/quality/pipeline-common' + ref: 3.1.3 + file: + - /ci/base.gitlab-ci.yml + - /ci/allure-report.yml + - /ci/knapsack-report.yml + +stages: + - test + - report + - notify + +.qa-install: + variables: + BUNDLE_SUPPRESS_INSTALL_USING_MESSAGES: "true" + BUNDLE_SILENCE_ROOT_WARNING: "true" + RUN_WITH_BUNDLE: "true" # instructs pipeline to install and run gitlab-qa gem via bundler + QA_PATH: qa # sets the optional path for bundler to run from + extends: + - .gitlab-qa-install dont-interrupt-me: extends: .rules:dont-interrupt @@ -11,6 +32,25 @@ dont-interrupt-me: script: - echo "This jobs makes sure this pipeline won't be interrupted! See https://docs.gitlab.com/ee/ci/yaml/#interruptible." +download-knapsack-report: + extends: + - .gitlab-qa-image + - .rules:download-knapsack + stage: .pre + variables: + KNAPSACK_DIR: ${CI_PROJECT_DIR}/qa/knapsack + GIT_STRATEGY: none + script: + # when using qa-image, code runs in /home/gitlab/qa folder + - bundle exec rake "knapsack:download[test]" + - mkdir -p "$KNAPSACK_DIR" && cp knapsack/*.json "${KNAPSACK_DIR}/" + - echo "$PROCESS_TEST_RESULTS" + allow_failure: true + artifacts: + paths: + - qa/knapsack/*.json + expire_in: 1 day + .run-tests: stage: test image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/debian-bullseye-ruby-${RUBY_VERSION}:bundler-2.3-chrome-${CHROME_VERSION}-docker-${DOCKER_VERSION} @@ -25,15 +65,15 @@ dont-interrupt-me: DOCKER_DRIVER: overlay2 DOCKER_HOST: tcp://docker:2375 QA_GDK_IMAGE: "${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-qa-gdk:master" - QA_GENERATE_ALLURE_REPORT: "false" + QA_GENERATE_ALLURE_REPORT: "true" QA_CAN_TEST_PRAEFECT: "false" QA_INTERCEPT_REQUESTS: "false" - QA_RUN_TYPE: e2e-test-on-gdk TEST_LICENSE_MODE: $QA_TEST_LICENSE_MODE EE_LICENSE: $QA_EE_LICENSE GITHUB_ACCESS_TOKEN: $QA_GITHUB_ACCESS_TOKEN GITLAB_QA_ADMIN_ACCESS_TOKEN: $QA_ADMIN_ACCESS_TOKEN QA_KNAPSACK_REPORTS: qa-smoke,ee-instance-parallel + RSPEC_REPORT_OPTS: "--format QA::Support::JsonFormatter --out tmp/rspec-${CI_JOB_ID}.json --format RspecJunitFormatter --out tmp/rspec-${CI_JOB_ID}.xml --format html --out tmp/rspec-${CI_JOB_ID}.htm --color --format documentation" timeout: 2 hours artifacts: when: always @@ -41,6 +81,8 @@ dont-interrupt-me: - test_output - logs expire_in: 7 days + reports: + junit: test_output/**/rspec-*.xml script: - echo -e "\e[0Ksection_start:`date +%s`:pull_image\r\e[0KPull GDK QA image" - docker pull ${QA_GDK_IMAGE} @@ -57,8 +99,8 @@ dont-interrupt-me: --volume $CI_PROJECT_DIR/test_output:/home/gdk/gdk/gitlab/qa/tmp:z \ --volume $CI_PROJECT_DIR/logs/gdk:/home/gdk/gdk/log \ --volume $CI_PROJECT_DIR/logs/gitlab:/home/gdk/gdk/gitlab/log \ - ${QA_GDK_IMAGE} "${CI_COMMIT_SHA}" "$TEST_GDK_TAGS --tag ~requires_praefect" || true - - echo -e "\e[0Ksection_end:`date +%s`:launch_gdk_and_tests\r\e[0K" + ${QA_GDK_IMAGE} "${CI_COMMIT_SHA}" "$RSPEC_REPORT_OPTS $TEST_GDK_TAGS --tag ~requires_praefect" + # The above image's launch script takes two arguments only - first one is the commit sha and the second one Rspec Args allow_failure: true test-on-gdk-smoke: @@ -79,3 +121,98 @@ test-on-gdk-full: QA_KNAPSACK_REPORT_NAME: ee-instance-parallel rules: - when: manual + +# ========================================== +# Post test stage +# ========================================== +e2e-test-report: + extends: + - .generate-allure-report-base + - .rules:report:allure-report + stage: report + variables: + ALLURE_JOB_NAME: e2e-test-on-gdk + ALLURE_RESULTS_GLOB: test_output/allure-results + GITLAB_AUTH_TOKEN: $PROJECT_TOKEN_FOR_CI_SCRIPTS_API_USAGE + ALLURE_PROJECT_PATH: $CI_PROJECT_PATH + ALLURE_MERGE_REQUEST_IID: $CI_MERGE_REQUEST_IID + +upload-knapsack-report: + extends: + - .generate-knapsack-report-base + - .qa-install + - .ruby-qa-image + - .rules:report:process-results + variables: + QA_KNAPSACK_REPORT_FILE_PATTERN: $CI_PROJECT_DIR/test_output/knapsack/*/*.json + stage: report + when: always + +export-test-metrics: + extends: + - .qa-install + - .ruby-qa-image + - .rules:report:process-results + stage: report + when: always + script: + - pwd + - bundle exec rake "ci:export_test_metrics[$CI_PROJECT_DIR/test_output/test-metrics-*.json]" + +relate-test-failures: + extends: + - .qa-install + - .ruby-qa-image + - .rules:report:process-results + stage: report + variables: + QA_FAILURES_REPORTING_PROJECT: gitlab-org/gitlab + QA_FAILURES_MAX_DIFF_RATIO: "0.15" + GITLAB_QA_ACCESS_TOKEN: $QA_GITLAB_CI_TOKEN + when: on_failure + script: + - | + bundle exec gitlab-qa-report \ + --relate-failure-issue "$CI_PROJECT_DIR/test_output/rspec-*.json" \ + --project "$QA_FAILURES_REPORTING_PROJECT" \ + --max-diff-ratio "$QA_FAILURES_MAX_DIFF_RATIO" + +generate-test-session: + extends: + - .qa-install + - .ruby-qa-image + - .rules:report:process-results + stage: report + variables: + QA_TESTCASE_SESSIONS_PROJECT: gitlab-org/quality/testcase-sessions + GITLAB_QA_ACCESS_TOKEN: $QA_TEST_SESSION_TOKEN + GITLAB_CI_API_TOKEN: $QA_GITLAB_CI_TOKEN + when: always + script: + - | + bundle exec gitlab-qa-report \ + --generate-test-session "$CI_PROJECT_DIR/test_output/rspec-*.json" \ + --project "$QA_TESTCASE_SESSIONS_PROJECT" + artifacts: + when: always + expire_in: 1d + paths: + - qa/REPORT_ISSUE_URL + +notify-slack: + extends: + - .notify-slack-qa + - .qa-install + - .ruby-qa-image + - .rules:report:process-results + stage: notify + variables: + ALLURE_JOB_NAME: e2e-test-on-gdk + SLACK_ICON_EMOJI: ci_failing + STATUS_SYM: ☠️ + STATUS: failed + TYPE: "(e2e-test-on-gdk) " + when: on_failure + script: + - bundle exec gitlab-qa-report --prepare-stage-reports "$CI_PROJECT_DIR/test_output/rspec-*.xml" # generate summary + - !reference [.notify-slack-qa, script] diff --git a/Gemfile.checksum b/Gemfile.checksum index 5471e4e7ea7..29417b7a380 100644 --- a/Gemfile.checksum +++ b/Gemfile.checksum @@ -450,11 +450,11 @@ {"name":"premailer","version":"1.16.0","platform":"ruby","checksum":"03e4402c448e6bae13fb5f6301a8bde4f3508e1bff90ae7c0972c7be94694786"}, {"name":"premailer-rails","version":"1.10.3","platform":"ruby","checksum":"7cdcb97027866f7a81c490c6d15ada7f39666b5f6375f0821b7e97e0483b112f"}, {"name":"proc_to_ast","version":"0.1.0","platform":"ruby","checksum":"92a73fa66e2250a83f8589f818b0751bcf227c68f85916202df7af85082f8691"}, -{"name":"prometheus-client-mmap","version":"0.23.0","platform":"aarch64-linux","checksum":"1f17600960e3d779ccf40da9e0603d8d754eceff6addb2f5b8482b10e93c980a"}, -{"name":"prometheus-client-mmap","version":"0.23.0","platform":"arm64-darwin","checksum":"8b1872583814e9d8bbf81032c67412fcec7a8130a2d1e7e4a5784cd3c7300a89"}, -{"name":"prometheus-client-mmap","version":"0.23.0","platform":"ruby","checksum":"e90b353fc583e0a317a3aeae02439a96e86d2193d4a3a31b733cafc6e4d6280b"}, -{"name":"prometheus-client-mmap","version":"0.23.0","platform":"x86_64-darwin","checksum":"d73d3a2b1f1d0f3e1f4b9b8b06c4471f1cf951a7e6b38b83f2f3eb4816500cac"}, -{"name":"prometheus-client-mmap","version":"0.23.0","platform":"x86_64-linux","checksum":"35dc742f4b3a718bda62c80cd618c0ec42accabf58f8330d92e39f67be58a0bf"}, +{"name":"prometheus-client-mmap","version":"0.23.1","platform":"aarch64-linux","checksum":"4091121090d1d44747b3d09f2dbd5fdd61e274d557b8ed98b06c65cdd006d174"}, +{"name":"prometheus-client-mmap","version":"0.23.1","platform":"arm64-darwin","checksum":"fa54f230631852392b38cba1ad396c0472cb9f088eef563d0c381b19b1333855"}, +{"name":"prometheus-client-mmap","version":"0.23.1","platform":"ruby","checksum":"48545f23217a5e85ca79fa8c2563711e319debdae46ddbd6348ff37f48029c40"}, +{"name":"prometheus-client-mmap","version":"0.23.1","platform":"x86_64-darwin","checksum":"99b56f4017f0a1a062914da253c613b9957bfabf5b38af5012e3d8515ed49555"}, +{"name":"prometheus-client-mmap","version":"0.23.1","platform":"x86_64-linux","checksum":"624da747dbb97e0d88be1f2ba5ae5253941fc85dea875845f5b4c7a2c95ee032"}, {"name":"pry","version":"0.14.2","platform":"java","checksum":"fd780670977ba04ff7ee32dabd4d02fe4bf02e977afe8809832d5dca1412862e"}, {"name":"pry","version":"0.14.2","platform":"ruby","checksum":"c4fe54efedaca1d351280b45b8849af363184696fcac1c72e0415f9bdac4334d"}, {"name":"pry-byebug","version":"3.10.1","platform":"ruby","checksum":"c8f975c32255bfdb29e151f5532130be64ff3d0042dc858d0907e849125581f8"}, diff --git a/Gemfile.lock b/Gemfile.lock index e2054d98a13..e4f49206276 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1154,7 +1154,7 @@ GEM coderay parser unparser - prometheus-client-mmap (0.23.0) + prometheus-client-mmap (0.23.1) rb_sys (~> 0.9) pry (0.14.2) coderay (~> 1.1) diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue b/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue index d32e90f3adb..48e5bb8f0b2 100644 --- a/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue +++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue @@ -114,8 +114,11 @@ export default { showDeleteDropdown() { return this.group.dependencyProxyManifests?.nodes.length > 0 && this.canClearCache; }, + dependencyProxyImagePrefix() { + return this.group.dependencyProxyImagePrefix; + }, showDependencyProxyImagePrefix() { - return this.group.dependencyProxyImagePrefix?.length > 0; + return this.dependencyProxyImagePrefix?.length > 0; }, }, methods: { @@ -208,6 +211,7 @@ export default { <manifests-list v-if="manifests && manifests.length" + :dependency-proxy-image-prefix="dependencyProxyImagePrefix" :loading="$apollo.queries.group.loading" :manifests="manifests" :pagination="pageInfo" diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifest_row.vue b/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifest_row.vue index 1bbd0c32dc4..254fd578cf1 100644 --- a/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifest_row.vue +++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifest_row.vue @@ -3,11 +3,16 @@ import { GlIcon, GlSprintf } from '@gitlab/ui'; import { MANIFEST_PENDING_DESTRUCTION_STATUS } from '~/packages_and_registries/dependency_proxy/constants'; import ListItem from '~/vue_shared/components/registry/list_item.vue'; import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; +import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import { s__ } from '~/locale'; +const SHORT_DIGEST_START_INDEX = 7; +const SHORT_DIGEST_END_INDEX = 14; + export default { name: 'ManifestRow', components: { + ClipboardButton, GlIcon, GlSprintf, ListItem, @@ -18,13 +23,25 @@ export default { type: Object, required: true, }, + dependencyProxyImagePrefix: { + type: String, + default: '', + required: false, + }, }, computed: { name() { - return this.manifest?.imageName.split(':')[0]; + if (this.containsDigestInImageName) { + return this.manifest?.imageName.split(':')[0]; + } + return this.manifest?.imageName; }, - version() { - return this.manifest?.imageName.split(':')[1]; + imageCopyText() { + const name = this.manifest?.imageName.replace(':sha256:', '@sha256:') ?? ''; + return `${this.dependencyProxyImagePrefix}/${name}`; + }, + containsDigestInImageName() { + return this.manifest?.imageName.includes(':sha256:'); }, isErrorStatus() { return this.manifest?.status === MANIFEST_PENDING_DESTRUCTION_STATUS; @@ -32,9 +49,16 @@ export default { disabledRowStyle() { return this.isErrorStatus ? 'gl-font-weight-normal gl-text-gray-500' : ''; }, + shortDigest() { + // digest is in the format `sha256:995efde2e81b21d1ea7066aa77a59298a62a9e9fbb4b77f36c189774ec9b1089` + // for short digest, remove sha256: from the string, and show only the first 7 char + return this.manifest.digest?.substring(SHORT_DIGEST_START_INDEX, SHORT_DIGEST_END_INDEX); + }, }, i18n: { cachedAgoMessage: s__('DependencyProxy|Cached %{time}'), + copyImagePathTitle: s__('DependencyProxy|Copy image path'), + digestLabel: s__('DependencyProxy|Digest: %{shortDigest}'), scheduledForDeletion: s__('DependencyProxy|Scheduled for deletion'), }, }; @@ -44,9 +68,21 @@ export default { <list-item :disabled="isErrorStatus"> <template #left-primary> <span :class="disabledRowStyle">{{ name }}</span> + <clipboard-button + class="gl-ml-2" + :text="imageCopyText" + :title="$options.i18n.copyImagePathTitle" + category="tertiary" + /> </template> <template #left-secondary> - {{ version }} + <span data-testid="manifest-row-short-digest"> + <gl-sprintf :message="$options.i18n.digestLabel"> + <template #shortDigest> + {{ shortDigest }} + </template> + </gl-sprintf> + </span> <span v-if="isErrorStatus" class="gl-ml-4" data-testid="status" ><gl-icon name="clock" /> {{ $options.i18n.scheduledForDeletion }}</span > diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifests_list.vue b/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifests_list.vue index 0d9b8330fe3..9870841f1ff 100644 --- a/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifests_list.vue +++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifests_list.vue @@ -25,6 +25,11 @@ export default { required: false, default: () => false, }, + dependencyProxyImagePrefix: { + type: String, + default: '', + required: false, + }, }, i18n: { listTitle: s__('DependencyProxy|Image list'), @@ -45,7 +50,12 @@ export default { <div class="gl-border-t-1 gl-border-gray-100 gl-border-t-solid gl-display-flex gl-flex-direction-column" > - <manifest-row v-for="(manifest, index) in manifests" :key="index" :manifest="manifest" /> + <manifest-row + v-for="(manifest, index) in manifests" + :key="index" + :dependency-proxy-image-prefix="dependencyProxyImagePrefix" + :manifest="manifest" + /> </div> <div class="gl-display-flex gl-justify-content-center"> <gl-keyset-pagination diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql b/app/assets/javascripts/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql index c1597625964..db0e596ba64 100644 --- a/app/assets/javascripts/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql +++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql @@ -19,6 +19,7 @@ query getDependencyProxyDetails( nodes { id createdAt + digest imageName status } diff --git a/app/assets/javascripts/super_sidebar/components/nav_item.vue b/app/assets/javascripts/super_sidebar/components/nav_item.vue index 1d8c5b30e13..55146610d5f 100644 --- a/app/assets/javascripts/super_sidebar/components/nav_item.vue +++ b/app/assets/javascripts/super_sidebar/components/nav_item.vue @@ -137,7 +137,7 @@ export default { </div> <slot name="actions"></slot> <span v-if="hasPill || isPinnable" class="gl-flex-grow-1 gl-text-right gl-mr-3"> - <gl-badge v-if="hasPill" size="sm" variant="info"> + <gl-badge v-if="hasPill" size="sm" variant="neutral"> {{ pillData }} </gl-badge> <gl-button diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 8b51322f1dc..a537e52ccf6 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -449,6 +449,9 @@ class ApplicationSetting < MainClusterwide::ApplicationRecord validates :silent_mode_enabled, inclusion: { in: [true, false], message: N_('must be a boolean value') } + validates :remember_me_enabled, + inclusion: { in: [true, false], message: N_('must be a boolean value') } + Gitlab::SSHPublicKey.supported_types.each do |type| validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type } end diff --git a/app/models/concerns/enums/package_metadata.rb b/app/models/concerns/enums/package_metadata.rb index a866e2b995a..69108ae9cc4 100644 --- a/app/models/concerns/enums/package_metadata.rb +++ b/app/models/concerns/enums/package_metadata.rb @@ -17,6 +17,11 @@ module Enums cbl_mariner: 12 }.with_indifferent_access.freeze + ADVISORY_SOURCES = { + glad: 1, # gitlab advisory db + trivy: 2 + }.with_indifferent_access.freeze + def self.purl_types PURL_TYPES end @@ -24,5 +29,9 @@ module Enums def self.purl_types_numerical purl_types.invert end + + def self.advisory_sources + ADVISORY_SOURCES + end end end diff --git a/db/docs/pm_advisories.yml b/db/docs/pm_advisories.yml new file mode 100644 index 00000000000..010f029978e --- /dev/null +++ b/db/docs/pm_advisories.yml @@ -0,0 +1,11 @@ +--- +table_name: pm_advisories +classes: +- PackageMetadata::Advisory +feature_categories: +- software_composition_analysis +- container_scanning +description: Stores security advisories. +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/117326 +milestone: '16.0' +gitlab_schema: gitlab_pm diff --git a/db/docs/pm_affected_packages.yml b/db/docs/pm_affected_packages.yml new file mode 100644 index 00000000000..ad389c72b59 --- /dev/null +++ b/db/docs/pm_affected_packages.yml @@ -0,0 +1,11 @@ +--- +table_name: pm_affected_packages +classes: +- PackageMetadata::AffectedPackage +feature_categories: +- software_composition_analysis +- container_scanning +description: Stores info for packages affected by an advisory. +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/117326 +milestone: '16.0' +gitlab_schema: gitlab_pm diff --git a/db/migrate/20230411205121_create_package_metadata_advisory_info.rb b/db/migrate/20230411205121_create_package_metadata_advisory_info.rb new file mode 100644 index 00000000000..adfc8e868f7 --- /dev/null +++ b/db/migrate/20230411205121_create_package_metadata_advisory_info.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +class CreatePackageMetadataAdvisoryInfo < Gitlab::Database::Migration[2.1] + def change + create_table :pm_advisories do |t| + t.text :advisory_xid, limit: 36, null: false + t.date :published_date, null: false + t.timestamps_with_timezone null: false + t.integer :source_xid, limit: 2, null: false + + t.text :title, limit: 256 + t.text :description, limit: 8192 + t.text :cvss_v2, limit: 128 + t.text :cvss_v3, limit: 128 + t.text :urls, array: true, default: [] + t.jsonb :identifiers, null: false + + t.index [:advisory_xid, :source_xid], unique: true + t.check_constraint 'CARDINALITY(urls) <= 10' + end + + create_table :pm_affected_packages do |t| + t.references :pm_advisory, index: true, foreign_key: { on_delete: :cascade }, null: false + t.timestamps_with_timezone null: false + t.integer :purl_type, limit: 2, null: false + + t.text :package_name, limit: 256, null: false + t.text :distro_version, limit: 256, null: true + t.text :solution, limit: 2048, null: true + t.text :affected_range, limit: 512, null: false + t.text :fixed_versions, array: true, default: [] + t.jsonb :overridden_advisory_fields, null: false, default: {} + + t.index [:pm_advisory_id, :purl_type, :package_name, :distro_version], unique: true, + name: 'i_affected_packages_unique_for_upsert' + t.check_constraint 'CARDINALITY(fixed_versions) <= 10' + end + end +end diff --git a/db/migrate/20230501163253_add_remember_me_enabled_to_application_settings.rb b/db/migrate/20230501163253_add_remember_me_enabled_to_application_settings.rb new file mode 100644 index 00000000000..40c4ccd9f26 --- /dev/null +++ b/db/migrate/20230501163253_add_remember_me_enabled_to_application_settings.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddRememberMeEnabledToApplicationSettings < Gitlab::Database::Migration[2.1] + def change + add_column :application_settings, :remember_me_enabled, :boolean, default: true, null: false + end +end diff --git a/db/schema_migrations/20230411205121 b/db/schema_migrations/20230411205121 new file mode 100644 index 00000000000..b90bda11c27 --- /dev/null +++ b/db/schema_migrations/20230411205121 @@ -0,0 +1 @@ +cb1766c3c3b6604353dfcb774b8b4f3fe65dac10d15312785855153769ac6fe0
\ No newline at end of file diff --git a/db/schema_migrations/20230501163253 b/db/schema_migrations/20230501163253 new file mode 100644 index 00000000000..e0f178a65a7 --- /dev/null +++ b/db/schema_migrations/20230501163253 @@ -0,0 +1 @@ +e13f88c8de95d10e1150b07e6d112aaa9221e0a866fce3f92883cec9ee026acd
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 47569c39a8d..8516c54c821 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -11844,6 +11844,7 @@ CREATE TABLE application_settings ( encrypted_tofa_client_library_fetch_access_token_method_iv bytea, encrypted_tofa_access_token_expires_in bytea, encrypted_tofa_access_token_expires_in_iv bytea, + remember_me_enabled boolean DEFAULT true NOT NULL, CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)), CONSTRAINT app_settings_container_registry_pre_import_tags_rate_positive CHECK ((container_registry_pre_import_tags_rate >= (0)::numeric)), CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)), @@ -20224,6 +20225,64 @@ CREATE SEQUENCE plans_id_seq ALTER SEQUENCE plans_id_seq OWNED BY plans.id; +CREATE TABLE pm_advisories ( + id bigint NOT NULL, + advisory_xid text NOT NULL, + published_date date NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + source_xid smallint NOT NULL, + title text, + description text, + cvss_v2 text, + cvss_v3 text, + urls text[] DEFAULT '{}'::text[], + identifiers jsonb NOT NULL, + CONSTRAINT check_152def3868 CHECK ((char_length(cvss_v2) <= 128)), + CONSTRAINT check_19cbd06439 CHECK ((char_length(advisory_xid) <= 36)), + CONSTRAINT check_bed97fa77a CHECK ((char_length(cvss_v3) <= 128)), + CONSTRAINT check_e4bfd3ffbf CHECK ((char_length(title) <= 256)), + CONSTRAINT check_fee880f7aa CHECK ((char_length(description) <= 8192)), + CONSTRAINT chk_rails_e73af9de76 CHECK ((cardinality(urls) <= 10)) +); + +CREATE SEQUENCE pm_advisories_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + +ALTER SEQUENCE pm_advisories_id_seq OWNED BY pm_advisories.id; + +CREATE TABLE pm_affected_packages ( + id bigint NOT NULL, + pm_advisory_id bigint NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + purl_type smallint NOT NULL, + package_name text NOT NULL, + distro_version text, + solution text, + affected_range text NOT NULL, + fixed_versions text[] DEFAULT '{}'::text[], + overridden_advisory_fields jsonb DEFAULT '{}'::jsonb NOT NULL, + CONSTRAINT check_5dd528a2be CHECK ((char_length(package_name) <= 256)), + CONSTRAINT check_80dea16c7b CHECK ((char_length(affected_range) <= 512)), + CONSTRAINT check_d1d4646298 CHECK ((char_length(solution) <= 2048)), + CONSTRAINT check_ec4c8efb5e CHECK ((char_length(distro_version) <= 256)), + CONSTRAINT chk_rails_a0f80d74e0 CHECK ((cardinality(fixed_versions) <= 10)) +); + +CREATE SEQUENCE pm_affected_packages_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + +ALTER SEQUENCE pm_affected_packages_id_seq OWNED BY pm_affected_packages.id; + CREATE TABLE pm_checkpoints ( sequence integer NOT NULL, created_at timestamp with time zone NOT NULL, @@ -25483,6 +25542,10 @@ ALTER TABLE ONLY plan_limits ALTER COLUMN id SET DEFAULT nextval('plan_limits_id ALTER TABLE ONLY plans ALTER COLUMN id SET DEFAULT nextval('plans_id_seq'::regclass); +ALTER TABLE ONLY pm_advisories ALTER COLUMN id SET DEFAULT nextval('pm_advisories_id_seq'::regclass); + +ALTER TABLE ONLY pm_affected_packages ALTER COLUMN id SET DEFAULT nextval('pm_affected_packages_id_seq'::regclass); + ALTER TABLE ONLY pm_licenses ALTER COLUMN id SET DEFAULT nextval('pm_licenses_id_seq'::regclass); ALTER TABLE ONLY pm_package_version_licenses ALTER COLUMN id SET DEFAULT nextval('pm_package_version_licenses_id_seq'::regclass); @@ -27777,6 +27840,12 @@ ALTER TABLE ONLY plan_limits ALTER TABLE ONLY plans ADD CONSTRAINT plans_pkey PRIMARY KEY (id); +ALTER TABLE ONLY pm_advisories + ADD CONSTRAINT pm_advisories_pkey PRIMARY KEY (id); + +ALTER TABLE ONLY pm_affected_packages + ADD CONSTRAINT pm_affected_packages_pkey PRIMARY KEY (id); + ALTER TABLE ONLY pm_checkpoints ADD CONSTRAINT pm_checkpoints_pkey PRIMARY KEY (purl_type); @@ -29387,6 +29456,8 @@ CREATE UNIQUE INDEX finding_link_url_idx ON vulnerability_finding_links USING bt CREATE INDEX finding_links_on_vulnerability_occurrence_id ON vulnerability_finding_links USING btree (vulnerability_occurrence_id); +CREATE UNIQUE INDEX i_affected_packages_unique_for_upsert ON pm_affected_packages USING btree (pm_advisory_id, purl_type, package_name, distro_version); + CREATE INDEX i_batched_background_migration_job_transition_logs_on_job_id ON ONLY batched_background_migration_job_transition_logs USING btree (batched_background_migration_job_id); CREATE UNIQUE INDEX i_bulk_import_export_batches_id_batch_number ON bulk_import_export_batches USING btree (export_id, batch_number); @@ -31863,6 +31934,10 @@ CREATE UNIQUE INDEX index_plan_limits_on_plan_id ON plan_limits USING btree (pla CREATE UNIQUE INDEX index_plans_on_name ON plans USING btree (name); +CREATE UNIQUE INDEX index_pm_advisories_on_advisory_xid_and_source_xid ON pm_advisories USING btree (advisory_xid, source_xid); + +CREATE INDEX index_pm_affected_packages_on_pm_advisory_id ON pm_affected_packages USING btree (pm_advisory_id); + CREATE INDEX index_pm_package_version_licenses_on_pm_license_id ON pm_package_version_licenses USING btree (pm_license_id); CREATE INDEX index_pm_package_version_licenses_on_pm_package_version_id ON pm_package_version_licenses USING btree (pm_package_version_id); @@ -35735,6 +35810,9 @@ ALTER TABLE ONLY gpg_signatures ALTER TABLE ONLY project_authorizations ADD CONSTRAINT fk_rails_11e7aa3ed9 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; +ALTER TABLE ONLY pm_affected_packages + ADD CONSTRAINT fk_rails_1279c1b9a1 FOREIGN KEY (pm_advisory_id) REFERENCES pm_advisories(id) ON DELETE CASCADE; + ALTER TABLE ONLY description_versions ADD CONSTRAINT fk_rails_12b144011c FOREIGN KEY (merge_request_id) REFERENCES merge_requests(id) ON DELETE CASCADE; diff --git a/doc/administration/package_information/defaults.md b/doc/administration/package_information/defaults.md index 932342a6a92..68958102d4e 100644 --- a/doc/administration/package_information/defaults.md +++ b/doc/administration/package_information/defaults.md @@ -50,7 +50,8 @@ by default: | Consul | No | Port | X | 8300, 8301(UDP), 8500, 8600[^Consul-notes] | | Patroni | No | Port | X | 8008 | | GitLab KAS | Yes | Port | X | 8150 | -| Gitaly | No | Port | X | 8075 | +| Gitaly | No | Port | X | 8075 or 9999 (TLS) | +| Praefect | No | Port | X | 2305 or 3305 (TLS) | Legend: diff --git a/doc/development/testing_guide/end_to_end/execution_context_selection.md b/doc/development/testing_guide/end_to_end/execution_context_selection.md index 23b8ab7287b..4d83884f4d0 100644 --- a/doc/development/testing_guide/end_to_end/execution_context_selection.md +++ b/doc/development/testing_guide/end_to_end/execution_context_selection.md @@ -34,7 +34,8 @@ Matches use: - Regex for environments. - String matching for pipelines. -- Regex or string matching for jobs. +- Regex or string matching for jobs +- Lambda or truthy/falsey value for generic condition | Test execution context | Key | Matches | | ---------------- | --- | --------------- | @@ -47,6 +48,7 @@ Matches use: | The `nightly` and `canary` pipelines | `only: { pipeline: [:nightly, :canary] }` | ["nightly"](https://gitlab.com/gitlab-org/quality/nightly) and ["canary"](https://gitlab.com/gitlab-org/quality/canary) | | The `ee:instance` job | `only: { job: 'ee:instance' }` | The `ee:instance` job in any pipeline | | Any `quarantine` job | `only: { job: '.*quarantine' }` | Any job ending in `quarantine` in any pipeline | +| Any run where condition evaluates to a truthy value | `only: { condition: -> { ENV['TEST_ENV'] == 'true' } }` | Any run where `TEST_ENV` is set to true ```ruby RSpec.describe 'Area' do @@ -62,6 +64,8 @@ RSpec.describe 'Area' do it 'runs only in nightly pipeline', only: { pipeline: :nightly } do; end it 'runs in nightly and canary pipelines', only: { pipeline: [:nightly, :canary] } do; end + + it 'runs in specific environment matching condition', only: { condition: -> { ENV['TEST_ENV'] == 'true' } } do; end end ``` @@ -73,7 +77,8 @@ Matches use: - Regex for environments. - String matching for pipelines. -- Regex or string matching for jobs. +- Regex or string matching for jobs +- Lambda or truthy/falsey value for generic condition | Test execution context | Key | Matches | | ---------------- | --- | --------------- | @@ -86,6 +91,7 @@ Matches use: | The `nightly` and `canary` pipelines | `except: { pipeline: [:nightly, :canary] }` | ["nightly"](https://gitlab.com/gitlab-org/quality/nightly) and ["canary"](https://gitlab.com/gitlab-org/quality/canary) | | The `ee:instance` job | `except: { job: 'ee:instance' }` | The `ee:instance` job in any pipeline | | Any `quarantine` job | `except: { job: '.*quarantine' }` | Any job ending in `quarantine` in any pipeline | +| Any run except where condition evaluates to a truthy value | `except: { condition: -> { ENV['TEST_ENV'] == 'true' } }` | Any run where `TEST_ENV` is not set to true ```ruby RSpec.describe 'Area' do @@ -96,6 +102,8 @@ RSpec.describe 'Area' do it 'runs in any execution context except the nightly pipeline', except: { pipeline: :nightly } do; end it 'runs in any execution context except the ee:instance job', except: { job: 'ee:instance' } do; end + + it 'runs in specific environment not matching condition', except: { condition: -> { ENV['TEST_ENV'] == 'true' } } do; end end ``` diff --git a/doc/user/application_security/sast/index.md b/doc/user/application_security/sast/index.md index 90a7d5e72ae..d3edb7dd932 100644 --- a/doc/user/application_security/sast/index.md +++ b/doc/user/application_security/sast/index.md @@ -615,6 +615,7 @@ flags are added to the scanner's CLI options. | Analyzer | CLI option | Description | |------------------------------------------------------------------------------|--------------------|------------------------------------------------------------------------------| | [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) | `--max-memory` | Sets the maximum system memory to use when running a rule on a single file. Measured in MB. | +| [Flawfinder](https://gitlab.com/gitlab-org/security-products/analyzers/flawfinder) | `--neverignore` | Never ignore security issues, even if they have an “ignore” directive in a comment. Adding this option is likely to result in the analyzer detecting additional vulnerability findings which cannot be automatically resolved. | #### Custom CI/CD variables diff --git a/doc/user/application_security/secret_detection/index.md b/doc/user/application_security/secret_detection/index.md index 78de8df5cb9..c5e10d7a2df 100644 --- a/doc/user/application_security/secret_detection/index.md +++ b/doc/user/application_security/secret_detection/index.md @@ -460,6 +460,41 @@ For more information on the syntax of passthroughs, see the [passthroughs section on the SAST customize rulesets](../sast/customize_rulesets.md#the-analyzerpassthrough-section) page. +#### Extending the default configuration + +You can extend the default configuration with additional changes by using [Gitleaks `extend` support](https://github.com/gitleaks/gitleaks#configuration). + +In the following `file` passthrough example, the string `glpat-1234567890abcdefghij` is ignored by Secret Detection. That GitLab personal access token (PAT) is used in test cases. Detection of it would be a false positive. + +The `secret-detection-ruleset.toml` file defines that the configuration in `extended-gitleaks-config.toml` file is to be included. The `extended-gitleaks-config.toml` file defines the custom Gitleaks configuration. The `allowlist` stanza defines a regular expression that matches the secret that is to be ignored ("allowed"). + +```toml +# .gitlab/secret-detection-ruleset.toml +[secrets] + description = 'secrets custom rules configuration' + + [[secrets.passthrough]] + type = "file" + target = "gitleaks.toml" + value = "extended-gitleaks-config.toml" +``` + +```toml +# extended-gitleaks-config.toml +title = "extension of gitlab's default gitleaks config" + +[extend] +# Extends default packaged path +path = "/gitleaks.toml" + +[allowlist] + description = "allow list of test tokens to ignore in detection" + regexTarget = "match" + regexes = [ + '''glpat-1234567890abcdefghij''', + ] +``` + ## Running Secret Detection in an offline environment **(PREMIUM SELF)** An offline environment has limited, restricted, or intermittent access to external resources through diff --git a/doc/user/compliance/index.md b/doc/user/compliance/index.md index 7981ab44bf9..ad36684d987 100644 --- a/doc/user/compliance/index.md +++ b/doc/user/compliance/index.md @@ -7,6 +7,12 @@ info: To determine the technical writer assigned to the Stage/Group associated w # Compliance **(ULTIMATE)** -The compliance tools provided by GitLab help you keep an eye on various aspects of your project. For more information -on GitLab compliance features for projects, groups, and instances, see +The compliance tools provided by GitLab help you keep an eye on various aspects of your project, including: + +- [Compliance report](compliance_report/index.md). +- [License approval policies](license_approval_policies.md). +- [License list](license_list.md). +- [License scanning of CycloneDX files](license_scanning_of_cyclonedx_files/index.md). + +For more information on other GitLab compliance features for projects, groups, and instances, see [Compliance features](../../administration/compliance.md). diff --git a/doc/user/compliance/license_approval_policies.md b/doc/user/compliance/license_approval_policies.md index 4e10d01b18e..860c2008021 100644 --- a/doc/user/compliance/license_approval_policies.md +++ b/doc/user/compliance/license_approval_policies.md @@ -5,15 +5,15 @@ group: Security Policies info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- -# License Approval Policies **(ULTIMATE)** +# License approval policies **(ULTIMATE)** > - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/8092) in GitLab 15.9 [with a flag](../../administration/feature_flags.md) named `license_scanning_policies`. > - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/397644) in GitLab 15.11. Feature flag `license_scanning_policies` removed. -License Approval Policies allow you to specify multiple types of criteria that define when approval is required before a merge request can be merged in. +License approval policies allow you to specify multiple types of criteria that define when approval is required before a merge request can be merged in. NOTE: -License Approval Policies are applicable only to [protected](../project/protected_branches.md) target branches. +License approval policies are applicable only to [protected](../project/protected_branches.md) target branches. The following video provides an overview of these policies. diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 76cd90943ae..1ee59dde1f0 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -14574,6 +14574,9 @@ msgstr "" msgid "DependencyProxy|Contains %{count} blobs of images (%{size})" msgstr "" +msgid "DependencyProxy|Copy image path" +msgstr "" + msgid "DependencyProxy|Copy prefix" msgstr "" @@ -14583,6 +14586,9 @@ msgstr "" msgid "DependencyProxy|Dependency Proxy image prefix" msgstr "" +msgid "DependencyProxy|Digest: %{shortDigest}" +msgstr "" + msgid "DependencyProxy|Enable Dependency Proxy" msgstr "" @@ -21044,7 +21050,7 @@ msgstr "" msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"." msgstr "" -msgid "GroupSAML|Some todos may be hidden because your SAML session has expired. Click to reauthenticate with the following groups to view hidden todos:" +msgid "GroupSAML|Some to-do items may be hidden because your SAML session has expired. Select the group’s path to reauthenticate and view the hidden to-do items." msgstr "" msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to %{linkStart}reset it%{linkEnd}." diff --git a/qa/gdk/launch b/qa/gdk/launch index 8ad2ce7e5ad..5d123906f04 100755 --- a/qa/gdk/launch +++ b/qa/gdk/launch @@ -37,4 +37,4 @@ bundle install --jobs=$(nproc) --retry=3 --quiet # Run the tests bundle exec rake "knapsack:download[test]" -bundle exec bin/qa Test::Instance::All http://gdk.test:3000 -- $RSPEC_ARGS || true +bundle exec bin/qa Test::Instance::All http://gdk.test:3000 -- $RSPEC_ARGS diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_in_with_2fa_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_in_with_2fa_spec.rb index cf9282c1149..54fb2aca990 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/login/log_in_with_2fa_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_in_with_2fa_spec.rb @@ -43,7 +43,12 @@ module QA it( 'allows enforcing 2FA via UI and logging in with 2FA', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347931' + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347931', + quarantine: { + type: :bug, + only: { condition: -> { QA::Runtime::Env.super_sidebar_enabled? } }, + issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/409336' + } ) do enforce_two_factor_authentication_on_group(group) diff --git a/qa/qa/specs/helpers/context_selector.rb b/qa/qa/specs/helpers/context_selector.rb index 59ab7c9722e..2527895dc4d 100644 --- a/qa/qa/specs/helpers/context_selector.rb +++ b/qa/qa/specs/helpers/context_selector.rb @@ -10,7 +10,11 @@ module QA def except?(*options) return false if Runtime::Env.ci_job_name.blank? && options.any? { |o| o.is_a?(Hash) && o[:job].present? } - return false if Runtime::Env.ci_project_name.blank? && options.any? { |o| o.is_a?(Hash) && o[:pipeline].present? } + + return false if Runtime::Env.ci_project_name.blank? && options.any? do |o| + o.is_a?(Hash) && o[:pipeline].present? + end + return false if Runtime::Scenario.attributes[:gitlab_address].blank? context_matches?(*options) @@ -34,24 +38,13 @@ module QA opts.merge!(option) if option[:pipeline].present? - return true if Runtime::Env.ci_project_name.blank? - - return pipeline_matches?(option[:pipeline]) - + return evaluate_pipeline_context(option[:pipeline]) elsif option[:job].present? - return true if Runtime::Env.ci_job_name.blank? - - return job_matches?(option[:job]) - + return evaluate_job_context(option[:job]) + elsif !option[:condition].nil? + return evaluate_generic_condition(option[:condition]) elsif option[:subdomain].present? - opts[:subdomain] = case option[:subdomain] - when Array - "(#{option[:subdomain].join("|")})\\." - when Regexp - option[:subdomain] - else - "(#{option[:subdomain]})\\." - end + opts[:subdomain] = evaluate_subdomain_context(option[:subdomain]) end end @@ -60,6 +53,43 @@ module QA alias_method :dot_com?, :context_matches? + private + + def evaluate_pipeline_context(pipeline) + return true if Runtime::Env.ci_project_name.blank? + + pipeline_matches?(pipeline) + end + + def evaluate_job_context(job) + return true if Runtime::Env.ci_job_name.blank? + + job_matches?(job) + end + + def evaluate_generic_condition(condition) + return condition.call if condition.respond_to?(:call) + + condition + end + + def evaluate_subdomain_context(option) + case option + when Array + "(#{option.join('|')})\\." + when Regexp + option + else + "(#{option})\\." + end + end + + def pipeline_matches?(pipeline_to_run_in) + Array(pipeline_to_run_in).any? do |pipeline| + pipeline.to_s.casecmp?(pipeline_from_project_name(Runtime::Env.ci_project_name)) + end + end + def job_matches?(job_patterns) Array(job_patterns).any? do |job| pattern = job.is_a?(Regexp) ? job : Regexp.new(job) @@ -68,10 +98,6 @@ module QA end end - def pipeline_matches?(pipeline_to_run_in) - Array(pipeline_to_run_in).any? { |pipeline| pipeline.to_s.casecmp?(pipeline_from_project_name(Runtime::Env.ci_project_name)) } - end - def pipeline_from_project_name(project_name) project_name.to_s.start_with?('gitlab-qa') ? Runtime::Env.default_branch : project_name end diff --git a/qa/spec/specs/helpers/context_selector_spec.rb b/qa/spec/specs/helpers/context_selector_spec.rb index 9e46933542e..3c09f938684 100644 --- a/qa/spec/specs/helpers/context_selector_spec.rb +++ b/qa/spec/specs/helpers/context_selector_spec.rb @@ -132,6 +132,20 @@ RSpec.describe QA::Specs::Helpers::ContextSelector do end end + context 'with generic condition context matcher' do + it 'matches truthy lambda condition result' do + expect(described_class.context_matches?(condition: -> { true })).to be_truthy + end + + it 'matches truthy condition result' do + expect(described_class.context_matches?(condition: true)).to be_truthy + end + + it 'skips falsey condition result' do + expect(described_class.context_matches?(condition: false)).to be_falsey + end + end + it 'returns false for mismatching' do QA::Runtime::Scenario.define(:gitlab_address, "https://staging.gitlab.com") diff --git a/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js b/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js index 2e7195aa59b..0e383e551d0 100644 --- a/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js +++ b/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js @@ -165,6 +165,7 @@ describe('DependencyProxyApp', () => { it('shows list', () => { expect(findManifestList().props()).toMatchObject({ + dependencyProxyImagePrefix: proxyData().dependencyProxyImagePrefix, manifests: proxyManifests(), pagination: pagination(), }); diff --git a/spec/frontend/packages_and_registries/dependency_proxy/components/manifest_list_spec.js b/spec/frontend/packages_and_registries/dependency_proxy/components/manifest_list_spec.js index 0d8af42bae3..4149f728cd8 100644 --- a/spec/frontend/packages_and_registries/dependency_proxy/components/manifest_list_spec.js +++ b/spec/frontend/packages_and_registries/dependency_proxy/components/manifest_list_spec.js @@ -3,6 +3,7 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import ManifestRow from '~/packages_and_registries/dependency_proxy/components/manifest_row.vue'; import Component from '~/packages_and_registries/dependency_proxy/components/manifests_list.vue'; import { + proxyData, proxyManifests, pagination, } from 'jest/packages_and_registries/dependency_proxy/mock_data'; @@ -11,6 +12,7 @@ describe('Manifests List', () => { let wrapper; const defaultProps = { + dependencyProxyImagePrefix: proxyData().dependencyProxyImagePrefix, manifests: proxyManifests(), pagination: pagination(), loading: false, @@ -42,9 +44,15 @@ describe('Manifests List', () => { it('binds a manifest to each row', () => { createComponent(); - expect(findRows().at(0).props()).toMatchObject({ - manifest: defaultProps.manifests[0], - }); + expect(findRows().at(0).props('manifest')).toBe(defaultProps.manifests[0]); + }); + + it('binds a dependencyProxyImagePrefix to each row', () => { + createComponent(); + + expect(findRows().at(0).props('dependencyProxyImagePrefix')).toBe( + proxyData().dependencyProxyImagePrefix, + ); }); describe('loading', () => { diff --git a/spec/frontend/packages_and_registries/dependency_proxy/components/manifest_row_spec.js b/spec/frontend/packages_and_registries/dependency_proxy/components/manifest_row_spec.js index ace5ce3a58d..5f47a1b8098 100644 --- a/spec/frontend/packages_and_registries/dependency_proxy/components/manifest_row_spec.js +++ b/spec/frontend/packages_and_registries/dependency_proxy/components/manifest_row_spec.js @@ -1,15 +1,17 @@ import { GlSprintf } from '@gitlab/ui'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import ListItem from '~/vue_shared/components/registry/list_item.vue'; import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import Component from '~/packages_and_registries/dependency_proxy/components/manifest_row.vue'; import { MANIFEST_PENDING_DESTRUCTION_STATUS } from '~/packages_and_registries/dependency_proxy/constants'; -import { proxyManifests } from 'jest/packages_and_registries/dependency_proxy/mock_data'; +import { proxyData, proxyManifests } from 'jest/packages_and_registries/dependency_proxy/mock_data'; describe('Manifest Row', () => { let wrapper; const defaultProps = { + dependencyProxyImagePrefix: proxyData().dependencyProxyImagePrefix, manifest: proxyManifests()[0], }; @@ -24,8 +26,10 @@ describe('Manifest Row', () => { }); }; + const findClipboardButton = () => wrapper.findComponent(ClipboardButton); const findListItem = () => wrapper.findComponent(ListItem); const findCachedMessages = () => wrapper.findByTestId('cached-message'); + const findDigest = () => wrapper.findByTestId('manifest-row-short-digest'); const findTimeAgoTooltip = () => wrapper.findComponent(TimeagoTooltip); const findStatus = () => wrapper.findByTestId('status'); @@ -38,12 +42,18 @@ describe('Manifest Row', () => { expect(findListItem().exists()).toBe(true); }); - it('displays the name', () => { - expect(wrapper.text()).toContain('alpine'); + it('displays the name with tag & digest', () => { + expect(wrapper.text()).toContain('alpine:latest'); + expect(findDigest().text()).toMatchInterpolatedText('Digest: 995efde'); }); - it('displays the version', () => { - expect(wrapper.text()).toContain('latest'); + it('displays the name & digest for manifests that contain digest in image name', () => { + createComponent({ + ...defaultProps, + manifest: proxyManifests()[1], + }); + expect(wrapper.text()).toContain('alpine'); + expect(findDigest().text()).toMatchInterpolatedText('Digest: e95efde'); }); it('displays the cached time', () => { @@ -82,4 +92,35 @@ describe('Manifest Row', () => { expect(findStatus().text()).toBe('Scheduled for deletion'); }); }); + + describe('clipboard button', () => { + beforeEach(() => { + createComponent(); + }); + + it('exists', () => { + expect(findClipboardButton().exists()).toBe(true); + }); + + it('passes the correct title prop', () => { + expect(findClipboardButton().attributes('title')).toBe(Component.i18n.copyImagePathTitle); + }); + + it('has the correct copy text when image name contains tag name', () => { + expect(findClipboardButton().attributes('text')).toBe( + 'gdk.test:3000/private-group/dependency_proxy/containers/alpine:latest', + ); + }); + + it('has the correct copy text when image name contains digest', () => { + createComponent({ + ...defaultProps, + manifest: proxyManifests()[1], + }); + + expect(findClipboardButton().attributes('text')).toBe( + 'gdk.test:3000/private-group/dependency_proxy/containers/alpine@sha256:e95efde2e81b21d1ea7066aa77a59298a62a9e9fbb4b77f36c189774ec9b1089', + ); + }); + }); }); diff --git a/spec/frontend/packages_and_registries/dependency_proxy/mock_data.js b/spec/frontend/packages_and_registries/dependency_proxy/mock_data.js index 37c8eb669ba..4d0be0a0c09 100644 --- a/spec/frontend/packages_and_registries/dependency_proxy/mock_data.js +++ b/spec/frontend/packages_and_registries/dependency_proxy/mock_data.js @@ -11,13 +11,15 @@ export const proxyManifests = () => [ { id: 'proxy-1', createdAt: '2021-09-22T09:45:28Z', + digest: 'sha256:995efde2e81b21d1ea7066aa77a59298a62a9e9fbb4b77f36c189774ec9b1089', imageName: 'alpine:latest', status: 'DEFAULT', }, { id: 'proxy-2', createdAt: '2021-09-21T09:45:28Z', - imageName: 'alpine:stable', + digest: 'sha256:e95efde2e81b21d1ea7066aa77a59298a62a9e9fbb4b77f36c189774ec9b1089', + imageName: 'alpine:sha256:e95efde2e81b21d1ea7066aa77a59298a62a9e9fbb4b77f36c189774ec9b1089', status: 'DEFAULT', }, ]; diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index 7b5c3c82b7f..288eedffe1c 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -284,6 +284,9 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do it { is_expected.not_to allow_value(10.5).for(:ci_max_includes) } it { is_expected.not_to allow_value(-1).for(:ci_max_includes) } + it { is_expected.to allow_value([true, false]).for(:remember_me_enabled) } + it { is_expected.not_to allow_value(nil).for(:remember_me_enabled) } + context 'when deactivate_dormant_users is enabled' do before do stub_application_setting(deactivate_dormant_users: true) |